Skip to content

Commit 1d8cd99

Browse files
committed
Some more graph problems
1 parent fd3a87a commit 1d8cd99

4 files changed

Lines changed: 137 additions & 57 deletions

File tree

lib/2015/day_13.ex

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ defmodule AdventOfCode.Y2015.Day13 do
1414
def run(input \\ input()) do
1515
graph = parse(input)
1616

17-
{
18-
solve(graph),
19-
solve(add_me(graph))
20-
}
17+
{solve(graph), solve(add_me(graph))}
2118
end
2219

2320
def parse(data \\ input()) do
@@ -58,11 +55,9 @@ defmodule AdventOfCode.Y2015.Day13 do
5855
end
5956

6057
defp solve(graph) do
61-
nodes = Model.all_nodes(graph)
62-
[root | _rest] = nodes
58+
[root | _rest] = nodes = Model.all_nodes(graph)
6359
nodes_indexed = Enum.with_index(nodes)
6460
node_to_idx = Map.new(nodes_indexed)
65-
_idx_to_node = Map.new(Enum.map(nodes_indexed, fn {n, i} -> {i, n} end))
6661

6762
weights =
6863
for {u, i} <- nodes_indexed, {v, j} <- nodes_indexed, into: %{} do

lib/2017/day_07.ex

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,106 @@ defmodule AdventOfCode.Y2017.Day07 do
33
--- Day 7: Recursive Circus ---
44
Problem Link: https://adventofcode.com/2017/day/7
55
Difficulty: m
6-
Tags: tree
6+
Tags: tree graph
77
"""
88
alias AdventOfCode.Helpers.{InputReader, Transformers}
9+
alias Yog.Model
10+
911
def input, do: InputReader.read_from_file(2017, 7)
1012

1113
def run(input \\ input()) do
12-
input = parse(input)
14+
graph = parse(input)
15+
16+
{run_1(graph), run_2(graph)}
17+
end
18+
19+
defp run_1(graph) do
20+
Yog.arborescence_root(graph)
21+
end
1322

14-
{run_1(input), run_2(input)}
23+
defp run_2(graph) do
24+
root = run_1(graph)
25+
find_imbalance(graph, root)
1526
end
1627

17-
defp run_1(input) do
18-
input
19-
|> Enum.map(fn %{name: name} -> name end)
20-
|> MapSet.new()
21-
|> MapSet.difference(get_branches(input))
22-
|> MapSet.to_list()
23-
|> hd()
28+
defp find_imbalance(graph, node) do
29+
case find_unbalanced_child(graph, node) do
30+
{:imbalanced, result} ->
31+
result
32+
33+
{:balanced, _weight} ->
34+
nil
35+
end
36+
end
37+
38+
defp find_unbalanced_child(graph, node) do
39+
children = Model.successor_ids(graph, node)
40+
child_results = Enum.map(children, &{unit_and_total_weight(graph, &1), &1})
41+
42+
imbalanced_case =
43+
child_results
44+
|> Enum.group_by(fn {{_, total}, _} -> total end)
45+
|> Enum.find(fn {_total, nodes} -> length(nodes) == 1 end)
46+
47+
if imbalanced_case do
48+
{imbalanced_weight, [{_res, imbalanced_node}]} = imbalanced_case
49+
50+
{balanced_weight, _} =
51+
Enum.find(child_results, fn {{_, t}, _} -> t != imbalanced_weight end) |> elem(0)
52+
53+
case find_unbalanced_child(graph, imbalanced_node) do
54+
{:imbalanced, result} ->
55+
{:imbalanced, result}
56+
57+
{:balanced, _} ->
58+
diff = balanced_weight - imbalanced_weight
59+
{:imbalanced, Model.node(graph, imbalanced_node) + diff}
60+
end
61+
else
62+
total =
63+
Model.node(graph, node) +
64+
(child_results |> Enum.map(fn {{_, t}, _} -> t end) |> Enum.sum())
65+
66+
{:balanced, total}
67+
end
2468
end
2569

26-
defp run_2(_input) do
27-
{:todo, 2}
70+
# Helper to get (own_weight, total_subtree_weight)
71+
defp unit_and_total_weight(graph, node) do
72+
own = Model.node(graph, node)
73+
74+
total =
75+
Model.successor_ids(graph, node)
76+
|> Enum.map(&elem(unit_and_total_weight(graph, &1), 1))
77+
|> Enum.sum()
78+
|> Kernel.+(own)
79+
80+
{own, total}
2881
end
2982

3083
@regex ~r/(?<name>[a-z]+) \((?<weight>\d+)\)( -> (?<branches>(\s*[a-z]+\,?)+))?/
3184
def parse(data \\ input()) do
32-
data
33-
|> Transformers.lines()
34-
|> Enum.map(fn line ->
35-
Regex.named_captures(@regex, line)
36-
end)
37-
|> Enum.map(fn %{"branches" => branches, "weight" => weight, "name" => name} ->
38-
%{
39-
branches: (String.trim(branches) != "" && String.split(branches, ", ")) || nil,
40-
name: name,
41-
weight: String.to_integer(weight)
42-
}
43-
end)
44-
end
85+
parsed =
86+
data
87+
|> Transformers.lines()
88+
|> Enum.map(fn line -> Regex.named_captures(@regex, line) end)
89+
|> Enum.map(fn %{"branches" => branches, "weight" => weight, "name" => name} ->
90+
%{
91+
branches: (String.trim(branches) != "" && String.split(branches, ", ")) || [],
92+
name: name,
93+
weight: String.to_integer(weight)
94+
}
95+
end)
96+
97+
graph =
98+
Enum.reduce(parsed, Yog.directed(), fn %{name: name, weight: weight}, g ->
99+
Model.add_node(g, name, weight)
100+
end)
45101

46-
defp get_branches(parsed_data) do
47-
parsed_data
48-
|> Enum.flat_map(fn
49-
%{branches: nil} -> []
50-
%{branches: branches} -> branches
102+
Enum.reduce(parsed, graph, fn %{name: name, branches: branches}, g ->
103+
Enum.reduce(branches, g, fn branch, g_acc ->
104+
Model.add_edge!(g_acc, name, branch, nil)
105+
end)
51106
end)
52-
|> MapSet.new()
53107
end
54108
end

lib/2020/day_07.ex

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ defmodule AdventOfCode.Y2020.Day07 do
2020
defp solve_part_1(builder) do
2121
{:ok, shiny_gold_id} = Labeled.get_id(builder, "shiny gold")
2222
graph = Labeled.to_graph(builder)
23-
# Transpose to find containers (walking from gold bag outwards)
2423
transposed = Transform.transpose(graph)
2524

2625
Implicit.implicit_fold(
2726
from: shiny_gold_id,
2827
using: :breadth_first,
2928
initial: MapSet.new(),
3029
successors_of: fn id ->
31-
# Use transposed graph to find which bags can contain this one
3230
transposed
3331
|> Yog.successors(id)
3432
|> Enum.map(fn {nid, _} -> nid end)
@@ -49,11 +47,9 @@ defmodule AdventOfCode.Y2020.Day07 do
4947
end
5048

5149
defp count_bags_inside(graph, node_id) do
52-
# Successors are the bags contained within this one
5350
successors = Yog.successors(graph, node_id)
5451

5552
Enum.reduce(successors, 0, fn {child_id, count}, acc ->
56-
# Each child bag plus all the bags inside it
5753
acc + count + count * count_bags_inside(graph, child_id)
5854
end)
5955
end
@@ -62,7 +58,6 @@ defmodule AdventOfCode.Y2020.Day07 do
6258
lines = Transformers.lines(data)
6359

6460
Enum.reduce(lines, Labeled.directed(), fn line, builder ->
65-
# Example: "vibrant plum bags contain 5 faded blue bags, 6 dotted black bags."
6661
[parent, contents] = String.split(line, " bags contain ")
6762

6863
contents

lib/2020/day_24.ex

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule AdventOfCode.Y2020.Day24 do
33
--- Day 24: Lobby Layout ---
44
Problem Link: https://adventofcode.com/2020/day/24
55
Difficulty: m
6-
Tags: hexagon walk half-done
6+
Tags: hexagon walk
77
"""
88
alias AdventOfCode.Helpers.{InputReader, Transformers}
99

@@ -13,21 +13,63 @@ defmodule AdventOfCode.Y2020.Day24 do
1313

1414
def run(input \\ input()) do
1515
input = parse(input)
16-
{run_1(input), {:todo, 2}}
16+
black_tiles = run_1(input)
17+
18+
{
19+
MapSet.size(black_tiles),
20+
run_2(black_tiles)
21+
}
22+
end
23+
24+
def run_1(steps) do
25+
steps
26+
|> Enum.map(fn sides -> Enum.reduce(sides, {0, 0}, &walk/2) end)
27+
|> Enum.frequencies()
28+
|> Enum.filter(fn {_, count} -> rem(count, 2) == 1 end)
29+
|> MapSet.new(fn {tile, _} -> tile end)
30+
end
31+
32+
def run_2(black_tiles) do
33+
1..100
34+
|> Enum.reduce(black_tiles, fn _day, tiles -> simulate(tiles) end)
35+
|> MapSet.size()
36+
end
37+
38+
defp simulate(black_tiles) do
39+
all_to_check =
40+
black_tiles
41+
|> Enum.flat_map(fn pos -> [pos | hex_neighbors(pos)] end)
42+
|> MapSet.new()
43+
44+
# 2. Apply rules
45+
all_to_check
46+
|> Enum.reduce(MapSet.new(), fn pos, acc ->
47+
black_neighbor_count =
48+
hex_neighbors(pos)
49+
|> Enum.count(&MapSet.member?(black_tiles, &1))
50+
51+
is_black = MapSet.member?(black_tiles, pos)
52+
53+
should_be_black =
54+
if is_black do
55+
black_neighbor_count == 1 or black_neighbor_count == 2
56+
else
57+
black_neighbor_count == 2
58+
end
59+
60+
if should_be_black, do: MapSet.put(acc, pos), else: acc
61+
end)
1762
end
1863

