Advent of Code 2024

2 posts were split to a new topic: TWiT+ Feed for Advent of Code episode not streaming correct content?

Day 13 is a math problem. It’s solving a linear equation with two unknowns. Part 1 is easily done with brute force, and he as much as encourages you to because he says:

You estimate that each button would need to be pressed no more than 100 times to win a prize.

For part 2, the scale of the numbers is huge, and if you try to brute force it, it will have a run time of probably at least an hour, and that’s if you’re fairly clever. On the other hand, if you do the math, the answer is instant.

I created a class ClawGameParser to handle the parsing of the input. I considered just using a text editor to do some search and replacing followed by a macro, which probably would have been faster, but in the end writing the regex code didn’t take too long and worked right the first time. It calls a class ClawGame to actually “play” each game.

final class ClawGameParser
{
  private final List<ClawGame> clawGames = new ArrayList<>();
  
  public ClawGameParser(final String[] inputclawGames, final int aButtonCost, final int bButtonCost)
  {
    for (int i=0 ; i < inputclawGames.length; i += 4)  // 4: three separate lines followed by a blank line
    {
      final String buttonALine = inputclawGames[i];
      final String buttonBLine = inputclawGames[i + 1];
      final String prizeLine   = inputclawGames[i + 2];
      final ClawGame cg = parseClawGameLinesAndMakeGame(buttonALine, buttonBLine, prizeLine, aButtonCost, bButtonCost);
      clawGames.add(cg);
    }
  }

  final static Pattern A_LINE_PARSER = Pattern.compile("Button A: X[+]([0-9]+), Y[+]([0-9]+)");
  final static Pattern B_LINE_PARSER = Pattern.compile("Button B: X[+]([0-9]+), Y[+]([0-9]+)");
  final static Pattern PRIZE_LINE_PARSER = Pattern.compile("Prize: X=([0-9]+), Y=([0-9]+)");
  private ClawGame parseClawGameLinesAndMakeGame(final String buttonALine, final String buttonBLine, final String prizeLine, final int aButtonCost, final int bButtonCost)
  {
    final Matcher aLineMatcher = A_LINE_PARSER.matcher(buttonALine);
    if (!aLineMatcher.matches()) throw new IllegalStateException("A line failes: " + buttonALine);
    final Matcher bLineMatcher = B_LINE_PARSER.matcher(buttonBLine);
    if (!bLineMatcher.matches()) throw new IllegalStateException("B line failes: " + buttonBLine);
    final Matcher prizeLineMatcher = PRIZE_LINE_PARSER.matcher(prizeLine);
    if (!prizeLineMatcher.matches()) throw new IllegalStateException("Prize line failes: " + prizeLine);

    final long aButtonX = Long.parseLong(aLineMatcher.group(1));
    final long aButtonY = Long.parseLong(aLineMatcher.group(2));
    final long bButtonX = Long.parseLong(bLineMatcher.group(1));
    final long bButtonY = Long.parseLong(bLineMatcher.group(2));
    final long prizeX = Long.parseLong(prizeLineMatcher.group(1));
    final long prizeY = Long.parseLong(prizeLineMatcher.group(2));
  
    return new ClawGame(aButtonX, aButtonY, bButtonX, bButtonY, prizeX, prizeY, aButtonCost, bButtonCost);
  }

  public long playAllGames(int partNumber)  // part 1 and part 2
  {
    long sum = 0;
    for (final ClawGame cg : clawGames)
    {
      if (cg.findWinningButtonPresses(partNumber))
      {
        sum += cg.getCostOfWin();
      }
    }
    return sum;
  }
}
final class ClawGame
{
  private final long aButtonX;
  private final long aButtonY;
  private final long bButtonX;
  private final long bButtonY;
  private final long prizeX;
  private final long prizeY;
  private final int aButtonCost;
  private final int bButtonCost;
  
  private long numAButtonPressesForWin = -1;
  private long numBButtonPressesForWin = -1;
  
  private static final long PART2_OFFSET = 10_000_000_000_000L;

  public ClawGame(final long aButtonX, final long aButtonY,
      final long bButtonX, final long bButtonY,
      final long prizeX, final long prizeY,
      final int aButtonCost, final int bButtonCost)
  {
    this.aButtonX = aButtonX;
    this.aButtonY = aButtonY;
    this.bButtonX = bButtonX;
    this.bButtonY = bButtonY;
    this.prizeX = prizeX;
    this.prizeY = prizeY;
    this.aButtonCost = aButtonCost;
    this.bButtonCost = bButtonCost;
  }

  public boolean findWinningButtonPresses(final int partNumber)
  {
    numAButtonPressesForWin = -1;
    numBButtonPressesForWin = -1;
    
    if (partNumber == 1)
      return solveLinearEquation(prizeX, prizeY);
    if (partNumber == 2)
      return solveLinearEquation(prizeX + PART2_OFFSET, prizeY + PART2_OFFSET);

    throw new IllegalStateException("Only support part 1 or 2, partNumber=" + partNumber);
  }

  public boolean solveLinearEquation(final long targetX, final long targetY)
  {
    long b = (targetY * aButtonX - targetX * aButtonY) / 
        (bButtonY * aButtonX - bButtonX * aButtonY);
    long a = (targetX - b * bButtonX) / aButtonX;

    if ( (a * aButtonX + b * bButtonX == targetX) &&
         (a * aButtonY + b * bButtonY == targetY)  )
    {
      numAButtonPressesForWin = a;
      numBButtonPressesForWin = b;
      return true;
    }

    return false;
  }

  public long getCostOfWin()
  {
    if ( (numAButtonPressesForWin < 0) ||
         (numBButtonPressesForWin < 0)  )
    {
      throw new IllegalStateException("Call to getCostOfWin() when there is no winning condition");
    }

    final long costForWin =
        numAButtonPressesForWin * aButtonCost +
        numBButtonPressesForWin * bButtonCost;
    return costForWin;
  }
}

The run time is pretty instant, even though the answer for part 2 is around 80T.

Part 1: 0.0140132s
Part 2: 0.0010012s

The quicker speed of part 2 must be down to the just-in-time compiler compiling the code for part 1.

I figured I’d include my working of the math for Day 13 for anyone interested:

ax*a + bx*b = targetX
ay*a + by*b = targetY

a*ax = targetX - bx*b
a = targetX -bx*b
    -------------
         ax

a*ay = targetY - by*b
a = targetY - by*b
    --------------
         ay

targetX-bx*b     targetY-by*b
------------  =  ------------
    ax                ay

(targetX-bx*b)*ay = (targetY-by*b)*ax
targetX*ay - bx*b*ay = targetY*ax - by*b*ax
targetX*ay - targetY*ax = - by*b*ax + bx*b*ay
targetX*ay - targetY*ax = -b(by*ax - bx*ay)
b(by*ax - bx*ay) = targetY*ax - targetX*ay
b = (targetY*ax - targetX*ay) / (by*ax - bx*ay)

Day 14 is about robots patrolling the bathroom. Part 2 is an interesting challenge, more of a thought exercise but not really a hard one to code. I thought sure part 2 was gonna require some insanely large number of seconds of movement, so I prepared for that in part 1. It turned out my guess was wrong, but my preparations still helped for part 2. I created a class RobotGrid. For part 1 I had to deal with the negative delta directions in an extra step because Java does some weirdness with negative mods, but otherwise it was fairly straight-forward. Part 2 was even more straight-forward once I came up with an approach to try.

final class RobotGrid
{
  record Coord(int x, int y) {}
  record Vector(Coord location, Coord direction) {}
  
  private final int height;
  private final int width;
  private final List<Vector> robots = new ArrayList<>();

  public RobotGrid(final String[] robotPositionsAndVectors, final int inputHeight, final int inputWidth)
  {
    this.height = inputHeight;
    this.width  = inputWidth;
    parseRobotPositionsAndVectors(robotPositionsAndVectors);
  }

  private static final Pattern ROBOT_VECTOR_LINE = Pattern.compile("p=([0-9]+),([0-9]+) v=([-]?[0-9]+),([-]?[0-9]+)");
  private void parseRobotPositionsAndVectors(final String[] robotPositionsAndVectors)
  {
    for (final String line : robotPositionsAndVectors)
    {
      final Matcher robotPositionAndVector = ROBOT_VECTOR_LINE.matcher(line);
      if (!robotPositionAndVector.matches())  throw new IllegalStateException(line);
      final int x = Integer.parseInt(robotPositionAndVector.group(1));
      final int y = Integer.parseInt(robotPositionAndVector.group(2));
      final int dx = Integer.parseInt(robotPositionAndVector.group(3));
      final int dy = Integer.parseInt(robotPositionAndVector.group(4));
      robots.add(new Vector(new Coord(x, y), new Coord(dx, dy)));
    }
  }

