【问题标题】:Efficiently find all sets S from a collection of sets C that are contained in a set D从包含在集合 D 中的集合 C 中有效地找到所有集合 S
【发布时间】:2011-12-15 09:31:45
【问题描述】:

我开始收集C 的目标集。每个集合都包含单词(或不带空格的字符串)。当我遍历句子时,我可以将句子视为一组观察到的单词D。我的问题是,对于每个句子,我想在C 中找到所有集合S,使得D 包含S。换句话说,我想在C 中找到所有单词都在句子中的单词集。

例如,考虑C的以下内容:

  {fell, ate}
  {cat, snail, tiger}
  {tree, bush, jalepeno}
  {pen, paperclip, stapler}

现在如果我看到句子“The tree fall over the bush because it ate a jalepeno.”,我想退回以下两组。

  {fell, ate}
  {tree, bush, jalepeno}

这似乎有点类似于 trie。但是,它只是相似的,因为我不是匹配句子中的所有单词,而是匹配任何子集。一个想法是在一个类似于 trie 的数据结构中表示集合 C,每个级别都有一个 Map[String, PseudoTrie],并在我按排序顺序遍历句子中的单词时遵循多个路径。

我知道这类似于搜索查询。但是,如果我需要遍历所有句子也没关系,只要每个句子的计算速度很快。遍历C 中的每个集合并执行包含太慢了。

更新

我认为人们可能会对我的一些结果感兴趣。这些时间是在 100000 个句子上进行的,D 中的每个目标集 C 大约有 4 或 5 个单词。

  1. 原始申请。遍历所有目标集,看看它们是否包含在句子中,其中句子由一组单词表示。 227 秒

  2. 按词汇过滤。与原始过程相同,除了句子由该句子中也在某个目标集中的单词集表示。优点是我们在进行比较时只需要考虑句子中单词的子集。 110 秒

  3. 字符串被转换为从 0 开始的整数键。这也包括必要的试验 #2 中的过滤。 86 秒

  4. 添加倒排索引。 4 秒

我还尝试了带有倒排索引的 BitSet。花了20秒,所以我没有坚持下去。我不确定为什么会变慢——我可能做错了什么。

另外,我认为我最初的想法很棒(许多层的倒排索引)但它相当复杂,而且我已经有了相当不错的加速!

【问题讨论】:

    标签: algorithm scala set subset


    【解决方案1】:

    我们将从您要搜索的句子语料库开始:

    val corpus = Seq(
      Set("fell", "ate"),
      Set("cat", "snail", "tiger"),
      Set("tree", "bush", "jalapeno"),
      Set("pen", "paperclip", "stapler")
    )
    

    一种相当有效的表示方式是使用位表,将词汇类型作为列,将句子作为行。我们定义了几个函数来转换为这种表示形式:

    import scala.collection.immutable.BitSet
    
    def vocabulary(c: Seq[Set[String]]) = c.reduce(_ union _).zipWithIndex.toMap
    
    def convert(s: Set[String], v: Map[String, Int]) = (BitSet.empty /: s) {
      (b, w) => v.get(w).map(b + _).getOrElse(b)
    }
    

    还有一个函数可以在语料库c 中搜索给定句子s 包含的所有句子:

    def search(s: BitSet, c: Seq[BitSet]) = c.filter(x => (x & s) == x)
    

    这将非常快,因为它只是按位“与”和语料库中每个句子的相等比较。我们可以测试:

    val vocab = vocabulary(corpus)
    val table = corpus.map(convert(_, vocab))
    
    val sentence = convert(
      "The tree fell over on the bush because it ate a jalapeno".split(" ").toSet,
      vocab
    )
    
    val result = search(sentence, table)
    

    这给了我们List(BitSet(2, 6), BitSet(5, 7, 10))。确认这是我们想要的:

    val bacov = vocab.map(_.swap).toMap
    result.map(_.map(bacov(_)))
    

    这是List(Set(fell, ate), Set(jalapeno, tree, bush)),如您所愿。

    【讨论】:

    • val bacov 让我玩了一会儿;-)
    • 我认为 BitSet 不适合我的特定应用程序,因为我的词汇量会相当大(3000 字左右)。但很高兴知道 Scala 有如此易于使用的实现,并感谢您的出色回答。
    • 你可以试试BitSet——即使你的句子大小不会像BitSets那样占用比Strings更多的空间(特别是如果你排列词汇表,以便更常见的词出现在词汇表的前面)。
    • 这是一个公平的观点——希望我有时间尝试一下并做一些改进。我喜欢我原来的解决方案(基本上很多倒排索引),但它可能比必要的复杂。
    • 另一种写法def convert(s: Set[String], v: Map[String, Int]): BitSet = s.flatMap(v.get(_))(collection.breakOut)
    【解决方案2】:

    inverted index 对于此类问题可能非常有用。作为起点,考虑创建从C 中的单词到包含该单词的所有集合的列表的映射,因此具有类型Map[String, List[Set[String]]];这是倒排索引。

    使用倒排索引,您可以找到包含在D 中的集合,而无需检查那些与D 有空交集的集合。只需遍历D 中每个不同单词的集合列表,跟踪每个集合遇到的次数。将计数与集合的长度进行比较;集合SD 的子集当且仅当S 的计数等于S 中的元素数。

    这种方法通过消除对那些根本不与D 相交的集合的检查来加快搜索速度。您可以扩展这个想法,通过使用从两个单词集合到包含两个单词的集合列表的索引来消除更多检查。现在,与D 只有一个词相同的集合将不会被检查(因此只有一个词的集合需要单独处理!)。有必要遍历D 的所有二元素子集,将计数与每个集合S 的二元素子集的数量进行比较,但除此之外是相同的。

    甚至更大的子集也可以用作索引中的键,但在某些时候,您生成的可能键将超过将保存的操作数。最佳选择取决于C 的具体情况和句子集。

    【讨论】:

      【解决方案3】:

      您通常可以通过创建字典来为每个单词指定一个数字,然后从字符串之间的比较切换到数字之间的比较来加速基础比较。

      一种简单的方法是从每个集合中随机选择一个单词,然后创建一个字典,将每个单词映射到一个集合列表,从中选择它。然后,给定一个句子,在字典中查找其中的每个单词,看看句子中是否包含任何集合列表。

      您也许能够快速检测到集合何时不是句子的子集。为每个字创建一个稀疏的 64 位位模式,并将每个集合表示为其中每个字的位模式的或。将一个句子表示为其所有单词的或。然后如果 set &~sentence != 0,则该集合不包含在句子中。如果一个集合没有通过这个测试,它就不是一个子集。如果它通过了,不幸的是,它可能仍然不是一个子集,您将不得不使用较慢的测试来确认这一点,但如果有足够多的集合在第一个障碍中失败,您可能会节省时间。根据经验,我会让每个 64 位模式设置 k 个随机选择的位,选择 k 使得表示句子的 64 位模式有大约一半的位设置 - 但你可能会在一个信封的背面稍微想了想。如果您只是在句子中找到特定单词后才进行此测试,那么您当然不能将这个单词包含在您创建的集合中,认为它的存在是理所当然的。

      假设一个单词独立设置了我们 64 位位图中的每个位,并且以概率 x 设置失败。那么在一个有n个单词的句子中,位图中没有为该句子设置一个位,概率为x^n。对于一个有 k 个单词的集合,如果它是由句子而不是由单词设置的,我们可以基于该位丢弃,这种情况发生的概率为 (1-x^k)x^n。如果我对此进行区分,我会在 x = (n/(n+k))^(1/k) 处得到最大值。如果我设置 n = 20 k = 4 ,那么我想要 x = 0.9554 并且在大约 40% 的时间内,位在句子中是清晰的,而单个位在大约 7% 的时间内被丢弃。但是我们有 64 位,它们几乎独立于这个模型,所以我们在 98% 的时间内丢弃了完全不匹配。

      【讨论】:

      • 有关处理签名位图的系统方法,请参阅Bloom filters 或文章中提到的一些替代和扩展。
      猜你喜欢
      • 2014-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-02
      • 2014-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多