@@ -14,28 +14,16 @@ def loadInput(): String = loadFileSync(s"$currentDir/../input/day25")
14
14
import scala .collection .immutable .BitSet
15
15
import scala .collection .immutable .TreeSet
16
16
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
+
38
24
type Id = Int
25
+ type Vertices = BitSet
26
+ type Weight = Map [Id , Map [Id , Int ]]
39
27
40
28
def parse (input : String ): Map [String , Set [String ]] =
41
29
input
@@ -56,11 +44,17 @@ def readGraph(alist: Map[String, Set[String]]): Graph =
56
44
57
45
def asEdges (k : String , v : String ) =
58
46
val t = (lookup(k), lookup(v))
59
- List (t, t.swap)
47
+ t :: t.swap :: Nil
60
48
61
49
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
64
58
65
59
val w = edges
66
60
.groupBy((v, _) => v)
@@ -69,69 +63,83 @@ def readGraph(alist: Map[String, Set[String]]): Graph =
69
63
m
70
64
.groupBy((_, v) => v)
71
65
.view
72
- .mapValues(_ => 1L )
66
+ .mapValues(_ => 1 )
73
67
.toMap
74
68
.toMap
75
69
Graph (v, nodes, w)
76
70
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 =
79
77
val id = queue.head.id
80
78
id -> MostConnected (totalWeights - id, queue.tail)
81
79
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)
83
83
var totalWeights0 = totalWeights
84
84
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
87
87
totalWeights0 += id -> w1
88
88
queue0 += MostConnected .Entry (id, w1)
89
89
MostConnected (totalWeights0, queue0)
90
+ end expand
91
+
92
+ end MostConnected
90
93
91
94
object MostConnected :
92
95
def empty = MostConnected (Map .empty, TreeSet .empty)
93
96
given Ordering [Entry ] = (e1, e2) =>
94
97
val first = e2.weight.compareTo(e1.weight)
95
98
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 ):
97
100
override def hashCode : Int = id
98
101
override def equals (that : Any ): Boolean = that match
99
102
case that : Entry => id == that.id
100
103
case _ => false
101
104
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 ):
105
106
def cutOfThePhase (t : Id ) = Graph .Cut (t = t, edges = w(t))
106
107
107
- def partition (cut : Graph .Cut ): (Set [ String ], Set [ String ] ) =
108
+ def partition (cut : Graph .Cut ): (Vertices , Vertices ) =
108
109
(nodes(cut.t), (v - cut.t).flatMap(nodes))
109
110
110
111
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)
112
114
113
- val prunedW1 = (w - s - t).view.mapValues(_ - s - t).toMap
115
+ val prunedW = (w - t).view.mapValues(_ - t).toMap
114
116
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
117
121
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
120
127
val nodes1 = nodes - t + (s -> (nodes(s) ++ nodes(t)))
121
128
Graph (v1, nodes1, w1)
129
+ end shrink
122
130
123
131
object 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)
127
133
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
129
136
130
137
def minimumCutPhase (g : Graph ) =
131
138
val a = g.v.head
132
139
var A = a :: Nil
133
140
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)
135
143
while explore.nonEmpty do
136
144
val (z, rest) = mostConnected.pop
137
145
A ::= z
@@ -140,21 +148,17 @@ def minimumCutPhase(g: Graph) =
140
148
val t :: s :: _ = A : @ unchecked
141
149
(g.shrink(s, t), g.cutOfThePhase(t))
142
150
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
+ */
144
154
def minimumCut (g : Graph ) =
145
155
var g0 = g
146
- var min = (g, Graph .emptyCut, Long . MaxValue )
156
+ var min = (g, Graph .emptyCut)
147
157
while g0.v.size > 1 do
148
158
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)
152
163
g0 = g1
153
164
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