  public long getResultAfterSeconds(int numberOfSeconds)  // part 1
  {
    final int[] counts = new int[5];  // 4 quadrants + centre (which we'll ignore)
    for(final Vector robot : robots)
    {
      final Coord newRobotLocation = moveRobot(robot, numberOfSeconds);
      final int quadrant = getQuadrant(newRobotLocation);
      counts[quadrant]++;
    }
    int result = 1;
    for (int i=0; i<4; i++)
    {
      result *= counts[i];
    }
    return result;
  }

  // Java does some weirdness with mods of negative numbers.  Rather than deal
  // with that, I just used abs() and then added an extra unit of the mod value
  // to pull the number positive again
  private Coord moveRobot(final Vector startLocation, int numberOfSecondsToMove)
  {
    final int x = startLocation.location().x();
    final int y = startLocation.location().y();
    final int dx = startLocation.direction().x(); 
    final int dy = startLocation.direction().y();
    
    int xMove = (Math.abs(dx) * numberOfSecondsToMove) % width;
    if (dx < 0)  xMove = 0 - xMove;
    int yMove = (Math.abs(dy) * numberOfSecondsToMove) % height;
    if (dy < 0)  yMove = 0 - yMove;
    final int newX = (x + xMove + width) % width;
    final int newY = (y + yMove + height) % height;

    return new Coord(newX, newY);
  }

  // The quadrant mapping is almost irrelevant because the numbers will be multiplied 
  private int getQuadrant(final Coord location)
  {
    final int midY = height/2;
    final int midX = width/2;
    final int x = location.x();
    final int y = location.y();
    
    // ignore robots in the middle as instructed
    if ((x == midX) || (y == midY)) return 4;

    if (x < midX)
    {
      if (y < midY)
        return 0;
      else
        return 1;
    }
    if (y < midY)
      return 2;
    else
      return 3;
  }

  public long howLongForXmasTreeEasterEgg()  // part 2
  {
    int robotMovementSeconds = 1;
    Set<Coord> robots = moveRobots(robotMovementSeconds);
    while (!inXmasArrangement(robots))
    {
      robotMovementSeconds++;
      robots = moveRobots(robotMovementSeconds);
    }
    dumpRobots(robots);
    return robotMovementSeconds;
  }

  private Set<Coord> moveRobots(final int numberOfSeconds)
  {
    final Set<Coord> result = new HashSet<>();
    for(final Vector robot : robots)
    {
      final Coord newRobotLocation = moveRobot(robot, numberOfSeconds);
      result.add(newRobotLocation);
    }
    return result;
  }

  // Initial assumption that the easter egg means every robot is in a
  // unique position proved itself out
  private boolean inXmasArrangement(final Set<Coord> robotArrangement)
  {
    if (robotArrangement.size() == robots.size()) return true;
    return false;
  }

  private void dumpRobots(final Set<Coord> robotArrangement)
  {
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        final Coord c = new Coord(x, y);
        if (robotArrangement.contains(c))
          System.out.print("*");
        else
          System.out.print(".");
      }
      System.out.println();
    }
    System.out.println();
  }
}

And here’s the part 2 output

