从集合D中包含的集合C的集合中有效地找到所有集合S.

时间:2011-10-26 00:31:33

标签: algorithm scala set subset

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

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

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

现在,如果我看到句子“因为它吃了一个jalepeno,树倒在灌木丛上。”,我想要回到下面的两套。

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

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

我理解这类似于搜索查询。但是,如果我需要遍历所有句子,只要每个句子的计算速度很快就可以了。迭代C中的每个集合并执行包含太慢了。

更新

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

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

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

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

  4. 添加倒排索引。 4 s

  5. 我还尝试使用倒排索引的BitSet。花了20秒,所以我没有坚持下去。我不确定为什么放缓 - 我可能做错了什么。

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

3 个答案:

答案 0 :(得分:5)

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

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))

答案 1 :(得分:2)

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

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

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

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

答案 2 :(得分:1)

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

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

您可以快速检测到某个集合不是句子的子集。为每个字创建一个稀疏的64位位模式,并将每个集合表示为其中每个字的位模式。代表一个句子作为其所有单词。然后,如果设置&〜句子!= 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%的时间内丢弃完全不匹配。