5
5
6
6
from __future__ import annotations
7
7
8
+ import random
8
9
import sys
9
10
from collections import defaultdict
10
11
from copy import deepcopy
33
34
34
35
35
36
class Graph (NamedTuple ):
36
- edges : dict [str , set [str ]]
37
+ edges : dict [str , list [str ]]
37
38
38
39
@classmethod
39
40
def from_input (cls , input : InputData ) -> Graph :
40
- edges = defaultdict [str , set [str ]](set )
41
+ edges = defaultdict [str , list [str ]](list )
41
42
for line in input :
42
43
key , values = line .split (": " )
43
- edges [key ] |= { _ for _ in values .split ()}
44
+ edges [key ] += [ _ for _ in values .split ()]
44
45
for v in values .split ():
45
- edges [v ].add (key )
46
+ edges [v ].append (key )
46
47
return Graph (edges )
47
48
48
- def remove_edges (self , edges : list [tuple [str , str ]]) -> Graph :
49
- new_edges = deepcopy (self .edges )
50
- for edge in edges :
51
- first , second = edge
52
- new_edges [first ].remove (second )
53
- new_edges [second ].remove (first )
54
- return Graph (new_edges )
49
+ def combine_nodes (self , node_1 : str , node_2 : str , new_node : str ) -> Graph :
50
+ self .edges [new_node ] = [
51
+ d for d in self .edges [node_1 ] if d != node_2
52
+ ] + [d for d in self .edges [node_2 ] if d != node_1 ]
53
+ for node in {node_1 , node_2 }:
54
+ for dst in self .edges [node ]:
55
+ self .edges [dst ] = [
56
+ new_node if d == node else d for d in self .edges [dst ]
57
+ ]
58
+ del self .edges [node ]
59
+ return Graph (self .edges )
55
60
56
61
def get_connected (self , node : str ) -> set [str ]:
57
62
return flood_fill (node , lambda n : (_ for _ in self .edges [n ]))
58
63
59
- def visualize (self , file_name : str ) -> None :
60
- with open (file_name , "w" ) as f :
61
- print ("Graph {" , file = f )
62
- for k , v in self .edges .items ():
63
- for vv in v :
64
- print (f"{ k } -- { vv } " , file = f )
65
- print ("}" , file = f )
66
-
67
64
68
65
Input = Graph
69
66
Output1 = int
@@ -74,31 +71,26 @@ class Solution(SolutionBase[Input, Output1, Output2]):
74
71
def parse_input (self , input_data : InputData ) -> Input :
75
72
return Graph .from_input (input_data )
76
73
77
- def solve_1 (
78
- self , graph : Input , disconnects : list [tuple [str , str ]]
79
- ) -> Output1 :
80
- g = graph .remove_edges (disconnects )
81
- first = g .get_connected (disconnects [0 ][0 ])
82
- second = g .get_connected (disconnects [0 ][1 ])
83
- return len (first ) * len (second )
84
-
85
- def sample_1 (self , graph : Input ) -> Output1 :
86
- return self .solve_1 (
87
- graph , [("hfx" , "pzl" ), ("bvb" , "cmg" ), ("nvd" , "jqt" )]
88
- )
89
-
90
74
def part_1 (self , graph : Input ) -> Output1 :
91
- # visualize with graphviz/neato:
92
- # graph.visualize("/mnt/c/temp/graph.dot")
93
- # -> to cut: ssd--xqh, nrs--khn, qlc--mqb
94
- return self .solve_1 (
95
- graph , [("ssd" , "xqh" ), ("nrs" , "khn" ), ("qlc" , "mqb" )]
96
- )
75
+ while True :
76
+ g = deepcopy (graph )
77
+ counts = {node : 1 for node in g .edges .keys ()}
78
+ while len (g .edges ) > 2 :
79
+ a = random .sample (list (g .edges .keys ()), 1 )[0 ]
80
+ b = random .sample (list (g .edges [a ]), 1 )[0 ]
81
+ new_node = f"{ a } -{ b } "
82
+ counts [new_node ] = counts .get (a , 0 ) + counts .get (b , 0 )
83
+ del counts [a ]
84
+ del counts [b ]
85
+ g = g .combine_nodes (a , b , new_node )
86
+ a , b = g .edges .keys ()
87
+ if len (g .edges [a ]) == 3 :
88
+ return counts [a ] * counts [b ]
97
89
98
90
def part_2 (self , input : Input ) -> Output2 :
99
91
return "🎄"
100
92
101
- @aoc_samples ((("sample_1 " , TEST , 54 ),))
93
+ @aoc_samples ((("part_1 " , TEST , 54 ),))
102
94
def samples (self ) -> None :
103
95
pass
104
96
0 commit comments