【问题标题】:Speed up string search algorithm加速字符串搜索算法
【发布时间】:2012-02-10 21:23:37
【问题描述】:

我正在使用这个简单的算法来搜索文档中的一些文本并标记我在哪个页面上找到它

for (int i = 1; i <= a.PageCount; i++)
{
    Buf.Append(a.Pages[i].Text);
    String contain = Buf.ToString();
    if (contain != "")
    {
        // Inside is dictionary of keys and value contain page where I found it
        foreach (KeyValuePair<string, List<string>> pair in inside)
        {
              if (contain.Contains(pair.Key))
                  inside[pair.Key].Add((i).ToString());
        }
    }

    Buf.Clear();
 }

我没有问题,但是当我在700页的文档中搜索并且我正在寻找超过500个键时,它非常慢,大约需要1-2分钟才能通过,有什么办法可以加快速度吗?我正在使用 C#

谢谢!

【问题讨论】:

  • 什么是文件?您可以先确定整个文件中实际包含哪些键,然后逐页搜索这些键吗?
  • 它的 pdf 文档,但它与文件格式无关,它的产品目录和一些页面包含产品类型的表格 - 我需要创建所有键的索引 - 它们在哪里 - 在哪些页面上

标签: c# string performance dictionary


【解决方案1】:

几点:

  • 摆脱Buf;只需将a.Pages[i].Text 直接分配给contain
  • inside[pair.Key] 浪费时间查找与该键关联的值;时间被浪费了,因为您在 pair.Value 中对该对象的引用要便宜得多。
  • 如果您有一个整数值列表,为什么要将它们存储为字符串?

示例代码:

for (int i = 1; i <= a.PageCount; i++)
{
    String contain = a.Pages[i].Text
    if (contain != "")
    {
        // Inside is dictionary of keys and value contain page where I found it
        foreach (KeyValuePair<string, List<int>> pair in inside)
        {
            if (contain.Contains(pair.Key))
                pair.Value.Add(i);
        }
    }
}

最后,确保Pages 确实使用了从一开始的索引。集合更常见的是零索引。

编辑,因为Pages 是字典:

foreach (KeyValuePair<int, Page> kvp in a.Pages)
{
    string contain = kvp.Value.Text;
    if (contain == "")
        continue;
    foreach (KeyValuePair<string, List<int>> pair in inside)
        if (contain.Contains(pair.Key))
            pair.Value.Add(kvp.Key);
}

您为第一个代码示例计时了多少次?时间可能会因许多外部因素而异;一种方法的单次运行比另一种方法的单次运行更快或更慢这一事实并不能告诉你太多,尤其是因为我提出的建议可能无法解决大部分问题。

正如其他人指出的那样,主要问题是您调用了contain.Contains(pair.Key) 350,000 次;这可能是你的瓶颈。您可以分析该方法以确定是否属实。如果它正确的,那么像 Miserable Variable 建议的 Rabin Karp 算法可能是你最好的选择。

【讨论】:

  • 我试过了,但是比以前花了更长的时间,我不知道为什么。页面是字典类型,是的,它的页面来自 pdfLibNet,它们从 1 开始索引
  • @MartinCh 如果它是字典类型,那么您也可以在那里使用“foreach (KeyValuePair<... .>
  • 谢谢,我运行代码分析后发现,Pages[].Text 占用了大约 89% 的处理时间,所以存在主要问题
  • @MartinCh 很好地确认了在尝试提高其性能之前分析您的代码的建议!
【解决方案2】:

[[

编辑:以下内容无关紧要,因为您在循环结束时清除 Buf(但请注意,您实际上并不需要 buf,string pageText = a.Pages[i].Text 就是您所需要的)

Buf 是什么?你有

Buf.Append(a.Pages[i].Text);

这不会强制Contains 浏览越来越大的字符串吗?我很惊讶你没有用完 700 页的内存。

]]

有更有效的方法可以查看any of a set of strings 是否出现在另一个string 中。例如,您可以准备一个键的树结构,这样您就不必多次比较。

Rabin-Karp Algorithm

请考虑现有的第三方库,一定有一些。

【讨论】:

  • 他在循环的每次迭代结束时清除 Buf(在底部)
  • 我假设Buf 是一个StringBuilder。每次迭代都会清除它,所以除了减慢程序速度之外,它什么也不做。
【解决方案3】:

我没有 700 页可供测试,但您可以尝试使用正则表达式:

var s = Stopwatch.StartNew();
var r = new Regex(string.Join("|", from x in inside select Regex.Escape(x.Key)));

for (int i = 1; i <= a.PageCount; i++)
{
    foreach (Match match in r.Matches(a.Pages[i].Text))
    {
        inside[match.Value].Add(i.ToString());
    }
}

Console.WriteLine(s.Elapsed);

【讨论】:

  • 他有很多键要搜索。即使是单曲,我也怀疑regex 在非通配符匹配时会更快,但我可能是错的。
【解决方案4】:

标准性能/调试过程 - 注释掉您的代码片段并进行测量。一次添加一个,直到它“变坏”。这可能是您的问题所在。

例如,您可以从注释掉整个 foreach 开始。

看起来有一些可能很复杂/昂贵的对象在使用 - inside、Buf 等。注释掉这些对象的用法并一次放回一个。

【讨论】:

  • 似乎没有必要 - 使用 500 个键和 700 页,他的算法正在对一页文本进行 350,000 次搜索以查找键。这很可能是花费时间的地方。
猜你喜欢
  • 2012-01-24
  • 1970-01-01
  • 2010-12-18
  • 1970-01-01
  • 2013-01-20
  • 1970-01-01
  • 1970-01-01
  • 2013-01-06
相关资源
最近更新 更多