Skip to content

Commit 758f17f

Browse files
committed
Addressed slow/not-so-fast
1 parent 7fbe2e2 commit 758f17f

21 files changed

Lines changed: 470 additions & 362 deletions

lib/2015/day_06.ex

Lines changed: 84 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,114 @@ defmodule AdventOfCode.Y2015.Day06 do
33
--- Day 6: Probably a Fire Hazard ---
44
Problem Link: https://adventofcode.com/2015/day/6
55
Difficulty: m
6-
Tags: grid vector reduction slow
6+
Tags: grid vector reduction
77
"""
8-
alias AdventOfCode.Helpers.{InputReader, Transformers}
9-
alias Aja.Vector
8+
import Bitwise
9+
alias AdventOfCode.Helpers.InputReader
1010

1111
def input, do: InputReader.read_from_file(2015, 6)
1212

1313
def run(input \\ input()) do
1414
parsed_input = parse(input)
15-
grid = make_grid(1000)
1615

17-
task_1 = Task.async(fn -> brightness(Enum.reduce(parsed_input, grid, &apply_1/2)) end)
18-
task_2 = Task.async(fn -> brightness(Enum.reduce(parsed_input, grid, &apply_2/2)) end)
16+
task_1 = Task.async(fn -> part_1(parsed_input) end)
17+
task_2 = Task.async(fn -> part_2(parsed_input) end)
1918

2019
{Task.await(task_1, :infinity), Task.await(task_2, :infinity)}
2120
end
2221

2322
def parse(input \\ input()) do
24-
Enum.map(Transformers.lines(input), &parse_input/1)
23+
input
24+
|> String.split("\n", trim: true)
25+
|> Enum.map(&parse_input/1)
2526
end
2627

27-
def apply_1(line, grid),
28-
do: apply(line, grid, fn _ -> 1 end, fn _ -> 0 end, fn v -> (v == 0 && 1) || 0 end)
28+
# Part 1 implementation using bitwise operations on rows (represented as 1000-bit integers)
29+
defp part_1(instructions) do
30+
# Initial grid: 1000 rows of all zeros (represented as 0)
31+
grid = Tuple.duplicate(0, 1000)
2932

30-
def apply_2(line, grid), do: apply(line, grid, &(&1 + 1), &max(0, &1 - 1), &(&1 + 2))
33+
instructions
34+
|> Enum.reduce(grid, fn {cmd, x1..x2//_, y1..y2//_}, acc ->
35+
mask = ((1 <<< (x2 - x1 + 1)) - 1) <<< x1
3136

32-
defp apply({cmd, xs, ys}, grid, on, off, toggle) do
33-
Vector.foldl(coords(xs, ys), grid, fn [x, y], acc ->
37+
Enum.reduce(y1..y2, acc, fn y, rows ->
38+
row = elem(rows, y)
39+
40+
new_row =
41+
case cmd do
42+
"turn on" -> row ||| mask
43+
"turn off" -> row &&& bnot(mask)
44+
"toggle" -> bxor(row, mask)
45+
end
46+
47+
put_elem(rows, y, new_row)
48+
end)
49+
end)
50+
|> Tuple.to_list()
51+
|> Enum.reduce(0, fn row, acc -> acc + popcount(row) end)
52+
end
53+
54+
# Part 2 implementation using :counters for mutable-like performance
55+
defp part_2(instructions) do
56+
c = :counters.new(1_000_000, [])
57+
58+
Enum.each(instructions, fn {cmd, x1..x2//_, y1..y2//_} ->
3459
case cmd do
35-
"turn on" -> Vector.update_at(acc, x, &Vector.update_at(&1, y, on))
36-
"turn off" -> Vector.update_at(acc, x, &Vector.update_at(&1, y, off))
37-
"toggle" -> Vector.update_at(acc, x, &Vector.update_at(&1, y, toggle))
60+
"turn on" ->
61+
for y <- y1..y2 do
62+
offset = y * 1000 + 1
63+
for x <- x1..x2, do: :counters.add(c, offset + x, 1)
64+
end
65+
66+
"turn off" ->
67+
for y <- y1..y2 do
68+
offset = y * 1000 + 1
69+
70+
for x <- x1..x2 do
71+
idx = offset + x
72+
if :counters.get(c, idx) > 0, do: :counters.add(c, idx, -1)
73+
end
74+
end
75+
76+
"toggle" ->
77+
for y <- y1..y2 do
78+
offset = y * 1000 + 1
79+
for x <- x1..x2, do: :counters.add(c, offset + x, 2)
80+
end
3881
end
3982
end)
83+
84+
# Sum all values
85+
Enum.reduce(1..1_000_000, 0, fn i, acc -> acc + :counters.get(c, i) end)
4086
end
4187

42-
defp make_grid(dim), do: Vector.duplicate(Vector.duplicate(0, dim), dim)
43-
defp brightness(grid), do: Aja.Enum.reduce(grid, 0, &(&2 + Aja.Enum.sum(&1)))
44-
defp coords(xs, ys), do: Vector.new(for x <- xs, y <- ys, do: [x, y])
88+
# High performance popcount for large integers
89+
defp popcount(0), do: 0
90+
91+
defp popcount(n) do
92+
n
93+
|> :binary.encode_unsigned()
94+
|> count_binary_ones(0)
95+
end
96+
97+
defp count_binary_ones(<<>>, acc), do: acc
98+
99+
defp count_binary_ones(<<b, rest::binary>>, acc) do
100+
count_binary_ones(
101+
rest,
102+
acc +
103+
for <<(bit::1 <- <<b::8>>)>>, reduce: 0 do
104+
a -> a + bit
105+
end
106+
)
107+
end
45108

46109
@regex ~r"(?<cmd>toggle|turn\son|turn\soff)\s(?<x1>\d+),(?<y1>\d+)\s\S+\s(?<x2>\d+),(?<y2>\d+)"
47-
defp parse_input(line), do: format(Regex.named_captures(@regex, line))
110+
defp parse_input(line) do
111+
%{"cmd" => cmd, "x1" => x1, "x2" => x2, "y1" => y1, "y2" => y2} =
112+
Regex.named_captures(@regex, line)
48113

49-
defp format(%{"cmd" => cmd, "x1" => x1, "x2" => x2, "y1" => y1, "y2" => y2}) do
50114
{cmd, String.to_integer(x1)..String.to_integer(x2),
51115
String.to_integer(y1)..String.to_integer(y2)}
52116
end

lib/2015/day_15.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule AdventOfCode.Y2015.Day15 do
33
--- Day 15: Science for Hungry People ---
44
Problem Link: https://adventofcode.com/2015/day/15
55
Difficulty: m
6-
Tags: quadratic-time not-fast-enough sequence needs-improvement
6+
Tags: quadratic-time sequence
77
"""
88
alias AdventOfCode.Helpers.{InputReader, Transformers}
99

