Aetherus

Aetherus

Don’t know why the regex ~r/[\W && [^\.]]/x does not work in Elixir. It works pretty well in Ruby.

Anyway, here is my solution:

https://github.com/Aetherus/advent-of-code/blob/master/2023/day-03.livemd

Showing Posts 1 to 10

lud

lud

I like it, it’s very concise.

Mine is quite long, and this does not even contains the code for the Grid helper :smiley:

defmodule AdventOfCode.Y23.Day3 do
  alias AoC.Input, warn: false

  def read_file(file, _part) do
    Input.stream!(file, trim: true)
  end

  def parse_input(input, _part) do
    AoC.Grid.parse_stream(input, &parse_char/1)
  end

  defp parse_char(<<n>>) when n in ?0..?9, do: {:ok, n - ?0}
  defp parse_char(<<?.>>), do: :ignore
  defp parse_char(<<?*>>), do: {:ok, :gear}
  defp parse_char(<<_>>), do: {:ok, :sym}

  def part_one(grid) do
    grid
    |> Enum.filter(fn {xy, val} -> is_integer(val) and sym_neighbour?(grid, xy) end)
    |> Enum.map(fn {xy, _} -> first_digit(grid, xy) end)
    |> Enum.uniq()
    |> Enum.map(&collect_number(grid, &1))
    |> Enum.sum()
  end

  defp sym_neighbour?(grid, xy) do
    xy |> AoC.Grid.cardinal8() |> Enum.any?(fn txy -> Map.get(grid, txy) in [:sym, :gear] end)
  end

  defp first_digit(grid, xy) do
    west_xy = AoC.Grid.translate(xy, :w)

    case Map.get(grid, west_xy) do
      n when is_integer(n) -> first_digit(grid, west_xy)
      _ -> xy
    end
  end

  defp collect_number(grid, xy) do
    collect_number(grid, xy, [Map.fetch!(grid, xy)])
  end

  defp collect_number(grid, xy, acc) do
    east_xy = AoC.Grid.translate(xy, :e)

    case Map.get(grid, east_xy) do
      n when is_integer(n) -> collect_number(grid, east_xy, [n | acc])
      _ -> acc |> :lists.reverse() |> Integer.undigits()
    end
  end

  # -- Part 2 -----------------------------------------------------------------

  def part_two(grid) do
    grid
    # For each digit in the grid, associate the digit XY with the neighbouring
    # gear XY, keeping only digits with such neighbour.
    |> Enum.flat_map(fn
      {xy, val} when is_integer(val) ->
        case gear_neighbour(grid, xy) do
          nil -> []
          gear_xy -> [{gear_xy, xy}]
        end

      _ ->
        []
    end)
    # Group those digits XY by their neighbouring gear XY.
    |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
    # Transform the digits XY into the first digit XY of their number
    |> Enum.flat_map(fn {_gear_xy, digits_xys} ->
      first_digits = digits_xys |> Enum.map(&first_digit(grid, &1)) |> Enum.uniq()
      # Keep only the gear groups with exactly two numbers. Collect the actual
      # numbers and compute the product.
      case first_digits do
        [digit_a_xy, digit_b_xy] ->
          num_a = collect_number(grid, digit_a_xy)
          num_b = collect_number(grid, digit_b_xy)
          [num_a * num_b]

        _ ->
          []
      end
    end)
    |> Enum.reduce(&Kernel.+/2)
  end

  defp gear_neighbour(grid, xy) do
    xy |> AoC.Grid.cardinal8() |> Enum.find(fn txy -> Map.get(grid, txy) == :gear end)
  end
end

But hey, it works :smiley: Part 2 is solved in 5ms which I am not very satisfied with.

Aetherus

Aetherus OP

I’m not satisfied with my Part 2, either. It costs 300ms to run on my computer :face_with_spiral_eyes:

code-shoily

code-shoily

Wow this one. Not a dataset I’d like to traverse at midnight. But I love LiveBook. I really do.