...............................*...........*......................................*..................
...........................................*.......................*.................................
.....................................................................................................
........................................*............................................................
.....................................................................................................
..............................................*................................*.....................
.....................................................................................................
..........................................*......................................*...................
...................................................................*.....................*...........
..............*...................................................*.......*..........................
.........................*................................................................*..........
.................................*...................................................................
.....................................................................................................
..*..................................................................................................
.....................................................................................................
................*.................*......................*...........................................
...........................................*.........................................................
.....................................................................................................
........................................*..................................*.........................
..............*.................................................................*..................*.
.........................................................*...........................................
.....................................................................................................
..........................................................................................*..........
...........................*...................*.................................................*...
...........................*................................................*........................
.....................................................................................................
.......*......................*......................*....*..........................................
.....................................................................................................
............*........................................................................................
................................*........................................*...........................
...........................................................*.........................................
..................................................................*..................................
.....................................................................................................
........................*.......*...............*....................................................
...............................*.................*...............*............................*......
.....................................................................................................
.....................................................................................................
.................................................................................*...................
...*........*.................................*......................................................
...............................................................................................*.....
.....................................................................................................
..........*............*................................*......................................*.....
...................................*..................*.....................*........................
.................................................................................................*...
............................................................................*........................
........................*............*.............*...............*............................*....
...................................................................*.................................
.....................................................................................................
.................................................*******************************.....................
....*..........*.............................*...*.............................*.....................
............*.....*.*............................*.............................*.....................
....................*.........................*..*.............................*.....................
...........*..................................*..*.............................*........*............
.................................................*..............*..............*...............*.....
.................................................*.............***.............*.....................
...........*.....................................*............*****............*.....................
.................*.......*.......................*...........*******...........*.....................
.................................................*..........*********..........*.....................
.............*...................................*............*****............*.............*.......
.................................................*...........*******...........*.....................
.....*...........................................*..........*********..........*..................*..
.................................................*.........***********.........*....*................
...........................*................*....*........*************........*.....................
...........**....................................*..........*********..........*.....................
.......................................*.........*.........***********.........*.....................
.................................................*........*************........*.....................
.................................................*.......***************.......*.....................
.................................................*......*****************......*.....................
.................................................*........*************........*.....................
.................................................*.......***************.......*.....................
.................................................*......*****************......*.....................
.................................................*.....*******************.....*...............*.....
.................................................*....*********************....*.....................
........................................*........*.............***.............*.....................
............................*....................*.............***.............*...........*....*....
......................................*........*.*.............***.............*.....................
.................................................*.............................*.....................
.................................................*.............................*.....................
.................................................*.............................*....*................
.................................................*.............................*.....................
..............................*.........*........*******************************.....................
.....................................*...............................................*...............
...............................................*.......*...*....................*....................
.......*.......................*.....................................................................
...................................*...........................................*.....................
................................*....*...............................................................
........................................................*...............*...........*................
.....................................................................................................
.........................*.........................*...........................*.....................
.....................................................................................................
............................*..............................*..........*..............................
.....................................................................................................
.....................................................................................................
.........................*.....................*.....................................................
.....................................................................................................
..............................*......................**.......*......................................
..............................................................*.....*............**..........*.......
............................*................................*.....*.................................
.....................................................................................................
..............*......................................................................................
...................*.................................................................*...............
*...............................*....................................................................
.............................................*.......................................................

The runtime was pretty reasonable

Part 1: 0.0110099s
Part 2: 0.2041993s
1 Like

Day 15 is sort of a version of Sokoban. You have a robot that is trying to move around, and can push [any number of] boxes in a row if there is space for the boxes to move into. For part one I created a class RobotBoxer (mostly because I chuckled when I thought of the name as clearly RobotBoxMover or something less clever would be more technically correct. :wink: ) For part 2 the conditions change, as everything gets twice as wide except the robot. This means that boxes can overlap, and you need to figure out how to deal with the overlaps. For this reason I copied my part 1 RobotBoxer class and used it as the basis to create RobotBoxer2 for part 2.

Here’s the part 1 RobotBoxer. There’s a fair amount of symmetry in the code because the direction doesn’t change the overall means of completing the task. I also separated the two parts of the input and passed the layout/map into the constructor, and the robot movements into the GPS() (Goods Positioning System) calculation method. (The desired result is a summation related to the co-ordinates of the boxes.)

final class RobotBoxer
{
  record Coord(int x, int y) {}
  private static final Coord BAD_COORD = new Coord(-1, -1);

  private final Set<Coord> boxLocations = new HashSet<>();
  private final Coord robotStartCoord;
  private final Set<Coord> walls = new HashSet<>();
  private final int height;
  private final int width;