lib/2015/day_17.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule AdventOfCode.Y2015.Day17 do
33
--- Day 17: No Such Thing as Too Much ---
44
Problem Link: https://adventofcode.com/2015/day/17
55
Difficulty: s
6-
Tags: slow revisit combinatorics
6+
Tags: combinatorics
77
"""
88
alias AdventOfCode.Algorithms.Combinatorics
99
alias AdventOfCode.Helpers.{InputReader, Transformers}

lib/2015/day_18.ex

Lines changed: 49 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,84 +3,75 @@ defmodule AdventOfCode.Y2015.Day18 do
33
--- Day 18: Like a GIF For Your Yard ---
44
Problem Link: https://adventofcode.com/2015/day/18
55
Difficulty: m
6-
Tags: grid map not-fast-enough
6+
Tags: grid map cellular-automata
77
"""
8-
alias AdventOfCode.Algorithms.Grid
98
alias AdventOfCode.Helpers.{InputReader, Transformers}
109

10+
@rows 100
11+
@cols 100
12+
@corners [{0, 0}, {0, 99}, {99, 0}, {99, 99}]
13+
1114
def input, do: InputReader.read_from_file(2015, 18)
12-
@repetitions 1..100
13-
@last_index 99
14-
@corners [{0, 0}, {0, @last_index}, {@last_index, 0}, {@last_index, @last_index}]
1515

1616
def run(input \\ input()) do
17-
grid = parse(input)
18-
19-
solution_1 = Task.async(fn -> steps(grid, &state/3) end)
17+
on_set = parse(input)
2018

21-
solution_2 =
22-
Task.async(fn -> grid |> update_corners() |> steps(&state_with_faulty_corners/3) end)
19+
task_1 = Task.async(fn -> solve(on_set, false) end)
20+
task_2 = Task.async(fn -> solve(on_set, true) end)
2321

