后续搜索

时间:2011-09-21 01:18:44

标签: regex language-agnostic sequence

我有大量的列表(总共35 MB),我想搜索子序列:每个术语必须按顺序出现,但不一定是连续出现。 <1,2>每个匹配

1, 2, 3, 4, 5, 6
1, 2, 2, 3, 3, 3

但不是

6, 5, 4, 3, 2, 1
123, 4, 5, 6, 7

,是分隔符,不是要匹配的字符。)

如果没有在数十或数十万个序列上运行正则表达式(/1, ([^,]+, )*2, ([^,]+, )*3/),我如何确定哪些序列匹配?我可以预处理序列,但内存使用需要保持合理(在现有序列大小的常数因子内,比方说)。最长的序列很短,不到一千字节,所以你可以假设查询也很短。

3 个答案:

答案 0 :(得分:2)

这让我想起了生物信息学中的序列比对,您尝试将一小段DNA与大型数据库进行匹配。差异是您可能更大的字母表,以及您对任意长间隙的容忍度增加。

你可能会发现一些灵感来看现有的工具和算法,特别是Smith-Waterman和BLAST。

答案 1 :(得分:1)

也许我误解了,但这不是直截了当的吗?

search = [1 2 3]
for sequence in sequences:
  sidx = 0
  for item in sequence:
    if item==search[sidx]:
       sidx++
       if sidx>=len(search): break
  if sidx>len(search):
    print sequence + "matches"

对于N个序列似乎是O(N) 和O(M)用于搜索子序列长度M

不确定这是否会比正则表达式快得多?

答案 2 :(得分:1)

如果单个数字分布在文件上而没有出现在大多数行上,那么简单地索引它们出现的行号可以加快速度。但是,如果您的数据是以不同顺序重复的相同数字的行,则会更慢。

要构建索引,只需要沿着这些行单次传递数据:

Hash<int, List<int>> index

line_number = 1
foreach(line in filereader)
{
    line_number += 1
    foreach(parsed_number in line)
        index[parsed_number].append(line)
}

可以为数据集存储和重用该索引。要搜索它只需要这样的东西。请原谅混合的伪代码,我试图尽可能清楚。当它超出可能的匹配时它“返回”,当子串的所有元素都出现在该行上时,“产生”一个行号。

// prefilled hash linking number searched for to a list of line numbers
// the lines should be in ascending order
Hash<int, List<int>> index

// The subsequence we're looking for
List<int> subsequence = {1, 2, 3}
int len = subsequence.length()

// Take all the lists from the index that match the numbers we're looking for
List<List<int>> lines = index[number] for number in subsequence

// holder for our current search row
// has the current lowest line number each element occurs on 
int[] search = new int[len]
for(i = 0; i < len; i++)
    search[i] = lines[i].pop()

while(true)
{
    // minimum line number, substring position and whether they're equal
    min, pos, eq = search[0], 0, true

    // find the lowest line number and whether they all match
    for(i = 0; i < len; i++)
    {
        if(search[i] < min)
            min, p, eq = search[i], i, false
        else if (search[i] > min)
            eq = false
    }

    // if they do all match every one of the numbers occurs on that row
    if(eq)
    {
        yield min; // line has all the elements

        foreach(list in lines)
            if(list.empty())  // one of the numbers isn't in any more lines
                 return

        // update the search to the next lowest line number for every substring element
        for(i = 0; i < len; i++)
            search[i] = lines[i].pop()
    }
    else
    {
        // the lowest line number for each element is not the same, so discard the lowest one
        if(lines[position].empty()) // there are no more lines for the element we'd be updating
            return

        search[position] = lines[position].pop();
    }
}

注意:

  1. 这可以简单地扩展到存储行中的位置以及行号,然后在“yield”点只有一点额外的逻辑就可以确定实际的匹配而不仅仅是这些物品都存在。

  2. 我使用“pop”来显示它是如何在行号中移动的,但实际上并不想在每次搜索时都破坏索引。

  3. 我假设这些数字都适合整数。如果你的数字真的很大,可以将它扩展为long,甚至将每个数字的字符串表示映射到int。

  4. 有一些加速,特别是在“流行”阶段跳过线路,但我更明确的解释。

  5. 无论是使用这种方法还是其他方法,您还可以根据数据进行计算。单个通道可以确定每条线是上升,下降,所有奇数,所有偶数,还是最高和最低数字都可以用来减少每个子字符串的搜索空间。这些是否有用完全取决于您的数据集。