defmodule AdventOfCode.Y2023.Day03 do
  alias AdventOfCode.Helpers.{InputReader, Transformers}

  def input, do: InputReader.read_from_file(2023, 3)

  def run(input \\ input()) do
    input = parse(input)

    {run_1(input), run_2(input)}
  end

  defp run_1(input) do
    input
    |> Enum.map(fn {v, _, _} -> String.to_integer(v) end)
    |> Enum.sum()
  end

  defp run_2(input) do
    input
    |> Enum.map(fn {v, _, gear} -> {String.to_integer(v), Enum.uniq(gear)} end)
    |> Enum.flat_map(fn {num, gears} ->
      gears
      |> Enum.map(fn gear ->
        {gear, num}
      end)
    end)
    |> Enum.group_by(fn {a, _} -> a end, fn {_, b} -> b end)
    |> Map.filter(fn {_, v} -> length(v) == 2 end)
    |> Map.values()
    |> Enum.map(fn [a, b] -> a * b end)
    |> Enum.sum()
  end

  def parse(data \\ input()) do
    grid = data |> Transformers.lines() |> Enum.map(&String.graphemes/1) |> Transformers.grid2d()

    0..(grid |> Map.keys() |> Enum.max() |> elem(0))
    |> Enum.flat_map(&collect_all(grid, &1))
    |> Enum.filter(fn {_, n, _} -> n == true end)
  end

  defp dirs(x, y) do
    [
      {x + 1, y},
      {x - 1, y},
      {x, y + 1},
      {x, y - 1},
      {x + 1, y + 1},
      {x - 1, y - 1},
      {x + 1, y - 1},
      {x - 1, y + 1}
    ]
  end

  @reject MapSet.new(~w/. 1 2 3 4 5 6 7 8 9 0/)
  defp is_part(grid, {x, y}) do
    dirs(x, y)
    |> Enum.map(&grid[&1])
    |> Enum.reject(&(is_nil(&1) || MapSet.member?(@reject, &1)))
    |> Enum.empty?()
    |> Kernel.not()
  end

  defp get_gears(grid, {x, y}), do: Enum.filter(dirs(x, y), &(grid[&1] == "*"))

  defp collect_one(grid, pos), do: collect_one(grid[pos], grid, pos, "", false, [])

  defp collect_one(cur, grid, {x, y}, digits, part?, gears) when cur in ~w/1 2 3 4 5 6 7 8 9 0/ do
    collect_one(
      grid[{x, y + 1}],
      grid,
      {x, y + 1},
      digits <> cur,
      part? || is_part(grid, {x, y}),
      get_gears(grid, {x, y}) ++ gears
    )
  end

  defp collect_one(nil, _, {_, _}, digits, part?, gears), do: {:halt, digits, part?, gears}
  defp collect_one(_, _, {_, y}, digits, part?, gears), do: {{:cont, y}, digits, part?, gears}

  defp collect_all(grid, row), do: collect_all(grid, row, 0, [])

  defp collect_all(grid, row, col, numbers) do
    case collect_one(grid, {row, col}) do
      {:halt, "", _, _} ->
        numbers

      {_, "", _, _} ->
        collect_all(grid, row, col + 1, numbers)

      {:halt, number, part?, gears} ->
        [{number, part?, gears} | numbers]

      {{:cont, next}, number, part?, gears} ->
        collect_all(grid, row, next, [{number, part?, gears} | numbers])
    end
  end
end
seeplusplus

seeplusplus

This one took me two hours continuously to get working.

contains_symbol = fn s ->
  match = Regex.match?(~r/[^\d.]/, s)
  match
end

grab_numbers = fn line ->
  Regex.scan(~r/\d+/, line, return: :index)
  |> Enum.map(fn [{index, len}] ->
    {index, len, line |> String.slice(index, len) |> Integer.parse() |> elem(0)}
  end)
end

