move to 2020 fodler

This commit is contained in:
2021-12-01 11:43:46 +01:00
parent eb251ac1ea
commit c651a48895
33 changed files with 0 additions and 0 deletions

129
2020/.gitignore vendored Normal file
View File

@@ -0,0 +1,129 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

9
2020/README.md Normal file
View File

@@ -0,0 +1,9 @@
# Advent Of Code Solutions
Here are my solutions for the advent of code 2020 🎄🎅
Note that the exact solutions are different for some people.
The main ones are solved in python. 1-4 I also did in Go, but I quickly [did not like it](./learning/Go.md).
`/solutions/:day/*`

15
2020/learning/Go.md Normal file
View File

@@ -0,0 +1,15 @@
# Go Learning
So this will be my first time at writing some go.
Please excuse me for the ranting 😅
## WTFs
- Why is there no bitwise OR for bools?! Please what? XOR == `a != b`, but still...
- `math.Max()` only works with floats. Of course.
- so apparently we don't have optional parameters and defaults. Lol
- go has maps, but no native way of getting keys or values without a loop
## Syntax
- we constantly need to write `:=` which is annoying as hell

View File

@@ -0,0 +1,12 @@
# 1
This is quite simple, just iterate and find.
<details>
<summary>Solutions</summary>
<ol>
<li>1224 and 796 -> 974304</li>
<li>332, 858 and 830 -> 236430480</li>
</ol>
</details>

View File

@@ -0,0 +1,55 @@
package main
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
)
const target uint64 = 2020
func findTwo(list []uint64) {
for _, a := range list {
for _, b := range list {
if a+b == target {
fmt.Printf("The numbers: %v and %v.\tSolution: %v\n", a, b, a*b)
return
}
}
}
}
func findThree(list []uint64) {
for _, a := range list {
for _, b := range list {
for _, c := range list {
if a+b+c == target {
fmt.Printf("The numbers: %v, %v and %v.\tSolution: %v\n", a, b, c, a*b*c)
return
}
}
}
}
}
func main() {
data, err := ioutil.ReadFile("./solutions/1/data.txt")
if err != nil {
fmt.Println("File reading error", err)
return
}
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
intLines := []uint64{}
for _, i := range lines {
num, _ := strconv.ParseUint(i, 10, 64)
intLines = append(intLines, num)
}
// fmt.Println("Result: ", findTwo(intLines))
// fmt.Println("Result: ", findThree(intLines))
findTwo(intLines)
findThree(intLines)
}

View File

@@ -0,0 +1,17 @@
from typing import List
from itertools import product
from os.path import join, dirname
target = 2020
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
numbers: List[int] = list(map(int, f.readlines()))
for a, b in product(numbers, numbers):
if a + b == target:
print(f'The numbers: {a} and {b}.\tSolution: {a*b}')
break
for a, b, c in product(numbers, numbers, numbers):
if a + b + c == target:
print(f'The numbers: {a}, {b} and {c}.\tSolution: {a*b*c}')
break

View File

@@ -0,0 +1,43 @@
# 10
# First
The first one is really easy. Just sort, make the diff and count.
First I take the list and sort it:
```python
[16, 10, 15, 5, 1, 11, 7, 19, 6, 12, 4]
[0, 1, 4, 5, 6, 7, 10, 11, 12, 15, 16, 19, 22] # Sorted and added the wall plug (0) and the phone (biggest + 3)
[1, 3, 1, 1, 1, 3, 1, 1, 3, 1, 3, 3] # The size of each step
```
Now we can simply count how many `1` and `3` there are with `l.count(1)`.
## Second
This is where it gets tricky.
First lets find all the consecutive `1`s ad only they can be removed. If we have more than 1 consecutive `1` we can remove one of it. However we need to be careful not to remove to many or the step will be higher than `3` and the chain breaks.
```python
[1, 1, 1, 1] # We can transform this example by adding 2 numbers together and "joining" them.
[1, 2, 1] # Valid
[1, 1, 2] # Valid
[1, 3] # Valid
[4] # Invalid because we can jump a max of 3 steps at a time.
```
Now we could iterate but I wanted to find a formula. Not sure this is correct but here we go.
Basically we take the length of the consecutive `1` and compute `2**(l-1)` to get all possible combinations.
Now we need to subtract the possible `4` which can only be achieved if we have at least 4 numbers -> `floor(l/4)`
For a grand total of `2**(l-1) - floor(l/4)`
<details>
<summary>Solutions</summary>
<ol>
<li>2475</li>
<li>442136281481216</li>
</ol>
</details>

