合并集合包含Scala中的公共元素

时间:2014-09-02 04:44:12

标签: scala scala-collections

我想在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中执行此操作会有什么好处,有效且有用的功能?

5 个答案:

答案 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