12 votes

Day 12: Rain Risk

Today's problem description: https://adventofcode.com/2020/day/12


Join the Tildes private leaderboard! You can do that on this page, by entering join code 730956-de85ce0c.

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

13 comments

  1. PapaNachos
    Link
    Got distracted by Cyberpunk again. Oops. This way pretty fun though. I got to reuse some of the techniques I used on earlier days And once again EUROBEAT MAKES YOU GOOD AT CODE Day 12 Part A –...

    Got distracted by Cyberpunk again. Oops. This way pretty fun though. I got to reuse some of the techniques I used on earlier days

    And once again EUROBEAT MAKES YOU GOOD AT CODE

    Day 12 Part A – Python

    For this one I handled direction by making an array that I would push and pop from to rotate. Sort of like the buffer on day 9. Then, if I ran into a forward command, I would just use the value at
    the front of the buffer.

    import re
    
    #directions = test_data.split('\n')
    directions = input_data.split('\n')
    
    facing = ['E','S','W','N']
    x_loc = 0
    y_loc = 0
    
    pattern = re.compile('([\w])(\d+)')
    
    for row in directions:
        match = pattern.search(row)
        letter = match[1]
        number = int(match[2])
        if letter == 'F':
            letter = facing[0]
        if letter == 'N':
            y_loc = y_loc + number
        elif letter == 'S':
            y_loc = y_loc - number
        elif letter == 'E':
            x_loc = x_loc + number
        elif letter == 'W':
            x_loc = x_loc - number
        elif letter == 'R':
            while number > 0:
                number = number - 90
                facing.append(facing.pop(0))
        elif letter == 'L':
            while number > 0:
                number = number - 90
                facing.insert(0,facing.pop(-1))
    print(x_loc, y_loc)
    
    Day 12 Part B – Python

    This wasn't too bad either. I just had to make some minor modifications to track the waypoint separate from the ship. The trickiest part was just figuring out how to rotate the waypoint, but I wrote down the coordinates and was able to figure out how to move in each direction.

    import re
    
    #directions = test_data.split('\n')
    directions = input_data.split('\n')
    
    
    x_loc = 0
    y_loc = 0
    
    waypoint_x = 10
    waypoint_y = 1
    
    pattern = re.compile('([\w])(\d+)')
    
    for row in directions:
        match = pattern.search(row)
        letter = match[1]
        number = int(match[2])
        if letter == 'F':
            x_loc = x_loc + (waypoint_x * number)
            y_loc = y_loc + (waypoint_y * number)
        if letter == 'N':
            waypoint_y = waypoint_y + number
        elif letter == 'S':
            waypoint_y = waypoint_y - number
        elif letter == 'E':
            waypoint_x = waypoint_x + number
        elif letter == 'W':
            waypoint_x = waypoint_x - number
        elif letter == 'R':
            while number > 0:
                number = number - 90
                temp = waypoint_x
                waypoint_x = waypoint_y
                waypoint_y = temp * -1
        elif letter == 'L':
            while number > 0:
                number = number - 90
                temp = waypoint_y
                waypoint_y = waypoint_x
                waypoint_x = temp * -1
    print(x_loc, y_loc)
    
    Tips and Commentary
    • I assumed 90 degree turns and that worked, it might help to simplify it. It also helps if you make it so that you can handle multiple magnitudes of turn in one smooth motion

    • Grabbing a pen and paper (or an excel spreadsheet) and drawing out how turning changes things may help. Especially for part B

    • You don't actually need a whole-ass grid, you can just track the relative positions and facing

    4 votes
  2. tomf
    Link
    Sheets! link to sheet This was awful at first, but once the structure was there, it all came together.

    Sheets! link to sheet

    This was awful at first, but once the structure was there, it all came together.

    4 votes
  3. JRandomHacker
    Link
    C# Part 1 public override string SolvePart1() { using (var fs = new StreamReader(Inputs.Year2020.Inputs2020.Input12)) { string line; int dir = 0; // 0 -> 3 CCW from east int x = 0; int y = 0;...

    C#

    Part 1
    public override string SolvePart1()
    {
    	using (var fs = new StreamReader(Inputs.Year2020.Inputs2020.Input12))
    	{
    		string line;
    		int dir = 0; // 0 -> 3 CCW from east
    		int x = 0;
    		int y = 0;
    		while ((line = fs.ReadLine()) != null)
    		{
    			switch (line[0])
    			{
    				case 'N':
    					y += int.Parse(line.Substring(1));
    					break;
    				case 'S':
    					y -= int.Parse(line.Substring(1));
    					break;
    				case 'E':
    					x += int.Parse(line.Substring(1));
    					break;
    				case 'W':
    					x -= int.Parse(line.Substring(1));
    					break;
    				case 'L':
    					dir = (dir + (int.Parse(line.Substring(1)) / 90)) % 4;
    					break;
    				case 'R':
    					dir = (dir + 4 - (int.Parse(line.Substring(1)) / 90)) % 4;
    					break;
    				case 'F':
    					switch (dir)
    					{
    						case 0:
    							x += int.Parse(line.Substring(1));
    							break;
    						case 1:
    							y += int.Parse(line.Substring(1));
    							break;
    						case 2:
    							x -= int.Parse(line.Substring(1));
    							break;
    						case 3:
    							y -= int.Parse(line.Substring(1));
    							break;
    						default:
    							throw new Exception();
    					}
    					break;
    				default:
    					throw new Exception();
    			}
    		}
    		return $"Distance is {Math.Abs(x) + Math.Abs(y)} at {x}, {y}";
    	}
    }
    
    Part 2
    public override string SolvePart2()
    {
    	using (var fs = new StreamReader(Inputs.Year2020.Inputs2020.Input12))
    	{
    		string line;
    		int x = 0;
    		int y = 0;
    		int wayX = 10;
    		int wayY = 1;
    		while ((line = fs.ReadLine()) != null)
    		{
    			switch (line[0])
    			{
    				case 'N':
    					wayY += int.Parse(line.Substring(1));
    					break;
    				case 'S':
    					wayY -= int.Parse(line.Substring(1));
    					break;
    				case 'E':
    					wayX += int.Parse(line.Substring(1));
    					break;
    				case 'W':
    					wayX -= int.Parse(line.Substring(1));
    					break;
    				case 'L':
    					var rotCountL = int.Parse(line.Substring(1)) / 90;
    					for (int i = 0; i < rotCountL; i++)
    					{
    						var temp = -1 * wayY;
    						wayY = wayX;
    						wayX = temp;
    					}
    					break;
    				case 'R':
    					var rotCountR = int.Parse(line.Substring(1)) / 90;
    					for (int i = 0; i < rotCountR; i++)
    					{
    						var temp = -1 * wayX;
    						wayX = wayY;
    						wayY = temp;
    					}
    					break;
    				case 'F':
    					x += wayX * int.Parse(line.Substring(1));
    					y += wayY * int.Parse(line.Substring(1));
    					break;
    				default:
    					throw new Exception();
    			}
    		}
    		return $"Distance is {Math.Abs(x) + Math.Abs(y)} at {x}, {y}";
    	}
    }
    
    Commentary I liked the twist for part 2 on this one. I was expecting a twist of non-90-degree rotations and not looking forward to it. If that were the case, I probably would have actually gone to actual rotation matrices, but the 90-degree rotations are easy enough to do with coordinate swap-and-inversion.
    3 votes
  4. [3]
    Crespyl
    Link
    Ruby This one is another pretty straightforward one, the only thing I got hung up on a few times was forgetting to account for the parameter to the L/R instructions. Part 1 def compute_p1(input)...

    Ruby

    This one is another pretty straightforward one, the only thing I got hung up on a few times was forgetting to account for the parameter to the L/R instructions.

    Part 1
    def compute_p1(input)
      x, y = 0, 0
      dirs = [[+1, 0], [0, +1], [-1, 0], [0, -1]]
      dir = 0 # 0 = east, 1 = south, 2 = west, 3 = north
      input
        .lines
        .map { |l| l.match(/^([NSEWLRF])(\d+)\n$/).captures }
        .each do |action, value|
          value = value.to_i
          #puts "%s: %i" % [action, value]
    
          case action
          when "N"
            y -= value
          when "S"
            y += value
          when "E"
            x += value
          when "W"
            x -= value
          when "R"
            dir = (dir + (value / 90)) % 4
          when "L"
            dir = (dir - (value / 90)) % 4
          when "F"
            x += dirs[dir][0] * value
            y += dirs[dir][1] * value
          end
    
          #puts "  (%i, %i, %i)" % [x, y, dir]
      end
    
      return (x.abs) + (y.abs)
    end
    
    Part 2
    def compute_p2(input)
      wx, wy = 10, -1
      x, y = 0, 0
    
      input
        .lines
        .map { |l| l.match(/^([NSEWLRF])(\d+)\n$/).captures }
        .each do |action, value|
          value = value.to_i
          #puts "%s: %i" % [action, value]
    
          case action
          when "N"
            wy -= value
          when "S"
            wy += value
          when "E"
            wx += value
          when "W"
            wx -= value
          when "R"
            (value/90).times do
              wx, wy = wy * -1, wx
            end
          when "L"
            (value/90).times do
              wx, wy = wy, wx * -1
            end
          when "F"
            x += (wx * value)
            y += (wy * value)
          end
    
          #puts "  (%i, %i), (%i, %i)" % [x, y, wx, wy]
      end
    
      return (x.abs) + (y.abs)
    end
    
    3 votes
    1. [2]
      leif
      Link Parent
      Ooh chucking the directions into an array and rotating by moving between elements is a good idea. I manually wrote down the (x, y) -> (-y, x) transformations for 90 degrees left, 90 degrees right,...

      Ooh chucking the directions into an array and rotating by moving between elements is a good idea. I manually wrote down the (x, y) -> (-y, x) transformations for 90 degrees left, 90 degrees right, etc.

      3 votes
      1. Crespyl
        Link Parent
        Haha, yeah I've used that trick before in other situations, saves some repetition and makes it easier to extend if you have to include the corners as well (NE/NW/SE/SW). It also helps that the...

        Haha, yeah I've used that trick before in other situations, saves some repetition and makes it easier to extend if you have to include the corners as well (NE/NW/SE/SW).

        It also helps that the input only deals in rotations that are whole multiples of 90 degrees. I saw a few other solutions using complex numbers for "real" rotation math, which would make it a lot easier to handle a trickier input.

        I think that would've made my part 2 "rotate around origin" cleaner too.

        3 votes
  5. Nuolong
    Link
    Python Repo Link In short, I think I overcomplicated this relatively simple problem. I ended up using a dictionary to mark the ship/waypoint coordinates. I realized it would have been easier to...

    Python
    Repo Link

    In short, I think I overcomplicated this relatively simple problem. I ended up using a dictionary to mark the ship/waypoint coordinates. I realized it would have been easier to just use cartesian coordinates but I decided to go this route anyway.

    Part 1 I used a `pointing` variable to keep track of which direction the ship was facing. For moving the ship, I would add the value to the appropriate direction and subtract the value from the opposite. In the end, I was only interested in the absolute `E` and `N` values (which would be analogous to the `x` and `y` in a normal approach.
    #!/usr/bin/env python3
    
    import sys
    
    DIRECTIONS = ['E', 'S', 'W', 'N']
    
    def travel(dir_lst):
        status = {'E' : 0, 'S' : 0, 'W' : 0, 'N' : 0}
        pointing = 0 # East
    
        for dir in dir_lst:
            if dir[0] in DIRECTIONS:
                status[dir[0]] += dir[1]
                opposite = (DIRECTIONS.index(dir[0]) + 2) % len(DIRECTIONS)
                status[DIRECTIONS[opposite]] -= dir[1]
            elif dir[0] == 'L':
                pointing -= (dir[1] // 90) % len(DIRECTIONS)
                pointing %= len(DIRECTIONS)
            elif dir[0] == 'R':
                pointing += (dir[1] // 90) % len(DIRECTIONS)
                pointing %= len(DIRECTIONS)
            elif dir[0] == 'F':
                status[DIRECTIONS[pointing]] += dir[1]
                opposite = (pointing + 2) % len(DIRECTIONS)
                status[DIRECTIONS[opposite]] -= dir[1]
    
        print(abs(status['E']) + abs(status['N']))
    
    
    def main():
        dir_lst = []
        for dir in sys.stdin:
            dir_lst.append((dir[0], int(dir[1:])))
    
        travel(dir_lst)
    
    
    if __name__ == '__main__':
        main()
    
    Part 2 I used a similar approach as part 1. As for rotating the waypoint, I just rotated the values of my dictionary manually.
    #!/usr/bin/env python3
    
    import sys
    
    DIRECTIONS = ['E', 'S', 'W', 'N']
    
    def travel(dir_lst):
        status = {'E' : 0, 'S' : 0, 'W' : 0, 'N' : 0}
        waypoint = {'E' : 10, 'S' : -1, 'W' : -10, 'N' : 1}
    
        for dir in dir_lst:
            if dir[0] in DIRECTIONS:
                waypoint[dir[0]] += dir[1]
                opposite = (DIRECTIONS.index(dir[0]) + 2) % len(DIRECTIONS)
                waypoint[DIRECTIONS[opposite]] -= dir[1]
            elif dir[0] == 'L':
                for _ in range(dir[1] // 90):
                    temp_1 = waypoint['E']
                    waypoint['E'] = waypoint['S']
                    waypoint['S'] = waypoint['W']
                    waypoint['W'] = waypoint['N']
                    waypoint['N'] = temp_1
            elif dir[0] == 'R':
                for _ in range(dir[1] // 90):
                    temp_1 = waypoint['E']
                    waypoint['E'] = waypoint['N']
                    waypoint['N'] = waypoint['W']
                    waypoint['W'] = waypoint['S']
                    waypoint['S'] = temp_1
            elif dir[0] == 'F':
                status['E'] += waypoint['E'] * dir[1]
                status['S'] += waypoint['S'] * dir[1]
                status['W'] += waypoint['W'] * dir[1]
                status['N'] += waypoint['N'] * dir[1]
    
        print(abs(status['E']) + abs(status['N']))
    
    def main():
        dir_lst = []
        for dir in sys.stdin:
            dir_lst.append((dir[0], int(dir[1:])))
    
        travel(dir_lst)
    
    
    if __name__ == '__main__':
        main()
    
    3 votes
  6. jzimbel
    (edited )
    Link
    Elixir I'm not sure whether to be proud or ashamed of my solution. I have a bad habit of trying to generalize my part 1 solution to work with both part 1 and part 2 rather than just addressing...

    Elixir

    I'm not sure whether to be proud or ashamed of my solution. I have a bad habit of trying to generalize my part 1 solution to work with both part 1 and part 2 rather than just addressing each one independently when that makes sense to do, so I think I really overdid the abstraction for this one.

    I'm also bad at math so my point rotation logic is probably not as concise as it could be.

    I also decided to not parse the input beyond splitting the lines into a list, which made for some fun practice with binary pattern matching. Plus, I dipped my toes into macro-land to generate multiple function clauses that handle all of the direction/rotation actions. Another solution with zero if/case expressions! (except for all of the implicit ones in the function clauses)

    Part 1 runs in 245 μs; part 2 in 258 μs. 🏎

    Edit: I offloaded the macro stuff concerned with translating 'NESW', 'LR' to the appropriate unit vector/rotation direction sign to the Point module. This made things a lot cleaner and better follows separation of concerns, I think.

    Point data structure / functions
    defmodule AdventOfCode.Point do
      @moduledoc """
      Functions for manipulating integer-valued points on an image plane,
      where +x == east/right and +y == south/down.
      """
    
      @origin {0, 0}
    
      @type t :: {integer(), integer()}
    
      @type direction :: ?N | ?E | ?S | ?W
      @type rot_direction :: ?L | ?R
    
      defguardp is_cardinal(deg) when rem(deg, 90) == 0
    
      @doc """
      Translates `pt` by `dir`, `mag` times.
    
      `dir` can be either an `{x, y}` pair,
      or one of `?N` `?E` `?S` `?W`; each representing a unit vector in that direction.
      """
      @spec translate(t(), t() | direction, integer()) :: t()
      def translate(pt, dir, mag \\ 1)
    
      def translate({x, y}, {dx, dy}, mag) do
        {x + mag * dx, y + mag * dy}
      end
    
      for {dir, unit_vec} <- %{?N => {0, -1}, ?E => {1, 0}, ?S => {0, 1}, ?W => {-1, 0}} do
        def translate(pt, unquote(dir), mag) do
          translate(pt, unquote(unit_vec), mag)
        end
      end
    
      @doc """
      Rotates `pt` about `center` by `deg` degrees, in the direction of `dir`.
    
      `deg` must be a multiple of 90.
    
      A `dir` value of `?L` or 1 represents counterclockwise rotation; `?R` or -1 clockwise.
    
      `center` can be omitted to perform a rotation about the origin.
      """
      @spec rotate(t(), t(), non_neg_integer, -1 | 1 | rot_direction) :: t()
      def rotate(pt, center \\ @origin, deg, rot_dir)
    
      def rotate(pt, _, 0, _), do: pt
    
      def rotate(pt, @origin, deg, rot_dir) when is_cardinal(deg) do
        do_rotate(pt, deg, rotater_for(rot_dir))
      end
    
      def rotate(pt, {cx, cy}, deg, rot_dir) when is_cardinal(deg) do
        pt
        |> translate({-cx, -cy})
        |> rotate(deg, rot_dir)
        |> translate({cx, cy})
      end
    
      defp do_rotate(pt, 0, _), do: pt
    
      defp do_rotate(pt, deg, rotater), do: do_rotate(rotater.(pt), deg - 90, rotater)
    
      defp rotater_for(?L), do: rotater_for(1)
      defp rotater_for(?R), do: rotater_for(-1)
      defp rotater_for(sign), do: fn {x, y} -> {sign * y, -sign * x} end
    end
    
    Parts 1 and 2
    defmodule AdventOfCode.Day12 do
      defmodule Ship do
        @enforce_keys ~w[cursor nav_mod]a
        defstruct posn: {0, 0},
                  cursor: nil,
                  nav_mod: nil
    
        def navigate(%Ship{} = t, []), do: t
    
        def navigate(%Ship{} = t, [action | actions]) do
          t
          |> t.nav_mod.do_action(action)
          |> navigate(actions)
        end
      end
    
      defmodule Nav1 do
        import AdventOfCode.Point
    
        def do_action(ship, <<rot_dir, deg::binary>>) when rot_dir in 'LR' do
          %{ship | cursor: rotate(ship.cursor, String.to_integer(deg), rot_dir)}
        end
    
        def do_action(ship, <<?F, mag::binary>>) do
          %{ship | posn: translate(ship.posn, ship.cursor, String.to_integer(mag))}
        end
    
        def do_action(ship, <<dir, mag::binary>>) when dir in 'NESW' do
          %{ship | posn: translate(ship.posn, dir, String.to_integer(mag))}
        end
      end
    
      defmodule Nav2 do
        import AdventOfCode.Point
    
        def do_action(ship, <<dir, mag::binary>>) when dir in 'NESW' do
          %{ship | cursor: translate(ship.cursor, dir, String.to_integer(mag))}
        end
    
        def do_action(ship, action) do
          Nav1.do_action(ship, action)
        end
      end
    
      def part1(args) do
        get_final_posn(%Ship{cursor: {1, 0}, nav_mod: Nav1}, args)
      end
    
      def part2(args) do
        get_final_posn(%Ship{cursor: {10, -1}, nav_mod: Nav2}, args)
      end
    
      defp get_final_posn(ship, input) do
        ship
        |> Ship.navigate(String.split(input))
        |> Map.get(:posn)
        |> manhattan({0, 0})
      end
    
      defp manhattan({x1, y1}, {x2, y2}), do: abs(x2 - x1) + abs(y2 - y1)
    end
    
    3 votes
  7. Gyrfalcon
    Link
    Language: Julia Repository This was a pretty simple one, though I did forget that turns could be more than 90 degrees at first. Actually ended up using trig functions in part 2 because I think...

    This was a pretty simple one, though I did forget that turns could be more than 90 degrees at first. Actually ended up using trig functions in part 2 because I think that ended up being the least work. Had to look up how that rotation works though because this year has made it feel like an eternity since I thought about trig!

    Part 1
    function main()
    
        input = []
        open("Day12/input.txt") do fp
            input = readlines(fp)
        end
    
        input = map(x -> (x[1], parse(Int, x[2:end])), input)
    
        result_1 = manhattan_distance(input)
    
        println(result_1)
    
    end
    
    function manhattan_distance(directions)
        east = 0
        north = 0
        heading = 0
        for instruction in directions
            if instruction[1] == 'N'
                north += instruction[2]
            elseif instruction[1] == 'S'
                north -= instruction[2]
            elseif instruction[1] == 'E'
                east += instruction[2]
            elseif instruction[1] == 'W'
                east -= instruction[2]
            elseif instruction[1] == 'R'
                heading = mod(heading + instruction[2], 360)
            elseif instruction[1] == 'L'
                heading = mod(heading - instruction[2], 360)
            else
                if heading == 0
                    east += instruction[2]
                elseif heading == 90
                    north -= instruction[2]
                elseif heading == 180
                    east -= instruction[2]
                else
                    north += instruction[2]
                end
                
            end
        end
        return abs(east) + abs(north)
    end
    
    
    
    main()
    
    Part 2 diff
    @@ -11,6 +11,10 @@ function main()
     
         println(result_1)
     
    +    result_2 = waypoint_distance(input)
    +
    +    println(result_2)
    +
     end
     
     function manhattan_distance(directions)
    @@ -46,6 +50,35 @@ function manhattan_distance(directions)
         return abs(east) + abs(north)
     end
     
    +function waypoint_distance(directions)
    +    waypoint = [10, 1] # East, north
    +    ship = [0, 0] # East, north
    +
    +    for instruction in directions
    +
    +        if instruction[1] == 'N'
    +            waypoint[2] += instruction[2]
    +        elseif instruction[1] == 'S'
    +            waypoint[2] -= instruction[2]
    +        elseif instruction[1] == 'E'
    +            waypoint[1] += instruction[2]
    +        elseif instruction[1] == 'W'
    +            waypoint[1] -= instruction[2]
    +        elseif instruction[1] == 'R'
    +            theta = instruction[2]
    +            waypoint = [waypoint[1] * cosd(theta) + waypoint[2] * sind(theta),
    +                        -waypoint[1] * sind(theta) + waypoint[2] * cosd(theta)]
    +        elseif instruction[1] == 'L'
    +            theta = -instruction[2]
    +            waypoint = [waypoint[1] * cosd(theta) + waypoint[2] * sind(theta),
    +                        -waypoint[1] * sind(theta) + waypoint[2] * cosd(theta)]
    +        else
    +            ship = ship .+ (waypoint .* instruction[2])
    +        end
    +    end
    +
    +    return sum(abs.(ship))
    +end
     
    +main()
     
    -main()
    
    2 votes
  8. pnutzh4x0r
    Link
    Python Repo Link Part 1 This took some time in terms of thinking about to handle the orientations, but I think I figured a nice way without having to hardcode too much. import sys # Constants...

    Python

    Repo Link

    Part 1

    This took some time in terms of thinking about to handle the orientations, but I think I figured a nice way without having to hardcode too much.

    import sys
    
    # Constants
    
    ORIENTATIONS = (
        ( 1,  0),   # E
        ( 0, -1),   # S
        (-1,  0),   # W
        ( 0,  1),   # N
    )
    
    # Functions
    
    def manhattan_distance(x, y):
        return abs(x) + abs(y)
    
    def navigate_ship():
        x, y, orientation = 0, 0, 0
    
        for line in sys.stdin:
            direction, magnitude = line[0], int(line[1:].strip())
            
            if direction == 'N':
                y += magnitude
            elif direction == 'S':
                y -= magnitude
            elif direction == 'E':
                x += magnitude
            elif direction == 'W':
                x -= magnitude
            elif direction == 'R':
                orientation = (orientation + magnitude // 90) % len(ORIENTATIONS)
            elif direction == 'L':
                orientation = (orientation - magnitude // 90) % len(ORIENTATIONS)
            elif direction == 'F':
                x += magnitude * ORIENTATIONS[orientation][0]
                y += magnitude * ORIENTATIONS[orientation][1]
    
        return x, y
    
    # Main Execution
    
    def main():
        x, y     = navigate_ship()
        distance = manhattan_distance(x, y)
    
        print(distance)
    
    if __name__ == '__main__':
        main()
    
    Part 2

    For this one, I had to sit down with a piece of paper and draw out the rotations. Once I did that, I could see that I just needed to swap the waypoints x and y and negate either the x or y coordinate (depending on the direction). After that, it was pretty much the same as Part 1.

    import sys
    
    # Functions
    
    def manhattan_distance(x, y):
        return abs(x) + abs(y)
    
    def navigate_ship():
        sx, sy, wx, wy = 0, 0, 10, 1
    
        for line in sys.stdin:
            direction, magnitude = line[0], int(line[1:].strip())
            
            if direction == 'N':
                wy += magnitude
            elif direction == 'S':
                wy -= magnitude
            elif direction == 'E':
                wx += magnitude
            elif direction == 'W':
                wx -= magnitude
            elif direction == 'R':
                for _ in range(magnitude // 90):
                    wx, wy =  wy, -wx   # Flip and negate new y
            elif direction == 'L':
                for _ in range(magnitude // 90):
                    wx, wy = -wy, wx    # Flip and negate new x
            elif direction == 'F':
                sx += magnitude * wx
                sy += magnitude * wy
    
        return sx, sy
    
    # Main Execution
    
    def main():
        x, y     = navigate_ship()
        distance = manhattan_distance(x, y)
    
        print(distance)
    
    if __name__ == '__main__':
        main()
    
    2 votes
  9. wycy
    (edited )
    Link
    Rust My first time doing this type of problem with rotations was in AoC 2018 which I did in Fortran. Fortran's MATMUL made it easy to apply a rotation matrix via matrix multiplication. For this...

    Rust

    My first time doing this type of problem with rotations was in AoC 2018 which I did in Fortran. Fortran's MATMUL made it easy to apply a rotation matrix via matrix multiplication. For this case using Rust, I wrote my own rotation_matmul function to multiply a 2x2 matrix by a 2x1 matrix.

    Rust
    use std::env;
    use std::io::{prelude::*, BufReader};
    use std::fs::File;
    
    #[derive(Debug)]
    struct Action {
        direction: Direction,
        magnitude: i64,
    }
    impl From<&String> for Action {
        fn from(s: &String) -> Self {
            use Direction::*;
            Self {
                direction: match s.chars().next().unwrap() {
                    'N' => North,
                    'S' => South,
                    'E' => East,
                    'W' => West,
                    'L' => Left,
                    'R' => Right,
                    'F' => Forward,
                    other => panic!("Unknown character: {}", other),
                },
                magnitude: s[1..].parse().unwrap(),
            }
        }
    }
    #[derive(Debug)]
    enum Direction {
        North,
        South,
        East,
        West,
        Left,
        Right,
        Forward,
    }
    
    // Rotation matrices
    const ROTATE_CW: (i64,i64,i64,i64) = (0, 1, -1, 0);
    const ROTATE_CCW: (i64,i64,i64,i64) = (0, -1, 1, 0);
    
    fn rotation_matmul(m2x2: (i64,i64,i64,i64), m2x1: (i64,i64)) -> (i64,i64) {
        (m2x2.0 * m2x1.0 + m2x2.1 * m2x1.1, m2x2.2 * m2x1.0 + m2x2.3 * m2x1.1)
    }
    
    fn day12(input: &str) {
        use Direction::*;
        let file = File::open(input).expect("Input file not found.");
        let reader = BufReader::new(file);
        let input: Vec<_> = match reader.lines().collect() {
            Err(err) => panic!("Unknown error reading input: {}", err),
            Ok(result) => result,
        };
        let directions = input
            .iter()
            .map(Action::from)
            .collect::<Vec<_>>();
    
        // Part 1
        let mut facing = (1,0);
        let mut pos = (0,0);
        for dir in &directions {
            match dir.direction {
                North => { pos.1 += dir.magnitude }, 
                South => { pos.1 -= dir.magnitude },
                East  => { pos.0 += dir.magnitude },
                West  => { pos.0 -= dir.magnitude },
                Left  => {
                    for _ in 0..dir.magnitude / 90 {
                        facing = rotation_matmul(ROTATE_CCW,facing);
                    }
                },
                Right   => {
                    for _ in 0..dir.magnitude / 90 {
                        facing = rotation_matmul(ROTATE_CW,facing);
                    }
                },
                Forward => {
                    pos.0 += dir.magnitude * facing.0;
                    pos.1 += dir.magnitude * facing.1;
                },
            }
        }
        let part1 = (pos.0).abs() + (pos.1).abs();
        println!("Part 1: {}", part1); // 962
    
        // Part 2
        let mut pos = (0,0);
        let mut waypoint = (10,1);
        for dir in &directions {
            match dir.direction {
                North => { waypoint.1 += dir.magnitude }, 
                South => { waypoint.1 -= dir.magnitude },
                East  => { waypoint.0 += dir.magnitude },
                West  => { waypoint.0 -= dir.magnitude },
                Left  => {
                    for _ in 0..dir.magnitude / 90 {
                        waypoint = rotation_matmul(ROTATE_CCW,waypoint);
                    }
                },
                Right   => {
                    for _ in 0..dir.magnitude / 90 {
                        waypoint = rotation_matmul(ROTATE_CW,waypoint);
                    }
                },
                Forward => {
                    pos.0 += dir.magnitude * waypoint.0;
                    pos.1 += dir.magnitude * waypoint.1;
                },
            }
        }
        let part2 = (pos.0).abs() + (pos.1).abs();
        println!("Part 2: {}", part2); // 56135
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        day12(&filename);
    }
    
    2 votes
  10. nothis
    Link
    Python It worries me a little that I had the same problem as yesterday at first: Test input passed, actual solution failed (for part 2). Only retyping it from scratch helped, as I didn't actually...

    Python

    It worries me a little that I had the same problem as yesterday at first: Test input passed, actual solution failed (for part 2). Only retyping it from scratch helped, as I didn't actually find the mistake. That's why part 1 and 2 use different ways of dealing with rotation. I initially just always stored the direction in degrees and flipped based on that value. For part 2, I now "actually" rotate the waypoint's coordinates in 90° increments.

    Part 1+2
    with open("input.txt") as inputFile:
        instructions = [{"command": ins[0], "value": int(ins[1:])}
                        for ins in inputFile.read().split("\n")]
    
    
    def steerShip(instructions):
        ship = {
            "N": 0,
            "E": 0,
            "direction": 0
        }
    
        for ins in instructions:
            if ins["command"] == "N":
                ship["N"] += ins["value"]
            elif ins["command"] == "S":
                ship["N"] -= ins["value"]
            elif ins["command"] == "E":
                ship["E"] += ins["value"]
            elif ins["command"] == "W":
                ship["E"] -= ins["value"]
    
            elif ins["command"] == "L":
                ship["direction"] = (
                    ship["direction"] - ins["value"]) % 360
            elif ins["command"] == "R":
                ship["direction"] = (
                    ship["direction"] + ins["value"]) % 360
            elif ins["command"] == "F":
                if ship["direction"] == 0:
                    ship["E"] += ins["value"]  # east
                elif ship["direction"] == 90:
                    ship["N"] -= ins["value"]  # south
                elif ship["direction"] == 180:
                    ship["E"] -= ins["value"]  # west
                elif ship["direction"] == 270:
                    ship["N"] += ins["value"]  # north
        print("Manhattan distance (no waypoint) =",
              abs(ship["N"]) + abs(ship["E"]))
    
    
    def steerWaypoint(instructions):
        waypoint = {
            "N": 1,
            "E": 10
        }
        ship = {
            "N": 0,
            "E": 0
        }
    
        for ins in instructions:
            if ins["command"] == "N":
                waypoint["N"] += ins["value"]
            elif ins["command"] == "S":
                waypoint["N"] -= ins["value"]
            elif ins["command"] == "E":
                waypoint["E"] += ins["value"]
            elif ins["command"] == "W":
                waypoint["E"] -= ins["value"]
    
            elif ins["command"] == "L":
                for i in range(0, int(ins["value"]/90)):
                    waypoint = {"N": waypoint["E"],
                                "E": -waypoint["N"]}  # rotate left
            elif ins["command"] == "R":
                for i in range(0, int(ins["value"]/90)):
                    waypoint = {"N": -waypoint["E"],
                                "E": waypoint["N"]}  # rotate right
            elif ins["command"] == "F":
                ship["N"] += waypoint["N"] * ins["value"]
                ship["E"] += waypoint["E"] * ins["value"]
        print("Manhattan distance (waypoint) =", abs(ship["N"]) + abs(ship["E"]))
    
    
    steerShip(instructions)
    steerWaypoint(instructions)
    
    2 votes
  11. 3d12
    Link
    I didn't quite enjoy this one as much as the cellular automation of day 11, but this was still pretty entertaining to code overall. Like some others, I too got hung up on the L-R rotations in Part...

    I didn't quite enjoy this one as much as the cellular automation of day 11, but this was still pretty entertaining to code overall. Like some others, I too got hung up on the L-R rotations in Part 2. I was definitely expecting them to go the complex-degrees-math route so it was nice to see that everything stayed in even increments of 90.

    e: Oh! And I almost forgot, this one led me to run into another annoying "gotcha" of Javascript that betrays my assumptions built on previous experience with other languages... At least this was relatively easy to track down, since it just meant my direction turning was totally off.

    Part 1
    const fs = require('fs');
    const readline = require('readline');
    
    let instructionsArray = []
    
    async function openFileForReading(file) {
    	const fileStream = fs.createReadStream(file);
    
    	const rl = readline.createInterface({
    		input: fileStream,
    		crlfDelay: Infinity
    	});
    
    	for await (const line of rl) {
    		instructionsArray.push(line);
    	}
    }
    
    function parseLine(line) {
    	let regex = /(\w)(\d+)/;
    	let found = line.match(regex);
    	let direction = found[1];
    	let amount = found[2];
    	let action = undefined;
    	if (['N','S','E','W','F'].includes(direction)) {
    		action = 'move';
    	} else if (['L','R'].includes(direction)) {
    		action = 'turn';
    	}
    	return { action, direction, amount };
    }
    
    class Ship {
    	constructor() {
    		this.x = 0;
    		this.y = 0;
    		this.direction = 90;
    	}
    
    	moveShip(direction, amount) {
    		if (direction == 'F') {
    			direction = this.facingDirection(this.direction);
    		}
    		switch(direction) {
    			case 'N': this.y += parseInt(amount); break;
    			case 'S': this.y -= parseInt(amount); break;
    			case 'E': this.x += parseInt(amount); break;
    			case 'W': this.x -= parseInt(amount); break;
    		}
    	}
    
    	facingDirection(degrees) {
    		switch(degrees) {
    			case 0: return 'N';
    			case 90: return 'E';
    			case 180: return 'S';
    			case 270: return 'W';
    		}
    	}
    
    	turnShip(direction, amount) {
    		switch(direction) {
    			case 'L': let tempDirection = (this.direction - parseInt(amount));
    				if (tempDirection < 0) {
    					this.direction = (tempDirection + 360);
    				} else {
    					this.direction = (tempDirection % 360);
    				}
    				break;
    			case 'R': this.direction = ((this.direction + parseInt(amount)) % 360); break;
    		}
    	}
    
    	currentCoordinates() {
    		return this.x + "," + this.y;
    	}
    }
    
    function manhattanDistance(x1, x2, y1, y2) {
    	return (Math.abs(x1 - x2) + Math.abs(y1 - y2));
    }
    
    (async function mainExecution() {
    	await openFileForReading('input.txt');
    	let theShip = new Ship();
    	console.log("DEBUG: Ship is currently at " + theShip.currentCoordinates() + " - facing: " + theShip.facingDirection(theShip.direction));
    	for (const inst of instructionsArray) {
    		let parsedLine = parseLine(inst);
    		console.log(parsedLine);
    		switch (parsedLine.action) {
    			case 'move': theShip.moveShip(parsedLine.direction, parsedLine.amount); break;
    			case 'turn': theShip.turnShip(parsedLine.direction, parsedLine.amount); break;
    		}
    		console.log("DEBUG: Ship is currently at " + theShip.currentCoordinates() + " - facing " + theShip.facingDirection(theShip.direction));
    	}
    	console.log("Done! Manhattan distance from 0,0 is: " + manhattanDistance(0, theShip.x, 0, theShip.y));
    })();
    
    Part 2
    const fs = require('fs');
    const readline = require('readline');
    
    let instructionsArray = []
    
    async function openFileForReading(file) {
    	const fileStream = fs.createReadStream(file);
    
    	const rl = readline.createInterface({
    		input: fileStream,
    		crlfDelay: Infinity
    	});
    
    	for await (const line of rl) {
    		instructionsArray.push(line);
    	}
    }
    
    function parseLine(line) {
    	let regex = /(\w)(\d+)/;
    	let found = line.match(regex);
    	let direction = found[1];
    	let amount = found[2];
    	let action = undefined;
    	if (['N','S','E','W','F'].includes(direction)) {
    		action = 'move';
    	} else if (['L','R'].includes(direction)) {
    		action = 'turn';
    	}
    	return { action, direction, amount };
    }
    
    class Ship {
    	constructor() {
    		this.x = 0;
    		this.y = 0;
    	}
    
    	move(waypoint) {
    		this.x += waypoint.offsetX;
    		this.y += waypoint.offsetY;
    	}
    
    	currentCoordinates() {
    		return this.x + "," + this.y;
    	}
    }
    
    class Waypoint {
    	constructor() {
    		this.offsetX = 10;
    		this.offsetY = 1;
    	}
    
    	rotate(direction, amount) {
    		switch(direction) {
    			case 'L': for (let i = (amount / 90); i > 0; i--) {
    				let tempOffsetX = this.offsetX;
    				this.offsetX = -this.offsetY;
    				this.offsetY = tempOffsetX;
    				}
    				break;
    			case 'R': for (let i = (amount / 90); i > 0; i--) {
    				let tempOffsetX = this.offsetX;
    				this.offsetX = this.offsetY;
    				this.offsetY = -tempOffsetX;
    				}
    				break;
    		}
    	}
    
    	move(direction, amount) {
    		switch(direction) {
    			case 'N': this.offsetY += parseInt(amount); break;
    			case 'S': this.offsetY -= parseInt(amount); break;
    			case 'E': this.offsetX += parseInt(amount); break;
    			case 'W': this.offsetX -= parseInt(amount); break;
    		}
    	}
    
    	currentCoordinates() {
    		return this.offsetX + "," + this.offsetY;
    	}
    }
    
    function manhattanDistance(x1, x2, y1, y2) {
    	return (Math.abs(x1 - x2) + Math.abs(y1 - y2));
    }
    
    (async function mainExecution() {
    	await openFileForReading('input.txt');
    	let theShip = new Ship();
    	let theWaypoint = new Waypoint();
    	console.log("DEBUG: Ship is currently at " + theShip.currentCoordinates() + " - waypoint offsets: " + theWaypoint.currentCoordinates());
    	for (const inst of instructionsArray) {
    		let parsedLine = parseLine(inst);
    		console.log(parsedLine);
    		switch (parsedLine.action) {
    			case 'move':
    				if (parsedLine.direction == 'F') {
    					for (let i = parsedLine.amount; i > 0; i--) {
    						theShip.move(theWaypoint);
    					}
    				} else {
    					theWaypoint.move(parsedLine.direction, parsedLine.amount);
    				}
    				break;
    			case 'turn': theWaypoint.rotate(parsedLine.direction, parsedLine.amount); break;
    		}
    		console.log("DEBUG: Ship is currently at " + theShip.currentCoordinates() + " - waypoint offsets: " + theWaypoint.currentCoordinates());
    	}
    	console.log("Done! Manhattan distance from 0,0 is: " + manhattanDistance(0, theShip.x, 0, theShip.y));
    })();
    
    1 vote