【问题标题】:Scala Performant Filter Subsets in same lists相同列表中的 Scala 高性能过滤器子集
【发布时间】:2017-02-09 06:54:18
【问题描述】:

我正在过滤一个 Set,基于同一个 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 个值

有什么方法可以让这个过程更快吗? (使用另一种类型的集合,不同的方法调用,改变它的循环方式等)

【问题讨论】:

  • 可能还有其他/更好的解决方案,但有一个想法:如果您可以将外部Set 更改为排序集合(按内部Sets 的大小),您可以只检查Sets 比当前更大。 Set 也有一个 subsetOf() 方法,它可能比 s.forall(x.contains) 更快。
  • @Marth,然后(来自 Scala 库源)def subsetOf(that: GenSet[A]): Boolean = this forall that :) (x.contains 等同于 x.apply 对于集合,所以这与 OP 的测试相同)。不过,使用subsetOf 更能揭示意图

标签: performance scala collections


【解决方案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
  }

老实说,我在分析什么时候这比其他任何事情都好时有点问题,但是你去吧。你能说我很无聊吗?

【讨论】:

  • 如果您有理由相信您的许多集合将具有独特的元素,那么您可以维护迄今为止看到的所有元素的集合,并首先与会短路单独检查每个好的集合的那个进行比较。
  • 好主意,会有很多独特的元素,所以这可能会提高性能,我会尽快测试一下
【解决方案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.

【讨论】:

  • 哦,这是个好主意,我的数据实际上将是 Longs,因为数量庞大(和一些随机性),所以这可能是一个很好的解决方案,我会尝试一下让您随时了解情况
  • 我已经尝试过这种方法,它的时间大约减半,非常完美,我会尝试进一步优化它,但这几乎解决了我的问题。谢谢
猜你喜欢
  • 2014-03-31
  • 2021-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多