  public RobotBoxer(final String[] inputLayout)
  {
    height = inputLayout.length;
    width  = inputLayout[0].length();
    Coord startCoord = BAD_COORD; 
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        final char c = inputLayout[y].charAt(x);
        final Coord coord = new Coord(x, y);
        if (c == '@')
        {
          startCoord = coord;
          continue;
        }
        if (c == 'O')
        {
          boxLocations.add(coord);
          continue;
        }
        if (c == '#')
        {
          walls.add(coord);
          continue;
        }
        if (c != '.')  throw new IllegalStateException("Unknown char: " + c);
      }
    }
    if (startCoord.equals(BAD_COORD))  throw new IllegalStateException("Robot start location not defined");
    robotStartCoord = startCoord;
  }

  public long GPS(final String[] inputMoves)  // Part 1
  {
    Coord robotLocation = robotStartCoord;
    for (int line = 0; line < inputMoves.length; line++)
    {
      for (final char c : inputMoves[line].toCharArray())
      {
        robotLocation = switch(c)
        {
          case '<' -> moveLeftIfPossible(robotLocation);
          case '>' -> moveRightIfPossible(robotLocation);
          case '^' -> moveUpIfPossible(robotLocation);
          case 'v' -> moveDownIfPossible(robotLocation);
          default -> throw new IllegalStateException("Invalid movement char: " + c);
        };
      }
    }

    return calculateBoxGPSVal();
  }

  private long calculateBoxGPSVal()
  {
    long sum = 0;
    for (final Coord boxCoord : boxLocations)
    {
      sum += boxCoord.y()*100 + boxCoord.x();
    }
    return sum;
  }

  private Coord moveLeftIfPossible(final Coord robotLocation)
  {
    final Coord moveCoord = new Coord (-1, 0);
    return doMove(robotLocation, moveCoord);
  }

  private Coord moveRightIfPossible(final Coord robotLocation)
  {
    final Coord moveCoord = new Coord (1, 0);
    return doMove(robotLocation, moveCoord);
  }

  private Coord moveUpIfPossible(final Coord robotLocation)
  {
    final Coord moveCoord = new Coord (0, -1);
    return doMove(robotLocation, moveCoord);
  }

  private Coord  moveDownIfPossible(final Coord robotLocation)
  {
    final Coord moveCoord = new Coord (0, 1);
    return doMove(robotLocation, moveCoord);
  }

  private Coord doMove(final Coord robotLocation, final Coord moveCoord)
  {
    final Coord destCoord = new Coord(robotLocation.x() + moveCoord.x(), robotLocation.y() + moveCoord.y());
    if (walls.contains(destCoord)) return robotLocation;
    if (boxLocations.contains(destCoord))
    {
      if (!canMoveBox(destCoord, moveCoord)) return robotLocation;
      moveBox(destCoord, moveCoord);
    }
    return destCoord;
  }

  private boolean canMoveBox(final Coord destCoord, final Coord moveCoord)
  {
    int x = destCoord.x();
    int y = destCoord.y();
    while (true)
    {
      final Coord newCoord = new Coord(x,y);
      if (walls.contains(newCoord)) return false;
      if (!boxLocations.contains(newCoord)) return true;
      x += moveCoord.x();
      y += moveCoord.y();
    }
  }

  private void moveBox(final Coord destCoord, final Coord moveCoord)
  {
    int x = destCoord.x();
    int y = destCoord.y();
    if (!boxLocations.contains(destCoord))  throw new IllegalStateException("Call to move a box that was not present: " + destCoord);
    while (true)
    {
      final Coord newCoord = new Coord(x,y);
      if (walls.contains(newCoord)) throw new IllegalStateException("Hit wall while moving boxes: " + newCoord);
      if (!boxLocations.contains(newCoord))
      {
        boxLocations.remove(destCoord);  // Remove the original
        boxLocations.add(newCoord);      // place it here
        return;
      }
      x += moveCoord.x();
      y += moveCoord.y();
    }
  }
}

For part 2 things get hellishly more complicated for up and down movements. You pretty much have to use recursion to handle the fact that one box on the bottom could move a whole pyramidal stack above it (or below it.) I chose a representation of using one hash table for the left half of the double sided boxes and another for the right half. I’m not sure if this is the best design, but I felt it helped me reason about the recursion in the clearest way.

Here’s the class for RobotBoxer2. There is some mighty repetition here. At one point I tried combining it all, but it became hard to debug and more complicated to understand, so I undid all that attempt as left it as it is now. It’s a fair bit of code.

final class RobotBoxer2
{
  record Coord(int x, int y) {}
  private static final Coord BAD_COORD = new Coord(-1, -1);

  private final Set<Coord> boxLeftLocations  = new HashSet<>();
  private final Set<Coord> boxRightLocations = new HashSet<>();
  private final Coord robotStartCoord;
  private final Set<Coord> walls = new HashSet<>();
  private final int height;
  private final int width;