View File

@@ -0,0 +1,54 @@
from os.path import join, dirname
from typing import List, Optional, Set, Tuple
from itertools import combinations, count
from math import floor, prod
def parse(s: str) -> List[int]:
numbers: List[int] = sorted(map(int, s.strip().split('\n')))
numbers.insert(0, 0) # The wall
numbers.append(numbers[-1] + 3) # Phone itself
return numbers
def diff(l: List[int]) -> List[int]:
return [
l[x] - l[x-1]
for x in range(1, len(l))
]
def calc(d: List[int]) -> int:
one = d.count(1)
three = d.count(3)
return one * three
def find_valid_permutations(d: List[int]) -> int:
i = 0
l = len(d)
slices: List[int] = []
while i < l:
if d[i] != 3:
try:
n = d.index(3, i + 1) # Find the next three
diff = n - i
if diff > 1:
slices.append(diff)
i = n
continue
except:
pass
i += 1
return prod([
2**(s-1) - floor(s/4)
for s in slices
])
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
numbers: List[int] = parse(f.read())
d = diff(numbers)
print(calc(d))
print(find_valid_permutations(d))

View File

@@ -0,0 +1,12 @@
# 11
Today: Game of life, but a bit different :)
I lost so much time because I missed that in the second part we now require 5 instead of 4 occupied seats 😩
<details>
<summary>Solutions</summary>
<ol>
<li>2296</li>
<li>2089</li>
</ol>
</details>

View File

@@ -0,0 +1,99 @@
from os.path import join, dirname
from typing import List, Optional
from itertools import product
from copy import deepcopy
TSeats = List[List[Optional[bool]]]
mapping = {
'.': None,
'#': True,
'L': False
}
inv = {v: k for k, v in mapping.items()}
class Seats:
p = [-1, 0, 1]
def __init__(self, plan: str, alt: bool = False) -> None:
self.seats: TSeats = [
[
mapping[seat]
for seat in row
]
for row in plan.strip().split('\n')
]
self.max_x = len(self.seats[0])
self.max_y = len(self.seats)
self.alt = alt
def __str__(self) -> str:
return '\n'.join([
''.join([inv[seat] for seat in row])
for row in self.seats
])
def find_next_in_direction(self, y: int, x: int, dy: int, dx: int) -> Optional[bool]:
y += dy
x += dx
while 0 <= x < self.max_x and 0 <= y < self.max_y:
cur = self.seats[y][x]
if cur is not None:
return cur
y += dy
x += dx
return None
def get_occupied(self, y: int, x: int,) -> int:
occupied = 0
for dx, dy in product(self.p, self.p):
if dx == 0 and dy == 0:
continue
if self.alt and self.find_next_in_direction(y, x, dy, dx) == True:
occupied += 1
else:
dx += x
dy += y
if 0 <= dx < self.max_x and 0 <= dy < self.max_y and self.seats[dy][dx]:
occupied += 1
return occupied
def iteration(self) -> int:
changed = 0
future: TSeats = deepcopy(self.seats)
required_to_leave = 4 if self.alt else 3
for y, x in product(range(self.max_y), range(self.max_x)):
current = self.seats[y][x]
if current == None:
continue
occupied = self.get_occupied(y, x)
if (current == True and occupied > required_to_leave) or (current == False and occupied == 0):
future[y][x] = not current
changed += 1
self.seats = future
return changed
def count_occupied(self) -> int:
return sum([
sum([
1 if seat == True else 0
for seat in row
])
for row in self.seats
])
def find_equilibrium(self) -> int:
while self.iteration() > 0:
pass
return self.count_occupied()
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
txt = f.read()
seats = Seats(txt)
print(seats.find_equilibrium())
seats = Seats(txt, True)
print(seats.find_equilibrium())