part1 = fn s ->
  for [pre, curr, post] <-
        s
        |> Enum.reject(&(String.length(&1) == 0))
        |> then(fn x -> Enum.concat([""], x) end)
        |> Enum.chunk(3, 1)
        |> Enum.map(fn u -> Enum.map(u, &String.trim/1) end),
      [{index, length}] <- Regex.scan(~r/\d+/, curr, return: :index),
      contains_symbol.(String.slice(curr, max(index - 1, 0), length + 2)) ||
        contains_symbol.(String.slice(pre, max(index - 1, 0), length + 2)) ||
        contains_symbol.(String.slice(post, max(index - 1, 0), length + 2)),
      {number, _} = String.slice(curr, index, length) |> Integer.parse() do
    number
  end
  |> Enum.sum()
end

part2 = fn s ->
  for [pre, curr, post] <-
        s
        |> Enum.reject(&(String.length(&1) == 0))
        |> Enum.chunk(3, 1)
        |> Enum.map(fn u -> Enum.map(u, &String.trim/1) end),
      curr |> String.contains?("*"),
      [{gear_idx, _}] <- Regex.scan(~r/\*/, curr, return: :index),
      matches =
        grab_numbers.(pre) |> Enum.concat(grab_numbers.(post)) |> Enum.concat(grab_numbers.(curr)),
      valid_matches =
        matches
        |> Enum.filter(fn {m_idx, m_len, _} ->
          gear_idx in max(0, m_idx - 1)..(m_idx + m_len)
        end),
      valid_matches |> Enum.count() == 2,
      [{_, _, a}, {_, _, b}] = valid_matches do
    a * b
  end
  |> Enum.sum()
end

part1.(IO.stream()) |> IO.puts()
mexicat

mexicat

I’m not proud of this code, but hey, it works and it’s fast.

https://github.com/mexicat/aoc-2023/blob/main/lib/aoc/day_03.ex

midouest

midouest

I got tripped up on an off-by-one error with my ranges in part 1. That’s what I get for staying up late to do this one.

https://github.com/midouest/advent-of-code-2023/blob/main/notebooks/day03.livemd

hq1

hq1

Not super happy about that one, I guess the parser emission could’ve been more elegant

https://github.com/aerosol/aoc/blob/main/lib/advent_of_code/day_03.ex

pehbehbeh

pehbehbeh

Performance of list_adjecent_numbers is bad but it works… :upside_down_face:

https://github.com/pehbehbeh/adventofcode/blob/main/2023/03.livemd

trnasistor

trnasistor

My beginner’s solution, Day 3 part 1.
My focus was on readability. How I did?

defmodule Day03 do

  def part1(input) do
    # " 467..
    #   ...*.
    #   ..35  "    

    schematic = input
    |> String.split("\n")
    |> Enum.map(&String.graphemes/1)
# [ ["4", "6", "7", ".", "."],
#   [".", ".", ".", "*", "."],
#   [".", ".", "3", "5", "."]  ]
    
    matrix_of_positions_adjecent_to_symbols(schematic)
# [ [false, false, true, true, true],
#   [false, false, true, false, true],
#   [false, false, true, true, true]  ]
    |> Enum.zip(schematic)
    |> Enum.map(fn {m, s} -> Enum.zip(s, m) end)
# [ [{"4", false}, {"6", false}, {"7", true}, {".", true}, {".", true}],
#   [{".", false}, {".", false}, {".", true}, {"*", false}, {".", true}],
#   [{".", false}, {".", false}, {".", true}, {"3", true}, {"5", true}]  ]
    |> Enum.map(&extract_valid_numbers_from_zipped_row/1)
    |> List.flatten
    |> Enum.sum   
  end

  def matrix_of_positions_adjecent_to_symbols(schematic) do
    blank_matrix = List.duplicate(false, 140)
    |> List.duplicate(140)
# [ [false, false, false, false, false],
#   [false, false, false, false, false],
#   [false, false, false, false, false]  ]    

    schematic
    |> Enum.map(&Enum.with_index/1) 
    |> Enum.with_index
# [ {[{"4", 0}, {"6", 1}, {"7", 2}, {".", 3}, {".", 4}], 0},
#   {[{".", 0}, {".", 1}, {".", 2}, {"*", 3}, {".", 4}], 1},
#   {[{".", 0}, {".", 1}, {".", 2}, {"3", 3}, {"5", 4}], 2}  ]
    |> Enum.reduce(blank_matrix, 
         fn {row, row_index}, acc -> 
            Enum.reduce(row, acc, 
              fn {p, index}, acc -> 
                cond do
                  is_symbol?(p) -> encrircle_with_true_at(acc, row_index, index)
                  true -> acc
                end  
             end)
        end)
