Advent of Code 2021 - Day 2

My solution, simple but honest:

Part 1:

defmodule Submarine do

  def calc_position(list) do
    forward_sum = sum_movement(list, "forward")
    up_sum = sum_movement(list, "up")
    down_sum = sum_movement(list, "down")  
    depth = down_sum - up_sum
    forward_sum * depth
  end

  defp sum_movement(movs, type) do
    movs
      |> Enum.filter(fn [k,_v] -> k == type end)
      |> Enum.map(fn [_k,v] -> String.to_integer(v) end)
      |> Enum.reduce(fn x, acc -> x + acc end)
  end
end

input
|> String.split(" ")
|> Enum.chunk_every(2)
|> Submarine.calc_position

Part 2:

defmodule Submarine2 do

  def calc_position(list) do
    _calc(list, 0, 0, 0)
  end

  defp _calc([head | tail], aim, horizontal, depth) do
    [key, value] = head
    cond do
      key == "forward" ->
        new_horizontal = horizontal + String.to_integer(value)
        _calc(tail, aim, new_horizontal, calc_depth(depth, aim, String.to_integer(value)) )
      key == "down" ->
        _calc(tail, aim + String.to_integer(value), horizontal, depth)
      key == "up" ->
        _calc(tail, aim - String.to_integer(value), horizontal, depth)
    end
  end

  defp _calc([], _, horizontal, depth) do    
    horizontal * depth
  end

  defp calc_depth(depth, aim, forward_value) do  
    depth + (aim * forward_value)
  end
end

input
|> String.split(" ")
|> Enum.chunk_every(2)
|> Submarine2.calc_position

My simple solution with reduce and pattern matching:

defmodule AdventOfCode.Day02 do
  def part1(input) do
    {hor, dep} = input |> parse_input() |> Enum.reduce({0, 0}, &move/2)
    hor * dep
  end

  def part2(input) do
    {hor, dep, _aim} = input |> parse_input() |> Enum.reduce({0, 0, 0}, &move_2/2)
    hor * dep
  end

  def move({:up, x}, {hor, dep}), do: {hor, dep - x}
  def move({:down, x}, {hor, dep}), do: {hor, dep + x}
  def move({:forward, x}, {hor, dep}), do: {hor + x, dep}

  def move_2({:down, x}, {hor, dep, aim}), do: {hor, dep, aim + x}
  def move_2({:up, x}, {hor, dep, aim}), do: {hor, dep, aim - x}
  def move_2({:forward, x}, {hor, dep, aim}), do: {hor + x, dep + aim * x, aim}

  def parse_input(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&String.split/1)
    |> Enum.map(fn [d, x] -> {String.to_atom(d), String.to_integer(x)} end)
  end
end

Hello there :wave: It’s time for Livebook again

# Day 2

## Input

<!-- livebook:{"livebook_object":"cell_input","name":"input","type":"text","value":"forward 5 down 5 forward 8 up 3 down 8 forward 2"} -->

```elixir
processed =
  IO.gets(:input)
  |> String.split(~r{\s}, trim: true)
  |> Enum.chunk_every(2)
  |> Enum.map(fn [x, y] -> {String.to_atom(x), String.to_integer(y)} end)
```

## Part 1

```elixir
processed
|> Enum.reduce(
  [0, 0],
  fn {command, n}, [x, y] ->
    case command do
      :forward ->
        [x + n, y]

      :up ->
        [x, y - n]

      :down ->
        [x, y + n]
    end
  end
)
|> Enum.product()
```

## Part 2

```elixir
processed
|> Enum.reduce(
  [0, 0, 0],
  fn {command, n}, [x, y, aim] ->
    case command do
      :forward ->
        [x + n, y + aim * n, aim]

      :up ->
        [x, y, aim - n]

      :down ->
        [x, y, aim + n]
    end
  end
)
|> Enum.take(2)
|> Enum.product()
```

My LiveBook:


Day 2

Load input

We do parsing there, as it will help us with the latter tasks. Pattern matching
is the simplest approach there, as input is in form of:

forward 10
up 20
down 30

We need to trim/1 input to make sure that the last newline will not interrupt
String.to_integer/1 calls.

stream =
  File.stream!("day2.txt")
  |> Stream.map(fn input ->
    case String.trim(input) do
      "forward " <> n -> {:forward, String.to_integer(n)}
      "up " <> n -> {:up, String.to_integer(n)}
      "down " <> n -> {:down, String.to_integer(n)}
    end
  end)

Task 1

{h, d} =
  stream
  |> Enum.reduce({0, 0}, fn
    {:forward, n}, {h, d} -> {h + n, d}
    {:up, n}, {h, d} -> {h, d - n}
    {:down, n}, {h, d} -> {h, d + n}
  end)

h * d

Task 2

{h, d, _} =
  stream
  |> Enum.reduce({0, 0, 0}, fn
    {:forward, n}, {h, d, a} -> {h + n, d + a * n, a}
    {:up, n}, {h, d, a} -> {h, d, a - n}
    {:down, n}, {h, d, a} -> {h, d, a + n}
  end)

