# Advent of Code 2022 - Day 2

Continuation of Advent of Code 2022🎄, Day 1:

## Day 2!

1 Like

My solution lives here, with a test suite.

Part 1:
``````defmodule AoC.TwentyTwentyTwo.Day.Two.Part.One do
def solve(input) do
input
|> Enum.map(&score_round/1)
|> Enum.sum()
end

defp score_round({"A", "X"}), do: 1 + 3
defp score_round({"A", "Y"}), do: 2 + 6
defp score_round({"A", "Z"}), do: 3 + 0

defp score_round({"B", "X"}), do: 1 + 0
defp score_round({"B", "Y"}), do: 2 + 3
defp score_round({"B", "Z"}), do: 3 + 6

defp score_round({"C", "X"}), do: 1 + 6
defp score_round({"C", "Y"}), do: 2 + 0
defp score_round({"C", "Z"}), do: 3 + 3
end
``````

Part 2:
``````defmodule AoC.TwentyTwentyTwo.Day.Two.Part.Two do
def solve(input) do
input
|> Enum.map(&score_round/1)
|> Enum.sum()
end

defp score_round({"A", "X"}), do: 0 + 3
defp score_round({"A", "Y"}), do: 3 + 1
defp score_round({"A", "Z"}), do: 6 + 2

defp score_round({"B", "X"}), do: 0 + 1
defp score_round({"B", "Y"}), do: 3 + 2
defp score_round({"B", "Z"}), do: 6 + 3

defp score_round({"C", "X"}), do: 0 + 2
defp score_round({"C", "Y"}), do: 3 + 3
defp score_round({"C", "Z"}), do: 6 + 1
end
``````

A little hard-coded-y; I could have separated the scoring between win or lose, and the two different ways to interpret the second letter in each instruction.

2 Likes

Nothing too clever in this solution, I’m afraid. About as naive as it gets.

``````  def part1(input) do
File.stream!(input)
|> Enum.reduce(0, fn line, score -> score + score_line(String.trim(line)) end)
end

defp score_line("A X"), do: 4
defp score_line("A Y"), do: 8
defp score_line("A Z"), do: 3
defp score_line("B X"), do: 1
defp score_line("B Y"), do: 5
defp score_line("B Z"), do: 9
defp score_line("C X"), do: 7
defp score_line("C Y"), do: 2
defp score_line("C Z"), do: 6

def part2(input) do
File.stream!(input)
|> Enum.reduce(0, fn line, score -> score + score_line_pt2(String.trim(line)) end)
end

defp score_line_pt2("A X"), do: score_line("A Z")
defp score_line_pt2("A Y"), do: score_line("A X")
defp score_line_pt2("A Z"), do: score_line("A Y")
defp score_line_pt2("B X"), do: score_line("B X")
defp score_line_pt2("B Y"), do: score_line("B Y")
defp score_line_pt2("B Z"), do: score_line("B Z")
defp score_line_pt2("C X"), do: score_line("C Y")
defp score_line_pt2("C Y"), do: score_line("C Z")
defp score_line_pt2("C Z"), do: score_line("C X")
``````
1 Like

Similar to @stevensonmt.

``````defmodule Day02 do
use AOC

def part1 do
input(2)
~> String.split("\n")
~> Enum.map(fn round ->
case round ~> String.split(" ") do
["A", "X"] -> 4
["B", "X"] -> 1
["C", "X"] -> 7
["A", "Y"] -> 8
["B", "Y"] -> 5
["C", "Y"] -> 2
["A", "Z"] -> 3
["B", "Z"] -> 9
["C", "Z"] -> 6
end
end)
~> Enum.sum()
end

def part2 do
input(2)
~> String.split("\n")
~> Enum.map(fn round ->
case round ~> String.split(" ") do
["A", "X"] -> 3
["B", "X"] -> 1
["C", "X"] -> 2
["A", "Y"] -> 4
["B", "Y"] -> 5
["C", "Y"] -> 6
["A", "Z"] -> 8
["B", "Z"] -> 9
["C", "Z"] -> 7
end
end)
~> Enum.sum()
end

end
``````
1 Like

Not sure why I did that extra split just to pattern match on the array of both of them. Matching on the string makes way more sense.

1 Like

I do like my pattern of parsing input separately from solving each part; keeps the actual interpretation of inputs up to the part of the problem in question (useful here, as we were asked to re-interpret our input).

3 Likes

I am trying to fit tweet size so far ``````score = fn
"A X" -> {4, 3}
"A Y" -> {8, 4}
"A Z" -> {3, 8}
"B X" -> {1, 1}
"B Y" -> {5, 5}
"B Z" -> {9, 9}
"C X" -> {7, 2}
"C Y" -> {2, 6}
"C Z" -> {6, 7}
end