View File

@@ -0,0 +1,13 @@
# 12
Let's navigate!
Reminded me that I had to refresh trigonometry a bit xD
<details>
<summary>Solutions</summary>
<ol>
<li>796</li>
<li>39446</li>
</ol>
</details>

View File

@@ -0,0 +1,83 @@
from os.path import join, dirname
from typing import List, Optional
from math import cos, sin, radians, atan2, degrees, sqrt
directions = {
'N': 0,
'E': 90,
'S': 180,
'W': 270,
}
class Ship:
def __init__(self, waypoint=False) -> None:
self.x: float = 0
self.y: float = 0
self.d: int = directions['E']
self.waypoint = waypoint
self.wx: float = 10
self.wy: float = 1
@property
def distance(self) -> int:
return round(abs(self.x) + abs(self.y))
def __str__(self) -> str:
return f'⛴ X={round(self.x)} Y={round(self.y)} 𒎓={round(self.d)}\t🏴‍☠️ X={round(self.wx)} Y={round(self.wy)}'
def navigate(self, amount: int, degree: Optional[int] = None) -> None:
if degree == None:
if self.waypoint:
self.x += self.wx * amount
self.y += self.wy * amount
return
degree = self.d
dx = amount * sin(radians(degree))
dy = amount * cos(radians(degree))
if self.waypoint:
self.wx += dx
self.wy += dy
else:
self.x += dx
self.y += dy
def move(self, instruction: str) -> None:
cmd: str = instruction[0]
amount: int = int(instruction[1:])
if cmd in directions:
self.navigate(amount, degree=directions[cmd])
elif cmd == 'F':
self.navigate(amount)
else:
diff = amount if cmd == 'R' else -amount
if self.waypoint:
size = sqrt(self.wx**2 + self.wy**2)
d = degrees(atan2(self.wy, self.wx))
d -= diff
self.wx = size * cos(radians(d))
self.wy = size * sin(radians(d))
else:
self.d = (self.d + diff) % 360
def follow(self, file: str) -> None:
instructions = file.strip().split('\n')
for instruction in instructions:
self.move(instruction)
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
txt = f.read()
ship = Ship()
ship.follow(txt)
print(ship)
print(ship.distance)
ship = Ship(waypoint=True)
ship.follow(txt)
print(ship)
print(ship.distance)

View File

@@ -0,0 +1,12 @@
# 13
The second part one was really interesting. While my solution is correct, it takes way to much time in the data set.
I started by checking what is the biggest ID number and check each location.
<details>
<summary>Solutions</summary>
<ol>
<li>156</li>
<li>404517869995362</li>
</ol>
</details>

View File

