├── .gitattributes ├── .gitignore ├── BENCHMARKS-M1.md ├── BENCHMARKS.md ├── LICENSE ├── README.md ├── agg.py ├── array.mojo ├── benchy.py ├── bugs ├── bufferfun.mojo ├── lifetime.mojo ├── lifetime2.mojo ├── memset.mojo ├── modulus.mojo ├── paracrash.mojo └── strreplace.mojo ├── day00.mojo ├── day01.mojo ├── day01.py ├── day02.mojo ├── day02.py ├── day03.mojo ├── day03.py ├── day04.mojo ├── day04.py ├── day05.mojo ├── day05.py ├── day06.mojo ├── day06.py ├── day07.mojo ├── day07.py ├── day08.mojo ├── day08.py ├── day09.mojo ├── day09.py ├── day09sym.mojo ├── day09sym.py ├── day10.mojo ├── day10.py ├── day11.mojo ├── day11.py ├── day11hv.mojo ├── day11hv.py ├── day12.mojo ├── day12.py ├── day13.mojo ├── day13.py ├── day14.mojo ├── day14.py ├── day15.mojo ├── day15.py ├── day16.cc ├── day16.mojo ├── day16.py ├── day17.mojo ├── day17.py ├── day18.mojo ├── day18.py ├── day19.mojo ├── day19.py ├── day19codegen.py ├── day19gcc.py ├── day19lambda.py ├── day20.mojo ├── day20.py ├── day21.mojo ├── day21.py ├── day22.mojo ├── day22.py ├── day23.mojo ├── day23.py ├── day24.mojo ├── day24.py ├── day25.mojo ├── day25.py ├── mojoproject.toml ├── parser.mojo ├── pyproject.toml ├── quicksort.mojo ├── run.sh ├── vis ├── box1.png ├── box1l.png ├── box2.png ├── box2l.png ├── box3.png ├── box3l.png ├── common.py ├── day02.py ├── day03.py ├── day04.py ├── day05.py ├── day06.py ├── day07.py ├── day08.py ├── day09.py ├── day10.py ├── day11.py ├── day12.py ├── day13.py ├── day14.py ├── day15.py ├── day16.py ├── day17.py ├── day18.py ├── day19.py ├── day20.py ├── day21.py ├── day22.py ├── day23.py ├── day24.py ├── day25.py └── deflektor.py └── wrappers.mojo /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub syntax highlighting 2 | pixi.lock linguist-language=YAML linguist-generated=true 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | __pycache__/ 3 | /tmp/ 4 | *.py[cod] 5 | *.mp4 6 | *.jpg 7 | *.ttf 8 | *.txt 9 | *.dSYM 10 | tempCodeRunnerFile.* 11 | nanobench.h 12 | /day19.cc 13 | /day19 14 | /day16 15 | 16 | # pixi environments 17 | .pixi 18 | *.egg-info 19 | # magic environments 20 | .magic 21 | magic.lock 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | p88h's Advent of Code 2023 2 | ========================== 3 | 4 | This is a repository containing solutions for the 2023 Advent of Code (https://adventofcode.com/). 5 | 6 | This years language of choice is 'Mojo vs Python', that is, all solutions are implemented in both languages 7 | and cross-benchmarked between Python/PyPy and Mojo implementations. 8 | 9 | You can read a bit more about my experience with Mojo on [Medium](https://medium.com/@p88h/advent-of-mojo-6d6d0d00761b) 10 | 11 | If you would like to run this yourself, you can do it like so, from the top-level directory of the project: 12 | 13 | ``` 14 | $ mojo run dayXY.mojo 15 | $ {python3|pypy3} dayXY.py 16 | ``` 17 | 18 | This will run the selected day. You need to paste the contents of the specific days input into dayXY.txt. 19 | The final newline should be stripped from the inputs (sorry about this, I actually cut and paste the inputs which doesn't store the final newline). 20 | 21 | Only the standard library is required for Mojo solutions. Python solutions may require numpy. 22 | 23 | Benchmarking 24 | ============ 25 | 26 | Each days solution automatically runs 27 | Benchmarks from my system are also included in [BENCHMARKS.md](BENCHMARKS.md) and [BENCHMARKS-M1.md](BENCHMARKS-M1.md) for a MacBook Pro w/M1 chip. 28 | You can run each day across all platforms and generate the benchmark summary this way: 29 | 30 | ``` 31 | $ for day in `seq 01 25`; do ./run.sh $day; done 32 | $ python3 agg.py 33 | ``` 34 | 35 | This will create temporary `all_{python3|pypy3|mojo}_{pc|mac}.txt` files. Only PC (=anything other than a Mac) and Mac are supported by the `run.sh` script. 36 | 37 | Visualisations 38 | ============== 39 | 40 | The vis/ directory contains visualisations code - written in Pythin, with PyGame. You can run each one directly: 41 | 42 | 43 | ``` 44 | $ python3 vis/dayXY.py 45 | ``` 46 | 47 | Running these requires the pygame package, and you will also need to download [Inconsolata-SemiBold.ttf](https://github.com/googlefonts/Inconsolata/raw/main/fonts/ttf/Inconsolata-SemiBold.ttf) from [Google Fonts](https://fonts.google.com/specimen/Inconsolata) and place it in the `vis` folder. 48 | 49 | Visualisations can also write video to `dayXX.mp4` files automatically, if you pass additional '-r' parameter. 50 | 51 | ``` 52 | $ python3 vis/dayXY.py -r 53 | ``` 54 | 55 | This requires ffmpeg binary installed. You will need to create a 'tmp' directory for the frames. 56 | 57 | Most videos are also published to [YouTube](https://www.youtube.com/playlist?list=PLgRrl8I0Q16_XH4iOGfXA5uaVDlfuyYVC) 58 | 59 | Copyright disclaimer 60 | ==================== 61 | 62 | Licensed under the Apache License, Version 2.0 (the "License"); 63 | you may not use these files except in compliance with the License. 64 | You may obtain a copy of the License at 65 | 66 | https://www.apache.org/licenses/LICENSE-2.0 67 | 68 | Unless required by applicable law or agreed to in writing, software 69 | distributed under the License is distributed on an "AS IS" BASIS, 70 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 71 | See the License for the specific language governing permissions and 72 | limitations under the License. 73 | -------------------------------------------------------------------------------- /agg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import glob 4 | import math 5 | 6 | order={"python3": 0, "pypy3": 1, "mojo": 2, "mojo_parallel": 3} 7 | mat = {} 8 | 9 | for f in glob.glob("all_*.txt"): 10 | col = f.split("_")[1] 11 | lines = open(f).read().split("\n") 12 | cur = None 13 | for line in lines: 14 | line = line.replace(":","").replace(" parallel","_parallel") 15 | suf = "" 16 | if line.startswith("day"): 17 | cur = line.split(".")[0] 18 | if line.startswith("par"): 19 | (sub, time, unit) = line.split()[0:3] 20 | if sub.endswith("parallel"): 21 | sub = sub[:-9] 22 | suf = "_parallel" 23 | row = cur+"_"+sub 24 | if row not in mat: 25 | mat[row] = [math.nan] * 8 26 | mat[row][7]=" " 27 | mat[row][order[col+suf]*2]=float(time) 28 | mat[row][order[col+suf]*2+1]=unit 29 | 30 | 31 | sums = [0] * 4 32 | for row in sorted(mat): 33 | label = row.replace("day","Day").replace("_", " ") 34 | raw = [] 35 | for i in range(4): 36 | if math.isnan(mat[row][2*i]): 37 | sums[i] += raw[2] 38 | continue 39 | # convert everything to microseconds 40 | unit = mat[row][2*i+1] 41 | if unit == "μs": 42 | raw.append(mat[row][2*i]) 43 | elif unit == "ms": 44 | raw.append(mat[row][2*i]*1000) 45 | elif unit == "s": 46 | raw.append(mat[row][2*i]*1000000) 47 | elif unit == "ns": 48 | raw.append(mat[row][2*i]/1000) 49 | else: 50 | print("BORK BORK BORK:", label, row, unit, mat[row][2*i]) 51 | exit(0) 52 | sums[i] += raw[i] 53 | if len(raw) > 3 and raw[3] < raw[2]: 54 | raw[2] = raw[3] 55 | r1 = int(raw[1] / raw[2]) 56 | r2 = int(raw[0] / raw[2]) 57 | print("{0:<16s}{1:01.2f} {2:<2s} {3:01.2f} {4:<2s} {5:01.2f} {6:<2s} {7:01.2f} {8:<2s} * {9} - {10}".format(label,*mat[row],r1,r2)) 58 | #print(label, mat[row]) 59 | 60 | tr1 = int(sums[1] / sums[3]) 61 | tr2 = int(sums[0] / sums[3]) 62 | totf = [] 63 | for i in range(4): 64 | totf.append(sums[i] / 1000) 65 | totf.append("ms") 66 | #totf.append(math.nan) 67 | #totf.append(" ") 68 | 69 | print() 70 | print("{0:<13s}{1:>7.2f} {2} {3:>6.2f} {4} {5:>5.2f} {6} {7:5.2f} {8} * {9} - {10}".format("Total",*totf,tr1,tr2)) 71 | -------------------------------------------------------------------------------- /array.mojo: -------------------------------------------------------------------------------- 1 | from memory.unsafe_pointer import UnsafePointer 2 | from sys.info import simdwidthof, sizeof 3 | 4 | @value 5 | struct Array[AType: DType](CollectionElement): 6 | """ 7 | Simple data array with fast clear and initialization. 8 | """ 9 | alias simd_width = simdwidthof[AType]() 10 | var data: UnsafePointer[SIMD[AType, 1]] 11 | var size: Int 12 | var dynamic_size: Int 13 | 14 | fn __init__(inout self, size: Int, value: SIMD[AType, 1] = 0): 15 | pad = size + (Self.simd_width - 1) & ~(Self.simd_width - 1) 16 | # print("pad", size, "to", pad, "align", Self.simd_width) 17 | self.data = UnsafePointer[SIMD[AType, 1], alignment=Self.simd_width].alloc(pad) 18 | self.size = size 19 | self.dynamic_size = size 20 | self.fill(value) 21 | 22 | fn __getitem__(self, idx: Int) -> SIMD[AType, 1]: 23 | return self.data[idx] 24 | 25 | fn __getitem__(self, idx: Int32) -> SIMD[AType, 1]: 26 | return self.data[int(idx)] 27 | 28 | fn __setitem__(inout self, idx: Int, val: SIMD[AType, 1]): 29 | self.data[idx] = val 30 | 31 | fn __setitem__(inout self, idx: Int32, val: SIMD[AType, 1]): 32 | self.data[int(idx)] = val 33 | 34 | fn __del__(owned self): 35 | self.data.free() 36 | 37 | fn fill(inout self, value: SIMD[AType, 1] = 0): 38 | initializer = SIMD[AType, Self.simd_width](value) 39 | for i in range((self.size + Self.simd_width - 1) // Self.simd_width): 40 | self.data.store[width=Self.simd_width](i * Self.simd_width, initializer) 41 | 42 | fn swap(inout self, inout other: Self): 43 | (self.data, other.data) = (other.data, self.data) 44 | (self.size, other.size) = (other.size, self.size) 45 | 46 | # Buffer compat 47 | fn bytecount(self) -> Int: 48 | return self.size * sizeof[AType]() 49 | 50 | fn zero(inout self): 51 | self.fill(0) 52 | 53 | fn load[width: Int, T: IntLike](self, ofs: T) -> SIMD[AType, width]: 54 | return self.data.load[width=width](ofs) 55 | 56 | fn store[width: Int, T: IntLike](self, ofs: T, val: SIMD[AType, width]): 57 | self.data.store[width=width](ofs, val) 58 | 59 | fn load[T: IntLike](self, ofs: T) -> SIMD[AType, 1]: 60 | return self.data.load[width=1](ofs) 61 | 62 | fn store[T: IntLike](self, ofs: T, val: SIMD[AType, 1]): 63 | self.data.store[width=1](ofs, val) -------------------------------------------------------------------------------- /benchy.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def minibench(fns, loops=100): 5 | units = ["s", "ms", "μs", "ns"] 6 | for key in fns: 7 | sloop = loops // 10 8 | start = end = 0 9 | while end - start < 3: 10 | sloop *= 10 11 | start = time.time() 12 | t = 0 13 | for _ in range(sloop): 14 | t += fns[key]() 15 | end = time.time() 16 | ofs = 0 17 | div = 1 18 | while (end - start) * div / sloop < 0.01: 19 | ofs += 1 20 | div *= 1000 21 | print("{}\t {} {} ({} loops)".format(key, ((end - start) * div) / sloop, units[ofs], sloop)) 22 | 23 | #def foo(): 24 | # time.sleep(0.01) 25 | # return 1 26 | #minibench({"sleep": foo}) -------------------------------------------------------------------------------- /bugs/bufferfun.mojo: -------------------------------------------------------------------------------- 1 | # See https://github.com/modularml/mojo/issues/3579 2 | 3 | from array import Array 4 | from testing import assert_true 5 | 6 | alias maxc = 16 7 | alias maxn = 1600 8 | 9 | fn main() raises: 10 | graph = Array[DType.int16](maxn * maxn) 11 | marks = Array[DType.int32](maxn * maxn) 12 | prev = Array[DType.int16](maxn) 13 | work = Array[DType.int16](maxn) 14 | var mark = 0 15 | graph.zero() 16 | marks.zero() 17 | 18 | # compute a bfs path from src to tgt 19 | # if tgt not specified, a path to somewhere farthest from src. 20 | @parameter 21 | fn bfs(src: Int, tgt: Int = -1) raises -> Int: 22 | work[0] = src 23 | var ss = 1 24 | var si = 0 25 | while si < ss: 26 | cur = int(work[si]) 27 | si += 1 28 | assert_true(cur == 0) 29 | assert_true(graph[0] == 0) 30 | # uncommenting this assert makes the (sample) code run properly 31 | # assert_true(graph[cur] == 0) 32 | for di in range(graph[cur]): 33 | assert_true(cur * maxc == 0) 34 | # while the above is true, the code does not crash with the term removed below 35 | dst = int(graph[cur * maxc + 1 + di]) 36 | assert_true(dst == 0) 37 | assert_true(cur * maxn + dst == 0) 38 | # this assert also helps survive the crash 39 | # assert_true(marks[cur * maxn + dst] == mark) 40 | if marks[cur * maxn + dst] != mark and prev[dst] < 0: 41 | # and similarly, removing this assert makes the crash go away... 42 | # assert_true(False) 43 | assert_true(ss == 1) 44 | work[ss] = dst 45 | ss += 1 46 | return ss 47 | 48 | @parameter 49 | fn part1() raises -> Int64: 50 | return bfs(0, -1) 51 | 52 | print(part1()) 53 | 54 | # without these, it likely crashes for different reasons. 55 | print(graph.bytecount(), "graph size", mark) 56 | print(marks.bytecount() + prev.bytecount() + work.bytecount() , "work buffers size") 57 | -------------------------------------------------------------------------------- /bugs/lifetime.mojo: -------------------------------------------------------------------------------- 1 | # https://github.com/modularml/mojo/issues/1404 2 | 3 | from array import Array 4 | from memory import memcpy 5 | from memory.unsafe_pointer import UnsafePointer 6 | 7 | struct Keeper: 8 | var keep: String 9 | 10 | fn __init__(inout self, owned s: String): 11 | # the behavior is broken regardless of whether we copy or move. 12 | self.keep = s ^ 13 | 14 | fn slice(inout self, start: Int, l: Int) -> StringRef: 15 | return StringRef(self.keep._steal_ptr().offset(start), l) 16 | 17 | fn size(self) -> Int: 18 | return len(self.keep) 19 | 20 | 21 | alias charptr = UnsafePointer[UInt8] 22 | 23 | 24 | struct ByteKeeper: 25 | var keep: charptr 26 | var size: Int 27 | 28 | fn __init__(inout self, owned s: String): 29 | self.keep = charptr.alloc(len(s)) 30 | self.size = len(s) 31 | # It seems to be also broken if we manually copy underlying bytes. 32 | memcpy(self.keep, s._steal_ptr(), len(s)) 33 | 34 | fn slice(self, start: Int, l: Int) -> StringRef: 35 | return StringRef(self.keep.offset(start), l) 36 | 37 | 38 | fn main(): 39 | keeper = Keeper(String("I want you to hold this")) 40 | bkeeper = ByteKeeper(String("I want you to hold this")) 41 | simpler = String("I want you to hold this") 42 | buffer = Array[DType.uint8](128) 43 | memcpy(buffer.data, bkeeper.keep, bkeeper.size) 44 | 45 | @parameter 46 | fn doit(): 47 | for i in range(keeper.size() - 5): 48 | print(keeper.slice(i, i + 5)) 49 | for i in range(bkeeper.size - 5): 50 | print(bkeeper.slice(i, i + 5)) 51 | for i in range(len(simpler) - 5): 52 | print(simpler[i : i + 5]) 53 | for i in range(len(simpler) - 5): 54 | for j in range(i,i+5): 55 | print(chr(int(buffer[j]))) 56 | print() 57 | 58 | doit() 59 | 60 | # uncomment to make it actually work 61 | # print(keeper.size()) 62 | # print(len(simpler)) 63 | -------------------------------------------------------------------------------- /bugs/lifetime2.mojo: -------------------------------------------------------------------------------- 1 | fn call_closure[func: fn() capturing -> None]() -> None: 2 | func() 3 | 4 | fn main(): 5 | simpler = String("I want you to hold this") 6 | 7 | @parameter 8 | fn doit(): 9 | for i in range(len(simpler)-5): 10 | print(simpler[i:i+5]) 11 | 12 | call_closure[doit]() 13 | # If uncommented, this will actually work correctly 14 | #print(len(simpler)) -------------------------------------------------------------------------------- /bugs/memset.mojo: -------------------------------------------------------------------------------- 1 | # https://github.com/modularml/mojo/issues/1368 2 | # fixed 3 | 4 | from memory import memset 5 | from array import Array 6 | from memory.unsafe_pointer import UnsafePointer 7 | 8 | alias intptr = UnsafePointer[Int32] 9 | 10 | fn main(): 11 | # Prints junk, not 1 12 | buf = intptr.alloc(1000) 13 | memset(buf, -1, 1000) 14 | print(buf[0], buf[999]) 15 | # This works 16 | buff = Array[DType.int32](1024) 17 | buff.fill(1) 18 | print(buff[0],buff[1023]) 19 | -------------------------------------------------------------------------------- /bugs/modulus.mojo: -------------------------------------------------------------------------------- 1 | # https://github.com/modularml/mojo/issues/1482 2 | # fixed 3 | 4 | from memory.unsafe_pointer import UnsafePointer 5 | 6 | fn main(): 7 | array = UnsafePointer[Int].alloc(1000000) 8 | bug = 1000 % (array[0] | 1) 9 | print(bug) -------------------------------------------------------------------------------- /bugs/paracrash.mojo: -------------------------------------------------------------------------------- 1 | # https://github.com/modularml/mojo/issues/3578 2 | 3 | from algorithm import parallelize 4 | from os.atomic import Atomic 5 | 6 | fn main() raises: 7 | tiles = List[String]() 8 | tiles.extend(List[String]("abcde", "fghij", "klmno", "pqrst", "uvwxy")) 9 | dimx = tiles.size 10 | dimy = len(tiles[0]) 11 | var mmax = Atomic[DType.int64](0) 12 | 13 | # this doesn't reference any globals so is ok 14 | fn idx(x: Int32, y: Int32) -> Int32: 15 | return 220 + ((y + 1) % 2) * 110 + x + 1 16 | 17 | # this references dimx, dimy but is not @parameter bound 18 | fn bar(i: Int) -> SIMD[DType.int32, 4]: 19 | return SIMD[DType.int32, 4](dimy, i - 2 * dimx - dimy, -1, 0) 20 | 21 | @parameter 22 | fn foo(start: SIMD[DType.int32, 4]) -> Int64: 23 | return int(idx(start[0], start[1])) 24 | 25 | @parameter 26 | fn step2(i: Int): 27 | mmax.max(foo(bar(i))) 28 | 29 | @parameter 30 | fn invoke() -> Int64: 31 | parallelize[step2](4) 32 | return mmax.value 33 | 34 | # this works, despite bar not having @parameter decorator 35 | print(foo(bar(1))) 36 | # and this causes the whole runtime to crash 37 | print(invoke()) 38 | 39 | print(tiles.size, "tokens", dimx, dimy) 40 | -------------------------------------------------------------------------------- /bugs/strreplace.mojo: -------------------------------------------------------------------------------- 1 | # https://github.com/modularml/mojo/issues/1367 2 | # fixed 3 | 4 | fn main(): 5 | var s = String("a complex test case with some spaces") 6 | s = s.replace(" ", " ") 7 | print(s) 8 | # prints "a complex test case with some aces" 9 | -------------------------------------------------------------------------------- /day00.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | 3 | struct Solution: 4 | var parse: Parser 5 | 6 | fn __init__(inout self, s: String): 7 | self.parse = make_parser[10](s) 8 | 9 | fn part1(self) raises -> String: 10 | l = self.parse.get(0) 11 | var x: Int = int(atoi(l)) 12 | x *= 2 13 | return str(x) 14 | 15 | fn part2(self) raises -> String: 16 | var s: Int = 0 17 | for i in range(self.parse.length()): 18 | l = self.parse.get(i) 19 | y = int(atoi(l)) 20 | s += y 21 | return str(s) 22 | 23 | fn main(): 24 | try: 25 | f = open("day00.txt", "r") 26 | s = Solution(f.read()) 27 | print(s.part1()) 28 | print(s.part2()) 29 | except e: 30 | print(e) 31 | -------------------------------------------------------------------------------- /day01.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day01.txt").readlines() 4 | 5 | 6 | def look(line): 7 | for i in range(len(line)): 8 | if line[i] >= "0" and line[i] <= "9": 9 | return int(line[i]) 10 | return 0 11 | 12 | 13 | def look2(line, patterns): 14 | for i in range(len(line)): 15 | if line[i] >= "0" and line[i] <= "9": 16 | return int(line[i]) 17 | p = line[i:] 18 | for d in range(len(patterns)): 19 | if p.startswith(patterns[d]): 20 | return d 21 | return 0 22 | 23 | 24 | def part1(): 25 | s = 0 26 | for line in lines: 27 | s += look(line) * 10 + look(line[::-1]) 28 | return s 29 | 30 | 31 | def part2(): 32 | s = 0 33 | digits = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"] 34 | stigid = [s[::-1] for s in digits] 35 | for line in lines: 36 | s += look2(line, digits) * 10 + look2(line[::-1], stigid) 37 | return s 38 | 39 | 40 | print(part1()) 41 | print(part2()) 42 | 43 | minibench({"part1": part1, "part2": part2}) 44 | -------------------------------------------------------------------------------- /day02.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from os.atomic import Atomic 3 | from wrappers import run_multiline_task 4 | 5 | # Replaces ord('a') 6 | alias ord_a = ord("a") 7 | 8 | 9 | fn maxdict(s: StringSlice) raises -> Tuple[Int, Int, Int]: 10 | """ 11 | Parse a single line and return a dictionary with maximum values for each ball color 12 | across all the draws. Internally uses hierarchical parsing to split off the header, 13 | split draws, and then split colors. 14 | """ 15 | # Skip header. Game IDs are sequential, anyway. 16 | alias cOlon = ord(":") 17 | alias cR = ord("r") 18 | alias cG = ord("g") 19 | start = s.find(cOlon) + 2 # ':' 20 | # Top-level parser for draws - semicolon separated 21 | draws = make_parser[";"](s[start:]) 22 | red = green = blue = 0 23 | for d in range(draws.length()): 24 | # Secondary level parser for comma-separated colors 25 | colors = make_parser[","](draws.get(d)) 26 | for b in range(colors.length()): 27 | # split color name and value 28 | tok = make_parser[" "](colors.get(b)) 29 | v = int(atoi(tok.get(0))) 30 | col = tok.get(1) 31 | if col[0] == cR: 32 | red = max(red, v) 33 | elif col[0] == cG: 34 | green = max(green, v) 35 | else: 36 | blue = max(blue, v) 37 | return (red, green, blue) 38 | 39 | 40 | fn main() raises: 41 | f = open("day02.txt", "r") 42 | lines = make_parser["\n"](f.read()) 43 | var sum1 = Atomic[DType.int32](0) 44 | var sum2 = Atomic[DType.int32](0) 45 | 46 | # Handle one line for the first task. If the maximum ball counts for a given line exceed 47 | # the limits, update the counter. 48 | @parameter 49 | fn step1(l: Int): 50 | try: 51 | (r, g, b) = maxdict(lines.get(l)) 52 | if r <= 12 and g <= 13 and b <= 14: 53 | sum1 += l + 1 54 | except: 55 | pass 56 | 57 | # Handle one line for the second task. Just multiply the maximum counts. 58 | @parameter 59 | fn step2(l: Int): 60 | try: 61 | (r, g, b) = maxdict(lines.get(l)) 62 | sum2 += r * g * b 63 | except: 64 | pass 65 | 66 | @parameter 67 | fn results(): 68 | print(int(sum1.value)) 69 | print(int(sum2.value)) 70 | 71 | run_multiline_task[step1, step2, results](lines.length()) 72 | 73 | # Same as in part1 - ensure `lines` actually lives through to the end of the program. 74 | print(lines.length(), "rows") 75 | -------------------------------------------------------------------------------- /day02.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | 4 | def maxballs(line): 5 | (_, rest) = line.split(": ") 6 | games = rest.split("; ") 7 | mdict = {} 8 | mdict["red"] = mdict["green"] = mdict["blue"] = 0 9 | for game in games: 10 | game = game.replace(",", "") 11 | toks = game.split(" ") 12 | for i in range(len(toks) // 2): 13 | cnt = int(toks[i * 2]) 14 | col = toks[i * 2 + 1] 15 | mdict[col] = max(mdict[col], cnt) 16 | return mdict 17 | 18 | 19 | lines = open("day02.txt").read().split("\n") 20 | 21 | 22 | def part1(): 23 | id = 1 24 | sum = 0 25 | for line in lines: 26 | mdict = maxballs(line) 27 | if mdict["red"] <= 12 and mdict["green"] <= 13 and mdict["blue"] <= 14: 28 | sum += id 29 | id += 1 30 | return sum 31 | 32 | 33 | def part2(): 34 | sum = 0 35 | for line in lines: 36 | mdict = maxballs(line) 37 | sum += mdict["red"] * mdict["green"] * mdict["blue"] 38 | return sum 39 | 40 | 41 | print(part1()) 42 | print(part2()) 43 | 44 | minibench({"part1": part1, "part2": part2}) 45 | -------------------------------------------------------------------------------- /day03.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from collections import List 3 | from wrappers import minibench 4 | from array import Array 5 | 6 | 7 | fn main() raises: 8 | f = open("day03.txt", "r") 9 | lines = make_parser["\n"](f.read()) 10 | dimx = lines.get(0).size 11 | dimy = lines.length() 12 | # Here we'll keep all the found numbers - the format is POS_Y,POS_X,LENGTH,VALUE 13 | nums = Array[DType.int32](2000 * 8) 14 | var cnt = 0 15 | # And here we'll keep all the information about gears. We just keep a huge table 16 | # with enough space to have gears in all position of the board. No hashing - the 17 | # gear position _is_ the index into this array. 18 | gears = Array[DType.int32](160 * 160) 19 | 20 | @parameter 21 | fn push_num(y: Int, x: Int, l: Int, v: Int): 22 | # Bounding box around the number 23 | sx = max(x - 1, 0) 24 | lx = min(x + l, dimx - 1) 25 | sy = max(y - 1, 0) 26 | ly = min(y + 1, dimy - 1) 27 | nums.store[width=8](cnt * 8, SIMD[DType.int32, 8](sx, lx, sy, ly, v)) 28 | cnt += 1 29 | 30 | # This is the common 'parse' task: scan the board and find anythin that looks like 31 | # a number. Store all found numbers into the 'nums' vector. This could be nicer 32 | # with return values and all, but we weant to benchmark it and benchmark doesn't like 33 | # functions with parameters. 34 | @parameter 35 | fn find_nums() -> Int64: 36 | cnt = 0 37 | for y in range(lines.length()): 38 | var r: Int = 0 39 | var q: Int = 0 40 | line = lines[y] 41 | for x in range(dimx): 42 | c = int(line[x]) 43 | # char is in 0..9 range 44 | if c >= 48 and c <= 57: 45 | r = r * 10 + c - 48 46 | q += 1 47 | # or not, but we have some leftover number 48 | elif q > 0: 49 | push_num(y, x - q, q, r) 50 | r = q = 0 51 | # handle numbers at the right edge 52 | if q > 0: 53 | push_num(y, dimx - q, q, r) 54 | return cnt 55 | 56 | # Step 1 will search around the specified number and look for special characters. 57 | # If any are found, will increment the sum. 58 | @parameter 59 | fn step1(i: Int) -> Int64: 60 | rec = nums.load[width=8](i * 8) 61 | # unpack 62 | sx = rec[0] 63 | lx = rec[1] 64 | sy = rec[2] 65 | ly = rec[3] 66 | v = int(rec[4]) 67 | # Scan the box. This is rather fast in Mojo. 68 | for gy in range(sy, ly + 1): 69 | line = lines[gy] 70 | for gx in range(sx, lx + 1): 71 | c = line[gx] 72 | # Not a number and not a dot 73 | if (c < 48 or c > 57) and (c != 46): 74 | return v 75 | return 0 76 | 77 | @parameter 78 | fn part1() -> Int64: 79 | var sum1: Int64 = 0 80 | for i in range(cnt): 81 | sum1 += step1(i) 82 | return sum1 83 | 84 | # Part 2 is actually really similar to Part1, but we're only looking for stars. 85 | @parameter 86 | fn step2(i: Int) -> Int64: 87 | rec = nums.load[width=8](i * 8) 88 | # unpack 89 | sx = rec[0] 90 | lx = rec[1] 91 | sy = rec[2] 92 | ly = rec[3] 93 | v = int(rec[4]) 94 | var sum2: Int64 = 0 95 | for gy in range(sy, ly + 1): 96 | line = lines[gy] 97 | for gx in range(sx, lx + 1): 98 | if line[gx] == 42: 99 | # If a star is found, look up its state (previously found neighbor value) in `gears` 100 | gk = gy * dimy + gx 101 | # Apparently, there is never a gear with more than tow neighbors so this is sufficient 102 | if gears[gk] > 0: 103 | sum2 += v * int(gears[gk]) 104 | # Just store the current value. 105 | gears[gk] = v 106 | return sum2 107 | 108 | @parameter 109 | fn part2() -> Int64: 110 | gears.zero() 111 | var sum2: Int64 = 0 112 | for i in range(cnt): 113 | sum2 += step2(i) 114 | return sum2 115 | 116 | # This part doesn't seem to benefit much from parallelization, so just run benchmarks. 117 | minibench[find_nums]("parse") 118 | minibench[part1]("part1") 119 | minibench[part2]("part2") 120 | 121 | # Ensure `lines` and `nums` are still in use 122 | print(lines.length(), "rows") 123 | print(cnt, "numbers") 124 | print(nums.bytecount() + gears.bytecount(), "bytes") 125 | -------------------------------------------------------------------------------- /day03.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day03.txt").read().split("\n") 4 | dimx = len(lines[0]) 5 | dimy = len(lines) 6 | nums = [] 7 | 8 | 9 | def find_nums(): 10 | nums.clear() 11 | for y in range(dimy): 12 | r = 0 13 | q = 0 14 | for x in range(dimx): 15 | c = ord(lines[y][x]) 16 | if c >= 48 and c <= 57: 17 | d = c - 48 18 | r = r * 10 + d 19 | q += 1 20 | else: 21 | if q > 0: 22 | nums.append((y, x - q, q, r)) 23 | r = q = 0 24 | if q > 0: 25 | nums.append((y, dimx - q, q, r)) 26 | return 0 27 | 28 | 29 | def part1(): 30 | s = 0 31 | for y, x, l, v in nums: 32 | n = "" 33 | sx = max(x - 1, 0) 34 | if y > 0: 35 | n = n + lines[y - 1][sx : x + l + 1] 36 | if x > 0: 37 | n += lines[y][x - 1] 38 | if x + l < dimx: 39 | n += lines[y][x + l] 40 | if y + 1 < dimy: 41 | n += lines[y + 1][sx : x + l + 1] 42 | for c in n: 43 | if c != "." and not c.isdigit(): 44 | s += v 45 | break 46 | return s 47 | 48 | 49 | def part2(): 50 | gc = [0] * (dimx * dimy) 51 | s2 = 0 52 | for y, x, l, v in nums: 53 | sx = max(x - 1, 0) 54 | lx = min(x + l, dimx - 1) 55 | sy = max(y - 1, 0) 56 | ly = min(y + 1, dimy - 1) 57 | for gy in range(sy, ly + 1): 58 | for gx in range(sx, lx + 1): 59 | if lines[gy][gx] == "*": 60 | gk = gy * dimx + gx 61 | if gc[gk] != 0: 62 | s2 += gc[gk] * v 63 | gc[gk] = v 64 | return s2 65 | 66 | 67 | find_nums() 68 | print(part1()) 69 | print(part2()) 70 | 71 | minibench({"parse": find_nums, "part1": part1, "part2": part2}) 72 | -------------------------------------------------------------------------------- /day04.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from array import Array 3 | from wrappers import minibench 4 | from bit import pop_count 5 | 6 | alias space = ord(" ") 7 | alias zero = ord("0") 8 | 9 | 10 | # Count number of matches in a game 11 | @always_inline 12 | fn matches(win: SIMD[DType.uint8, 16], hand: SIMD[DType.uint8, 16]) -> Int: 13 | return int(pop_count(win & hand).reduce_add()) 14 | 15 | 16 | @always_inline 17 | fn main() raises: 18 | f = open("day04.txt", "r") 19 | lines = make_parser["\n"](f.read()) 20 | 21 | count = lines.length() 22 | # Each game is represented as two 128bit numbers, stored as 2x16-byte SIMD vectors 23 | games = Array[DType.uint8](256 * 32) 24 | # Counts number of instances of each ticket 25 | draws = Array[DType.int32](256) 26 | 27 | # Set a single bit in a 8-bit based bitfield 28 | @always_inline 29 | fn setbit(inout m: SIMD[DType.uint8, 16], v: Int): 30 | g = v // 8 31 | b = v % 8 32 | m[g] = m[g] | (1 << b) 33 | 34 | # Build a bitfield from a string containing integers 35 | fn bitfield(s: StringSlice) -> SIMD[DType.uint8, 16]: 36 | var ret = SIMD[DType.uint8, 16](0) 37 | var pos = 0 38 | l = s.size 39 | var r = 0 40 | while pos < l: 41 | if s[pos] != space: 42 | r = r * 10 + int(s[pos]) - zero 43 | elif r > 0: 44 | setbit(ret, r) 45 | r = 0 46 | pos += 1 47 | if r > 0: 48 | setbit(ret, r) 49 | r = 0 50 | return ret 51 | 52 | # Scan each game, split it into winning numbers and store as bit vectors 53 | @parameter 54 | fn parse() -> Int64: 55 | games.zero() 56 | for y in range(count): 57 | s = lines[y] 58 | alias cOlon = ord(":") 59 | start = s.find(cOlon) 60 | alias cPipe = ord("|") 61 | sep = s.find(cPipe) 62 | s1 = s[start + 2 : sep] 63 | s2 = s[sep + 2 :] 64 | games.store[width=16](y * 32, bitfield(s1)) 65 | games.store[width=16](y * 32 + 16, bitfield(s2)) 66 | return count 67 | 68 | # Take numbers of matches, exponentiate, sum up 69 | @parameter 70 | fn part1() -> Int64: 71 | var sum1 = 0 72 | for i in range(count): 73 | w = 1 << matches(games.load[width=16](i * 32), games.load[width=16](i * 32 + 16)) 74 | sum1 += w >> 1 75 | return sum1 76 | 77 | # Computes the ticket counts in draws table on the go 78 | @parameter 79 | fn part2() -> Int64: 80 | draws.fill(1) 81 | var sum2: Int64 = 0 82 | for i in range(count): 83 | cd = draws[i] 84 | x = matches(games.load[width=16](i * 32), games.load[width=16](i * 32 + 16)) 85 | # Update next x draws 86 | for j in range(i + 1, min(count, i + x + 1)): 87 | draws[j] += cd 88 | sum2 += int(cd) 89 | return sum2 90 | 91 | # This part doesn't seem to benefit much from parallelization, so just run benchmarks. 92 | minibench[parse]("parse") 93 | minibench[part1]("part1") 94 | minibench[part2]("part2") 95 | 96 | # Ensure `lines` and `games` are still in use 97 | print(lines.length(), "rows", count) 98 | print(games.bytecount() + draws.bytecount(), "bytes") 99 | -------------------------------------------------------------------------------- /day04.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day04.txt").read().split("\n") 4 | games = [] 5 | 6 | 7 | def parse(): 8 | games.clear() 9 | for id, line in enumerate(lines): 10 | line = line.replace(" ", " ") 11 | (_, t) = line.split(": ") 12 | (a, b) = t.split(" | ") 13 | n1 = set(map(int, a.split(" "))) 14 | n2 = list(map(int, b.split(" "))) 15 | games.append((id, n1, n2)) 16 | return 0 17 | 18 | 19 | def part1(): 20 | s = 0 21 | for _, win, hand in games: 22 | c = 1 23 | for num in hand: 24 | if num in win: 25 | c *= 2 26 | s += c // 2 27 | return s 28 | 29 | 30 | def part2(): 31 | l = len(games) 32 | w = [1] * l 33 | s2 = 0 34 | for id, win, hand in games: 35 | x = 0 36 | for num in hand: 37 | if num in win: 38 | x += 1 39 | for i in range(id + 1, min(l, id + x + 1)): 40 | w[i] += w[id] 41 | s2 += w[id] 42 | return s2 43 | 44 | 45 | parse() 46 | print(part1()) 47 | print(part2()) 48 | 49 | minibench({"parse": parse, "part1": part1, "part2": part2}) 50 | -------------------------------------------------------------------------------- /day05.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day05.txt").read().split("\n") 4 | lines.append(":") 5 | numbers = [] 6 | ranges = [] 7 | steps = [] 8 | 9 | 10 | def parse(): 11 | s = lines[0].split() 12 | numbers.clear() 13 | ranges.clear() 14 | steps.clear() 15 | numbers.extend(map(int, s[1:])) 16 | for i in range(len(numbers) // 2): 17 | ranges.append((numbers[2 * i], numbers[2 * i] + numbers[2 * i + 1] - 1)) 18 | cur = [] 19 | for line in lines[3:]: 20 | if not line: 21 | continue 22 | if line[-1] == ":": 23 | cur.sort() 24 | steps.append(cur) 25 | cur = [] 26 | continue 27 | (dst, src, l) = map(int, line.split()) 28 | cur.append((src, dst, l)) 29 | return 0 30 | 31 | 32 | def part1(): 33 | work = numbers.copy() 34 | for step in steps: 35 | next = [] 36 | for n in work: 37 | for src, dst, l in step: 38 | if n >= src + l: 39 | continue 40 | if n < src: 41 | next.append(n) 42 | else: 43 | next.append(n + dst - src) 44 | n = -1 45 | break 46 | if n >= 0: 47 | next.append(n) 48 | work = next 49 | return min(work) 50 | 51 | 52 | def part2(): 53 | work = ranges.copy() 54 | for step in steps: 55 | next = [] 56 | for a, b in work: 57 | for src, dst, l in step: 58 | ofs = dst - src 59 | if a >= src + l: 60 | continue 61 | if a < src: # some is untranslated 62 | next.append((a, src - 1)) 63 | a = src 64 | # a >= src. 65 | if b >= src + l: # some range remains 66 | next.append((a + ofs, (src + l - 1) + ofs)) 67 | a = src + l 68 | else: # everything fits 69 | next.append((a + ofs, b + ofs)) 70 | a = b + 1 71 | break 72 | if a <= b: # some was left untranslated 73 | next.append((a, b)) 74 | work = [] 75 | for a, b in sorted(next): 76 | if work and (a == work[-1][1] + 1): 77 | work[-1] = (work[-1][0], b) 78 | else: 79 | work.append((a, b)) 80 | return work[0][0] 81 | 82 | 83 | parse() 84 | print(part1()) 85 | print(part2()) 86 | 87 | minibench({"parse": parse, "part1": part1, "part2": part2}) 88 | -------------------------------------------------------------------------------- /day06.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from math import sqrt 3 | from wrappers import minibench 4 | 5 | 6 | # this is actually faster than math.sqrt(Int), but works for 64-bit numbers 7 | fn ssqrt(x: Int64) -> Int64: 8 | var d: Int64 = 1 9 | while d * d < x: 10 | d *= 2 11 | var a: Int64 = 0 12 | while d > 1: 13 | d = d / 2 14 | if x > (a + d) * (a + d): 15 | a += d 16 | return a 17 | 18 | 19 | # Computes the integral distance between solutions of a quadratic function (x)(t-x)-d 20 | # / number of values of x such for which the function is strictly larger than zero 21 | fn quadratic(t: Int64, d: Int64) -> Int64: 22 | delta = t * t - 4 * d 23 | if delta <= 0: 24 | return 0 25 | ds = ssqrt(delta) 26 | var x0 = (t - ds) / 2 27 | var x1 = (t + ds) / 2 28 | if x0 * (t - x0) <= d: 29 | x0 += 1 30 | if x1 * (t - x1) <= d: 31 | x1 -= 1 32 | return int((x1 - x0) + 1) 33 | 34 | 35 | fn main() raises: 36 | f = open("day06.txt", "r") 37 | lines = make_parser['\n'](f.read()) 38 | 39 | @parameter 40 | fn part1() -> Int64: 41 | times = make_parser[' '](lines.get(0)) 42 | dist = make_parser[' '](lines.get(1)) 43 | var s: Int64 = 1 44 | for i in range(1,times.length()): 45 | t = atoi(times.get(i)) 46 | d = atoi(dist.get(i)) 47 | q = quadratic(t, d) 48 | s *= q 49 | return s 50 | 51 | @parameter 52 | fn part2() -> Int64: 53 | t = atoi(lines.get(0)[10:]) 54 | d = atoi(lines.get(1)[10:]) 55 | return quadratic(t, d) 56 | 57 | minibench[part1]("part1") 58 | minibench[part2]("part2") 59 | print(lines.length(), "rows") 60 | -------------------------------------------------------------------------------- /day06.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | import math 3 | 4 | lines = open("day06.txt").read().split("\n") 5 | 6 | 7 | def quadratic(t, d): 8 | delta = t * t - 4 * d 9 | if delta <= 0: 10 | return 0 11 | ds = math.sqrt(delta) 12 | x0 = math.ceil((t - ds) / 2) 13 | x1 = math.floor((t + ds) / 2) 14 | if x0 * (t - x0) == d: 15 | x0 += 1 16 | if x1 * (t - x1) == d: 17 | x1 -= 1 18 | return (x1 - x0) + 1 19 | 20 | 21 | def part1(): 22 | times = list(map(int, lines[0].split()[1:])) 23 | dist = list(map(int, lines[1].split()[1:])) 24 | s = 1 25 | for i in range(len(times)): 26 | s *= quadratic(times[i], dist[i]) 27 | return s 28 | 29 | 30 | def part2(): 31 | t = int(lines[0][10:].replace(" ", "")) 32 | d = int(lines[1][10:].replace(" ", "")) 33 | return quadratic(t, d) 34 | 35 | 36 | print(part1()) 37 | print(part2()) 38 | 39 | minibench({"part1": part1, "part2": part2}, 1000) 40 | -------------------------------------------------------------------------------- /day07.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from math import sqrt 3 | from wrappers import minibench 4 | from quicksort import qsort 5 | from array import Array 6 | 7 | 8 | fn main() raises: 9 | f = open("day07.txt", "r") 10 | tokens = make_parser[" "](f.read().replace("\n", " ")) 11 | mapp = Array[DType.int32](128) 12 | codes = Array[DType.int32](tokens.length() // 2) 13 | counts = Array[DType.int8](128) 14 | 15 | # Converts the hand (passed in the ss) to a integer value representaion 16 | # high-ish bits represent the rank, low-ish represent the cards themselves 17 | # essentially encoding the whole hand and the renk into a 22-bit value. 18 | # Card ranking is injected via the mapp pointer which is actually a 19 | # mapping from character values to indexes. 20 | @parameter 21 | fn rank[joke: Int](ss: StringSlice) -> Int: 22 | counts.fill(1) 23 | var r = 0 24 | 25 | # Compute the counts of cards, and the card value index by iterating 26 | # through the hand. counts keeps track of occurances of each card; 27 | for p in range(5): 28 | c = int(mapp[int(ss[p])]) 29 | counts[c] += 1 30 | r = r * 14 + c 31 | # Joker code 32 | jc = mapp.load(74) 33 | # ideally this should be optimized out if joke = 0 at compile time 34 | j = int(counts[int(jc)]) * joke 35 | var s = 6 36 | # This allows us to determine the top card counts. 37 | # counts are all +1 to make them non-zero 38 | # the possible values are: 39 | # 5:0 - 6, 4:1 - 10, 3:2 - 12, 3:1:1 - 16, 2:2:1 - 18, 2:1:1:1 - 24, 1:1:1:1:1 - 32 40 | kpro = counts.load[width=16](0).reduce_mul() 41 | # 5-of-a-kind -> there is a group of count 5 42 | if kpro == 6: 43 | s = 0 44 | # Same for 4-of-a-kind, can be upgraded to 5 with a joker 45 | elif kpro == 10: 46 | s = 1 47 | if j > 1: 48 | s = 0 49 | # Full house, becomes five of a kind with a joker 50 | # since the joker has to be either the 3-group or 2-group 51 | elif kpro == 12: 52 | s = 2 53 | if j > 1: 54 | s = 0 55 | # Three of a kind, becomes four-of-a-kind with a joker 56 | elif kpro == 16: 57 | s = 3 58 | if j > 1: 59 | s = 1 60 | # Two pairs. Can become either a full-house with one joker 61 | # or four-of-a-kind with two. 62 | elif kpro == 18: 63 | s = 4 64 | if j == 2: 65 | s = 2 66 | elif j == 3: 67 | s = 1 68 | # One pair. Can become a three with a joker. 69 | elif kpro == 24: 70 | s = 5 71 | if j > 1: 72 | s = 3 73 | # If everything is single, but we have a joker, that makes a pair. 74 | elif j > 1: 75 | s = 5 76 | # 537824 = 14 ^ 5, the range for card index value. 77 | return s * 537824 + r 78 | 79 | @parameter 80 | fn prep() -> Int64: 81 | alphabet = String("AKQJT98765432") 82 | # Map characters to their order values 83 | for i in range(len(alphabet)): 84 | mapp.store(ord(alphabet[i]), i) 85 | return len(alphabet) 86 | 87 | @parameter 88 | fn play[joke: Int]() -> Int64: 89 | # map the joker to 3 or 13 90 | mapp[74] = 3 + joke * 10 91 | for l in range(tokens.length() // 2): 92 | code = rank[joke](tokens[l * 2]) 93 | score = atoi(tokens[l * 2 + 1]) 94 | codes[l] = code * 1024 + int(score) 95 | qsort[1](codes) 96 | var p = codes.size 97 | var s: Int32 = 0 98 | for i in range(codes.size): 99 | s += (codes[i] & 1023) * p 100 | p -= 1 101 | return int(s) 102 | 103 | @parameter 104 | fn part1() -> Int64: 105 | return play[0]() 106 | 107 | @parameter 108 | fn part2() -> Int64: 109 | return play[1]() 110 | 111 | print(prep()) 112 | minibench[part1]("part1") 113 | minibench[part2]("part2") 114 | print(tokens.length(), "tokens") 115 | print(codes.bytecount() + counts.bytecount() + mapp.bytecount(), "bytes") 116 | -------------------------------------------------------------------------------- /day07.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day07.txt").read().split() 4 | alphabet = "AKQJT98765432" 5 | mapp = {v: k for (k,v) in enumerate(alphabet)} 6 | 7 | def rank(ss, joke): 8 | k=[ 0 ]*6 9 | j = ss.count('J') * joke 10 | for chr in alphabet: 11 | k[ss.count(chr)] += 1 12 | if (k[5] > 0): 13 | s = 0 14 | elif (k[4] > 0): 15 | s = 1 16 | if (j > 0): 17 | s = 0 18 | elif (k[3] > 0) and (k[2] > 0) : 19 | s = 2 20 | if (j > 0): 21 | s = 0 22 | elif (k[3] > 0): 23 | s = 3 24 | if (j > 0): 25 | s = 1 26 | elif (k[2] > 1): 27 | s = 4 28 | if (j == 1): 29 | s = 2 30 | elif (j == 2): 31 | s = 1 32 | elif (k[2] > 0): 33 | s = 5 34 | if (j > 0): 35 | s = 3 36 | elif (j > 0): 37 | s = 5 38 | else: 39 | s=6 40 | for chr in ss: 41 | if (joke and chr == "J"): 42 | s = s * 14 + 13 43 | else: 44 | s = s * 14 + mapp[chr] 45 | return s 46 | 47 | 48 | def play(joke): 49 | ranked = [] 50 | for l in range(len(lines)//2): 51 | (hand, s) = (lines[l*2], lines[l*2+1]) 52 | ranked.append((rank(hand, joke), int(s), hand)) 53 | p = len(ranked) 54 | s = 0 55 | for (r,v,h) in sorted(ranked): 56 | # print (r,h,v,"*",p) 57 | s += v * p 58 | p -= 1 59 | return(s) 60 | 61 | 62 | def part1(): 63 | return play(0) 64 | 65 | def part2(): 66 | return play(1) 67 | 68 | print(part1()) 69 | print(part2()) 70 | minibench({"part1": part1, "part2": part2}, 1000) 71 | -------------------------------------------------------------------------------- /day08.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from algorithm import parallelize 4 | from memory import memset 5 | from array import Array 6 | 7 | # Encode a 3-letter identifier to 15-bit integer 8 | @always_inline 9 | fn encode(s: StringSlice) -> Int: 10 | return ((int(s[0]) - 64) << 10) + 11 | ((int(s[1]) - 64) << 5) + 12 | (int(s[2]) - 64) 13 | 14 | @always_inline 15 | fn gcd(a1: Int64,b1: Int64) -> Int64: 16 | var a = a1 17 | var b = b1 18 | while b != 0: 19 | c = a % b 20 | a = b 21 | b = c 22 | return a 23 | 24 | @always_inline 25 | fn lcm(a: Int64,b: Int64) -> Int64: 26 | g = gcd(a,b) 27 | return (a // g) * b 28 | 29 | struct Game: 30 | var lr: Array[DType.int32] 31 | var sp: List[Int] 32 | var cnt: Int 33 | 34 | fn __init__(inout self): 35 | self.lr = Array[DType.int32](32768) 36 | self.sp = List[Int]() 37 | self.cnt = 0 38 | 39 | fn clear(inout self): 40 | self.lr.zero() 41 | self.sp.clear() 42 | self.cnt = 0 43 | 44 | fn add(inout self, s: StringSlice): 45 | n = encode(s[0:3]) 46 | l = encode(s[7:10]) 47 | r = encode(s[12:15]) 48 | # start node goes into the vector 49 | alias cA = ord('A') 50 | if s[2] == cA: # 'A' 51 | self.sp.append(n) 52 | self.lr[n] = (l << 15) + r 53 | # final node gets a bit 54 | alias cZ = ord('Z') 55 | if s[2] == cZ: # 'Z' 56 | self.lr[n] |= 1 << 30 57 | # print(s,n,l,r,self.lr[n]) 58 | self.cnt += 1 59 | 60 | fn next(self, id: Int, c: UInt8) -> Int: 61 | if c == 76: # 'L' 62 | return int((self.lr[id] >> 15) & 0x7FFF) 63 | else: 64 | return int((self.lr[id]) & 0x7FFF) 65 | 66 | fn main() raises: 67 | f = open("day08.txt", "r") 68 | lines = make_parser['\n'](f.read()) 69 | ins = lines.get(0) 70 | var game = Game() 71 | var results = List[Int]() 72 | 73 | @parameter 74 | fn parse() -> Int64: 75 | game.clear() 76 | results.clear() 77 | for i in range(1,lines.length()): 78 | game.add(lines.get(i)) 79 | for _ in range(game.sp.size): 80 | results.append(0) 81 | return game.cnt 82 | 83 | @parameter 84 | fn part1() -> Int64: 85 | var s = 0 86 | var id = (1 << 10) + (1 << 5) + 1 # 'AAA' 87 | fin = (26 << 10) + (26 << 5) + 26 # 'ZZZ' 88 | while id != fin and id != 0: 89 | id = game.next(id, ins[s % ins.size]) 90 | s += 1 91 | return s 92 | 93 | @parameter 94 | fn step2(i: Int) -> None: 95 | var s = 0 96 | var id = game.sp[i] 97 | while game.lr[id] >> 30 == 0: 98 | id = game.next(id, ins[s % ins.size]) 99 | s = s + 1 100 | results[i] = s 101 | 102 | @parameter 103 | fn part2() -> Int64: 104 | var z : Int64 = 1 105 | for l in range(game.sp.size): 106 | step2(l) 107 | # print(l, results[l]) 108 | z = lcm(z, results[l]) 109 | return z 110 | 111 | @parameter 112 | fn part2_parallel() -> Int64: 113 | parallelize[step2](game.sp.size, game.sp.size) 114 | var z : Int64 = 1 115 | for l in range(game.sp.size): 116 | z = lcm(z, results[l]) 117 | return z 118 | 119 | minibench[parse]("parse") 120 | minibench[part1]("part1") 121 | minibench[part2]("part2") 122 | minibench[part2_parallel]("part2_parallel") 123 | 124 | print(lines.length(), "lines") 125 | print(game.cnt, "nodes") 126 | print(ins.size, "steps") 127 | print(results[0]) 128 | -------------------------------------------------------------------------------- /day08.py: -------------------------------------------------------------------------------- 1 | from math import lcm 2 | from benchy import minibench 3 | 4 | 5 | class Game: 6 | def __init__(self, rules): 7 | self.D = {} 8 | self.F = set() 9 | self.S = [] 10 | for id, l in enumerate(rules): 11 | n = l[0:3] 12 | self.D[n] = id 13 | if n[-1] == "A": 14 | self.S.append(id) 15 | if n[-1] == "Z": 16 | self.F.add(id) 17 | self.L = [-1] * len(self.D) 18 | self.R = [-1] * len(self.D) 19 | for l in lines[2:]: 20 | n = self.D[l[0:3]] 21 | self.L[n] = self.D[l[7:10]] 22 | self.R[n] = self.D[l[12:15]] 23 | 24 | 25 | lines = open("day08.txt").read().split("\n") 26 | g = None 27 | ins = lines[0] 28 | 29 | 30 | def parse(): 31 | global g 32 | g = Game(lines[2:]) 33 | return len(g.D) 34 | 35 | 36 | def part1(): 37 | s = 0 38 | m = len(ins) 39 | id = g.D["AAA"] 40 | fin = g.D["ZZZ"] 41 | while id != fin: 42 | c = ins[s % m] 43 | if c == "L": 44 | id = g.L[id] 45 | else: 46 | id = g.R[id] 47 | s += 1 48 | return s 49 | 50 | 51 | def part2(): 52 | z = 1 53 | m = len(ins) 54 | for start in g.S: 55 | s = 0 56 | id = start 57 | while id not in g.F: 58 | c = ins[s % m] 59 | if c == "L": 60 | id = g.L[id] 61 | else: 62 | id = g.R[id] 63 | s = s + 1 64 | z = lcm(z, s) 65 | return z 66 | 67 | 68 | parse() 69 | print(part1()) 70 | print(part2()) 71 | 72 | minibench({"parse": parse, "part1": part1, "part2": part2}) 73 | -------------------------------------------------------------------------------- /day09.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from memory import memset 4 | from array import Array 5 | from sys.info import simdwidthof, sizeof 6 | 7 | alias simd_width_u32 = simdwidthof[DType.int32]() 8 | 9 | @value 10 | struct Matrix2D: 11 | var nums: Array[DType.int32] 12 | var rows: Int 13 | var cols: Int 14 | 15 | # blank initializer 16 | fn __init__(inout self, w: Int, h: Int): 17 | # ensure everything is 8-padded. 18 | self.rows = w 19 | self.cols = (h + simd_width_u32 - 1) & ~(simd_width_u32 - 1) 20 | self.nums = Array[DType.int32](self.rows * self.cols) 21 | 22 | # override copy constructor to also copy memory 23 | fn __copyinit__(inout self, other: Self): 24 | self.rows = other.rows 25 | self.cols = other.cols 26 | self.nums = Array[DType.int32](self.rows * self.cols) 27 | memcpy(self.nums.data, other.nums.data, self.rows * self.cols) 28 | 29 | # parse and store an input line as a column in the matrix 30 | fn store_column(inout self, x: Int, s: StringSlice): 31 | p = make_parser[" "](s) 32 | for i in range(p.length()): 33 | self.nums[self.cols * i + x] = int(atoi(p.get(i))) 34 | 35 | # computes the differential, from top to bottown, leaving last row intact (used in the final pass) 36 | fn reduce_down(inout self, rows: Int): 37 | for ofs in range(self.cols // simd_width_u32): 38 | var prev = self.nums.load[width=simd_width_u32](ofs * simd_width_u32) 39 | for i in range(1, rows): 40 | next = self.nums.load[width=simd_width_u32](i * self.cols + ofs * simd_width_u32) 41 | prev = next - prev 42 | self.nums.store[width=simd_width_u32](((i - 1) * self.cols + ofs * simd_width_u32), prev) 43 | prev = next 44 | 45 | # computes the differential, leaving first row intact (used in the final pass) 46 | fn reduce_up(inout self, skip: Int): 47 | for ofs in range(self.cols // simd_width_u32): 48 | var prev = self.nums.load[width=simd_width_u32](skip * self.cols + ofs * simd_width_u32) 49 | for i in range(skip + 1, self.rows): 50 | next = self.nums.load[width=simd_width_u32](i * self.cols + ofs * simd_width_u32) 51 | prev = next - prev 52 | self.nums.store[width=simd_width_u32]((i * self.cols + ofs * simd_width_u32), prev) 53 | prev = next 54 | 55 | # Returns the sum of all elements in the matrix 56 | fn sum(inout self) -> Int64: 57 | var tot = SIMD[DType.int32, simd_width_u32](0) 58 | for ofs in range(self.cols // simd_width_u32): 59 | var tmp = SIMD[DType.int32, simd_width_u32](0) 60 | for i in range(self.rows): 61 | tmp += self.nums.load[width=simd_width_u32](i * self.cols + ofs * simd_width_u32) 62 | tot += tmp 63 | return int(tot.reduce_add()) 64 | 65 | # Returns the sum of sequential differences of all the columns in the matrix 66 | fn dsum(inout self) -> Int64: 67 | var tot = SIMD[DType.int32, simd_width_u32](0) 68 | for ofs in range(self.cols // simd_width_u32): 69 | var prev = self.nums.load[width=simd_width_u32]((self.rows - 1) * self.cols + ofs * simd_width_u32) 70 | for i in range(self.rows - 2, -1, -1): 71 | next = self.nums.load[width=simd_width_u32](i * self.cols + ofs * simd_width_u32) 72 | prev = next - prev 73 | tot += prev 74 | return int(tot.reduce_add()) 75 | 76 | # for debugging 77 | fn print(self, y: Int): 78 | for i in range(self.rows): 79 | print(self.nums[i * self.cols + y], "", end="") 80 | print() 81 | 82 | 83 | fn main() raises: 84 | f = open("day09.txt", "r") 85 | lines = make_parser["\n"](f.read()) 86 | first = make_parser[" "](lines.get(0)) 87 | var mat = Matrix2D(first.length(), lines.length()) 88 | 89 | @parameter 90 | fn parse() -> Int64: 91 | for i in range(lines.length()): 92 | mat.store_column(i, lines.get(i)) 93 | return lines.length() 94 | 95 | @parameter 96 | fn part1() -> Int64: 97 | var work = mat 98 | for l in range(first.length(), 0, -1): 99 | work.reduce_down(l) 100 | return work.sum() 101 | 102 | @parameter 103 | fn part2() -> Int64: 104 | var work = mat 105 | for l in range(first.length() - 1): 106 | work.reduce_up(l) 107 | return work.dsum() 108 | 109 | minibench[parse]("parse") 110 | minibench[part1]("part1") 111 | minibench[part2]("part2") 112 | 113 | print(lines.length(), "lines") 114 | print(first.length(), "items each") 115 | print(mat.rows * mat.cols, "cells") 116 | -------------------------------------------------------------------------------- /day09.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | def differential(s): 4 | d = [] 5 | orr = 0 6 | for i in range(1,len(s)): 7 | t = s[i] - s[i-1] 8 | orr |= t 9 | d.append(t) 10 | s.clear() 11 | s.extend(d) 12 | return orr 13 | 14 | def diff1(s): 15 | r = [s[-1]] 16 | while differential(s): 17 | r.append(s[-1]) 18 | return sum(r) 19 | 20 | def diff2(s): 21 | l = [s[0]] 22 | while differential(s): 23 | l.append(s[0]) 24 | for i in range(len(l)-2,-1,-1): 25 | l[i] -= l[i+1] 26 | return l[0] 27 | 28 | lines = open("day09.txt").read().split("\n") 29 | seqs = [] 30 | 31 | def parse(): 32 | seqs.clear() 33 | for l in lines: 34 | seqs.append(list(map(int, l.split(" ")))) 35 | return len(seqs) 36 | 37 | def part1(): 38 | return sum([diff1(s.copy()) for s in seqs]) 39 | 40 | def part2(): 41 | return sum([diff2(s.copy()) for s in seqs]) 42 | 43 | parse() 44 | print(part1()) 45 | print(part2()) 46 | 47 | minibench({"parse": parse, "part1": part1, "part2": part2}) 48 | -------------------------------------------------------------------------------- /day09sym.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from array import Array 4 | 5 | 6 | struct SymSolver: 7 | # A vector of up to 32 symbolic vectors, representing up to 32 variables. 8 | # We need the number to be a power of two to make the SIMD code simpler. 9 | var syms: Array[DType.int32] 10 | var size: Int 11 | 12 | # initialize and set the values to an 'identity matrix' of sorts. 13 | fn __init__(inout self, count: Int): 14 | self.syms = Array[DType.int32](256 * 32) 15 | self.size = count 16 | for i in range(count): 17 | var s = SIMD[DType.int32, 32](0) 18 | s[i] = 1 19 | self.syms.store[width=32](i * 32, s) 20 | 21 | # Computes the grid and returns the flat multiplier array (and it's mirror image) 22 | fn compute(inout self) -> Tuple[SIMD[DType.int32, 32], SIMD[DType.int32, 32]]: 23 | var f = SIMD[DType.int32, 32](0) 24 | # This is exactly the same as the specified problem, but working on 'symbolic' numbers 25 | # The math is the same, though, just instead of adding individual values, we do 32 at a time 26 | for k in range(self.size): 27 | # previous item 28 | var p = self.syms.load[width=32](0) 29 | for i in range(self.size - k - 1): 30 | # next item 31 | n = self.syms.load[width=32](i * 32 + 32) 32 | # compute diff and store back 33 | self.syms.store[width=32](i * 32, n - p) 34 | p = n 35 | # add the last element to our formula 36 | f += p 37 | # Compute the flipped version of the formula for the part 2 38 | # (you could flip the data matrix, but that's way more expensive) 39 | var r = SIMD[DType.int32, 32](0) 40 | for i in range(self.size): 41 | r[i] = f[self.size - i - 1] 42 | return (f, r) 43 | 44 | 45 | struct VecMatrix: 46 | """ 47 | Represents the input matrix, with each line stored in a single SIMD vector. 48 | Can handle up to 32 elements per line. 49 | """ 50 | 51 | var nums: Array[DType.int32] 52 | var cols: Int 53 | var rows: Int 54 | 55 | # initializer. No need to clear, fma will ignore unused cells. 56 | fn __init__(inout self, rows: Int, cols: Int): 57 | self.cols = cols 58 | self.rows = 0 59 | # ensure everything is padded to 32. 60 | self.nums = Array[DType.int32](256 * 32) 61 | 62 | fn clear(inout self): 63 | self.rows = 0 64 | 65 | # parse and store an input line as a column in the matrix 66 | fn add_row(inout self, s: StringSlice): 67 | p = make_parser[" "](s) 68 | # When storing data, we just pad everything to 32, no SIMD here 69 | for i in range(p.length()): 70 | self.nums[self.rows * 32 + i] = int(atoi(p.get(i))) 71 | self.rows += 1 72 | 73 | # multiply each vector by the mult parameter and retun sum of results 74 | fn fma(self, mult: SIMD[DType.int32, 32]) -> Int: 75 | var acc = SIMD[DType.int32, 32](0) 76 | for i in range(self.rows): 77 | # multiply the whole row by the whole formula in one go 78 | # (well, not really one op, unless you have 1024-bit SIMD operations. 79 | # Mojo doesn't even do AVX512, so this breaks down to 4x8-wide ops with AVX2. 80 | # Or equivalent. Manually doing 3x8 is a tiny bit faster but too 81 | # complicated) 82 | acc = self.nums.load[width=32](i * 32).fma(mult, acc) 83 | return int(acc.reduce_add()) 84 | 85 | 86 | fn main() raises: 87 | f = open("day09.txt", "r") 88 | lines = make_parser["\n"](f.read()) 89 | first = make_parser[" "](lines.get(0)) 90 | var mat = VecMatrix(lines.length(), first.length()) 91 | var flat = SIMD[DType.int32, 32](0) 92 | var talf = SIMD[DType.int32, 32](0) 93 | 94 | @parameter 95 | fn parse() -> Int64: 96 | var solver = SymSolver(first.length()) 97 | (flat, talf) = solver.compute() 98 | mat.clear() 99 | for i in range(lines.length()): 100 | mat.add_row(lines.get(i)) 101 | return lines.length() 102 | 103 | @parameter 104 | fn part1() -> Int64: 105 | return mat.fma(flat) 106 | 107 | @parameter 108 | fn part2() -> Int64: 109 | return mat.fma(talf) 110 | 111 | minibench[parse]("parse") 112 | minibench[part1]("part1") 113 | minibench[part2]("part2") 114 | 115 | print(lines.length(), "lines") 116 | print(first.length(), "items each") 117 | print(mat.cols, "vectors") 118 | print(flat) 119 | -------------------------------------------------------------------------------- /day09sym.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | # we first solve the problem using symbolic math to get the 4 | # direct formula that works for any input. In symbolic representation 5 | # we'll use a k-element vector to represent k terms, with 1 at position 6 | # i generated as follows: 7 | def sym(k, i): 8 | ret = [0] * k 9 | ret[i] = 1 10 | return ret 11 | 12 | # addition or subtraction is just adding vectors together 13 | def sym_add(seq1, seq2, sign): 14 | return [seq1[i] + (seq2[i] * sign) for i in range(len(seq1))] 15 | 16 | lines = open("day09.txt").read().split("\n") 17 | flat = [] 18 | seqs = [] 19 | K = 0 20 | 21 | # prepare the symbolic forumla in flat and parse inputs into seqs 22 | def prep(): 23 | global flat, K 24 | seqs.clear() 25 | for l in lines: 26 | seqs.append(list(map(int, l.split(" ")))) 27 | flat.clear() 28 | K = len(seqs[0]) 29 | syms = [sym(K, i) for i in range(K)] 30 | flat = [0] * K 31 | for round in range(K): 32 | for i in range(K - round - 1): 33 | syms[i] = sym_add(syms[i + 1], syms[i], -1) 34 | flat = sym_add(flat, syms[K - round - 1], 1) 35 | return K 36 | 37 | def part1(): 38 | sum1 = 0 39 | for s in seqs: 40 | for i in range(K): 41 | sum1 += flat[i] * s[i] 42 | return sum1 43 | 44 | def part2(): 45 | sum2 = 0 46 | for s in seqs: 47 | for i in range(K): 48 | sum2 += flat[i] * s[K - i - 1] 49 | return sum2 50 | 51 | prep() 52 | print(flat) 53 | print(part1()) 54 | print(part2()) 55 | 56 | minibench({"parse": prep, "part1": part1, "part2": part2}) -------------------------------------------------------------------------------- /day10.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day10.txt").read().split("\n") 4 | visited = set() 5 | 6 | def part1(): 7 | start = None 8 | for y in range(len(lines)): 9 | if "S" in lines[y]: 10 | x = lines[y].find("S") 11 | start = (y, x) 12 | break 13 | visited.clear() 14 | visited.add((y,x)) 15 | if lines[y][x + 1] in ("J", "-", "7"): 16 | current = (y, x + 1) 17 | if lines[y][x - 1] in ("L", "-", "F"): 18 | current = (y, x - 1) 19 | if lines[y - 1][x] in ("|", "F", "7"): 20 | current = (y - 1, x) 21 | if lines[y + 1][x] in ("|", "L", "J"): 22 | current = (y + 1, x) 23 | mapping = { 24 | "-": [(0, 1), (0, -1)], 25 | "|": [(1, 0), (-1, 0)], 26 | "L": [(0, 1), (-1, 0)], 27 | "F": [(0, 1), (1, 0)], 28 | "7": [(0, -1), (1, 0)], 29 | "J": [(0, -1), (-1, 0)], 30 | } 31 | prev = start 32 | while current != start: 33 | visited.add(current) 34 | (y, x) = current 35 | char = lines[y][x] 36 | for dy, dx in mapping[char]: 37 | next = (y + dy, x + dx) 38 | if next != prev: 39 | prev = current 40 | current = next 41 | break 42 | return len(visited) // 2 43 | 44 | def part2(): 45 | cnt = 0 46 | skip = " " 47 | for y in range(len(lines)): 48 | out = True 49 | for x in range(len(lines[y])): 50 | if (y, x) in visited: 51 | # start of the fence, maybe 52 | if lines[y][x] == "F": 53 | skip = "7" 54 | elif lines[y][x] == "L": 55 | skip = "J" 56 | elif lines[y][x] == "-": 57 | # walking along the fence, whatever. 58 | continue 59 | else: 60 | # equivalent to "|" now 61 | if lines[y][x] != skip: 62 | out = not out 63 | # stop walking along the fence 64 | skip = " " 65 | elif not out: 66 | cnt += 1 67 | return cnt 68 | 69 | print(part1()) 70 | print(part2()) 71 | 72 | minibench({"part1": part1, "part2": part2}) 73 | -------------------------------------------------------------------------------- /day11.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from array import Array 4 | 5 | fn main() raises: 6 | f = open("day11.txt", "r") 7 | lines = make_parser['\n'](f.read()) 8 | counter = 0 9 | 10 | @parameter 11 | fn compute(cosmic_constant: Int64) -> Int64: 12 | # initializers for blank space detection 13 | dimy = lines.length() 14 | dimx = lines.get(0).size 15 | var vexp = Array[DType.int64](256) 16 | var hexp = Array[DType.int64](256) 17 | var hsum = Array[DType.uint8](256) 18 | var vsum = Array[DType.uint8](256) 19 | # find empty lines 20 | for i in range(dimy): 21 | for j in range(dimx): 22 | hsum[j] |= lines[i][j] 23 | vsum[i] |= lines[i][j] 24 | # compute h & v expansion 25 | alias cDot = ord(".") 26 | for i in range(dimy): 27 | if vsum[i] == cDot: 28 | vexp[i] = vexp[i - 1] + cosmic_constant 29 | else: 30 | vexp[i] = vexp[i - 1] 31 | for i in range(dimx): 32 | if hsum[i] == cDot: 33 | hexp[i] = hexp[i - 1] + cosmic_constant 34 | else: 35 | hexp[i] = hexp[i - 1] 36 | 37 | # initializers for space sweeper 38 | var psum = Array[DType.int64](256) 39 | var nsum = Array[DType.int64](256) 40 | var lcnt = Array[DType.int64](256) 41 | 42 | var dsum : Int64 = 0 # result 43 | var vtot : Int64 = 0 # total sum of flipped coordinates 44 | var stot : Int64 = 0 # total count of all stars 45 | alias cHash = ord('#') 46 | for i in range(dimy): 47 | var pst : Int64 = 0 # sum of coordinates in the left-up quadrant 48 | var lct : Int64 = 0 # count of stars in the quadrant 49 | var nst : Int64 = 0 # sum of flipped coordinates 50 | for j in range(dimx): 51 | if lines[i][j] == cHash: 52 | # psum[j] is the sum of coordinates of all stars so far with x==j 53 | # lst is the sum of psum over 0..j now. We have hct stars there. 54 | cp = i + vexp[i] + j + hexp[j] 55 | psum[j] += cp 56 | lcnt[j] += 1 57 | pst += psum[j] 58 | lct += lcnt[j] 59 | 60 | # sum of distances to everything on the left/upwards is equal to 61 | # hct times current coordinates minus all other coordinates 62 | var dss = lct * cp - pst 63 | 64 | # vsum is similar but with the x-coordinates negated. 65 | # we also keep global vtot & stot for these, so we can compute 66 | # the right-up quadrant sum easily 67 | cn = i + vexp[i] - j - hexp[j] 68 | nsum[j] += cn 69 | nst += nsum[j] 70 | vtot += cn 71 | stot += 1 72 | 73 | # Now to get the top-right quadrant 74 | dss += (stot - lct) * cn - (vtot - nst) 75 | # print(i,j,"pst",pst,"lct",lct,"nst",nst,"stot",stot,"rct",stot - lct,"dss",dss) 76 | dsum += dss 77 | else: 78 | pst += psum[j] 79 | lct += lcnt[j] 80 | nst += nsum[j] 81 | counter += 1 82 | return dsum 83 | 84 | 85 | @parameter 86 | fn part1() -> Int64: 87 | return compute(1) 88 | 89 | 90 | @parameter 91 | fn part2() -> Int64: 92 | return compute(999999) 93 | 94 | minibench[part1]("part1") 95 | minibench[part2]("part2") 96 | 97 | print(lines.length(), "lines", counter) 98 | -------------------------------------------------------------------------------- /day11.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day11.txt").read().split("\n") 4 | 5 | def compute(cosmic_constant): 6 | # initializers for blank space detection 7 | vexp = [cosmic_constant] * len(lines) 8 | hexp = [cosmic_constant] * len(lines[0]) 9 | # find empty lines, eliminate 10 | for i in range(len(lines)): 11 | for j in range(len(lines[i])): 12 | if lines[i][j] == '#': 13 | hexp[j]=vexp[i]=0 14 | # sum up 15 | for i in range(1, len(hexp)): 16 | hexp[i] += hexp[i-1] 17 | for i in range(1, len(vexp)): 18 | vexp[i] += vexp[i-1] 19 | 20 | # initializers for space sweeper 21 | psum = [0] * len(lines[0]) 22 | nsum = [0] * len(lines[0]) 23 | lcnt = [0] * len(lines[0]) 24 | 25 | dsum = 0 # result 26 | vtot = 0 # total sum of flipped coordinates 27 | stot = 0 # total count of all stars 28 | for i in range(len(lines)): 29 | pst = 0 # sum of coordinates in the left-up quadrant 30 | lct = 0 # count of stars in the quadrant 31 | nst = 0 # sum of flipped coordinates 32 | for j in range(len(lines[i])): 33 | if lines[i][j] == "#": 34 | # psum[j] is the sum of coordinates of all stars so far with x==j 35 | # lst is the sum of psum over 0..j now. We have hct stars there. 36 | cp = i + vexp[i] + j + hexp[j] 37 | psum[j] += cp 38 | lcnt[j] += 1 39 | pst += psum[j] 40 | lct += lcnt[j] 41 | 42 | # sum of distances to everything on the left/upwards is equal to 43 | # hct times current coordinates minus all other coordinates 44 | dss = lct * cp - pst 45 | 46 | # vsum is similar but with the x-coordinates negated. 47 | # we also keep global vtot & stot for these, so we can compute 48 | # the right-up quadrant sum easily 49 | cn = i + vexp[i] - j - hexp[j] 50 | nsum[j] += cn 51 | nst += nsum[j] 52 | vtot += cn 53 | stot += 1 54 | 55 | # Now to get the top-right quadrant 56 | dss += (stot - lct) * cn - (vtot - nst) 57 | # print(i,j,"pst",pst,"lct",lct,"nst",nst,"stot",stot,"rct",stot - lct,"dss",dss) 58 | dsum += dss 59 | else: 60 | pst += psum[j] 61 | lct += lcnt[j] 62 | nst += nsum[j] 63 | return dsum 64 | 65 | def part1(): 66 | return compute(1) 67 | 68 | def part2(): 69 | return compute(999999) 70 | 71 | print(part1()) 72 | print(part2()) 73 | 74 | minibench({"part1": part1, "part2": part2}) -------------------------------------------------------------------------------- /day11hv.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from array import Array 4 | 5 | fn main() raises: 6 | f = open("day11.txt", "r") 7 | lines = make_parser['\n'](f.read()) 8 | 9 | fn compute1(owned exp: Array[DType.int64], owned cnt: Array[DType.int64]) -> Int64: 10 | var pos: Int64 = 0 11 | var tot: Int64 = 0 12 | var dst: Int64 = 0 13 | var sum: Int64 = 0 14 | for i in range(cnt.size): 15 | tot += cnt[i] 16 | dst += cnt[i] * pos 17 | sum += cnt[i] * (tot * pos - dst) 18 | pos += exp[i] 19 | return sum 20 | 21 | @parameter 22 | fn compute(cosmic_constant: Int64) -> Int64: 23 | # initializers for blank space detection 24 | dimy = lines.length() 25 | dimx = lines.get(0).size 26 | var vexp = Array[DType.int64](256, cosmic_constant) 27 | var hexp = Array[DType.int64](256, cosmic_constant) 28 | var vcnt = Array[DType.int64](256) 29 | var hcnt = Array[DType.int64](256) 30 | # find empty lines 31 | alias cHash = ord('#') 32 | for i in range(dimy): 33 | for j in range(dimx): 34 | if lines[i][j] == cHash: 35 | vexp[i] = 1 36 | hexp[j] = 1 37 | vcnt[i] += 1 38 | hcnt[j] += 1 39 | return compute1(hexp, hcnt) + compute1(vexp, vcnt) 40 | 41 | @parameter 42 | fn part1() -> Int64: 43 | return compute(2) 44 | 45 | @parameter 46 | fn part2() -> Int64: 47 | return compute(1000000) 48 | 49 | minibench[part1]("part1") 50 | minibench[part2]("part2") 51 | 52 | print(lines.length(), "lines") 53 | -------------------------------------------------------------------------------- /day11hv.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day11.txt").read().split("\n") 4 | 5 | def compute(cosmic_constant): 6 | # initializers for blank space detection 7 | vexp = [cosmic_constant] * len(lines) 8 | hexp = [cosmic_constant] * len(lines[0]) 9 | vcnt = [0] * len(lines) 10 | hcnt = [0] * len(lines[0]) 11 | # find empty lines and count stars at each x and y separately 12 | for i in range(len(lines)): 13 | for j in range(len(lines[i])): 14 | if lines[i][j] == '#': 15 | vexp[i]=hexp[j]=1 16 | vcnt[i]+=1 17 | hcnt[j]+=1 18 | sum = 0 19 | for (a,b) in [(hcnt,hexp),(vcnt,vexp)]: 20 | pos = cnt = dst = 0 21 | for i in range(len(a)): 22 | cnt += a[i] 23 | dst += a[i]*pos 24 | sum += a[i]*(cnt*pos - dst) 25 | pos += b[i] 26 | return sum 27 | 28 | def part1(): 29 | return compute(2) 30 | 31 | def part2(): 32 | return compute(1000000) 33 | 34 | print(part1()) 35 | print(part2()) 36 | 37 | minibench({"part1": part1, "part2": part2}) -------------------------------------------------------------------------------- /day12.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import run_multiline_task 3 | from array import Array 4 | from memory import memcpy, memset 5 | from os.atomic import Atomic 6 | 7 | struct Solver: 8 | var cache : Array[DType.int64] 9 | var csize : Int 10 | var buf : Array[DType.uint8] 11 | var syms : StringSlice 12 | var rules : List[Int] 13 | 14 | fn __init__(inout self, line: StringSlice, mult: Int = 1): 15 | alias cAsk = ord('?') 16 | p = make_parser[' '](line) 17 | r = make_parser[','](p[1]) 18 | l = p[0].size * mult + mult - 1 19 | self.csize = (l+2) * 32 20 | self.cache = Array[DType.int64](self.csize, -1) 21 | self.buf = Array[DType.uint8](l) 22 | self.syms = StringSlice(self.buf.data, l) 23 | self.rules = List[Int](capacity = r.length() * mult) 24 | for i in range(r.length()): 25 | self.rules.append(int(atoi(r[i]))) 26 | memcpy(self.buf.data, line.ptr, p[0].size) 27 | # replicate 28 | for i in range (1, mult): 29 | ofs = (p[0].size + 1) * i 30 | self.buf.store(ofs - 1, cAsk) 31 | memcpy(self.buf.data.offset(ofs), line.ptr, p[0].size) 32 | for j in range(r.length()): 33 | self.rules.append(self.rules[j]) 34 | 35 | fn count(inout self, sp: Int, rp: Int) -> Int64: 36 | var sp1 = sp 37 | # skip empty 38 | alias cDot = ord('.') 39 | alias cHash = ord('#') 40 | alias cAsk = ord('?') 41 | while sp1 < self.syms.size and self.syms[sp1] == cDot: 42 | sp1 += 1 43 | ck = sp1*32+rp 44 | if self.cache[ck] >= 0: 45 | return self.cache[ck] 46 | var ret : Int64 = 0 47 | if rp == self.rules.size: 48 | if self.syms.find(cHash, sp) < 0: 49 | ret = 1 50 | self.cache[ck] = ret 51 | return ret 52 | elif sp >= self.syms.size: 53 | return 0 54 | var l = self.rules[rp] 55 | var s = sp1 56 | var p = sp1 57 | # scan the current rule 58 | var f = False 59 | while l > 0 and p < self.syms.size and (self.syms[p] == cAsk or self.syms[p] == cHash): 60 | f = f or self.syms[p] == cHash 61 | p += 1 62 | l -= 1 63 | # not able to fit 64 | if l > 0 and (f or p >= self.syms.size): 65 | return 0 66 | if l > 0: 67 | ret = self.count(p + 1, rp) 68 | self.cache[ck] = ret 69 | return ret 70 | # Following symbol has to be '.' or '?' or EOL 71 | if p == self.syms.size or self.syms[p] != cHash: 72 | ret += self.count(p + 1, rp + 1) 73 | # turn '?' at the start into '.' 74 | while p < self.syms.size and self.syms[s] == cAsk and (self.syms[p] == cHash or self.syms[p] == cAsk): 75 | f = f or self.syms[p] == cHash 76 | s += 1 77 | p += 1 78 | if p == self.syms.size or self.syms[p] != cHash: 79 | ret += self.count(p + 1, rp + 1) 80 | if not f and p < self.syms.size: 81 | ret += self.count(p + 1, rp) 82 | self.cache[ck] = ret 83 | return ret 84 | 85 | 86 | fn main() raises: 87 | f = open("day12.txt", "r") 88 | lines = make_parser['\n'](f.read()) 89 | var sum1 = Atomic[DType.int64](0) 90 | var sum2 = Atomic[DType.int64](0) 91 | 92 | alias chunk_size = 20 93 | 94 | @parameter 95 | fn chunk_step(i: Int, mult: Int = 1) -> Int64: 96 | var lsum : Int64 = 0 97 | for j in range(chunk_size): 98 | var solver = Solver(lines[i * chunk_size + j], mult) 99 | lsum += solver.count(0, 0) 100 | return lsum 101 | 102 | @parameter 103 | fn step1(i : Int): 104 | sum1 += chunk_step(i, 1) 105 | 106 | @parameter 107 | fn step2(i : Int): 108 | sum2 += chunk_step(i, 5) 109 | 110 | @parameter 111 | fn results(): 112 | print(int(sum1.value)) 113 | print(int(sum2.value)) 114 | 115 | run_multiline_task[step1, step2, results](lines.length() // chunk_size) 116 | 117 | print(lines.length(), "lines", sum1.value, sum2.value) 118 | -------------------------------------------------------------------------------- /day12.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | lines = open("day12.txt").read().split("\n") 3 | 4 | cache = {} 5 | def count(syms, sp, rules, rp): 6 | # skip empty 7 | while sp < len(syms) and syms[sp] == ".": 8 | sp += 1 9 | if (sp, rp) in cache: 10 | return cache[(sp, rp)] 11 | if rp == len(rules): 12 | if all([c == "." or c == "?" for c in syms[sp:]]): 13 | z = 1 14 | else: 15 | z = 0 16 | cache[(sp, rp)] = z 17 | return z 18 | if sp >= len(syms): 19 | return 0 20 | l = rules[rp] 21 | s = p = sp 22 | # scan the current rule 23 | f = False 24 | while l > 0 and p < len(syms) and (syms[p] == "?" or syms[p] == "#"): 25 | f = f or syms[p] == "#" 26 | p += 1 27 | l -= 1 28 | # not able to fit 29 | if l > 0 and (f or p >= len(syms)): 30 | return 0 31 | if l > 0 and p < len(syms): 32 | return count(syms, p + 1, rules, rp) 33 | cnt = 0 34 | # Following symbol has to be '.' or '?' or EOL 35 | if p == len(syms) or syms[p] != "#": 36 | cnt += count(syms, p + 1, rules, rp + 1) 37 | # turn '?' at the start into '.' 38 | while p < len(syms) and syms[s] == "?" and (syms[p] == "#" or syms[p] == "?"): 39 | f = f or syms[p] == "#" 40 | s += 1 41 | p += 1 42 | if p == len(syms) or syms[p] != "#": 43 | cnt += count(syms, p + 1, rules, rp + 1) 44 | if not f and p < len(syms): 45 | cnt += count(syms, p + 1, rules, rp) 46 | cache[(sp, rp)] = cnt 47 | return cnt 48 | 49 | def run(mult): 50 | tot = 0 51 | for l in lines: 52 | a, b = l.split() 53 | c = list(map(int, b.split(","))) 54 | cache.clear() 55 | aa = a 56 | if (mult > 1): 57 | aa = ((a + "?") * 5)[:-1] 58 | z = count(aa , 0, c * mult , 0) 59 | tot += z 60 | return tot 61 | 62 | def part1(): 63 | return run(1) 64 | 65 | def part2(): 66 | return run(5) 67 | 68 | print(part1()) 69 | print(part2()) 70 | 71 | minibench({"part1": part1, "part2": part2}) -------------------------------------------------------------------------------- /day13.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from array import Array 4 | from bit import pop_count 5 | 6 | # Popcnt based on https://en.wikipedia.org/wiki/Hamming_weight 7 | fn popcnt(v: SIMD[DType.int32, 1]) -> Int: 8 | alias m1 = 0x5555555555555555 9 | alias m2 = 0x3333333333333333 10 | alias m4 = 0x0F0F0F0F0F0F0F0F 11 | alias h01 = 0x0101010101010101 12 | var x: UInt64 = int(v) 13 | x -= (x >> 1) & m1 14 | x = (x & m2) + ((x >> 2) & m2) 15 | x = (x + (x >> 4)) & m4 16 | return int((x * h01) >> 56) 17 | 18 | 19 | fn main() raises: 20 | f = open("day13.txt", "r") 21 | lines = make_parser["\n"](f.read(), False) 22 | mats = Array[DType.int32](200 * 20) 23 | var matcnt: Int = 0 24 | 25 | @parameter 26 | # A matrix is represented as a list of integers, with cells represented as single bits. 27 | # This finds for matching pairs of rows and then extends the match until running into 28 | # the border. If that happens, that's a match. Vertical matches use rotated arrays 29 | # represented the same way. 30 | fn find_match(ofs: Int, size: Int) -> Int64: 31 | for i in range(1, size): 32 | var b = 0 33 | while i > b and i + b < size and mats[ofs + i - b - 1] == mats[ofs + i + b]: 34 | b += 1 35 | if i == b or i + b == size: 36 | return i 37 | return 0 38 | 39 | @parameter 40 | # Same as the trivial match, but uses a 'bit budget' and a bit-difference comparison 41 | # based on popcnt. We set the budget to 1 initially and allow a mismatch up to a budget. 42 | # Then the algorithm works just like the regular one, except we require that the budget 43 | # is actually used up at the end. 44 | fn find_match_dirty(ofs: Int, size: Int) -> Int64: 45 | for i in range(1, size): 46 | var b = 0 47 | var tbb : Int32 = 1 48 | while i > b and i + b < size: 49 | pc = pop_count(mats[ofs + i - b - 1] ^ mats[ofs + i + b]) 50 | if pc > tbb: 51 | break 52 | # apparently compiler is smart enough to not compute this 53 | tbb -= pc 54 | b += 1 55 | if tbb == 0 and (i == b or i + b == size): 56 | return i 57 | return 0 58 | 59 | @parameter 60 | fn parse() -> Int64: 61 | var ofs = 400 62 | var prev = 0 63 | mats.fill(0) 64 | matcnt = 0 65 | for l in range(lines.length()): 66 | # Empty lines trigger parsing the preceding array 67 | if lines[l].size == 0: 68 | dimY = l - prev 69 | r = mats.data.offset(ofs) 70 | dimX = lines[l - 1].size 71 | c = mats.data.offset(ofs + dimY) 72 | # Store each cell into bits of r and c 73 | # array r is represented row-wise, c is column-wise (transposed r) 74 | # all data is stored flat in the mats array. 75 | for i in range(dimY): 76 | for j in range(dimX): 77 | v = int(lines[prev + i][j]) & 1 78 | r[i] = r[i] * 2 + v 79 | c[j] = c[j] * 2 + v 80 | prev = l + 1 81 | # save positions at the start of mats array 82 | mats[matcnt * 4] = ofs 83 | mats[matcnt * 4 + 1] = dimY 84 | mats[matcnt * 4 + 2] = ofs + dimY 85 | mats[matcnt * 4 + 3] = dimX 86 | ofs += dimY + dimX 87 | matcnt += 1 88 | return matcnt 89 | 90 | @parameter 91 | fn run[match_fn: fn (Int, Int, /) capturing -> Int64]() -> Int64: 92 | var sum: Int64 = 0 93 | for i in range(matcnt): 94 | sum += 100 * match_fn(int(mats[i * 4]), int(mats[i * 4 + 1])) 95 | sum += match_fn(int(mats[i * 4 + 2]), int(mats[i * 4 + 3])) 96 | return sum 97 | 98 | @parameter 99 | fn part1() -> Int64: 100 | return run[find_match]() 101 | 102 | @parameter 103 | fn part2() -> Int64: 104 | return run[find_match_dirty]() 105 | 106 | minibench[parse]("parse") 107 | minibench[part1]("part1") 108 | minibench[part2]("part2") 109 | 110 | print(lines.length(), "lines") 111 | print(mats.size, "bytes used") 112 | print(matcnt, "arrays") 113 | -------------------------------------------------------------------------------- /day13.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day13.txt").read().split("\n") 4 | mats = [] 5 | 6 | def find_match(a): 7 | for i in range(1,len(a)): 8 | b = 0 9 | while i>b and i+bb and i+b Int: 14 | var v = 0 15 | for i in range(s.size): 16 | v = int((v + s[i].cast[DType.uint8]()) * 17) & 255 17 | return v 18 | 19 | @parameter 20 | fn part1() -> Int64: 21 | var sum = 0 22 | for i in range(tokens.length()): 23 | sum += hash(tokens[i]) 24 | return sum 25 | 26 | # a slightly better hash function 27 | fn fnv1a(s: StringSlice) -> UInt32: 28 | var hash: UInt32 = 2166136261 29 | for i in range(s.size): 30 | hash = (hash ^ s[i].cast[DType.uint32]()) * 16777619 31 | return hash 32 | 33 | @parameter 34 | fn part2() -> Int64: 35 | for i in range(256): 36 | boxes[i*16] = 1 37 | for t in range(tokens.length()): 38 | tok = tokens[t] 39 | alias cDash = ord('-') 40 | if tok[tok.size-1] == cDash: 41 | l = tok[:tok.size-1] 42 | hc = hash(l) 43 | fc = fnv1a(l) % modulus 44 | var d = 0 45 | # remove fc from box 46 | for i in range(1,int(boxes[hc*16])): 47 | if boxes[hc*16+i] & 0xFFFFFF == fc: 48 | d = 1 49 | elif d > 0: 50 | boxes[hc*16+i-d] = boxes[hc*16+i] 51 | if d > 0: 52 | boxes[hc*16] -= d 53 | else: # =X 54 | l = tok[:tok.size-2] 55 | hc = hash(l) 56 | fc = fnv1a(l) % modulus 57 | alias cZero = ord('0') 58 | st = (tok[tok.size-1] - cZero).cast[DType.uint32]() << 24 59 | var add = True 60 | # add (fc,st) to box 61 | for i in range(1,int(boxes[hc*16])): 62 | if boxes[hc*16+i] & 0xFFFFFF == fc: 63 | boxes[hc*16+i] = st + fc 64 | add = False 65 | break 66 | if add: 67 | boxes[hc*16 + int(boxes[hc*16])] = st + fc 68 | boxes[hc*16] += 1 69 | var sum = 0 70 | for i in range(256): 71 | for j in range(1, int(boxes[i*16])): 72 | sum += (i+1)*j*int(boxes[i*16+j] >> 24) 73 | return sum 74 | 75 | minibench[part1]("part1") 76 | minibench[part2]("part2") 77 | 78 | print(tokens.length(), "tokens") 79 | print(boxes.bytecount(), "boxes") 80 | -------------------------------------------------------------------------------- /day15.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | tokens = open("day15.txt").read().split(",") 4 | 5 | def hash(s): 6 | v = 0 7 | for c in s: 8 | v += ord(c) 9 | v *= 17 10 | v &= 255 11 | return v 12 | 13 | def part1(): 14 | return sum(map(hash,tokens)) 15 | 16 | def part2(): 17 | strength = {} 18 | boxes = [] 19 | for i in range(256): 20 | # sets are slower here, anyway. max number of items in the list is ~6 21 | boxes.append([]) 22 | for tok in tokens: 23 | if tok[-1] == "-": 24 | l = tok[:-1] 25 | hc = hash(l) 26 | if l in boxes[hc]: 27 | boxes[hc].remove(l) 28 | else: 29 | l = tok[:-2] 30 | hc = hash(l) 31 | st = int(tok[-1]) 32 | # checking via strength map is slower. 33 | if l not in boxes[hc]: 34 | boxes[hc].append(l) 35 | strength[l] = st 36 | sum = 0 37 | for i in range(256): 38 | for j,l in enumerate(boxes[i]): 39 | sum += (i+1)*(j+1)*strength[l] 40 | return sum 41 | 42 | print(part1()) 43 | print(part2()) 44 | 45 | minibench({"part1": part1, "part2": part2}) 46 | -------------------------------------------------------------------------------- /day16.cc: -------------------------------------------------------------------------------- 1 | #define ANKERL_NANOBENCH_IMPLEMENT 2 | #include "nanobench.h" 3 | #include 4 | using namespace std; 5 | 6 | typedef struct pt { 7 | int x, y, dx, dy; 8 | } pt; 9 | 10 | int bfs(pt start, const vector &tiles) { 11 | pt current[1000], next[1000]; 12 | pt *currptr = ¤t[0], *nextptr = &next[0]; 13 | char visited[110 * 110]; 14 | memset(visited, 0, 110 * 110); 15 | int curs = 1, dimx = tiles[0].size(), dimy = tiles.size(), warm = 0; 16 | current[0] = start; 17 | while (curs > 0) { 18 | int nexs = 0; 19 | for (int i = 0; i < curs; i++) { 20 | pt t = currptr[i]; 21 | t.x += t.dx; 22 | t.y += t.dy; 23 | if (t.x < 0 || t.y < 0 || t.x >= dimx || t.y >= dimy) 24 | continue; 25 | int op = t.y * dimx + t.x; 26 | int bp = 1 << ((t.dy + 1) * 3 + (t.dx + 1)); 27 | // cout << t.dy << " " << t.dx << " " << bp << endl; 28 | if ((visited[op] & bp) != 0) 29 | continue; 30 | if (!visited[op]) 31 | warm++; 32 | visited[op] |= bp; 33 | char c = tiles[t.y][t.x]; 34 | if (c == '.' || (c == '|' && t.dx == 0) || (c == '-' && t.dy == 0)) { 35 | nextptr[nexs++] = t; 36 | } else if (c == '|') { 37 | nextptr[nexs++] = pt{t.x, t.y, 0, 1}; 38 | nextptr[nexs++] = pt{t.x, t.y, 0, -1}; 39 | } else if (c == '-') { 40 | nextptr[nexs++] = pt{t.x, t.y, 1, 0}; 41 | nextptr[nexs++] = pt{t.x, t.y, -1, 0}; 42 | } else if (c == '/') { 43 | nextptr[nexs++] = pt{t.x, t.y, -t.dy, -t.dx}; 44 | } else if (c == '\\') { 45 | nextptr[nexs++] = pt{t.x, t.y, t.dy, t.dx}; 46 | } 47 | } 48 | pt *tmp = currptr; 49 | currptr = nextptr; 50 | nextptr = tmp; 51 | curs = nexs; 52 | } 53 | return warm; 54 | } 55 | 56 | pt start(int i, int dimx, int dimy) { 57 | if (i < dimx) 58 | return pt{i, -1, 0, 1}; 59 | if (i < 2 * dimx) 60 | return pt{i - dimx, dimy, 0, -1}; 61 | if (i < 2 * dimx + dimy) 62 | return pt{-1, i - 2 * dimx, 1, 0}; 63 | return pt{dimy, i - 2 * dimx - dimy, -1, 0}; 64 | } 65 | 66 | int part2(const vector &lines) { 67 | int mm = 0; 68 | int dimx = lines.size(); 69 | int dimy = lines[0].size(); 70 | for (int i = 0; i < 2 * dimx + 2 * dimy; i++) { 71 | int t = bfs(start(i, dimx, dimy), lines); 72 | if (t > mm) 73 | mm = t; 74 | } 75 | return mm; 76 | } 77 | 78 | int part2_parallel(const vector &lines) { 79 | int dimx = lines.size(); 80 | int dimy = lines[0].size(); 81 | std::vector workers; 82 | std::vector results(24, 0); 83 | for (int n = 0; n < 24; n++) 84 | workers.push_back(std::thread([&] { 85 | int mm = 0; 86 | for (int i = n; i < 2 * dimx + 2 * dimy; i += 24) { 87 | int t = bfs(start(i, dimx, dimy), lines); 88 | if (t > mm) 89 | mm = t; 90 | } 91 | results[n] = mm; 92 | })); 93 | for (auto &worker: workers) worker.join(); 94 | auto it = max_element(begin(results), end(results)); 95 | return *it; 96 | } 97 | 98 | int main() { 99 | ios_base::sync_with_stdio(false); 100 | cin.tie(0); 101 | ifstream file("day16.txt"); 102 | string s; 103 | vector lines; 104 | while (getline(file, s)) 105 | lines.push_back(s); 106 | cout << bfs(pt{-1, 0, 1, 0}, lines) << endl; 107 | cout << part2(lines) << endl; 108 | cout << part2_parallel(lines) << endl; 109 | long tot = 0; 110 | ankerl::nanobench::Bench() 111 | .minEpochIterations(100) 112 | .run("part2", [&] { 113 | tot += part2(lines); 114 | ankerl::nanobench::doNotOptimizeAway(tot); 115 | }); 116 | ankerl::nanobench::Bench() 117 | .minEpochIterations(100) 118 | .run("part2_parallel", [&] { 119 | tot += part2_parallel(lines); 120 | ankerl::nanobench::doNotOptimizeAway(tot); 121 | }); 122 | } 123 | -------------------------------------------------------------------------------- /day16.mojo: -------------------------------------------------------------------------------- 1 | from algorithm import parallelize 2 | from parser import * 3 | from wrappers import minibench 4 | from array import Array 5 | from os.atomic import Atomic 6 | 7 | 8 | fn main() raises: 9 | f = open("day16.txt", "r") 10 | tiles = make_parser["\n"](f.read()) 11 | dimx = tiles.length() 12 | dimy = tiles[0].size 13 | scnt = dimx * 2 + dimy * 2 14 | ignored = Array[DType.int16](1000) 15 | var mmax = Atomic[DType.int64](0) 16 | 17 | @always_inline 18 | fn bidx(x: Int32, y: Int32) -> Int32: 19 | if (y + 1) % 111 != 0: 20 | return ((x + 1) % 2) * 110 + y + 1 21 | return 220 + ((y + 1) % 2) * 110 + x + 1 22 | 23 | @parameter 24 | fn bfs(start: SIMD[DType.int32, 4]) -> Int64: 25 | var current = Array[DType.int32](1000) 26 | var next = Array[DType.int32](1000) 27 | var visited = Array[DType.int8](110 * 110) 28 | (x, y, dx, dy) = (start[0], start[1], start[2], start[3]) 29 | b = bidx(x, y) 30 | if ignored[int(b)] != 0: 31 | return 0 32 | var curs = 4 33 | var warm = 0 34 | current.store[width=4](0, start) 35 | while curs > 0: 36 | var nexs = 0 37 | for i in range(0, curs, 4): 38 | tmp = current.load[width=4](i) 39 | (x, y, dx, dy) = (tmp[0], tmp[1], tmp[2], tmp[3]) 40 | # move out of the previous tile 41 | x += dx 42 | y += dy 43 | # out of bounds 44 | if x < 0 or y < 0 or x >= dimx or y >= dimy: 45 | ignored[int(bidx(x, y))] = 1 46 | continue 47 | # if we already _entered_ this tile this way, skip 48 | op = int(y * dimx + x) 49 | bp = int(1 << (dy + 1) * 3 + (dx + 1)) # maps to bits 5,3,2,0 50 | if visited[op] & bp != 0: 51 | continue 52 | if visited[op] == 0: 53 | warm += 1 54 | visited[op] |= bp 55 | t = tiles[int(y)][int(x)] 56 | alias cDot = ord(".") 57 | alias cPipe = ord("|") 58 | alias cDash = ord("-") 59 | alias cSlash = ord("/") 60 | alias cBack = ord("\\") 61 | # empty or ignored splitter 62 | if t == cDot or (t == cPipe and dx == 0) or (t == cDash and dy == 0): 63 | next.store[width=4](nexs, SIMD[DType.int32, 4](x, y, dx, dy)) 64 | nexs += 4 65 | # split vertically 66 | elif t == cPipe and dx != 0: 67 | next.store[width=4](nexs, SIMD[DType.int32, 4](x, y, 0, 1)) 68 | next.store[width=4](nexs + 4, SIMD[DType.int32, 4](x, y, 0, -1)) 69 | nexs += 8 70 | # or horizontally 71 | elif t == cDash and dy != 0: 72 | next.store[width=4](nexs, SIMD[DType.int32, 4](x, y, 1, 0)) 73 | next.store[width=4](nexs + 4, SIMD[DType.int32, 4](x, y, -1, 0)) 74 | nexs += 8 75 | # mirror 1 76 | elif t == cSlash: 77 | next.store[width=4](nexs, SIMD[DType.int32, 4](x, y, -dy, -dx)) 78 | nexs += 4 79 | # mirror 2 80 | elif t == cBack: 81 | next.store[width=4](nexs, SIMD[DType.int32, 4](x, y, dy, dx)) 82 | nexs += 4 83 | curs = nexs 84 | swap(current.data, next.data) 85 | return warm 86 | 87 | @parameter 88 | fn start(i: Int) -> SIMD[DType.int32, 4]: 89 | if i < dimx: 90 | return SIMD[DType.int32, 4](i, -1, 0, 1) 91 | if i < 2 * dimx: 92 | return SIMD[DType.int32, 4](i - dimx, dimy, 0, 1) 93 | if i < 2 * dimx + dimy: 94 | return SIMD[DType.int32, 4](-1, i - 2 * dimx, 1, 0) 95 | return SIMD[DType.int32, 4](dimy, i - 2 * dimx - dimy, -1, 0) 96 | 97 | @parameter 98 | fn part1() -> Int64: 99 | ignored[int(bidx(-1, 0))] = 0 100 | return bfs(SIMD[DType.int32, 4](-1, 0, 1, 0)) 101 | 102 | alias chunk_size = 10 103 | 104 | @parameter 105 | fn step2(i: Int): 106 | var lmax: Int64 = 0 107 | for j in range(chunk_size): 108 | lmax = max(lmax, bfs(start(i * chunk_size + j))) 109 | mmax.max(lmax) 110 | 111 | @parameter 112 | fn part2() -> Int64: 113 | ignored.zero() 114 | for i in range(scnt // chunk_size): 115 | step2(i) 116 | return mmax.value 117 | 118 | @parameter 119 | fn part2_parallel() -> Int64: 120 | ignored.zero() 121 | parallelize[step2](scnt // chunk_size) 122 | return mmax.value 123 | 124 | minibench[part1]("part1") 125 | minibench[part2]("part2") 126 | minibench[part2_parallel]("part2_parallel") 127 | 128 | print(tiles.length(), "tokens", dimx, dimy, scnt) 129 | print(ignored.bytecount(), "temp size", mmax.value) 130 | -------------------------------------------------------------------------------- /day16.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | tiles = open("day16.txt").read().split("\n") 4 | dimx = len(tiles[0]) 5 | dimy = len(tiles) 6 | 7 | def bfs(start, ignored): 8 | if start in ignored: 9 | return 0 10 | current = [ start ] 11 | visited = set() 12 | warm = set() 13 | while current: 14 | next = [] 15 | for (x,y,dx,dy) in current: 16 | # move out of the previous tile 17 | x += dx 18 | y += dy 19 | # out of bounds 20 | if x < 0 or y < 0 or x >= dimx or y >= dimy: 21 | ignored.add((x,y,-dx,-dy)) 22 | continue 23 | # if we already _entered_ this tile this way, skip 24 | if (x,y,dx,dy) in visited: 25 | continue 26 | visited.add((x,y,dx,dy)) 27 | warm.add((x,y)) 28 | t = tiles[y][x] 29 | # empty or ignored splitter 30 | if t == '.' or (t == '|' and dx == 0) or (t =='-' and dy == 0): 31 | next.append((x,y,dx,dy)) 32 | # split vertically 33 | elif t == '|' and dx != 0: 34 | next.append((x,y,0,1)) 35 | next.append((x,y,0,-1)) 36 | elif t == '-' and dy != 0: 37 | next.append((x,y,1,0)) 38 | next.append((x,y,-1,0)) 39 | # mirror 1 40 | elif t == '/': 41 | next.append((x,y,-dy,-dx)) 42 | # mirror 2 43 | elif t == '\\': 44 | next.append((x,y,dy,dx)) 45 | current = next 46 | return len(warm) 47 | 48 | def part1(): 49 | return bfs((-1,0,1,0),set()) 50 | 51 | def part2(): 52 | ig = set() 53 | m = 0 54 | for x in range(dimx): 55 | m = max(m, bfs((x,-1,0,1),ig)) 56 | m = max(m, bfs((x,dimy,0,-1),ig)) 57 | for y in range(dimy): 58 | m = max(m, bfs((-1,0,1,0),ig)) 59 | m = max(m, bfs((dimx,0,-1,0),ig)) 60 | return m 61 | 62 | print(part1()) 63 | print(part2()) 64 | 65 | minibench({"part1": part1, "part2": part2}) 66 | -------------------------------------------------------------------------------- /day17.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from array import Array 4 | from math import sqrt 5 | from utils.loop import unroll 6 | 7 | fn main() raises: 8 | f = open("day17.txt", "r") 9 | tiles = make_parser["\n"](f.read()) 10 | dimx = tiles.length() 11 | dimy = tiles[0].size 12 | alias maxq = 600 13 | alias maxd = 1200 14 | work = Array[DType.int32](maxd * maxq) # dijkstra tables 15 | 16 | # packs into 24-bit representation 17 | @parameter 18 | fn pack(x: Int, y: Int, dir: Int, r: Int) -> Int32: 19 | return ((y * dimx + x) << 6) + dir * 10 + r 20 | 21 | @parameter 22 | fn bfs(minrun: Int, maxrun: Int) -> Int64: 23 | alias IntPair = SIMD[DType.int32, 2] 24 | alias dirs = List[IntPair](IntPair(-1, 0), IntPair(1, 0), IntPair(0, -1), IntPair(0, 1)) 25 | var count = Array[DType.int8](dimx * dimy) # heuristic counters 26 | var best = Array[DType.int16](dimx * dimy * 64) # 1MB best-distance array 27 | work[0] = 2 28 | work[1] = pack(0, 0, 1, 0) 29 | work[2] = pack(0, 0, 3, 0) 30 | var found = False 31 | var distance = 0 32 | var maxp = 0 33 | # reset the work queues 34 | for w in range(1, maxd): 35 | work[w * maxq] = 0 36 | while not found: 37 | # process the boundary at distance 38 | q = int(work[distance * maxq]) 39 | for cp in range(q): 40 | current = int(work[distance * maxq + cp + 1]) 41 | # unpack the current record 42 | x = (current >> 6) % dimx 43 | y = (current >> 6) // dimx 44 | dir = (current & 0x3F) // 10 45 | r = (current & 0x3F) % 10 46 | dx = int(dirs[dir][0]) 47 | dy = int(dirs[dir][1]) 48 | # final destination 49 | if x == dimx - 1 and y == dimy - 1: 50 | found = True 51 | break 52 | # skip if already processed same 53 | if best[current] < distance : 54 | continue 55 | # + heuristic to trim the number of reviewed states. Probably could refine this. 56 | count[y * dimx + x] += 1 57 | if count[y * dimx + x] > maxrun + 1: 58 | continue 59 | # + another heuristic - limit to +-20 in distance from the frontier 60 | if x + y + 22 < maxp: 61 | continue 62 | # finally, completely cut out the middle circle. 63 | cx = abs(x - dimx // 2) 64 | cy = abs(y - dimx // 2) 65 | cd = sqrt(cx * cx + cy * cy) 66 | if cd < dimx // 2 - 7: 67 | continue 68 | if x + y > maxp: 69 | maxp = x + y 70 | 71 | # this inner function thing is much faster than iterating with a loop. 72 | @parameter 73 | fn maybe_add[ndir: Int](): 74 | nx = int(dirs[ndir][0]) 75 | ny = int(dirs[ndir][1]) 76 | var nr = 0 77 | # don't turn back 78 | if nx == -dx and ny == -dy: 79 | return 80 | # extend run length 81 | if nx == dx and ny == dy: 82 | nr = r + 1 83 | if nr > maxrun: 84 | return 85 | elif r < minrun: 86 | return 87 | # boundaries check 88 | if x + nx < 0 or x + nx >= dimx or y + ny < 0 or y + ny >= dimy: 89 | return 90 | # compute the next state 91 | alias zero = ord("0") 92 | cost = int(tiles[y + ny][x + nx] - zero) 93 | p = pack(x + nx, y + ny, ndir, nr) 94 | # skip if already processed 95 | if best[p] > 0 and best[p] <= distance + cost: 96 | return 97 | # store into the work buffers 98 | best[int(p)] = distance + cost 99 | work[(distance + cost) * maxq] += 1 100 | work[(distance + cost) * maxq + work[(distance + cost) * maxq]] = p 101 | 102 | unroll[maybe_add, range(4)]() 103 | 104 | distance += 1 105 | return distance - 1 106 | 107 | @parameter 108 | fn part1() -> Int64: 109 | return bfs(0, 2) 110 | 111 | @parameter 112 | fn part2() -> Int64: 113 | return bfs(3, 9) 114 | 115 | minibench[part1]("part1") 116 | minibench[part2]("part2") 117 | 118 | print(tiles.length(), "tokens", dimx, dimy) 119 | print(work.bytecount(), "work buffers") 120 | print(dimx, dimy) 121 | -------------------------------------------------------------------------------- /day17.py: -------------------------------------------------------------------------------- 1 | import math 2 | from benchy import minibench 3 | 4 | lines = open("day17.txt").read().split("\n") 5 | tiles = [ list(map(int, line)) for line in lines ] 6 | dimx = len(tiles[0]) 7 | dimy = len(tiles) 8 | 9 | def pack(x, y, dir, r): 10 | return (y * dimx + x) * 40 + dir * 10 + r 11 | 12 | def bfs(minrun,maxrun): 13 | dirs = [ (-1,0), (1,0), (0,-1), (0,1) ] 14 | dijkstra = [ [] for _ in range(2000) ] 15 | best = [ 9999 ] * dimx * dimy * 40 16 | count = [ 0 ] * dimx * dimy 17 | dijkstra[0] = [(0,0,1,0),(0,0,3,0)] 18 | for (x,y,dir,r) in dijkstra[0]: 19 | best[pack(x,y,dir,r)] = 0 20 | found = False 21 | distance = 0 22 | maxp = 0 23 | while not found: 24 | for current in dijkstra[distance]: 25 | (x,y,dir,r) = current 26 | (dx,dy) = dirs[dir] 27 | if x == dimx -1 and y == dimy -1: 28 | found = True 29 | break 30 | key = pack(x,y,dir,r) 31 | if best[key] < distance: 32 | continue 33 | count[y * dimx + x] += 1 34 | if count[y * dimx + x] > maxrun + 1: 35 | continue 36 | if x + y + 22 < maxp: 37 | continue 38 | cx = abs(x - dimx//2) 39 | cy = abs(y - dimy//2) 40 | cd = math.sqrt(cx*cx+cy*cy) 41 | if cd < dimx // 2 - 7: 42 | continue 43 | if x + y > maxp: 44 | maxp = x + y 45 | for ndir in range(4): 46 | (nx,ny) = dirs[ndir] 47 | nr = 0 48 | if (nx,ny) == (-dx,-dy): 49 | continue 50 | if (nx,ny) == (dx,dy): 51 | nr = r + 1 52 | if nr > maxrun: 53 | continue 54 | elif r < minrun: 55 | continue 56 | if x + nx < 0 or x + nx >= dimx or y + ny < 0 or y + ny >= dimy: 57 | continue 58 | cost = tiles[y + ny][x + nx] 59 | next = (x + nx, y + ny, ndir, nr) 60 | nkey = pack(x + nx, y + ny,ndir,nr) 61 | if best[nkey] <= distance + cost: 62 | continue 63 | best[nkey] = distance + cost 64 | dijkstra[distance + cost].append(next) 65 | distance += 1 66 | return distance - 1 67 | 68 | def part1(): 69 | return bfs(0,2) 70 | 71 | def part2(): 72 | return bfs(3,9) 73 | 74 | print(part1()) 75 | print(part2()) 76 | 77 | minibench({"part1": part1, "part2": part2}) 78 | -------------------------------------------------------------------------------- /day18.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from array import Array 4 | 5 | alias ordR = ord('R') 6 | alias ordD = ord('D') 7 | alias ordL = ord('L') 8 | alias ordU = ord('U') 9 | alias zero = ord('0') 10 | 11 | struct Solver: 12 | var cx: Int 13 | var cy: Int 14 | var cc: Int 15 | var ca: Int 16 | 17 | fn __init__(inout self): 18 | self.cx = self.cy = self.ca = 0 19 | self.cc = 1 20 | 21 | fn move(inout self,dir: UInt8,count: Int): 22 | # print(chr(int(dir)), count) 23 | self.cc += count 24 | if dir == ordR: 25 | self.ca += self.cy * count 26 | self.cx += count 27 | elif dir == ordL: 28 | self.ca -= self.cy * count 29 | self.cx -= count 30 | elif dir == ordD: 31 | self.cy += count 32 | elif dir == ordU: 33 | self.cy -= count 34 | 35 | fn area(self) -> Int64: 36 | return abs(self.ca) + self.cc//2 + 1 37 | 38 | fn main() raises: 39 | f = open("day18.txt", "r") 40 | lines = make_parser["\n"](f.read()) 41 | 42 | @parameter 43 | fn part1() -> Int64: 44 | alias sep = ord('(') 45 | var s = Solver() 46 | for i in range(lines.length()): 47 | line = lines[i] 48 | p = line.find(sep) 49 | s.move(line[0],int(atoi(line[2:p-1]))) 50 | return s.area() 51 | 52 | @parameter 53 | fn part2() -> Int64: 54 | alias dirs = VariadicList[UInt8]( ordR, ordD, ordL, ordU ) 55 | var s = Solver() 56 | for i in range(lines.length()): 57 | line = lines[i] 58 | c = line[line.size-2] - zero 59 | d = dirs[int(c)] 60 | x = xtoi(line[line.size-7:line.size-2]) 61 | s.move(d,int(x)) 62 | return s.area() 63 | 64 | print(part1()) 65 | print(part2()) 66 | minibench[part1]("part1") 67 | minibench[part2]("part2") 68 | 69 | print(lines.length(), "lines") 70 | -------------------------------------------------------------------------------- /day18.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day18.txt").read().split("\n") 4 | 5 | class Solver: 6 | def __init__(self) -> None: 7 | self.cx = self.cy = self.ca = 0 8 | self.cc = 1 9 | 10 | def move(self,dir,count): 11 | self.cc += count 12 | if dir == 'R': 13 | self.ca += self.cy * count 14 | self.cx += count 15 | elif dir == 'L': 16 | self.ca -= self.cy * count 17 | self.cx -= count 18 | elif dir == 'U': 19 | self.cy += count 20 | elif dir == 'D': 21 | self.cy -= count 22 | 23 | def area(self): 24 | return abs(self.ca) + self.cc//2 + 1 25 | 26 | def part1(): 27 | s = Solver() 28 | for line in lines: 29 | (d,n,_) = line.split() 30 | s.move(d,int(n)) 31 | return s.area() 32 | 33 | def part2(): 34 | s = Solver() 35 | m = "RDLU" 36 | for line in lines: 37 | h = line.split()[2] 38 | d = m[int(h[7])] 39 | n = int(h[2:7],16) 40 | s.move(d,int(n)) 41 | return s.area() 42 | 43 | print(part1()) 44 | print(part2()) 45 | 46 | minibench({"part1": part1, "part2": part2}) 47 | 48 | ####### inner area = 6*2 = 12 49 | # a21 # circumf = 16 (15+1) 50 | ####### 12 * 0.5 + 4 * 0.75 = 16 * 0.5 + 4 * 0.25 = 8 + 1 -------------------------------------------------------------------------------- /day19.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day19.txt").read().split("\n") 4 | 5 | def replace(t, i, v): 6 | r = t.copy() 7 | r[i] = v 8 | return r 9 | 10 | class BaseRule(): 11 | def __init__(self, name, lbl = None) -> None: 12 | self.name = name 13 | self.lbl = lbl 14 | self.rule = None 15 | self.next = None 16 | 17 | def apply(self, ho, hoho) -> None: 18 | # print("{} => {}".format(self.name, self.lbl)) 19 | return self.rule.apply(ho, hoho) 20 | 21 | class BasicRule(BaseRule): 22 | def __init__(self, name, lbl, i, n, v) -> None: 23 | BaseRule.__init__(self, name, lbl) 24 | self.v = v 25 | self.n = n 26 | self.i = i 27 | 28 | class LessRule(BasicRule): 29 | def apply(self, ho, hoho): 30 | # print("{} {}({}-{}) < {} : {} / {}".format(self.name, self.v, ho[self.i], hoho[self.i], self.n, self.lbl, self.next.name)) 31 | if hoho[self.i] < self.n: 32 | return self.rule.apply(ho, hoho) 33 | if ho[self.i] < self.n: 34 | return self.rule.apply(ho, replace(hoho, self.i, self.n - 1)) + self.next.apply(replace(ho, self.i, self.n), hoho) 35 | return self.next.apply(ho, hoho) 36 | 37 | class MoreRule(BasicRule): 38 | def apply(self, ho, hoho): 39 | # print("{} {}({}-{}) > {} : {} / {}".format(self.name, self.v, ho[self.i], hoho[self.i], self.n, self.lbl, self.next.lbl)) 40 | if ho[self.i] > self.n: 41 | return self.rule.apply(ho, hoho) 42 | if hoho[self.i] > self.n: 43 | return self.rule.apply(replace(ho, self.i, self.n + 1), hoho) + self.next.apply(ho, replace(hoho, self.i, self.n)) 44 | return self.next.apply(ho, hoho) 45 | 46 | class Accept(BaseRule): 47 | def apply(self, ho, hoho) -> None: 48 | return (hoho[0] - ho[0] + 1) * (hoho[1] - ho[1] + 1) * (hoho[2] - ho[2] + 1) * (hoho[3] - ho[3] + 1) 49 | 50 | class Reject(BaseRule): 51 | def apply(self, ho, hoho) -> None: 52 | return 0 53 | 54 | rules = {} 55 | insn = [] 56 | 57 | def parse(): 58 | rules.clear() 59 | insn.clear() 60 | mapp = {"x": 0, "m": 1, "a": 2, "s": 3} 61 | rules["A"] = Accept("A") 62 | rules["R"] = Reject("R") 63 | all_rules = [] 64 | for line in lines: 65 | pos = line.find("{") 66 | if pos > 0: 67 | tok = line[:pos] 68 | rrr = line[pos + 1 : -1].split(",") 69 | prev = None 70 | for r in rrr: 71 | if ":" in r: 72 | a, b = r.split(":") 73 | v, o, n = a[0], a[1], int(a[2:]) 74 | i = mapp[v] 75 | if o == "<": 76 | rule = LessRule(tok, b, i, n, v) 77 | else: 78 | rule = MoreRule(tok, b, i, n, v) 79 | else: 80 | rule = BaseRule(tok, r) 81 | all_rules.append(rule) 82 | if prev: 83 | prev.next = rule 84 | else: 85 | rules[tok] = rule 86 | prev = rule 87 | elif pos == 0: 88 | hohoho = [] 89 | for t in line[1:-1].split(","): 90 | _, n = t.split("=") 91 | hohoho.append(int(n)) 92 | insn.append(hohoho) 93 | for rule in all_rules: 94 | rule.rule = rules[rule.lbl] 95 | return len(insn)+len(rules) 96 | 97 | def part1(): 98 | ret = 0 99 | for hohoho in insn: 100 | ret += sum(hohoho) * rules["in"].apply(hohoho, hohoho) 101 | return ret 102 | 103 | def part2(): 104 | return rules["in"].apply([1,1,1,1],[4000,4000,4000,4000]) 105 | 106 | parse() 107 | print(part1()) 108 | print(part2()) 109 | 110 | minibench({"parse": parse, "part1": part1, "part2": part2}) 111 | -------------------------------------------------------------------------------- /day19codegen.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | lines = open("day19.txt").read().split("\n") 3 | the_code = None 4 | def parse(): 5 | global the_code 6 | code = [] 7 | code.append("class xmas:") 8 | code.append(" def __init__(self,x,m,a,s) -> None:") 9 | code.append(" self.x = x; self.m = m; self.a = a; self.s = s\n") 10 | code.append(" def sum(self):") 11 | code.append(" return self.x+self.m+self.a+self.s\n") 12 | code.append(" def copy(self):") 13 | code.append(" return xmas(self.x,self.m,self.a,self.s)\n") 14 | 15 | code.append("def f_A(ho, hoho):") 16 | code.append(" return (hoho.x - ho.x + 1)*(hoho.m - ho.m + 1)*(hoho.a - ho.a + 1)*(hoho.s - ho.s + 1)\n") 17 | code.append("def f_R(ho, hoho):") 18 | code.append(" return 0\n") 19 | for line in lines: 20 | pos = line.find('{') 21 | if pos > 0: 22 | tok = line[:pos] 23 | rules = line[pos+1:-1].split(',') 24 | code.append("def f_{}(ho, hoho):".format(tok)) 25 | code.append(" ret = 0") 26 | for r in rules: 27 | if ':' in r: 28 | a,b = r.split(':') 29 | v,o,n = a[0],a[1],int(a[2:]) 30 | if o == '<': 31 | code.append(" if (hoho.{} < {}):\n return ret + f_{}(ho.copy(),hoho.copy())".format(v,n,b)) 32 | code.append(" if (ho.{} < {}):".format(v,n)) 33 | code.append(" t = hoho.{}; hoho.{} = {}".format(v,v,n-1)) 34 | code.append(" ret += f_{}(ho.copy(),hoho.copy())".format(b)) 35 | code.append(" ho.{} = {}; hoho.{} = t".format(v,n,v)) 36 | else: 37 | code.append(" if (ho.{} > {}):\n return ret + f_{}(ho.copy(),hoho.copy())".format(v,n,b)) 38 | code.append(" if (hoho.{} > {}):".format(v,n)) 39 | code.append(" t = ho.{}; ho.{} = {}".format(v,v,n+1)) 40 | code.append(" ret += f_{}(ho.copy(),hoho.copy())".format(b)) 41 | code.append(" hoho.{} = {}; ho.{} = t".format(v,n,v)) 42 | else: 43 | code.append(" return ret + f_{}(ho,hoho)".format(r)) 44 | code.append("\n") 45 | elif pos == 0: 46 | l = line[1:-1] 47 | code.append(" hohoho = xmas({});".format(l)) 48 | code.append(" merry += hohoho.sum()*f_in(hohoho,hohoho);") 49 | else: 50 | code.append("def part1():") 51 | code.append(" merry=0\n hohoho=xmas(0,0,0,0)") 52 | code.append(" return merry\n") 53 | code.append("def part2():") 54 | code.append(" return f_in(xmas(1,1,1,1),xmas(4000,4000,4000,4000))\n") 55 | # the_code = compile("\n".join(code),".tmp","exec") 56 | the_code = "\n".join(code) 57 | return 1 58 | 59 | parse() 60 | exec(the_code) 61 | print(part1()) 62 | print(part2()) 63 | 64 | minibench({"parse": parse, "part1": part1, "part2": part2}) 65 | -------------------------------------------------------------------------------- /day19gcc.py: -------------------------------------------------------------------------------- 1 | import os 2 | from benchy import minibench 3 | lines = open("day19.txt").read().split("\n") 4 | code = [] 5 | code.append("#include ") 6 | code.append('#define ANKERL_NANOBENCH_IMPLEMENT') 7 | code.append('#include "nanobench.h"') 8 | code.append("using namespace std;") 9 | code.append("typedef struct xmas { int64_t x,m,a,s; int64_t sum() { return x+m+a+s; }} xmas;") 10 | code.append("long long f_A(xmas ho, xmas hoho) {") 11 | code.append(" return (hoho.x - ho.x + 1)*(hoho.m - ho.m + 1)*(hoho.a - ho.a + 1)*(hoho.s - ho.s + 1);\n}\n") 12 | code.append("long long f_R(xmas ho, xmas hoho) {") 13 | code.append(" return 0;\n}\n") 14 | body = [] 15 | for line in lines: 16 | pos = line.find('{') 17 | if pos > 0: 18 | tok = line[:pos] 19 | rules = line[pos+1:-1].split(',') 20 | code.append("long long f_{}(xmas ho, xmas hoho);".format(tok)) 21 | 22 | body.append("long long f_{}(xmas ho, xmas hoho) {{".format(tok)) 23 | body.append(" long long ret = 0;") 24 | for r in rules: 25 | if ':' in r: 26 | a,b = r.split(':') 27 | v,o,n = a[0],a[1],int(a[2:]) 28 | if o == '<': 29 | body.append(" if (hoho.{} < {}) return ret + f_{}(ho,hoho);".format(v,n,b)) 30 | body.append(" if (ho.{} < {}) {{".format(v,n)) 31 | body.append(" int64_t t = hoho.{}; hoho.{} = {};".format(v,v,n-1)) 32 | body.append(" ret += f_{}(ho,hoho);".format(b)) 33 | body.append(" ho.{} = {}; hoho.{} = t;\n }}".format(v,n,v)) 34 | else: 35 | body.append(" if (ho.{} > {}) return ret + f_{}(ho,hoho);".format(v,n,b)) 36 | body.append(" if (hoho.{} > {}) {{".format(v,n)) 37 | body.append(" int64_t t = ho.{}; ho.{} = {};".format(v,v,n+1)) 38 | body.append(" ret += f_{}(ho,hoho);".format(b)) 39 | body.append(" hoho.{} = {}; ho.{} = t;\n }}".format(v,n,v)) 40 | else: 41 | body.append(" return ret + f_{}(ho,hoho);".format(r)) 42 | body.append("}\n") 43 | elif pos == 0: 44 | l = line[1:-1].replace(',',';').replace(';',',.') 45 | code.append(" hohoho={{.{}}};".format(l)) 46 | code.append(" merry += hohoho.sum()*f_in(hohoho,hohoho);") 47 | else: 48 | code.extend(body) 49 | code.append("\nint64_t part1() {") 50 | code.append(" long long merry=0;\n xmas hohoho;") 51 | code.append(" return merry;\n}") 52 | code.append("\nint64_t part2() {") 53 | code.append(" return f_in({1,1,1,1},xmas{4000,4000,4000,4000});\n}") 54 | code.append("\nint main() {") 55 | code.append(" cout << part1() << endl;") 56 | code.append(" cout << part2() << endl;") 57 | code.append(' int64_t tot = 0;') 58 | code.append(' ankerl::nanobench::Bench().minEpochIterations(100).run(') 59 | code.append(' "part1", [&] { tot += part1(); ankerl::nanobench::doNotOptimizeAway(tot);});') 60 | code.append(' ankerl::nanobench::Bench().minEpochIterations(100).run(') 61 | code.append(' "part2", [&] { tot += part1(); ankerl::nanobench::doNotOptimizeAway(tot);});') 62 | code.append(" return 0;\n}") 63 | cc = open("day19.cc",'w') 64 | cc.write("\n".join(code)) 65 | os.system("g++ -std=c++20 -O2 day19.cc -o day19") 66 | os.system("./day19") 67 | -------------------------------------------------------------------------------- /day19lambda.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | lines = open("day19.txt").read().split("\n") 4 | 5 | 6 | def replace(t, i, v): 7 | r = t.copy() 8 | r[i] = v 9 | return r 10 | 11 | 12 | funs = {} 13 | insn = [] 14 | 15 | 16 | def parse(): 17 | funs.clear() 18 | insn.clear() 19 | mapp = {"x": 0, "m": 1, "a": 2, "s": 3} 20 | funs["A"] = lambda ho, hoho: (hoho[0] - ho[0] + 1) * (hoho[1] - ho[1] + 1) * (hoho[2] - ho[2] + 1) * (hoho[3] - ho[3] + 1) 21 | funs["R"] = lambda ho, hoho: 0 22 | for line in lines: 23 | pos = line.find("{") 24 | if pos > 0: 25 | tok = line[:pos] 26 | ntok = tok + "_" 27 | rules = line[pos + 1 : -1].split(",") 28 | for r in rules: 29 | if ":" in r: 30 | a, b = r.split(":") 31 | v, o, n = a[0], a[1], int(a[2:]) 32 | i = mapp[v] 33 | if o == "<": 34 | funs[tok] = lambda ho, hoho, i=i, n=n, ntok=ntok, b=b: ( 35 | funs[b](ho, hoho) if hoho[i] < n else funs[ntok](ho, hoho) 36 | ) 37 | tok = ntok 38 | ntok = tok + "_" 39 | funs[tok] = lambda ho, hoho, i=i, n=n, ntok=ntok, b=b: ( 40 | funs[b](ho, replace(hoho, i, n - 1)) + funs[ntok](replace(ho, i, n), hoho) 41 | if ho[i] < n 42 | else funs[ntok](ho, hoho) 43 | ) 44 | tok = ntok 45 | ntok = tok + "_" 46 | else: 47 | funs[tok] = lambda ho, hoho, i=i, n=n, ntok=ntok, b=b: ( 48 | funs[b](ho, hoho) if ho[i] > n else funs[ntok](ho, hoho) 49 | ) 50 | tok = ntok 51 | ntok = tok + "_" 52 | funs[tok] = lambda ho, hoho, i=i, n=n, ntok=ntok, b=b: ( 53 | funs[b](replace(ho, i, n + 1), hoho) + funs[ntok](ho, replace(hoho, i, n)) 54 | if hoho[i] > n 55 | else funs[ntok](ho, hoho) 56 | ) 57 | tok = ntok 58 | ntok = tok + "_" 59 | else: 60 | funs[tok] = lambda ho, hoho, r=r: funs[r](ho, hoho) 61 | elif pos == 0: 62 | hohoho = [] 63 | for t in line[1:-1].split(","): 64 | _, n = t.split("=") 65 | hohoho.append(int(n)) 66 | insn.append(hohoho) 67 | return len(insn)+len(funs) 68 | 69 | def part1(): 70 | ret = 0 71 | for hohoho in insn: 72 | ret += sum(hohoho) * funs["in"](hohoho, hohoho) 73 | return ret 74 | 75 | def part2(): 76 | return funs["in"]([1,1,1,1],[4000,4000,4000,4000]) 77 | 78 | parse() 79 | print(part1()) 80 | print(part2()) 81 | 82 | minibench({"parse": parse, "part1": part1, "part2": part2}) 83 | -------------------------------------------------------------------------------- /day20.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | class Module: 4 | def __init__(self, id, targets) -> None: 5 | self.id = id 6 | self.targets = targets 7 | 8 | def pulse(self, level, source="ignored"): 9 | return [(self.id, level, dst) for dst in self.targets] 10 | 11 | def add(self, id): 12 | pass 13 | 14 | def reset(self): 15 | pass 16 | 17 | class Dummy(Module): 18 | def __init__(self, id, targets) -> None: 19 | super().__init__(id, targets) 20 | self.count = [0, 0] 21 | 22 | def pulse(self, level, _): 23 | self.count[level] += 1 24 | return [] 25 | 26 | def reset(self): 27 | self.count.clear() 28 | self.count.extend([0, 0]) 29 | 30 | class FlipFlop(Module): 31 | def __init__(self, id, targets) -> None: 32 | super().__init__(id, targets) 33 | self.level = 0 34 | 35 | def pulse(self, level, source): 36 | if level == 0: 37 | self.level = 1 - self.level 38 | return super().pulse(self.level, source) 39 | return [] 40 | 41 | def reset(self): 42 | self.level = 0 43 | 44 | class Conjunction(Module): 45 | def __init__(self, id, targets) -> None: 46 | super().__init__(id, targets) 47 | self.states = self.counts = self.conn = 0 48 | self.linked = [] 49 | 50 | def add(self, id): 51 | self.linked.append(id) 52 | self.conn += 1 53 | 54 | def pulse(self, level, source): 55 | self.counts |= level << source 56 | self.states &= ~(1 << source) 57 | self.states |= (level << source) 58 | if self.states.bit_count() == self.conn: 59 | return super().pulse(0, source) 60 | else: 61 | return super().pulse(1, source) 62 | 63 | def reset(self): 64 | self.states = self.counts = 0 65 | 66 | lines = open("day20.txt").read().split("\n") 67 | modules = [None] 68 | monitor = -1 69 | 70 | def parse(): 71 | # parse 72 | global monitor 73 | modules.clear() 74 | modules.append(None) 75 | ids = {"br": 0} 76 | for line in lines: 77 | if ">" not in line: 78 | continue 79 | l, r = line.split(" -> ") 80 | dst = r.split(", ") 81 | if l != "broadcaster": 82 | o = l[0] 83 | n = l[1:] 84 | ids[n] = len(modules) 85 | if o == "&": 86 | m = Conjunction(ids[n], dst) 87 | else: 88 | m = FlipFlop(ids[n], dst) 89 | modules.append(m) 90 | else: 91 | o = "*" 92 | n = "br" 93 | m = Module(0, dst) 94 | modules[0] = m 95 | 96 | # link up 97 | for mod in modules: 98 | for dst in mod.targets: 99 | if dst not in ids: 100 | ids[dst] = len(modules) 101 | monitor = mod.id 102 | else: 103 | modules[ids[dst]].add(mod.id) 104 | mod.targets = [ids[dst] for dst in mod.targets] 105 | modules.append(Dummy(len(modules), [])) 106 | return len(modules) 107 | 108 | def pushit(): 109 | pcnt = [1,0] 110 | pulses = [] 111 | pp = 0 112 | pulses.extend(modules[0].pulse(0)) 113 | while pp < len(pulses): 114 | (src, level, dst) = pulses[pp] 115 | # print(src,level,dst) 116 | pcnt[level] += 1 117 | pulses.extend(modules[dst].pulse(level, src)) 118 | pp += 1 119 | return pcnt 120 | 121 | def reset(): 122 | for mod in modules: 123 | mod.reset() 124 | 125 | def part1(): 126 | reset() 127 | los = his = 0 128 | for _ in range(1000): 129 | (lo, hi) = pushit() 130 | los += lo 131 | his += hi 132 | return los * his 133 | 134 | def part2(): 135 | reset() 136 | cnt = 0 137 | prod = 1 138 | seen = 0 139 | while modules[-1].count[0] == 0: 140 | pushit() 141 | cnt += 1 142 | if modules[monitor].counts != seen: 143 | seen = modules[monitor].counts 144 | prod *= cnt 145 | if seen.bit_count() == modules[monitor].conn: 146 | return prod 147 | return cnt 148 | 149 | parse() 150 | print(part1()) 151 | print(part2()) 152 | 153 | minibench({"parse": parse, "part1": part1, "part2": part2}) 154 | -------------------------------------------------------------------------------- /day21.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from array import Array 4 | from utils.loop import unroll 5 | 6 | @always_inline 7 | fn trisum(rem: Int, odds: Int) -> Int: 8 | fullsum = (rem) * (1 + rem) // 2 9 | evensum = (rem // 2) * (1 + rem // 2) 10 | if odds: 11 | return fullsum - evensum 12 | else: 13 | return evensum 14 | 15 | @always_inline 16 | fn oddcnt(rem: Int, odds: Int) -> Int: 17 | if rem % 2 == 0: 18 | return rem // 2 19 | return rem // 2 + odds 20 | 21 | fn main() raises: 22 | f = open("day21.txt", "r") 23 | tiles = make_parser["\n"](f.read()) 24 | dimx = tiles.length() 25 | dimy = tiles[0].size 26 | alias maxq = 256*1024 27 | alias maxw = 144 28 | work = Array[DType.int16](maxq) 29 | visited = Array[DType.int16](maxw * maxw * 9) 30 | var start: Int64 31 | 32 | @parameter 33 | fn parse() -> Int64: 34 | alias cS = ord("S") 35 | for y in range(dimy): 36 | for x in range(dimx): 37 | if tiles[y][x] == cS: 38 | return (y << 16) + x 39 | return -1 40 | 41 | @parameter 42 | fn bfs(start: Int32, steps: Int, limx: Int, limy: Int) -> Int64: 43 | alias cHash = ord("#") 44 | alias IntPair = SIMD[DType.int64, 2] 45 | alias dirs = List[IntPair](IntPair(-1, 0), IntPair(1, 0), IntPair(0, -1), IntPair(0, 1)) 46 | sy = int(start) >> 16 47 | sx = int(start) & 0xFFFF 48 | var current = 0 49 | var limit = 2 50 | var total = 0 51 | work[0] = sy 52 | work[1] = sx 53 | visited.zero() 54 | while current < limit: # aaand ? 55 | y = int(work[current]) 56 | x = int(work[current + 1]) 57 | p = y * limx + x 58 | distance = visited[p] 59 | # ... and break here 60 | if distance > steps or distance > dimx + dimx // 2 + 1: 61 | break 62 | current += 2 63 | if dimx == limx and distance % 2 == steps % 2: 64 | total += 1 65 | 66 | @parameter 67 | fn maybe_add[ndir: Int](): 68 | nx = x + int(dirs[ndir][0]) 69 | ny = y + int(dirs[ndir][1]) 70 | if nx < 0 or nx >= limx or ny < 0 or ny >= limy: 71 | return 72 | np = ny * limx + nx 73 | if tiles[ny % dimy][nx % dimx] == cHash or visited[np] != 0: 74 | return 75 | visited[np] = distance + 1 76 | work[limit] = ny 77 | work[limit + 1] = nx 78 | limit += 2 79 | 80 | unroll[maybe_add, range(4)]() 81 | 82 | 83 | if dimx == limx: 84 | return total - 1 85 | 86 | for y in range(dimy): 87 | for x in range(dimx): 88 | cp = (y + dimx) * limx + x + dimx 89 | if visited[cp] == 0 and not (x + dimx == sx and y + dimy == sy): 90 | continue 91 | alias ofs1 = List[IntPair](IntPair(-1, 0), IntPair(1, 0), IntPair(0, -1), IntPair(0, 1)) 92 | @parameter 93 | fn scan1[i: Int](): 94 | np = cp + dimx * int(ofs1[i][0]) + dimx * limx * int(ofs1[i][1]) 95 | if visited[np] == 0: 96 | visited[np] = visited[cp] + dimx 97 | cd = int(visited[np]) 98 | rem = (steps - cd) // dimx 99 | total += oddcnt(rem + 1, cd % 2) 100 | unroll[scan1, range(4)]() 101 | 102 | alias ofs2 = List[IntPair](IntPair(-1, -1), IntPair(-1, 1), IntPair(1, -1), IntPair(1, 1)) 103 | @parameter 104 | fn scan2[i: Int](): 105 | np = cp + dimx * int(ofs2[i][0]) + dimx * limx * int(ofs2[i][1]) 106 | if visited[np] == 0: 107 | np1 = cp + dimx * int(ofs2[i][0]) 108 | np2 = cp + dimx * limx * int(ofs2[i][1]) 109 | visited[np] = min(visited[np1], visited[np2]) + dimx 110 | cd = int(visited[np]) 111 | rem = (steps - cd) // dimx 112 | total += trisum(rem + 1, cd % 2) 113 | unroll[scan2, range(4)]() 114 | 115 | total += int(visited[cp]) % 2 116 | return total 117 | 118 | @parameter 119 | fn part1() -> Int64: 120 | return bfs(int(start), 64, dimx, dimy) 121 | 122 | @parameter 123 | fn part2() -> Int64: 124 | ofs = (dimy << 16) + dimx 125 | return bfs(int(start) + ofs, 26501365, dimx * 3, dimy * 3) 126 | 127 | start = parse() 128 | minibench[part1]("part1") 129 | minibench[part2]("part2") 130 | 131 | print(tiles.length(), "tokens", dimx, dimy) 132 | print(work.bytecount() + visited.bytecount(), "work buffers") 133 | -------------------------------------------------------------------------------- /day21.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from benchy import minibench 3 | 4 | tiles = open("day21.txt").read().split("\n") 5 | dimx = len(tiles[0]) 6 | dimy = len(tiles) 7 | for y, line in enumerate(tiles): 8 | if "S" in line: 9 | (sx, sy) = (line.find("S"), y) 10 | break 11 | 12 | def sign(x): 13 | if x < 0: 14 | return -1 15 | elif x > 0: 16 | return 1 17 | else: 18 | return 0 19 | 20 | def trisum(rem, odds): 21 | fullsum = (rem) * (1 + rem) // 2 22 | evensum = (rem // 2) * (1 + rem // 2) 23 | if odds: 24 | return fullsum - evensum 25 | else: 26 | return evensum 27 | 28 | def oddcnt(rem, odds): 29 | if rem % 2 == 0: 30 | return rem // 2 31 | return rem // 2 + odds 32 | 33 | def bfs(start, steps, expand): 34 | dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)] 35 | distance = 0 36 | current = [start] 37 | visited = set(current) 38 | history = defaultdict(list) 39 | total = 0 40 | minlen = 0 41 | (ox, oy) = start 42 | while current and distance < steps and minlen < 6: 43 | minlen = 9 * expand 44 | next = [] 45 | for pos in current: 46 | (x, y) = pos 47 | if expand: 48 | r = (x // dimx - ox // dimx, y // dimy - oy // dimy) 49 | history[(x % dimx, y % dimy)].append((r, distance)) 50 | minlen = min(minlen, len(history[(x % dimx, y % dimy)])) 51 | elif distance % 2 == steps % 2: 52 | total += 1 53 | for ndir in range(4): 54 | (dx, dy) = dirs[ndir] 55 | if expand: 56 | (nx, ny) = ((x + dx) % dimx, (y + dy) % dimy) 57 | else: 58 | if x + dx < 0 or x + dx >= dimx or y + dy < 0 or y + dy >= dimy: 59 | continue 60 | (nx, ny) = (x + dx, y + dy) 61 | if tiles[ny][nx] == "#" or (x + dx, y + dy) in visited: 62 | continue 63 | visited.add((x + dx, y + dy)) 64 | next.append((x + dx, y + dy)) 65 | distance += 1 66 | current = next 67 | if not expand: 68 | total += len(current) 69 | return total 70 | for pos in history: 71 | pfx = 6 72 | d9 = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 73 | c9 = [0] * 9 74 | ch = history[pos] 75 | ((xx, yy), _) = ch[pfx - 1] 76 | if abs(xx) == 2 or abs(yy) == 2: 77 | pfx -= 1 78 | # map first few visited nodes into 3x3 grid and compute their counts 79 | for j in range(pfx): 80 | ((x1, y1), d1) = ch[j] 81 | d9[y1 + 1][x1 + 1] = d1 82 | rem = (steps - d1) // dimx 83 | if x1 == 0 or y1 == 0: 84 | c9[(y1 + 1) * 3 + x1 + 1] = oddcnt(rem + 1, d1 % 2) 85 | else: 86 | c9[(y1 + 1) * 3 + x1 + 1] = trisum(rem + 1, d1 % 2) 87 | # create the rest of the 3x3 grid and compute remaining quadrants 88 | for cx, cy in [(0, 0), (0, 2), (2, 2), (2, 0)]: 89 | if d9[cy][cx] == 0: 90 | d9[cy][cx] = dimx + min(d9[cy][1], d9[1][cx]) 91 | rem = (steps - d9[cy][cx]) // dimx 92 | c9[cy * 3 + cx] = trisum(rem + 1, d9[cy][cx] % 2) 93 | c9[4] = d9[1][1] % 2 94 | total += sum(c9) 95 | return total 96 | 97 | def part1(): 98 | return bfs((sx, sy), 64, 0) 99 | 100 | def part2(): 101 | return bfs((sx + dimx * 1000, sy + dimy * 1000), 26501365, 1) 102 | 103 | print(part1()) 104 | print(part2()) 105 | 106 | minibench({"part1": part1, "part2": part2}) 107 | -------------------------------------------------------------------------------- /day22.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from array import Array 4 | from array import Array 5 | from quicksort import qsort 6 | 7 | 8 | fn main() raises: 9 | f = open("day22.txt", "r") 10 | lines = make_parser["\n"](f.read()) 11 | lcnt = lines.length() 12 | # lots of arrays 13 | bricks = Array[DType.int16](lcnt * 8) 14 | deps = Array[DType.int16](lcnt * 8) 15 | hmap = Array[DType.int16](100) 16 | bmap = Array[DType.int16](100) 17 | supp = Array[DType.int16](lcnt) 18 | work = Array[DType.int16](lcnt) 19 | safe = Array[DType.int16](lcnt, 1) 20 | 21 | @parameter 22 | fn parse() -> Int64: 23 | for i in range(lcnt): 24 | line = lines[i] 25 | var vec = atomi[8, DType.int16, False, False](line).rotate_right[1]() 26 | v1 = vec.slice[4, offset=0]().rotate_right[1]() 27 | v2 = vec.slice[4, offset=4]().rotate_right[2]() 28 | if v2[0] < v1[0]: 29 | vec = v2.join(v1) 30 | else: 31 | vec = v1.join(v2) 32 | bricks.store[width=8](i * 8, vec) 33 | qsort[8, DType.int16, 1](bricks) 34 | return lcnt 35 | 36 | @parameter 37 | fn reset(): 38 | hmap.zero() 39 | bmap.zero() 40 | safe.fill(1) 41 | for k in range(lcnt): 42 | deps[k*8] = 0 43 | 44 | @parameter 45 | fn drop(idx: Int): 46 | var vec = bricks.load[width=8](idx * 8) 47 | var rng = SIMD[DType.int16, 16](0) 48 | var rl = 0 49 | if vec[2] != vec[6]: 50 | for x in range(vec[2], vec[6] + 1): 51 | rng[rl] = x + vec[3] * 10 52 | rl += 1 53 | else: 54 | for y in range(vec[3], vec[7] + 1): 55 | rng[rl] = vec[2] + y * 10 56 | rl += 1 57 | # print(idx,vec, rl, rng) 58 | if vec[0] > 1: 59 | var mh: Int16 = 0 60 | for i in range(rl): 61 | mh = max(mh, hmap[int(rng[i])]) 62 | if mh < vec[0] - 1: 63 | alias sub = SIMD[DType.int16, 8](1, 0, 0, 0, 1, 0, 0, 0) 64 | vec -= sub * (vec[0] - 1 - mh) 65 | var sup = SIMD[DType.int16, 8](0) 66 | var prev : Int16 = -1 67 | for i in range(rl): 68 | ri = int(rng[i]) 69 | if vec[0] > 1 and hmap[ri] == vec[0] - 1: 70 | base = bmap[ri] 71 | if base != prev: 72 | sup[0] += 1 73 | sup[int(sup[0])] = base 74 | prev = base 75 | hmap[ri] = vec[4] 76 | bmap[ri] = idx 77 | supp[idx] = sup[0] 78 | for j in range(sup[0]): 79 | k = int(sup[j+1]) 80 | deps[k * 8] += 1 81 | deps[k * 8 + int(deps[k * 8])] = idx 82 | if sup[0] == 1: 83 | safe[int(sup[1])] = 0 84 | 85 | @parameter 86 | fn explode(start: Int) -> Int: 87 | var sup2 = Array[DType.int16](lcnt) 88 | memcpy(sup2.data, supp.data, lcnt) 89 | work[0] = start 90 | var pos = 0 91 | var lim = 1 92 | while pos < lim: 93 | cur = int(work[pos]) 94 | pos += 1 95 | for di in range(deps[cur * 8]): 96 | dst = int(deps[cur * 8 + di + 1]) 97 | sup2[dst] -= 1 98 | if sup2[dst] == 0: 99 | work[lim] = dst 100 | lim += 1 101 | return lim - 1 102 | 103 | @parameter 104 | fn part1() -> Int64: 105 | var sum = 0 106 | reset() 107 | for i in range(lcnt): 108 | drop(i) 109 | for i in range(lcnt): 110 | sum += int(safe[i]) 111 | return sum 112 | 113 | @parameter 114 | fn part2() -> Int64: 115 | var cache = Array[DType.int32](lcnt, - 1) 116 | var esum = 0 117 | for start in range(lcnt): 118 | var ecnt : Int 119 | if safe[start] == 1: 120 | continue 121 | if cache[start] >= 0: 122 | ecnt = int(cache[start]) 123 | else: 124 | ecnt = explode(start) 125 | if deps[start * 8] == 1: 126 | cache[int(deps[start * 8 + 1])] = ecnt -1 127 | esum += ecnt 128 | return esum 129 | 130 | minibench[parse]("parse") 131 | minibench[part1]("part1") 132 | minibench[part2]("part2") 133 | 134 | print(lines.length(), "bricks", lcnt) 135 | print(bricks.bytecount(), "bricks mem") 136 | print(hmap.bytecount()+bmap.bytecount()+safe.bytecount(),"drop mem") 137 | print(deps.bytecount()+supp.bytecount()+work.bytecount(),"deps mem") 138 | -------------------------------------------------------------------------------- /day22.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | 3 | def drop(bricks): 4 | mapp = [ 0 ] * 100 5 | height = [ 0 ] * 100 6 | suppby = [ 0 ] * len(bricks) 7 | suppon = [] 8 | unsafe = [ 0 ] * len(bricks) 9 | for i,(z1,x1,y1,z2,x2,y2) in enumerate(bricks): 10 | suppon.append([]) 11 | if z1 > 1: 12 | if x1 != x2: 13 | mh = max([height[x + y1*10] for x in range(x1,x2+1)]) 14 | else: 15 | mh = max([height[x1 + y*10] for y in range(y1,y2+1)]) 16 | if z1 - 1 > mh: 17 | z2 -= z1 - 1 - mh 18 | z1 = mh + 1 19 | sup = [] 20 | prev = -1 21 | if x1 != x2: 22 | r = [ (x,y1) for x in range(x1,x2+1)] 23 | else: 24 | r = [ (x1,y) for y in range(y1,y2+1)] 25 | for (x,y) in r: 26 | if z1 > 1 and height[x + y*10] == z1 - 1: 27 | base = mapp[x+y*10] 28 | if base != prev: 29 | sup.append(base) 30 | prev = base 31 | height[x + y*10] = z2 32 | mapp[x + y*10] = i 33 | suppby[i] = len(sup) 34 | for j in sup: 35 | suppon[j].append(i) 36 | if len(sup) == 1: 37 | unsafe[sup[0]] = 1 38 | return (unsafe, suppby, suppon) 39 | 40 | def explode(suppby, suppon, start): 41 | supp = suppby.copy() 42 | work = [ start ] 43 | pos = 0 44 | while pos < len(work): 45 | cur = work[pos] 46 | pos += 1 47 | for dst in suppon[cur]: 48 | supp[dst] -= 1 49 | if not supp[dst]: 50 | work.append(dst) 51 | return len(work) - 1 52 | 53 | 54 | lines = open("day22.txt").read().split("\n") 55 | bricks = [] 56 | 57 | def parse(): 58 | bricks.clear() 59 | for line in lines: 60 | line = line.replace("~",",") 61 | (x1,y1,z1,x2,y2,z2) = map(int,line.split(",")) 62 | # swap so that z1 is lower 63 | if (z2 < z1): 64 | (x1,y1,z1,x2,y2,z2)=(x2,y2,z2,x1,y1,z1) 65 | elif (x2 < x1): # z1,y1 is same as z2,y2 66 | x1,x2 = x2,x1 67 | elif (y2 < y1): # z1,x1 is same as z2,y2 68 | y1,y2 = y2,y1 69 | bricks.append((z1,x1,y1,z2,x2,y2)) 70 | bricks.sort() 71 | return len(bricks) 72 | 73 | def part1(): 74 | global suppby, suppon, unsafe 75 | unsafe, suppby, suppon = drop(bricks) 76 | return len(bricks) - sum(unsafe) 77 | 78 | def part2(): 79 | cache = [ -1 ] * len(bricks) 80 | esum = 0 81 | for start in range(len(bricks)): 82 | if not unsafe[start]: 83 | continue 84 | if cache[start] >= 0: 85 | ecnt = cache[start] 86 | else: 87 | ecnt = explode(suppby, suppon, start) 88 | if len(suppon[start]) == 1: 89 | cache[suppon[start][0]] = ecnt -1 90 | esum += ecnt 91 | return esum 92 | 93 | parse() 94 | print(part1()) 95 | print(part2()) 96 | 97 | minibench({"parse": parse, "part1": part1, "part2": part2}) 98 | -------------------------------------------------------------------------------- /day23.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | import sys 3 | 4 | sys.setrecursionlimit(10000) 5 | tiles = open("day23.txt").read().split("\n") 6 | dimx = len(tiles[0]) 7 | dimy = len(tiles) 8 | (sx, sy) = (tiles[0].find("."), 0) 9 | (fx, fy) = (tiles[dimy - 1].find("."), dimy - 1) 10 | 11 | dirs = ((-1, 0, "<"), (1, 0, ">"), (0, -1, "^"), (0, 1, "v")) 12 | 13 | def dfs1(x, y, path, plen): 14 | dirs = ((-1, 0, "<"), (1, 0, ">"), (0, -1, "^"), (0, 1, "v")) 15 | if (x, y) == (fx, fy): 16 | return plen 17 | path[y * dimx + x] = 1 18 | best = 0 19 | for dx, dy, dc in dirs: 20 | (nx, ny) = (x + dx, y + dy) 21 | if (tiles[ny][nx] == "." or tiles[ny][nx] == dc) and not path[ny * dimx + nx]: 22 | best = max(best, dfs1(nx, ny, path, plen + 1)) 23 | path[y * dimx + x] = 0 24 | return best 25 | 26 | def part1(): 27 | p = [0] * (dimx * dimy) 28 | p[sx] = 1 29 | return dfs1(sx, sy + 1, p, 1) 30 | 31 | def dfs2(x, y, visited, prev, last, steps, branches, graph): 32 | visited.add((x, y)) 33 | cnt = 0 34 | for dx, dy, _ in dirs: 35 | (nx, ny) = (x + dx, y + dy) 36 | if tiles[ny][nx] != "#" and (nx, ny) != prev: 37 | cnt += 1 38 | if cnt > 1: 39 | cur = branches[(x, y)] = len(branches) 40 | graph.append([]) 41 | graph[cur].append((last, steps)) 42 | graph[last].append((cur, steps)) 43 | last = cur 44 | steps = 0 45 | for dx, dy, _ in dirs: 46 | (nx, ny) = (x + dx, y + dy) 47 | if (nx, ny) != prev and (nx, ny) in branches: 48 | cur = branches[(nx, ny)] 49 | graph[cur].append((last, steps + 1)) 50 | graph[last].append((cur, steps + 1)) 51 | elif tiles[ny][nx] != "#" and (nx, ny) not in visited: 52 | dfs2(nx, ny, visited, (x, y), last, steps + 1, branches, graph) 53 | 54 | def dfs3(cur, path, steps, graph): 55 | if cur == 1: 56 | return steps 57 | path |= 1 << cur 58 | best = 0 59 | for dst, add in graph[cur]: 60 | if not path & (1 << dst): 61 | best = max(best, dfs3(dst, path, steps + add, graph)) 62 | path ^= 1 << cur 63 | return best 64 | 65 | def bfstrim(start, graph): 66 | stak = [ start ] 67 | while stak: 68 | next = [] 69 | for cur in stak: 70 | for dst,_ in graph[cur]: 71 | if len(graph[dst]) == 3: 72 | for (t,d) in graph[dst]: 73 | if t == cur: 74 | tod = (t,d) 75 | graph[dst].remove(tod) 76 | next.append(dst) 77 | stak = next 78 | 79 | def part2(): 80 | branches = {(sx, sy): 0, (fx, fy): 1} 81 | graph = [[], []] 82 | dfs2(sx, sy + 1, set(), (sx, sy), 0, 1, branches, graph) 83 | bfstrim(0, graph) 84 | return dfs3(0, 0, 0, graph) 85 | 86 | print(part1()) 87 | print(part2()) 88 | 89 | minibench({"part1": part1, "part2": part2}) -------------------------------------------------------------------------------- /day24.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | import numpy as np 3 | 4 | lines = open("day24.txt").read().split("\n") 5 | 6 | px, py, pz = ([], [], []) 7 | vx, vy, vz = ([], [], []) 8 | 9 | def parse(): 10 | for a in [px, py, pz, vx, vy, vz]: 11 | a.clear() 12 | for line in lines: 13 | line = line.replace(" @ ", ", ") 14 | nums = list(map(int, line.split(", "))) 15 | p = 0 16 | for a in [px, py, pz, vx, vy, vz]: 17 | a.append(nums[p]) 18 | p += 1 19 | return len(lines) 20 | 21 | def part1(): 22 | num = len(lines) 23 | r1 = 200000000000000 24 | r2 = 400000000000000 25 | count = 0 26 | for i in range(num): 27 | a1 = vy[i] / vx[i] 28 | b1 = py[i] - a1 * px[i] 29 | for j in range(i + 1, num): 30 | a2 = vy[j] / vx[j] 31 | b2 = py[j] - a2 * px[j] 32 | if a1 == a2: 33 | continue 34 | x = (b2 - b1) / (a1 - a2) 35 | y = a1 * x + b1 36 | t1 = (x - px[i]) / vx[i] 37 | t2 = (x - px[j]) / vx[j] 38 | if t1 > 0 and t2 > 0 and x >= r1 and x <= r2 and y >= r1 and y <= r2: 39 | count += 1 40 | return count 41 | 42 | def part2(): 43 | A = np.array( 44 | [ 45 | [vy[1] - vy[0], vx[0] - vx[1], 0, py[0] - py[1], px[1] - px[0], 0], 46 | [vy[2] - vy[0], vx[0] - vx[2], 0, py[0] - py[2], px[2] - px[0], 0], 47 | [vz[1] - vz[0], 0, vx[0] - vx[1], pz[0] - pz[1], 0, px[1] - px[0]], 48 | [vz[2] - vz[0], 0, vx[0] - vx[2], pz[0] - pz[2], 0, px[2] - px[0]], 49 | [0, vz[1] - vz[0], vy[0] - vy[1], 0, pz[0] - pz[1], py[1] - py[0]], 50 | [0, vz[2] - vz[0], vy[0] - vy[2], 0, pz[0] - pz[2], py[2] - py[0]], 51 | ] 52 | ) 53 | 54 | x = [ 55 | (py[0] * vx[0] - py[1] * vx[1]) - (px[0] * vy[0] - px[1] * vy[1]), 56 | (py[0] * vx[0] - py[2] * vx[2]) - (px[0] * vy[0] - px[2] * vy[2]), 57 | (pz[0] * vx[0] - pz[1] * vx[1]) - (px[0] * vz[0] - px[1] * vz[1]), 58 | (pz[0] * vx[0] - pz[2] * vx[2]) - (px[0] * vz[0] - px[2] * vz[2]), 59 | (pz[0] * vy[0] - pz[1] * vy[1]) - (py[0] * vz[0] - py[1] * vz[1]), 60 | (pz[0] * vy[0] - pz[2] * vy[2]) - (py[0] * vz[0] - py[2] * vz[2]), 61 | ] 62 | np.set_printoptions(linewidth=100000) 63 | sol = np.linalg.solve(A, x) 64 | return round(sol[0] + sol[1] + sol[2]) 65 | 66 | 67 | parse() 68 | print(part1()) 69 | print(part2()) 70 | minibench({"parse": parse, "part1": part1, "part2": part2}) 71 | -------------------------------------------------------------------------------- /day25.mojo: -------------------------------------------------------------------------------- 1 | from parser import * 2 | from wrappers import minibench 3 | from array import Array 4 | 5 | alias maxc = 16 6 | alias maxn = 1600 7 | 8 | @always_inline 9 | fn encode(s: StringSlice, inout mapp: Array[DType.int16]) -> Int: 10 | alias orda = ord("a") 11 | var ret = 0 12 | for i in range(s.size): 13 | ret = ret * 26 + (int(s[i])) - orda 14 | if mapp[ret] == 0: 15 | mapp[0] += 1 16 | mapp[ret] = mapp[0] 17 | return int(mapp[ret]) 18 | 19 | @always_inline 20 | fn link(a: Int, b: Int, inout graph: Array[DType.int16]): 21 | ai = int(graph[a * maxc]) 22 | graph[a * maxc + 1 + ai] = b 23 | graph[a * maxc] = ai + 1 24 | bi = int(graph[b * maxc]) 25 | graph[b * maxc + 1 + bi] = a 26 | graph[b * maxc] = bi + 1 27 | 28 | fn main() raises: 29 | f = open("day25.txt", "r") 30 | lines = make_parser["\n"](f.read()) 31 | enc = Array[DType.int16](26 * 26 * 26) 32 | graph = Array[DType.int16](maxn * maxc) 33 | marks = Array[DType.int32](maxn * maxn) 34 | prev = Array[DType.int16](maxn) 35 | work = Array[DType.int16](maxn) 36 | var mark = 1 37 | 38 | @parameter 39 | fn parse() -> Int64: 40 | graph.zero() 41 | for l in range(lines.length()): 42 | line = lines[l] 43 | src = encode(line[0:3], enc) 44 | for k in range(5, line.size, 4): 45 | dst = encode(line[k : k + 3], enc) 46 | # print(line[0:3],src,line[k:k+3],dst) 47 | link(src, dst, graph) 48 | return int(enc[0]) 49 | 50 | # compute a bfs path from src to tgt 51 | # if tgt not specified, a path to somewhere farthest from src. 52 | @parameter 53 | fn bfs(src: Int, tgt: Int = -1) -> Int: 54 | prev.fill(-1) 55 | work[0] = src 56 | prev[src] = 0 57 | var ss = 1 58 | var si = 0 59 | while si < ss: 60 | cur = int(work[si]) 61 | si += 1 62 | if cur == tgt: 63 | break 64 | for di in range(graph[cur * maxc]): 65 | dst = int(graph[cur * maxc + 1 + di]) 66 | if marks[cur * maxn + dst] != mark and prev[dst] < 0: 67 | prev[dst] = cur 68 | work[ss] = dst 69 | ss += 1 70 | var dsl = tgt 71 | if dsl < 0: 72 | dsl = int(work[ss - 1]) 73 | if prev[dsl] > 0: 74 | ss = 0 75 | while dsl > 0: 76 | work[ss] = dsl 77 | ss += 1 78 | dsl = int(prev[dsl]) 79 | return ss 80 | 81 | # push cnt flow between src and tgt 82 | # then identify the cut and compute the subgraphs 83 | @parameter 84 | fn edmond_karpik(cnt: Int, src: Int) -> Int: 85 | mark += 1 86 | var tgt = -1 87 | for i in range(cnt): 88 | plen = bfs(src, tgt) 89 | var pre = int(work[0]) 90 | tgt = pre 91 | for i in range(1, plen): 92 | cur = int(work[i]) 93 | marks[cur * maxn + pre] = mark 94 | pre = cur 95 | 96 | # Compute the reachable nodes in residual graph 97 | return bfs(src, tgt) 98 | 99 | @parameter 100 | fn part1() -> Int64: 101 | start = encode(lines[0][0:3], enc) 102 | reachable = edmond_karpik(3, start) 103 | return reachable * (int(enc[0]) - reachable) 104 | 105 | minibench[parse]("parse") 106 | minibench[part1]("part1") 107 | 108 | print(lines.length(), "lines") 109 | print(graph.bytecount(), "graph size") 110 | print(marks.bytecount(), "marks size") 111 | print(prev.bytecount() + work.bytecount() + enc.bytecount(), "work buffers size") 112 | -------------------------------------------------------------------------------- /day25.py: -------------------------------------------------------------------------------- 1 | from benchy import minibench 2 | from collections import defaultdict 3 | lines = open("day25.txt").read().split("\n") 4 | graph = defaultdict(set) 5 | 6 | def parse(): 7 | graph.clear() 8 | for line in lines: 9 | src = line[:3] 10 | for dst in line[5:].split(' '): 11 | graph[src].add(dst) 12 | graph[dst].add(src) 13 | return len(graph) 14 | 15 | # compute a bfs path from src to tgt 16 | # if tgt not specified, a path to somewhere farthest from src. 17 | def bfs(src, tgt = None): 18 | prev = { src : None } 19 | stak = [ src ] 20 | sp = 0 21 | while sp < len(stak): 22 | cur = stak[sp] 23 | sp += 1 24 | if cur == tgt: 25 | break 26 | for dst in graph[cur]: 27 | if dst not in prev: 28 | prev[dst] = cur 29 | stak.append(dst) 30 | if tgt == None: 31 | tgt = stak[-1] 32 | elif tgt not in prev: 33 | return stak 34 | path = [] 35 | while (tgt != None): 36 | path.append(tgt) 37 | tgt = prev[tgt] 38 | return path 39 | 40 | # push cnt flow between src and tgt 41 | # then identify the cut and compute the subgraphs 42 | def edmond_karp(cnt, src, tgt = None): 43 | removed = [] 44 | for i in range(cnt): 45 | path = bfs(src, tgt) 46 | tgt = pre = path[0] 47 | for i in range(1, len(path)): 48 | cur = path[i] 49 | graph[cur].remove(pre) 50 | removed.append((cur,pre)) 51 | pre = cur 52 | # Compute the reachable nodes in residual graph 53 | reachable = bfs(src, tgt) 54 | # Restore removed edges 55 | for (a,b) in removed: 56 | graph[a].add(b) 57 | return len(reachable) 58 | 59 | def part1(): 60 | total = len(graph) 61 | first = lines[0][:3] 62 | reachable = edmond_karp(3, first) 63 | return(reachable * (total-reachable)) 64 | 65 | print(parse()) 66 | print(part1()) 67 | 68 | minibench({"parse": parse, "part1": part1}) -------------------------------------------------------------------------------- /mojoproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | authors = ["Staszek Pasko "] 3 | channels = ["conda-forge", "https://conda.modular.com/max"] 4 | description = "Advent of Code 2023 (in Mojo)" 5 | name = "aoc2023" 6 | platforms = ["linux-64"] 7 | version = "0.1.0" 8 | 9 | [tasks] 10 | 11 | [dependencies] 12 | max = ">=24.5.0,<25" 13 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 128 3 | skip-magic-trailing-comma = 1 4 | -------------------------------------------------------------------------------- /quicksort.mojo: -------------------------------------------------------------------------------- 1 | from array import Array 2 | 3 | # Comparison 'operator' for SIMD vectors. 4 | @always_inline 5 | fn lessthan[width: Int, AType: DType, steps: Int = width](lhs: SIMD[AType, width], rhs: SIMD[AType, width]) -> Bool: 6 | for i in range(steps): 7 | if lhs[i] < rhs[i]: 8 | return True 9 | if lhs[i] > rhs[i]: 10 | return False 11 | return False 12 | 13 | # Implements Quicksort for SIMD vectors (not _using_ SIMD for anything, just sorting SIMD data) 14 | # Supports partial sorting (set steps to less than width) 15 | fn qsort[width: Int, AType: DType, steps: Int = width](inout a : Array[AType], start: Int = 0, end: Int = -1): 16 | var tend = end 17 | if tend < 0: 18 | tend = a.dynamic_size // width 19 | if tend - start < 2: 20 | return 21 | # Use Lomuto-like midpoint scheme which also helps us sort 2/3 element ranges 'for free' 22 | mp = (start + tend)//2 23 | var a1 = a.load[width=width](start * width) 24 | var a2 = a.load[width=width](mp * width) 25 | # compare [mid] vs [lo] 26 | var s = 0 27 | if lessthan[width, AType, steps](a2, a1): 28 | t = a2 29 | a2 = a1 30 | a1 = t 31 | s += 1 32 | if tend - start == 2: 33 | if s > 0: 34 | a.store[width=width](start * width, a1) 35 | a.store[width=width](mp * width,a2) 36 | # print(a1,a2) 37 | return 38 | var a3 = a.load[width=width]((tend - 1) * width) 39 | # compare [hi] vs [lo] 40 | if lessthan[width, AType, steps](a3, a1): 41 | t = a3 42 | a3 = a1 43 | a1 = t 44 | s += 1 45 | # compare [mid] vs [lo] 46 | if lessthan[width, AType, steps](a3, a2): 47 | t = a3 48 | a3 = a2 49 | a2 = t 50 | s += 1 51 | if s > 0: 52 | a.store[width=width](start * width, a1) 53 | a.store[width=width](mp * width, a2) 54 | a.store[width=width]((tend - 1) * width,a3) 55 | if tend - start == 3: 56 | return 57 | 58 | # Handle the rest with Hoare partitioning. 59 | # The 3-point method above places the midpoint in the middle, 60 | # rather than the end like normal Lomuto 61 | var i = start 62 | var j = tend - 1 63 | while True: 64 | while lessthan(a1, a2): 65 | i += 1 66 | a1 = a.load[width=width](i * width) 67 | while lessthan(a2, a3): 68 | j -= 1 69 | a3 = a.load[width=width](j * width) 70 | if i >= j: 71 | break 72 | t = a3 73 | a3 = a1 74 | a1 = t 75 | a.store[width=width](i * width, a1) 76 | a.store[width=width](j * width, a3) 77 | 78 | # Recursive calls 79 | qsort[width, AType, steps](a, start, j + 1) 80 | qsort[width, AType, steps](a, j + 1, tend) 81 | 82 | 83 | fn main(): 84 | var buf = Array[DType.int32](40) 85 | for i in range(10): 86 | t = SIMD[DType.int32, 4](1000 - i//2, 1000 - i//3, i, 100 - i) 87 | buf.store[width=4](i * 4, t) 88 | print(t) 89 | print() 90 | qsort[4](buf) 91 | for i in range(10): 92 | print(buf.load[width=4](i*4)) 93 | 94 | 95 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | suffix=pc 2 | if [ `uname` == "Darwin" ]; then 3 | suffix="mac" 4 | fi 5 | echo $suffix 6 | 7 | ( echo $1.py; python3 $1.py ) | tee -a all_python3_$suffix.txt 8 | ( echo $1.py; pypy3 $1.py ) | tee -a all_pypy3_$suffix.txt 9 | ( echo $1.mojo; mojo $1.mojo ) | tee -a all_mojo_$suffix.txt -------------------------------------------------------------------------------- /vis/box1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p88h/aoc2023/daaa74422bc84e67bfefcdf242e73222e23d75d6/vis/box1.png -------------------------------------------------------------------------------- /vis/box1l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p88h/aoc2023/daaa74422bc84e67bfefcdf242e73222e23d75d6/vis/box1l.png -------------------------------------------------------------------------------- /vis/box2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p88h/aoc2023/daaa74422bc84e67bfefcdf242e73222e23d75d6/vis/box2.png -------------------------------------------------------------------------------- /vis/box2l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p88h/aoc2023/daaa74422bc84e67bfefcdf242e73222e23d75d6/vis/box2l.png -------------------------------------------------------------------------------- /vis/box3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p88h/aoc2023/daaa74422bc84e67bfefcdf242e73222e23d75d6/vis/box3.png -------------------------------------------------------------------------------- /vis/box3l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p88h/aoc2023/daaa74422bc84e67bfefcdf242e73222e23d75d6/vis/box3l.png -------------------------------------------------------------------------------- /vis/common.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import glob 3 | import os 4 | import time 5 | import argparse 6 | 7 | 8 | class View: 9 | def __init__(self, W=1920, H=1080, FPS=60, FS=16) -> None: 10 | self.width = W 11 | self.height = H 12 | self.fps = FPS 13 | self.save = False 14 | self.frame = 0 15 | self.fsize = FS 16 | 17 | def setup(self, title="AOC"): 18 | pygame.init() 19 | pygame.font.init() 20 | self.win = pygame.display.set_mode((self.width, self.height)) 21 | pygame.display.set_caption(title) 22 | self.font = pygame.freetype.Font("vis/Inconsolata-SemiBold.ttf", self.fsize) 23 | self.font.origin = True 24 | self.clock = pygame.time.Clock() 25 | self.win.fill((0, 0, 0)) 26 | 27 | def snaps(self): 28 | return "{}/frame_*.jpg".format(self.tmp_path) 29 | 30 | def record(self, output_path): 31 | self.save = True 32 | self.output_path = output_path 33 | self.tmp_path = os.path.dirname(output_path) + "/tmp/" 34 | for f in glob.glob(self.snaps()): 35 | os.remove(f) 36 | print("Recording video to: ", self.output_path) 37 | 38 | def render(self, controller): 39 | for object in controller.objects: 40 | object.update(self, controller) 41 | 42 | pygame.display.update() 43 | self.clock.tick(self.fps) 44 | 45 | if controller.animate: 46 | self.frame += 1 47 | if self.save: 48 | pygame.image.save(self.win, "{}/frame_{:04}.jpg".format(self.tmp_path, self.frame)) 49 | 50 | def finish(self): 51 | if not self.save: 52 | return 53 | print("Now saving video") 54 | os.system("ffmpeg -r {} -i {}/frame_%04d.jpg {}".format(self.fps, self.tmp_path, self.output_path)) 55 | print("Cleaning up snaps") 56 | for f in glob.glob(self.snaps()): 57 | os.remove(f) 58 | 59 | 60 | class Controller: 61 | def __init__(self, start_anim=True) -> None: 62 | self.animate = start_anim 63 | self.quit = False 64 | self.objects = [] 65 | self.clickables = [] 66 | parser = argparse.ArgumentParser() 67 | parser.add_argument("-r", "--rec", help="record movie", action="store_true") 68 | parser.add_argument("-f", "--fps", help="set fps", type=int) 69 | self.args = parser.parse_args() 70 | 71 | def workdir(self): 72 | import __main__ 73 | 74 | return os.path.dirname(os.path.dirname(os.path.realpath(__main__.__file__))) 75 | 76 | def basename(self): 77 | import __main__ 78 | 79 | return os.path.splitext(os.path.basename(os.path.realpath(__main__.__file__)))[0] 80 | 81 | def add(self, object, clickable=False): 82 | self.objects.append(object) 83 | if clickable: 84 | self.clickables.append(object) 85 | 86 | def run(self, view): 87 | if self.args.rec: 88 | view.record(os.path.join(self.workdir(), self.basename() + ".mp4")) 89 | if self.args.fps: 90 | view.fps = self.args.fps 91 | print("Override FPS to: ", view.fps) 92 | pygame.event.clear() 93 | start = time.time() 94 | fcnt = 0 95 | while not self.quit: 96 | view.render(self) 97 | fcnt += 1 98 | for event in pygame.event.get(): 99 | if event.type == pygame.QUIT: 100 | self.quit = True 101 | if event.type == pygame.KEYDOWN: 102 | if event.key == pygame.K_SPACE: 103 | self.animate = not self.animate 104 | if event.key == pygame.K_ESCAPE: 105 | self.quit = True 106 | if event.type == pygame.MOUSEBUTTONDOWN: 107 | pos = pygame.mouse.get_pos() 108 | for obj in self.clickables: 109 | obj.click(pos, True) 110 | if event.type == pygame.MOUSEBUTTONUP: 111 | pos = pygame.mouse.get_pos() 112 | for obj in self.clickables: 113 | obj.click(pos, False) 114 | delta = time.time() - start 115 | if delta >= 3: 116 | print("FPS: {:02f}".format(fcnt / delta)) 117 | fcnt = 0 118 | start = time.time() 119 | 120 | pygame.quit() 121 | view.finish() 122 | -------------------------------------------------------------------------------- /vis/day02.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | from random import randint 3 | import math 4 | import pygame 5 | 6 | 7 | class Game: 8 | def __init__(self, lines) -> None: 9 | self.lines = lines 10 | self.fidx = 0 11 | self.colors = {"red": (250, 200, 200), "green": (200, 250, 200), "blue": (200, 200, 250)} 12 | self.maxs = [] 13 | 14 | def update(self, view, controller): 15 | if not controller.animate: 16 | return 17 | view.win.fill((0, 0, 0)) 18 | ox = 60 19 | for p in range(self.fidx): 20 | oy = 0 21 | for col in ["red", "green", "blue"]: 22 | cnt = self.maxs[p][col] 23 | for j in range(cnt): 24 | pygame.draw.circle(view.win,self.colors[col],(ox,1060-oy), 8) 25 | oy += 18 26 | oy += (19-j)*18 27 | oy += 9 28 | ox += 18 29 | ox += 9 30 | if self.fidx >= len(self.lines): 31 | return 32 | line = self.lines[self.fidx] 33 | (_, rest) = line.split(": ") 34 | draws = rest.split("; ") 35 | mdict = {"red": 0, "green": 0, "blue": 0} 36 | for draw in draws: 37 | draw = draw.replace(",", "") 38 | toks = draw.split(" ") 39 | for i in range(len(toks) // 2): 40 | cnt = int(toks[i * 2]) 41 | col = toks[i * 2 + 1] 42 | mdict[col] = max(mdict[col],cnt) 43 | for j in range(cnt): 44 | pygame.draw.circle(view.win,self.colors[col],(ox,1060-j*18), 8) 45 | ox += 18 46 | ox += 9 47 | self.maxs.append(mdict) 48 | self.fidx += 1 49 | 50 | 51 | 52 | 53 | 54 | view = View(1920, 1080, 30, 24) 55 | view.setup("Day 02") 56 | controller = Controller() 57 | lines = open(controller.workdir() + "/day02.txt").read().split("\n") 58 | controller.add(Game(lines)) 59 | controller.run(view) 60 | -------------------------------------------------------------------------------- /vis/day03.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from common import View, Controller 3 | 4 | 5 | class Background: 6 | def __init__(self, lines, font) -> None: 7 | (fw, fh) = (8, 20) 8 | self.width = fw * len(lines[0]) + 40 9 | self.height = fh * len(lines) + 40 10 | print(self.width, self.height) 11 | self.tmp = pygame.Surface((self.width, self.height), pygame.SRCALPHA) 12 | self.tmp.fill((0, 0, 0, 0)) 13 | for i in range(len(lines)): 14 | view.font.render_to(self.tmp, (20, 20 + fh * i), lines[i], (200, 200, 200)) 15 | self.pan = 0 16 | 17 | def update(self, view, controller): 18 | if not controller.animate: 19 | return 20 | view.win.fill((0, 0, 0)) 21 | view.win.blit(self.tmp, (60, 30 - self.pan)) 22 | if self.pan < 100: 23 | self.pan += 1 24 | elif self.pan < 2200: 25 | self.pan += 2 26 | 27 | 28 | class Numbers: 29 | def __init__(self, nums, bg) -> None: 30 | self.tiles = [] 31 | self.fidx = 0 32 | self.nums = nums 33 | self.bg = bg 34 | self.tmp = pygame.Surface((bg.width, bg.height), pygame.SRCALPHA) 35 | self.tmp.fill((0, 0, 0, 0)) 36 | 37 | def update(self, view, controller): 38 | if not controller.animate: 39 | return 40 | (fw, fh) = (8, 20) 41 | if self.fidx < len(self.nums): 42 | (x, y, w, r, bx, by, bw, bh, s) = self.nums[self.fidx] 43 | pygame.draw.rect(self.tmp, (200, 200, 160, 180), (20 + x * fw, 4 + y * fh, fw * w, fh), 1) 44 | if self.fidx >= 50 and self.fidx - 50 < len(self.nums): 45 | (x, y, w, r, bx, by, bw, bh, s) = self.nums[self.fidx - 50] 46 | pygame.draw.rect(self.tmp, (180, 220, 220, 200), (20 + bx * fw, 4 + by * fh, bw * fw, bh * fh), 1) 47 | if self.fidx >= 100 and self.fidx - 100 < len(self.nums): 48 | (x, y, w, r, bx, by, bw, bh, s) = self.nums[self.fidx - 100] 49 | if not s: 50 | pygame.draw.rect(self.tmp, (0, 0, 0, 255), (20 + bx * fw, 4 + by * fh, bw * fw, bh * fh), 1) 51 | view.win.blit(self.tmp, (60, 30 - bg.pan)) 52 | self.fidx += 1 53 | 54 | 55 | def bbox(y, x, l, r, dimx, dimy, lines): 56 | sx = max(x - 1, 0) 57 | lx = min(x + l, dimx - 1) + 1 58 | sy = max(y - 1, 0) 59 | ly = min(y + 1, dimy - 1) + 1 60 | star = None 61 | for gy in range(sy, ly): 62 | for gx in range(sx, lx): 63 | if lines[gy][gx] == "*": 64 | star = (gy, gx) 65 | return (x, y, l, r, sx, sy, lx - sx, ly - sy, star) 66 | 67 | 68 | def find_nums(lines): 69 | dimx = len(lines[0]) 70 | dimy = len(lines) 71 | nums = [] 72 | for y in range(dimy): 73 | r = 0 74 | q = 0 75 | for x in range(dimx): 76 | c = ord(lines[y][x]) 77 | if c >= 48 and c <= 57: 78 | d = c - 48 79 | r = r * 10 + d 80 | q += 1 81 | else: 82 | if q > 0: 83 | nums.append(bbox(y, x - q, q, r, dimx, dimy, lines)) 84 | r = q = 0 85 | if q > 0: 86 | nums.append(bbox(y, dimx - q, q, r, dimx, dimy, lines)) 87 | return nums 88 | 89 | 90 | view = View(1280, 720, 60) 91 | view.setup("Day 03") 92 | controller = Controller() 93 | lines = open(controller.workdir() + "/day03.txt").read().split("\n") 94 | print(view.font) 95 | bg = Background(lines, view.font) 96 | controller.add(bg) 97 | controller.add(Numbers(find_nums(lines), bg)) 98 | controller.run(view) 99 | -------------------------------------------------------------------------------- /vis/day05.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | import pygame 3 | 4 | 5 | def parse(lines): 6 | s = lines[0].split() 7 | ranges = [] 8 | numbers = list(map(int, s[1:])) 9 | for i in range(len(numbers) // 2): 10 | ranges.append((numbers[2 * i], numbers[2 * i] + numbers[2 * i + 1] - 1)) 11 | cur = [] 12 | steps = [] 13 | for line in lines[3:]: 14 | if not line: 15 | continue 16 | if line[-1] == ":": 17 | steps.append(sorted(cur)) 18 | cur = [] 19 | continue 20 | (dst, src, l) = map(int, line.split()) 21 | cur.append((src, dst, l)) 22 | steps.append(sorted(cur)) 23 | return (numbers, ranges, steps) 24 | 25 | 26 | def split(ranges, step): 27 | next = [] 28 | tmp = [] 29 | for a, b in ranges: 30 | for src, dst, l in step: 31 | ofs = dst - src 32 | if a >= src + l: 33 | continue 34 | if a < src: # some is untranslated 35 | tmp.append((a, b)) 36 | next.append((a, src - 1)) 37 | a = src 38 | # a >= src. 39 | if b >= src + l: # some range remains 40 | tmp.append((a, b)) 41 | next.append((a + ofs, (src + l - 1) + ofs)) 42 | a = src + l 43 | else: # everything fits 44 | tmp.append((a, b)) 45 | next.append((a + ofs, b + ofs)) 46 | a = b + 1 47 | break 48 | if a <= b: # some was left untranslated 49 | tmp.append((a, b)) 50 | next.append((a, b)) 51 | return (tmp, next) 52 | 53 | 54 | class Game: 55 | def __init__(self, parse) -> None: 56 | (self.numbers, self.ranges, self.steps) = parse 57 | self.mode = 0 58 | self.sidx = 0 59 | self.fidx = 0 60 | 61 | def update(self, view, controller): 62 | if not controller.animate: 63 | return 64 | view.win.fill((0, 0, 0)) 65 | for i in range(len(self.steps)): 66 | step = self.steps[i] 67 | for src, dst, l in step: 68 | y0 = 60 + 140 * i 69 | y1 = y0 + 100 70 | x0 = src // 2236962 71 | x1 = (src + l) // 2236962 72 | x2 = dst // 2236962 73 | x3 = (dst + l) // 2236962 74 | cr = src % 240 75 | cb = dst % 240 76 | pygame.draw.polygon(view.win, (cr, 220, cb, 160), [(x0, y0), (x1, y0), (x3, y1), (x2, y1)], 1) 77 | if self.fidx == 0: 78 | (self.ranges, self.next) = split(self.ranges, self.steps[self.sidx]) 79 | print(self.ranges, self.next) 80 | 81 | for i in range(len(self.ranges)): 82 | (a, b) = self.ranges[i] 83 | (c, d) = self.next[i] 84 | x = (a * (140 - self.fidx) + c * self.fidx) // (140 * 2236962) 85 | w = (b - a) // 2236962 86 | y = 20 + 140 * self.sidx + self.fidx 87 | pygame.draw.rect(view.win, (200, 240, 200, 120), (x, y, w, 40)) 88 | pygame.draw.rect(view.win, (160, 140, 100, 220), (x, y, w, 40), 1) 89 | self.fidx += 1 90 | if self.fidx == 140: 91 | self.fidx = 0 92 | self.sidx += 1 93 | self.ranges = self.next 94 | if self.sidx == len(self.steps): 95 | controller.animate = False 96 | 97 | 98 | view = View(1920, 1080, 30, 24) 99 | view.setup("Day 05") 100 | controller = Controller() 101 | lines = open(controller.workdir() + "/day05.txt").read().split("\n") 102 | controller.add(Game(parse(lines))) 103 | controller.run(view) 104 | -------------------------------------------------------------------------------- /vis/day06.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | from random import randint 3 | import math 4 | import pygame 5 | 6 | 7 | class Game: 8 | def __init__(self, lines) -> None: 9 | self.times = list(map(int, lines[0].split()[1:])) 10 | self.dist = list(map(int, lines[1].split()[1:])) 11 | self.tidx = 0 12 | self.vidx = 0 13 | self.colors = [ 14 | (250, 200, 200), 15 | (200, 250, 200), 16 | (200, 200, 250), 17 | (250, 250, 200), 18 | (250, 200, 250), 19 | (200, 250, 250), 20 | (250, 250, 250), 21 | ] 22 | 23 | def update(self, view, controller): 24 | if not controller.animate: 25 | return 26 | # view.win.fill((0, 0, 0)) 27 | t = self.times[self.tidx] 28 | p = 0 29 | for x in range(1, self.vidx + 1): 30 | v = (x * (t - x)) / 2 31 | pygame.draw.line(view.win, self.colors[self.tidx], ((x - 1) * 10 + 50, 1040 - p), (x * 10 + 50, 1040 - v)) 32 | p = v 33 | if self.vidx < t: 34 | self.vidx += 1 35 | else: 36 | self.vidx = 0 37 | self.tidx += 1 38 | if self.tidx == len(self.times): 39 | controller.animate = False 40 | 41 | 42 | view = View(1920, 1080, 30, 24) 43 | view.setup("Day 06") 44 | controller = Controller() 45 | lines = open(controller.workdir() + "/day06.txt").read().split("\n") 46 | controller.add(Game(lines)) 47 | controller.run(view) 48 | -------------------------------------------------------------------------------- /vis/day07.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | from random import randint 3 | import pygame 4 | 5 | pats = { 6 | '2': [" "," * "," "," * "," "], 7 | '3': [" * "," "," * "," "," * "], 8 | '4': ["* *"," "," "," ","* *"], 9 | '5': ["* *"," "," * "," ","* *"], 10 | '6': ["* *"," ","* *"," ","* *"], 11 | '7': [" * ","* *"," * ","* *"," * "], 12 | '8': ["* *"," * ","* *"," * ","* *"], 13 | '9': [" *"," **","***","** ","* "], 14 | 'T': [" * ","***","* *","***"," * "]} 15 | 16 | class Game: 17 | def __init__(self, hands) -> None: 18 | self.hands = hands 19 | self.mode = 0 20 | self.sidx = 0 21 | self.fidx = 0 22 | self.bigfont = pygame.freetype.Font("vis/Inconsolata-SemiBold.ttf", 48) 23 | 24 | def update(self, view, controller): 25 | if not controller.animate: 26 | return 27 | view.win.fill((0, 0, 0)) 28 | p = 0 29 | for hand, score, col in self.hands: 30 | x = p * 100 + 40 31 | if x > 1920: 32 | break 33 | y = 960 34 | pygame.draw.line(view.win, (220, 220, 220, 240), (x, y), (x + 80, y), 1) 35 | cols = "♠♥♦♣" 36 | for c in hand: 37 | points = [(x, y), (x, y - 160), (x + 80, y - 160), (x + 80, y)] 38 | pygame.draw.lines(view.win, (220, 220, 220, 240), False, points, 1) 39 | view.font.render_to(view.win, (x + 60, y - 130), c, (200, 200, 200)) 40 | if c in pats: 41 | pat = pats[c] 42 | j = 0 43 | for row in pat: 44 | j += 1 45 | if j == 5 and y != 960: 46 | break 47 | k = 0 48 | for el in row: 49 | k += 1 50 | if (el == '*'): 51 | view.font.render_to(view.win, (x + 20*k-5, y - 130 + 20*j), cols[col], (200, 200, 200)) 52 | 53 | y -= 120 54 | p += 1 55 | 56 | 57 | view = View(1920, 1080, 30, 24) 58 | view.setup("Day 07") 59 | controller = Controller() 60 | lines = open(controller.workdir() + "/day07.txt").read().split() 61 | parsed = [(lines[i * 2], int(lines[i * 2 + 1]), randint(0,3)) for i in range(len(lines) // 2)] 62 | controller.add(Game(parsed)) 63 | controller.run(view) 64 | -------------------------------------------------------------------------------- /vis/day08.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | from random import randint 3 | import math 4 | import pygame 5 | 6 | 7 | class Game: 8 | def __init__(self, lines) -> None: 9 | self.I = lines[0] 10 | self.S = [] 11 | self.F = set() 12 | self.L = {} 13 | self.R = {} 14 | for l in lines[2:]: 15 | n = l[0:3] 16 | if n[-1] == "A": 17 | self.S.append(n) 18 | if n[-1] == "Z": 19 | self.F.add(n) 20 | self.L[n] = l[7:10] 21 | self.R[n] = l[12:15] 22 | self.B = {} 23 | self.Z = [] 24 | for s in self.S: 25 | vis = set([s]) 26 | self.B[s] = [[s]] 27 | d = 0 28 | while self.B[s][d]: 29 | nex = [] 30 | for t in self.B[s][d]: 31 | l = self.L[t] 32 | r = self.R[t] 33 | if l not in vis: 34 | vis.add(l) 35 | nex.append(l) 36 | if r not in vis: 37 | vis.add(r) 38 | nex.append(r) 39 | self.B[s].append(nex) 40 | d += 1 41 | self.Z.append((len(self.B[s]),s)) 42 | self.Z.sort(reverse=True) 43 | print("subgraphs:", self.Z) 44 | self.speed = 1 45 | self.sidx = 0 46 | self.fidx = 0 47 | self.act = self.S 48 | self.prepare() 49 | 50 | def prepare(self): 51 | self.tmp = pygame.Surface((1920,1080), pygame.SRCALPHA) 52 | self.tmp.fill((0, 0, 0, 0)) 53 | self.loc = {} 54 | self.dist = [ 0 ] * len(self.Z) 55 | siz = 24 56 | pad = siz + 8 57 | # Just ... precomputed 58 | w = [ 10, 7, 6, 5, 4, 3 ] 59 | x = 60 60 | y = 80 61 | for c in range(len(self.Z)): 62 | dx = 0 63 | dy = pad 64 | ox = -pad 65 | oy = 0 66 | (k, s) = self.Z[c] 67 | turns = [k//2-w[c],k//2,k-w[c]-1,k-1] 68 | for d in range (1,len(self.B[s])): 69 | foo = self.B[s][d] 70 | p1 = [x,y,siz,siz] 71 | p2 = [x+ox,y+oy,siz,siz] 72 | pygame.draw.rect(self.tmp, (250,250,250,250), p1, 1) 73 | if (len(foo)>0): 74 | pygame.draw.rect(self.tmp, (250,250,250,250), p2, 1) 75 | self.loc[foo[0]]=p1 76 | self.loc[foo[1]]=p2 77 | else: 78 | self.loc[self.B[s][0][0]]=p1 79 | 80 | if d in turns: 81 | (dx, dy) = (dy, -dx) 82 | ox = oy = 0 83 | if dx > 0: 84 | oy = pad 85 | elif dx < 0: 86 | oy = -pad 87 | elif dy < 0: 88 | ox = pad 89 | y += dy 90 | x += dx 91 | if d + 1 in turns: 92 | if dy > 0: 93 | oy = pad 94 | elif dy < 0: 95 | oy = -pad 96 | elif dx < 0: 97 | ox = -pad 98 | elif dx > 0: 99 | ox = pad 100 | x += (w[c] + 4) * pad 101 | 102 | def update(self, view, controller): 103 | if not controller.animate: 104 | return 105 | view.win.fill((0, 0, 0)) 106 | view.win.blit(self.tmp, (0, 0)) 107 | al = 240 // self.speed 108 | txt = "NEXT DIRECTIONS: " 109 | for o in range(self.sidx,self.sidx+10): 110 | txt += self.I[(self.sidx + o) % len(self.I)] 111 | view.font.render_to(view.win, (1460,860), txt, (200, 200, 200)) 112 | for r in range(self.speed): 113 | c = self.I[self.sidx % len(self.I)] 114 | for i in range(len(self.act)): 115 | n = self.act[i] 116 | p = self.loc[n] 117 | if r == 0: 118 | txt = "Camel " + str(i) + " at: " + str(n) + " (" + str(self.dist[i]) + " steps)" 119 | view.font.render_to(view.win, (1460,884 + i * 24), txt, (200, 200, 200)) 120 | if n in self.F: 121 | pygame.draw.rect(view.win, (al*r,al*(r+1),al*r,240), p) 122 | continue 123 | self.dist[i] += 1 124 | pygame.draw.rect(view.win, (al*r,al*(r+1),al*r,240), p) 125 | if c == "L": 126 | n = self.L[n] 127 | else: 128 | n = self.R[n] 129 | self.act[i] = n 130 | self.sidx += 1 131 | self.fidx += 1 132 | if self.fidx % 120 == 0 and self.speed < 5: 133 | self.speed += 1 134 | print(self.speed) 135 | 136 | view = View(1920, 1080, 30, 24) 137 | view.setup("Day 08") 138 | controller = Controller() 139 | lines = open(controller.workdir() + "/day08.txt").read().split("\n") 140 | controller.add(Game(lines)) 141 | controller.run(view) 142 | -------------------------------------------------------------------------------- /vis/day09.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | from random import randint 3 | import pygame 4 | 5 | 6 | class Game: 7 | def __init__(self, lines) -> None: 8 | self.seqs = [] 9 | for l in lines: 10 | self.seqs.append(list(map(int, l.split(" ")))) 11 | self.fidx = 0 12 | self.pidx = 0 13 | self.cidx = 0 14 | self.colors = [] 15 | for i in range(24): 16 | r = 10 + i * 10 17 | b = 250 - i * 10 18 | g = 230 19 | self.colors.append((r,g,b)) 20 | 21 | def update_text(self, view): 22 | txt = "F" + "'"*self.cidx + " = " 23 | for v in self.seqs[self.fidx]: 24 | txt += str(v) + " " 25 | view.font.render_to(view.win, (20,20+self.cidx*20), txt, self.colors[self.cidx]) 26 | if self.cidx == 0: 27 | txt2 = str(self.fidx + 1) + " / " + str(len(self.seqs)) 28 | view.font.render_to(view.win, (1800,20), txt2, self.colors[self.cidx]) 29 | 30 | 31 | def update(self, view, controller): 32 | if not controller.animate: 33 | return 34 | if self.fidx == 0 and self.pidx == 0 and self.cidx == 0: 35 | view.win.fill((0, 0, 0)) 36 | self.update_text(view) 37 | # take the current sequence 38 | t = self.seqs[self.fidx] 39 | # plot up to the current point 40 | oy = min(t) 41 | sy = ((max(t)-min(t)) // 1000) + 1 42 | sx = 1800 // len(t) 43 | px = pv = -1 44 | for i in range(1, self.pidx): 45 | x = 60 + i * sx 46 | v = 1040 - (t[i] - oy) // sy 47 | if px > 0: 48 | pygame.draw.line(view.win, self.colors[self.cidx], (px, pv), (x, v), 2) 49 | (px, pv) = (x, v) 50 | self.pidx += 1 51 | if self.pidx == len(t): 52 | self.pidx = 0 53 | orr = 0 54 | for i in range(len(t)-1): 55 | t[i] = t[i+1] - t[i] 56 | orr |= t[i] 57 | t.pop() 58 | if not(t) or orr == 0: 59 | self.fidx += 1 60 | if self.fidx < len(self.seqs): 61 | view.win.fill((0, 0, 0)) 62 | self.cidx = 0 63 | else: 64 | controller.animate = False 65 | else: 66 | self.cidx += 1 67 | if controller.animate: 68 | self.update_text(view) 69 | 70 | 71 | view = View(1920, 1080, 60, 24) 72 | view.setup("Day 09") 73 | controller = Controller() 74 | lines = open(controller.workdir() + "/day09.txt").read().split("\n") 75 | controller.add(Game(lines)) 76 | controller.run(view) 77 | -------------------------------------------------------------------------------- /vis/day11.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | from random import randint 3 | from pygame import gfxdraw 4 | import pygame 5 | 6 | 7 | class Display: 8 | def __init__(self, lines) -> None: 9 | self.lines = lines 10 | self.stars = {} 11 | self.fidx = 0 12 | self.speed = 1 13 | self.scale = 8 14 | self.emptyv = [ True ] * len(self.lines) 15 | self.emptyh = [ True ] * len(self.lines) 16 | for y in range(len(lines)): 17 | for x in range(len(lines[y])): 18 | if lines[y][x] == '#': 19 | self.emptyv[x] = False 20 | self.emptyh[y] = False 21 | self.cntv = self.emptyv.count(True) 22 | self.cnth = self.emptyh.count(True) 23 | 24 | def update(self, view, controller): 25 | if not controller.animate: 26 | return 27 | dimx = len(self.lines[0]) + self.cntv * self.fidx 28 | dimy = len(self.lines) + self.cnth * self.fidx 29 | self.scale = 1200 / dimy 30 | print(self.scale) 31 | view.win.fill((10, 10, 10, 0)) 32 | bw = int(dimx * self.scale) 33 | bh = int(dimy * self.scale) 34 | oy = by = (1200 - bh) // 2 35 | ox = bx = (1920 - bw) // 2 36 | sr = self.scale / 2 37 | for x in range(len(self.lines[0])): 38 | if self.emptyv[x]: 39 | pygame.draw.rect(view.win, (0,0,0), (int(ox), int(oy), int((1 + self.fidx) * self.scale), bh)) 40 | ox += self.fidx * self.scale 41 | ox += self.scale 42 | for y in range(len(self.lines)): 43 | ox = bx 44 | if self.emptyh[y]: 45 | pygame.draw.rect(view.win, (0,0,0), (int(ox), int(oy), bw, int((1 + self.fidx) * self.scale))) 46 | oy += self.fidx * self.scale 47 | else: 48 | for x in range(len(self.lines[y])): 49 | if self.lines[y][x] == '#': 50 | if sr < 1: 51 | gfxdraw.pixel(view.win, int(ox), int(oy), (0xff,0xcd,0x3c)) 52 | else: 53 | pygame.draw.circle(view.win, (0xff,0xcd,0x3c), (int(ox + sr), int(oy + sr)), sr) 54 | if self.emptyv[x]: 55 | ox += self.fidx * self.scale 56 | ox += self.scale 57 | oy += self.scale 58 | self.fidx += 1 59 | if (self.fidx == 300): 60 | controller.animate = False 61 | view.font.render_to(view.win, (bx-160,by+16), "Current step: " + str(self.fidx), (255,255,255,255)) 62 | 63 | 64 | view = View(1920, 1200, 10, 16) 65 | view.setup("Day 11") 66 | controller = Controller() 67 | lines = open(controller.workdir() + "/day11.txt").read().split("\n") 68 | controller.add(Display(lines)) 69 | controller.run(view) 70 | -------------------------------------------------------------------------------- /vis/day15.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from common import View, Controller 3 | import math 4 | import pygame 5 | 6 | def hash(s): 7 | v = 0 8 | for c in s: 9 | v += ord(c) 10 | v *= 17 11 | v &= 255 12 | return v 13 | 14 | def fnv1a(s): 15 | hash= 2166136261 16 | for c in s: 17 | hash = ((hash ^ ord(c)) * 16777619) % 0xFFFFFFFF 18 | return hash 19 | 20 | def color(l, alpha = 255): 21 | h = fnv1a(l) 22 | return (h & 0xFF, (h>>8)&0xFF, (h>>16)&0xFF, alpha) 23 | 24 | 25 | gcx = 660 26 | gcy = 540 27 | 28 | class Segment: 29 | def __init__(self, ri, rmax, color, length): 30 | self.rad = (ri * math.pi * 2) / 256 31 | self.color = color 32 | self.lmax = length 33 | self.lc = 0 34 | self.rmax = rmax 35 | self.rc = 0 36 | 37 | def update(self, view, pmax): 38 | if self.lc < self.lmax: 39 | self.lc += 1 40 | if self.lc > self.lmax: 41 | self.lc //= 2 42 | if self.rc < self.rmax and self.rc < pmax: 43 | self.rc += 4 44 | elif self.rc > pmax: 45 | self.rc = pmax 46 | r1 = self.rc 47 | r2 = r1 - self.lc 48 | x1, y1 = gcx + r1 * math.cos(self.rad), gcy + r1 * math.sin(self.rad) 49 | x2, y2 = gcx + r2 * math.cos(self.rad), gcy + r2 * math.sin(self.rad) 50 | pygame.draw.line(view.win, self.color, (x1,y1), (x2,y2), 3) 51 | return self.rc - self.lc 52 | 53 | class Display: 54 | def __init__(self, tokens) -> None: 55 | self.tokens = tokens 56 | self.boxes = [] 57 | self.segments = [] 58 | self.segmap = {} 59 | self.strength = defaultdict(int) 60 | self.textbox = [] 61 | for _ in range(256): 62 | self.boxes.append([]) 63 | self.segments.append([]) 64 | self.fidx = 0 65 | self.tidx = 0 66 | self.speed = 10 67 | self.ridx = 300 68 | 69 | def update(self, view, controller): 70 | if not controller.animate: 71 | return 72 | view.win.fill((0,0,0,0)) 73 | for i in range(256): 74 | r1 = 500 75 | r2 = r1-4 76 | rad = (i * math.pi * 2) / 256 77 | x1, y1 = gcx + r1 * math.cos(rad), gcy + r1 * math.sin(rad) 78 | x2, y2 = gcx + r2 * math.cos(rad), gcy + r2 * math.sin(rad) 79 | pygame.draw.line(view.win, (240,240,240,240), (x1,y1), (x2,y2), 3) 80 | mark = [] 81 | for segment in self.segments[i]: 82 | r2 = segment.update(view, r2) 83 | # cleanup maybe ? 84 | if segment.lmax == 0 and segment.lc == 0: 85 | mark.append(segment) 86 | for marked in mark: 87 | self.segments[i].remove(marked) 88 | alpha = 10 89 | cx = gcx + 600 90 | cy = gcy + 120 91 | for i in range(len(self.textbox)-20,len(self.textbox)): 92 | if i >= 0: 93 | o,l,v = self.textbox[i] 94 | view.font.render_to(view.win, (cx, cy), o + " " + l + " " + str(v), color(l, alpha)) 95 | cy -= 16 96 | alpha += 12 97 | cy -= 16 98 | view.font.render_to(view.win, (cx, cy), str(self.tidx) + " / " + str(len(self.tokens)), (255,255,255,255)) 99 | if self.fidx < self.speed: 100 | self.fidx += 1 101 | return 102 | self.fidx = 0 103 | if self.tidx == len(self.tokens): 104 | if self.ridx == 0: 105 | controller.animate = False 106 | else: 107 | self.ridx -= 1 108 | return 109 | if self.tidx % 100 == 99 and self.speed > 1: 110 | self.speed -= 1 111 | tok = self.tokens[self.tidx] 112 | if tok[-1] == "-": 113 | l = tok[:-1] 114 | hc = hash(l) 115 | if l in self.boxes[hc]: 116 | self.boxes[hc].remove(l) 117 | self.segmap[l].lmax = 0 118 | self.textbox.append(("-",l, self.strength[l])) 119 | else: 120 | l = tok[:-2] 121 | hc = hash(l) 122 | st = int(tok[-1]) 123 | # checking via strength map is slower. 124 | if l not in self.boxes[hc]: 125 | self.boxes[hc].append(l) 126 | seg = Segment(hc, 500, color(l), 8*st+4) 127 | self.segments[hc].append(seg) 128 | self.segmap[l] = seg 129 | else: 130 | self.segmap[l].lmax = 8*st+4 131 | self.strength[l] = st 132 | self.textbox.append(("=",l, st)) 133 | self.tidx += 1 134 | 135 | 136 | 137 | view = View(1920, 1080, 60, 24) 138 | view.setup("Day 15") 139 | controller = Controller() 140 | lines = open(controller.workdir() + "/day15.txt").read().split(",") 141 | controller.add(Display(lines)) 142 | controller.run(view) 143 | -------------------------------------------------------------------------------- /vis/day16.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | import pygame 3 | 4 | class Display: 5 | def __init__(self, tiles) -> None: 6 | self.tiles = tiles 7 | self.hidx = 0 8 | self.dimx = len(tiles[0]) 9 | self.dimy = len(tiles) 10 | # first BFS 11 | self.history = self.bfs((-1,0,1,0)) 12 | self.order = [] 13 | for y in range(0,self.dimy): 14 | self.order.append((-1,y,1,0)) 15 | for x in range(0,self.dimx): 16 | self.order.append((x,self.dimy,0,-1)) 17 | for y in range(self.dimy,-1,-1): 18 | self.order.append((self.dimx,y,-1,0)) 19 | for x in range(self.dimx,-1,-1): 20 | self.order.append((x,-1,0,1)) 21 | self.oidx = 0 22 | 23 | def bfs(self, start): 24 | history = [] 25 | current = [ start ] 26 | visited = set() 27 | warm = set() 28 | while current: 29 | history.append(current) 30 | next = [] 31 | for (x,y,dx,dy) in current: 32 | # move out of the previous tile 33 | x += dx 34 | y += dy 35 | # out of bounds 36 | if x < 0 or y < 0 or x >= self.dimx or y >= self.dimy: 37 | continue 38 | # if we already _entered_ this tile this way, skip 39 | if (x,y,dx,dy) in visited: 40 | continue 41 | visited.add((x,y,dx,dy)) 42 | warm.add((x,y)) 43 | t = self.tiles[y][x] 44 | # empty or ignored splitter 45 | if t == '.' or (t == '|' and dx == 0) or (t =='-' and dy == 0): 46 | next.append((x,y,dx,dy)) 47 | # split vertically 48 | elif t == '|' and dx != 0: 49 | next.append((x,y,0,1)) 50 | next.append((x,y,0,-1)) 51 | elif t == '-' and dy != 0: 52 | next.append((x,y,1,0)) 53 | next.append((x,y,-1,0)) 54 | # mirror 1 55 | elif t == '/': 56 | next.append((x,y,-dy,-dx)) 57 | # mirror 2 58 | elif t == '\\': 59 | next.append((x,y,dy,dx)) 60 | current = next 61 | return history 62 | 63 | def center(self,x,y): 64 | return (self.sx + x * self.size + self.size // 2, 65 | self.sy + y * self.size + self.size // 2) 66 | 67 | def update(self, view: View, controller: Controller): 68 | if not controller.animate: 69 | return 70 | self.size = 10 71 | self.sx = (view.width - self.dimx * self.size) // 2 72 | self.sy = (view.height - self.dimy * self.size) // 2 73 | hsize = self.size // 2 74 | tcol = (120,120,120,255) 75 | if self.hidx == 0: 76 | view.win.fill((0,0,0,0)) 77 | pygame.draw.rect(view.win,tcol,(self.sx-4,self.sy-4,self.dimx*self.size+4,self.dimy*self.size+4),4) 78 | for y in range(self.dimy): 79 | for x in range(self.dimx): 80 | cx,cy = self.center(x,y) 81 | t = self.tiles[y][x] 82 | if t == '|': 83 | pygame.draw.line(view.win, tcol, (cx,cy-hsize), (cx,cy+hsize), 3) 84 | if t == '-': 85 | pygame.draw.line(view.win, tcol, (cx-hsize,cy), (cx+hsize,cy), 3) 86 | elif t == '/': 87 | pygame.draw.line(view.win, tcol, (cx-hsize,cy+hsize), (cx+hsize,cy-hsize), 4) 88 | elif t == '\\': 89 | pygame.draw.line(view.win, tcol, (cx-hsize,cy-hsize), (cx+hsize,cy+hsize), 4) 90 | lcol = (255,255,255,255) 91 | for _ in range(min(10,self.oidx + 1)): 92 | for (x,y,dx,dy) in self.history[self.hidx]: 93 | cx1, cy1 = self.center(x,y) 94 | cx2, cy2 = self.center(x+dx,y+dy) 95 | pygame.draw.line(view.win, lcol, (cx1, cy1), (cx2, cy2), 2) 96 | self.hidx += 1 97 | if self.hidx == len(self.history): 98 | if (self.oidx + 1 >= len(self.order)): 99 | controller.animate = False 100 | else: 101 | self.oidx += 1 102 | self.history = self.bfs(self.order[self.oidx]) 103 | self.hidx = 0 104 | return 105 | 106 | 107 | 108 | view = View(1920, 1200, 60, 24) 109 | view.setup("Day 16") 110 | controller = Controller() 111 | lines = open(controller.workdir() + "/day16.txt").read().splitlines() 112 | controller.add(Display(lines)) 113 | controller.run(view) 114 | -------------------------------------------------------------------------------- /vis/day18.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | from random import randint 3 | import pygame 4 | from pygame import gfxdraw 5 | 6 | class Display: 7 | def __init__(self, lines) -> None: 8 | self.segments = [] 9 | for line in lines: 10 | (d, n, c) = line.split() 11 | self.segments.append((d, int(n) * 3, (int(c[2:4], 16), int(c[4:6], 16), int(c[6:8], 16), 255))) 12 | self.lidx = 0 13 | self.pidx = 0 14 | self.speed = 1 15 | self.cx = 660 16 | self.cy = 740 17 | self.cc = 3 18 | self.ca = 0 19 | self.border = [] 20 | self.lines = pygame.Surface((1920, 1080), pygame.SRCALPHA) 21 | self.fills = pygame.Surface((1920, 1080)) 22 | self.fills.fill((128, 128, 200)) 23 | self.phase2 = False 24 | 25 | def blit(self, view: View): 26 | view.win.blit(self.fills, (0, 0)) 27 | view.win.blit(self.lines, (0, 0)) 28 | view.font.render_to(view.win, (40, 40), "Border diameter: " + str(self.cc//3), (0,0,0,255)) 29 | view.font.render_to(view.win, (40, 70), "Integral area (absolute): " + str(abs(self.ca//9)), (0,0,0,255)) 30 | view.font.render_to(view.win, (40, 100), "Total area w/border: " + str(abs(self.ca//9) + self.cc//6 + 1) , (0,0,0,255)) 31 | 32 | def update(self, view: View, controller: Controller): 33 | if not controller.animate: 34 | return 35 | if self.phase2: 36 | if not self.border: 37 | controller.animate = False 38 | next = [] 39 | for (x,y) in self.border: 40 | if randint(1,10) < 2: 41 | col = self.fills.get_at((x, y)) 42 | for (dx,dy) in [(-1,0),(1,0),(0,1),(0,-1)]: 43 | (r,g,b,a) = self.fills.get_at((x+dx, y+dy)) 44 | if r == 191 and g == 191 and b == 200: 45 | gfxdraw.pixel(self.fills,x+dx,y+dy,col) 46 | next.append((x+dx,y+dy)) 47 | else: 48 | next.append((x,y)) 49 | self.border = next 50 | self.blit(view) 51 | return 52 | view.win.fill((0, 0, 0, 255)) 53 | (dir, num, col) = self.segments[self.lidx] 54 | m = min(self.speed, num - self.pidx) 55 | self.pidx += m 56 | self.cc += m 57 | if dir == "R": 58 | self.fills.fill((63,63,0),(self.cx,self.cy,m,1080-self.cy),special_flags=pygame.BLEND_ADD) 59 | pygame.draw.line(self.lines, col, (self.cx, self.cy), (self.cx + m, self.cy), 3) 60 | for i in range(m): 61 | self.border.append((self.cx+i,self.cy)) 62 | self.cx += m 63 | self.ca += m * self.cy 64 | elif dir == "L": 65 | self.fills.fill((63,63,0),(self.cx-m,self.cy,m,1080-self.cy),special_flags=pygame.BLEND_SUB) 66 | pygame.draw.line(self.lines, col, (self.cx, self.cy), (self.cx - m, self.cy), 3) 67 | for i in range(m): 68 | self.border.append((self.cx-i,self.cy)) 69 | self.cx -= m 70 | self.ca -= m * self.cy 71 | elif dir == "U": 72 | pygame.draw.line(self.lines, col, (self.cx, self.cy), (self.cx, self.cy - m), 3) 73 | for i in range(m): 74 | self.border.append((self.cx-1,self.cy)) 75 | self.cy -= m 76 | elif dir == "D": 77 | self.cy += m 78 | pygame.draw.line(self.lines, col, (self.cx, self.cy), (self.cx, self.cy + m), 3) 79 | for i in range(m): 80 | self.border.append((self.cx-1,self.cy)) 81 | if self.pidx == num: 82 | self.lidx += 1 83 | self.pidx = 0 84 | if self.speed < 10: 85 | self.speed += 1 86 | if self.lidx == len(self.segments): 87 | self.phase2 = True 88 | for (x,y) in self.border: 89 | pix = self.lines.get_at((x,y)) 90 | gfxdraw.pixel(self.fills,x,y,pix) 91 | # controller.animate = False 92 | self.blit(view) 93 | 94 | 95 | 96 | 97 | view = View(1920, 1080, 60, 24) 98 | view.setup("Day 18") 99 | controller = Controller() 100 | lines = open(controller.workdir() + "/day18.txt").read().splitlines() 101 | controller.add(Display(lines)) 102 | controller.run(view) 103 | -------------------------------------------------------------------------------- /vis/day21.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | from random import randint 3 | import pygame 4 | 5 | 6 | class Display: 7 | def __init__(self, lines) -> None: 8 | self.lines = lines 9 | for y in range(len(lines)): 10 | x = lines[y].find('S') 11 | if x > 0: 12 | self.start = (x,y) 13 | break 14 | self.current = [self.start] 15 | self.rocks = {} 16 | self.visited = {self.start: 0} 17 | self.fidx = 0 18 | self.speed = 1 19 | self.scale = 8 20 | 21 | def step(self, expand = False): 22 | if not self.current: 23 | return 24 | dimx = len(self.lines[0]) 25 | dimy = len(self.lines) 26 | dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)] 27 | next = [] 28 | for pos in self.current: 29 | distance = self.visited[pos] 30 | (x, y) = pos 31 | for ndir in range(4): 32 | (dx, dy) = dirs[ndir] 33 | if expand: 34 | (nx, ny) = ((x + dx) % dimx, (y + dy) % dimy) 35 | else: 36 | if x + dx < 0 or x + dx >= dimx or y + dy < 0 or y + dy >= dimy: 37 | continue 38 | (nx, ny) = (x + dx, y + dy) 39 | if (x + dx, y + dy) in self.visited: 40 | continue 41 | if self.lines[ny][nx] == "#": 42 | self.rocks[(x + dx, y + dy)] = distance + 1 43 | continue 44 | self.visited[(x + dx, y + dy)] = distance + 1 45 | next.append((x + dx, y + dy)) 46 | self.current = next 47 | 48 | def update(self, view, controller): 49 | if not controller.animate: 50 | return 51 | view.win.fill((0, 0, 0, 0)) 52 | by = (1200 - len(self.lines) * self.scale)//2 53 | bx = (1920 - len(self.lines) * self.scale)//2 54 | sz = self.scale 55 | for y in range(len(self.lines)): 56 | for x in range(len(self.lines[y])): 57 | (ox,oy) = (bx + x * self.scale, by + y * self.scale) 58 | if self.lines[y][x] == '#': 59 | pygame.draw.rect(view.win, (100,60,60), (ox + 1, oy + 1, sz - 2, sz - 2)) 60 | for (x,y) in self.visited: 61 | d = self.visited[(x,y)] 62 | if d % 2: 63 | col = (80,100,80) 64 | else: 65 | col = (80,80,100) 66 | (ox,oy) = (bx + x * self.scale, by + y * self.scale) 67 | pygame.draw.rect(view.win, col, (ox, oy, sz, sz)) 68 | for (x,y) in self.rocks: 69 | (ox,oy) = (bx + x * self.scale, by + y * self.scale) 70 | pygame.draw.rect(view.win, (200,100,100), (ox+1, oy+1, sz-2, sz-2)) 71 | if self.fidx < 196: 72 | self.step(True) 73 | self.fidx += 1 74 | else: 75 | controller.animate = False 76 | if (self.fidx * 2 * self.scale) > 1200 : 77 | self.scale -= 1 78 | view.font.render_to(view.win, (by,by), "Current step: " + str(self.fidx), (255,255,255,255)) 79 | 80 | 81 | view = View(1920, 1200, 10, 16) 82 | view.setup("Day 21") 83 | controller = Controller() 84 | lines = open(controller.workdir() + "/day21.txt").read().split("\n") 85 | controller.add(Display(lines)) 86 | controller.run(view) 87 | -------------------------------------------------------------------------------- /vis/day24.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | import pygame 3 | from pygame import gfxdraw 4 | import math 5 | import numpy as np 6 | 7 | 8 | def parse(lines): 9 | stones = [] 10 | for line in lines: 11 | line = line.replace(" @ ", ", ") 12 | nums = list(map(int, line.split(", "))) 13 | stones.append(nums) 14 | return stones 15 | 16 | def project2d(x, y, z, phi = 0): 17 | x1 = x * math.cos(phi) + y * math.sin(phi) 18 | y1 = -x * math.sin(phi) + y * math.cos(phi) 19 | xp = (x1 + y1) / math.sqrt(2) 20 | yp = (x1 - 2 * z - y1) / math.sqrt(6) 21 | return (int(xp), int(yp)) 22 | 23 | 24 | def collider(stones): 25 | px = [ stones[0][0], stones[1][0], stones[2][0] ] 26 | py = [ stones[0][1], stones[1][1], stones[2][1] ] 27 | pz = [ stones[0][2], stones[1][2], stones[2][2] ] 28 | vx = [ stones[0][3], stones[1][3], stones[2][3] ] 29 | vy = [ stones[0][4], stones[1][4], stones[2][4] ] 30 | vz = [ stones[0][5], stones[1][5], stones[2][5] ] 31 | A = np.array( 32 | [ 33 | [vy[1] - vy[0], vx[0] - vx[1], 0, py[0] - py[1], px[1] - px[0], 0], 34 | [vy[2] - vy[0], vx[0] - vx[2], 0, py[0] - py[2], px[2] - px[0], 0], 35 | [vz[1] - vz[0], 0, vx[0] - vx[1], pz[0] - pz[1], 0, px[1] - px[0]], 36 | [vz[2] - vz[0], 0, vx[0] - vx[2], pz[0] - pz[2], 0, px[2] - px[0]], 37 | [0, vz[1] - vz[0], vy[0] - vy[1], 0, pz[0] - pz[1], py[1] - py[0]], 38 | [0, vz[2] - vz[0], vy[0] - vy[2], 0, pz[0] - pz[2], py[2] - py[0]], 39 | ] 40 | ) 41 | 42 | x = [ 43 | (py[0] * vx[0] - py[1] * vx[1]) - (px[0] * vy[0] - px[1] * vy[1]), 44 | (py[0] * vx[0] - py[2] * vx[2]) - (px[0] * vy[0] - px[2] * vy[2]), 45 | (pz[0] * vx[0] - pz[1] * vx[1]) - (px[0] * vz[0] - px[1] * vz[1]), 46 | (pz[0] * vx[0] - pz[2] * vx[2]) - (px[0] * vz[0] - px[2] * vz[2]), 47 | (pz[0] * vy[0] - pz[1] * vy[1]) - (py[0] * vz[0] - py[1] * vz[1]), 48 | (pz[0] * vy[0] - pz[2] * vy[2]) - (py[0] * vz[0] - py[2] * vz[2]), 49 | ] 50 | return np.linalg.solve(A, x) 51 | 52 | class Background: 53 | tidx = 0 54 | fidx = 0 55 | minx = None 56 | miny = None 57 | 58 | def __init__(self, stones) -> None: 59 | self.stones = stones 60 | self.speed = 1000000000 61 | self.coll = collider(stones) 62 | 63 | def shift(self, p): 64 | (x, y) = p 65 | return (x - self.minx + 500, y - self.miny + 200) 66 | 67 | def update(self, view, controller): 68 | if not controller.animate: 69 | return 70 | phi = math.pi * self.fidx / 750 71 | view.win.fill((0, 0, 0, 0)) 72 | pixels = [] 73 | for stone in self.stones: 74 | sx, sy, sz = [(stone[i] + (stone[i+3] * self.tidx))/434197082915 - 500 for i in range(3)] 75 | pixels.append(project2d(sx, sy, sz, phi)) 76 | if not self.minx: 77 | self.minx = min([x for (x,_) in pixels]) 78 | self.miny = min([y for (_,y) in pixels]) 79 | for p in pixels: 80 | (x,y) = self.shift(p) 81 | gfxdraw.pixel(view.win, x, y, (255,255,255)) 82 | cx, cy, cz = [(self.coll[i])/434197082915 - 500 for i in range(3)] 83 | start = self.shift(project2d(cx,cy,cz, phi)) 84 | cx, cy, cz = [(self.coll[i] + (self.coll[i+3] * self.tidx))/434197082915 - 500 for i in range(3)] 85 | end = self.shift(project2d(cx,cy,cz, phi)) 86 | pygame.draw.line(view.win, (0xff,0xcd,0x3c), start, end) 87 | (ex, ey) = end 88 | pygame.draw.circle(view.win, (0xff,0xcd,0x3c), (ex+1,ey+1), 2) 89 | self.tidx += self.speed 90 | self.fidx += 1 91 | if self.fidx == 1800: 92 | controller.animate = False 93 | view.font.render_to(view.win, (20, 20), "T: " + str(self.tidx), (255, 255, 255)) 94 | 95 | view = View(1920, 1200, 60, 24) 96 | view.setup("Day 24") 97 | controller = Controller() 98 | lines = open("day24.txt").read().splitlines() 99 | controller.add(Background(parse(lines))) 100 | controller.run(view) 101 | -------------------------------------------------------------------------------- /vis/day25.py: -------------------------------------------------------------------------------- 1 | from common import View, Controller 2 | import pygame 3 | from collections import defaultdict 4 | 5 | 6 | def parse(lines): 7 | graph = defaultdict(set) 8 | for line in lines: 9 | src = line[:3] 10 | for dst in line[5:].split(" "): 11 | graph[src].add(dst) 12 | graph[dst].add(src) 13 | return graph 14 | 15 | 16 | def bfs(graph, src): 17 | prev = {src: None} 18 | hist = [] 19 | stak = [src] 20 | while stak: 21 | next = [] 22 | for cur in stak: 23 | for dst in graph[cur]: 24 | if dst not in prev: 25 | prev[dst] = cur 26 | next.append(dst) 27 | # next.sort() 28 | hist.append(stak) 29 | stak = next 30 | 31 | return (hist, prev) 32 | 33 | 34 | def edmond_karp(graph, cnt=4): 35 | mconn = -1 36 | for node in graph: 37 | if len(graph[node]) > mconn: 38 | src = node 39 | frames = [] 40 | pos = {} 41 | tgt = None 42 | for i in range(cnt): 43 | (history, prev) = bfs(graph, src) 44 | # print(history) 45 | if not tgt: 46 | tgt = history[-1][10] 47 | for lvl, nodes in enumerate(history): 48 | cx = 0 49 | for node in nodes: 50 | if node not in pos: 51 | pos[node] = (cx, lvl + 1) 52 | cx += 1 53 | frames.append((node, i + 1, prev[node])) 54 | pre = tgt 55 | while pre in prev and prev[pre]: 56 | frames.append((pre, -1, prev[pre])) 57 | cur = prev[pre] 58 | graph[cur].remove(pre) 59 | pre = cur 60 | return pos, frames 61 | 62 | 63 | class Display: 64 | fidx = 0 65 | speed = 2 66 | reachable = 0 67 | 68 | def __init__(self, graph) -> None: 69 | self.pos, self.frames = edmond_karp(graph) 70 | self.total = len(graph) 71 | 72 | def update(self, view, controller): 73 | if not controller.animate: 74 | return 75 | dy = 80 76 | for _ in range(self.speed): 77 | cf, cv, cp = self.frames[self.fidx] 78 | cx, cy = self.pos[cf] 79 | if cv > 0: 80 | cc = (80 + cv * 42, 80 + cv * 42, 80 + cv * 42) 81 | pygame.draw.rect(view.win, (0, 0, 0), (cx * 7 + 1, cy * dy + 1, 5, 46)) 82 | view.font.render_to(view.win, (cx * 7, cy * dy + 14), cf[0], cc) 83 | view.font.render_to(view.win, (cx * 7, cy * dy + 28), cf[1], cc) 84 | view.font.render_to(view.win, (cx * 7, cy * dy + 42), cf[2], cc) 85 | self.reachable += 1 86 | else: 87 | pygame.draw.rect(view.win, (255, 255, 255), (cx * 7, cy * dy, 7, 44), 1) 88 | self.reachable = 0 89 | if cv == 1 and cp: 90 | px, py = self.pos[cp] 91 | pygame.draw.line(view.win, cc, (cx * 7 + 3, cy * dy), (px * 7 + 3, py * dy + 44)) 92 | if cv == -1 and cp: 93 | px, py = self.pos[cp] 94 | pygame.draw.line(view.win, (0,0,0,0), (cx * 7 + 3, cy * dy), (px * 7 + 3, py * dy + 44),2) 95 | self.fidx += 1 96 | if self.fidx >= len(self.frames): 97 | controller.animate = False 98 | break 99 | pygame.draw.rect(view.win, (0, 0, 0), (0, 0, 200, 30)) 100 | view.font.render_to(view.win, (10, 20), "Reachable: " + str(self.reachable), (255, 255, 255)) 101 | 102 | 103 | view = View(1920, 1200, 60, 12) 104 | view.setup("Day 25") 105 | controller = Controller() 106 | lines = open("day25.txt").read().splitlines() 107 | controller.add(Display(parse(lines))) 108 | controller.run(view) 109 | -------------------------------------------------------------------------------- /wrappers.mojo: -------------------------------------------------------------------------------- 1 | from algorithm import parallelize 2 | import time 3 | import benchmark 4 | 5 | fn minibench[fun: fn () capturing -> Int64](label: StringLiteral, loops: Int = 100): 6 | units = VariadicList[StringLiteral]("ns", "μs", "ms", "s") 7 | var start = time.now() 8 | var end = start 9 | var sloop = loops // 10 10 | var t : Int64 = 0 11 | while sloop <= 100000000 and end - start < 1000000000: 12 | sloop *= 10 13 | start = time.now() 14 | t = 0 15 | for _ in range(sloop): 16 | t += fun() 17 | end = time.now() 18 | 19 | avg = (end - start) / sloop 20 | var div = 1 21 | var pos = 0 22 | 23 | while avg / div >= 10: 24 | div *= 1000 25 | pos += 1 26 | 27 | unit = units[pos] 28 | print(fun()) 29 | print(label, ":", avg / div, unit, "(", sloop, "loops ) avg", t // sloop,"mod",t % sloop) 30 | 31 | fn run_multiline_task[f1: fn (Int, /) capturing -> None, f2: fn (Int, /) capturing -> None, disp: fn () capturing -> None] 32 | (len: Int, workers: Int = 12): 33 | @parameter 34 | fn part1() -> Int64: 35 | for l in range(len): 36 | f1(l) 37 | return 1 38 | 39 | @parameter 40 | fn part1_parallel() -> Int64: 41 | parallelize[f1](len, workers) 42 | return workers 43 | 44 | @parameter 45 | fn part2() -> Int64: 46 | for l in range(len): 47 | f2(l) 48 | return 1 49 | 50 | @parameter 51 | fn part2_parallel() -> Int64: 52 | parallelize[f2](len, workers) 53 | return workers 54 | 55 | _ = part1() 56 | _ = part2() 57 | disp() 58 | print("using",workers,"parallel threads") 59 | minibench[part1]("part1") 60 | minibench[part1_parallel]("part1 parallel") 61 | minibench[part2]("part2") 62 | minibench[part2_parallel]("part2 parallel") 63 | 64 | # test the benchmark accuracy (or, well, sleep acuracy, since you can't sleep for <~100 usec reliably) 65 | fn main(): 66 | @parameter 67 | fn sleep20usec() -> Int64: 68 | time.sleep(0.00002) 69 | return 1 70 | 71 | @parameter 72 | fn sleep100usec() -> Int64: 73 | time.sleep(0.0001) 74 | return 1 75 | 76 | @parameter 77 | fn sleep500usec() -> Int64: 78 | time.sleep(0.0005) 79 | return 1 80 | 81 | @parameter 82 | fn sleep10ms() -> Int64: 83 | time.sleep(0.01) 84 | return 1 85 | 86 | @parameter 87 | fn sleep50ms() -> Int64: 88 | time.sleep(0.05) 89 | return 1 90 | 91 | minibench[sleep20usec]("sleep20usec") 92 | minibench[sleep100usec]("sleep100usec") 93 | minibench[sleep500usec]("sleep500usec") 94 | minibench[sleep10ms]("sleep10ms") 95 | minibench[sleep50ms]("sleep50ms") 96 | --------------------------------------------------------------------------------