input
|> String.split("\n")
|> Enum.map(score)
|> Enum.reduce({0, 0}, fn
{r1, r2}, {acc1, acc2} -> {acc1 + r1, acc2 + r2}
end)
``````
3 Likes

I was annoyed a little while making this, don’t know why, maybe for all those reading.

Interesting to see so many of our solutions look similar.

3 Likes

I ended up writing a bit more code I realized pretty quickly I could just precompute the score for every combination like most of you did, but what’s the fun in that? I’m using Livebook this year, so thats why the code ends up in `fn -> end` instead of wrapped in a module ``````input =
File.stream!("/Users/kwando/projects/AoC2022/02/input.txt")
|> Stream.map(fn line ->
line
|> String.trim()
|> String.split(" ", parts: 2)
|> List.to_tuple()
end)

mapping = %{
"A" => :rock,
"B" => :paper,
"C" => :scissors
}

shape_score = fn
:rock -> 1
:paper -> 2
:scissors -> 3
end

game_score = fn
any, any -> 3
:rock, :paper -> 6
:paper, :scissors -> 6
:scissors, :rock -> 6
_, _ -> 0
end

tally_games = fn games, your_pick ->
for {elf, you} <- games, reduce: 0 do
score ->
elf = mapping[elf]
you = your_pick.(elf, you)
score + game_score.(elf, you) + shape_score.(you)
end
end

your_pick = fn
_, "X" -> :rock
_, "Y" -> :paper
_, "Z" -> :scissors
end

# part 1
tally_games.(input, your_pick)

# X = lose
# Y = draw
# Z = win
pick_shape = fn
# losing
:rock, "X" -> :scissors
:paper, "X" -> :rock
:scissors, "X" -> :paper
# draw
any, "Y" -> any
# winning
:rock, "Z" -> :paper
:paper, "Z" -> :scissors
:scissors, "Z" -> :rock
end

# part 2
tally_games.(input, pick_shape)
``````
3 Likes

And here’s mine, nothing too fancy and I even though I find multiple functions usually pretty readable, the second part of the puzzle made my head spin a little and is not readable at all ``````  @input "./lib/day2_input.txt"

@x 1
@y 2
@z 3

@win 6
@loss 0
@draw 3

def puzzle1() do
process(&score/1)
end

def puzzle2() do
process(&cheat/1)
end

defp process(fun) do
@input
|> String.split(~r/\R/, trim: true)
|> Enum.map(fn row ->
row
|> String.split()
|> (&(fun.(&1))).()
end)
|> Enum.sum()
end

defp score(["A", "X"]), do: @x + @draw
defp score(["A", "Y"]), do: @y + @win
defp score(["A", "Z"]), do: @z + @loss

defp score(["B", "X"]), do: @x + @loss
defp score(["B", "Y"]), do: @y + @draw
defp score(["B", "Z"]), do: @z + @win

defp score(["C", "X"]), do: @x + @win
defp score(["C", "Y"]), do: @y + @loss
defp score(["C", "Z"]), do: @z + @draw

defp cheat(["A", "X"]), do: score(["A", "Z"])
defp cheat(["A", "Y"]), do: score(["A", "X"])
defp cheat(["A", "Z"]), do: score(["A", "Y"])

defp cheat(["B", arg]), do: score(["B", arg])

defp cheat(["C", "X"]), do: score(["C", "Y"])
defp cheat(["C", "Y"]), do: score(["C", "Z"])
defp cheat(["C", "Z"]), do: score(["C", "X"])
``````
2 Likes

I did write a bit more code, but I used metaprogramming to generate all the score cases instead of hardcoding them. Would’ve been more useful it this hadn’t been just a 3x3 grid of possible inputs per line.

Solution
``````defmodule Day2 do
def find_score(text) do
text
|> String.split("\n")
|> Enum.reject(&(&1 == ""))
|> Enum.map(&score/1)
|> Enum.sum()
end

def find_score_alternate(text) do
text
|> String.split("\n")
|> Enum.reject(&(&1 == ""))
|> Enum.map(&score_alternate/1)
|> Enum.sum()
end

@score_per_type %{rock: 1, paper: 2, scissors: 3}
@score_per_result %{loss: 0, draw: 3, win: 6}

scores_per_round =
for opponent <- Map.keys(@score_per_type),
myself <- Map.keys(@score_per_type),
into: %{} do
result =
case {opponent, myself} do
{x, x} -> :draw
{:scissors, :rock} -> :win
{:paper, :scissors} -> :win
{:rock, :paper} -> :win
_ -> :loss
end