@@ -0,0 +1,76 @@
from os.path import join, dirname
from typing import List, Tuple
from math import ceil, floor
class Station:
def __init__(self, txt: str) -> None:
arrival, busses = txt.strip().split('\n')
self.arrival = int(arrival)
self.busses = [
int(bus)
for bus in busses.replace('x', '0').split(',')
]
@staticmethod
def get_next_for_id(id: int, arrival: int) -> int:
return id * ceil(arrival / id)
def find_next(self) -> Tuple[int, int]:
arrivals: List[Tuple[int, int]] = []
for bus in self.busses:
if bus == 0:
continue
arrivals.append((bus, self.get_next_for_id(bus, self.arrival)))
return min(arrivals, key=lambda x: x[1])
def get_flag(self) -> int:
id, arrives = self.find_next()
return id * (arrives - self.arrival)
def contest(self, offset: int = 1) -> int:
# Prepare
highest = max(self.busses)
highest_i = self.busses.index(highest)
others = [
(bus, i - highest_i)
for i, bus in enumerate(self.busses)
if bus != 0 and i != highest_i
]
others.sort(key=lambda x: x[0], reverse=True)
# Compute
i: int = max(1, floor(offset / highest))
while True:
x = highest * i
error = False
for bus, diff in others:
dx = x + diff
if dx != self.get_next_for_id(bus, dx):
error = True
break
if not error:
return x - highest_i
i += 1
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
# Some "testing"
all = {
'17,x,13,19': 3417,
'67,7,59,61': 754018,
'67,x,7,59,61': 779210,
'67,7,x,59,61': 1261476,
'1789,37,47,1889': 1202161486,
}
for busses, expected in all.items():
station = Station('69\n' + busses)
print(expected, expected == station.contest())
txt = f.read()
station = Station(txt)
print(station.get_flag())
print(station.contest(offset=10**14))

View File

@@ -0,0 +1,14 @@
# 2
For the first we can simply count the occurrences and see if they are between the accepted values.
Just some simple parsing.
The second one is similar, but we can be more efficient if we XOR the first and second position.
<details>
<summary>Solutions</summary>
<ol>
<li>548</li>
<li>502</li>
</ol>
</details>

View File

@@ -0,0 +1,77 @@
package main
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
)
type sRow struct {
min, max int
char, password string
}
func parse(data []byte) []sRow {
parsed := []sRow{}
for _, row := range strings.Split(strings.TrimSpace(string(data)), "\n") {
s0 := strings.Split(row, ":")
rule := strings.TrimSpace(s0[0])
password := strings.TrimSpace(s0[1])
s1 := strings.Split(rule, " ")
minMax := strings.TrimSpace(s1[0])
char := strings.TrimSpace(s1[1])
s2 := strings.Split(minMax, "-")
min, _ := strconv.Atoi(strings.TrimSpace(s2[0]))
max, _ := strconv.Atoi(strings.TrimSpace(s2[1]))
r := sRow{
min: min,
max: max,
char: char,
password: password,
}
parsed = append(parsed, r)
}
return parsed
}
func validSimple(rows []sRow) int {
valid := 0
for _, row := range rows {
count := strings.Count(row.password, row.char)
if row.min <= count && count <= row.max {
valid++
}
}
return valid
}
func validComplex(rows []sRow) int {
valid := 0
for _, row := range rows {
l := len(row.password)
min := row.min - 1
max := row.max - 1
if min >= l || max >= l {
continue
}
r := []rune(row.password)
a := string(r[min]) == row.char
b := string(r[max]) == row.char
if a != b {
valid++
}
}
return valid
}
func main() {
data, _ := ioutil.ReadFile("./solutions/2/data.txt")
rows := parse(data)
simple := validSimple(rows)
fmt.Println(simple)
complex := validComplex(rows)
fmt.Println(complex)
}

View File

@@ -0,0 +1,27 @@
from os.path import join, dirname
def checkRow(row: str, alternative=False) -> bool:
rule, password = map(lambda s: s.strip(), row.split(':'))
amount, char = rule.split(' ')
minimum, maximum = map(int, amount.split('-'))
if alternative:
return (password[minimum - 1] == char) ^ (password[maximum - 1] == char)
else:
occurrences = password.count(char)
return minimum <= occurrences <= maximum
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
valid = 0
valid_alt = 0
rows = list(f.read().strip().split('\n'))
for row in rows:
if(checkRow(row)):
valid += 1
if(checkRow(row, alternative=True)):
valid_alt += 1
print(f'Found {valid} valid passwords.')
print('Policy changed...')
print(f'Found {valid_alt} valid passwords.')

