Skip to content

Commit

Permalink
🎨 accept any Iterable for graph library functions
Browse files Browse the repository at this point in the history
  • Loading branch information
twentylemon committed Dec 23, 2024
1 parent d1e679b commit a934e00
Show file tree
Hide file tree
Showing 6 changed files with 33 additions and 24 deletions.
8 changes: 4 additions & 4 deletions src/main/scala/org/lemon/advent/lib/graph/clique.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import scala.collection.mutable
* @param adjacency edges between nodes
* @return set of all maximal cliques
*/
def cliques[N](nodes: Set[N], adjacency: N => Set[N]): Set[Set[N]] =
def cliques[N](nodes: Set[N], adjacency: N => Iterable[N]): Set[Set[N]] =
val result = mutable.Set.empty[Set[N]]

def bronKerbosch(r: Set[N], p: Set[N], x: Set[N]): Unit =
if p.isEmpty && x.isEmpty then result.add(r)
else
var (xv, pv) = (x, p)
val u = p.union(x).head
for v <- p.diff(adjacency(u)) do
bronKerbosch(r + v, pv.intersect(adjacency(v)), xv.intersect(adjacency(v)))
for v <- p.diff(adjacency(u).toSet) do
bronKerbosch(r + v, pv.intersect(adjacency(v).toSet), xv.intersect(adjacency(v).toSet))
xv += v
pv -= v

Expand All @@ -33,5 +33,5 @@ def cliques[N](nodes: Set[N], adjacency: N => Set[N]): Set[Set[N]] =
* @param graph the graph as an adjacency list
* @return set of all maximal cliques
*/
def cliques[N](graph: Map[N, Set[N]]): Set[Set[N]] =
def cliques[N](graph: Map[N, Iterable[N]]): Set[Set[N]] =
cliques(graph.keySet, graph.withDefaultValue(Set.empty))
11 changes: 6 additions & 5 deletions src/main/scala/org/lemon/advent/lib/graph/fill.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import scala.math.Ordering.Implicits._
* @return the set of reachable nodes from `start`
* @tparam N the node type
*/
def fill[N](adjacency: N => Seq[N], start: N): Set[N] =
def fill[N](adjacency: N => Iterable[N], start: N): Set[N] =
val nodes = mutable.Set(start)
val queue = mutable.Queue(start)
while !queue.isEmpty do
Expand All @@ -29,7 +29,7 @@ def fill[N](adjacency: N => Seq[N], start: N): Set[N] =
* @tparam N the node type
* @tparam D the distance type
*/
def distanceFrom[N, D: Numeric](adjacency: N => Seq[(N, D)], end: N): Map[N, D] =
def distanceFrom[N, D: Numeric](adjacency: N => Iterable[(N, D)], end: N): Map[N, D] =
val distances = mutable.Map(end -> Numeric[D].zero)
given Ordering[(N, D)] = Ordering.by[(N, D), D](_._2)
val queue = mutable.PriorityQueue(distances.head)
Expand All @@ -51,7 +51,7 @@ def distanceFrom[N, D: Numeric](adjacency: N => Seq[(N, D)], end: N): Map[N, D]
* @return the set of reachable nodes from `start`
* @tparam N the node type
*/
def distanceFrom[N](adjacency: N => Seq[N], end: N): Map[N, Int] =
def distanceFrom[N](adjacency: N => Iterable[N], end: N): Map[N, Int] =
distanceFrom(unitAdjacency(adjacency), end)

/** Performs a breadth first fill of the graph from the starting node to the ending nodes, returning
Expand All @@ -65,14 +65,15 @@ def distanceFrom[N](adjacency: N => Seq[N], end: N): Map[N, Int] =
* @return the set of all paths from `start` to any of `ends`
* @tparam N the node type
*/
def allPaths[N, D: Numeric](adjacency: N => Seq[(N, D)], start: N, ends: N => Boolean): Set[Path[N, D]] =
def allPaths[N, D: Numeric](adjacency: N => Iterable[(N, D)], start: N, ends: N => Boolean): Set[Path[N, D]] =
val paths = mutable.Set.empty[Path[N, D]]
val queue = mutable.Queue(Path(path = Vector(start), distance = Numeric[D].zero))

while queue.nonEmpty do
val node @ Path(path, distance) = queue.dequeue
if ends(node.at) then paths.add(node)
queue ++= adjacency(node.at).map((neigh, dist) => Path(path :+ neigh, distance + dist))

paths.toSet

/** Performs a breadth first fill of the graph from the starting node to the ending nodes, returning
Expand All @@ -86,5 +87,5 @@ def allPaths[N, D: Numeric](adjacency: N => Seq[(N, D)], start: N, ends: N => Bo
* @return the set of all paths from `start` to any of `ends`
* @tparam N the node type
*/
def allPaths[N](adjacency: N => Seq[N], start: N, ends: N => Boolean): Set[Path[N, Int]] =
def allPaths[N](adjacency: N => Iterable[N], start: N, ends: N => Boolean): Set[Path[N, Int]] =
allPaths(unitAdjacency(adjacency), start, ends)
25 changes: 17 additions & 8 deletions src/main/scala/org/lemon/advent/lib/graph/search.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import scala.math.Ordering.Implicits._
* @tparam N the node type
* @tparam D the distance type
*/
def pathFind[N, D: Numeric](adjacency: N => Seq[(N, D)], start: N, ends: N => Boolean): Option[Path[N, D]] =
def pathFind[N, D: Numeric](adjacency: N => Iterable[(N, D)], start: N, ends: N => Boolean): Option[Path[N, D]] =
given Ordering[Path[N, D]] = Ordering.by[Path[N, D], D](_.distance).reverse
val queue = mutable.PriorityQueue(Path(path = Vector(start), distance = Numeric[D].zero))
val visited = mutable.Set(start)
Expand All @@ -36,7 +36,7 @@ def pathFind[N, D: Numeric](adjacency: N => Seq[(N, D)], start: N, ends: N => Bo
* @tparam N the node type
* @tparam D the distance type
*/
def allShortestPaths[N, D: Numeric](adjacency: N => Seq[(N, D)], start: N, ends: N => Boolean): Set[Path[N, D]] =
def allShortestPaths[N, D: Numeric](adjacency: N => Iterable[(N, D)], start: N, ends: N => Boolean): Set[Path[N, D]] =
val paths = mutable.Set.empty[Path[N, D]]
val queue = mutable.Queue(Path(path = Vector(start), distance = Numeric[D].zero))
val costs = mutable.Map(start -> Numeric[D].zero)
Expand All @@ -58,9 +58,6 @@ def allShortestPaths[N, D: Numeric](adjacency: N => Seq[(N, D)], start: N, ends:

paths.toSet

def allShortestPaths[N](adjacency: N => Seq[N], start: N, ends: N => Boolean): Set[Path[N, Int]] =
allShortestPaths(unitAdjacency(adjacency), start, ends)

/** Performs a dijkstra's search of the graph from `start` to `end`, returning
* the shortest path between them.
*
Expand All @@ -71,7 +68,7 @@ def allShortestPaths[N](adjacency: N => Seq[N], start: N, ends: N => Boolean): S
* @tparam N the node type
* @tparam D the distance type
*/
def pathFind[N, D: Numeric](adjacency: N => Seq[(N, D)], start: N, end: N): Option[Path[N, D]] =
def pathFind[N, D: Numeric](adjacency: N => Iterable[(N, D)], start: N, end: N): Option[Path[N, D]] =
pathFind(adjacency, start, end == _)

/** Performs a dijkstra's search of the graph from `start` to `end`, returning
Expand All @@ -83,7 +80,7 @@ def pathFind[N, D: Numeric](adjacency: N => Seq[(N, D)], start: N, end: N): Opti
* @return the shortest path between `start` and `end`, or empty if no path exists
* @tparam N the node type
*/
def pathFind[N](adjacency: N => Seq[N], start: N, ends: N => Boolean): Option[Path[N, Int]] =
def pathFind[N](adjacency: N => Iterable[N], start: N, ends: N => Boolean): Option[Path[N, Int]] =
pathFind(unitAdjacency(adjacency), start, ends)

/** Performs a dijkstra's search of the graph from `start` to `end`, returning
Expand All @@ -95,7 +92,7 @@ def pathFind[N](adjacency: N => Seq[N], start: N, ends: N => Boolean): Option[Pa
* @return the shortest path between `start` and `end`, or empty if no path exists
* @tparam N the node type
*/
def pathFind[N](adjacency: N => Seq[N], start: N, end: N): Option[Path[N, Int]] =
def pathFind[N](adjacency: N => Iterable[N], start: N, end: N): Option[Path[N, Int]] =
pathFind(unitAdjacency(adjacency), start, end)

/** Performs a dijkstra's search of the graph from `start` to `end`, returning
Expand All @@ -122,3 +119,15 @@ def pathFind[N, D: Numeric](graph: WeightedGraph[N, D], start: N, end: N): Optio
*/
def pathFind[N](graph: UnitGraph[N], start: N, end: N): Option[Path[N, Int]] =
pathFind(graph.apply, start, end)

/** Finds all shortest paths in the graph from `start` to `end`.
* The distance between each node is assumed to be one.
*
* @param adjacency function to return edges for a given node, all with distance one
* @param start the start node
* @param ends function to check if a node is an ending node
* @return set of all shortest paths between `start` and `end`
* @tparam N the node type
*/
def allShortestPaths[N](adjacency: N => Iterable[N], start: N, ends: N => Boolean): Set[Path[N, Int]] =
allShortestPaths(unitAdjacency(adjacency), start, ends)
4 changes: 2 additions & 2 deletions src/main/scala/org/lemon/advent/lib/graph/types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ package org.lemon.advent.lib.graph
* @tparam N the node type
* @tparam D the distance type
*/
type WeightedGraph[N, D] = Map[N, Seq[(N, D)]]
type WeightedGraph[N, D] = Map[N, Iterable[(N, D)]]

/** Static adjacency list. A map of `node => [neighbour...]`. The distances between all
* nodes is assumed to be one.
* @tparam N the node type
*/
type UnitGraph[N] = Map[N, Seq[N]]
type UnitGraph[N] = Map[N, Iterable[N]]

/** The path taken by a search algorithm.
* @param path the path taken
Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/org/lemon/advent/lib/graph/util.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package org.lemon.advent.lib.graph

private def unitAdjacency[N](adjacency: N => Seq[N]): N => Seq[(N, Int)] =
import scala.collection.IterableOps

private def unitAdjacency[N, C[x] <: Iterable[x]](adjacency: N => IterableOps[N, C, _]): N => C[(N, Int)] =
node => adjacency(node).map((_, 1))
5 changes: 1 addition & 4 deletions src/main/scala/org/lemon/advent/year2024/Day23.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ private object Day23:
case s"$a-$b" => Seq((a, b), (b, a))
)
.toSeq
.groupMap(_._1)(_._2)
.mapValues(_.toSet)
.toMap
.withDefaultValue(Set.empty)
.groupMapReduce(_._1)(x => Set(x._2))(_ ++ _)

def part1(input: String) =
val graph = parse(input)
Expand Down

0 comments on commit a934e00

Please sign in to comment.