A port of @Ajax1234's Python code in Scala.
1987 bytes, it can be golfed much more.
Golfed version. Attempt This Online!
def e(edges: List[(Int, Int)]): Set[Int] = { edges.flatMap { case (a, b) => List(a, b) }.toSet } def t[A](seq: Seq[A]): List[A] = { seq.toList } def b(edges: List[(Int, Int)]): Map[Int, Set[Int]] = { edges.foldLeft(Map[Int, Set[Int]]()) { (A, edge) => val (a, b) = edge A.updated(a, A.getOrElse(a, Set()) + b) .updated(b, A.getOrElse(b, Set()) + a) } } def f(A: Map[Int, Set[Int]]): Map[List[Int], List[List[Int]]] = { var queue = A.filter(_._2.size == 1).keys.map(node => (node, List(node), List(A(node).size))).toList var paths = Map[List[Int], List[List[Int]]]() while (queue.nonEmpty) { val (currentNode, path, degrees) = queue.head queue = queue.tail var isLeaf = true for (neighbor <- A(currentNode) -- path.toSet) { queue = queue :+ (neighbor, path :+ neighbor, degrees :+ A(neighbor).size) isLeaf = false } if (isLeaf) { val d= t(degrees) val r= t(degrees.reverse) if (paths.contains(d)) { if (!paths(d).contains(path) && !paths(d).contains(path.reverse)) { paths = paths.updated(d, paths(d) :+ path) } } else if (paths.contains(r)) { if (!paths(r).contains(path) && !paths(r).contains(path.reverse)) { paths = paths.updated(r, paths(r) :+ path) } } else { paths = paths.updated(d, List(path)) } } } paths } def c(edges: List[(Int, Int)]): Int = { val paths = f(b(edges)) var u= 0 for ((degrees, pathList) <- paths) { var subgraphs = List[List[List[Int]]]() for (path<-pathList) { val E=edges.filter { case (a, b) => !path.contains(a) && !path.contains(b) } val N= e(edges).diff(e(E)).diff(path.toSet) val newSubgraph = f(b(E ++ N.map(node => (node, node)))).keys.toList subgraphs = subgraphs :+ newSubgraph.map(_.sorted) } u+= subgraphs.distinct.size } u }
Ungolfed version. Attempt This Online!
import scala.math.Ordering.Implicits.seqOrdering object Main { def extractNodesFromEdges(edges: List[(Int, Int)]): Set[Int] = { edges.flatMap { case (a, b) => List(a, b) }.toSet } def toTuple[A](seq: Seq[A]): List[A] = { seq.toList } def buildAdjacencyList(edges: List[(Int, Int)]): Map[Int, Set[Int]] = { edges.foldLeft(Map[Int, Set[Int]]()) { (adjacencyList, edge) => val (a, b) = edge adjacencyList.updated(a, adjacencyList.getOrElse(a, Set()) + b) .updated(b, adjacencyList.getOrElse(b, Set()) + a) } } def findAllPaths(adjacencyList: Map[Int, Set[Int]]): Map[List[Int], List[List[Int]]] = { var queue = adjacencyList.filter(_._2.size == 1).keys.map(node => (node, List(node), List(adjacencyList(node).size))).toList var paths = Map[List[Int], List[List[Int]]]() while (queue.nonEmpty) { val (currentNode, path, degrees) = queue.head queue = queue.tail var isLeaf = true for (neighbor <- adjacencyList(currentNode) -- path.toSet) { queue = queue :+ (neighbor, path :+ neighbor, degrees :+ adjacencyList(neighbor).size) isLeaf = false } if (isLeaf) { val degreesTuple = toTuple(degrees) val reversedDegreesTuple = toTuple(degrees.reverse) if (paths.contains(degreesTuple)) { if (!paths(degreesTuple).contains(path) && !paths(degreesTuple).contains(path.reverse)) { paths = paths.updated(degreesTuple, paths(degreesTuple) :+ path) } } else if (paths.contains(reversedDegreesTuple)) { if (!paths(reversedDegreesTuple).contains(path) && !paths(reversedDegreesTuple).contains(path.reverse)) { paths = paths.updated(reversedDegreesTuple, paths(reversedDegreesTuple) :+ path) } } else { paths = paths.updated(degreesTuple, List(path)) } } } paths } def countUniqueSubgraphs(edges: List[(Int, Int)]): Int = { val paths = findAllPaths(buildAdjacencyList(edges)) var uniqueCount = 0 for ((degrees, pathList) <- paths) { var subgraphs = List[List[List[Int]]]() for (path <- pathList) { val remainingEdges = edges.filter { case (a, b) => !path.contains(a) && !path.contains(b) } val remainingNodes = extractNodesFromEdges(edges).diff(extractNodesFromEdges(remainingEdges)).diff(path.toSet) val newSubgraph = findAllPaths(buildAdjacencyList(remainingEdges ++ remainingNodes.map(node => (node, node)))).keys.toList subgraphs = subgraphs :+ newSubgraph.map(_.sorted) } uniqueCount += subgraphs.distinct.size } uniqueCount } def main(args: Array[String]): Unit = { println(countUniqueSubgraphs(List((1, 2), (2, 3), (3, 4), (3, 5)))) println(countUniqueSubgraphs(List((1, 2), (2, 3), (3, 4), (5, 6), (6, 7), (3, 7), (7, 8)))) println(countUniqueSubgraphs(List((1, 2), (2, 3), (3, 4), (3, 5), (3, 6), (3, 7)))) println(countUniqueSubgraphs(List((1, 2), (2, 3), (3, 4), (3, 5), (5, 6), (6, 7), (6, 8), (6, 9)))) println(countUniqueSubgraphs(List((1, 2), (2, 3), (3, 4), (4, 5), (4, 6), (7, 8), (8, 9), (6, 9), (9, 10)))) println(countUniqueSubgraphs(List((1, 2), (2, 3), (3, 4), (4, 5), (4, 6), (6, 7), (4, 8), (8, 9), (9, 10), (9, 11), (11, 12)))) println(countUniqueSubgraphs(List((1, 2), (2, 3), (2, 4), (4, 5), (4, 6), (6, 7), (7, 8), (7, 9)))) } }
[[1, 2], [2, 3], [2, 4], [4, 5], [4, 6], [6, 7], [7, 8], [7, 9]]this has five backbones. This breaks solutions which try to check isomorphism of two paths by only comparing number of nodes connected to each node on the path. \$\endgroup\$