View File

@@ -0,0 +1,16 @@
# 3
We can simply parse the forest as an array of strings.
The trees repeat infinitely to the right, this screams for a good mod.
This means: `char = row[x % len(row)]`. No complex logic needed
For the second one we simply automate the process and sum up the total.
We can simply encode the coordinates as a function of the index we are current at.
<details>
<summary>Solutions</summary>
<ol>
<li>252</li>
<li>57 * 252 * 64 * 66 * 43 = 2608962048</li>
</ol>
</details>

View File

@@ -0,0 +1,66 @@
package main
import (
"fmt"
"io/ioutil"
"strings"
)
const high = rune('#')
type sForest struct {
data [][]bool
height int
width int
}
func (f sForest) at(y, x int) bool {
if y >= f.height {
return false
}
return f.data[y][x%f.width]
}
func (f sForest) traverse(y, x int) int {
trees := 0
for dy := 0; dy <= f.height; dy++ {
tree := f.at(dy*y, dy*x)
if tree {
trees++
}
}
return trees
}
func main() {
data, _ := ioutil.ReadFile("./solutions/3/data.txt")
rows := strings.Split(strings.TrimSpace(string(data)), "\n")
height, width := len(rows), len(rows[0])
d := make([][]bool, height)
for y, row := range rows {
d[y] = make([]bool, width)
for x, char := range []rune(row) {
d[y][x] = char == high
}
}
forest := sForest{
data: d,
height: height,
width: width,
}
fmt.Println("Simple: ", forest.traverse(1, 3))
trees11 := forest.traverse(1, 1)
trees13 := forest.traverse(1, 3)
trees15 := forest.traverse(1, 5)
trees17 := forest.traverse(1, 7)
trees21 := forest.traverse(2, 1)
fmt.Println(trees11, trees13, trees15, trees17, trees21)
fmt.Println(trees11 * trees13 * trees15 * trees17 * trees21)
}

View File

@@ -0,0 +1,50 @@
from typing import Dict
from os.path import join, dirname
from functools import reduce
class Forest():
def __init__(self, text: str) -> None:
self.text = text.strip().split('\n')
@property
def height(self) -> int:
return len(self.text)
def is_tree_at(self, y: int, x: int) -> bool:
if y > self.height:
return False
row = self.text[y]
return row[x % len(row)] == '#'
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
forest = Forest(f.read())
# 1
trees: int = 0
for y in range(forest.height):
is_tree: bool = forest.is_tree_at(y, y*3)
if is_tree:
trees += 1
print(f'Result Simple: {trees}')
# 2
all: Dict[str, int] = {
'11': 0,
'13': 0,
'15': 0,
'17': 0,
'21': 0,
}
for i in range(forest.height):
for key, value in all.items():
dy, dx = map(int, list(key))
y = i * dy
x = i * dx
if forest.is_tree_at(y, x):
all[key] += 1
total = reduce((lambda x, y: x * y), all.values())
print(f'Result Combined: {list(all.values())} = {total}')

View File

@@ -0,0 +1,14 @@
# 4
This one was a lot of parsing, but nothing regexp can't do.
The first is quite straight forward, just check that all but `cid` are present.
The second was a bit of validation for each field, but again some simple regexp and number checking and the job is done 🙂
<details>
<summary>Solutions</summary>
<ol>
<li>206</li>
<li>123</li>
</ol>
</details>

103
2020/solutions/4/go/main.go Normal file
View File