24-
{
25-
Task.await(solution_1, 10_000),
26-
Task.await(solution_2, 10_000)
27-
}
22+
{Task.await(task_1), Task.await(task_2)}
2823
end
2924

30-
def parse(data \\ input()) do
25+
def parse(data) do
3126
data
3227
|> Transformers.lines()
33-
|> Enum.map(&String.graphemes/1)
34-
|> Grid.grid2d()
35-
end
36-
37-
defp steps(grid, tx) do
38-
@repetitions
39-
|> Enum.reduce(grid, fn _, acc -> iterate(acc, tx) end)
40-
|> Enum.count(fn {_, light} -> light == "#" end)
41-
end
42-
43-
defp iterate(grid, tx) do
44-
Map.new(grid, fn {{x, y}, state} ->
45-
tx.(grid, {x, y}, state)
28+
|> Enum.with_index()
29+
|> Enum.reduce(MapSet.new(), fn {line, r}, acc ->
30+
line
31+
|> String.graphemes()
32+
|> Enum.with_index()
33+
|> Enum.reduce(acc, fn {char, c}, inner_acc ->
34+
if char == "#", do: MapSet.put(inner_acc, {r, c}), else: inner_acc
35+
end)
4636
end)
4737
end
4838

49-
defp state(grid, {x, y}, state) do
50-
ons = grid |> get_neighbors({x, y}) |> Enum.count(&(&1 == "#"))
39+
defp solve(on_set, corners_sticky?) do
40+
on_set = if corners_sticky?, do: MapSet.union(on_set, MapSet.new(@corners)), else: on_set
5141

52-
state =
53-
case state do
54-
"#" -> (ons in [2, 3] && "#") || "."
55-
"." -> (ons == 3 && "#") || "."
56-
end
42+
1..100
43+
|> Enum.reduce(on_set, fn _, current_on ->
44+
# Step 1: Count neighbors for all "interesting" cells
45+
neighbor_counts =
46+
Enum.reduce(current_on, %{}, fn {r, c}, counts ->
47+
Enum.reduce(neighbors(r, c), counts, fn pos, acc ->
48+
Map.update(acc, pos, 1, &(&1 + 1))
49+
end)
50+
end)
5751

58-
{{x, y}, state}
59-
end
52+
# Step 2: Apply GOL rules
53+
new_on =
54+
Enum.reduce(neighbor_counts, MapSet.new(), fn {pos, count}, acc ->
55+
cond do
56+
count == 3 -> MapSet.put(acc, pos)
57+
count == 2 and MapSet.member?(current_on, pos) -> MapSet.put(acc, pos)
58+
true -> acc
59+
end
60+
end)
6061

61-
defp get_neighbors(grid, {x, y}) do
62-
[
63-
{x + 1, y},
64-
{x - 1, y},
65-
{x, y + 1},
66-
{x, y - 1},
67-
{x + 1, y + 1},
68-
{x + 1, y - 1},
69-
{x - 1, y - 1},
70-
{x - 1, y + 1}
71-
]
72-
|> Enum.map(fn coords -> grid[coords] end)
73-
|> Enum.reject(&is_nil/1)
74-
end
75-
76-
defp update_corners(grid) do
77-
Map.merge(grid, Map.new(@corners, &{&1, "#"}))
62+
if corners_sticky?, do: MapSet.union(new_on, MapSet.new(@corners)), else: new_on
63+
end)
64+
|> MapSet.size()
7865
end
7966

80-
defp state_with_faulty_corners(grid, {x, y}, state) do
81-
case state(grid, {x, y}, state) do
82-
{corner, _} when corner in @corners -> {corner, "#"}
83-
state -> state
67+
defp neighbors(r, c) do
68+
for dr <- -1..1,
69+
dc <- -1..1,
70+
not (dr == 0 and dc == 0),
71+
nr = r + dr,
72+
nc = c + dc,
73+
nr >= 0 and nr < @rows and nc >= 0 and nc < @cols do
74+
{nr, nc}
8475
end
8576
end
8677
end

lib/2015/day_19.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule AdventOfCode.Y2015.Day19 do
33
--- Day 19: Medicine for Rudolph ---
44
Problem Link: https://adventofcode.com/2015/day/19
55
Difficulty: m
6-
Tags: not-fast-enough needs-improvement vector random-access
6+
Tags: vector random-access
77
88
Helpful Tips for Part II: (,) analogy
99
https://www.reddit.com/r/adventofcode/comments/3xflz8/day_19_solutions/cy4etju

lib/2015/day_20.ex

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,61 @@ defmodule AdventOfCode.Y2015.Day20 do
33
--- Day 20: Infinite Elves and Infinite Houses ---
44
Problem Link: https://adventofcode.com/2015/day/20
55
Difficulty: l
6-
Tags: slow infinite-sequence sequence
6+
Tags: slow infinite-sequence sequence sieve optimization
77
"""
8-
alias AdventOfCode.Algorithms.Arithmetics
8+
99
alias AdventOfCode.Helpers.InputReader
1010

1111
def input, do: InputReader.read_from_file(2015, 20)
1212

1313
def run(input \\ input()) do
14-
input = String.to_integer(input)
15-
16-
part_1 = Task.async(fn -> run_1(input) end)
17-
part_2 = Task.async(fn -> run_2(input) end)
14+
target = String.trim(input) |> String.to_integer()
1815

19-
{Task.await(part_1, 10_000), Task.await(part_2, 10_000)}
20-
end
21-
22-
defp run_1(input) do
23-
house_with_gifts(input, &number_of_gifts/1)
24-
end
16+
task_1 = Task.async(fn -> solve_1(target) end)
17+
task_2 = Task.async(fn -> solve_2(target) end)
2518

26-
defp run_2(input) do
27-
house_with_gifts(input, &number_of_gifts_below_50/1)
19+
{Task.await(task_1, :infinity), Task.await(task_2, :infinity)}
2820
end
2921

30-
def house_with_gifts(limit, fun) do
31-
Stream.iterate(1, &(&1 + 1))
32-
|> Stream.map(fun)
33-
|> Enum.take_while(fn {_, gifts} -> gifts < limit end)
34-
|> Enum.max_by(fn {house, _} -> house end)
35-
|> then(fn {house, _} -> house + 1 end)
22+
# Part 1: Each elf i delivers 10*i gifts to every i-th house.
23+
# First house with >= target gifts.
24+
def solve_1(target) do
25+
# Upper bound: house `target/10` is guaranteed to get `target` gifts from elf `target/10`.
26+
limit = div(target, 10)
27+
arr = :atomics.new(limit, [{:signed, false}])
28+
29+
# Sieve of Eratosthenes-style accumulation
30+
# O(N log N) total operations
31+
Enum.each(1..limit, fn elf ->
32+
val = elf * 10
33+
# Start from elf, go to limit by elf steps
34+
for house <- elf..limit//elf do
35+
:atomics.add(arr, house, val)
36+
end
37+
end)
38+
39+
Enum.find(1..limit, fn h -> :atomics.get(arr, h) >= target end)
3640
end
3741

38-
defp number_of_gifts(house) do
39-
gifts =
40-
house
41-
|> Arithmetics.divisors()
42-
|> Enum.sum_by(&(&1 * 10))
43-
44-
{house, gifts}
45-
end
46-
47-
defp number_of_gifts_below_50(house) do
48-
gifts =
49-
house
50-
|> Arithmetics.divisors()
51-
|> Enum.filter(fn divisor -> div(house, divisor) < 50 end)
52-
|> Enum.sum_by(&(&1 * 11))
53-
54-
{house, gifts}
42+
# Part 2: Each elf i delivers 11*i gifts to their first 50 houses.
43+
def solve_2(target) do
44+
limit = div(target, 11) + 1
45+
arr = :atomics.new(limit, [{:signed, false}])
46+
47+
Enum.each(1..limit, fn elf ->
48+
val = elf * 11
49+
# Elf i delivers to i, 2i, ..., 50i
50+
# We take the first 50 houses but must not exceed limit
51+
steps = min(50, div(limit, elf))
52+
53+
if steps > 0 do
54+
for s <- 1..steps do
55+
house = s * elf
56+
if house <= limit, do: :atomics.add(arr, house, val)
57+
end
58+
end
59+
end)
60+
61+
Enum.find(1..limit, fn h -> :atomics.get(arr, h) >= target end)
5562
end
5663
end

0 commit comments

Comments
 (0)