  public RobotBoxer2(final String[] inputLayout)
  {
    height = inputLayout.length;
    width  = inputLayout[0].length();
    Coord startCoord = BAD_COORD; 
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        final char c = inputLayout[y].charAt(x);
        final Coord coord1 = new Coord(x*2, y);
        final Coord coord2 = new Coord(x*2+1, y);
        if (c == '@')
        {
          startCoord = coord1;
          continue;
        }
        if (c == 'O')
        {
          boxLeftLocations.add(coord1);
          boxRightLocations.add(coord2);
          continue;
        }
        if (c == '#')
        {
          walls.add(coord1);
          walls.add(coord2);
          continue;
        }
        if (c != '.')  throw new IllegalStateException("Unknown char: " + c);
      }
    }
    if (startCoord.equals(BAD_COORD))  throw new IllegalStateException("Robot start location not defined");
    robotStartCoord = startCoord;
  }

  public long GPS(final String[] inputMoves)
  {
    Coord robotLocation = robotStartCoord;
    for (int line = 0; line < inputMoves.length; line++)
    {
      for (final char c : inputMoves[line].toCharArray())
      {
        robotLocation = switch(c)
        {
          case '<' -> moveLeftIfPossible(robotLocation);
          case '>' -> moveRightIfPossible(robotLocation);
          case '^' -> moveUpIfPossible(robotLocation);
          case 'v' -> moveDownIfPossible(robotLocation);
          default -> throw new IllegalStateException("Invalid movement char: " + c);
        };
      }
    }
    return calculateBoxGPSVal();
  }

  private long calculateBoxGPSVal()
  {
    long sum = 0;
    for (final Coord boxCoord : boxLeftLocations)
    {
      sum += boxCoord.y()*100 + boxCoord.x();
    }
    return sum;
  }

  private Coord moveLeftIfPossible(final Coord robotLocation)
  {
    final Coord moveCoord = new Coord (-1, 0);
    return doMove(robotLocation, moveCoord);
  }

  private Coord moveRightIfPossible(final Coord robotLocation)
  {
    final Coord moveCoord = new Coord (1, 0);
    return doMove(robotLocation, moveCoord);
  }

  private Coord moveUpIfPossible(final Coord robotLocation)
  {
    final Coord moveCoord = new Coord (0, -1);
    return doMove(robotLocation, moveCoord);
  }

  private Coord  moveDownIfPossible(final Coord robotLocation)
  {
    final Coord moveCoord = new Coord (0, 1);
    return doMove(robotLocation, moveCoord);
  }

  private Coord doMove(final Coord robotLocation, final Coord moveCoord)
  {
    final Coord destCoord = new Coord(robotLocation.x() + moveCoord.x(), robotLocation.y() + moveCoord.y());
    if (walls.contains(destCoord)) return robotLocation;
    if (moveCoord.x() != 0)  // Horizontal move
    {
      if (boxLeftLocations.contains(destCoord) || boxRightLocations.contains(destCoord))
      {
        if (!canMoveBoxLeftOrRight(destCoord, moveCoord)) return robotLocation;
        moveBoxLeftOrRight(destCoord, moveCoord);
      }
    }
    else  // Vertical move
    {
      if (boxLeftLocations.contains(destCoord) || boxRightLocations.contains(destCoord))
      {
        if (!canMoveBoxUpOrDown(destCoord, moveCoord)) return robotLocation;
        moveBoxUpOrDown(destCoord, moveCoord);
      }
    }
    return destCoord;
  }

  private boolean canMoveBoxLeftOrRight(final Coord destCoord, final Coord moveCoord)
  {
    int x = destCoord.x();
    int y = destCoord.y();
    while (true)
    {
      final Coord newCoord = new Coord(x,y);
      if (walls.contains(newCoord)) return false;
      if (!(boxLeftLocations.contains(newCoord) || boxRightLocations.contains(newCoord))) return true;
      x += moveCoord.x();
    }
  }

  private void moveBoxLeftOrRight(final Coord destCoord, final Coord moveCoord)
  {
    int x = destCoord.x();
    int y = destCoord.y();
    if (moveCoord.x() < 0)  // moving left
    {
      if (!boxRightLocations.contains(destCoord))  throw new IllegalStateException("Call to move a box left that was not present: " + destCoord);
    }
    else  // moving right
    {
      if (!boxLeftLocations.contains(destCoord))  throw new IllegalStateException("Call to move a box right that was not present: " + destCoord);
    }
    final List<Coord> destBoxCoords = new ArrayList<>();
    while (true)
    {
      final Coord newCoord = new Coord(x,y);
      if (walls.contains(newCoord)) throw new IllegalStateException("Hit wall while moving boxes: " + newCoord);
      destBoxCoords.add(newCoord);
      if (!(boxLeftLocations.contains(newCoord) || boxRightLocations.contains(newCoord)))
      {
        if (moveCoord.x() < 0)  // moving left
        {
          boolean isRight = true;
          for (final Coord boxCoord : destBoxCoords)
          {
            if (boxCoord.equals(destCoord))
              boxRightLocations.remove(destCoord);  // Remove the original
            else if (isRight)
            {
              boxLeftLocations.remove(boxCoord);
              boxRightLocations.add(boxCoord);
              isRight = false;
            }
            else
            {
              boxRightLocations.remove(boxCoord);
              boxLeftLocations.add(boxCoord);
              isRight = true;
            }
          }
        }
        else  // moving right
        {
          boolean isLeft = true;
          for (final Coord boxCoord : destBoxCoords)
          {
            if (boxCoord.equals(destCoord))
              boxLeftLocations.remove(destCoord);  // Remove the original
            else if (isLeft)
            {
              boxRightLocations.remove(boxCoord);
              boxLeftLocations.add(boxCoord);
              isLeft = false;
            }
            else
            {
              boxLeftLocations.remove(boxCoord);
              boxRightLocations.add(boxCoord);
              isLeft = true;
            }
          }
        }
        return;
      }
      x += moveCoord.x();
    }
  }

  private boolean canMoveBoxUpOrDown(final Coord destCoord, final Coord moveCoord)
  {
    int x = destCoord.x();
    int y = destCoord.y();
    
    if (boxRightLocations.contains(destCoord))  x--;
    final Coord newCoord = new Coord(x,y);
    final List<Coord> boxes = List.of(newCoord);
    return canMoveBoxesUpOrDown(boxes, moveCoord);
  }

  private void moveBoxUpOrDown(final Coord destCoord, final Coord moveCoord)
  {
    int x = destCoord.x();
    int y = destCoord.y();
    
    if (boxRightLocations.contains(destCoord))  x--;
    final Coord newCoord = new Coord(x,y);
    final List<Coord> boxes = List.of(newCoord);
    moveBoxesUpOrDown(boxes, moveCoord);
  }

  private boolean canMoveBoxesUpOrDown(final List<Coord> boxes, final Coord moveCoord)
  {
    boolean anyBoxes = false;
    for (final Coord boxCoord : boxes)
    {
      final Coord checkCoord1 = new Coord(boxCoord.x(), boxCoord.y() + moveCoord.y());
      final Coord checkCoord2 = new Coord(boxCoord.x() + 1, boxCoord.y() + moveCoord.y());
      if (walls.contains(checkCoord1))  return false;
      if (walls.contains(checkCoord2))  return false;
      if (boxLeftLocations.contains(checkCoord1) || boxRightLocations.contains(checkCoord1))
        anyBoxes = true;
      if (boxLeftLocations.contains(checkCoord2) || boxRightLocations.contains(checkCoord2))
        anyBoxes = true;
    }

    if (!anyBoxes) return true;

    final List<Coord> newBoxes = new ArrayList<>();
    for (final Coord boxCoord : boxes)
    {
      final Coord checkCoord1 = new Coord(boxCoord.x(), boxCoord.y() + moveCoord.y());
      final Coord checkCoord2 = new Coord(boxCoord.x() + 1, boxCoord.y() + moveCoord.y());
      if (boxLeftLocations.contains(checkCoord1))
      {
        newBoxes.add(checkCoord1);
      }
      else if (boxRightLocations.contains(checkCoord1) && boxLeftLocations.contains(checkCoord2))
      {
        final Coord additionalLeftBox = new Coord(checkCoord1.x() - 1, checkCoord1.y());
        if (!newBoxes.contains(additionalLeftBox))
          newBoxes.add(additionalLeftBox);
        newBoxes.add(checkCoord2);
      }
      else if (boxRightLocations.contains(checkCoord1))
      {
        final Coord additionalLeftBox = new Coord(checkCoord1.x() - 1, checkCoord1.y());
        if (!newBoxes.contains(additionalLeftBox))
          newBoxes.add(additionalLeftBox);
      }
      else if (boxLeftLocations.contains(checkCoord2))
      {
        newBoxes.add(checkCoord2);
      }
    }

    return canMoveBoxesUpOrDown(newBoxes, moveCoord);
  }

  private void moveBoxesUpOrDown(final List<Coord> boxes, final Coord moveCoord)
  {
    boolean anyBoxes = false;
    for (final Coord boxCoord : boxes)
    {
      final Coord checkCoord1 = new Coord(boxCoord.x(), boxCoord.y() + moveCoord.y());
      final Coord checkCoord2 = new Coord(boxCoord.x() + 1, boxCoord.y() + moveCoord.y());
      if (walls.contains(checkCoord1))  throw new IllegalStateException("" + checkCoord1);
      if (walls.contains(checkCoord2))  throw new IllegalStateException("" + checkCoord2);
      if (boxLeftLocations.contains(checkCoord1) || boxRightLocations.contains(checkCoord1))
        anyBoxes = true;
      if (boxLeftLocations.contains(checkCoord2) || boxRightLocations.contains(checkCoord2))
        anyBoxes = true;
    }
    
    if (anyBoxes)
    {
      final List<Coord> newBoxes = new ArrayList<>();
      for (final Coord boxCoord : boxes)
      {
        final Coord checkCoord1 = new Coord(boxCoord.x(), boxCoord.y() + moveCoord.y());
        final Coord checkCoord2 = new Coord(boxCoord.x() + 1, boxCoord.y() + moveCoord.y());
        if (boxLeftLocations.contains(checkCoord1))
        {
          newBoxes.add(checkCoord1);
        }
        else if (boxRightLocations.contains(checkCoord1) && boxLeftLocations.contains(checkCoord2))
        {
          final Coord additionalLeftBox = new Coord(checkCoord1.x() - 1, checkCoord1.y());
          if (!newBoxes.contains(additionalLeftBox))
            newBoxes.add(additionalLeftBox);
          newBoxes.add(checkCoord2);
        }
        else if (boxRightLocations.contains(checkCoord1))
        {
          final Coord additionalLeftBox = new Coord(checkCoord1.x() - 1, checkCoord1.y());
          if (!newBoxes.contains(additionalLeftBox))
            newBoxes.add(additionalLeftBox);
        }
        else if (boxLeftLocations.contains(checkCoord2))
        {
          newBoxes.add(checkCoord2);
        }
      }
      moveBoxesUpOrDown(newBoxes, moveCoord);
    }
    
    // finally do the move
    for (final Coord boxCoord : boxes)
    {
      boxLeftLocations.add(new Coord(boxCoord.x(), boxCoord.y() + moveCoord.y()));       // add left  half 1 unit higher
      boxRightLocations.add(new Coord(boxCoord.x() + 1, boxCoord.y() + moveCoord.y()));  // add right half 1 unit higher
      boxLeftLocations.remove(boxCoord);                                     // remove original left  half
      boxRightLocations.remove(new Coord(boxCoord.x() + 1, boxCoord.y()));   // remove original right half
    }
  }
}

