Advent of Code 2021 - Day 5

My approach in a nutshell: convert lines to list of points and find points that occur more than once in the (flattened) list of points.

Part 1: ignore diagonals
Part 2: include diagonals

defmodule AOC2021.Day05.Solver do
  def solve(stream, :first), do: solve(stream, fn _ -> [] end)
  def solve(stream, :second), do: solve(stream, fn [x1, y1, x2, y2] -> Enum.zip(x1..x2, y1..y2) end)

  def solve(stream, get_diagonal) do
    stream
    |> Stream.map(&String.trim/1)
    |> Stream.map(&String.split(&1, [" -> ", ","]))
    |> Stream.map(fn line -> Enum.map(line, &String.to_integer/1) end)
    |> Stream.flat_map(fn
      [x, y1, x, y2] -> Enum.map(y1..y2, fn y -> {x, y} end)
      [x1, y, x2, y] -> Enum.map(x1..x2, fn x -> {x, y} end)
      diagonal_line -> get_diagonal.(diagonal_line)
    end)
    |> Enum.group_by(&Function.identity/1)
    |> Enum.count(fn {_, val} -> Enum.count(val) >= 2 end)
  end
end
1 Like

TIL Function.identity. Thanks!

1 Like

part 1:

part 2:

My solution. Think it’s pretty similar to others.
Believe my diagonal approach is too naive, but happened to work for the input.

defmodule Advent.Y2021.D05 do
  @spec part_one(Enumerable.t()) :: non_neg_integer()
  def part_one(input) do
    input
    |> parse_input()
    |> Stream.filter(fn {{x1, y1}, {x2, y2}} ->
      x1 == x2 || y1 == y2
    end)
    |> count_overlapping_coords()
  end

  @spec part_two(Enumerable.t()) :: non_neg_integer()
  def part_two(input) do
    input
    |> parse_input()
    |> count_overlapping_coords()
  end

  @spec parse_input(Enumerable.t()) :: Enumerable.t()
  defp parse_input(input) do
    Stream.map(input, fn line ->
      [x1, y1, x2, y2] =
        Regex.run(~r/(\d+),(\d+)\s+\->\s+(\d+),(\d+)/, line, capture: :all_but_first)
        |> Enum.map(&String.to_integer/1)

      {{x1, y1}, {x2, y2}}
    end)
  end

  @spec count_overlapping_coords(Enumerable.t()) :: non_neg_integer()
  defp count_overlapping_coords(coords) do
    coords
    |> Stream.flat_map(fn
      {{x, y1}, {x, y2}} -> Enum.map(y1..y2, fn y -> {x, y} end)
      {{x1, y}, {x2, y}} -> Enum.map(x1..x2, fn x -> {x, y} end)
      {{x1, y1}, {x2, y2}} -> Enum.zip(x1..x2, y1..y2)
    end)
    |> Enum.frequencies()
    |> Enum.count(fn {_, count} -> count > 1 end)
  end
end

input =
  File.stream!("d05_input.txt")
  |> Stream.map(&String.trim/1)

IO.inspect(Advent.Y2021.D05.part_one(input))
IO.inspect(Advent.Y2021.D05.part_two(input))

A bit late, but my solution for day 5:

defmodule Advent5 do

  def calc do
    {:ok, contents} = File.read("input5.txt")
    moves = contents |> String.split("\n", trim: true) |> Enum.map(&pars_move(&1))
    hight_points = build_map_points(moves, []) |> Enum.frequencies() |> Enum.to_list() |> Enum.filter(fn {_,hight} -> hight >= 2 end)
    Enum.count(hight_points)
  end

  def build_map_points([],points), do: points
  def build_map_points([move|tail],points), do: build_map_points(tail, points ++ build_path(move))

  def pars_move(move) do
    move |> String.replace(" ", "") |> String.split("->", trim: true) |> Enum.map(fn cord ->
      [x,y] = String.split(cord, ",")
      {ix,_} = Integer.parse(x)
      {iy,_} = Integer.parse(y)
      {ix,iy}
    end)
  end

  def build_path([{xs,ys},{xe,ye}]) when xs == xe ,do: Enum.to_list(ys..ye) |> Enum.map(fn y -> {xs,y} end)
  def build_path([{xs,ys},{xe,ye}]) when ys == ye ,do: Enum.to_list(xs..xe) |> Enum.map(fn x -> {x,ys} end)
  def build_path([{xs,ys},{xe,ye}]) ,do: Enum.zip(Enum.to_list(xs..xe), Enum.to_list(ys..ye))

end

I just uploaded Jose’s day 5 stream summary: Safely Navigate The Submarine with Elixir (Advent of Code Day 5: Hydrothermal Venture) - YouTube. Hope this summary could be of use for anyone. :smiley: