Performance 相同列表中的Scala Performant筛选器子集

Performance 相同列表中的Scala Performant筛选器子集,performance,scala,collections,Performance,Scala,Collections,我过滤一个集合,基于同一集合的其他值,更准确地说,过滤掉完全包含在另一个集合中的集合 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]] = {

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

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个值


有没有办法让这一切进行得更快?(使用另一种类型的集合、不同的方法调用、更改循环方式等)

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

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
  }

老实说,我在分析这比什么都好的时候遇到了一点问题,但这就是问题所在。你能告诉我我很无聊吗?

一般来说,你不会比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.

可能还有其他/更好的解决方案,但有一个想法:如果您可以将外部
集合
更改为排序集合(根据内部
集合
s的大小),则只能检查比当前集合大的
集合
Set
还有一个
subsetOf()
方法,该方法可能比
s.forall(x.contains)
@Marth快,然后(从Scala库源代码)再次使用
def subsetOf(即:GenSet[a]):Boolean=this for all the
:)(
x.contains
相当于集合的
x.apply
,因此这与OP的测试相同).使用
子项
更能揭示意图,如果你有理由相信你的许多集合都有独特的元素,那么你可以保留一组迄今为止看到的所有元素,并首先与短路检查每个好集合的元素进行比较。好主意,将会有许多独特的元素,因此这如果要提高性能,我会尽快进行测试。这是一个好主意,我的数据实际上会很长,因为数字太多了(还有一些随机性),所以这可能是一个很好的解决方案,我会尝试一下,让你一直坚持下去。我已经尝试过这种方法,大约有一半的时间,这是完美的,我“我会尝试进一步优化,但这基本上解决了我的问题。谢谢