C#中的高速字符串匹配

时间:2011-11-16 03:52:56

标签: c# string search

我在List<T>中列出了大约10,000名员工,我有一个ListBox,其中包含这些员工的子集,具体取决于文本框中的搜索字词。

假设Staff对象具有以下公开公开的属性:

string FirstName
string LastName
string MiddleName
   int StaffID
   int CostCentre

我可以写一个这样的函数:

bool staffMatchesSearch(Staff stf)
{
  if (tbSrch.Text.Trim() == string.Empty)
    return true; // No search = match always.

  string s = tbSrch.Text.Trim().ToLower();

  // Do the checks in the order most likely to return soonest:
  if (stf.LastName.ToLower().Contains(s))
    return true;
  if (stf.FirstName.ToLower().Contains(s))
    return true;
  if (stf.MiddleName.ToLower().Contains(s))
    return true;

  if (stf.CostCentre.ToString().Contains(s))
    return true; // Yes, we want partial matches on CostCentre
  if (stf.StaffID.ToString().Contains(s))
    return true; // And also on StaffID

  return false;
}

然后执行以下操作:

tbSrch_TextChanged(object sender, EventArgs e)
{
  lbStaff.BeginUpdate();
  lbStaff.Items.Clear();

  foreach (Staff stf in staff)
    if (staffMatchesSearch(stf))
      lbStaff.Items.Add(stf);

  lbStaff.EndUpdate();
}

每次用户更改tbSrch框的内容时,都会重新评估过滤。

这很有效,并且非常慢,但我想知道我是否可以更快地制作它?

我试图将整个事情重新编写为多线程,但只有10,000名员工,开销似乎带走了大部分收益。此外,还有一堆其他错误,如果搜索&#34; John&#34;,用户首先按下&#34; J&#34;它会线程化线程,但当用户按下&#34; o&#34;在第一批有机会返回结果之前,另一组被假脱机。很多时候,结果会以混乱的顺序返回,并且会发生各种令人讨厌的事情。

我可以想到一些调整可以使最佳情况显着改善,但它们也会使最坏情况更糟糕。

您对如何改进这一点有什么想法吗?


我已经实施的很好的建议及其结果:

  • ValueChanged事件上添加延迟,以便在用户在键盘上快速键入5个字符的名称时,它最后只执行1次搜索而不是5次串行搜索。
  • 预评估ToLower()并存储在Staff类中(作为[NonSerialized]属性,因此它不会占用保存文件中的额外空间。)
  • get中添加Staff属性,该属性将所有搜索条件作为单个长小写连接字符串返回。然后在其上运行一个Contains()。 (此字符串存储在Staff对象中,因此只构造一次。)

到目前为止,这些搜索时间从大约140毫秒降低到大约60毫秒(尽管这些数字非常主观,具体取决于实际执行的搜索和返回的结果数量。)

5 个答案:

答案 0 :(得分:7)

1)正如评论中所指出的,你可能不应该.ToString数字字段 - 只匹配数字

2)ToLower调用是一个perf漏洞。将这些属性的小写版本添加到Staff类,因此ToLower只需要执行一次

3)当用户输入另一个字符时,您无需重新评估所有项目。输入一个字符只会减少匹配次数,所以只能重新评估之前的匹配。

4)使用计时器在用户输入和开始搜索之间引入延迟。如果他们快速输入多个字符,你可能要等到他们暂停了半秒

5)检查按下的键。如果NaN则不检查int属性。

答案 1 :(得分:2)

您可以将“SearchTerm”私有属性添加到(FirstName + LastName + MiddleName + StaffID + CostCentre).ToLower()的Staff对象,然后执行Contains()检查。这将阻止您每次对每个字符串执行ToLower(),并将Contains()个检查的数量从5减少到1。

答案 2 :(得分:2)

您可以尝试实施trie或“前缀树”:

http://en.wikipedia.org/wiki/Trie

这将允许您使用值搜索开始的文本。

我相信suffix-trees上的链接文章会允许您进行全文搜索,但它具有更高的存储要求。

在将结果插入结构时,请确保ToLower所有数据,这样您在查找时就不必进行不区分大小写的比较。

答案 3 :(得分:1)

看到你做过的事情(主要来自对@ mikel回答的评论),听起来就像你到了那里。我没有看到哪个可以提高速度的建议是使用StringComparison.OrdinalIgnoreCase进行比较。在您的情况下,这将意味着替换

if (stf.LastName.ToLower().Contains(s))
  return true;

if (stf.LastName.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0)
  return true;

这是MSDN link,讨论快速字符串比较。

答案 4 :(得分:0)

using System;
using System.Text.RegularExpressions;
namespace PatternMatching1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Please enter the first string.");
                string str = Console.ReadLine(); ;
                string replacestr = Regex.Replace(str, "[^a-zA-Z0-9_]+", " ");



                Console.WriteLine("Please enter the second string.");
                string str1 = Console.ReadLine(); ;
                string replacestr1 = Regex.Replace(str1, "[^a-zA-Z0-9_]+", " ");



                if (replacestr.Length == replacestr1.Length)
                {
                    char[] cFirst = replacestr.ToLower().ToCharArray();
                    char[] cSecond = replacestr1.ToLower().ToCharArray();

                    Array.Sort<char>(cFirst);
                    Array.Sort<char>(cSecond);

                    if ((new string(cFirst)).Equals((new string(cSecond))))
                        Console.WriteLine("Both String Same");
                    else
                        Console.WriteLine("Both String Not Same");
                }
                else
                    Console.WriteLine("oopsss, something going wrong !!!! try again");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.Read();
        }
    }
}

`