# [ [false, false, true, true, true],
#   [false, false, true, false, true],
#   [false, false, true, true, true]  ]
  end

  def is_symbol?(grapheme) do
    ["*", "&", "@", "/", "+", "-", "%", "$", "=", "#"]
    |> Enum.member?(grapheme)
  end

  def is_digit?(grapheme) do
    ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
    |> Enum.member?(grapheme)
  end
  
  def encrircle_with_true_at(matrix, row, pos) do
    matrix
    |> replace_with_true_at(row-1, pos-1)
    |> replace_with_true_at(row-1, pos)
    |> replace_with_true_at(row-1, pos+1)
    |> replace_with_true_at(row, pos-1)
    |> replace_with_true_at(row, pos+1)
    |> replace_with_true_at(row+1, pos-1)
    |> replace_with_true_at(row+1, pos)
    |> replace_with_true_at(row+1, pos+1)
  end
      
  def replace_with_true_at(matrix, row, position) do
    matrix
    |> List.pop_at(row)
    |> elem(0)
    |> List.replace_at(position, true)
    |> then(&List.replace_at(matrix, row, &1))
  end

  def extract_valid_numbers_from_zipped_row(row) do  
# [{"4", false}, {"6", false}, {"7", true}, {".", true}, {".", true}]
    row
    |> Enum.chunk_by(fn {n, _} -> is_digit?(n) end)
    |> Enum.filter(fn x -> x |> List.first |> elem(0) |> is_digit? end)
    |> Enum.map(&Enum.unzip/1)
    # [{["4", "6", "7"], [false, false, true]}, {[...],[...]}, ...]
    |> Enum.filter(fn {_, truth_list} -> Enum.any?(truth_list) end)
    |> Enum.map(fn x -> x
                  |> elem(0)
                  |> Enum.join
                  |> String.to_integer
                end)
  end

end
hauleth

hauleth

Preprocessing:

defmodule Day03 do
  def parse_line("", {_, _, acc}), do: acc
  def parse_line("." <> rest, {x, y, acc}), do: parse_line(rest, {x + 1, y, acc})

  def parse_line(<<c>> <> _ = str, {x, y, items}) when c in ?0..?9 do
    {num, rest} = Integer.parse(str)
    len = floor(:math.log10(num) + 1)
    ref = make_ref()

    points =
      for i <- 0..(len - 1) do
        {{x + i, y}, {ref, num}}
      end

    parse_line(rest, {x + len, y, points ++ items})
  end

  def parse_line(<<c>> <> rest, {x, y, items}) do
    parse_line(rest, {x + 1, y, [{{x, y}, <<c>>} | items]})
  end

  def around({x, y}) do
    for dx <- -1..1, dy <- -1..1, do: {x + dx, y + dy}
  end
end

grid =
  puzzle_input
  |> String.split()
  |> Enum.with_index()
  |> Enum.flat_map(fn {line, y} ->
    Day03.parse_line(line, {0, y, []})
  end)

{parts, ids} =
  grid
  |> Enum.split_with(fn {_, value} ->
    is_binary(value)
  end)

parts = Map.new(parts)
ids = Map.new(ids)

Part 1:

used_parts =
  for {xy, _} <- parts,
      dxy <- Day03.around(xy),
      {:ok, val} <- [Map.fetch(ids, dxy)],
      into: %{},
      do: val

Enum.sum(Map.values(used_parts))

Part 2:

cogs =
  for {xy, _} <- parts,
      values =
        Map.take(ids, Day03.around(xy)) |> Map.values() |> Enum.uniq() |> Enum.map(&elem(&1, 1)),
      match?([_, _], values),
      do: Enum.product(values)

Enum.sum(cogs)

Where Next? Top

Trending in Challenges Top

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews