Skip to content

Commit bd698a4

Browse files
authored
update day 25 code (#539)
delete unused code, rename some things, use Int rather than Long for weights
1 parent 2dace8a commit bd698a4

File tree

1 file changed

+62
-58
lines changed

1 file changed

+62
-58
lines changed

2023/src/day25.scala

Lines changed: 62 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,16 @@ def loadInput(): String = loadFileSync(s"$currentDir/../input/day25")
1414
import scala.collection.immutable.BitSet
1515
import 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+
3824
type Id = Int
25+
type Vertices = BitSet
26+
type Weight = Map[Id, Map[Id, Int]]
3927

4028
def 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

9194
object 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

123131
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)
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

130137
def 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+
*/
144154
def 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

Comments
 (0)