The runtime is pretty good

Part 1: 0.0290247s
Part 2: 0.02202s

Research Fellow Dr. Peter Falloon of the University of Western Australia (UWA), at Perth, submitted his Advent of Code 2024 solutions to the Wolfram Community Forums this week. They are all implemented in the Wolfram Language. He submitted his entries as a Wolfram Language Notebook – a computational essay.

Theo Grey – Wolfram Research employee #2 and the guy who made the Wooden Periodic Table Table – created Mathematica Notebooks back in 1988. In a 2019 blog post, Stephen Wolfram noted that Wolfram Notebooks created in V.1 of Mathematica can still be loaded and executed 30 years later (now 35 years). It’s remarkable that a fully-functional language can be actively grown for that long while maintaining functional compatibility back to Day 1. I don’t think there’s anything else in all of computerdom that has that backwards-compatibility. Clearly this has been a priority for Wolfram Research: it’s in their DNA.

When Stephen writes books, he’s really writing computational essays. His book What Is “ChatGPT Doing … and Why Does It Work?” (March 2023) was taken verbatim from his blog: Part1 and Part2. Steve Gibson said that this was one of the 3 AI/LLM books he was going to read over the holidays. These essays are an excellent way to explore LLMs, because the functions he’s invoking are rather obvious and can be easily grasped by anyone familiar with C, Java, Python – (and even Lisp, @Leo). If rich functions in the WL are unclear, they can always be looked up. Wolfram Research seems to have leveraged off of the intelligence of their rich and highly evolved API. They have a new code generator that works quite well – far better than the MASM code generation disaster that SteveG noted in ChatGPT.

@PHolder (and anyone else): if you’re curious how the WL compares to your solutions, it’s easy to look up Peter’s solutions in his Notebook.

1 Like