opponent_key =
case opponent do
:rock -> "A"
:paper -> "B"
:scissors -> "C"
end

myself_key =
case myself do
:rock -> "X"
:paper -> "Y"
:scissors -> "Z"
end

{"#{opponent_key} #{myself_key}",
Map.fetch!(@score_per_result, result) + Map.fetch!(@score_per_type, myself)}
end

for {round, score} <- scores_per_round do
defp score(unquote(round)), do: unquote(score)
end

scores_per_round_alternate =
for opponent <- Map.keys(@score_per_type),
result <- Map.keys(@score_per_result),
into: %{} do
myself =
case {opponent, result} do
{x, :draw} -> x
{:scissors, :win} -> :rock
{:paper, :win} -> :scissors
{:rock, :win} -> :paper
{:scissors, :loss} -> :paper
{:paper, :loss} -> :rock
{:rock, :loss} -> :scissors
end

opponent_key =
case opponent do
:rock -> "A"
:paper -> "B"
:scissors -> "C"
end

result_key =
case result do
:loss -> "X"
:draw -> "Y"
:win -> "Z"
end

{"#{opponent_key} #{result_key}",
Map.fetch!(@score_per_result, result) + Map.fetch!(@score_per_type, myself)}
end

for {round, score} <- scores_per_round_alternate do
defp score_alternate(unquote(round)), do: unquote(score)
end

@doc false
def debug, do: unquote(Macro.escape(scores_per_round))

@doc false
def debug_alternate, do: unquote(Macro.escape(scores_per_round_alternate))
end
``````
4 Likes

Like others, I pre-computed the value for each combination

1 Like

I am still trying to go with the least amount of premature optimization.

Livebook with solution

1 Like

I went for “be very explicit” on this one ``````  defp play({:rock, :rock}), do: @rock + @draw
defp play({:paper, :rock}), do: @rock + @lose
defp play({:scissors, :rock}), do: @rock + @win
defp play({:rock, :paper}), do: @paper + @win
defp play({:paper, :paper}), do: @paper + @draw
defp play({:scissors, :paper}), do: @paper + @lose
defp play({:rock, :scissors}), do: @scissors + @lose
defp play({:paper, :scissors}), do: @scissors + @win
defp play({:scissors, :scissors}), do: @scissors + @draw
``````
4 Likes

Seems we all take a similar path, so I won’t post my full code here. There’s something interesting though.

1. For each round I get a score from 1 to 9.
2. For each score, there’s exactly one case that I can get that score.

I feel there has to be something mathematical, just that I can’t find. I tried rearanging the cases, like this:

``````  # A = Rock = 1
# B = Paper = 2
# C = Scissors = 3
# X = Rock = 1
# Y = Paper = 2
# Z = Scissors = 3
defp score1("B X"), do: 1
defp score1("C Y"), do: 2
defp score1("A Z"), do: 3
defp score1("A X"), do: 4
defp score1("B Y"), do: 5
defp score1("C Z"), do: 6
defp score1("C X"), do: 7
defp score1("A Y"), do: 8
defp score1("B Z"), do: 9

# A = Rock = 1
# B = Paper = 2
# C = Scissors = 3
# X = Lose = 0
# Y = Draw = 3
# Z = Win = 6
defp score2("B X"), do: 1
defp score2("C X"), do: 2
defp score2("A X"), do: 3
defp score2("A Y"), do: 4
defp score2("B Y"), do: 5
defp score2("C Y"), do: 6
defp score2("C Z"), do: 7
defp score2("A Z"), do: 8
defp score2("B Z"), do: 9
``````

yet I still can’t find anything.

2 Likes

One of my colleagues came up with something completely different which I thought was interesting:

(This is translated from python to elixir)

`````` File.stream!("/Users/kwando/projects/AoC2022/02/input.txt")
|> Stream.map(fn line -> String.trim(line) |> String.to_charlist() end)
|> Enum.reduce(0, fn [elf, _, you], score ->
shape_score = you - ?X + 1

case {elf - ?A, you - ?X} do
{any, any} ->
score + 3 + shape_score
{elf, you} when rem(you + 2, 3) == elf ->
score + 6 + shape_score
{_, _} ->
score + shape_score
end
end)
``````
6 Likes

Nice! I suspected you could use modulo arithmetic for the cycle, but my brain was basically mush at midnight. Thanks for sharing!

3 Likes

Day 2 is nice for binary matching too

``````defmodule AOC do

def traverse(string, score \\ 0) do
case string do
<<his, " ", result, "\n", tail :: binary>> ->
his = his - ?A + 1
result = result - ?X + 1
traverse(tail, score + win_score(his, result))

"" ->
score

<<_, tail :: binary>> ->
traverse(tail, score)
end
end

def win_score(his, 2), do: 3 + his
def win_score(1, 1), do: 3
def win_score(2, 1), do: 1
def win_score(3, 1), do: 2
def win_score(1, 3), do: 2 + 6
def win_score(2, 3), do: 3 + 6
def win_score(3, 3), do: 1 + 6

end

``````
2 Likes

I go full-on domain-driven design with custom types, typespecs, documentation, and even tests, so I’m only posting part two of day 2.

``````defmodule Day2.PartTwo do
@moduledoc """
Solution to Day 2 Part Two
"""

@typedoc """
Represents a move in the game of rock, paper, scissors
"""
@type move() :: :rock | :paper | :scissors

@typedoc """
Represents the result of a single round of rock, paper, scissors
"""
@type result() :: :win | :lose | :draw

@typedoc """
Represents a single round of the game rock, paper, scissors
"""
@type round() :: %{
opponent: move(),
response: move()
}

@typedoc """
Represents a strategy for a single round of the game rock, paper, scissors
"""
@type round_strategy() :: %{
opponent: move(),
expected_result: result()
}

@doc """
Parses a move consisting of "A", "B", "C", "X", "Y", "Z" into the corresponding
move of `:rock`, `:paper`, or `:scissors`
"""
@spec parse_move(String.t()) :: move()
def parse_move(move) do
case move do
"A" -> :rock
"B" -> :paper
"C" -> :scissors
end
end

@doc """
Parses an expected result consisting of "X", "Y", or "Z" into the corresponding
result of `:win`, `:lose`, or `:draw`
"""
@spec parse_expected_result(String.t()) :: result()
def parse_expected_result(move) do
case move do
"X" -> :lose
"Y" -> :draw
"Z" -> :win
end
end

@doc """
List of all round stategies
"""
@spec round_strategies() :: [round_strategy()]
def round_strategies() do
|> Stream.map(fn <<opponent::bytes-size(1)>> <> " " <> expected_result ->
%{
opponent: parse_move(opponent),
expected_result: parse_expected_result(expected_result)
}
end)
|> Enum.to_list()
end

@doc """
Judge the given round to determine if it is a win, loss, or draw for the player
"""
@spec judge_round(round()) :: result()
def judge_round(%{opponent: opponent, response: response}) do
case {opponent, response} do
{:rock, :rock} -> :draw
{:rock, :paper} -> :win
{:rock, :scissors} -> :lose
{:paper, :rock} -> :lose
{:paper, :paper} -> :draw
{:paper, :scissors} -> :win
{:scissors, :rock} -> :win
{:scissors, :paper} -> :lose
{:scissors, :scissors} -> :draw
end
end

@doc """
Score the round according to the given rubric that calculates a score based upon the
reponse alone plus a score from the round's result
"""
@spec score_round(round()) :: pos_integer()
def score_round(%{opponent: _, response: response} = round) do
response_score =
case response do
:rock -> 1
:paper -> 2
:scissors -> 3
end

outcome_score =
case judge_round(round) do
:win -> 6
:lose -> 0
:draw -> 3
end

response_score + outcome_score
end

@doc """
Convert a strategy to a round by computing which move is required to respond to
the opponent to guarantee the expected result
"""
@spec convert_strategy_to_round(round_strategy()) :: round()
def convert_strategy_to_round(round_strategy) do
response =
case {round_strategy.opponent, round_strategy.expected_result} do
{:rock, :win} -> :paper
{:rock, :lose} -> :scissors
{:paper, :win} -> :scissors
{:paper, :lose} -> :rock
{:scissors, :win} -> :rock
{:scissors, :lose} -> :paper
{move, :draw} -> move
end

%{opponent: round_strategy.opponent, response: response}
end

@doc """
A list of all the rounds' scores
"""
@spec scored_rounds_with_strategy() :: [pos_integer()]
def scored_rounds_with_strategy() do
round_strategies()
|> Enum.map(&convert_strategy_to_round/1)
|> Enum.map(&score_round/1)
end

def solution(), do: scored_rounds_with_strategy() |> Enum.sum()
end
``````
2 Likes

I kind of wish there was an `Enum.sum/2` that took a function and accumulated the totals from applying that function in one pass. Obviously you can just reduce instead to accomplish that, but it is less readable.

I say this because today and yesterday I made an effort to sum in one pass just to practice, and it saddens me when I see how much cleaner the alternative is.

2 Likes