├── input └── .gitkeep ├── nim.cfg ├── .gitignore ├── 02.possum ├── 03.possum ├── 01.possum ├── test ├── 10.txt ├── 19.txt ├── 06.txt ├── 07.txt ├── 12.txt ├── 08.txt ├── 14.txt ├── 13.txt ├── 05.txt ├── 25.txt ├── 13.json ├── 05.json ├── 07.json ├── 14.json └── t15.nim ├── 07.possum ├── 14.possum ├── 03_2.possum ├── 05.possum ├── bug1.possum ├── 13.possum ├── 02.md ├── 04.md ├── day19.nim ├── day03.nim ├── day01.nim ├── day13.nim ├── 03.md ├── day02.nim ├── day25.nim ├── LICENSE ├── README.md ├── notes.md ├── day08.nim ├── 01.md ├── day04.nim ├── day11.nim ├── day14.nim ├── day10.nim ├── day22.nim ├── day12.nim ├── day06.nim ├── day07.nim ├── day09.nim ├── day05.nim ├── day09no.nim ├── day17.nim ├── day16.nim └── day15.nim /input/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nim.cfg: -------------------------------------------------------------------------------- 1 | --verbosity:0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | input/* 2 | !.gitkeep 3 | possum -------------------------------------------------------------------------------- /02.possum: -------------------------------------------------------------------------------- 1 | report = array_sep(int, " ") 2 | array_sep(report, nl) -------------------------------------------------------------------------------- /03.possum: -------------------------------------------------------------------------------- 1 | op = "mul(" > tuple2_sep(int, ",", int) < ")" 2 | array(find(op)) -------------------------------------------------------------------------------- /01.possum: -------------------------------------------------------------------------------- 1 | pair = record2_sep($"left", int, " ", $"right", int) 2 | array_sep(pair, nl) -------------------------------------------------------------------------------- /test/10.txt: -------------------------------------------------------------------------------- 1 | 89010123 2 | 78121874 3 | 87430965 4 | 96549874 5 | 45678903 6 | 32019012 7 | 01329801 8 | 10456732 -------------------------------------------------------------------------------- /test/19.txt: -------------------------------------------------------------------------------- 1 | r, wr, b, g, bwu, rb, gb, br 2 | 3 | brwrr 4 | bggr 5 | gbbr 6 | rrbgbr 7 | ubwu 8 | bwurrg 9 | brgr 10 | bbrgwb -------------------------------------------------------------------------------- /07.possum: -------------------------------------------------------------------------------- 1 | array_sep(equation, nl) 2 | equation = record2_sep($"test", int, ": ", $"numbers", numbers) 3 | numbers = array_sep(int, " ") -------------------------------------------------------------------------------- /test/06.txt: -------------------------------------------------------------------------------- 1 | ....#..... 2 | .........# 3 | .......... 4 | ..#....... 5 | .......#.. 6 | .......... 7 | .#..^..... 8 | ........#. 9 | #......... 10 | ......#... -------------------------------------------------------------------------------- /test/07.txt: -------------------------------------------------------------------------------- 1 | 190: 10 19 2 | 3267: 81 40 27 3 | 83: 17 5 4 | 156: 15 6 5 | 7290: 6 8 6 15 6 | 161011: 16 10 13 7 | 192: 17 8 14 8 | 21037: 9 7 18 13 9 | 292: 11 6 16 20 -------------------------------------------------------------------------------- /test/12.txt: -------------------------------------------------------------------------------- 1 | RRRRIICCFF 2 | RRRRIICCCF 3 | VVRRRCCFFF 4 | VVRCCCJFFF 5 | VVVVCJJCFE 6 | VVIVCCJJEE 7 | VVIIICJJEE 8 | MIIIIIJJEE 9 | MIIISIJEEE 10 | MMMISSJEEE -------------------------------------------------------------------------------- /14.possum: -------------------------------------------------------------------------------- 1 | array_sep(robot, nl) 2 | robot = record2_sep($"pos", pos, " ", $"vel", vel) 3 | vel = "v=" > xy 4 | pos = "p=" > xy 5 | xy = record2_sep($"x", int, ",", $"y", int) -------------------------------------------------------------------------------- /03_2.possum: -------------------------------------------------------------------------------- 1 | mul = "mul(" > tuple2_sep(int, ",", int) -> Args < ")" $ {"op": "mul", "args": Args} 2 | do = "do()" $ {"op": "do", "args": []} 3 | dont = "don't()" $ {"op": "dont", "args": []} 4 | array(find(mul | do | dont)) -------------------------------------------------------------------------------- /05.possum: -------------------------------------------------------------------------------- 1 | input 2 | rule = tuple2_sep(int, "|", int) 3 | rules = array_sep(rule, nl) 4 | update = array_sep(int, ",") 5 | updates = array_sep(update, nl) 6 | input = record2_sep($"rules", rules, nl + nl, $"updates", updates) -------------------------------------------------------------------------------- /test/08.txt: -------------------------------------------------------------------------------- 1 | ............ 2 | ........0... 3 | .....0...... 4 | .......0.... 5 | ....0....... 6 | ......A..... 7 | ............ 8 | ............ 9 | ........A... 10 | .........A.. 11 | ............ 12 | ............ -------------------------------------------------------------------------------- /bug1.possum: -------------------------------------------------------------------------------- 1 | input 2 | rule = tuple2_sep(int, "|", int) 3 | rules = array_sep(rule, nl) 4 | update = array_sep(int, ",") 5 | updates = array_sep(update, nl) 6 | input = record2_sep($"rules", rules, nl + nl, $"updates": updates) -------------------------------------------------------------------------------- /test/14.txt: -------------------------------------------------------------------------------- 1 | p=0,4 v=3,-3 2 | p=6,3 v=-1,-3 3 | p=10,3 v=-1,2 4 | p=2,0 v=2,-1 5 | p=0,0 v=1,3 6 | p=3,0 v=-2,-2 7 | p=7,6 v=-1,-3 8 | p=3,0 v=-1,-2 9 | p=9,3 v=2,3 10 | p=7,3 v=-1,2 11 | p=2,4 v=2,-3 12 | p=9,5 v=-3,-3 -------------------------------------------------------------------------------- /test/13.txt: -------------------------------------------------------------------------------- 1 | Button A: X+94, Y+34 2 | Button B: X+22, Y+67 3 | Prize: X=8400, Y=5400 4 | 5 | Button A: X+26, Y+66 6 | Button B: X+67, Y+21 7 | Prize: X=12748, Y=12176 8 | 9 | Button A: X+17, Y+86 10 | Button B: X+84, Y+37 11 | Prize: X=7870, Y=6450 12 | 13 | Button A: X+69, Y+23 14 | Button B: X+27, Y+71 15 | Prize: X=18641, Y=10279 -------------------------------------------------------------------------------- /test/05.txt: -------------------------------------------------------------------------------- 1 | 47|53 2 | 97|13 3 | 97|61 4 | 97|47 5 | 75|29 6 | 61|13 7 | 75|53 8 | 29|13 9 | 97|29 10 | 53|29 11 | 61|53 12 | 97|53 13 | 61|29 14 | 47|13 15 | 75|47 16 | 97|75 17 | 47|61 18 | 75|61 19 | 47|29 20 | 75|13 21 | 53|13 22 | 23 | 75,47,61,53,29 24 | 97,61,53,29,13 25 | 75,29,13 26 | 75,97,47,61,53 27 | 61,13,29 28 | 97,13,75,29,47 -------------------------------------------------------------------------------- /13.possum: -------------------------------------------------------------------------------- 1 | array_sep(machine, nl + nl) 2 | machine = buttonA -> A & nl & buttonB -> B & nl & prize -> P $ {"buttA": A, "buttB": B, "prize": P} 3 | button = ": X+" & int -> Dx & ", Y+" & int -> Dy $ {"x": Dx, "y": Dy} 4 | buttonA = "Button A" & button 5 | buttonB = "Button B" & button 6 | prize = "Prize: X=" & int -> X & ", Y=" & int -> Y $ {"x": X, "y": Y} -------------------------------------------------------------------------------- /test/25.txt: -------------------------------------------------------------------------------- 1 | ##### 2 | .#### 3 | .#### 4 | .#### 5 | .#.#. 6 | .#... 7 | ..... 8 | 9 | ##### 10 | ##.## 11 | .#.## 12 | ...## 13 | ...#. 14 | ...#. 15 | ..... 16 | 17 | ..... 18 | #.... 19 | #.... 20 | #...# 21 | #.#.# 22 | #.### 23 | ##### 24 | 25 | ..... 26 | ..... 27 | #.#.. 28 | ###.. 29 | ###.# 30 | ###.# 31 | ##### 32 | 33 | ..... 34 | ..... 35 | ..... 36 | #.... 37 | #.#.. 38 | #.#.# 39 | ##### -------------------------------------------------------------------------------- /test/13.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "buttA": {"x": 94, "y": 34}, 4 | "buttB": {"x": 22, "y": 67}, 5 | "prize": {"x": 8400, "y": 5400} 6 | }, 7 | { 8 | "buttA": {"x": 26, "y": 66}, 9 | "buttB": {"x": 67, "y": 21}, 10 | "prize": {"x": 12748, "y": 12176} 11 | }, 12 | { 13 | "buttA": {"x": 17, "y": 86}, 14 | "buttB": {"x": 84, "y": 37}, 15 | "prize": {"x": 7870, "y": 6450} 16 | }, 17 | { 18 | "buttA": {"x": 69, "y": 23}, 19 | "buttB": {"x": 27, "y": 71}, 20 | "prize": {"x": 18641, "y": 10279} 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /02.md: -------------------------------------------------------------------------------- 1 | ## feasibility thoughts 2 | 3 | - safe reports can be intuitevly understood as feasible solutions 4 | - the decision variables can be taken as a sequence of integers 5 | - the constraints can be expressed in terms of deltas of the report: 6 | - allIn(deltas, [1, 2, 3]) or allIn(deltas, [-1, -2, -3]) 7 | - but of course the allIn constraint is one I just made up and it will totally depend on the expressivity of the constraint solver 8 | - furthermore the damped problem is also more complex (and either requires more expressivity or more complex decision variables) 9 | -------------------------------------------------------------------------------- /04.md: -------------------------------------------------------------------------------- 1 | ## random thoughts 2 | 3 | ok the feasibility thoughts thing is probably an occasional thing by now 4 | 5 | - after a parsing day, a grid day 6 | - in this case no parsing with possum 7 | - in Nim I always do my grids by hand 8 | - and I always end up doing them differently than last time 9 | - this time I tried first using option from stdlib 10 | - but they are awkward to use so I reverted to having a placeholder null character 11 | - got tricked by the CrossMas thing, but recovered rather quickly 12 | - this day is the first day I felt the need to use the test example -------------------------------------------------------------------------------- /day19.nim: -------------------------------------------------------------------------------- 1 | import re, strutils 2 | 3 | type 4 | Input = object 5 | patterns: seq[string] 6 | designs: seq[string] 7 | 8 | func parse(text: string): Input = 9 | let split2 = text.split("\n\n") 10 | result.patterns = split2[0].split(", ") 11 | result.designs = split2[1].splitLines 12 | 13 | func part1(inp: Input): int = 14 | let pattern = re("^(" & inp.patterns.join("|") & ")+$") 15 | for design in inp.designs: 16 | if design.match(pattern): 17 | inc result 18 | 19 | echo "test/19.txt".readFile.parse.part1 20 | echo "input/19.txt".readFile.parse.part1 21 | -------------------------------------------------------------------------------- /day03.nim: -------------------------------------------------------------------------------- 1 | import jsony 2 | 3 | let input = "input/03.json".readFile.fromJson(seq[(int, int)]) 4 | 5 | var part1 = 0 6 | for op in input: 7 | part1 += op[0]*op[1] 8 | echo part1 9 | 10 | type 11 | Op = object 12 | op: string 13 | args: seq[int] 14 | 15 | let input2 = "input/03_2.json".readFile.fromJson(seq[Op]) 16 | 17 | var part2 = 0 18 | var enable = true 19 | for op in input2: 20 | if op.op == "do": 21 | enable = true 22 | elif op.op == "dont": 23 | enable = false 24 | elif op.op == "mul": 25 | if enable: 26 | part2 += op.args[0]*op.args[1] 27 | echo part2 28 | -------------------------------------------------------------------------------- /test/05.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | [47, 53], 4 | [97, 13], 5 | [97, 61], 6 | [97, 47], 7 | [75, 29], 8 | [61, 13], 9 | [75, 53], 10 | [29, 13], 11 | [97, 29], 12 | [53, 29], 13 | [61, 53], 14 | [97, 53], 15 | [61, 29], 16 | [47, 13], 17 | [75, 47], 18 | [97, 75], 19 | [47, 61], 20 | [75, 61], 21 | [47, 29], 22 | [75, 13], 23 | [53, 13] 24 | ], 25 | "updates": [ 26 | [75, 47, 61, 53, 29], 27 | [97, 61, 53, 29, 13], 28 | [75, 29, 13], 29 | [75, 97, 47, 61, 53], 30 | [61, 13, 29], 31 | [97, 13, 75, 29, 47] 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /test/07.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "test": 190, 4 | "numbers": [10, 19] 5 | }, 6 | { 7 | "test": 3267, 8 | "numbers": [81, 40, 27] 9 | }, 10 | { 11 | "test": 83, 12 | "numbers": [17, 5] 13 | }, 14 | { 15 | "test": 156, 16 | "numbers": [15, 6] 17 | }, 18 | { 19 | "test": 7290, 20 | "numbers": [6, 8, 6, 15] 21 | }, 22 | { 23 | "test": 161011, 24 | "numbers": [16, 10, 13] 25 | }, 26 | { 27 | "test": 192, 28 | "numbers": [17, 8, 14] 29 | }, 30 | { 31 | "test": 21037, 32 | "numbers": [9, 7, 18, 13] 33 | }, 34 | { 35 | "test": 292, 36 | "numbers": [11, 6, 16, 20] 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /day01.nim: -------------------------------------------------------------------------------- 1 | import std / [algorithm, sugar, tables] 2 | import jsony 3 | 4 | ## parsing with possum 5 | # possum 01.possum input/01.txt > input/01.json 6 | type 7 | Pair = object 8 | left, right: int 9 | 10 | ## reading into nim and transforming into two sequences 11 | let input = "input/01.json".readFile.fromJson(seq[Pair]) 12 | 13 | var left, right: seq[int] 14 | for pair in input: 15 | left.add pair.left 16 | right.add pair.right 17 | 18 | ## sort the sequences 19 | sort left 20 | sort right 21 | 22 | ## solve part1 23 | var part1 = 0 24 | for i in left.low .. left.high: 25 | part1 += abs(left[i] - right[i]) 26 | 27 | dump part1 28 | 29 | ## part2 is a twist 30 | let counts = right.toCountTable 31 | var part2 = 0 32 | for n in left: 33 | part2 += n*counts[n] 34 | 35 | dump part2 36 | 37 | 38 | -------------------------------------------------------------------------------- /day13.nim: -------------------------------------------------------------------------------- 1 | import std / sequtils 2 | import jsony 3 | 4 | type 5 | Vec = tuple[x, y: int] 6 | Machine = object 7 | buttA, buttB, prize: Vec 8 | 9 | let tmachines = "test/13.json".readFile.fromJson(seq[Machine]) 10 | let machines = "input/13.json".readFile.fromJson(seq[Machine]) 11 | 12 | func hit(m: Machine, a, b: int): bool = 13 | (m.buttA.x*a + m.buttB.x*b == m.prize.x) and (m.buttA.y*a + m.buttB.y*b == m.prize.y) 14 | 15 | func solve(m: Machine): seq[Vec] = 16 | for b in 0 .. 100: 17 | for a in 0 .. 100: 18 | if m.hit(a, b): 19 | result.add (a, b) 20 | 21 | func part1(machines: seq[Machine]): int = 22 | for machine in machines: 23 | let sol = solve machine 24 | if len(sol) > 0: 25 | result.inc min(sol.mapIt(3*it.x + it.y)) 26 | 27 | echo tmachines.part1 28 | echo machines.part1 -------------------------------------------------------------------------------- /test/14.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pos": {"x": 0, "y": 4}, 4 | "vel": {"x": 3, "y": -3} 5 | }, 6 | { 7 | "pos": {"x": 6, "y": 3}, 8 | "vel": {"x": -1, "y": -3} 9 | }, 10 | { 11 | "pos": {"x": 10, "y": 3}, 12 | "vel": {"x": -1, "y": 2} 13 | }, 14 | { 15 | "pos": {"x": 2, "y": 0}, 16 | "vel": {"x": 2, "y": -1} 17 | }, 18 | { 19 | "pos": {"x": 0, "y": 0}, 20 | "vel": {"x": 1, "y": 3} 21 | }, 22 | { 23 | "pos": {"x": 3, "y": 0}, 24 | "vel": {"x": -2, "y": -2} 25 | }, 26 | { 27 | "pos": {"x": 7, "y": 6}, 28 | "vel": {"x": -1, "y": -3} 29 | }, 30 | { 31 | "pos": {"x": 3, "y": 0}, 32 | "vel": {"x": -1, "y": -2} 33 | }, 34 | { 35 | "pos": {"x": 9, "y": 3}, 36 | "vel": {"x": 2, "y": 3} 37 | }, 38 | { 39 | "pos": {"x": 7, "y": 3}, 40 | "vel": {"x": -1, "y": 2} 41 | }, 42 | { 43 | "pos": {"x": 2, "y": 4}, 44 | "vel": {"x": 2, "y": -3} 45 | }, 46 | { 47 | "pos": {"x": 9, "y": 5}, 48 | "vel": {"x": -3, "y": -3} 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /03.md: -------------------------------------------------------------------------------- 1 | ## parsing thoughts 2 | 3 | instead of feasibility thoughts today is more interesting to add an explainer on possum parsing for part 2: 4 | 5 | let me put here again the parsing code for convenience: 6 | 7 | ``` 8 | mul = "mul(" > tuple2_sep(int, ",", int) -> Args < ")" $ {"op": "mul", "args": Args} 9 | do = "do()" $ {"op": "do", "args": []} 10 | dont = "don't()" $ {"op": "dont", "args": []} 11 | array(scan(mul | do | dont)) 12 | ``` 13 | 14 | reading from bottom to top: 15 | - array(scan(mul | do | dont)) is the main parser: it produces an array of things. Thing is either a mul or a do or a dont. The scan keyword (in current possum docs is find but last release still uses scan) tells you to keep scanning the input (and skipping) until it matches a thing 16 | - a do or dont thing is a string literal. After match it produces (by using the "$" keyowrd) a json object with fields "op" and "args". This is needed to keep it consistent with the output of mul thing 17 | - mul thing also produces a json output but you can see that arguments are the stuff that is extracted from literals and named with -> Args and reused later. -------------------------------------------------------------------------------- /day02.nim: -------------------------------------------------------------------------------- 1 | import jsony 2 | import std / sugar 3 | 4 | type 5 | Report = seq[int] 6 | 7 | let input = "input/02.json".readFile.fromJson(seq[Report]) 8 | 9 | # is a report safe 10 | func isSafe(report: Report): bool = 11 | let direction = report[1] > report[0] 12 | for i in report.low .. report.high - 1: 13 | if (report[i + 1] > report[i]) != direction: 14 | return false 15 | if abs(report[i + 1] - report[i]) not_in [1, 2, 3]: 16 | return false 17 | return true 18 | 19 | # solve part1 20 | var part1 = 0 21 | for report in input: 22 | if report.isSafe: 23 | part1 += 1 24 | 25 | dump part1 26 | 27 | # damp a specific level 28 | func damp(report: Report, i: int): Report = 29 | report[0 ..< i] & report[(i + 1) .. report.high] 30 | 31 | func isSafeWithDampener(report: Report): bool = 32 | if isSafe(report): 33 | return true 34 | for i in report.low .. report.high: 35 | if isSafe(report.damp(i)): 36 | return true 37 | return false 38 | 39 | # solve part2 40 | var part2 = 0 41 | for report in input: 42 | if report.isSafeWithDampener: 43 | part2 += 1 44 | 45 | dump part2 46 | -------------------------------------------------------------------------------- /day25.nim: -------------------------------------------------------------------------------- 1 | import strutils, algorithm 2 | 3 | type 4 | Lock = seq[int] 5 | Key = seq[int] 6 | Schematics = object 7 | locks: seq[Lock] 8 | keys: seq[Key] 9 | 10 | func isLock(chunk: string): bool = chunk.startsWith("#") 11 | 12 | func parseShape(lines: seq[string]): seq[int] = 13 | result = newSeq[int](len(lines[0])) 14 | for i in 1 .. lines.high: 15 | for j in 0 ..< result.len: 16 | if lines[i][j] == '#': 17 | inc result[j] 18 | 19 | func parse(text: string): Schematics = 20 | for chunk in text.split("\n\n"): 21 | if chunk.isLock: 22 | result.locks.add chunk.split("\n").parseShape 23 | else: 24 | result.keys.add chunk.split("\n").reversed.parseShape 25 | 26 | func fit(lock: Lock, key: Key): bool = 27 | for i, v in lock: 28 | if v + key[i] > 5: 29 | return false 30 | true 31 | 32 | func part1(inp: Schematics): int = 33 | for lock in inp.locks: 34 | for key in inp.keys: 35 | if lock.fit key: 36 | inc result 37 | 38 | 39 | let tschematics = "test/25.txt".readFile.parse 40 | echo tschematics.part1 41 | echo "input/25.txt".readFile.parse.part1 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Pietro Peterlongo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # advent-of-code-2024 2 | 3 | Code and thoughts for AoC 2024 4 | 5 | Parsing done with [possum](https://github.com/mulias/possum_parser_language), see [puzzling with possum](https://mulias.github.io/blog/puzzling-with-possum-pt1/). 6 | 7 | Solving done with [nim](https://nim-lang.org/). 8 | 9 | To reproduce solution for day01: 10 | - put your input puzzle in `input/01.txt` 11 | - run `possum 01.possum input/01.txt > input/01.json` 12 | - `nim r day01` 13 | 14 | Thinking focuses on making an effort to connect the puzzle to some Operations Research ([OR]) concept. Thinking might be recorded in some markdown file (like `01.md`). 15 | I call this thinking *Feasibility thoughts* because I have a working hypothesis that 16 | the most important part in apply OR techniques in the Industry is the focus on 17 | constraints and as such on feasibility of the solution. 18 | Contrast with the traditional approach in the Academy where the focus is on Optimality. 19 | 20 | **day 3 update**: skipping the feasibility thoughts and going with a parsing thoughts. I think the thoughts sections will become something with variable content. 21 | 22 | [OR]: https://en.wikipedia.org/wiki/Operations_research 23 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | ## day 7 2 | 3 | - a gratuitous error on part1 (check that sum of all > test, fails on inputs which contain a 1 and are satisfiable with 1 being multiplied) 4 | 5 | possum example with typo 6 | 7 | ``` 8 | equation 9 | equation = record2_sep(f"test", int, ": ", $"numbers", numbers) 10 | numbers = array_sep(int, " ") 11 | ``` 12 | 13 | suboptimal error message 14 | 15 | ``` 16 | [Line 2, 24-30] Error at '"test"': Expected closing ')' 17 | error: UnexpectedInput 18 | ``` 19 | 20 | ## day 5 21 | 22 | - reported panic in possum: https://github.com/mulias/possum_parser_language/issues/18 (see bug1.possum) 23 | 24 | ## day 3 25 | 26 | - possum `scan` is renamed to `find` but my version still uses `scan` 27 | - part2 more involved and I made a separate 03_2.possum file 28 | 29 | this was fun, I enjoyed learning a more advanced pattern for possum 30 | 31 | ## day 1 32 | 33 | - set up repo [x] 34 | - parsing: possum? 35 | - how to install? [x] 36 | - donwload binary and possibly notirize on mac 37 | - run possum with no arguments [x] 38 | - no need to notariza, just did `chmod +x` and executable is working for me! [x] 39 | - copied possum to `/usr/local/bin` [x] 40 | - parse with possum input -------------------------------------------------------------------------------- /test/t15.nim: -------------------------------------------------------------------------------- 1 | const t0* = """ 2 | ######## 3 | #..O.O.# 4 | ##@.O..# 5 | #...O..# 6 | #.#.O..# 7 | #...O..# 8 | #......# 9 | ######## 10 | 11 | <^^>>>vv>v<<""" 12 | 13 | const t1* = """########## 14 | #..O..O.O# 15 | #......O.# 16 | #.OO..O.O# 17 | #..O@..O.# 18 | #O#..O...# 19 | #O..O..O.# 20 | #.OO.O.OO# 21 | #....O...# 22 | ########## 23 | 24 | ^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ 25 | vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< 27 | <>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ 28 | ^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< 29 | ^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ 31 | <><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> 32 | ^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< 33 | v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^""" 34 | 35 | const t2* = """ 36 | ####### 37 | #...#.# 38 | #.....# 39 | #..OO@# 40 | #..O..# 41 | #.....# 42 | ####### 43 | 44 | = 0 and a.y >= 0 and a.x < map.w and a.y < map.h 35 | 36 | func part1(map: Map): int = 37 | var nodes = initHashSet[Vec]() 38 | for c in map.ants.keys: 39 | for a in map.ants[c]: 40 | for b in map.ants[c]: 41 | if a != b: 42 | let n = antinode(a, b) 43 | if n.inside map: 44 | nodes.incl n 45 | len nodes 46 | 47 | echo tMap.part1 48 | echo map.part1 49 | 50 | func allNodes(map: Map, a, b: Vec): seq[Vec] = 51 | result.add b 52 | let (dx, dy) = (b.x - a.x, b.y - a.y) 53 | var n: Vec = (b.x + dx, b.y + dy) 54 | while n.inside(map): 55 | result.add n 56 | n = (n.x + dx, n.y + dy) 57 | 58 | func part2(map: Map): int = 59 | var nodes = initHashSet[Vec]() 60 | for c in map.ants.keys: 61 | for a in map.ants[c]: 62 | for b in map.ants[c]: 63 | if a != b: 64 | for n in map.allNodes(a, b): 65 | nodes.incl n 66 | len nodes 67 | 68 | echo tMap.part2 69 | echo map.part2 70 | 71 | -------------------------------------------------------------------------------- /01.md: -------------------------------------------------------------------------------- 1 | # Feasibility thoughts for day 01 2 | 3 | math notation is a very loose version of latex (the minimal amount I can write with $ and {}) 4 | 5 | - focus on sorting and not on the solution 6 | - an interesting exercise is to think sorting as a OR problem 7 | 8 | a simple modelling approach is: 9 | - given a sequence s_i for i in 1 .. N 10 | - consider the matrix P_i,j with boolean values {0, 1} 11 | - P_i,j will be my decision variable (dimension is N^2) 12 | - where P_i,j = 1 if value of index i is "moved" to place j 13 | - P_i,j represents a permutation if it satisfies the following constraints: 14 | - Sum_j P_i,j = 1 and Sum_i P_i,j = 1 15 | - in this case I can think P_i,j as a bijection P(i) = j 16 | - and I can think of the permuted sequence s_j = s_P(i) 17 | - note that if I think s_i as a vector x then I can think of s_j as vector y = Px 18 | - and the linear relation with the two sequence is evident 19 | - s_j is sorted if it satisfies the constraints s_j <= s_j+1 20 | - these constraints can be translated into linear constraints on P (the x plays the role of weights) 21 | - so **sorting is now translated into a constrained programming problem** 22 | - note that there might be multiple solution in the case of sequences with multiple equal values 23 | - in that case an "optimal" one can be selected by minimizing a loss, for example Sum abs(j-i) 24 | - I guess this is some kind of transport loss 25 | 26 | the modelling approach above is very expensive in terms of memory (Nˆ2 matrix!). 27 | A better approach would be to do a local search in the space of permutations and 28 | perform the search by applying iteratively swap operations between two indices. 29 | In that case my decision variable is a sequence of integers 1..N where each integer appears only once. Swap operations preserve this invariant. 30 | A natural arising question is: is there an algorithm for sorting with the minimal 31 | amount of swapping? 32 | this is an interesting answer: https://stackoverflow.com/questions/44167728/sort-a-list-by-only-the-swapping-of-its-elements 33 | -------------------------------------------------------------------------------- /day04.nim: -------------------------------------------------------------------------------- 1 | import std / [strutils, sequtils] 2 | 3 | let test = """ 4 | MMMSXXMASM 5 | MSAMXMSMSA 6 | AMXSXMAAMM 7 | MSAMASMSMX 8 | XMASAMXAMM 9 | XXAMMXXAMA 10 | SMSMSASXSS 11 | SAXAMASAAA 12 | MAMMMXMMMM 13 | MXMXAXMASX""" 14 | 15 | type 16 | Grid = object 17 | data: seq[char] 18 | xLen: int 19 | yLen: int 20 | 21 | func toGrid(text: string): Grid = 22 | let lines = text.split('\n') 23 | result.xLen = lines[0].len 24 | result.yLen = lines.len 25 | for line in lines: 26 | result.data.add line.toSeq 27 | 28 | func yLen(g: Grid): int = g.data.len 29 | 30 | type Point = tuple[x,y: int] 31 | 32 | func toGridIndex(p: Point, g: Grid): int = 33 | p.x + g.xLen * p.y 34 | 35 | const null = '0' 36 | 37 | func `[]`(g: Grid, p: Point): char = 38 | if p.x >= 0 and p.x < g.xLen and p.y >= 0 and p.y < g.yLen: 39 | let i = p.toGridIndex(g) 40 | g.data[i] 41 | else: 42 | null 43 | 44 | func `+`(p, q: Point): Point = (p.x + q.x, p.y + q.y) 45 | 46 | const directions: array[0..7, Point] = [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)] 47 | 48 | func toWord(g: Grid, p: Point, dir: Point, length = 4): string = 49 | var p = p 50 | for _ in 1 .. length: 51 | if g[p] != null: 52 | result.add g[p] 53 | p = p + dir 54 | 55 | func part1(text: string): int = 56 | let g = text.toGrid 57 | for x in 0 ..< g.xLen: 58 | for y in 0 ..< g.yLen: 59 | if g[(x,y)] == 'X': 60 | for dir in directions: 61 | if g.toWord((x, y), dir) == "XMAS": 62 | inc result 63 | 64 | echo test.part1 65 | echo "input/04.txt".readFile.part1 66 | 67 | func isCrossMas(g: Grid, p: Point): bool = 68 | for a in ["MMSS", "MSMS", "SSMM", "SMSM"]: 69 | if g[p] == 'A' and g[p + (-1, -1)] == a[0] and g[p + (-1, 1)] == a[1] and g[p + (1, -1)] == a[2] and g[p + (1, 1)] == a[3]: 70 | return true 71 | 72 | func part2(text: string): int = 73 | let g = text.toGrid 74 | for x in 0 ..< g.xLen: 75 | for y in 0 ..< g.yLen: 76 | if g.isCrossMas((x,y)): 77 | inc result 78 | 79 | echo test.part2 80 | echo "input/04.txt".readFile.part2 81 | -------------------------------------------------------------------------------- /day11.nim: -------------------------------------------------------------------------------- 1 | import std / [math, tables, algorithm] 2 | 3 | func digits(n: int): int = 4 | len $n 5 | 6 | func blink(n: int): seq[int] = 7 | if n == 0: 8 | @[1] 9 | elif n.digits mod 2 == 0: 10 | let d = n.digits div 2 11 | @[n div 10^d, n mod 10^d] 12 | else: 13 | @[n*2024] 14 | 15 | type 16 | BlinkInput = tuple[n: int, logTime: int] 17 | BlinkTable = Table[BlinkInput, seq[int]] 18 | 19 | func logs(n: int): seq[int] = 20 | var 21 | n = n 22 | l = 1 23 | while n > 0: 24 | if n mod 2 == 1: 25 | result.add l 26 | n = n div 2 27 | l = l * 2 28 | reverse result 29 | 30 | func blink(t: var BlinkTable, n, times: int): seq[int] = 31 | var 32 | next: seq[int] 33 | temp: seq[int] 34 | temp2: seq[int] 35 | result = @[n] 36 | for l in times.logs: 37 | next = @[] 38 | for m in result: 39 | if (m, l) in t: 40 | next.add t[(m, l)] 41 | elif l == 1: 42 | temp = blink m 43 | t[(m, 1)] = temp 44 | next.add temp 45 | else: 46 | temp = blink(t, m, l div 2) 47 | for m2 in temp: 48 | temp2 = blink(t, m2, l div 2) 49 | next.add temp2 50 | result = next 51 | 52 | func blink(t: var BlinkTable, s: seq[int], times: int): seq[int] = 53 | for n in s: 54 | result.add t.blink(n, times) 55 | 56 | var t = initTable[BlinkInput, seq[int]]() 57 | echo t.blink(@[125, 17], 6) # ok 58 | echo t.blink(@[125, 17], 25).len # ok 55312 59 | 60 | import input / inp11 61 | echo t.blink(inp11seq, 25).len # ok part1 62 | 63 | proc blinkCounts(t: var BlinkTable, s: seq[int], times: int): CountTable[int] = 64 | t.blink(s, times).toCountTable 65 | 66 | proc part2(t: var BlinkTable, s: seq[int]): int = 67 | var 68 | counts = t.blinkCounts(s, 25) 69 | counts2 = initCountTable[int]() 70 | for n, c in counts: 71 | for m, d in t.blinkCounts(@[n], 25): 72 | counts2.inc(m, d*c) 73 | counts = counts2 74 | counts2 = initCountTable[int]() 75 | for n, c in counts: 76 | for m, d in t.blinkCounts(@[n], 25): 77 | counts2.inc(m, d*c) 78 | counts = counts2 79 | for _, c in counts: 80 | result.inc c 81 | 82 | echo t.part2(inp11seq) 83 | # ok part 2 -------------------------------------------------------------------------------- /day14.nim: -------------------------------------------------------------------------------- 1 | import std / [math, tables, sequtils] 2 | import jsony 3 | 4 | type 5 | Vec = tuple[x, y: int] 6 | Robot = tuple 7 | pos: Vec 8 | vel: Vec 9 | 10 | let trobots = "test/14.json".readFile.fromJson(seq[Robot]) 11 | let robots = "input/14.json".readFile.fromJson(seq[Robot]) 12 | 13 | func `*`(a: int, v: Vec): Vec = 14 | (a*v.x, a*v.y) 15 | 16 | func `+`(u, v : Vec): Vec = 17 | (u.x + v.x, u.y + v.y) 18 | 19 | func move(r: Robot, secs: int = 1): Robot = 20 | (r.pos + secs*r.vel, r.vel) 21 | 22 | func rem(n, M: int): int = 23 | ((n mod M) + M) mod M 24 | 25 | func quad(x, w: int): int = 26 | let 27 | x = (x.rem w) 28 | h1 = w div 2 29 | h2 = h1 + w.rem 2 30 | if x < h1: 31 | 1 32 | elif x >= h2: 33 | 2 34 | else: 35 | 0 36 | 37 | func quadrant(v: Vec, space: Vec): int = 38 | let q = (quad(v.x, space.x)*quad(v.y, space.y)^2) 39 | if q == 0: 40 | -1 41 | else: 42 | q.float.log2.int 43 | 44 | for n in [0, 1, 2, 3, 4]: 45 | #echo quad(n, 5) 46 | discard 47 | # 1 1 0 2 2 48 | 49 | for x in [0, 2, 4]: 50 | for y in [0, 2, 4]: 51 | #echo (x, y), quadrant((x, y), (5, 5)) 52 | discard 53 | # 1 2 54 | # 4 8 55 | 56 | func part1(robots: seq[Robot], space: Vec): int = 57 | var c = [0, 0, 0, 0] 58 | for robot in robots: 59 | let later = robot.move(100) 60 | let q = quadrant(later.pos, space) 61 | if q >= 0: 62 | inc c[q] 63 | #debugEcho c 64 | c[0]*c[1]*c[2]*c[3] 65 | 66 | 67 | 68 | func rem(pos: Vec, space: Vec): Vec = 69 | (pos.x.rem space.x, pos.y.rem space.y) 70 | 71 | #echo ((2, 4), (2, -3)).move(5).pos.rem((11, 7)) 72 | const space = (101, 103) 73 | when defined(part1): 74 | echo trobots.part1((11, 7)) 75 | echo robots.part1(space) 76 | 77 | proc countVec(robots: seq[Robot], space: Vec): CountTable[Vec] = 78 | for robot in robots: 79 | result.inc(robot.pos.rem space) 80 | 81 | proc viz(t: CountTable[Vec], space: Vec): string = 82 | for y in 0 ..< space.y: 83 | for x in 0 ..< space.x: 84 | if t[(x, y)] == 0: 85 | result.add '.' 86 | elif t[(x, y)] < 10: 87 | result.add $t[(x, y)] 88 | else: 89 | result.add '*' 90 | result.add '\n' 91 | 92 | var t = 100 93 | while t < 10_000: 94 | echo "t = ", t 95 | echo robots.mapIt(it.move(t)).countVec(space).viz(space) 96 | inc t -------------------------------------------------------------------------------- /day10.nim: -------------------------------------------------------------------------------- 1 | import std / [strutils, sets, deques] 2 | 3 | type 4 | Vec = tuple[x, y: int] 5 | Map = object 6 | w, h: int 7 | topo: seq[seq[int]] 8 | 9 | 10 | func parse(text: string): Map = 11 | let lines = text.split('\n') 12 | result.h = len lines 13 | result.w = len lines[0] 14 | var horiz: seq[int] 15 | for y in 0 ..< result.h: 16 | horiz = newSeq[int](result.w) 17 | for x in 0 ..< result.w: 18 | horiz[x] = ord(lines[y][x]) - ord('0') 19 | result.topo.add horiz 20 | 21 | let t0 = """ 22 | 0123 23 | 1234 24 | 8765 25 | 9876""".parse 26 | 27 | echo t0 28 | 29 | type 30 | BfsOut = object 31 | explored: HashSet[Vec] # for debugging 32 | peaks: HashSet[Vec] 33 | 34 | func `[]`(m: Map, v: Vec): int = 35 | if v.x >= 0 and v.x < m.w and v.y >= 0 and v.y < m.h: 36 | m.topo[v.y][v.x] 37 | else: 38 | -1 39 | 40 | func trailFrom(m: Map, now: Vec): seq[Vec] = 41 | for (dx, dy) in [(0, 1), (1, 0), (-1, 0), (0, -1)]: 42 | let next = (now.x + dx, now.y + dy) 43 | if m[next] == m[now] + 1: 44 | result.add next 45 | 46 | func bfs(start: Vec, map: Map): BfsOut = 47 | var q = initDeque[Vec]() 48 | result.explored.incl start 49 | q.addLast start 50 | while q.len > 0: 51 | let now = q.popFirst() 52 | for next in map.trailFrom(now): 53 | if next notin result.explored: 54 | result.explored.incl next 55 | q.addLast next 56 | if map[next] == 9: 57 | result.peaks.incl next 58 | 59 | func part1(m: Map): int = 60 | for y in 0 ..< m.h: 61 | for x in 0 ..< m.w: 62 | if m[(x, y)] == 0: 63 | let bfsOut = (x, y).bfs(m) 64 | result.inc bfsOut.peaks.len 65 | 66 | echo t0.part1 67 | echo "test/10.txt".readFile.parse.part1 68 | echo "input/10.txt".readFile.parse.part1 69 | 70 | type 71 | DfsOut = object 72 | numPaths: int 73 | 74 | func dfs(start: Vec, map: Map): DfsOut = 75 | var stack = newSeq[Vec]() 76 | stack.add start 77 | while stack.len > 0: 78 | let now = stack.pop 79 | for next in map.trailFrom(now): 80 | stack.add next 81 | if map[next] == 9: 82 | result.numPaths.inc 83 | 84 | func part2(m: Map): int = 85 | for y in 0 ..< m.h: 86 | for x in 0 ..< m.w: 87 | if m[(x, y)] == 0: 88 | let dfsOut = (x, y).dfs(m) 89 | result.inc dfsOut.numPaths 90 | 91 | echo "test/10.txt".readFile.parse.part2 92 | echo "input/10.txt".readFile.parse.part2 93 | -------------------------------------------------------------------------------- /day22.nim: -------------------------------------------------------------------------------- 1 | import strutils, bitops, sequtils, algorithm, tables, sets 2 | 3 | func mix(a, b: int): int = bitxor(a, b) 4 | 5 | func prune(a: int): int = a mod 16777216 6 | 7 | func next(secret: int): int = 8 | result = mix(secret*64, secret).prune 9 | result = mix(result div 32, result).prune 10 | result = mix(result*2048, result).prune 11 | 12 | var tsecret = 123 13 | for _ in 1 .. 10: 14 | tsecret = next tsecret 15 | echo tsecret 16 | 17 | func part1(secrets: seq[int]): int = 18 | for secret in secrets: 19 | var secret = secret 20 | for _ in 1 .. 2000: 21 | secret = next secret 22 | result.inc secret 23 | 24 | echo @[1, 10, 100, 2024].part1 25 | echo "input/22.txt".readFile.split("\n").mapIt(it.parseInt).part1 26 | 27 | 28 | # mapping sequence of 4 changes to 8 digit numbers by adding 10 to each change 29 | func next(secret: var int, changes: var int, price: var int) = 30 | let oldPrice = secret mod 10 31 | secret = next secret 32 | price = secret mod 10 33 | let change = (price - oldPrice) + 10 34 | changes = (changes*100 + change) mod 10000_0000 35 | 36 | func asChanges(num: int): seq[int] = 37 | var num = num 38 | while num > 0: 39 | result.add (num mod 100 - 10) 40 | num = num div 100 41 | result.reversed 42 | 43 | tsecret = 123 44 | var tchange = 0 45 | var tprice = 0 46 | for _ in 1 .. 9: 47 | next(tsecret, tchange, tprice) 48 | echo tchange.asChanges 49 | 50 | 51 | proc getPriceTable(secrets: seq[int]): CountTable[int] = 52 | result = initCountTable[int]() 53 | for secret in secrets: 54 | var seen = initHashSet[int]() 55 | var secret = secret 56 | var price = 0 57 | var change = 0 58 | for _ in 1 .. 4: 59 | next(secret, change, price) 60 | result.inc(change, price) 61 | seen.incl change 62 | for _ in 1 .. 1996: # or 1995? 63 | next(secret, change, price) 64 | if change notIn seen: 65 | result.inc(change, price) 66 | seen.incl change 67 | 68 | proc part2(secrets: seq[int]): int = 69 | let priceTable = secrets.getPriceTable 70 | for v in priceTable.values: 71 | if v > result: 72 | result = v 73 | 74 | func asNum(changes: seq[int]): int = 75 | for change in changes: 76 | result = result*100 + change + 10 77 | 78 | const tchangeNum = @[-2, 1, -1, 3].asNum 79 | echo tchangeNum 80 | echo tchangeNum.asChanges 81 | 82 | echo @[1, 2, 3, 2024].part2 #24? 83 | block: 84 | let tab = @[1].getPriceTable 85 | echo tab[tchangeNum] 86 | block: 87 | let tab = @[2].getPriceTable 88 | echo tab[tchangeNum] 89 | block: 90 | let tab = @[3].getPriceTable 91 | echo tab[tchangeNum] 92 | block: 93 | let tab = @[2024].getPriceTable 94 | echo tab[tchangeNum] 95 | 96 | echo "input/22.txt".readFile.split("\n").mapIt(it.parseInt).part2 97 | -------------------------------------------------------------------------------- /day12.nim: -------------------------------------------------------------------------------- 1 | import std / [strutils, sets, tables, algorithm] 2 | 3 | type 4 | Map = object 5 | w, h: int 6 | data: seq[string] 7 | Vec = tuple[x, y: int] 8 | Dir = enum 9 | Up, Down, Left, Right 10 | Move = tuple 11 | vec: Vec 12 | dir: Dir 13 | Region = object 14 | tag: char 15 | area, perimeter: int 16 | data: HashSet[Vec] 17 | border: HashSet[Move] 18 | sides: Table[Dir, Table[int, seq[int]]] 19 | 20 | func parse(text: string): Map = 21 | let lines = text.split('\n') 22 | result.h = len lines 23 | result.w = len lines[0] 24 | result.data = lines 25 | 26 | func `[]`(m: Map, v: Vec): char = 27 | if v.x >= 0 and v.x < m.w and v.y >= 0 and v.y < m.h: 28 | m.data[v.y][v.x] 29 | else: 30 | '0' 31 | 32 | let tMap = "test/12.txt".readFile.parse 33 | let map = "input/12.txt".readFile.parse 34 | 35 | proc explore(map: Map, u: Vec): Region = 36 | var q: seq[Vec] 37 | result.tag = map[u] 38 | #echo result.tag 39 | q.add u 40 | while q.len > 0: 41 | let v = q.pop() 42 | #echo v 43 | result.data.incl v 44 | inc result.area 45 | for move in [((v.x + 1, v.y), Right).Move, ((v.x - 1, v.y), Left), ((v.x, v.y + 1), Down), ((v.x, v.y - 1), Up)]: 46 | if move.vec notIn result.data and move.vec notIn q: 47 | if map[move.vec] == result.tag: 48 | q.add move.vec 49 | else: 50 | #echo "perimeter", w 51 | result.border.incl move 52 | inc result.perimeter 53 | 54 | proc explore(map: Map): HashSet[Region] = 55 | var explored = initHashSet[Vec]() 56 | for y in 0 ..< map.h: 57 | for x in 0 ..< map.w: 58 | if (x, y) in explored: 59 | continue 60 | let region = explore(map, (x, y)) 61 | result.incl region 62 | explored.incl region.data 63 | 64 | proc part1(map: Map): int = 65 | for region in map.explore: 66 | #echo region.tag, " ", region.area, " ", region.perimeter 67 | result.inc region.area * region.perimeter 68 | 69 | when defined(part1): 70 | echo tmap.part1 71 | echo map.part1 72 | quit() 73 | 74 | func countSides(s: seq[int]): int = 75 | let s = sorted(s) 76 | var prev = -3 77 | for next in s: 78 | if next != prev + 1: 79 | inc result 80 | prev = next 81 | 82 | proc getSides(region: var Region) = 83 | var 84 | sides = initTable[Dir, Table[int, seq[int]]]() 85 | for dir in Dir: 86 | sides[dir] = initTable[int, seq[int]]() 87 | 88 | for b in region.border: 89 | if b.dir in [Up, Down]: 90 | if b.vec.y notIn sides[b.dir]: 91 | sides[b.dir][b.vec.y] = @[] 92 | sides[b.dir][b.vec.y].add b.vec.x 93 | if b.dir in [Left, Right]: 94 | if b.vec.x notIn sides[b.dir]: 95 | sides[b.dir][b.vec.x] = @[] 96 | sides[b.dir][b.vec.x].add b.vec.y 97 | region.sides = sides 98 | 99 | proc countSides(region: Region): int = 100 | var region = region 101 | region.getSides 102 | for dir in Dir: 103 | for key, val in region.sides[dir]: 104 | #echo key, val 105 | let c = countSides(val) 106 | #echo c 107 | result.inc c 108 | 109 | proc part2(map: Map): int = 110 | for region in map.explore: 111 | #echo region.tag, " ", region.area, " ", region.perimeter 112 | result.inc region.area * region.countSides 113 | 114 | let m0 = """ 115 | AAAA 116 | BBCD 117 | BBCC 118 | EEEC""".parse 119 | let r0 = m0.explore 120 | 121 | echo m0.part2 122 | echo tmap.part2 123 | echo map.part2 124 | -------------------------------------------------------------------------------- /day06.nim: -------------------------------------------------------------------------------- 1 | import std / [strutils, sequtils, strformat, algorithm, tables, sets] 2 | import jsony 3 | 4 | #possum 06.possum input/06.txt > input/06.json 5 | #possum 06.possum test/06.txt > test/06.json 6 | #echo "input/06.txt".readFile.fromJson() 7 | #echo "test/06.txt".readFile.fromJson() 8 | 9 | template dbg(things: varargs[untyped]) = 10 | when defined(dbg): 11 | debugEcho things 12 | 13 | template dbg2(things: varargs[untyped]) = 14 | when defined(dbg2): 15 | debugEcho things 16 | 17 | # no possum today 18 | type 19 | Vec2 = tuple[x, y:int] 20 | Map = object 21 | w, h: int 22 | obstacles: Hashset[Vec2] 23 | Dir = enum 24 | Up, Down, Left, Right 25 | Guard = object 26 | pos: Vec2 27 | dir: Dir 28 | 29 | # complains of side effects?? 30 | proc parse(text: string): (Map, Guard) = 31 | var map: Map 32 | var guard: Guard 33 | map.obstacles = initHashSet[Vec2]() 34 | let lines = text.split("\n") 35 | map.h = len(lines) 36 | map.w = lines[0].len 37 | for y in 0 ..< map.h: 38 | for x in 0 ..< map.w: 39 | if lines[y][x] == '#': 40 | map.obstacles.incl (x,y) 41 | elif lines[y][x] == '^': 42 | guard.pos = (x, y) 43 | guard.dir = Up 44 | elif lines[y][x] == 'v': 45 | guard.pos = (x, y) 46 | guard.dir = Down 47 | elif lines[y][x] == '>': 48 | guard.pos = (x, y) 49 | guard.dir = Right 50 | elif lines[y][x] == '<': 51 | guard.pos = (x, y) 52 | guard.dir = Left 53 | return (map, guard) 54 | 55 | 56 | func inside(pos: Vec2, map: Map): bool = 57 | pos.x >= 0 and pos.x < map.w and pos.y >= 0 and pos.y < map.h 58 | 59 | func inside(guard: Guard, map: Map): bool = 60 | guard.pos.inside(map) 61 | 62 | func turnRight(d: Dir): Dir = 63 | case d 64 | of Up: 65 | Right 66 | of Right: 67 | Down 68 | of Down: 69 | Left 70 | of Left: 71 | Up 72 | 73 | func `+`(v, w: Vec2): Vec2 = (v.x + w.x, v.y + w.y) 74 | 75 | const toVec: array[Dir, Vec2] = [(0, -1), (0, 1), (-1, 0), (1, 0)] 76 | 77 | proc move(guard: var Guard, map: Map) = 78 | let next = guard.pos + toVec[guard.dir] 79 | if next in map.obstacles: 80 | guard.dir = guard.dir.turnRight 81 | else: 82 | guard.pos = next 83 | 84 | proc part1(map: Map, guard: Guard): int = 85 | var visits = initHashSet[Vec2]() 86 | var guard = guard 87 | visits.incl guard.pos 88 | while guard.inside(map): 89 | guard.move(map) 90 | visits.incl guard.pos 91 | return visits.len - 1 92 | 93 | let (tMap, tGuard) = "test/06.txt".readFile.parse 94 | #echo part1(tMap, tGuard) 95 | 96 | let (map, guard) = "input/06.txt".readFile.parse 97 | #echo part1(map, guard) 98 | 99 | proc loops(guard: Guard, map: Map): bool = 100 | var path = initHashSet[Guard]() 101 | var guard = guard 102 | path.incl guard 103 | while guard.inside(map): 104 | guard.move(map) 105 | if guard in path: 106 | return true 107 | path.incl guard 108 | 109 | proc addObstacle(map: Map, pos: Vec2): Map = 110 | result = map 111 | result.obstacles.incl pos 112 | 113 | proc part2(map: Map, guard: Guard): int = 114 | var guard = guard 115 | var lobs = initHashSet[Vec2]() 116 | var visits = initHashSet[Vec2]() 117 | while guard.inside(map): 118 | visits.incl guard.pos 119 | let obs = guard.pos + toVec[guard.dir] 120 | if obs.inside(map) and obs notin visits and guard.loops(map.addObstacle(obs)): 121 | lobs.incl obs 122 | guard.move(map) 123 | result = len lobs 124 | 125 | echo part2(tMap, tGuard) 126 | echo part2(map, guard) 127 | # 437 NO, 436 NO, 438 NO, 1867 NO, 1888 NO, 1887 NO, 1782 NO, 1711 YES! 128 | -------------------------------------------------------------------------------- /day07.nim: -------------------------------------------------------------------------------- 1 | import std / [math, strutils] 2 | import jsony 3 | 4 | type 5 | Equation = object 6 | test: int 7 | numbers: seq[int] 8 | 9 | template dbg(things: varargs[untyped]) = 10 | when defined(dbg): 11 | debugEcho things 12 | 13 | template vdbg(things: varargs[untyped]) = 14 | when defined(dbg): 15 | if verbose: 16 | debugEcho things 17 | 18 | func isSatisfiable(eq: Equation, verbose = false): bool = 19 | if eq.numbers.len == 2: 20 | if eq.numbers.sum == eq.test: 21 | vdbg " sum 2 ok: ", eq.test, " ", eq.numbers 22 | return true 23 | elif eq.numbers[0]*eq.numbers[1] == eq.test: 24 | vdbg " mul 2 ok: ", eq.test, " ", eq.numbers 25 | return true 26 | else: 27 | return false 28 | #if eq.numbers.sum > eq.test: 29 | # return false 30 | let last = eq.numbers[^1] 31 | let testMultiply = 32 | if eq.test mod last == 0: 33 | vdbg " test mul: ", Equation(test: eq.test div last, numbers: eq.numbers[0 ..< ^1]) 34 | isSatisfiable(Equation(test: eq.test div last, numbers: eq.numbers[0 ..< ^1]), verbose=verbose) 35 | else: 36 | false 37 | let testSum = block: 38 | vdbg " test sum: ", Equation(test: eq.test - last, numbers: eq.numbers[0 ..< ^1]) 39 | isSatisfiable(Equation(test: eq.test - last, numbers: eq.numbers[0 ..< ^1]), verbose=verbose) 40 | return testMultiply or testSum 41 | 42 | func part1(eqs: seq[Equation]): int = 43 | for eq in eqs: 44 | if eq.isSatisfiable: 45 | dbg "eq: ", eq 46 | discard eq.isSatisfiable(verbose = true) 47 | result.inc eq.test 48 | 49 | #echo "test/07.json".readFile.fromJson(seq[Equation]).part1 50 | #echo "input/07.json".readFile.fromJson(seq[Equation]).part1 # nope 51 | # 21572034122397 (case with 1 that has sum bigger) 52 | # 21572148763543 53 | 54 | 55 | func cat(a, b: int): int = 56 | a*10^len($b) + b 57 | 58 | func modCat(t, a: int): bool = 59 | ($t).endsWith($a) 60 | 61 | func divCat(t, a: int): int = 62 | t div 10^len($a) 63 | 64 | func isSat2(eq: Equation, verbose = false): bool = 65 | if eq.numbers.len == 2: 66 | if eq.numbers.sum == eq.test: 67 | vdbg " sum 2 ok: ", eq.test, " ", eq.numbers 68 | return true 69 | elif eq.numbers[0]*eq.numbers[1] == eq.test: 70 | vdbg " mul 2 ok: ", eq.test, " ", eq.numbers 71 | return true 72 | elif cat(eq.numbers[0], eq.numbers[1]) == eq.test: 73 | vdbg " cat 2 ok: ", eq.test, " ", eq.numbers 74 | return true 75 | else: 76 | return false 77 | let last = eq.numbers[^1] 78 | let testCat = 79 | if eq.test.modCat last: 80 | vdbg " test cat: ", Equation(test: eq.test.divCat last, numbers: eq.numbers[0 ..< ^1]) 81 | isSat2(Equation(test: eq.test.divCat last, numbers: eq.numbers[0 ..< ^1]), verbose=verbose) 82 | else: 83 | false 84 | let testMultiply = 85 | if eq.test mod last == 0: 86 | vdbg " test mul: ", Equation(test: eq.test div last, numbers: eq.numbers[0 ..< ^1]) 87 | isSat2(Equation(test: eq.test div last, numbers: eq.numbers[0 ..< ^1]), verbose=verbose) 88 | else: 89 | false 90 | let testSum = block: 91 | vdbg " test sum: ", Equation(test: eq.test - last, numbers: eq.numbers[0 ..< ^1]) 92 | isSat2(Equation(test: eq.test - last, numbers: eq.numbers[0 ..< ^1]), verbose=verbose) 93 | return testCat or testMultiply or testSum 94 | 95 | func part2(eqs: seq[Equation]): int = 96 | for eq in eqs: 97 | if eq.isSat2: 98 | dbg "eq: ", eq 99 | dbg eq.isSat2(verbose = true) 100 | result.inc eq.test 101 | 102 | echo "test/07.json".readFile.fromJson(seq[Equation]).part2 103 | echo "input/07.json".readFile.fromJson(seq[Equation]).part2 104 | -------------------------------------------------------------------------------- /day09.nim: -------------------------------------------------------------------------------- 1 | template dbg(things: varargs[untyped]) = 2 | when defined(dbg): 3 | debugEcho things 4 | 5 | type 6 | Disk = seq[int16] 7 | 8 | func parse(text: string): Disk = 9 | var id = 0.int16 10 | var l = 0 11 | for i, c in text: 12 | l = (ord(c) - ord('0')) 13 | if i mod 2 == 0: 14 | for _ in 1 .. l: 15 | result.add id 16 | inc id 17 | else: 18 | for _ in 1 .. l: 19 | result.add -1.int16 20 | 21 | let 22 | t1 = "12345".parse 23 | t2 = "2333133121414131402".parse 24 | inp = "input/09.txt".readFile().parse 25 | 26 | for d in [t1, t2, inp]: 27 | echo d.len 28 | 29 | func toStr(d: Disk): string = 30 | for n in d: 31 | if n == -1.int16: 32 | result.add '.' 33 | else: 34 | result.add $(n) 35 | 36 | const empty = -1.int16 37 | 38 | func isEmpty(disk: Disk, i: int): bool = 39 | disk[i] == empty 40 | 41 | func compact(disk: Disk): Disk = 42 | result = disk 43 | var 44 | i = 0 45 | j = disk.len - 1 46 | while i < j: 47 | if not result.isEmpty(i): 48 | inc i 49 | elif result.isEmpty(j): 50 | dec j 51 | else: 52 | result[i] = result[j] 53 | result[j] = empty 54 | 55 | for d in [t1, t2]: 56 | echo d.toStr 57 | echo d.compact.toStr 58 | 59 | assert t1.compact.toStr == "022111222......" 60 | assert t2.compact.toStr == "0099811188827773336446555566.............." 61 | 62 | func checksum(disk: Disk): int = 63 | for i, id in disk: 64 | if id == empty: 65 | continue 66 | result.inc(i*id.int) 67 | 68 | echo t1.compact.checksum 69 | echo t2.compact.checksum 70 | assert t2.compact.checksum == 1928 71 | 72 | echo inp.compact.checksum 73 | 74 | type 75 | EmptyLocation = tuple 76 | start, length: int 77 | FileLocation = tuple 78 | start, length: int 79 | id: int16 80 | 81 | func findEmpty(disk: Disk, start: int): EmptyLocation = 82 | var i = start 83 | while i < disk.len and not disk.isEmpty(i): 84 | inc i 85 | if i == disk.len: 86 | return (i, 0) 87 | var j = i 88 | while j < disk.len and disk.isEmpty(j): 89 | inc j 90 | return (i, j - i) 91 | 92 | func findEmptyWithLen(disk: Disk, start: int, length: int): EmptyLocation = 93 | var 94 | i = start 95 | loc = findEmpty(disk, start) 96 | while loc.length > 0 and loc.length < length: 97 | i = loc.start + 1 98 | loc = findEmpty(disk, i) 99 | result = loc 100 | 101 | func findFile(disk: Disk, start: int): FileLocation = 102 | var j = start 103 | while j >= 0 and disk.isEmpty(j) : 104 | dec j 105 | if j < 0: 106 | return (0, 0, empty) 107 | var i = j 108 | let id = disk[i] 109 | while i >= 0 and not disk.isEmpty(i) and disk[i] == id: 110 | dec i 111 | return (i + 1, j - i, id) 112 | 113 | echo t1.findEmpty(0) # 1, 2 114 | echo t1.findFile(t1.high) # (10, 5, 2) 115 | echo t1.findEmptyWithLen(0, 3) # (start: 6, length: 4) 116 | echo t1.findEmptyWithLen(0, 5) # 1, 2 117 | 118 | func move(disk: var Disk, file: FileLocation, space: EmptyLocation) = 119 | for i in space.start ..< (space.start + file.length): 120 | disk[i] = file.id 121 | for j in file.start ..< (file.start + file.length): 122 | disk[j] = empty 123 | 124 | func defrag(disk: Disk): Disk = 125 | var 126 | j = disk.len - 1 127 | file = disk.findFile(j) 128 | emptyLoc = disk.findEmptyWithLen(0, file.length) 129 | i = emptyLoc.start 130 | result = disk 131 | 132 | while file.id != empty: 133 | dbg "file: ", file 134 | dbg "space: ", emptyLoc 135 | dbg "j: ", j 136 | dbg "i: ", i 137 | if i < j and emptyLoc.length > 0: 138 | result.move(file, emptyLoc) 139 | dbg "disk: ", result.toStr 140 | file = result.findFile(file.start - 1) 141 | j = file.start 142 | emptyLoc = result.findEmptyWithLen(0, file.length) 143 | i = emptyLoc.start 144 | dbg "file: ", file 145 | dbg "space: ", emptyLoc 146 | dbg "j: ", j 147 | dbg "i: ", i 148 | 149 | echo t2.defrag.toStr 150 | assert "00992111777.44.333....5555.6666.....8888.." == t2.defrag.toStr 151 | 152 | echo t2.defrag.checksum 153 | echo inp.defrag.checksum -------------------------------------------------------------------------------- /day05.nim: -------------------------------------------------------------------------------- 1 | import std / [tables, intsets, algorithm, heapqueue] 2 | import jsony 3 | 4 | type 5 | Rule = tuple[before, after: int] 6 | PuzzleInput = object 7 | rules: seq[Rule] 8 | updates: seq[seq[int]] 9 | 10 | 11 | type 12 | Update = object 13 | data: seq[int] 14 | pages: IntSet 15 | pageIndex: Table[int, int] 16 | RuleBook = object 17 | data: seq[Rule] 18 | book: Table[int, IntSet] 19 | Lookup = object 20 | rules: RuleBook 21 | updates: seq[Update] 22 | 23 | func toLookup(input: PuzzleInput): Lookup = 24 | result.rules.data = input.rules 25 | # put rules int a lookup book 26 | for rule in result.rules.data: 27 | let (before, after) = rule 28 | if before notIn result.rules.book: 29 | result.rules.book[before] = initIntSet() 30 | result.rules.book[before].incl after 31 | # create lookup updates 32 | for data in input.updates: 33 | var update = Update(data: data) 34 | for i, page in data: 35 | update.pages.incl page 36 | update.pageIndex[page] = i 37 | result.updates.add update 38 | 39 | func check(update: Update, rules: RuleBook): bool = 40 | for i, page in update.data: 41 | if page in rules.book: 42 | let pagesAfter = rules.book[page] 43 | for after in pagesAfter: 44 | if after in update.pages and update.pageIndex[after] < i: 45 | return false 46 | return true 47 | 48 | func midPage(update: Update): int = 49 | update.data[(update.data.len div 2)] 50 | 51 | func part1(look: Lookup): int = 52 | for update in look.updates: 53 | if update.check(look.rules): 54 | #debugEcho "check ok: ", update.data 55 | result.inc update.midPage 56 | #debugEcho " add ", update.midPageNumber 57 | 58 | 59 | when defined(part1): 60 | echo "test/05.json".readFile.fromJson(PuzzleInput).toLookup.part1 61 | echo "input/05.json".readFile.fromJson(PuzzleInput).toLookup.part1 62 | 63 | # plan for part2 64 | # (topological ordering) 65 | # push pages on a queue (first pages should be the first out) 66 | # process queue by finding first element which does not have dependencies 67 | 68 | # let's start with an example coming from test set 69 | let 70 | tInput = "test/05.json".readFile.fromJson(PuzzleInput) 71 | input = "input/05.json".readFile.fromJson(PuzzleInput) 72 | ex0 = tInput.updates[0] 73 | 74 | echo ex0 75 | 76 | # extract rule set that applies in a structure that tracks 77 | # on which pages a certain page depends on 78 | type 79 | Depends = Table[int, IntSet] # ex 80 | 81 | func filter(rules: seq[Rule], update: seq[int]): Depends = 82 | for page in update: 83 | if page notin result: 84 | result[page] = initIntSet() 85 | for rule in rules: 86 | if page == rule.after and rule.before in update: 87 | result[page].incl rule.before 88 | 89 | let 90 | dep0 = tInput.rules.filter(ex0) 91 | 92 | # actually it seems filtered rules force a complete ordering 93 | func check(dep: Depends): bool = 94 | var depsLen = initIntSet() 95 | for page, deps in dep: 96 | depsLen.incl deps.len 97 | result = depsLen.len == dep.len 98 | if not result: 99 | debugEcho depsLen 100 | 101 | func check(inp: PuzzleInput): bool = 102 | for upd in inp.updates: 103 | if not inp.rules.filter(upd).check: 104 | debugEcho upd 105 | debugecho inp.rules.filter(upd) 106 | return false 107 | return true 108 | 109 | # both are true! 110 | echo tInput.check 111 | echo input.check 112 | 113 | # ok then let's proceed with the ordering indeed 114 | # still useful to use a queue, but I can use 115 | type 116 | Page = object 117 | value: int 118 | depsLen: int 119 | 120 | func `<`(p, q: Page): bool = p.depsLen < q.depsLen 121 | 122 | func sort(dep: Depends): seq[int] = 123 | var q = initHeapQueue[Page]() 124 | for val, deps in dep: 125 | q.push(Page(value: val, depsLen: deps.len)) 126 | while q.len > 0: 127 | result.add q.pop().value 128 | 129 | echo dep0 130 | echo dep0.sort 131 | 132 | func part2(inp: PuzzleInput): int = 133 | for upd in inp.updates: 134 | let 135 | deps = inp.rules.filter(upd) 136 | updSorted = sort deps 137 | midPage = updSorted[updSorted.len div 2] 138 | if upd != updSorted: 139 | when defined(dbg): 140 | debugEcho "upd: ", upd 141 | debugEcho "deps: ", deps 142 | debugEcho "updSorted: ", updSorted 143 | debugEcho "midPage: ", midPage 144 | result.inc midPage 145 | 146 | echo tInput.part2 147 | #echo 47 + 29 + 47 = 123 148 | when not defined(dbg): 149 | echo input.part2 150 | -------------------------------------------------------------------------------- /day09no.nim: -------------------------------------------------------------------------------- 1 | import std / [math, lists] 2 | 3 | template dbg(things: varargs[untyped]) = 4 | when defined(dbg): 5 | debugEcho things 6 | 7 | type 8 | Block = object 9 | id: int # id >= 0 if file -1 if empty 10 | len8: int8 11 | Disk = DoublyLinkedList[Block] 12 | 13 | func parse(text: string): Disk = 14 | var id = 0 15 | var len8 = 0.int8 16 | for i, c in text: 17 | len8 = (ord(c) - ord('0')).int8 18 | if i mod 2 == 0: 19 | result.add Block(id: id, len8: len8) 20 | inc id 21 | else: 22 | result.add Block(id: -1, len8: len8) 23 | 24 | let 25 | t1 = "12345".parse 26 | t2 = "2333133121414131402".parse 27 | inp = "input/09.txt".readFile().parse 28 | 29 | proc diagnostic(disk: Disk) = 30 | var 31 | l = 0 32 | t = 0 33 | for b in disk: 34 | inc l 35 | t += b.len8.int 36 | echo "len: ", l 37 | echo "sum: ", t 38 | 39 | for d in [t1, t2, inp]: 40 | diagnostic d 41 | 42 | func toStr(d: Disk): string = 43 | for b in d: 44 | if b.id == -1: 45 | for _ in 1 .. b.len8: 46 | result.add '.' 47 | else: 48 | for _ in 1 .. b.len8: 49 | result.add $(b.id) 50 | 51 | 52 | func isEmpty(b: Block): bool = b.id == -1 53 | 54 | proc fix(head: var DoublyLinkedNode[Block], tail: var DoublyLinkedNode[Block]) = 55 | assert head.value.isEmpty 56 | assert not tail.value.isEmpty 57 | if tail.value.len8 == head.value.len8: 58 | var 59 | newTail = newDoublyLinkedNode[Block](Block(id: -1, len8: tail.value.len8)) 60 | newTail.prev = tail.prev 61 | newTail.next = tail.next 62 | var 63 | newHead = newDoublyLinkedNode[Block](Block(id: tail.value.id, len8: head.value.len8)) 64 | newHead.prev = head.prev 65 | newHead.next = head.next 66 | head = newHead 67 | if head.prev != nil: 68 | head.prev.next = head 69 | if head.next != nil: 70 | head.next.prev = head 71 | tail = newTail 72 | if tail.prev != nil: 73 | tail.prev.next = tail 74 | if tail.next != nil: 75 | tail.next.prev = tail 76 | elif tail.value.len8 < head.value.len8: 77 | var 78 | newTail = newDoublyLinkedNode[Block](Block(id: -1, len8: tail.value.len8)) 79 | newTail.prev = tail.prev 80 | newTail.next = tail.next 81 | tail = newTail 82 | if tail.prev != nil: 83 | tail.prev.next = tail 84 | if tail.next != nil: 85 | tail.next.prev = tail 86 | var 87 | newHead = newDoublyLinkedNode[Block](Block(id: tail.value.id, len8: tail.value.len8)) 88 | newHead2 = newDoublyLinkedNode[Block](Block(id: -1, len8: head.value.len8 - tail.value.len8)) 89 | newHead.prev = head.prev 90 | newHead.next = newHead2 91 | newHead2.prev = newHead 92 | newHead2.next = head.next 93 | head = newHead 94 | if head.prev != nil: 95 | head.prev.next = head 96 | if head.next.next != nil: 97 | head.next.next.prev = head.next 98 | else: # tail > head 99 | var 100 | newTail = newDoublyLinkedNode[Block](Block(id: tail.value.id, len8: tail.value.len8 - head.value.len8)) 101 | newTail2 = newDoublyLinkedNode[Block](Block(id: -1, len8: head.value.len8)) 102 | newTail.prev = tail.prev 103 | newTail.next = newTail2 104 | newTail2.prev = newTail 105 | newTail2.next = tail.next 106 | tail = newTail 107 | if tail.prev != nil: 108 | tail.prev.next = tail 109 | if tail.next.next != nil: 110 | tail.next.next.prev = tail.next 111 | var 112 | newHead = newDoublyLinkedNode[Block](Block(id: tail.value.id, len8: head.value.len8)) 113 | newHead.prev = head.prev 114 | newHead.next = head.next 115 | head = newHead 116 | if head.prev != nil: 117 | head.prev.next = head 118 | if head.next != nil: 119 | head.next.prev = head 120 | 121 | func compact(disk: Disk): Disk = 122 | var disk = disk 123 | var head = disk.head 124 | var tail = disk.tail 125 | # find next empty spot in head (quit if reached tail) 126 | while head != tail and head != nil: 127 | dbg "head: ", head.value 128 | if not head.value.isEmpty: 129 | head = head.next 130 | else: 131 | # find next file from tail (no need to quit?) 132 | while tail.value.isEmpty: 133 | if tail == head: 134 | return # just to be safe 135 | tail = tail.prev 136 | dbg "tail: ", tail.value 137 | fix(head, tail) 138 | dbg "after fix: ", disk.toStr 139 | dbg "new tail: ", tail.value 140 | dbg "new head: ", head.value 141 | head = head.next 142 | 143 | result = disk 144 | 145 | for d in [t1]:#, t2]: 146 | #echo d 147 | echo d.toStr 148 | echo d.compact.toStr 149 | -------------------------------------------------------------------------------- /day17.nim: -------------------------------------------------------------------------------- 1 | import std / [sequtils, math, bitops, strutils, algorithm] 2 | 3 | type 4 | Machine = object 5 | a, b, c: int 6 | prog: seq[int] 7 | pnt: int 8 | outt: seq[int] 9 | Opcode = enum 10 | adv, bxl, bst, jnz, bxc, outt, bdv, cdv 11 | 12 | let m0 = Machine( 13 | a: 729, 14 | b: 0, 15 | c: 0, 16 | prog: @[0, 1, 5, 4, 3, 0], 17 | pnt: 0 18 | ) 19 | 20 | func literal(m: Machine): int = m.prog[m.pnt + 1] 21 | 22 | func combo(m: Machine): int = 23 | case m.prog[m.pnt + 1] 24 | of 0 .. 3: 25 | m.prog[m.pnt + 1] 26 | of 4: 27 | m.a 28 | of 5: 29 | m.b 30 | of 6: 31 | m.c 32 | else: 33 | raise ValueError.newException("invalid combo: " & $m.prog[m.pnt + 1]) 34 | 35 | proc eval(m: var Machine): bool = 36 | if m.pnt > m.prog.len - 1: 37 | return false 38 | let opcode = OpCode(m.prog[m.pnt]) 39 | var jump = false 40 | #debugEcho opcode 41 | case opcode 42 | of adv: 43 | m.a = m.a div (2^(m.combo)) 44 | of bxl: 45 | m.b = bitxor(m.b, m.literal) 46 | of bst: 47 | m.b = (m.combo mod 8) 48 | of jnz: 49 | if m.a == 0: 50 | discard 51 | else: 52 | m.pnt = m.literal 53 | jump = true 54 | of bxc: 55 | m.b = bitxor(m.b, m.c) 56 | of outt: 57 | m.outt.add (m.combo mod 8) 58 | of bdv: 59 | m.b = m.a div (2^(m.combo)) # unused? 60 | of cdv: 61 | m.c = m.a div (2^(m.combo)) 62 | 63 | 64 | if not jump: 65 | m.pnt.inc 2 66 | true 67 | 68 | func part1(m: Machine): string = 69 | var m = m 70 | while eval(m): 71 | #debugEcho m 72 | discard 73 | result = m.outt.mapIt($it).join(",") 74 | 75 | let inp = Machine( 76 | a: 33940147, 77 | b: 0, 78 | c: 0, 79 | prog: @[2,4,1,5,7,5,1,6,4,2,5,5,0,3,3,0], 80 | pnt: 0 81 | ) 82 | 83 | echo m0.part1 84 | echo inp.part1 85 | 86 | func reset(m: var Machine, a: int) = 87 | m.outt = @[] 88 | m.pnt = 0 89 | m.a = a 90 | 91 | func quine(m: var Machine): bool = 92 | var lenout = m.outt.len 93 | while eval(m): 94 | if m.outt.len > m.prog.len: 95 | return false 96 | elif m.outt.len != lenout: 97 | if m.outt[m.outt.high] != m.prog[m.outt.high]: 98 | return false 99 | if m.outt == m.prog: 100 | return true 101 | 102 | func part2basic(m: Machine, start=0): int = 103 | result = start 104 | var m = m 105 | var maxlen = 0 106 | var prev = result 107 | reset(m, result) 108 | while not quine(m): 109 | if m.outt.len >= maxlen: 110 | maxlen = m.outt.len 111 | debugEcho "a: ", result, " a mod prev ", (result mod prev), " lenout: ", maxlen 112 | prev = result 113 | inc result 114 | reset(m, result) 115 | 116 | func part2(m: Machine, start=1): int = 117 | result = start 118 | var m = m 119 | var maxlen = 0 120 | var prev = result 121 | var prevMod = 0 122 | var prevMods = @[8, 255] 123 | var resultPlusMod = 0 124 | var strategyPrevMod = false 125 | reset(m, result) 126 | block outer: 127 | while not quine(m): 128 | if not strategyPrevMod: 129 | if m.outt.len >= maxlen: 130 | maxlen = m.outt.len 131 | prevMod = (result mod prev) 132 | debugEcho "a: ", result, " a mod prev ", prevMod, " lenout: ", maxlen 133 | if prevMod > 700 and prevMod notIn prevMods: 134 | debugEcho "NEW mod: ", prevMod, " len prevMods: ", len(prevMods), " prevMods: ", prevMods 135 | prevMods.add prevMod 136 | prevMods = reversed(sorted(prevMods)) 137 | prev = result 138 | inc result 139 | reset(m, result) 140 | strategyPrevMod = true 141 | else: 142 | #debugecho "testing prevModes ", len(prevMods) 143 | for plusMod in prevMods: 144 | if plusMod < 8: 145 | continue 146 | resultPlusMod = result + plusMod 147 | reset(m, resultPlusMod) 148 | if quine(m): 149 | result = resultPlusMod 150 | break outer 151 | if m.outt.len >= maxlen: 152 | maxlen = m.outt.len 153 | prevMod = (result mod prev) 154 | debugEcho "a: ", result, " a mod prev ", prevMod, " PREVMOD! lenout: ", maxlen 155 | if prevMod > 700 and prevMod notIn prevMods: 156 | debugEcho "NEW mod: ", prevMod, " len prevMods: ", len(prevMods), " prevMods: ", prevMods 157 | prevMods.add prevMod 158 | prevMods = reversed(sorted(prevMods)) 159 | prev = result 160 | break 161 | inc result 162 | reset(m, result) 163 | strategyPrevMod = false 164 | 165 | 166 | let m1 = Machine( 167 | a: 2024, 168 | b: 0, 169 | c: 0, 170 | prog: @[0,3,5,4,3,0], 171 | pnt: 0 172 | ) 173 | #echo m1.part2 # ok 117440 174 | echo inp.part2(3287451) 175 | #[ 176 | a: 0 lenout: 1 177 | a: 10 lenout: 2 178 | a: 778 lenout: 4 179 | a: 10651 lenout: 5 180 | a: 62218 lenout: 6 181 | a: 3287451 lenout: 8 182 | a: 137505179 lenout: 9 183 | a: 1763797403 lenout: 10 184 | a: 8328317084 lenout: 11 185 | a: 42688055452 lenout: 12 186 | 187 | a: 8328317083 a mod prev 0 lenout: 11 188 | a: 8328317338 a mod prev 255 lenout: 11 189 | a: 8328318721 a mod prev 1383 lenout: 11 190 | a: 8328318729 a mod prev 8 lenout: 11 191 | a: 8328980890 a mod prev 662161 lenout: 11 192 | a: 8329079194 a mod prev 98304 lenout: 11 193 | 194 | a: 42688055451 a mod prev 0 lenout: 12 195 | a: 42688055706 a mod prev 255 lenout: 12 196 | a: 42688057089 a mod prev 1383 lenout: 12 197 | a: 42688057097 a mod prev 8 lenout: 12 198 | a: 42688719258 a mod prev 662161 lenout: 12 199 | a: 42688817562 a mod prev 98304 lenout: 12 200 | 201 | ]# -------------------------------------------------------------------------------- /day16.nim: -------------------------------------------------------------------------------- 1 | import std / [sets, strutils, heapqueue, tables] 2 | when defined(dbg): 3 | import sugar 4 | 5 | type 6 | Vec = tuple[x, y: int] 7 | Dir = enum 8 | East, South, West, North 9 | Map = object 10 | w, h: int 11 | start, fin: Vec 12 | path: HashSet[Vec] 13 | 14 | func parse(text: string): Map = 15 | let lines = text.split('\n') 16 | result.h = lines.len 17 | result.w = lines[0].len 18 | for y in 0 ..< result.h: 19 | for x in 0 ..< result.w: 20 | case lines[y][x] 21 | of '.': 22 | result.path.incl (x, y) 23 | of 'S': 24 | result.path.incl (x, y) 25 | result.start = (x, y) 26 | of 'E': 27 | result.path.incl (x, y) 28 | result.fin = (x, y) 29 | else: 30 | discard 31 | 32 | let map0 = """ 33 | ############### 34 | #.......#....E# 35 | #.#.###.#.###.# 36 | #.....#.#...#.# 37 | #.###.#####.#.# 38 | #.#.#.......#.# 39 | #.#.#####.###.# 40 | #...........#.# 41 | ###.#.#####.#.# 42 | #...#.....#.#.# 43 | #.#.#.###.#.#.# 44 | #.....#...#.#.# 45 | #.###.#.#.#.#.# 46 | #S..#.....#...# 47 | ###############""".parse 48 | 49 | type 50 | Node = ref object 51 | pos: Vec 52 | dir: Dir 53 | dist: int 54 | Move = object 55 | next: Node 56 | cost: int 57 | 58 | proc `<`(a, b: Node): bool = a.dist < b.dist 59 | 60 | func `+`(v: Vec, d: Dir): Vec = 61 | case d 62 | of North: 63 | (v.x, v.y - 1) 64 | of South: 65 | (v.x, v.y + 1) 66 | of West: 67 | (v.x - 1, v.y) 68 | of East: 69 | (v.x + 1, v.y) 70 | 71 | func movesFrom(m: Map, n: Node): seq[Move] = 72 | for dir in Dir: 73 | let nextPos = (n.pos + dir) 74 | if nextPos in m.path: 75 | let cost = if dir == n.dir: 1 else: 1001 76 | let next = Node(pos: nextPos, dir: dir, dist: n.dist + cost) 77 | result.add Move(next: next, cost: cost) 78 | 79 | func withDist(n: Node, d: int): Node = 80 | result = n 81 | result.dist = d 82 | 83 | func part1(map: Map): int = 84 | # dijkstra 85 | let start = Node(pos: map.start, dir: East, dist: 0) 86 | var q = initHeapQueue[Node]() 87 | var dist = initTable[Vec, int]() 88 | var visited = initHashSet[Vec]() 89 | q.push start 90 | while q.len > 0: 91 | let here = q.pop() 92 | if here.pos in visited: 93 | continue 94 | visited.incl here.pos 95 | 96 | if here.pos == map.fin: 97 | break 98 | 99 | for move in map.movesFrom(here): 100 | let newDist = here.dist + move.cost 101 | if move.next.pos notIn dist or newDist < dist[move.next.pos]: 102 | dist[move.next.pos] = newDist 103 | q.push(move.next.withDist(newDist)) 104 | 105 | return dist[map.fin] 106 | 107 | echo part1(map0) # ok 7036 108 | 109 | let map1 = """ 110 | ################# 111 | #...#...#...#..E# 112 | #.#.#.#.#.#.#.#.# 113 | #.#.#.#...#...#.# 114 | #.#.#.#.###.#.#.# 115 | #...#.#.#.....#.# 116 | #.#.#.#.#.#####.# 117 | #.#...#.#.#.....# 118 | #.#.#####.#.###.# 119 | #.#.#.......#...# 120 | #.#.###.#####.### 121 | #.#.#...#.....#.# 122 | #.#.#.#####.###.# 123 | #.#.#.........#.# 124 | #.#.#.#########.# 125 | #S#.............# 126 | #################""".parse 127 | 128 | echo part1(map1) # ok 7036 129 | echo "input/16.txt".readFile.parse.part1 130 | 131 | type 132 | PrevTable = Table[Vec, seq[Vec]] 133 | 134 | template dbge(arg: untyped) = 135 | when defined(dbg): 136 | debugEcho arg 137 | 138 | func dijkstraPrev(map: Map): PrevTable = 139 | # dijkstra modified to track path and explore the whole maze 140 | let start = Node(pos: map.start, dir: East, dist: 0) 141 | var q = initHeapQueue[Node]() 142 | var dist = initTable[Vec, int]() 143 | var prev = initTable[Vec, seq[Vec]]() 144 | q.push start 145 | while q.len > 0: 146 | let here = q.pop() 147 | debugEcho here.pos 148 | if here.pos == map.fin: 149 | continue 150 | 151 | for move in map.movesFrom(here): 152 | let newDist = here.dist + move.cost 153 | if move.next.pos notIn dist or newDist < dist[move.next.pos]: 154 | dist[move.next.pos] = newDist 155 | q.push(move.next.withDist(newDist)) 156 | prev[move.next.pos] = @[here.pos] 157 | elif newDist == dist[move.next.pos]: 158 | prev[move.next.pos].add @[here.pos] 159 | 160 | return prev 161 | 162 | proc bestPaths(map: Map): HashSet[Vec] = 163 | let prevTable = map.dijkstraPrev 164 | var bestPaths = initHashSet[Vec]() 165 | var stack = @[map.fin] 166 | while stack.len > 0: 167 | let now = stack.pop() 168 | if now in bestPaths: 169 | continue 170 | bestPaths.incl now 171 | if now notIn prevTable: 172 | continue 173 | for prev in prevTable[now]: 174 | stack.add prev 175 | return bestPaths 176 | 177 | func part2(map: Map): int = 178 | len(map.bestPaths) 179 | 180 | func dbgPrevTable(map: Map): string = 181 | let prevTable = map.dijkstraPrev 182 | for y in 0 ..< map.h: 183 | for x in 0 ..< map.w: 184 | if (x, y) in map.path: 185 | if (x,y) in prevTable: 186 | if len(prevTable[(x, y)]) > 1: 187 | result.add '*' 188 | else: 189 | let w = (x, y) 190 | let v = prevTable[(x, y)][0] 191 | let c = block: 192 | if v + North == w: 193 | '^' 194 | elif v + South == w: 195 | 'v' 196 | elif v + East == w: 197 | '>' 198 | elif v + West == w: 199 | '<' 200 | else: 201 | '?' 202 | result.add c 203 | else: 204 | result.add '.' 205 | else: 206 | result.add '#' 207 | result.add '\n' 208 | 209 | func dbgBestPaths(map: Map): string = 210 | let bestPaths = map.bestPaths 211 | for y in 0 ..< map.h: 212 | for x in 0 ..< map.w: 213 | if (x, y) in map.path: 214 | if (x,y) in bestPaths: 215 | result.add 'O' 216 | else: 217 | result.add '.' 218 | else: 219 | result.add '#' 220 | result.add '\n' 221 | 222 | echo dbgPrevtable(map0) 223 | #echo dbgBestPaths(map0) 224 | #echo dbgBestPaths(map1) 225 | 226 | #echo part2(map0) 227 | #echo part2(map1) 228 | #echo "input/16.txt".readFile.parse.part2 229 | -------------------------------------------------------------------------------- /day15.nim: -------------------------------------------------------------------------------- 1 | import std / [tables, strutils, algorithm] 2 | import test / t15 3 | 4 | type 5 | Vec = tuple[x, y: int] 6 | Map = object 7 | w, h: int 8 | data: Table[Vec, char] 9 | robot: Vec 10 | Dir = enum 11 | Up = "^", Down = "V", Left = "<", Right = ">" 12 | Whouse = object 13 | map: Map 14 | dirs: seq[Dir] 15 | t: int 16 | 17 | 18 | const 19 | wall = '#' 20 | robot = '@' 21 | box = 'O' 22 | 23 | # another odd example of something where LSP says it has side effects when it doesn't (compiler does not complain) 24 | func parseMap(text: string): Map = 25 | let lines = text.split('\n') 26 | result.h = len lines 27 | result.w = len lines[0] 28 | for y in 0 ..< result.h: 29 | for x in 0 ..< result.w: 30 | if lines[y][x] in [wall, box]: 31 | result.data[(x, y)] = lines[y][x] 32 | elif lines[y][x] == robot: 33 | result.robot = (x, y) 34 | 35 | func `[]`(m: Map, v: Vec): char = 36 | if v == m.robot: 37 | robot 38 | elif v in m.data: 39 | m.data[v] 40 | else: 41 | '.' 42 | 43 | func `$`(map: Map): string = 44 | for y in 0 ..< map.h: 45 | for x in 0 ..< map.w: 46 | result.add map[(x, y)] 47 | result.add '\n' 48 | 49 | func `$`(w: Whouse): string = 50 | if w.t == 0: 51 | result.add "Initial state:\n" 52 | else: 53 | result.add "Move " & $(w.dirs[w.t - 1]) & ":\n" 54 | result.add $w.map 55 | 56 | func parseDir(text: string): seq[Dir] = 57 | for c in text: 58 | if c == '^': 59 | result.add Up 60 | elif c == 'v': 61 | result.add Down 62 | elif c == '<': 63 | result.add Left 64 | elif c == '>': 65 | result.add Right 66 | 67 | func parse(text: string): Whouse = 68 | let two = text.split("\n\n") 69 | result.map = two[0].parseMap 70 | result.dirs = two[1].parseDir 71 | 72 | func `+`(v: Vec, d: Dir): Vec = 73 | case d 74 | of Up: 75 | (v.x, v.y - 1) 76 | of Down: 77 | (v.x, v.y + 1) 78 | of Left: 79 | (v.x - 1, v.y) 80 | of Right: 81 | (v.x + 1, v.y) 82 | 83 | # algorithm to move the robot 84 | # if there is something blocking the robot 85 | # try move that something 86 | # if there is something else blocking something 87 | # try move something else 88 | 89 | proc doMove(w: var Whouse, v: Vec, d: Dir) = 90 | if v == w.map.robot: 91 | w.map.robot = v + d 92 | elif v in w.map.data: 93 | w.map.data[v + d] = w.map.data[v] 94 | w.map.data.del v 95 | 96 | proc tryMove(w: var Whouse, v: Vec, d: Dir, again=false): bool = 97 | if (v + d) in w.map.data: 98 | if w.map.data[v + d] != wall and not again: 99 | if not tryMove(w, v+d, d): 100 | discard tryMove(w, v+d, d, true) 101 | else: 102 | doMove(w, v, d) 103 | result = true 104 | 105 | proc tick(w: var Whouse) = 106 | if not tryMove(w, w.map.robot, w.dirs[w.t]): 107 | discard tryMove(w, w.map.robot, w.dirs[w.t], again=true) 108 | inc w.t 109 | 110 | func gps(v: Vec): int = 111 | 100*v.y + v.x 112 | 113 | func part1(w: Whouse, verbose=false): int = 114 | var w = w 115 | if verbose: 116 | debugecho w 117 | while w.t < w.dirs.len: 118 | tick w 119 | if verbose: 120 | debugecho w 121 | for v, c in w.map.data: 122 | if c == box: 123 | result.inc gps(v) 124 | 125 | 126 | when defined(part1): 127 | let w0 = t0.parse 128 | let w1 = t1.parse 129 | let inp = "input/15.txt".readFile.parse 130 | discard part1(w0, true) 131 | echo part1(w1) 132 | echo part1(inp) 133 | 134 | func parseMap2(text: string): Map = 135 | let lines = text.split('\n') 136 | result.h = lines.len 137 | result.w = 2*lines[0].len 138 | for y in 0 ..< result.h: 139 | for x in 0 ..< (result.w div 2): 140 | if lines[y][x] == wall: 141 | result.data[(2*x, y)] = '#' 142 | result.data[(2*x + 1, y)] = '#' 143 | elif lines[y][x] == box: 144 | result.data[(2*x, y)] = '[' 145 | result.data[(2*x + 1, y)] = ']' 146 | elif lines[y][x] == robot: 147 | result.robot = (2*x, y) 148 | 149 | func parse2(text: string): Whouse = 150 | let two = text.split("\n\n") 151 | result.map = two[0].parseMap2 152 | result.dirs = two[1].parseDir 153 | 154 | func stuck(m: Map, v: Vec): bool = 155 | m[v] == '#' 156 | 157 | func next(m: Map, v: Vec, d: Dir): seq[Vec] = 158 | let u = v + d 159 | if u in m.data: 160 | result.add u 161 | if m.data[u] == '[': 162 | result.add u + Right 163 | elif m.data[u] == ']': 164 | result.add u + Left 165 | 166 | proc sort1(u, v: Vec): int = 167 | cmp(u.y, v.y) 168 | 169 | proc sort2(u, v: Vec): int = 170 | cmp(v.y, u.y) 171 | 172 | func dfs(map: Map, v: Vec, d: Dir): seq[Vec] = 173 | assert d in [Up, Down] 174 | var stack = @[v] 175 | while stack.len > 0: 176 | let now = stack.pop 177 | result.add now 178 | for next in map.next(now, d): 179 | if map.stuck(next): 180 | return @[] 181 | stack.add next 182 | if d == Up: 183 | result.sort(sort2) 184 | else: 185 | result.sort(sort1) 186 | 187 | proc doMove2(w: var Whouse, v: Vec) = 188 | let d = w.dirs[w.t] 189 | assert d in [Up, Down] 190 | if v == w.map.robot: 191 | w.map.robot = v + d 192 | elif v in w.map.data: 193 | w.map.data[v + d] = w.map.data[v] 194 | w.map.data.del v 195 | if w.map[v] == '[': 196 | w.map.data[v + d + Right] = w.map.data[v + Right] 197 | w.map.data.del (v + Right) 198 | elif w.map[v] == ']': 199 | w.map.data[v + d + Left] = w.map.data[v + Left] 200 | w.map.data.del (v + Left) 201 | 202 | proc tick2(w: var Whouse) = 203 | var moves = dfs(w.map, w.map.robot, w.dirs[w.t]) 204 | while moves.len > 0: 205 | doMove2(w, moves.pop()) 206 | inc w.t 207 | 208 | func gps2(v: Vec): int = 209 | 100*v.y + v.x 210 | 211 | func part2(w: Whouse, verbose=false): int = 212 | var w = w 213 | if verbose: 214 | debugecho w 215 | while w.t < w.dirs.len: 216 | if w.dirs[w.t] in [Left, Right]: 217 | tick w 218 | else: 219 | tick2 w 220 | if verbose: 221 | debugecho w 222 | for v, c in w.map.data: 223 | if c == '[': 224 | result.inc gps(v) 225 | 226 | when not defined(part1): 227 | let w1 = t1.parse2 228 | let inp = "input/15.txt".readFile.parse2 229 | #echo w1.map 230 | let w2 = t2.parse2 231 | #echo w2.map 232 | #echo part1(w2, true) # fun that part1 works although it scrambles boxes 233 | #echo part2(w2, true) 234 | echo part2(w1) 235 | echo part2(inp) 236 | --------------------------------------------------------------------------------