@@ -0,0 +1,103 @@
package main
import (
"fmt"
"io/ioutil"
"regexp"
"strconv"
"strings"
k "github.com/wesovilabs/koazee"
)
type tPassport = map[string]string
func stringBetween(s string, min, max int) bool {
num, _ := strconv.Atoi(s)
return min <= num && num <= max
}
func verifyPassport(passport tPassport) (bool, bool) {
requiredKeys := k.StreamOf([]string{"byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"})
eyeColors := k.StreamOf([]string{"amb", "blu", "brn", "gry", "grn", "hzl", "oth"})
// Simple
counted := 0
for k := range passport {
if k == "cid" {
continue
}
included, _ := requiredKeys.Contains(k)
if !included {
return false, false
}
counted++
}
if counted < 7 {
return false, false
}
// Complex
if !stringBetween(passport["byr"], 1920, 2002) || !stringBetween(passport["iyr"], 2010, 2020) || !stringBetween(passport["eyr"], 2020, 2030) {
return true, false
}
tmp := []rune(passport["hgt"])
hgtLen := len(tmp)
hgt, _ := strconv.Atoi(string(tmp[0 : hgtLen-2]))
unit := string(tmp[hgtLen-2:])
if unit != "cm" && unit != "in" {
return true, false
}
if unit == "cm" && (hgt < 150 || hgt > 193) {
return true, false
}
if unit == "in" && (hgt < 59 || hgt > 76) {
return true, false
}
if !regexp.MustCompile(`^#[\dabdcdef]{6}$`).MatchString(passport["hcl"]) {
return true, false
}
if !regexp.MustCompile(`^\d{9}$`).MatchString(passport["pid"]) {
return true, false
}
ecl, _ := eyeColors.Contains(passport["ecl"])
if !ecl {
return true, false
}
return true, true
}
func main() {
data, _ := ioutil.ReadFile("./solutions/4/data.txt")
passportsRaw := strings.Split(strings.TrimSpace(string(data)), "\n\n")
passports := []tPassport{}
re := regexp.MustCompile(`\n|\s`)
for _, passportRaw := range passportsRaw {
passport := tPassport{}
entries := re.Split(passportRaw, -1)
for _, entry := range entries {
split := strings.Split(entry, ":")
passport[split[0]] = split[1]
}
passports = append(passports, passport)
}
validSimple := 0
validComplex := 0
for _, passport := range passports {
simple, complex := verifyPassport(passport)
if simple {
validSimple++
}
if complex {
validComplex++
}
}
fmt.Println("Simple Validation:\t", validSimple)
fmt.Println("Extended Validation:\t", validComplex)
}

View File

@@ -0,0 +1,52 @@
from os.path import join, dirname
import re
def validate_chunk(chunk, extended=False):
parts = re.split(' |\n', chunk.strip())
password = dict(map(lambda p: p.split(":"), parts))
required = ['byr', 'iyr', 'eyr', 'hgt', 'hcl', 'ecl', 'pid']
if not all(item in password.keys() for item in required):
return False
if not extended:
return True
if not 1920 <= int(password['byr']) <= 2002:
return False
if not 2010 <= int(password['iyr']) <= 2020:
return False
if not 2020 <= int(password['eyr']) <= 2030:
return False
tmp = password['hgt']
hgt = int(tmp[:-2])
unit = tmp[-2:]
if not unit in ['cm', 'in']:
return False
if unit == 'cm' and not 150 <= hgt <= 193:
return False
if unit == 'in' and not 59 <= hgt <= 76:
return False
if not re.match(r'^#[\dabcdef]{6}$', password['hcl']):
return False
if not re.match(r'^\d{9}$', password['pid']):
return False
if password['ecl'] not in ['amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth']:
return False
return True
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
chunks = re.split('\n\n+', f.read().strip())
total_simple = 0
total_extended = 0
for chunk in chunks:
total_simple += int(validate_chunk(chunk))
total_extended += int(validate_chunk(chunk, extended=True))
print(f'Simple Validation:\t{total_simple}')
print(f'Extended Validation:\t{total_extended}')

View File

@@ -0,0 +1,13 @@
# 5
For the first one we treat the codes as 2 binary codes. One 7 and the other 3 long.
The second tripped me up as I was returning the binary encoded ticket number but AOC was expecting the seat ID. My bad, took me long to get it. Thanks to @tcq1
<details>
<summary>Solutions</summary>
<ol>
<li>998</li>
<li>84, 4 -> 676</li>
</ol>
</details>

