Skip to content

update day 25 code #539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 62 additions & 58 deletions 2023/src/day25.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,16 @@ def loadInput(): String = loadFileSync(s"$currentDir/../input/day25")
import scala.collection.immutable.BitSet
import scala.collection.immutable.TreeSet

// /**THE BRUTE FORCE WAY - useful for article notes
// * We could try to brute force the problem,
// * finding all possible 3 pairs of connections,
// * and checking for two groups of connections.
// * However, this is not feasible, because
// * in the input there are 3375 connections,
// * then selecting 3 possible pairs, there are
// * 3375 choose 3 = 6,401,532,375 possible configurations,
// * we need a way to divide the problem.
// */
// def groups(list: AList, acc: Seq[Set[String]]): Seq[Set[String]] =
// list match
// case Seq((k, vs), rest*) =>
// val conn = Set(k) ++ vs
// val (conns, acc1) = acc.partition(_.intersect(conn).nonEmpty)
// val merged = conns.foldLeft(conn)(_ ++ _)
// groups(rest, acc1 :+ merged)
// case _ => acc

type Weight = Map[Id, Map[Id, Long]]
type Vertices = BitSet
def part1(input: String): Int =
val alist = parse(input)
val g = readGraph(alist)
val (graph, cut) = minimumCut(g)
val (out, in) = graph.partition(cut)
in.size * out.size

type Id = Int
type Vertices = BitSet
type Weight = Map[Id, Map[Id, Int]]

def parse(input: String): Map[String, Set[String]] =
input
Expand All @@ -56,11 +44,17 @@ def readGraph(alist: Map[String, Set[String]]): Graph =

def asEdges(k: String, v: String) =
val t = (lookup(k), lookup(v))
List(t, t.swap)
t :: t.swap :: Nil

val v = lookup.values.to(BitSet)
val nodes = lookup.map((k, v) => v -> Set(k))
val edges = alist.toSet.flatMap((k, vs) => vs.flatMap(v => asEdges(k, v)))
val nodes = v.unsorted.map(id => id -> BitSet(id)).toMap
val edges =
for
(k, vs) <- alist.toSet
v <- vs
e <- asEdges(k, v)
yield
e

val w = edges
.groupBy((v, _) => v)
Expand All @@ -69,69 +63,83 @@ def readGraph(alist: Map[String, Set[String]]): Graph =
m
.groupBy((_, v) => v)
.view
.mapValues(_ => 1L)
.mapValues(_ => 1)
.toMap
.toMap
Graph(v, nodes, w)

class MostConnected(totalWeights: Map[Id, Long], queue: TreeSet[MostConnected.Entry]):
def pop: (Id, MostConnected) =
class MostConnected(
totalWeights: Map[Id, Int],
queue: TreeSet[MostConnected.Entry]
):

def pop =
val id = queue.head.id
id -> MostConnected(totalWeights - id, queue.tail)

def expand(z: Id, explore: Vertices, w: Weight): MostConnected =
def expand(z: Id, explore: Vertices, w: Weight) =
val connectedEdges =
w(z).view.filterKeys(explore)
var totalWeights0 = totalWeights
var queue0 = queue
for (id, w) <- w(z).view.filterKeys(explore) do
val w1 = totalWeights0.getOrElse(id, 0L) + w
for (id, w) <- connectedEdges do
val w1 = totalWeights0.getOrElse(id, 0) + w
totalWeights0 += id -> w1
queue0 += MostConnected.Entry(id, w1)
MostConnected(totalWeights0, queue0)
end expand

end MostConnected

object MostConnected:
def empty = MostConnected(Map.empty, TreeSet.empty)
given Ordering[Entry] = (e1, e2) =>
val first = e2.weight.compareTo(e1.weight)
if first == 0 then e2.id.compareTo(e1.id) else first
class Entry(val id: Id, val weight: Long):
class Entry(val id: Id, val weight: Int):
override def hashCode: Int = id
override def equals(that: Any): Boolean = that match
case that: Entry => id == that.id
case _ => false

