@@ -14,28 +14,16 @@ def loadInput(): String = loadFileSync(s"$currentDir/../input/day25")
1414import scala .collection .immutable .BitSet
1515import scala .collection .immutable .TreeSet
1616
17- // /**THE BRUTE FORCE WAY - useful for article notes
18- // * We could try to brute force the problem,
19- // * finding all possible 3 pairs of connections,
20- // * and checking for two groups of connections.
21- // * However, this is not feasible, because
22- // * in the input there are 3375 connections,
23- // * then selecting 3 possible pairs, there are
24- // * 3375 choose 3 = 6,401,532,375 possible configurations,
25- // * we need a way to divide the problem.
26- // */
27- // def groups(list: AList, acc: Seq[Set[String]]): Seq[Set[String]] =
28- // list match
29- // case Seq((k, vs), rest*) =>
30- // val conn = Set(k) ++ vs
31- // val (conns, acc1) = acc.partition(_.intersect(conn).nonEmpty)
32- // val merged = conns.foldLeft(conn)(_ ++ _)
33- // groups(rest, acc1 :+ merged)
34- // case _ => acc
35-
36- type Weight = Map [Id , Map [Id , Long ]]
37- type Vertices = BitSet
17+ def part1 (input : String ): Int =
18+ val alist = parse(input)
19+ val g = readGraph(alist)
20+ val (graph, cut) = minimumCut(g)
21+ val (out, in) = graph.partition(cut)
22+ in.size * out.size
23+
3824type Id = Int
25+ type Vertices = BitSet
26+ type Weight = Map [Id , Map [Id , Int ]]
3927
4028def parse (input : String ): Map [String , Set [String ]] =
4129 input
@@ -56,11 +44,17 @@ def readGraph(alist: Map[String, Set[String]]): Graph =
5644
5745 def asEdges (k : String , v : String ) =
5846 val t = (lookup(k), lookup(v))
59- List (t, t.swap)
47+ t :: t.swap :: Nil
6048
6149 val v = lookup.values.to(BitSet )
62- val nodes = lookup.map((k, v) => v -> Set (k))
63- val edges = alist.toSet.flatMap((k, vs) => vs.flatMap(v => asEdges(k, v)))
50+ val nodes = v.unsorted.map(id => id -> BitSet (id)).toMap
51+ val edges =
52+ for
53+ (k, vs) <- alist.toSet
54+ v <- vs
55+ e <- asEdges(k, v)
56+ yield
57+ e
6458
6559 val w = edges
6660 .groupBy((v, _) => v)
@@ -69,69 +63,83 @@ def readGraph(alist: Map[String, Set[String]]): Graph =
6963 m
7064 .groupBy((_, v) => v)
7165 .view
72- .mapValues(_ => 1L )
66+ .mapValues(_ => 1 )
7367 .toMap
7468 .toMap
7569 Graph (v, nodes, w)
7670
77- class MostConnected (totalWeights : Map [Id , Long ], queue : TreeSet [MostConnected .Entry ]):
78- def pop : (Id , MostConnected ) =
71+ class MostConnected (
72+ totalWeights : Map [Id , Int ],
73+ queue : TreeSet [MostConnected .Entry ]
74+ ):
75+
76+ def pop =
7977 val id = queue.head.id
8078 id -> MostConnected (totalWeights - id, queue.tail)
8179
82- def expand (z : Id , explore : Vertices , w : Weight ): MostConnected =
80+ def expand (z : Id , explore : Vertices , w : Weight ) =
81+ val connectedEdges =
82+ w(z).view.filterKeys(explore)
8383 var totalWeights0 = totalWeights
8484 var queue0 = queue
85- for (id, w) <- w(z).view.filterKeys(explore) do
86- val w1 = totalWeights0.getOrElse(id, 0L ) + w
85+ for (id, w) <- connectedEdges do
86+ val w1 = totalWeights0.getOrElse(id, 0 ) + w
8787 totalWeights0 += id -> w1
8888 queue0 += MostConnected .Entry (id, w1)
8989 MostConnected (totalWeights0, queue0)
90+ end expand
91+
92+ end MostConnected
9093
9194object MostConnected :
9295 def empty = MostConnected (Map .empty, TreeSet .empty)
9396 given Ordering [Entry ] = (e1, e2) =>
9497 val first = e2.weight.compareTo(e1.weight)
9598 if first == 0 then e2.id.compareTo(e1.id) else first
96- class Entry (val id : Id , val weight : Long ):
99+ class Entry (val id : Id , val weight : Int ):
97100 override def hashCode : Int = id
98101 override def equals (that : Any ): Boolean = that match
99102 case that : Entry => id == that.id
100103 case _ => false
101104
102- case class Graph (v : Vertices , nodes : Map [Id , Set [String ]], w : Weight ):
103- def initialFrontier : IArray [Long ] = IArray .fill(v.max + 1 )(0L )
104-
105+ case class Graph (v : Vertices , nodes : Map [Id , Vertices ], w : Weight ):
105106 def cutOfThePhase (t : Id ) = Graph .Cut (t = t, edges = w(t))
106107
107- def partition (cut : Graph .Cut ): (Set [ String ], Set [ String ] ) =
108+ def partition (cut : Graph .Cut ): (Vertices , Vertices ) =
108109 (nodes(cut.t), (v - cut.t).flatMap(nodes))
109110
110111 def shrink (s : Id , t : Id ): Graph =
111- def fetch (v : Id ) = w(v).view.filterKeys(y => y != s && y != t)
112+ def fetch (x : Id ) =
113+ w(x).view.filterKeys(y => y != s && y != t)
112114
113- val prunedW1 = (w - s - t).view.mapValues(_ - s - t).toMap
115+ val prunedW = (w - t).view.mapValues(_ - t).toMap
114116
115- val ms = fetch(s).toMap
116- val mergedWeights = ms ++ fetch(t).map((k, v) => k -> (ms.getOrElse(k, 0L ) + v))
117+ val fromS = fetch(s).toMap
118+ val fromT = fetch(t).map: (y, w0) =>
119+ y -> (fromS.getOrElse(y, 0 ) + w0)
120+ val mergedWeights = fromS ++ fromT
117121
118- val w1 = prunedW1 + (s -> mergedWeights) ++ mergedWeights.view.map((y, v) => y -> (prunedW1(y) + (s -> v)))
119- val v1 = v - t
122+ val reverseMerged = mergedWeights.view.map: (y, w0) =>
123+ y -> (prunedW(y) + (s -> w0))
124+
125+ val v1 = v - t // 5.
126+ val w1 = prunedW + (s -> mergedWeights) ++ reverseMerged
120127 val nodes1 = nodes - t + (s -> (nodes(s) ++ nodes(t)))
121128 Graph (v1, nodes1, w1)
129+ end shrink
122130
123131object Graph :
124- def emptyCut = Cut (t = 0 , edges = Map .empty[Id , Long ])
125- case class Cut (t : Id , edges : Map [Id , Long ]):
126- def weight : Long = edges.values.foldLeft(0L )(_ + _)
132+ def emptyCut = Cut (t = - 1 , edges = Map .empty)
127133
128- case class Partition (in : Set [String ], out : Set [String ])
134+ case class Cut (t : Id , edges : Map [Id , Int ]):
135+ lazy val weight : Int = edges.values.sum
129136
130137def minimumCutPhase (g : Graph ) =
131138 val a = g.v.head
132139 var A = a :: Nil
133140 var explore = g.v - a
134- var mostConnected = MostConnected .empty.expand(a, explore, g.w)
141+ var mostConnected =
142+ MostConnected .empty.expand(a, explore, g.w)
135143 while explore.nonEmpty do
136144 val (z, rest) = mostConnected.pop
137145 A ::= z
@@ -140,21 +148,17 @@ def minimumCutPhase(g: Graph) =
140148 val t :: s :: _ = A : @ unchecked
141149 (g.shrink(s, t), g.cutOfThePhase(t))
142150
143- /** see Stoer-Wagner min cut algorithm https://dl.acm.org/doi/pdf/10.1145/263867.263872 */
151+ /** See Stoer-Wagner min cut algorithm
152+ * https://dl.acm.org/doi/pdf/10.1145/263867.263872
153+ */
144154def minimumCut (g : Graph ) =
145155 var g0 = g
146- var min = (g, Graph .emptyCut, Long . MaxValue )
156+ var min = (g, Graph .emptyCut)
147157 while g0.v.size > 1 do
148158 val (g1, cutOfThePhase) = minimumCutPhase(g0)
149- val weight = cutOfThePhase.weight
150- if weight < min(2 ) then
151- min = (g0, cutOfThePhase, weight)
159+ if cutOfThePhase.weight < min(1 ).weight
160+ || min(1 ).weight == 0 // initial case
161+ then
162+ min = (g0, cutOfThePhase)
152163 g0 = g1
153164 min
154-
155- def part1 (input : String ): Long =
156- val alist = parse(input)
157- val g = readGraph(alist)
158- val (graph, cut, weight) = minimumCut(g)
159- val (out, in) = graph.partition(cut)
160- in.size * out.size
0 commit comments