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
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()
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
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”