Scala Performant过滤器子集在同一列表中

时间:2016-09-30 16:15:33

标签: performance scala collections

我正在过滤一个Set,基于同一个Set的其他值,更确切地说,过滤掉完全包含在另一个集合中的集合

Set(Set(1, 2), Set(1, 3), Set(1, 4), Set(1, 2, 3))

这将导致:

Set(Set(1, 4), Set(1, 2, 3))

1,2和2,3完全包含在最后一组中(基本上,我只想要该批次中最大的子集)

我已经提出了这段代码,它可以解决这个问题:

def filterIncluded(data: Set[Set[Int]]): Set[Set[Int]] = {  
  data.filterNot{ s =>
    data.exists(x => x.size > s.size && s.forall(x.contains))
  }
}

除了一个问题外,效果很好:效率非常低,我的实际数据集将拥有数百万套,每套都有2到100个值

有没有办法让它更快? (使用其他类型的集合,不同的方法调用,更改循环方式等)。

2 个答案:

答案 0 :(得分:2)

一般来说,你不能做得比N ^ 2更好,因为你正在一个更大的空间中寻找碰撞,而这个空间并没有以任何常规的方式受到约束。

但是你可能解决这个问题。您的数据可能有一个特定的结构。

例如,如果数字大致是随机的,您可以计算每个数字的出现次数;如果数字只出现一次,则包含它的集合不能是严格的子集。如果您只有一个小数字,只需像上面那样强行搜索,您就会知道哪些是唯一的。如果您开始使用该区别号码开始获取大量集合(如果数字大致是随机的,则不太可能,但假设您这样做),您可以再次根据第二个数字进行细分。使用您的示例:

data.toList.flatMap(_.toList).groupBy(identity).map{ 
  case (k,vs) => k -> vs.length
}
// Gives counts: 1 -> 4, 2 -> 2, 3 -> 2, 4 -> 1
// Pick out the set with a 4: it is unique
// Pick out sets with a 2: Set(1, 2), Set(1, 2, 3)
// Run your algorithm to discard Set(1,2)
// Pick out sets with a 3: Set(1, 3), Set(1, 2, 3)
// Run your algorithm to discard Set(1,3)
// Pick out sets with a 1: only Set(1, 2, 3) remains, keep it

或者,如果你可以拥有任何Int,但实际上往往有一堆相似的数字,你可以构建相当于后缀树的集合。从一个集合开始,这是所有数字的联合。然后,对于每个元素,列出具有该元素的每个集合。然后,在该列表下,再次通过第二个元素将其分解。只要你达到一个实际拥有全套的级别,并且列表是非空的,你就可以丢弃整套。

1 -> Set(1, 2, 3), Set(1, 2), Set(1, 3), Set(1, 4)
  2 -> Set(1, 2, 3), Set(1, 2)
    But we're _at_ 1,2 so
      throw away Set(1, 2)
      only Set(1, 2, 3) is left--keep it
  3 ->  Set(1, 2, 3); Set(1, 3)
    We're at 1,3 so
      throw away Set(1, 3)
      the other set is already kept
  4 -> Set(1, 4)
    Oh, there's only one.  Keep it.

答案 1 :(得分:1)

我能想到的第一个改进是:

def filterIncluded(data: Set[Set[Int]]): Set[Set[Int]] = {
    val undecided = data.toList.sortBy(_.size).reverse

    undecided.foldLeft(List.empty[Set[Int]]){ case (goodSets, s) =>
        if(goodSets.forall(goodSet => !s.forall(goodSet contains _))) s :: goodSets
        else goodSets
    }.toSet
  }

排序是NLogN,但是您只需将每个元素与已经证明良好的元素进行比较,因为您只能是较大或相同大小的集合的适当子集。它仍然是N ^ 2,但是我认为它的效率稍高一些。

或者你可以做这个更复杂的事情,实际上听起来像其他人的答案,你维护一个元素的地图到包括它的好集。然后在检查一个新集时,您可以获取包含第一个元素的集合,然后为每个后续元素获取哪些集合具有该集合并获取交集,直到您有一个空交集(没有任何超集)或者您运行元素之外(剩下的一切都是超集)。这可能是一个丑陋的实现:

  def filterIncluded(data: Set[Set[Int]]): Set[Set[Int]] = {
    def isGood(s: Set[Int], goodSets: Map[Int, Set[Set[Int]]]): Boolean = goodSets.get(s.head) match {
      case None => true
      case Some(sets) => _isGood(s.tail, sets, goodSets)
    }

    def _isGood(s: Set[Int], potentialSupersets: Set[Set[Int]], goodSets: Map[Int, Set[Set[Int]]]): Boolean = {
      // println(s"s($s)\npotentialSupersets($potentialSupersets)\ngoodSets($goodSets)\n")
      goodSets.get(s.head) match {
        case None => true
        case Some(sets) =>
          (s.tail.isEmpty, potentialSupersets & sets) match {
            case (true, remaining) if remaining.nonEmpty => false
            case (false, remaining) if remaining.nonEmpty => _isGood(s.tail, remaining, goodSets)
            case _ => true
          }
        }
    }

    def addToGoodSets(s: Set[Int], goodSets: Map[Int, Set[Set[Int]]]): Map[Int, Set[Set[Int]]] = {
      s.foldLeft(goodSets){case (g, i) => g + (i -> (g.getOrElse(i, Set.empty)+s))}
    }

    val undecided = data.toList.sortBy(_.size).reverse
    // println("UNDECIDED: "+undecided)

    undecided.foldLeft(Map.empty[Int, Set[Set[Int]]]){ case (goodSets, s) =>
      if(isGood(s, goodSets)) addToGoodSets( s, goodSets)
      else goodSets
    }.values.flatten.toSet
  }

老实说,分析什么时候比其他任何东西都要好,我有点问题,但是你去了。你能告诉我无聊吗?