View File

@@ -0,0 +1,51 @@
from os.path import join, dirname
from itertools import product
from typing import Tuple
def from_binary(code: str, high: str) -> int:
return int(''.join([
'1' if char == high else '0'
for char in code
]), 2)
def read_seat(seat) -> Tuple[int, int]:
row_raw = seat[:-3]
column_raw = seat[-3:]
row = from_binary(row_raw, 'B')
column = from_binary(column_raw, 'R')
return row, column
def seat_code(row: int, column: int) -> int:
return row * 8 + column
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
seats = f.read().strip().split('\n')
free = list(product(range(4, 126), range(8)))
maximum = 0
for seat in seats:
row, column = read_seat(seat)
m = row * 8 + column
maximum = max(maximum, m)
free.remove((row, column))
print(f"Highers ID:\t{maximum}")
# Find the remaining seat
row_max = 0
row_min = 128
for row, _ in free:
row_max = max(row_max, row)
row_min = min(row_min, row)
remaining = [
(row, column)
for row, column in free
if row >= row_min + 1 and row <= row_max - 2
][0]
my_ticket = seat_code(*remaining)
print(f"My Ticket:\t{my_ticket}")

View File

@@ -0,0 +1,13 @@
# 6
Basically I own this one to the built in `set()` of python.
The first is an union, the second an intersection. Did not know they existed
<details>
<summary>Solutions</summary>
<ol>
<li>6809</li>
<li>3394</li>
</ol>
</details>

View File

@@ -0,0 +1,28 @@
from os.path import join, dirname
from itertools import product
from typing import List, Set, Tuple
from functools import reduce
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
groups = f.read().strip().split('\n\n')
at_least_one: List[int] = []
everyone: List[int] = []
for group in groups:
answers: Set[str] = set()
combined = None
for answer in group.split('\n'):
answer = answer.strip()
as_set = set(list(answer))
answers = answers.union(as_set)
combined = as_set if combined == None else combined.intersection(
as_set)
at_least_one.append(len(answers))
everyone.append(len(combined))
# print(single)
# print(reduce(lambda a, b: a.intersection(b), single))
print(f'At least one person: {sum(at_least_one)}')
print(f'Everyone: {sum(everyone)}')

View File

@@ -0,0 +1,15 @@
# 7
This one was maybe the coolest yet! Fixed-point iteration and some recursion. Amazing :)
For the first part we iterate as long as we don't find any enclosing bags anymore. This can build long chains.
The second we recurse down the bag chain und sum it up recursively.
<details>
<summary>Solutions</summary>
<ol>
<li>164</li>
<li>7872</li>
</ol>
</details>

View File

@@ -0,0 +1,70 @@
from os.path import join, dirname
from typing import Dict, List, Set, Tuple
import re
TRules = Dict[str, Dict[str, int]]
def split_trim(input: str, split: str) -> List[str]:
return list(map(lambda s: s.strip(), input.split(split)))
def extract_inner(input: str) -> Tuple[int, str]:
parts = input.split(' ')
amount = int(parts[0])
color = ' '.join(parts[1:-1])
return amount, color
def parse_rules(rules: str) -> TRules:
d: TRules = {}
for rule in rules.strip().split('\n'):
outer, inner = split_trim(rule, 'contain')
outer = re.sub(r'bags?', '', outer).strip()
d[outer] = {
color: amount
for amount, color in [
extract_inner(i)
for i in split_trim(inner, ',')
if 'no other bag' not in i # Also matches "bags"
]
}
return d
def find_enclosing_bags(rules: TRules, color: str) -> Set[str]:
colors: Set[str] = set()
stack: Set[str] = set([color])
while len(stack):
for item in list(stack):
stack.remove(item)
for contains, enclosing in rules.items():
if item in enclosing:
if contains not in colors:
stack.add(contains)
colors.add(contains)
return colors
def count_containing(rules: TRules, color: str) -> int:
children = rules[color]
if not children:
return 0
return sum([
amount + amount * count_containing(rules, clr)
for clr, amount in children.items()
])
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
rules = parse_rules(f.read())
color = 'shiny gold'
first = len(find_enclosing_bags(rules, color))
print(f'We can pack the {color} into {first} bags')
second = count_containing(rules, color)
print(f'We need to put {second} bags into {color}')

