seeplusplus
Advent of Code 2024 - Day 7
This one was much easier for me than yesterday’s. Part 1 runs in 22ms and part 2 runs in ~3s.
defmodule BridgeRepair do
def parse_input(input) do
input
|> String.trim()
|> String.split("\n")
|> Stream.map(fn l ->
[acc, nums] = String.split(l, ":")
nums = nums |> String.trim() |> String.split(" ") |> Enum.map(&String.to_integer/1)
{acc |> String.to_integer(), nums}
end)
end
def combine_ints(acc, [], _), do: acc
def combine_ints(acc, [i | rest], ops) do
ops
|> Enum.flat_map(fn op ->
acc |> Enum.map(fn j ->
case op do
:plus -> j + i
:mul -> j * i
:cat -> "#{j}#{i}" |> String.to_integer()
end
end)
end)
|> combine_ints(rest, ops)
end
def part_one_ops() do
[:plus, :mul]
end
def part_two_ops() do
[:cat | part_one_ops()]
end
def solve(input, part) do
input
|> BridgeRepair.parse_input()
|> Stream.filter(fn {sum, [i | rest]} ->
BridgeRepair.combine_ints(
[i],
rest,
(if part == :part1, do: part_one_ops(), else: part_two_ops())
) |> Enum.any?(&(&1 == sum))
end)
|> Stream.map(fn {i, _} -> i end)
|> Enum.sum
end
end
Edit small optimization on the concat operation:
:cat -> j*10**(i |> Integer.digits() |> Enum.count) + i
reduces part 2 runtime to 800ms.
Most Liked
sevenseacat
Much easier than yesterday! I still went through a few iterations before landing on this one:
This takes about 250ms to run on my machine for part 2.
My aim for this year is to have each solution run in less than a second - we’ll see how far I get lol
billylanchantin
LOC: 11
defmodule Aoc2024.Day07 do
def part1(file), do: main(file, [&+/2, &*/2])
def part2(file), do: main(file, [&+/2, &*/2, &concat/2])
def main(file, fs), do: file |> f2ls(&p/1) |> Enum.reduce(0, &(&2 + value(&1, fs)))
def f2ls(f, fun), do: f |> File.read!() |> String.trim() |> String.split("\n") |> Enum.map(fun)
def p(l), do: l |> String.replace(":", "") |> String.split() |> Enum.map(&String.to_integer/1)
def value([out, in1 | rest], fs), do: if(search?(out, in1, rest, fs), do: out, else: 0)
def search?(out, acc, [], _), do: out == acc
def search?(out, acc, [h | t], fs), do: Enum.any?(fs, &search?(out, &1.(acc, h), t, fs))
def concat(a, b), do: Integer.undigits(Integer.digits(a) ++ Integer.digits(b))
end
In my actual solution, f2ls and p are better named via an import:
import Aoc2024.Util
#...
def main(file, fs), do: file |> file_to_lines(&parse/1) |> Enum.reduce(0, &(&2 + value(&1, fs)))
def parse(line), do: line |> String.replace(":", "") |> line_to_list_of_ints
But that bumps up the line count.
lkuty
I like recursion
Back in 1996-98, I did a lot of Scheme (MIT Scheme) and fell in love with that language, FP in general and tail recursion (and TCO of course). We had a teacher in Belgium who went to the USAs and was exposed to all of it. I had a really good time.
Excluding I/O, when I call C for the produced? function using a NIF, the code executes ± 3 times faster.
#!/usr/bin/env elixir
# AoC 2024. day 7.
###########################################################
# Part 1
defmodule Part1 do
def produced?(test_value, numbers), do: produced?(test_value, numbers, nil)
def produced?(test_value, [], value), do: value == test_value
def produced?(test_value, [x | numbers], nil), do: produced?(test_value, numbers, x)
def produced?(test_value, _numbers, value) when value > test_value, do: false
def produced?(test_value, [x | numbers], value) do
produced?(test_value, numbers, value*x) || produced?(test_value, numbers, value+x)
end
end
File.stream!("../day07.txt")
|> Stream.map(fn line -> Regex.scan(~r/\d+/, line) |> Enum.map(fn [x] -> String.to_integer(x) end) end)
|> Enum.reduce(0, fn [test_value | numbers], sum ->
if Part1.produced?(test_value, numbers), do: sum+test_value, else: sum
end)
|> IO.inspect(label: "Part 1")
###########################################################
# Part 2
defmodule Part2 do
def produced?(test_value, numbers), do: produced?(test_value, numbers, nil)
def produced?(test_value, [], value), do: value == test_value
def produced?(test_value, [x | numbers], nil), do: produced?(test_value, numbers, x)
def produced?(test_value, _numbers, value) when value > test_value, do: false
def produced?(test_value, [x | numbers], value) do
produced?(test_value, numbers, value*x) ||
produced?(test_value, numbers, value+x) ||
produced?(test_value, numbers, value*10**ndigits(x)+x)
end
@spec ndigits(pos_integer()) :: pos_integer()
defp ndigits(x), do: trunc(:math.floor(:math.log(x)/:math.log(10))+1)
end
File.stream!("../day07.txt")
|> Stream.map(fn line -> Regex.scan(~r/\d+/, line) |> Enum.map(fn [x] -> String.to_integer(x) end) end)
|> Enum.reduce(0, fn [test_value | numbers], sum ->
if Part2.produced?(test_value, numbers), do: sum+test_value, else: sum
end)
|> IO.inspect(label: "Part 2")
Aetherus
I rewrote my solution.
defmodule AoC2024.Day07 do
def part_1(input) do
input
|> Enum.filter(fn {result, rev_operands} ->
solvable_1?(result, rev_operands)
end)
|> Enum.map(&elem(&1, 0))
|> Enum.sum()
end
def part_2(input) do
input
|> Enum.filter(fn {result, rev_operands} ->
solvable_2?(result, rev_operands)
end)
|> Enum.map(&elem(&1, 0))
|> Enum.sum()
end
defp solvable_1?(target, []) do
target == 0
end
defp solvable_1?(target, [h | t]) do
solvable_1?(target - h, t) or
(rem(target, h) == 0 and solvable_1?(div(target, h), t))
end
defp solvable_2?(target, []) do
target == 0
end
defp solvable_2?(target, [h | t]) do
solvable_2?(target - h, t) or
(rem(target, h) == 0 and solvable_2?(div(target, h), t)) or
((truncated = truncate(target, h)) && solvable_2?(truncated, t))
end
defp truncate(a, b) when a < b, do: false
defp truncate(a, 0), do: a
defp truncate(a, b) when rem(a, 10) != rem(b, 10), do: false
defp truncate(a, b) do
truncate(div(a, 10), div(b, 10))
end
end