h * d
4 Likes

In my case I didn’t think about the string pattern matching approach until after I had already decided that converting the string number to an integer was part of processing the input. I still tend to be less idiomatic in using pattern matching in function signatures b/c it feels like inserting logic in a hidden way. Sometimes it’s beautiful, like your solution where the individual functions fit in one line and are grouped together making it clear what’s going on. Just still not instinctive for me.

I used pattern matching but forgot about anonymous function pattern matching; with that it looks nice:

defmodule Day2 do
  import String, only: [to_integer: 1]

  def solve_1() do
    parse()
    |> Enum.reduce({0, 0}, fn
      "forward " <> move, {horizontal, depth} -> {horizontal + to_integer(move), depth}
      "down " <> move, {horizontal, depth} -> {horizontal, depth + to_integer(move)}
      "up " <> move, {horizontal, depth} -> {horizontal, depth - to_integer(move)}
    end)
    |> Tuple.product()
  end

  def solve_2() do
    parse()
    |> Enum.reduce({0, 0, 0}, fn
      "forward " <> move, {aim, horizontal, depth} -> {aim, horizontal + to_integer(move), depth + aim * to_integer(move)}
      "down " <> move, {aim, horizontal, depth} -> {aim + to_integer(move), horizontal, depth}
      "up " <> move, {aim, horizontal, depth} -> {aim - to_integer(move), horizontal, depth}
    end)
    |> Tuple.delete_at(0)
    |> Tuple.product()
  end

  defp parse() do
    "day2.txt"
    |> File.stream!
    |> Stream.map(&String.trim/1)
  end
end

I’m still getting used to Elixir again, so as with day 1 any feedback is welcome!

Another take:

  def part1(), do: solve({0, 0})
  def part2(), do: solve({0, 0, 0})

  defp solve(seed) do
    File.stream!(@path)
    |> Stream.map(&parse_line/1)
    |> Enum.reduce(seed, &process_cmd/2)
    |> Tuple.product()
  end

  defp process_cmd({:forward, n}, {h, d}), do: {h+n, d}
  defp process_cmd({:up, n},      {h, d}), do: {h, d-n}
  defp process_cmd({:down, n},    {h, d}), do: {h, d+n}

  defp process_cmd({:forward, n}, {h, d, a}), do: {h+n, d + (n * a), a}
  defp process_cmd({:up, n},      {h, d, a}), do: {h, d, a-n}
  defp process_cmd({:down, n},    {h, d, a}), do: {h, d, a+n}

  defp parse_line(line) do
    case String.trim(line) do
      "forward " <> rest -> {:forward, String.to_integer(rest)}
      "up "      <> rest -> {:up,      String.to_integer(rest)}
      "down "    <> rest -> {:down,    String.to_integer(rest)}
    end
  end

Github

defmodule Day02 do
  @sample [
    "forward 5",
    "down 5",
    "forward 8",
    "up 3",
    "down 8",
    "forward 2"
  ]

  @day02_file "assets/day02.txt"

  def parse do
    @day02_file
    |> File.read!()
    |> String.split(~r/\n/, trim: true)
  end

  def sample_main do
    @sample
    |> split_str_by_2()
    |> parse_str_to_int()
    |> take_horizontalp_and_depth()
  end

  def main do
    parse()
    |> split_str_by_2()
    |> parse_str_to_int()
    |> take_horizontalp_and_depth()
  end

  def split_str_by_2(list) do
    list
    |> Enum.map(&(String.split(&1, " ")))
  end

  def parse_str_to_int([]), do: []
  def parse_str_to_int([[x, y] | t]) do
    [ {x, String.to_integer(y)} | parse_str_to_int(t)]
  end

  def take_horizontalp_and_depth(list) do
    {h, d} =
            list
            |> Enum.reduce({_h = 0, _d = 0}, fn
              {"forward", n}, {h, d} -> {h + n, d}
              {"down", n}, {h, d} -> {h, d + n}
              {"up", n}, {h, d} -> {h, d - n}
            end)

    h * d
  end

  # Part 2

  def sample_main2 do
    @sample
    |> split_str_by_2()
    |> parse_str_to_int()
    |> take_horizontalp_and_depth2()
  end

  def main2 do
    parse()
    |> split_str_by_2()
    |> parse_str_to_int()
    |> take_horizontalp_and_depth2()
  end

  def take_horizontalp_and_depth2(list) do
    {h, d, _a} =
            list
            |> Enum.reduce({0, 0, 0}, fn
              {"forward",  n}, {h, d, a} -> {h + n, d + (a * n), a}

              {"down", n}, {h, d, a} -> {h, d, a + n}

              {"up", n}, {h, d, a} -> {h, d, a - n}
            end)

    h * d
  end
end

I summarized Jose’s stream for day 2 for those that don’t have time! He did include an Nx solution but I’ve excluded them for now, like the previous, since it’s quite lengthy and dense with explanations so I’ll highlight it in its own video.

part 1:

part 2: