Skip to content

Commit 36bf25d

Browse files
committed
Day 12
1 parent 8306f60 commit 36bf25d

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

12_connected_groups.rb

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
require_relative 'lib/union_find'
2+
3+
neighbours = ARGF.to_h { |l|
4+
left, right = l.split('<->')
5+
[Integer(left), right.split(?,).map(&method(:Integer)).freeze]
6+
}.freeze
7+
8+
uf = UnionFind.new(neighbours.keys, storage: Array)
9+
neighbours.each { |k, vs| vs.each { |v| uf.union_sz(k, v) } }
10+
11+
puts uf.size(0)
12+
puts uf.num_sets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
require_relative '../lib/search'
2+
require_relative '../lib/union_find'
3+
4+
require 'benchmark'
5+
6+
bench_candidates = []
7+
8+
bench_candidates << def bfs(neighbours)
9+
0.step { |i|
10+
return i if neighbours.empty?
11+
12+
_, seen = Search::bfs(neighbours.keys.first, neighbours, ->(_) { false })
13+
14+
neighbours.delete_if { |k, _| seen.include?(k) }
15+
}
16+
end
17+
18+
# we do have to union by size for day 12,
19+
# but just for comparison:
20+
bench_candidates << def union_find_rank(neighbours)
21+
nodes = neighbours.keys
22+
23+
uf = UnionFind.new(nodes, storage: Array)
24+
neighbours.each { |k, vs| vs.each { |v| uf.union(k, v) } }
25+
26+
uf.num_sets
27+
end
28+
29+
bench_candidates << def union_find_sz(neighbours)
30+
nodes = neighbours.keys
31+
32+
uf = UnionFind.new(nodes, storage: Array)
33+
neighbours.each { |k, vs| vs.each { |v| uf.union_sz(k, v) } }
34+
35+
uf.num_sets
36+
end
37+
38+
neighbours = ARGF.to_h { |l|
39+
left, right = l.split('<->')
40+
[Integer(left), right.split(?,).map(&method(:Integer)).freeze]
41+
}.freeze
42+
43+
results = {}
44+
45+
Benchmark.bmbm { |bm|
46+
bench_candidates.each { |f|
47+
bm.report(f) { 100.times { results[f] = send(f, neighbours.dup) } }
48+
}
49+
}
50+
51+
# Obviously the benchmark would be useless if they got different answers.
52+
if results.values.uniq.size != 1
53+
results.each { |k, v| puts "#{k} #{v}" }
54+
raise 'differing answers'
55+
end

lib/search.rb

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
require 'set'
2+
3+
module Search
4+
module_function
5+
6+
def bfs(start, neighbours, goal)
7+
queue = [start]
8+
seen = Set.new
9+
10+
while (n = queue.pop)
11+
next if seen.include?(n)
12+
return [true, n] if goal[n]
13+
seen << n
14+
queue.concat(neighbours[n])
15+
end
16+
17+
[false, seen.freeze]
18+
end
19+
end

lib/union_find.rb

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
class UnionFind
2+
attr_reader :num_sets
3+
4+
def initialize(things, storage: Hash)
5+
@orig_things = things.freeze
6+
@num_sets = things.size
7+
if storage == Hash
8+
@parent = things.map { |x| [x, x] }.to_h
9+
@size = things.to_h { |x| [x, 1] }
10+
@rank = things.map { |x| [x, 0] }.to_h
11+
elsif storage == Array
12+
m = things.max
13+
@parent = Array.new(m + 1, &:itself)
14+
@size = Array.new(m + 1, 1)
15+
@rank = Array.new(m + 1, 0)
16+
else raise "invalid storage #{storage}"
17+
end
18+
end
19+
20+
def size(x)
21+
@size[find(x)]
22+
end
23+
24+
def union(x, y)
25+
xp = find(x)
26+
yp = find(y)
27+
28+
return if xp == yp
29+
30+
if @rank[xp] < @rank[yp]
31+
@parent[xp] = yp
32+
elsif @rank[xp] > @rank[yp]
33+
@parent[yp] = xp
34+
else
35+
@parent[yp] = xp
36+
@rank[xp] += 1
37+
end
38+
@num_sets -= 1
39+
end
40+
41+
# Just checking whether one's more expensive than the other
42+
def union_sz(x, y)
43+
xp = find(x)
44+
yp = find(y)
45+
46+
return if xp == yp
47+
48+
if @size[xp] <= @size[yp]
49+
@parent[xp] = yp
50+
@size[yp] += @size[xp]
51+
elsif @size[xp] > @size[yp]
52+
@parent[yp] = xp
53+
@size[xp] += @size[yp]
54+
end
55+
@num_sets -= 1
56+
end
57+
58+
def find(x)
59+
@parent[x] = find(@parent[x]) if @parent[x] != x
60+
@parent[x]
61+
end
62+
63+
def sets
64+
@orig_things.group_by(&method(:find)).values.map(&:freeze).freeze
65+
end
66+
end

0 commit comments

Comments
 (0)