View File

@@ -0,0 +1,15 @@
# 8
Keep getting better! Today we write a little state machine, love it.
So firstly we remember what `oc` (Operation Counter) we have already visited and if so we simply stop and return the accumulator.
For I basically run the code many times, inverting `nop` to `jmp` and vice versa until i found a working instruction set.
<details>
<summary>Solutions</summary>
<ol>
<li>1087</li>
<li>780</li>
</ol>
</details>

View File

@@ -0,0 +1,63 @@
from os.path import join, dirname
from typing import List, Optional, Set, Tuple
Instructions = List[Tuple[str, int]]
class VM:
def __init__(self, code: str) -> None:
instructionsRaw = code.strip().split('\n')
self.acc = 0
self.oc = 0
self.invert: Optional[int] = None
self.instructions: Instructions = []
for instruction in instructionsRaw:
op, value = instruction.split(' ')
self.instructions.append((op, int(value)))
def reset(self):
self.acc = 0
self.oc = 0
def exec(self):
op, value = self.instructions[self.oc]
if self.oc == self.invert:
op = 'jmp' if op == 'nop' else 'nop'
if op == 'nop':
self.oc += 1
elif op == 'acc':
self.acc += value
self.oc += 1
elif op == 'jmp':
self.oc += value
def run(self) -> Tuple[int, bool]:
self.reset()
already_visited: Set[int] = set()
m = len(self.instructions)
while True:
if self.oc in already_visited:
return (self.acc, True)
if not self.oc < m:
return (self.acc, False)
already_visited.add(self.oc)
self.exec()
def fix(self):
for i, instruction in enumerate(self.instructions):
op, _ = instruction
if op == 'nop' or op == 'jmp':
self.invert = i
acc, error = self.run()
if not error:
return acc
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
vm = VM(f.read())
acc, err = vm.run()
print(acc)
print(vm.fix())

View File

@@ -0,0 +1,11 @@
# 9
Again the python std lib saved me some lines of code. `itertools` to the rescue and basically we are done.
<details>
<summary>Solutions</summary>
<ol>
<li>23278925</li>
<li>4011064</li>
</ol>
</details>

View File

@@ -0,0 +1,50 @@
from os.path import join, dirname
from typing import List, Optional, Set, Tuple
from itertools import combinations
class XMAS:
def __init__(self, data: str, size: int) -> None:
self.size: int = size
self.position: int = size
self.buffer: List[int] = [
int(x)
for x in data.strip().split('\n')
]
def check_next(self) -> bool:
possible = [
a + b
for a, b in combinations(self.buffer[self.position - self.size: self.position], 2)
]
return self.buffer[self.position] in possible
def find_first_invalid(self) -> int:
l = len(self.buffer)
while self.position < l:
valid = self.check_next()
if not valid:
return self.buffer[self.position]
self.position += 1
raise Exception
def find_slice(self, target: int):
l = len(self.buffer)
for n in range(l):
for m in range(n, l):
slice = self.buffer[n: m]
if sum(slice) == target:
return min(slice) + max(slice)
raise Exception
data = join(dirname(__file__), '../data.txt')
with open(data) as f:
xmas = XMAS(f.read(), 25)
first_invalid = xmas.find_first_invalid()
print(first_invalid)
solution = xmas.find_slice(first_invalid)
print(solution)