La soluzione concisa di Samthebest è molto soddisfacente nella sua semplicità ed eleganza, ma sto lavorando con un gran numero di set e ho bisogno di una soluzione più performante che sia ancora immutabile e scritta in buono stile funzionale.
Per 10.000 set con 10 elementi ciascuno (valori scelti casualmente da 0 a 750.000), la soluzione concisa di samthebest richiedeva una media di ~ 30 secondi sul mio computer, mentre la mia soluzione di seguito richiedeva in media ~ 400 ms.
(Nel caso qualcuno si chiedeva, l'insieme risultante per le cardinalità impostata sopra contiene ~ 3600 insiemi, con una media di ~ 26 elementi ciascuno)
Se chiunque può vedere eventuali miglioramenti ho potuto fare rispetto a stile o prestazione, per favore fatemelo sapere!
Ecco cosa mi è venuta:
val sets = Set(Set(1, 2), Set(2, 3), Set(4, 5))
Association.associate(sets) => Set(Set(1, 2, 3), Set(4, 5))
object Association {
// Keep track of all current associations, as well as every element in any current association
case class AssociationAcc[A](associations: Set[Set[A]] = Set.empty[Set[A]], all: Set[A] = Set.empty[A]) {
def +(s: Set[A]) = AssociationAcc(associations + s, all | s)
}
// Add the newSet to the set associated with key A
// (or simply insert if there is no such key).
def updateMap[A](map: Map[A, Set[A]], key: A, newSet: Set[A]) = {
map + (key -> (map.getOrElse(key, Set.empty) ++ newSet))
}
// Turn a Set[Set[A]] into a map where each A points to a set of every other A
// it shared any set with.
//
// e.g. sets = Set(Set(1, 2), Set(2, 3), Set(4, 5))
// yields: Map(1 -> Set(2), 2 -> Set(1, 3), 3 -> Set(2),
// 4 -> Set(5), 5 -> Set(4))
def createAssociationMap[A](sets: Set[Set[A]]): Map[A, Set[A]] = {
sets.foldLeft(Map.empty[A, Set[A]]) { case (associations, as) =>
as.foldLeft(associations) { case (assoc, a) => updateMap(assoc, a, as - a) }
}
}
// Given a map where each A points to a set of every A it is associated with,
// and also given a key A starting point, return the total set of associated As.
//
// e.g. with map = Map(1 -> Set(2), 2 -> Set(1, 3), 3 -> Set(2),
// 4 -> Set(5), 5 -> Set(4))
// and key = 1 (or 2 or 3) yields: Set(1, 2, 3).
// with key = 4 (or 5) yields: Set(4, 5)
def getAssociations[A](map: Map[A, Set[A]], key: A, hit: Set[A] = Set.empty[A]): Set[A] = {
val newAssociations = map(key) &~ hit
newAssociations.foldLeft(newAssociations | hit + key) {
case (all, a) => getAssociations(map, a, all)
}
}
// Given a set of sets that may contain common elements, associate all sets that
// contain common elements (i.e. take union) and return the set of associated sets.
//
// e.g. Set(Set(1, 2), Set(2, 3), Set(4, 5)) yields: Set(Set(1, 2, 3), Set(4, 5))
def associate[A](sets: Set[Set[A]]): Set[Set[A]] = {
val associationMap = createAssociationMap(sets)
associationMap.keySet.foldLeft(AssociationAcc[A]()) {
case (acc, key) =>
if (acc.all.contains(key)) acc
else acc + getAssociations(associationMap, key)
}.associations
}
}
Non compilare (non si può avere _ in parentesi nel filtro). L'ho modificato per risolvere il problema e funziona sull'unico caso di prova che ho provato :) –
@Paul Thx, e in effetti c'era un bug che ho corretto. Anche io penso - è deprecato, quindi lo ho cambiato in filterNot – samthebest
Puoi usare 'partition' per dividere' cum' nei set con le intersezioni e il resto? Potrebbe essere un po 'più chiaro? –