From d2d5c5520b2c43ad92b9295424af8d84ad58a6fd Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 7 Jan 2025 11:42:40 -0800 Subject: [PATCH] Solutions for advent of code, day 4 - 13. (#4750) A collection of additional Carbon examples demonstrating current language capabilities and some limitations of the current state of the toolchain. --------- Co-authored-by: Jon Ross-Perkins --- examples/advent2024/BUILD | 63 +++------- examples/advent2024/README.md | 6 + examples/advent2024/day10_common.carbon | 29 +++++ examples/advent2024/day10_part1.carbon | 99 +++++++++++++++ examples/advent2024/day10_part2.carbon | 72 +++++++++++ examples/advent2024/day11_common.carbon | 31 +++++ examples/advent2024/day11_part1.carbon | 20 +++ examples/advent2024/day11_part2.carbon | 106 ++++++++++++++++ examples/advent2024/day12_common.carbon | 120 ++++++++++++++++++ examples/advent2024/day12_part1.carbon | 30 +++++ examples/advent2024/day12_part2.carbon | 69 +++++++++++ examples/advent2024/day13_common.carbon | 156 ++++++++++++++++++++++++ examples/advent2024/day13_part1.carbon | 22 ++++ examples/advent2024/day13_part2.carbon | 24 ++++ examples/advent2024/day1_common.carbon | 2 + examples/advent2024/day1_part1.carbon | 2 + examples/advent2024/day1_part2.carbon | 2 + examples/advent2024/day2_common.carbon | 2 + examples/advent2024/day2_part1.carbon | 2 + examples/advent2024/day2_part2.carbon | 2 + examples/advent2024/day3_common.carbon | 2 + examples/advent2024/day3_part1.carbon | 2 + examples/advent2024/day3_part2.carbon | 2 + examples/advent2024/day4_common.carbon | 58 +++++++++ examples/advent2024/day4_part1.carbon | 38 ++++++ examples/advent2024/day4_part2.carbon | 33 +++++ examples/advent2024/day5_common.carbon | 139 +++++++++++++++++++++ examples/advent2024/day5_part1.carbon | 26 ++++ examples/advent2024/day5_part2.carbon | 31 +++++ examples/advent2024/day6_common.carbon | 110 +++++++++++++++++ examples/advent2024/day6_part1.carbon | 16 +++ examples/advent2024/day6_part2.carbon | 63 ++++++++++ examples/advent2024/day7_common.carbon | 58 +++++++++ examples/advent2024/day7_part1.carbon | 22 ++++ examples/advent2024/day7_part2.carbon | 22 ++++ examples/advent2024/day8_common.carbon | 29 +++++ examples/advent2024/day8_part1.carbon | 52 ++++++++ examples/advent2024/day8_part2.carbon | 76 ++++++++++++ examples/advent2024/day9_common.carbon | 137 +++++++++++++++++++++ examples/advent2024/day9_part1.carbon | 16 +++ examples/advent2024/day9_part2.carbon | 16 +++ examples/advent2024/io_utils.carbon | 77 ++++++++++++ 42 files changed, 1838 insertions(+), 46 deletions(-) create mode 100644 examples/advent2024/day10_common.carbon create mode 100644 examples/advent2024/day10_part1.carbon create mode 100644 examples/advent2024/day10_part2.carbon create mode 100644 examples/advent2024/day11_common.carbon create mode 100644 examples/advent2024/day11_part1.carbon create mode 100644 examples/advent2024/day11_part2.carbon create mode 100644 examples/advent2024/day12_common.carbon create mode 100644 examples/advent2024/day12_part1.carbon create mode 100644 examples/advent2024/day12_part2.carbon create mode 100644 examples/advent2024/day13_common.carbon create mode 100644 examples/advent2024/day13_part1.carbon create mode 100644 examples/advent2024/day13_part2.carbon create mode 100644 examples/advent2024/day4_common.carbon create mode 100644 examples/advent2024/day4_part1.carbon create mode 100644 examples/advent2024/day4_part2.carbon create mode 100644 examples/advent2024/day5_common.carbon create mode 100644 examples/advent2024/day5_part1.carbon create mode 100644 examples/advent2024/day5_part2.carbon create mode 100644 examples/advent2024/day6_common.carbon create mode 100644 examples/advent2024/day6_part1.carbon create mode 100644 examples/advent2024/day6_part2.carbon create mode 100644 examples/advent2024/day7_common.carbon create mode 100644 examples/advent2024/day7_part1.carbon create mode 100644 examples/advent2024/day7_part2.carbon create mode 100644 examples/advent2024/day8_common.carbon create mode 100644 examples/advent2024/day8_part1.carbon create mode 100644 examples/advent2024/day8_part2.carbon create mode 100644 examples/advent2024/day9_common.carbon create mode 100644 examples/advent2024/day9_part1.carbon create mode 100644 examples/advent2024/day9_part2.carbon diff --git a/examples/advent2024/BUILD b/examples/advent2024/BUILD index 6e709b913042..c9dc82cce0b0 100644 --- a/examples/advent2024/BUILD +++ b/examples/advent2024/BUILD @@ -9,50 +9,21 @@ utils = [ "sort.carbon", ] -carbon_binary( - name = "day1_part1", - srcs = [ - "day1_common.carbon", - "day1_part1.carbon", - ] + utils, -) +# A list of examples that should be excluded because they don't build any more. +# For example, to exclude day3_part2, use: +# excluded = [(3, 2)] +excluded = [] -carbon_binary( - name = "day1_part2", - srcs = [ - "day1_common.carbon", - "day1_part2.carbon", - ] + utils, -) - -carbon_binary( - name = "day2_part1", - srcs = [ - "day2_common.carbon", - "day2_part1.carbon", - ] + utils, -) - -carbon_binary( - name = "day2_part2", - srcs = [ - "day2_common.carbon", - "day2_part2.carbon", - ] + utils, -) - -carbon_binary( - name = "day3_part1", - srcs = [ - "day3_common.carbon", - "day3_part1.carbon", - ] + utils, -) - -carbon_binary( - name = "day3_part2", - srcs = [ - "day3_common.carbon", - "day3_part2.carbon", - ] + utils, -) +# Produce a binary "dayX_partY" for each matching `.carbon` file. +[ + carbon_binary( + name = "day{0}_part{1}".format(day, part), + srcs = [ + "day{0}_common.carbon".format(day), + "day{0}_part{1}.carbon".format(day, part), + ] + utils, + ) + for day in range(1, 14) + for part in range(1, 3) + if (day, part) not in excluded +] diff --git a/examples/advent2024/README.md b/examples/advent2024/README.md index f7bdef6fbd35..31d2f8d8dcb9 100644 --- a/examples/advent2024/README.md +++ b/examples/advent2024/README.md @@ -15,3 +15,9 @@ style and idioms. Instead, the purpose of these examples are to test the current state of the toolchain against larger code examples than those that are present in the toolchain's own tests, to find bugs in the toolchain, and to drive feature development in the toolchain by presenting somewhat realistic testcases. + +If one of these examples stops building after a change to the toolchain, please: + +- Make sure that the build break is an expected consequence of the change. +- Update the `BUILD` file to exclude that example. +- File an issue and assign it to @zygoloid. diff --git a/examples/advent2024/day10_common.carbon b/examples/advent2024/day10_common.carbon new file mode 100644 index 000000000000..d2c40d9af10c --- /dev/null +++ b/examples/advent2024/day10_common.carbon @@ -0,0 +1,29 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/10 + +library "day10_common"; + +import Core library "io"; +import library "io_utils"; + +class Terrain { + fn Read() -> Terrain { + returned var me: Terrain; + var y: i32 = 0; + while (y < 43) { + var x: i32 = 0; + while (x < 43) { + me.height[x][y] = ReadChar() - 0x30; + ++x; + } + SkipNewline(); + ++y; + } + return var; + } + + var height: [[i32; 43]; 43]; +} diff --git a/examples/advent2024/day10_part1.carbon b/examples/advent2024/day10_part1.carbon new file mode 100644 index 000000000000..33877ade6b6e --- /dev/null +++ b/examples/advent2024/day10_part1.carbon @@ -0,0 +1,99 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/10 + +import Core library "io"; + +import library "day10_common"; +import library "io_utils"; + +// TODO: Add this to the prelude. +fn PopCount(n: u256) -> i32 { + var bit: u256 = 1; + var total: i32 = 0; + while (bit != 0) { + if (n & bit != 0) { + ++total; + } + bit = bit << 1; + } + return total; +} + +class Reachable { + fn Make(terrain: Terrain) -> Reachable { + returned var me: Reachable; + var next: u256 = 1; + var y: i32 = 0; + while (y < 43) { + var x: i32 = 0; + while (x < 43) { + if (terrain.height[x][y] == 0) { + me.trailheads[x][y] = next; + next = next << 1; + } + ++x; + } + ++y; + } + return var; + } + + fn AddLevel[addr self: Self*](terrain: Terrain, level: i32) { + let adj: [(i32, i32); 4] = ((-1, 0), (0, -1), (1, 0), (0, 1)); + var y: i32 = 0; + while (y < 43) { + var x: i32 = 0; + while (x < 43) { + if (terrain.height[x][y] == level) { + var reach: u256 = 0; + var i: i32 = 0; + while (i < 4) { + let adj_x: i32 = x + adj[i].0; + let adj_y: i32 = y + adj[i].1; + if (adj_x >= 0 and adj_x < 43 and + adj_y >= 0 and adj_y < 43 and + terrain.height[adj_x][adj_y] == level - 1) { + reach = reach | self->trailheads[adj_x][adj_y]; + } + ++i; + } + self->trailheads[x][y] = reach; + } + ++x; + } + ++y; + } + } + + fn Count[self: Self](terrain: Terrain, level: i32) -> i32 { + var total: i32 = 0; + var y: i32 = 0; + while (y < 43) { + var x: i32 = 0; + while (x < 43) { + if (terrain.height[x][y] == level) { + total += PopCount(self.trailheads[x][y]); + } + ++x; + } + ++y; + } + return total; + } + + var trailheads: [[u256; 43]; 43]; +} + +fn Run() { + var terrain: Terrain = Terrain.Read(); + var reachable: Reachable = Reachable.Make(terrain); + var i: i32 = 1; + while (i <= 9) { + reachable.AddLevel(terrain, i); + ++i; + } + Core.Print(reachable.Count(terrain, 9)); +} diff --git a/examples/advent2024/day10_part2.carbon b/examples/advent2024/day10_part2.carbon new file mode 100644 index 000000000000..0ac9731b524d --- /dev/null +++ b/examples/advent2024/day10_part2.carbon @@ -0,0 +1,72 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/10 + +import Core library "io"; + +import library "day10_common"; +import library "io_utils"; + +class PathsToTop { + fn Make(terrain: Terrain) -> PathsToTop { + returned var me: PathsToTop; + var y: i32 = 0; + while (y < 43) { + var x: i32 = 0; + while (x < 43) { + // TODO: We shouldn't need an explicit cast here. + me.paths[x][y] = + if terrain.height[x][y] == 9 then 1 as i64 else 0; + ++x; + } + ++y; + } + return var; + } + + fn AddLevel[addr self: Self*](terrain: Terrain, level: i32) -> i64 { + var total: i64 = 0; + let adj: [(i32, i32); 4] = ((-1, 0), (0, -1), (1, 0), (0, 1)); + var y: i32 = 0; + while (y < 43) { + var x: i32 = 0; + while (x < 43) { + if (terrain.height[x][y] == level) { + var paths: i64 = 0; + var i: i32 = 0; + while (i < 4) { + let adj_x: i32 = x + adj[i].0; + let adj_y: i32 = y + adj[i].1; + if (adj_x >= 0 and adj_x < 43 and + adj_y >= 0 and adj_y < 43 and + terrain.height[adj_x][adj_y] == level + 1) { + paths = paths + self->paths[adj_x][adj_y]; + } + ++i; + } + self->paths[x][y] = paths; + total = total + paths; + } + ++x; + } + ++y; + } + return total; + } + + var paths: [[i64; 43]; 43]; +} + +fn Run() { + var terrain: Terrain = Terrain.Read(); + var paths: PathsToTop = PathsToTop.Make(terrain); + var i: i32 = 8; + var total: i64; + while (i >= 0) { + total = paths.AddLevel(terrain, i); + --i; + } + PrintInt64(total); +} diff --git a/examples/advent2024/day11_common.carbon b/examples/advent2024/day11_common.carbon new file mode 100644 index 000000000000..8d55fcce6fe4 --- /dev/null +++ b/examples/advent2024/day11_common.carbon @@ -0,0 +1,31 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/11 + +library "day11_common"; + +import Core library "io"; +import library "io_utils"; + +fn Next(n: i64) -> (i64, i64) { + if (n == 0) { return (1, -1); } + var pow10: i64 = 10; + var pow100: i64 = 1; + while (n / pow100 >= 100) { + pow100 = pow100 * 100; + pow10 = pow10 * 10; + } + if (n / pow100 >= 10) { + return (n / pow10, n % pow10); + } + return (n * 2024, -1); +} + +fn Count(n: i64, depth: i32) -> i32 { + if (n == -1) { return 0; } + if (depth == 0) { return 1; } + let next: (i64, i64) = Next(n); + return Count(next.0, depth - 1) + Count(next.1, depth - 1); +} diff --git a/examples/advent2024/day11_part1.carbon b/examples/advent2024/day11_part1.carbon new file mode 100644 index 000000000000..ef98253c64cf --- /dev/null +++ b/examples/advent2024/day11_part1.carbon @@ -0,0 +1,20 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/11 + +import Core library "io"; + +import library "day11_common"; +import library "io_utils"; + +fn Run() { + var n: i64; + var total: i32 = 0; + while (ReadInt64(&n)) { + total = total + Count(n, 25); + SkipSpaces(); + } + Core.Print(total); +} diff --git a/examples/advent2024/day11_part2.carbon b/examples/advent2024/day11_part2.carbon new file mode 100644 index 000000000000..ea359d7ecdd0 --- /dev/null +++ b/examples/advent2024/day11_part2.carbon @@ -0,0 +1,106 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/11 + +import Core library "io"; + +import library "day11_common"; +import library "io_utils"; + +class Digits { + fn Make() -> Digits { + returned var me: Digits; + var digit: i32 = 0; + while (digit < 10) { + var depth: i32 = 0; + while (depth < 75) { + me.count[digit][depth] = 0; + ++depth; + } + ++digit; + } + return var; + } + + fn Print[self: Self](max_depth: i32) { + var digit: i32 = 0; + while (digit < 10) { + Core.PrintChar(digit + 0x30); + Core.PrintChar(0x3A); + var depth: i32 = 0; + while (depth <= max_depth) { + Core.PrintChar(0x20); + PrintInt64NoNewline(self.count[digit][depth]); + ++depth; + } + Core.PrintChar(0x0A); + ++digit; + } + Core.PrintChar(0x0A); + } + + var count: [[i64; 75]; 10]; +} + +// TODO: Add a builtin to perform integer conversion / truncation. +fn I64DigitToI32(a: i64) -> i32 { + if (a == 0) { return 0; } + if (a == 1) { return 1; } + if (a == 2) { return 2; } + if (a == 3) { return 3; } + if (a == 4) { return 4; } + if (a == 5) { return 5; } + if (a == 6) { return 6; } + if (a == 7) { return 7; } + if (a == 8) { return 8; } + return 9; +} + +fn ReduceToDigits(n: i64, depth: i32, multiplicity: i64, digits: Digits*) -> i64 { + if (n == -1) { return 0; } + if (depth == 0) { return multiplicity; } + if (n < 10) { + let count: i64* = &digits->count[I64DigitToI32(n)][depth - 1]; + *count = *count + multiplicity; + return 0; + } + let next: (i64, i64) = Next(n); + return ReduceToDigits(next.0, depth - 1, multiplicity, digits) + + ReduceToDigits(next.1, depth - 1, multiplicity, digits); +} + +fn Run() { + let max_depth: i32 = 75; + var total: i64 = 0; + var digits: Digits = Digits.Make(); + + var n: i64; + while (ReadInt64(&n)) { + total = total + ReduceToDigits(n, max_depth, 1, &digits); + PrintInt64(total); + digits.Print(max_depth - 1); + SkipSpaces(); + } + + var depth: i32 = max_depth - 1; + while (depth >= 0) { + PrintInt64(total); + digits.Print(depth); + var digit: i64 = 0; + while (digit < 10) { + let m: i64 = digits.count[I64DigitToI32(digit)][depth]; + if (m > 0) { + let next: (i64, i64) = Next(digit); + total = total + + ReduceToDigits(next.0, depth, m, &digits) + + ReduceToDigits(next.1, depth, m, &digits); + } + digit = digit + 1; + } + --depth; + } + + PrintInt64(total); +} diff --git a/examples/advent2024/day12_common.carbon b/examples/advent2024/day12_common.carbon new file mode 100644 index 000000000000..e1de4a983664 --- /dev/null +++ b/examples/advent2024/day12_common.carbon @@ -0,0 +1,120 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/12 + +library "day12_common"; + +import Core library "io"; +import library "io_utils"; + +class Map { + fn Read() -> Map { + returned var me: Self; + var y: i32 = 0; + while (y < 140) { + var x: i32 = 0; + while (x < 140) { + me.kind[x][y] = ReadChar(); + ++x; + } + SkipNewline(); + ++y; + } + return var; + } + + fn At[self: Self](x: i32, y: i32) -> i32 { + return if x < 0 or x >= 140 or y < 0 or y >= 140 + then -1 + else self.kind[x][y]; + } + + var kind: [[i32; 140]; 140]; +} + +class DisjointSetForest { + fn Make() -> DisjointSetForest { + returned var me: Self; + var i: i32 = 0; + while (i < 140 * 140) { + me.nodes[i].next = i; + me.nodes[i].weight = 1; + me.nodes[i].unions = 0; + ++i; + } + return var; + } + + fn Lookup[addr self: Self*](a: i32) -> i32 { + let next: i32 = self->nodes[a].next; + if (next == a) { + return next; + } + let resolved: i32 = self->Lookup(next); + self->nodes[a].next = resolved; + return resolved; + } + + fn Unions[self: Self](canon_a: i32) -> i32 { + return self.nodes[canon_a].unions; + } + + fn Weight[self: Self](canon_a: i32) -> i32 { + return self.nodes[canon_a].weight; + } + + fn Union[addr self: Self*](a: i32, b: i32) { + let canon_b: i32 = self->Lookup(b); + self->Set(a, canon_b); + ++self->nodes[canon_b].unions; + } + + private fn Set[addr self: Self*](a: i32, canon_b: i32) { + let next: i32 = self->nodes[a].next; + self->nodes[a].next = canon_b; + if (next == a) { + if (a != canon_b) { + self->nodes[canon_b].weight += self->nodes[a].weight; + self->nodes[canon_b].unions += self->nodes[a].unions; + } + } else { + self->Set(next, canon_b); + } + } + + // TODO: Consider adding ranked choice. + // TODO: Make this generic in the payload data. + var nodes: [{.next: i32, .weight: i32, .unions: i32}; 140 * 140]; +} + +fn MakeRegions(map: Map) -> DisjointSetForest { + returned var forest: DisjointSetForest = + DisjointSetForest.Make(); + + var x: i32 = 0; + while (x < 140) { + var y: i32 = 0; + while (y < 140) { + var a: i32 = 0; + while (a < 2) { + // TODO: Crashes toolchain: + // let adj: (i32, i32) = if a == 0 then (x - 1, y) else (x, y - 1); + // if (map.At(adj.0, adj.1) == map.At(x, y)) { + // forest.Union(y * 140 + x, adj.1 * 140 + adj.0); + // } + let adj_x: i32 = if a == 0 then x - 1 else x; + let adj_y: i32 = if a == 0 then y else y - 1; + if (map.At(adj_x, adj_y) == map.At(x, y)) { + forest.Union(y * 140 + x, adj_y * 140 + adj_x); + } + ++a; + } + ++y; + } + ++x; + } + + return var; +} diff --git a/examples/advent2024/day12_part1.carbon b/examples/advent2024/day12_part1.carbon new file mode 100644 index 000000000000..4cc354c0c9f8 --- /dev/null +++ b/examples/advent2024/day12_part1.carbon @@ -0,0 +1,30 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/12 + +import Core library "io"; + +import library "day12_common"; +import library "io_utils"; + +fn Run() { + var map: Map = Map.Read(); + var regions: DisjointSetForest = MakeRegions(map); + + var total: i32 = 0; + + var i: i32 = 0; + while (i < 140 * 140) { + if (regions.Lookup(i) == i) { + let area: i32 = regions.Weight(i); + let internal_edges: i32 = regions.Unions(i); + let perimeter: i32 = area * 4 - internal_edges * 2; + total += area * perimeter; + } + ++i; + } + + Core.Print(total); +} diff --git a/examples/advent2024/day12_part2.carbon b/examples/advent2024/day12_part2.carbon new file mode 100644 index 000000000000..0adb06ce8ed9 --- /dev/null +++ b/examples/advent2024/day12_part2.carbon @@ -0,0 +1,69 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/12 + +import Core library "io"; + +import library "day12_common"; +import library "io_utils"; + +fn CountExtensions(map: Map, regions: DisjointSetForest*) -> [i32; 140 * 140] { + returned var extensions: [i32; 140 * 140]; + var i: i32 = 0; + while (i < 140 * 140) { + extensions[i] = 0; + ++i; + } + + var ext: [{.same: (i32, i32), .adj: (i32, i32)}; 4] = ( + {.same = (-1, 0), .adj = (0, -1)}, + {.same = (-1, 0), .adj = (0, 1)}, + {.same = (0, -1), .adj = (-1, 0)}, + {.same = (0, -1), .adj = (1, 0)}, + ); + + var x: i32 = 0; + while (x < 140) { + var y: i32 = 0; + while (y < 140) { + let kind: i32 = map.At(x, y); + var e: i32 = 0; + while (e < 4) { + if (map.At(x + ext[e].same.0, y + ext[e].same.1) == kind and + map.At(x + ext[e].adj.0, y + ext[e].adj.1) != kind and + map.At(x + ext[e].same.0 + ext[e].adj.0, + y + ext[e].same.1 + ext[e].adj.1) != kind) { + ++extensions[regions->Lookup(y * 140 + x)]; + } + ++e; + } + ++y; + } + ++x; + } + return var; +} + +fn Run() { + var map: Map = Map.Read(); + var regions: DisjointSetForest = MakeRegions(map); + var ext: [i32; 140 * 140] = CountExtensions(map, ®ions); + + var total: i32 = 0; + + var i: i32 = 0; + while (i < 140 * 140) { + if (regions.Lookup(i) == i) { + let area: i32 = regions.Weight(i); + let internal_edges: i32 = regions.Unions(i); + let extensions: i32 = ext[i]; + let perimeter: i32 = area * 4 - internal_edges * 2 - extensions; + total += area * perimeter; + } + ++i; + } + + Core.Print(total); +} diff --git a/examples/advent2024/day13_common.carbon b/examples/advent2024/day13_common.carbon new file mode 100644 index 000000000000..29da4b506daa --- /dev/null +++ b/examples/advent2024/day13_common.carbon @@ -0,0 +1,156 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/13 + +library "day13_common"; + +import Core library "io"; +import library "io_utils"; + +// Returns m and n so that am + bn = gcd. +fn Euclid(a: i64, b: i64) -> {.m: i64, .n: i64, .gcd: i64} { + if (a < b) { + let reverse: {.m: i64, .n: i64, .gcd: i64} = Euclid(b, a); + return {.m = reverse.n, .n = reverse.m, .gcd = reverse.gcd}; + } + if (b == 0) { + return {.m = 1, .n = 0, .gcd = a}; + } + let next: {.m: i64, .n: i64, .gcd: i64} = Euclid(b, a % b); + return {.m = next.n, .n = next.m - next.n * (a / b), .gcd = next.gcd}; +} + +class Machine { + fn Read() -> Machine { + returned var me: Machine; + // "Button A: X+" + SkipNChars(12); + ReadInt64(&me.a.0); + // ", Y+" + SkipNChars(4); + ReadInt64(&me.a.1); + // "\nButton B: X+" + SkipNChars(13); + ReadInt64(&me.b.0); + // ", Y+" + SkipNChars(4); + ReadInt64(&me.b.1); + // "\nPrize: X=" + SkipNChars(10); + ReadInt64(&me.prize.0); + // ", Y=" + SkipNChars(4); + ReadInt64(&me.prize.1); + SkipNewline(); + return var; + } + + var a: (i64, i64); + var b: (i64, i64); + var prize: (i64, i64); +} + +// Set of solutions to 'm a + n b = c'. +class BezoutSolutionSet { + fn Make(a: i64, b: i64, c: i64) -> BezoutSolutionSet { + var e: {.m: i64, .n: i64, .gcd: i64} = Euclid(a, b); + if (c % e.gcd != 0) { + // Impossible. + return {.m0 = -1, .n0 = -1, .m_step = -1, .n_step = -1}; + } + + // Find an initial solution. Note that m and n might be negative. + let num_gcds: i64 = c / e.gcd; + e.m = e.m * num_gcds; + e.n = e.n * num_gcds; + + // Pick the smallest positive m we can. + let a_over_gcd: i64 = a / e.gcd; + let b_over_gcd: i64 = b / e.gcd; + var adj: i64 = 0; + // This is e.m / b rounded towards -inf. + // TODO: Should there be a way of expressing this directly? + if (e.m < 0) { + adj = (e.m - b_over_gcd + 1) / b_over_gcd; + } else { + adj = e.m / b_over_gcd; + } + e.m = e.m - adj * b_over_gcd; + e.n = e.n + adj * a_over_gcd; + return {.m0 = e.m, .n0 = e.n, + .m_step = b_over_gcd, .n_step = -a_over_gcd}; + } + + fn Valid[self: Self]() -> bool { + return self.m_step >= 0; + } + + fn Solution[self: Self](k: i64) -> (i64, i64) { + return (self.m0 + k * self.m_step, self.n0 + k * self.n_step); + } + + // m_k * a + n_k * b == c, where: + // m_k = m0 + k * m_step + // n_k = n0 * k * n_step + // m0 is the minimum non-negative m value, and n_step is negative. + var m0: i64; + var n0: i64; + var m_step: i64; + var n_step: i64; +} + +// Given two sets of points s and t, find the intersection in the first quadrant +// with the minimum x coordinate. Returns the intersection point, or (-1, -1) if +// there is no intersection. +fn FirstIntersection(s: BezoutSolutionSet, t: BezoutSolutionSet) -> (i64, i64) { + if (not s.Valid() or not t.Valid()) { + // One of the sets is empty. + return (-1, -1); + } + + // Easy case: lines meet at a point. This happens unless the lines are + // parallel. + let d: i64 = s.m_step * t.n_step - t.m_step * s.n_step; + if (d != 0) { + let u: i64 = (t.m0 - s.m0) * t.n_step - (t.n0 - s.n0) * t.m_step; + if (u % d != 0) { + // Lines don't meet at an integer point. + return (-1, -1); + } + let j: i64 = u / d; + if (j < 0 or s.n0 + j * s.n_step < 0) { + // Lines don't meet in first quadrant. + return (-1, -1); + } + return s.Solution(j); + } + + // Hard case: lines are parallel. We also get here if either "line" is a + // point, but that doesn't happen in this exercise. We know all integer points + // on the line are solutions, so the first intersection is the greater of the + // first point in s and the first point in t, if that point is on both lines. + if (s.m0 < t.m0) { + // (t.m0, t.n0) is the solution if it's in s. + if ((t.m0 - s.m0) % s.m_step == 0 and + (t.n0 - s.n0) % s.n_step == 0) { + return (t.m0, t.n0); + } + } else { + // (s.m0, s.n0) is the solution if it's in t. + if ((s.m0 - t.m0) % t.m_step == 0 and + (s.n0 - t.n0) % t.n_step == 0) { + return (s.m0, s.n0); + } + } + return (-1, -1); +} + +fn CostIfPossible(m: Machine) -> i64 { + let x: BezoutSolutionSet = BezoutSolutionSet.Make(m.a.0, m.b.0, m.prize.0); + let y: BezoutSolutionSet = BezoutSolutionSet.Make(m.a.1, m.b.1, m.prize.1); + let ab: (i64, i64) = FirstIntersection(x, y); + if (ab.0 == -1) { return 0; } + return ab.0 * 3 + ab.1; +} diff --git a/examples/advent2024/day13_part1.carbon b/examples/advent2024/day13_part1.carbon new file mode 100644 index 000000000000..21bec22b7d99 --- /dev/null +++ b/examples/advent2024/day13_part1.carbon @@ -0,0 +1,22 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/13 + +import Core library "io"; + +import library "day13_common"; +import library "io_utils"; + +fn Run() { + var total_cost: i64 = 0; + while (true) { + var m: Machine = Machine.Read(); + total_cost = total_cost + CostIfPossible(m); + if (not SkipNewline()) { + break; + } + } + PrintInt64(total_cost); +} diff --git a/examples/advent2024/day13_part2.carbon b/examples/advent2024/day13_part2.carbon new file mode 100644 index 000000000000..77e70bd07696 --- /dev/null +++ b/examples/advent2024/day13_part2.carbon @@ -0,0 +1,24 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/13 + +import Core library "io"; + +import library "day13_common"; +import library "io_utils"; + +fn Run() { + var total_cost: i64 = 0; + while (true) { + var m: Machine = Machine.Read(); + m.prize.0 = m.prize.0 + 10000000000000; + m.prize.1 = m.prize.1 + 10000000000000; + total_cost = total_cost + CostIfPossible(m); + if (not SkipNewline()) { + break; + } + } + PrintInt64(total_cost); +} diff --git a/examples/advent2024/day1_common.carbon b/examples/advent2024/day1_common.carbon index b4c1c21295ee..5913081e0c41 100644 --- a/examples/advent2024/day1_common.carbon +++ b/examples/advent2024/day1_common.carbon @@ -2,6 +2,8 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// https://adventofcode.com/2024/day/1 + library "day1_common"; import library "io_utils"; diff --git a/examples/advent2024/day1_part1.carbon b/examples/advent2024/day1_part1.carbon index b1d1f411e8aa..eb1a6ed739ab 100644 --- a/examples/advent2024/day1_part1.carbon +++ b/examples/advent2024/day1_part1.carbon @@ -2,6 +2,8 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// https://adventofcode.com/2024/day/1 + import Core library "io"; import library "day1_common"; diff --git a/examples/advent2024/day1_part2.carbon b/examples/advent2024/day1_part2.carbon index c6521822a8ec..65b98a6b37ad 100644 --- a/examples/advent2024/day1_part2.carbon +++ b/examples/advent2024/day1_part2.carbon @@ -2,6 +2,8 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// https://adventofcode.com/2024/day/1 + import Core library "io"; import library "day1_common"; diff --git a/examples/advent2024/day2_common.carbon b/examples/advent2024/day2_common.carbon index 89ff6d87c0eb..f643cead6589 100644 --- a/examples/advent2024/day2_common.carbon +++ b/examples/advent2024/day2_common.carbon @@ -2,6 +2,8 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// https://adventofcode.com/2024/day/2 + library "day2_common"; fn Abs(n: i32) -> i32 { diff --git a/examples/advent2024/day2_part1.carbon b/examples/advent2024/day2_part1.carbon index 58591d25c0cb..310f734935e9 100644 --- a/examples/advent2024/day2_part1.carbon +++ b/examples/advent2024/day2_part1.carbon @@ -2,6 +2,8 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// https://adventofcode.com/2024/day/2 + import Core library "io"; import library "day2_common"; diff --git a/examples/advent2024/day2_part2.carbon b/examples/advent2024/day2_part2.carbon index bacacd37b346..e085e655c32f 100644 --- a/examples/advent2024/day2_part2.carbon +++ b/examples/advent2024/day2_part2.carbon @@ -2,6 +2,8 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// https://adventofcode.com/2024/day/2 + import Core library "io"; import library "day2_common"; diff --git a/examples/advent2024/day3_common.carbon b/examples/advent2024/day3_common.carbon index 9d54ba54137d..1a5382ed9dcb 100644 --- a/examples/advent2024/day3_common.carbon +++ b/examples/advent2024/day3_common.carbon @@ -2,6 +2,8 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// https://adventofcode.com/2024/day/3 + library "day3_common"; import library "io_utils"; diff --git a/examples/advent2024/day3_part1.carbon b/examples/advent2024/day3_part1.carbon index c6b9dda6188d..0234d2a9dd25 100644 --- a/examples/advent2024/day3_part1.carbon +++ b/examples/advent2024/day3_part1.carbon @@ -2,6 +2,8 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// https://adventofcode.com/2024/day/3 + import Core library "io"; import library "day3_common"; diff --git a/examples/advent2024/day3_part2.carbon b/examples/advent2024/day3_part2.carbon index fd837667d1e9..55ca89f2ea93 100644 --- a/examples/advent2024/day3_part2.carbon +++ b/examples/advent2024/day3_part2.carbon @@ -2,6 +2,8 @@ // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// https://adventofcode.com/2024/day/3 + import Core library "io"; import library "day3_common"; diff --git a/examples/advent2024/day4_common.carbon b/examples/advent2024/day4_common.carbon new file mode 100644 index 000000000000..9f42bd129f3e --- /dev/null +++ b/examples/advent2024/day4_common.carbon @@ -0,0 +1,58 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/4 + +library "day4_common"; + +import library "io_utils"; + +class Wordsearch { + fn Read() -> Wordsearch { + returned var s: Wordsearch; + // TODO: Use for loops once they're implemented. + var y: i32 = 0; + while (y < 140) { + var x: i32 = 0; + while (x < 140) { + // TODO: Assert on failure. + s.grid[x][y] = ReadChar(); + ++x; + } + // TODO: Assert on failure. + SkipNewline(); + ++y; + } + return var; + } + + fn At[self: Self](x: i32, y: i32) -> i32 { + return if x < 0 or x >= 140 or y < 0 or y >= 140 then -1 else self.grid[x][y]; + } + + // TODO: Make this generic in the length of the search query. + fn Check4[self: Self](xmas: [i32; 4], x: i32, y: i32, dx: i32, dy: i32) -> bool { + var i: i32 = 0; + while (i < 4) { + if (self.At(x + i * dx, y + i * dy) != xmas[i]) { + return false; + } + ++i; + } + return true; + } + + fn Check3[self: Self](mas: [i32; 3], x: i32, y: i32, dx: i32, dy: i32) -> bool { + var i: i32 = 0; + while (i < 3) { + if (self.At(x + i * dx, y + i * dy) != mas[i]) { + return false; + } + ++i; + } + return true; + } + + var grid: [[i32; 140]; 140]; +} diff --git a/examples/advent2024/day4_part1.carbon b/examples/advent2024/day4_part1.carbon new file mode 100644 index 000000000000..938f90da0ffd --- /dev/null +++ b/examples/advent2024/day4_part1.carbon @@ -0,0 +1,38 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/4 + +import Core library "io"; + +import library "day4_common"; +import library "io_utils"; + +fn Run() { + var search: Wordsearch = Wordsearch.Read(); + var xmas: [i32; 4] = (0x58, 0x4D, 0x41, 0x53); + var found: i32 = 0; + + // TODO: Use for loops once they're implemented. + var y: i32 = 0; + while (y < 140) { + var x: i32 = 0; + while (x < 140) { + var dy: i32 = -1; + while (dy <= 1) { + var dx: i32 = -1; + while (dx <= 1) { + if (search.Check4(xmas, x, y, dx, dy)) { + ++found; + } + ++dx; + } + ++dy; + } + ++x; + } + ++y; + } + Core.Print(found); +} diff --git a/examples/advent2024/day4_part2.carbon b/examples/advent2024/day4_part2.carbon new file mode 100644 index 000000000000..291a5d69cff7 --- /dev/null +++ b/examples/advent2024/day4_part2.carbon @@ -0,0 +1,33 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/4 + +import Core library "io"; + +import library "day4_common"; +import library "io_utils"; + +fn Run() { + var search: Wordsearch = Wordsearch.Read(); + var mas: [i32; 3] = (0x4D, 0x41, 0x53); + var found: i32 = 0; + + // TODO: Use for loops once they're implemented. + var y: i32 = 1; + while (y < 139) { + var x: i32 = 1; + while (x < 139) { + if ((search.Check3(mas, x - 1, y - 1, 1, 1) or + search.Check3(mas, x + 1, y + 1, -1, -1)) and + (search.Check3(mas, x - 1, y + 1, 1, -1) or + search.Check3(mas, x + 1, y - 1, -1, 1))) { + ++found; + } + ++x; + } + ++y; + } + Core.Print(found); +} diff --git a/examples/advent2024/day5_common.carbon b/examples/advent2024/day5_common.carbon new file mode 100644 index 000000000000..e21d7c6ad2aa --- /dev/null +++ b/examples/advent2024/day5_common.carbon @@ -0,0 +1,139 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/5 + +library "day5_common"; + +import library "io_utils"; + +// fn LeftShift(a: Core.UInt(100), b: i32) -> Core.UInt(100) = "int.left_shift"; +fn LeftShift_u100(a: Core.UInt(100), b: Core.UInt(100)) -> Core.UInt(100) = "int.left_shift"; + +// TODO: Add a builtin that can do this! +fn Promote(a: i32) -> Core.UInt(100) { + var result: Core.UInt(100) = 0; + var bit_i32: i32 = 1; + var bit_u100: Core.UInt(100) = 1; + while (bit_i32 != 0) { + if (a & bit_i32 != 0) { + result = result | bit_u100; + } + bit_i32 <<= 1; + // TODO: bit_u100 <<= 1; + bit_u100 = bit_u100 << 1; + } + return result; +} + +fn LeftShift(a: Core.UInt(100), b: i32) -> Core.UInt(100) { + return LeftShift_u100(a, Promote(b)); +} + +fn PageMask(page: i32) -> Core.UInt(100) { + return LeftShift(1 as Core.UInt(100), page); +} + +class Rules { + fn Read() -> Rules { + returned var rules: Rules; + var i: i32 = 0; + while (i < 100) { + rules.disallowed_before[i] = 0; + ++i; + } + + var a: i32; + var b: i32; + while (ReadInt(&a) and ConsumeChar(0x7C) and ReadInt(&b)) { + // TODO: rules.disallowed_before[a] |= PageMask(b); + rules.disallowed_before[a] = rules.disallowed_before[a] | PageMask(b); + SkipNewline(); + } + return var; + } + + fn IsValidOrder[self: Self](a: i32, b: i32) -> bool { + return self.disallowed_before[b] & PageMask(a) == 0; + } + + var disallowed_before: [Core.UInt(100); 100]; +}; + +class PageList { + fn Empty() -> PageList { + returned var me: PageList; + me.num_pages = 0; + return var; + } + + fn Read() -> PageList { + returned var me: PageList = Empty(); + + var page: i32; + if (not ReadInt(&page)) { + return var; + } + me.Add(page); + while (ConsumeChar(0x2C)) { + ReadInt(&page); + me.Add(page); + } + SkipNewline(); + return var; + } + + fn Add[addr self: Self*](page: i32) { + self->pages[self->num_pages] = page; + ++self->num_pages; + } + + fn FollowsRules[self: Self](rules: Rules) -> bool { + var seen: Core.UInt(100) = 0; + var i: i32 = 0; + while (i < self.num_pages) { + let page: i32 = self.pages[i]; + if (seen & rules.disallowed_before[page] != 0) { + return false; + } + // TODO: seen |= PageMask(page); + seen = seen | PageMask(page); + ++i; + } + return true; + } + + fn IsPossibleFirstPage[self: Self](rules: Rules, page: i32) -> bool { + var i: i32 = 0; + while (i < self.num_pages) { + if (not rules.IsValidOrder(page, self.pages[i])) { + return false; + } + ++i; + } + return true; + } + + fn ExtractPossibleFirstPage[addr self: Self*](rules: Rules) -> i32 { + var i: i32 = 0; + while (i < self->num_pages) { + var page: i32 = self->pages[i]; + if (self->IsPossibleFirstPage(rules, page)) { + self->pages[i] = self->pages[self->num_pages - 1]; + --self->num_pages; + return page; + } + ++i; + } + // TODO: Assert. + return 0; + } + + fn MiddlePage[self: Self]() -> i32 { + return self.pages[self.num_pages / 2]; + } + + var pages: [i32; 24]; + var num_pages: i32; +}; diff --git a/examples/advent2024/day5_part1.carbon b/examples/advent2024/day5_part1.carbon new file mode 100644 index 000000000000..3763d6747d40 --- /dev/null +++ b/examples/advent2024/day5_part1.carbon @@ -0,0 +1,26 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/5 + +import Core library "io"; + +import library "day5_common"; +import library "io_utils"; + +fn Run() { + var rules: Rules = Rules.Read(); + SkipNewline(); + var total: i32 = 0; + while (true) { + var page_list: PageList = PageList.Read(); + if (page_list.num_pages == 0) { + break; + } + if (page_list.FollowsRules(rules)) { + total += page_list.MiddlePage(); + } + } + Core.Print(total); +} diff --git a/examples/advent2024/day5_part2.carbon b/examples/advent2024/day5_part2.carbon new file mode 100644 index 000000000000..ba48e77929dd --- /dev/null +++ b/examples/advent2024/day5_part2.carbon @@ -0,0 +1,31 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/5 + +import Core library "io"; + +import library "day5_common"; +import library "io_utils"; + +fn Run() { + var rules: Rules = Rules.Read(); + SkipNewline(); + var total: i32 = 0; + while (true) { + var page_list: PageList = PageList.Read(); + if (page_list.num_pages == 0) { + break; + } + if (page_list.FollowsRules(rules)) { + continue; + } + var new_page_list: PageList = PageList.Empty(); + while (page_list.num_pages != 0) { + new_page_list.Add(page_list.ExtractPossibleFirstPage(rules)); + } + total += new_page_list.MiddlePage(); + } + Core.Print(total); +} diff --git a/examples/advent2024/day6_common.carbon b/examples/advent2024/day6_common.carbon new file mode 100644 index 000000000000..eb945d2bcd4b --- /dev/null +++ b/examples/advent2024/day6_common.carbon @@ -0,0 +1,110 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/6 + +library "day6_common"; + +import Core library "io"; +import library "io_utils"; + +// TODO: Use a choice type. +fn Empty() -> i8 { return 0; } +fn Visited() -> i8 { return 1; } +fn Wall() -> i8 { return 2; } + +class Maze { + fn Read() -> Maze { + returned var me: Maze; + + var y: i32 = 0; + while (y < 130) { + var x: i32 = 0; + while (x < 130) { + var cell: i32 = Core.ReadChar(); + if (cell == 0x23) { + me.data[x][y] = Wall(); + } else if (cell == 0x5E) { + // TODO: Handle other starting directions? + me.data[x][y] = Visited(); + me.loc = (x, y); + me.dir = (0, -1); + } else { + me.data[x][y] = Empty(); + } + ++x; + } + SkipNewline(); + ++y; + } + return var; + } + + fn Copy[self: Self]() -> Maze { + returned var copy: Maze; + var y: i32 = 0; + while (y < 130) { + var x: i32 = 0; + while (x < 130) { + copy.data[x][y] = self.data[x][y]; + ++x; + } + ++y; + } + copy.loc = self.loc; + copy.dir = self.dir; + return var; + } + + fn Step[addr self: Self*]() -> bool { + let x: i32 = self->loc.0 + self->dir.0; + let y: i32 = self->loc.1 + self->dir.1; + if (x < 0 or x >= 130 or y < 0 or y >= 130) { + return false; + } + if (self->data[x][y] == Wall()) { + // TODO: Should this work? + // self->dir = (-self->dir.1, self->dir.0); + let d: (i32, i32) = self->dir; + self->dir = (-d.1, d.0); + } else { + self->loc = (x, y); + self->data[x][y] = Visited(); + } + return true; + } + + fn AddObstacle[addr self: Self*]() -> bool { + let x: i32 = self->loc.0 + self->dir.0; + let y: i32 = self->loc.1 + self->dir.1; + if (x < 0 or x >= 130 or y < 0 or y >= 130) { + return false; + } + if (self->data[x][y] != Empty()) { + return false; + } + self->data[x][y] = Wall(); + return true; + } + + fn CountVisited[self: Self]() -> i32 { + var total: i32 = 0; + var y: i32 = 0; + while (y < 130) { + var x: i32 = 0; + while (x < 130) { + if (self.data[x][y] == Visited()) { + ++total; + } + ++x; + } + ++y; + } + return total; + } + + var data: [[i8; 130]; 130]; + var loc: (i32, i32); + var dir: (i32, i32); +} diff --git a/examples/advent2024/day6_part1.carbon b/examples/advent2024/day6_part1.carbon new file mode 100644 index 000000000000..3e0a9c3e2f88 --- /dev/null +++ b/examples/advent2024/day6_part1.carbon @@ -0,0 +1,16 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/6 + +import Core library "io"; + +import library "day6_common"; + +fn Run() { + var maze: Maze = Maze.Read(); + while (maze.Step()) { + } + Core.Print(maze.CountVisited()); +} diff --git a/examples/advent2024/day6_part2.carbon b/examples/advent2024/day6_part2.carbon new file mode 100644 index 000000000000..f7a37b8b3017 --- /dev/null +++ b/examples/advent2024/day6_part2.carbon @@ -0,0 +1,63 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/6 + +import Core library "io"; + +import library "day6_common"; + +class LoopDetector { + fn Make() -> LoopDetector { + return {.last = ((-1, -1), (-1, -1)), .steps = 1, .next_steps = 1}; + } + + fn Check[addr self: Self*](next: ((i32, i32), (i32, i32))) -> bool { + // TODO: if (next == self->last) { + // TODO: The lexer mishandles `next.0.0` as `next` `.` `0.0`. + if (next.0 .0 == self->last.0 .0 and next.0 .1 == self->last.0 .1 and + next.1 .0 == self->last.1 .0 and next.1 .1 == self->last.1 .1) { + return true; + } + --self->steps; + if (self->steps == 0) { + self->steps = self->next_steps; + self->next_steps <<= 1; + self->last = next; + } + return false; + } + + var last: ((i32, i32), (i32, i32)); + var steps: i32; + var next_steps: i32; +} + +fn AddingObstacleMakesALoop(position: Maze) -> bool { + var maze: Maze = position.Copy(); + var loop: LoopDetector = LoopDetector.Make(); + if (not maze.AddObstacle()) { + return false; + } + while (maze.Step()) { + if (loop.Check((maze.loc, maze.dir))) { + return true; + } + } + return false; +} + +fn Run() { + var maze: Maze = Maze.Read(); + var loops: i32 = 0; + while (true) { + if (AddingObstacleMakesALoop(maze)) { + ++loops; + } + if (not maze.Step()) { + break; + } + } + Core.Print(loops); +} diff --git a/examples/advent2024/day7_common.carbon b/examples/advent2024/day7_common.carbon new file mode 100644 index 000000000000..d457cd5dc1c0 --- /dev/null +++ b/examples/advent2024/day7_common.carbon @@ -0,0 +1,58 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/7 + +library "day7_common"; + +import Core library "io"; +import library "io_utils"; + +fn Concat(a_val: i64, b_val: i64) -> i64 { + var a: i64 = a_val; + var b: i64 = b_val; + if (b == 0) { + return a * 10; + } + while (b != 0) { + a = a * 10; + b = b / 10; + } + return a + b_val; +} + +class Equation { + fn Read() -> Equation { + returned var me: Equation; + me.num_operands = 0; + ReadInt64(&me.result); + ConsumeChar(0x3A); + while (ConsumeChar(0x20)) { + ReadInt64(&me.operands[me.num_operands]); + ++me.num_operands; + } + while (SkipNewline()) {} + return var; + } + + fn SolveFrom[self: Self](start: i32, value: i64, concat: bool) -> bool { + if (value > self.result) { + return false; + } + if (start == self.num_operands) { + return value == self.result; + } + return self.SolveFrom(start + 1, value + self.operands[start], concat) or + self.SolveFrom(start + 1, value * self.operands[start], concat) or + (concat and self.SolveFrom(start + 1, Concat(value, self.operands[start]), true)); + } + + fn Solve[self: Self](concat: bool) -> bool { + return self.SolveFrom(1, self.operands[0], concat); + } + + var operands: [i64; 16]; + var num_operands: i32; + var result: i64; +} diff --git a/examples/advent2024/day7_part1.carbon b/examples/advent2024/day7_part1.carbon new file mode 100644 index 000000000000..92d1d7ee9843 --- /dev/null +++ b/examples/advent2024/day7_part1.carbon @@ -0,0 +1,22 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/7 + +import Core library "io"; + +import library "day7_common"; +import library "io_utils"; + +fn Run() { + var total: i64 = 0; + while (PeekChar() != Core.EOF()) { + var eq: Equation = Equation.Read(); + if (eq.Solve(false)) { + // TODO: total += eq.result; + total = total + eq.result; + } + } + PrintInt64(total); +} diff --git a/examples/advent2024/day7_part2.carbon b/examples/advent2024/day7_part2.carbon new file mode 100644 index 000000000000..7114580ca4ef --- /dev/null +++ b/examples/advent2024/day7_part2.carbon @@ -0,0 +1,22 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/7 + +import Core library "io"; + +import library "day7_common"; +import library "io_utils"; + +fn Run() { + var total: i64 = 0; + while (PeekChar() != Core.EOF()) { + var eq: Equation = Equation.Read(); + if (eq.Solve(true)) { + // TODO: total += eq.result; + total = total + eq.result; + } + } + PrintInt64(total); +} diff --git a/examples/advent2024/day8_common.carbon b/examples/advent2024/day8_common.carbon new file mode 100644 index 000000000000..308f4743511c --- /dev/null +++ b/examples/advent2024/day8_common.carbon @@ -0,0 +1,29 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/8 + +library "day8_common"; + +import Core library "io"; +import library "io_utils"; + +class Grid { + fn Read() -> Grid { + returned var me: Grid; + var y: i32 = 0; + while (y < 50) { + var x: i32 = 0; + while (x < 50) { + me.data[x][y] = ReadChar(); + ++x; + } + SkipNewline(); + ++y; + } + return var; + } + + var data: [[i32; 50]; 50]; +} diff --git a/examples/advent2024/day8_part1.carbon b/examples/advent2024/day8_part1.carbon new file mode 100644 index 000000000000..08898a9df430 --- /dev/null +++ b/examples/advent2024/day8_part1.carbon @@ -0,0 +1,52 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/8 + +import Core library "io"; + +import library "day8_common"; +import library "io_utils"; + +fn IsAntinode(grid: Grid, ox: i32, oy: i32) -> bool { + var ay: i32 = 0; + while (ay < 50) { + let by: i32 = ay * 2 - oy; + if (by >= 0 and by < 50) { + var ax: i32 = 0; + while (ax < 50) { + let bx: i32 = ax * 2 - ox; + if (bx >= 0 and bx < 50 and (ax != bx or ay != by)) { + if (grid.data[ax][ay] != 0x2E and + grid.data[ax][ay] == grid.data[bx][by]) { + return true; + } + } + ++ax; + } + } + ++ay; + } + return false; +} + +fn CountAntinodes(grid: Grid) -> i32 { + var count: i32 = 0; + var y: i32 = 0; + while (y < 50) { + var x: i32 = 0; + while (x < 50) { + if (IsAntinode(grid, x, y)) { + ++count; + } + ++x; + } + ++y; + } + return count; +} + +fn Run() { + Core.Print(CountAntinodes(Grid.Read())); +} diff --git a/examples/advent2024/day8_part2.carbon b/examples/advent2024/day8_part2.carbon new file mode 100644 index 000000000000..2539e8754e82 --- /dev/null +++ b/examples/advent2024/day8_part2.carbon @@ -0,0 +1,76 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/8 + +import Core library "io"; + +import library "day8_common"; +import library "io_utils"; + +fn MarkAndCount(marks: [[bool; 50]; 50]*, x: i32, y: i32) -> i32 { + if (not (*marks)[x][y]) { + (*marks)[x][y] = true; + return 1; + } + return 0; +} + +fn MarkAndCountAntinodesFor(grid: Grid, marks: [[bool; 50]; 50]*, ax: i32, ay: i32) -> i32 { + var count: i32 = 0; + var by: i32 = 0; + while (by < 50) { + var bx: i32 = 0; + while (bx < 50) { + let dx: i32 = bx - ax; + let dy: i32 = by - ay; + if (grid.data[bx][by] != grid.data[ax][ay] or (dx == 0 and dy == 0)) { + ++bx; + continue; + } + + var x: i32 = bx; + var y: i32 = by; + while (x >= 0 and x < 50 and y >= 0 and y < 50) { + count += MarkAndCount(marks, x, y); + x += dx; + y += dy; + } + ++bx; + } + ++by; + } + return count; +} + +fn MarkAndCountAntinodes(grid: Grid, marks: [[bool; 50]; 50]*) -> i32 { + var count: i32 = 0; + var y: i32 = 0; + while (y < 50) { + var x: i32 = 0; + while (x < 50) { + if (grid.data[x][y] != 0x2E) { + count += MarkAndCountAntinodesFor(grid, marks, x, y); + } + ++x; + } + ++y; + } + return count; +} + +fn Run() { + var marks: [[bool; 50]; 50]; + var y: i32 = 0; + while (y < 50) { + var x: i32 = 0; + while (x < 50) { + marks[x][y] = false; + ++x; + } + ++y; + } + + Core.Print(MarkAndCountAntinodes(Grid.Read(), &marks)); +} diff --git a/examples/advent2024/day9_common.carbon b/examples/advent2024/day9_common.carbon new file mode 100644 index 000000000000..8f227aa4e9f4 --- /dev/null +++ b/examples/advent2024/day9_common.carbon @@ -0,0 +1,137 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/9 + +library "day9_common"; + +import Core library "io"; +import library "io_utils"; + +// TODO: Add a builtin that can do this! +fn Promote(a: i32) -> i64 { + var result: i64 = 0; + var bit_i32: i32 = 1; + var bit_i64: i64 = 1; + while (bit_i32 != 0) { + if (a & bit_i32 != 0) { + result = result | bit_i64; + } + bit_i32 <<= 1; + // TODO: bit_i64 <<= 1; + bit_i64 = bit_i64 << 1; + } + return result; +} + +class SectorList { + fn Read() -> SectorList { + returned var me: SectorList; + me.size = 0; + var sector: i32 = 0; + var used: bool = true; + // TODO: An assignment expression would be useful here. + var c: i32 = ReadChar(); + while (c != Core.EOF() and c != 0xA) { + let v: i32 = if used then sector else -1; + var n: i32 = c - 0x30; + while (n > 0) { + me.data[me.size] = v; + ++me.size; + --n; + } + if (used) { + ++sector; + } + used = not used; + c = ReadChar(); + } + return var; + } + + fn DefragBlocks[addr self: Self*]() { + var i: i32 = 0; + var j: i32 = self->size - 1; + while (j > i) { + if (self->data[i] != -1) { + ++i; + } else if (self->data[j] == -1) { + --j; + } else { + self->data[i] = self->data[j]; + ++i; + --j; + } + } + self->size = j; + } + + fn HasSpace[addr self: Self*](start: i32, size: i32) -> bool { + var i: i32 = start; + while (i < start + size) { + if (self->data[i] != -1) { + return false; + } + ++i; + } + return true; + } + + fn Fill[addr self: Self*](start: i32, size: i32, sector: i32) { + var i: i32 = start; + while (i < start + size) { + self->data[i] = sector; + ++i; + } + } + + fn DefragFiles[addr self: Self*]() { + var j: i32 = self->size - 1; + while (j >= 0) { + // Skip empty sectors. + while (j >= 0 and self->data[j] == -1) { + --j; + } + + // Find the file size and sector and delete the file. + let last: i32 = j; + let sector: i32 = self->data[last]; + while (j >= 0 and self->data[j] == sector) { + self->data[j] = -1; + --j; + } + let first: i32 = j + 1; + let size: i32 = last - first + 1; + + // Find the first available space for the file. + var i: i32 = 0; + while (not self->HasSpace(i, size)) { + ++i; + if (i + size > first) { + // If it doesn't fit to the left of its old position, put it back + // where it was. + i = first; + } + } + + // Insert it. + self->Fill(i, size, sector); + } + } + + fn Checksum[self: Self]() -> i64 { + var total: i64 = 0; + var i: i32 = 0; + while (i < self.size) { + if (self.data[i] != -1) { + total = total + Promote(i * self.data[i]); + } + ++i; + } + return total; + } + + var data: [i32; 200000]; + var size: i32; +} diff --git a/examples/advent2024/day9_part1.carbon b/examples/advent2024/day9_part1.carbon new file mode 100644 index 000000000000..2f1d032d8411 --- /dev/null +++ b/examples/advent2024/day9_part1.carbon @@ -0,0 +1,16 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/9 + +import Core library "io"; + +import library "day9_common"; +import library "io_utils"; + +fn Run() { + var list: SectorList = SectorList.Read(); + list.DefragBlocks(); + PrintInt64(list.Checksum()); +} diff --git a/examples/advent2024/day9_part2.carbon b/examples/advent2024/day9_part2.carbon new file mode 100644 index 000000000000..c0ebb4b9a56c --- /dev/null +++ b/examples/advent2024/day9_part2.carbon @@ -0,0 +1,16 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// https://adventofcode.com/2024/day/9 + +import Core library "io"; + +import library "day9_common"; +import library "io_utils"; + +fn Run() { + var list: SectorList = SectorList.Read(); + list.DefragFiles(); + PrintInt64(list.Checksum()); +} diff --git a/examples/advent2024/io_utils.carbon b/examples/advent2024/io_utils.carbon index 0a84d10aa872..d80e14650bfb 100644 --- a/examples/advent2024/io_utils.carbon +++ b/examples/advent2024/io_utils.carbon @@ -26,6 +26,7 @@ fn UnreadChar(c: i32) { unread_char = c + 2; } +// TODO: Make generic. fn ReadInt(p: i32*) -> bool { var read_any_digits: bool = false; *p = 0; @@ -44,6 +45,71 @@ fn ReadInt(p: i32*) -> bool { return read_any_digits; } +fn ReadInt64(p: i64*) -> bool { + var read_any_digits: bool = false; + *p = 0; + + while (true) { + var c: i32 = ReadChar(); + if (c < 0x30 or c > 0x39) { + UnreadChar(c); + break; + } + // TODO: Check for overflow. + *p = *p * 10; + if (c == 0x31) { + *p = *p + 1; + } else if (c == 0x32) { + *p = *p + 2; + } else if (c == 0x33) { + *p = *p + 3; + } else if (c == 0x34) { + *p = *p + 4; + } else if (c == 0x35) { + *p = *p + 5; + } else if (c == 0x36) { + *p = *p + 6; + } else if (c == 0x37) { + *p = *p + 7; + } else if (c == 0x38) { + *p = *p + 8; + } else if (c == 0x39) { + *p = *p + 9; + } + read_any_digits = true; + } + return read_any_digits; +} + +fn PrintInt64NoNewline(n_val: i64) { + var pow10: i64 = 1; + var n: i64 = n_val; + while (n / 10 >= pow10) { + pow10 = pow10 * 10; + } + while (pow10 != 0) { + let d: i64 = n / pow10; + // TODO: Core.PrintChar(0x30 + (d as i32)); + if (d == 0) { Core.PrintChar(0x30); } + else if (d == 1) { Core.PrintChar(0x31); } + else if (d == 2) { Core.PrintChar(0x32); } + else if (d == 3) { Core.PrintChar(0x33); } + else if (d == 4) { Core.PrintChar(0x34); } + else if (d == 5) { Core.PrintChar(0x35); } + else if (d == 6) { Core.PrintChar(0x36); } + else if (d == 7) { Core.PrintChar(0x37); } + else if (d == 8) { Core.PrintChar(0x38); } + else if (d == 9) { Core.PrintChar(0x39); } + n = n % pow10; + pow10 = pow10 / 10; + } +} + +fn PrintInt64(n_val: i64) { + PrintInt64NoNewline(n_val); + Core.PrintChar(0xA); +} + fn PeekChar() -> i32 { var next: i32 = ReadChar(); UnreadChar(next); @@ -74,3 +140,14 @@ fn SkipNewline() -> bool { // TODO: Unread the CR if it was present? return ConsumeChar(0x0A); } + +fn SkipNChars(n: i32) -> bool { + var i: i32 = 0; + while (i < n) { + if (ReadChar() == Core.EOF()) { + return false; + } + ++i; + } + return true; +}