case class Graph(v: Vertices, nodes: Map[Id, Set[String]], w: Weight):
def initialFrontier: IArray[Long] = IArray.fill(v.max + 1)(0L)

case class Graph(v: Vertices, nodes: Map[Id, Vertices], w: Weight):
def cutOfThePhase(t: Id) = Graph.Cut(t = t, edges = w(t))

def partition(cut: Graph.Cut): (Set[String], Set[String]) =
def partition(cut: Graph.Cut): (Vertices, Vertices) =
(nodes(cut.t), (v - cut.t).flatMap(nodes))

def shrink(s: Id, t: Id): Graph =
def fetch(v: Id) = w(v).view.filterKeys(y => y != s && y != t)
def fetch(x: Id) =
w(x).view.filterKeys(y => y != s && y != t)

val prunedW1 = (w - s - t).view.mapValues(_ - s - t).toMap
val prunedW = (w - t).view.mapValues(_ - t).toMap

val ms = fetch(s).toMap
val mergedWeights = ms ++ fetch(t).map((k, v) => k -> (ms.getOrElse(k, 0L) + v))
val fromS = fetch(s).toMap
val fromT = fetch(t).map: (y, w0) =>
y -> (fromS.getOrElse(y, 0) + w0)
val mergedWeights = fromS ++ fromT

val w1 = prunedW1 + (s -> mergedWeights) ++ mergedWeights.view.map((y, v) => y -> (prunedW1(y) + (s -> v)))
val v1 = v - t
val reverseMerged = mergedWeights.view.map: (y, w0) =>
y -> (prunedW(y) + (s -> w0))

val v1 = v - t // 5.
val w1 = prunedW + (s -> mergedWeights) ++ reverseMerged
val nodes1 = nodes - t + (s -> (nodes(s) ++ nodes(t)))
Graph(v1, nodes1, w1)
end shrink

object Graph:
def emptyCut = Cut(t = 0, edges = Map.empty[Id, Long])
case class Cut(t: Id, edges: Map[Id, Long]):
def weight: Long = edges.values.foldLeft(0L)(_ + _)
def emptyCut = Cut(t = -1, edges = Map.empty)

case class Partition(in: Set[String], out: Set[String])
case class Cut(t: Id, edges: Map[Id, Int]):
lazy val weight: Int = edges.values.sum

def minimumCutPhase(g: Graph) =
val a = g.v.head
var A = a :: Nil
var explore = g.v - a
var mostConnected = MostConnected.empty.expand(a, explore, g.w)
var mostConnected =
MostConnected.empty.expand(a, explore, g.w)
while explore.nonEmpty do
val (z, rest) = mostConnected.pop
A ::= z
Expand All @@ -140,21 +148,17 @@ def minimumCutPhase(g: Graph) =
val t :: s :: _ = A: @unchecked
(g.shrink(s, t), g.cutOfThePhase(t))

/** see Stoer-Wagner min cut algorithm https://dl.acm.org/doi/pdf/10.1145/263867.263872 */
/** See Stoer-Wagner min cut algorithm
* https://dl.acm.org/doi/pdf/10.1145/263867.263872
*/
def minimumCut(g: Graph) =
var g0 = g
var min = (g, Graph.emptyCut, Long.MaxValue)
var min = (g, Graph.emptyCut)
while g0.v.size > 1 do
val (g1, cutOfThePhase) = minimumCutPhase(g0)
val weight = cutOfThePhase.weight
if weight < min(2) then
min = (g0, cutOfThePhase, weight)
if cutOfThePhase.weight < min(1).weight
|| min(1).weight == 0 // initial case
then
min = (g0, cutOfThePhase)
g0 = g1
min

def part1(input: String): Long =
val alist = parse(input)
val g = readGraph(alist)
val (graph, cut, weight) = minimumCut(g)
val (out, in) = graph.partition(cut)
in.size * out.size