Как генерировать транзитивное замыкание множества кортежей?
каков наилучший способ генерации транзитивного замыкания набора кортежей?
пример:
- вход
Set((1, 2), (2, 3), (3, 4), (5, 0))
- выход
Set((1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (5, 0))
4 ответов
//one transitive step
def addTransitive[A, B](s: Set[(A, B)]) = {
s ++ (for ((x1, y1) <- s; (x2, y2) <- s if y1 == x2) yield (x1, y2))
}
//repeat until we don't get a bigger set
def transitiveClosure[A,B](s:Set[(A,B)]):Set[(A,B)] = {
val t = addTransitive(s)
if (t.size == s.size) s else transitiveClosure(t)
}
println(transitiveClosure(Set((1,2), (2,3), (3,4))))
Это не очень эффективная реализация, но это просто.
С помощью unfold
,
def unfoldRight[A, B](seed: B)(f: B => Option[(A, B)]): List[A] = f(seed) match {
case Some((a, b)) => a :: unfoldRight(b)(f)
case None => Nil
}
def unfoldLeft[A, B](seed: B)(f: B => Option[(B, A)]) = {
def loop(seed: B)(ls: List[A]): List[A] = f(seed) match {
case Some((b, a)) => loop(b)(a :: ls)
case None => ls
}
loop(seed)(Nil)
}
становится довольно просто:
def transitiveClosure(input: Set[(Int, Int)]) = {
val map = input.toMap
def closure(seed: Int) = unfoldLeft(map get seed) {
case Some(`seed`) => None
case Some(n) => Some(seed -> n -> (map get n))
case _ => None
}
map.keySet flatMap closure
}
другой способ написания closure
это:
def closure(seed: Int) = unfoldRight(seed) {
case n if map.get(n) != seed => map get n map (x => seed -> x -> x)
case _ => None
}
Я сам не уверен, какой путь мне больше нравится. Мне нравится элегантность тестирования для Some(seed)
чтобы избежать циклов, но, по той же причине, мне также нравится элегантность отображения результата map get n
.
ни одна версия не возвращается seed -> seed
для циклов, поэтому вам придется добавить это, если это необходимо. Здесь:
def closure(seed: Int) = unfoldRight(map get seed) {
case Some(`seed`) => Some(seed -> seed -> None)
case Some(n) => Some(seed -> n -> (map get n))
case _ => None
}
моделируйте задачу как направленный граф следующим образом:
представляют числа в кортежах как вершины в графе. Тогда каждый кортеж (x, y) представляет собой направленное ребро от x до y. После этого используйте Warshall это найти транзитивное замыкание графа.
для результирующего графика каждое направленное ребро затем преобразуется в кортеж (x, y). Это транзитивное замыкание множества кортежей.
предполагая, что у вас есть DAG (в ваших данных примера нет циклов), вы можете использовать приведенный ниже код. Он ожидает, что DAG как карта от T до List[T], которую вы можете получить от своего ввода с помощью
input.groupBy(_._1) mapValues ( _ map (_._2) )
вот транзитивное замыкание:
def transitiveClosure[T]( dag: Map[ T, List[T] ] ) = {
var tc = Map.empty[ T, List[T] ]
def getItemTC( item:T ): List[T] = tc.get(item) match {
case None =>
val itemTC = dag(item) flatMap ( x => x::getItemTC(x) )
tc += ( item -> itemTC )
itemTC
case Some(itemTC) => itemTC
}
dag.keys foreach getItemTC
tc
}
этот код определяет закрытие для каждого элемента только один раз. Однако:
- этот код может вызвать переполнение стека, если есть достаточно длинные пути через DAG (рекурсия не хвостовая рекурсия).
- для большого графика вам, вероятно, будет лучше сделать TC изменяемой картой, а затем преобразовать ее в конце, если вы хотите неизменяемую карту.
- если бы ваши элементы были действительно маленькими целыми числами, как в вашем примере, вы могли бы значительно повысить производительность, используя массивы, а не карты, хотя это усложнило бы некоторые вещи.
чтобы устранить проблему переполнения стека (для DAG), вы можете выполнить топологическую сортировку, переверните его и обработайте элементы по порядку. Но Смотрите также эту страницу: