Advent of Code 2022 - Day 2

Lots of good answers. Nothing special here pensandoemelixir/day2.exs at main · adolfont/pensandoemelixir · GitHub

If I understand what you’re getting at: I think substituting Enum.map with Stream.map, in this case, effectively does what you’re talking about. Can’t say if it’ll perform better/worse, but the input list will only be passed over once

Like most others, went with the direct approach once i figured there were only 9 cases. Although today I remembered to use File.stream

Although with all the magic numbers here I would probably use constants for the score calculations even in a first iteration.

3 Likes

I prefer the solution from @tfwright. It is faster and cleaner. Should have used Enum.reduce/3.

part 1

defmodule MyModule do
  def outcome_score(65, 88), do: 3 # Rock     A - X Rock
  def outcome_score(65, 89), do: 6 # Rock     A - Y Paper
  def outcome_score(65, 90), do: 0 # Rock     A - Z Scissors
  def outcome_score(66, 88), do: 0 # Paper    B - X Rock
  def outcome_score(66, 89), do: 3 # Paper    B - Y Paper
  def outcome_score(66, 90), do: 6 # Paper    B - Z Scissors
  def outcome_score(67, 88), do: 6 # Scissors C - X Rock
  def outcome_score(67, 89), do: 0 # Scissors C - Y Paper
  def outcome_score(67, 90), do: 3 # Scissors C - Z Scissors

  def shape_score(88), do: 1 # X
  def shape_score(89), do: 2 # Y
  def shape_score(90), do: 3 # Z
end

start = System.monotonic_time(:microsecond)

File.stream!("input.txt")
|> Stream.map(fn <<opponent, " ", me, "\n">> -> MyModule.shape_score(me) + MyModule.outcome_score(opponent, me) end)
|> Enum.sum()
|> tap(fn sum -> IO.puts "Total score: #{sum}" end)

elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Job done in #{elapsed} µs"

part 2

defmodule MyModule do
  def score(65, 88), do: 0+3 # Rock     A - X Lose => Scissors
  def score(65, 89), do: 3+1 # Rock     A - Y Draw => Rock
  def score(65, 90), do: 6+2 # Rock     A - Z Win  => Paper
  def score(66, 88), do: 0+1 # Paper    B - X Lose => Rock
  def score(66, 89), do: 3+2 # Paper    B - Y Draw => Paper
  def score(66, 90), do: 6+3 # Paper    B - Z Win  => Scissors
  def score(67, 88), do: 0+2 # Scissors C - X Lose => Paper
  def score(67, 89), do: 3+3 # Scissors C - Y Draw => Scissors
  def score(67, 90), do: 6+1 # Scissors C - Z Win  => Rock
end

start = System.monotonic_time(:microsecond)

File.stream!("input.txt")
|> Stream.map(fn <<opponent, " ", goal, "\n">> -> MyModule.score(opponent, goal) end)
|> Enum.sum()
|> tap(fn sum -> IO.puts "Total score: #{sum}" end)

elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Job done in #{elapsed} µs"

My take.
part 1 and part 2 computed at the same time in the reduce. I wanted to avoid hard-coding, and keep it below 20 lines of code.

File.stream!("input")
|> Enum.map(& String.to_charlist(&1) |> List.to_tuple() )
|> Enum.reduce({0, 0}, fn {i, _, j, _}, {acc_part1, acc_part2} ->
  { acc_part1 + j - 87 +
    case Integer.mod(j - i - (?X-?A), 3) do
      2 -> 0       # loose
      x -> (x+1)*3 # win or draw
    end,
    acc_part2 + case j do
      ?X -> Integer.mod(i - ?A - 1, 3) + 1 + 0 # loose
      ?Y -> Integer.mod(i - ?A + 0, 3) + 1 + 3 # draw
      ?Z -> Integer.mod(i - ?A + 1, 3) + 1 + 6 # win
    end
  }
end)
|> IO.inspect()

Part 1

input
|> String.split("\n", trim: true)
|> Enum.reduce(0, fn
  "A X", acc -> acc + 3 + 1
  "A Y", acc -> acc + 6 + 2
  "A Z", acc -> acc + 0 + 3

  "B X", acc -> acc + 0 + 1
  "B Y", acc -> acc + 3 + 2
  "B Z", acc -> acc + 6 + 3

  "C X", acc -> acc + 6 + 1
  "C Y", acc -> acc + 0 + 2
  "C Z", acc -> acc + 3 + 3
end)

Part 2

input
|> String.split("\n", trim: true)
|> Enum.reduce(0, fn
  "A X", acc -> acc + 0 + 3
  "A Y", acc -> acc + 3 + 1
  "A Z", acc -> acc + 6 + 2

  "B X", acc -> acc + 0 + 1
  "B Y", acc -> acc + 3 + 2
  "B Z", acc -> acc + 6 + 3

  "C X", acc -> acc + 0 + 2
  "C Y", acc -> acc + 3 + 3
  "C Z", acc -> acc + 6 + 1
end)

You can find my solution here : Livebook.dev (comments are in french, but code is pretty expressive)

Here are some interesting part : the function playing the game :

defmodule Day2Helpers.Outcome do
  # Rock defeats Scissors, Scissors defeats Paper, and Paper defeats Rock.
  # If both players choose the same shape, the round instead ends in a draw.

  def round_outcome({:scissors, :rock}), do: :win
  def round_outcome({:paper, :scissors}), do: :win
  def round_outcome({:rock, :paper}), do: :win

  def round_outcome({opponent, me}) when opponent == me do
    :draw
  end

  def round_outcome({_, _}), do: :loose
end

Then for part two, I tried playing the game until I find the expected result :

  @shapes [:rock, :paper, :scissors]
#[...]
  defp c2_shape("X", opponent) do
    # Let's play the game and keep only loosing input shapes.
    @shapes
    |> Enum.filter(fn shape -> Day2Helpers.Outcome.round_outcome({opponent, shape}) == :loose end)
    |> Enum.at(0)
  end

I’m pretty new to Elixir, so feedback is always appreciated!