Browse Source

Add advent of code 2024 day 14 and day 15 part 1 solutions (#6597)

I've had these kicking around for a year but never got around to pushing
them. They seem to cover a few things that previous examples didn't, so
I think we may as well include them.

---------

Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
Richard Smith 3 months ago
parent
commit
7f0f402f95

+ 8 - 8
examples/advent2024/BUILD

@@ -11,19 +11,19 @@ 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 = ["day3_part2.carbon"]
 excluded = []
 
-# Produce a binary "dayX_partY" for each matching `.carbon` file.
+# Produce a binary `dayX_partY` for each matching `.carbon` file. Each binary
+# depends on `dayX_partY.carbon` and `dayX_common.carbon`.
 [
     carbon_binary(
-        name = "day{0}_part{1}".format(day, part),
+        name = carbon_file.removesuffix(".carbon"),
         srcs = [
-            "day{0}_common.carbon".format(day),
-            "day{0}_part{1}.carbon".format(day, part),
+            carbon_file,
+            carbon_file.rsplit("_", 1)[0] + "_common.carbon",
         ] + utils,
     )
-    for day in range(1, 14)
-    for part in range(1, 3)
-    if (day, part) not in excluded
+    for carbon_file in glob(["day*_part*.carbon"])
+    if carbon_file not in excluded
 ]

+ 49 - 0
examples/advent2024/day14_common.carbon

@@ -0,0 +1,49 @@
+// 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/14
+
+library "day14_common";
+
+import Core library "io";
+import library "io_utils";
+
+fn ReadXY(xy: (i32, i32)*) -> bool {
+  return ReadInt(ref xy->0) and ConsumeChar(',') and ReadInt(ref xy->1);
+}
+
+fn SkipString(s: str) -> bool {
+  return SkipNChars(s.Size() as i32);
+}
+
+fn Mod(a: i32, d: i32) -> i32 {
+  let rem: i32 = a % d;
+  return if rem < 0 then rem + d else rem;
+}
+
+class Robot {
+  fn Read() -> Robot {
+    returned var me: Robot;
+    SkipString("p=");
+    ReadXY(&me.p);
+    SkipString(" v=");
+    ReadXY(&me.v);
+    SkipNewline();
+    return var;
+  }
+
+  fn Pos[self: Self](t: i32) -> (i32, i32) {
+    return (Mod((self.p.0 + self.v.0 * Mod(t, 101)), 101),
+            Mod((self.p.1 + self.v.1 * Mod(t, 103)), 103));
+  }
+
+  impl as Core.Copy {
+    fn Op[self: Self]() -> Self {
+      return {.p = self.p, .v = self.v};
+    }
+  }
+
+  var p: (i32, i32);
+  var v: (i32, i32);
+}

+ 21 - 0
examples/advent2024/day14_part1.carbon

@@ -0,0 +1,21 @@
+// 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/14
+
+import Core library "io";
+
+import library "day14_common";
+import library "io_utils";
+
+fn Run() {
+  var q: array(array(i32, 3), 3) = ((0,0,0), (0,0,0), (0,0,0));
+  while (PeekChar() != CharOrEOF.EOF()) {
+    var r: Robot = Robot.Read();
+    let xy: (i32, i32) = r.Pos(100);
+    ++q[if xy.0 < 50 then 0 else if xy.0 > 50 then 2 else 1]
+       [if xy.1 < 51 then 0 else if xy.1 > 51 then 2 else 1];
+  }
+  Core.Print(q[0][0] * q[0][2] * q[2][0] * q[2][2]);
+}

+ 70 - 0
examples/advent2024/day14_part2.carbon

@@ -0,0 +1,70 @@
+// 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/14
+
+import Core library "io";
+import Core library "range";
+
+import library "day14_common";
+import library "io_utils";
+
+// Returns m and n so that am + bn = gcd.
+fn Euclid(a: i32, b: i32) -> {.m: i32, .n: i32, .gcd: i32} {
+  if (a < b) {
+    let reverse: {.m: i32, .n: i32, .gcd: i32} = 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: i32, .n: i32, .gcd: i32} = Euclid(b, a % b);
+  return {.m = next.n, .n = next.m - next.n * (a / b), .gcd = next.gcd};
+}
+
+// Uses Chinese remainder theorem to find the least positive x that solves
+//   x = a mod p
+//   x = b mod q
+// modulo pq / gcd(p,q), if one exists (if a = b mod gcd(p,q)).
+fn CRT(a: i32, b: i32, p: i32, q: i32) -> i32 {
+  let e: {.m: i32, .n: i32, .gcd: i32} = Euclid(p, q);
+  let x: i32 = a * (q / e.gcd) * e.n + b * (p / e.gcd) * e.m;
+  return Mod(x, p * (q / e.gcd));
+}
+
+fn PrintState(r: array(Robot, 500), t: i32) {
+  var display: array(array(char, 101), 103);
+
+  for (y: i32 in Core.Range(103)) {
+    for (x: i32 in Core.Range(101)) {
+      display[y][x] = '.';
+    }
+  }
+
+  for (ri: Robot in r) {
+    let xy: (i32, i32) = ri.Pos(t);
+    display[xy.1][xy.0] = '#';
+  }
+
+  for (y: i32 in Core.Range(103)) {
+    for (x: i32 in Core.Range(101)) {
+      Core.PrintChar(display[y][x]);
+    }
+    Core.PrintChar('\n');
+  }
+}
+
+fn Run() {
+  var r: array(Robot, 500);
+  for (i: i32 in Core.Range(500)) {
+    r[i] = Robot.Read();
+  }
+
+  let first_match_x: i32 = 28;
+  let first_match_y: i32 = 84;
+
+  var t: i32 = CRT(first_match_x, first_match_y, 101, 103);
+  Core.Print(t);
+  PrintState(r, t);
+}

+ 134 - 0
examples/advent2024/day15_common.carbon

@@ -0,0 +1,134 @@
+// 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/15
+
+library "day15_common";
+
+import Core library "io";
+import library "io_utils";
+
+// TODO: Use a choice type.
+class Square {
+  adapt char;
+
+  impl as Core.Copy {
+    fn Op[self: Self]() -> Self {
+      return (self as char).(Core.Copy.Op)() as Self;
+    }
+  }
+  impl as Core.EqWith(Square) {
+    fn Equal[self: Self](other: Self) -> bool {
+      // TODO: Use a `char` comparison when it's supported.
+      return (self as u8) == (other as u8);
+    }
+    fn NotEqual[self: Self](other: Self) -> bool {
+      // TODO: Use a `char` comparison when it's supported.
+      return (self as u8) != (other as u8);
+    }
+  }
+
+  fn Make(c: CharOrEOF) -> Square {
+    // TODO: Use `match`.
+    if (c == '#') { return ('#' as char) as Square; }
+    if (c == 'O') { return ('O' as char) as Square; }
+    if (c == '.') { return ('.' as char) as Square; }
+    if (c == '@') { return ('@' as char) as Square; }
+    return ('!' as char) as Square;
+  }
+}
+
+let Wall: Square = ('#' as char) as Square;
+let Box: Square = ('O' as char) as Square;
+let Empty: Square = ('.' as char) as Square;
+let Robot: Square = ('@' as char) as Square;
+
+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] = Square.Make(ReadChar());
+        if (me.data[x][y] == Robot) {
+          me.robot = (x, y);
+        }
+        ++x;
+      }
+      SkipNewline();
+      ++y;
+    }
+    return var;
+  }
+
+  fn Move[ref self: Self](d: (i32, i32)) {
+    var distance: i32 = 1;
+    while (self.data[self.robot.0 + distance * d.0][self.robot.1 + distance * d.1] == Box) {
+      ++distance;
+    }
+    if (self.data[self.robot.0 + distance * d.0][self.robot.1 + distance * d.1] == Empty) {
+      self.data[self.robot.0][self.robot.1] = Empty;
+      self.data[self.robot.0 + distance * d.0][self.robot.1 + distance * d.1] = Box;
+      self.data[self.robot.0 + d.0][self.robot.1 + d.1] = Robot;
+      self.robot.0 += d.0;
+      self.robot.1 += d.1;
+    }
+  }
+
+  fn Print[self: Self]() {
+    var y: i32 = 0;
+    while (y < 50) {
+      var x: i32 = 0;
+      while (x < 50) {
+        Core.PrintChar(self.data[x][y] as char);
+        ++x;
+      }
+      Core.PrintChar('\n');
+      ++y;
+    }
+    Core.PrintChar('\n');
+  }
+
+  fn Signature[self: Self]() -> i32 {
+    var s: i32 = 0;
+    var y: i32 = 0;
+    while (y < 50) {
+      var x: i32 = 0;
+      while (x < 50) {
+        if (self.data[x][y] == Box) {
+          s += y * 100 + x;
+        }
+        ++x;
+      }
+      ++y;
+    }
+    return s;
+  }
+
+  var data: array(array(Square, 50), 50);
+  var robot: (i32, i32);
+}
+
+fn ReadAndExecuteActions(ref g: Grid) {
+  while (true) {
+    var m: CharOrEOF = ReadChar();
+    // To watch the progress, uncomment the following:
+    // g.Print();
+    // Core.PrintChar(((m as i32) as u8) as char);
+    // Core.PrintChar('\n');
+    // TODO: Use `match` once it's available.
+    if (m == '^') {
+      g.Move((0, -1));
+    } else if (m == '<') {
+      g.Move((-1, 0));
+    } else if (m == '>') {
+      g.Move((1, 0));
+    } else if (m == 'v') {
+      g.Move((0, 1));
+    } else if (m == CharOrEOF.EOF()) {
+      return;
+    }
+  }
+}

+ 15 - 0
examples/advent2024/day15_part1.carbon

@@ -0,0 +1,15 @@
+// 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/15
+
+import Core library "io";
+
+import library "day15_common";
+
+fn Run() {
+  var g: Grid = Grid.Read();
+  ReadAndExecuteActions(ref g);
+  Core.Print(g.Signature());
+}

+ 22 - 15
examples/advent2024/io_utils.carbon

@@ -90,8 +90,24 @@ fn UnreadChar(c: CharOrEOF) {
   unread_char = Core.Optional(CharOrEOF).Some(c);
 }
 
+fn PeekChar() -> CharOrEOF {
+  var next: CharOrEOF = ReadChar();
+  UnreadChar(next);
+  return next;
+}
+
+fn ConsumeChar(c: char) -> bool {
+  var next: CharOrEOF = ReadChar();
+  if (next != c) {
+    UnreadChar(next);
+    return false;
+  }
+  return true;
+}
+
 fn ReadInt[N:! Core.IntLiteral()](ref p: Core.Int(N)) -> bool {
   var read_any_digits: bool = false;
+  let negative: bool = ConsumeChar('-');
   // TODO: `p = 0;` crashes the toolchain, see
   // https://github.com/carbon-language/carbon-lang/issues/6500.
   p = (0 as i32) as Core.Int(N);
@@ -108,6 +124,12 @@ fn ReadInt[N:! Core.IntLiteral()](ref p: Core.Int(N)) -> bool {
     p += (c - '0') as Core.Int(N);
     read_any_digits = true;
   }
+  if (negative) {
+    p = -p;
+    if (not read_any_digits) {
+      UnreadChar('-');
+    }
+  }
   return read_any_digits;
 }
 
@@ -135,21 +157,6 @@ fn PrintInt[N:! Core.IntLiteral()](n_val: Core.Int(N)) {
   Core.PrintChar('\n');
 }
 
-fn PeekChar() -> CharOrEOF {
-  var next: CharOrEOF = ReadChar();
-  UnreadChar(next);
-  return next;
-}
-
-fn ConsumeChar(c: char) -> bool {
-  var next: CharOrEOF = ReadChar();
-  if (next != c) {
-    UnreadChar(next);
-    return false;
-  }
-  return true;
-}
-
 fn SkipSpaces() -> bool {
   var skipped_any_spaces: bool = false;
   while (ConsumeChar(' ')) {