19-
def run_1(input) do
20-
input
21-
|> flip()
22-
|> black_tiles()
23-
|> length()
64+
defp hex_neighbors(pos) do
65+
["e", "w", "ne", "nw", "se", "sw"] |> Enum.map(&walk(&1, pos))
2466
end
2567

2668
@directions ~r/e|w|se|sw|ne|nw/
2769
def parse(input) do
2870
for line <- Transformers.lines(input) do
2971
@directions
30-
|> Regex.scan(line, capture: :first)
72+
|> Regex.scan(line)
3173
|> List.flatten()
3274
end
3375
end
@@ -38,10 +80,4 @@ defmodule AdventOfCode.Y2020.Day24 do
3880
defp walk("ne", {x, y}), do: {(Integer.is_even(y) && x + 1) || x, y - 1}
3981
defp walk("sw", {x, y}), do: {(Integer.is_even(y) && x) || x - 1, y + 1}
4082
defp walk("se", {x, y}), do: {(Integer.is_even(y) && x + 1) || x, y + 1}
41-
42-
defp flip(tiles), do: Enum.map(tiles, fn sides -> Enum.reduce(sides, {0, 0}, &walk/2) end)
43-
44-
defp black_tiles(tiles) do
45-
for {_, tile} <- Enum.frequencies(tiles), rem(tile, 2) == 1, do: tile
46-
end
4783
end

0 commit comments

Comments
 (0)