我想在Scala中实现一个函数,给定一组Ints将合并任何包含一个或多个公共元素的Set。
例如,给定:
def mergeSets(sets: Set[Set[Int]]): Set[Set[Int]] = ???
val sets = Set(Set(1,2), Set(2,3), Set(3,7), Set(8,10))
val mergedSets = mergeSets(sets)
mergedSets将包含Set(Set(1,2,3,7),Set(8,10))
如果可能的话,在Scala中执行此操作会有什么好处,有效且有用的功能?
答案 0 :(得分:6)
最有效的方法是使用可变结构,但是你要求一种功能方式,所以这里有:
sets.foldLeft(Set.empty[Set[Int]])((cum, cur) => {
val (hasCommon, rest) = cum.partition(_ & cur nonEmpty)
rest + (cur ++ hasCommon.flatten)
})
(未经测试,使用手机写这篇文章)
答案 1 :(得分:1)
一个很大程度上符合samthebest答案精神的版本,但(按设计)不太习惯。对于那些刚接触函数式编程的人来说,它可能更容易接近。 (似乎我们应该从这么好的问题中挤出一切。)
def mergeSets(sets: Set[Set[Int]]): Set[Set[Int]] = {
if (sets.isEmpty) {
Set.empty[Set[Int]]
} else {
val cur = sets.head
val merged = mergeSets(sets.tail)
val (hasCommon, rest) = merged.partition(_ & cur nonEmpty)
rest + (cur ++ hasCommon.flatten)
}
}
然而,以下替代方案具有尾递归的优点,也许还为理解samthebest的答案提供了更顺畅的途径:
def mergeSets(cum: Set[Set[Int]], sets: Set[Set[Int]]): Set[Set[Int]] = {
if (sets.isEmpty) {
cum
} else {
val cur = sets.head
val (hasCommon, rest) = cum.partition(_ & cur nonEmpty)
mergeSets(rest + (cur ++ hasCommon.flatten), sets.tail)
}
}
def mergeSets(sets: Set[Set[Int]]): Set[Set[Int]] =
mergeSets(Set.empty[Set[Int]], sets)
我并不认为这些都是优越的:只是作为学习工具有用。
答案 2 :(得分:1)
Samthebest的简洁解决方案非常令人满意,它的简洁和优雅,但我正在使用大量的设备,需要一个更高效的解决方案,仍然是不可改变的,并以良好的功能风格编写。
对于每组10个元素的10,000集(随机选择的0到750,000的整数),samthebest的简洁解决方案在我的计算机上平均需要约30秒,而我的解决方案平均需要约400毫秒。
(如果有人想知道,上面设定的基数的结果集包含~3600套,平均每个约26个元素)
如果有人能看到我在风格或表演方面有任何改进,请告诉我!
这是我想出的:
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
}
}
答案 3 :(得分:0)
这可能只是samthebest答案的变体,但为了多样性:
def mergeSets(sets: Set[Set[Int]]): Set[Set[Int]] = {
def hasIntersect(set: Set[Int]): Boolean =
sets.count(set.intersect(_).nonEmpty) > 1
val (merged, rejected) = sets partition hasIntersect
Set(merged.flatten, rejected.flatten)
}
答案 4 :(得分:0)
此问题也可以在不相交集(或联合查找)数据结构https://en.wikipedia.org/wiki/Disjoint-set_data_structure中轻松建模。
在大多数情况下,这将提供对数时间性能。我上传了一个作为修改过的UnionFind算法的gist,并提供了一个mergeSets方法来返回合并的集合。这可以通过路径压缩进一步优化,以实现几乎恒定的时间性能:https://gist.github.com/spyk/fa7ad42baa7abbf50337409c24c44303