Advent of Code 2024 - Day 7

ah that might need some refactoring indead … maybe i should go with strings for the operators instead of the actual functions … and pattern matching and short curcuit

now all my advent of code doctests completes in 62s, async test mode. Which is a lot … but i might not bother

You will get better performance if you stick with integers.
If you want to switch to strings for number concatenation, there are simple ways to do it with integers :slight_smile:

2 Likes

side note btw … in my mix project

mix setup_day 2024 8

lazy devs …

Ah i didnt mean strings for numbers. I meant the operators. Because then its easier to short curcuit all operations. My initial version was &+/2 etc

A day late and nearly past the next day, but I’m here. My original attempts had a bug in concatenation that had me in the weeds. After looking through this thread, I landed on the following. Still an interesting thought exercise to figure out why my old implementation was so out of whack.

#!/usr/bin/env elixir

defmodule Day7.Part2 do
  defp parse(str) do
    str
    |> String.trim()
    |> String.split([": ", " "], trim: true)
    |> Enum.map(&String.to_integer/1)
  end

  def concat(a, b) when b < 10, do: a * 10 + b
  def concat(a, b), do: concat(a, div(b, 10)) * 10 + rem(b, 10)

  def to_count([target | argv]),
    do: to_count([&Kernel.+/2, &Kernel.*/2, &concat(&1, &2)], target, argv)

  def to_count(_, target, [a]) do
    if target == a, do: target, else: nil
  end

  def to_count(ops, target, [a, b | rest]) do
    Enum.find_value(ops, fn op ->
      to_count(ops, target, [op.(a, b) | rest])
    end)
  end

  def solve() do
    File.stream!("07/input.txt")
    |> Stream.map(&parse/1)
    |> Task.async_stream(&to_count/1)
    |> Stream.map(fn
      {:ok, nil} -> 0
      {:ok, n} -> n
    end)
    |> Enum.sum()
    |> IO.puts()
  end
end

Day7.Part2.solve()
1 Like

Perhaps I’m just getting more familiar with writing Elixir code, but this was the easiest one yet for me. Didn’t do anything fancy, just tried every combination of calculations.

defmodule Aoc2024.Day7 do
  @moduledoc false

  defp get_input(file) do
    File.read!(file)
    |> String.split("\n", trim: true)
    |> Enum.map(&String.split(&1, ~r/[: ]+/, trim: true))
    |> Enum.map(fn l -> Enum.map(l, &String.to_integer/1) end)
  end

  defp pipepipe(left, right) do
    String.to_integer(Integer.to_string(left) <> Integer.to_string(right))
  end

  defp solvable([target | [first | rest]], operators) do
    solvable = Kernel.in(target,
      Enum.reduce(rest, [first], fn right, lefts ->
        for left <- lefts, op <- operators do
          op.(left, right)
        end
      end))
    if solvable, do: target, else: 0
  end

  def part1(file) do
    operators = [&+/2, &*/2]
    get_input(file)
    |> Enum.map(&solvable(&1, operators))
    |> Enum.sum()
  end

  def part2(file) do
    operators = [&+/2, &*/2, &pipepipe/2]
    get_input(file)
    |> Enum.map(&solvable(&1, operators))
    |> Enum.sum()
  end
end
1 Like

One of the good days, when the abstraction I picked made part 2 easier for once:

defmodule BridgeFix do
  def read(filename) do
    File.stream!(filename)
    |> Stream.map(&String.trim/1)
    |> Stream.map(&String.split(&1, ~r/[: ]+/))
    |> Stream.map(fn l -> Enum.map(l, &String.to_integer/1) end)
    |> Stream.map(fn [r | rest] -> {r, rest} end)
  end

  def can_work?({desired, values}) do
    values
    |> then(& length(&1) - 1)
    |> operators()
    |> Enum.any?(fn ops ->
      run(values, ops) == desired
    end)
  end

  @operators [:plus, :times, :concat]

  defp operators(0), do: [[]]

  defp operators(n) do
    prev = operators(n-1)

    @operators
    |> Enum.map(fn op -> Stream.map(prev, &[op | &1]) end)
    |> Stream.concat()
  end

  defp do_op(:plus, x, y), do: x + y
  defp do_op(:times, x, y), do: x * y
  defp do_op(:concat, x, y), do: String.to_integer("#{x}#{y}")

  defp run([x], []), do: x
  defp run([x, y | rest], [operator | op_rest]) do
    run([do_op(operator, x, y) | rest], op_rest)
  end
end

BridgeFix.read("input.txt")
|> Task.async_stream(&{BridgeFix.can_work?(&1), &1}, ordered: false)
|> Stream.flat_map(fn
  {:ok, {true, {x, _}}} -> [x]
  _ -> []
end)
|> Enum.sum()
|> IO.inspect()

(part 1 is the same code, just without :concat in @operators)

Task.async_stream helped quite a bit, since this is the textbook definition of “embarrassingly parallel” :stuck_out_tongue: