【问题标题】:Fast way to use String.Contains with huge list C#使用带有巨大列表 C# 的 String.Contains 的快速方法
【发布时间】:2015-01-26 10:34:31
【问题描述】:

我有这样的事情:

List<string> listUser;
listUser.Add("user1");
listUser.Add("user2");
listUser.Add("userhacker");
listUser.Add("user1other");

List<string> key_blacklist;
key_blacklist.Add("hacker");
key_blacklist.Add("other");

foreach (string user in listUser)
{
   foreach (string key in key_blacklist)
   {
      if (user.Contains(key))
      {
           // remove it in listUser
      }
   }
}

listUser的结果是:user1,user2。

问题是如果我有一个巨大的listUser(超过 1000 万)和巨大的key_blacklist(100.000)。该代码非常非常慢。 无论如何都要更快?

更新:我在那里找到了新的解决方案。 http://cc.davelozinski.com/c-sharp/fastest-way-to-check-if-a-string-occurs-within-a-string 希望当他进入那里时能对某人有所帮助! :)

【问题讨论】:

  • 是否可以将其保留为字典?
  • 该列表是错误的集合。 HashSet 会好很多。
  • @AndreasNiedermair, @Jon 当您检查字符串是否包含其他字符串时,HashSet 将如何提供帮助?
  • @tinhve 因为它被标记为database,所以您的 1000 万个项目可能存储在数据库中。正确的做法是让数据库来执行此操作,但为了让我们帮助您,我们需要知道您使用的是哪种数据库以及如何访问它。
  • 子字符串搜索总是很慢。但是你为什么要先添加这个用户呢?如果您不添加他,则以后不需要删除他。另外,正如 Rawling 已经提到的,您使用的是什么数据库?你想从数据库中删除它们吗?

标签: c# database performance


【解决方案1】:

如果您对如何构建用户列表没有太多控制权,您至少可以并行测试列表中的每个项目,这在具有多核的现代机器上会加快检查速度。

        listuser.AsParallel().Where(
            s =>
            {
                foreach (var key in key_blacklist)
                {
                    if (s.Contains(key))
                    {
                        return false; //Not to be included
                    }
                }

                return true; //To be included, as no match with the blacklist
            });

另外 - 你必须使用 .Contains 吗? .Equals 会快得多,因为在几乎所有情况下,当 HashCode 不同时,将确定不匹配,这只能通过整数比较找到。超级快。

如果您确实需要 .Contains,您可能需要考虑重构应用程序。列表中的这些字符串真正代表什么?单独的用户子组?我可以在添加时测试每个字符串是否代表黑名单上的用户?

更新:响应@Rawling 下面的评论 - 如果您知道有一组有限的用户名,例如,“hacker”作为子字符串,那么在运行 .Equals 测试之前,该组必须非常大对候选人的每个用户名将比在候选人上运行 .Contains 慢。这是因为HashCode真的很快。

【讨论】:

  • 从示例黑名单和用户名中,我认为很明显他确实需要Contains
  • 但不清楚的是他是否可以通过稍微不同地组织用户名来避免这样做?为什么黑名单需要包含子字符串而不是黑名单的完整集合?
  • (我不是说你错了,我只是说不清楚其他限制是什么,以及他是否可以通过采取不同的方法来提高效率)
  • 这是一个子串的黑名单。 hacker 大概会阻止任何带有hacker in 的用户名。但我同意“在添加时测试每个字符串是否代表黑名单上的用户?”
  • @tinhve - 还要记住 100 个线程是多余的。一旦每个内核有多个线程,很可能不是所有线程都在使用,或者上下文切换正在消耗 CPU 周期。
【解决方案2】:

如果您使用实体框架或 linq to sql,则使用 linq 并将查询发送到服务器可以提高性能。 然后,您实际上是在查询满足要求的项目,即名称不包含禁止表达的用户,而不是删除项目:

listUser.Where(u => !key_blacklist.Any(u.Contains)).Select(u => u).ToList();

【讨论】:

    【解决方案3】:

    一种可能的解决方案是使用树状数据结构。

    基本思想是将列入黑名单的单词按如下方式组织:

    + h
    | + ha
    |   + hac
    |     - hacker
    |     - [other words beginning with hac]
    | 
    + f
    | + fu
    |   + fuk
    |     - fukoff
    |     - [other words beginning with fuk]
    

    然后,当您检查列入黑名单的单词时,如果您发现您的用户字符串甚至不包含“h”,则避免搜索以“hac”开头的整个单词列表。

    在我提供的示例中,对于您的示例数据,这当然没有任何区别,但是对于实际数据集,这应该会显着减少 Contains 的数量,因为您没有检查完整列表每次都被列入黑名单。

    这是一个代码示例(请注意代码很糟糕,这只是为了说明我的想法)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    class Program {
    
        class Blacklist {
            public string Start;
            public int Level;
            const int MaxLevel = 3;
            public Dictionary<string, Blacklist> SubBlacklists = new Dictionary<string, Blacklist>();
            public List<string> BlacklistedWords = new List<string>();
    
            public Blacklist() {
                Start = string.Empty;
                Level = 0;
            }
    
            Blacklist(string start, int level) {
                Start = start;
                Level = level;
            }
    
            public void AddBlacklistedWord(string word) {
                if (word.Length > Level && Level < MaxLevel) {
                    string index = word.Substring(0, Level + 1);
                    Blacklist sublist = null;
                    if (!SubBlacklists.TryGetValue(index, out sublist)) {
                        sublist = new Blacklist(index, Level + 1);
                        SubBlacklists[index] = sublist;
                    }
                    sublist.AddBlacklistedWord(word);
                } else {
                    BlacklistedWords.Add(word);
                }
            }
    
            public bool ContainsBlacklistedWord(string wordToCheck) {
                if (wordToCheck.Length > Level && Level < MaxLevel) {
                    foreach (var sublist in SubBlacklists.Values) {
                        if (wordToCheck.Contains(sublist.Start)) {
                            return sublist.ContainsBlacklistedWord(wordToCheck);
                        }
                    }
                }
                return BlacklistedWords.Any(x => wordToCheck.Contains(x));
            }
    
        }
    
        static void Main(string[] args) {
    
            List<string> listUser = new List<string>();
            listUser.Add("user1");
            listUser.Add("user2");
            listUser.Add("userhacker");
            listUser.Add("userfukoff1");
    
            Blacklist blacklist = new Blacklist();
            blacklist.AddBlacklistedWord("hacker");
            blacklist.AddBlacklistedWord("fukoff");
    
            foreach (string user in listUser) {
                if (blacklist.ContainsBlacklistedWord(user)) {
                    Console.WriteLine("Contains blacklisted word: {0}", user);
                }
            }
        }
    }
    

    【讨论】:

    • 如果在字符串的开头搜索,树的效果相当好,但对于其他任何情况都没有帮助。考虑一个列入黑名单的词是否是“hacker”并且用户的名字是“ImNotAHacker”。能够显着减少 Contains() 调用次数的简单树的实际实例对于真实世界的数据来说非常小。否则,当您说WHERE column LIKE "%somevalue%" 时,为什么您认为任何 RDBMS 都必须诉诸表扫描?
    • @dodexahedron:如果你有一个单一的值要检查,你不能比全表扫描做得更好,但在这种情况下(100k 列入黑名单的词)无论如何这可能会有所作为。
    【解决方案4】:

    你使用了错误的东西。如果您有大量数据,您应该使用HashSet&lt;T&gt;SortedSet&lt;T&gt;。如果您不需要对数据进行排序,请使用HashSet&lt;T&gt;。这是我编写的一个程序来演示时差:

    class Program
    {
        private static readonly Random random = new Random((int)DateTime.Now.Ticks);
    
        static void Main(string[] args)
        {
            Console.WriteLine("Creating Lists...");
            var stringList = new List<string>();
            var hashList = new HashSet<string>();
            var sortedList = new SortedSet<string>();
    
            var searchWords1 = new string[3];
            int ndx = 0;
    
            for (int x = 0; x < 1000000; x++)
            {
                string str = RandomString(10);
    
                if (x == 5 || x == 500000 || x == 999999)
                {
                    str = "Z" + str;
                    searchWords1[ndx] = str;
                    ndx++;
                }
                stringList.Add(str);
                hashList.Add(str);
                sortedList.Add(str);
            }
    
            Console.WriteLine("Lists created!");
            var sw = new Stopwatch();
            sw.Start();
            bool search1 = stringList.Contains(searchWords1[2]);
            sw.Stop();
            Console.WriteLine("List<T> {0} ==> {1}ms", search1, sw.ElapsedMilliseconds);
            sw.Reset();
            sw.Start();
            search1 = hashList.Contains(searchWords1[2]);
            sw.Stop();
            Console.WriteLine("HashSet<T> {0} ==> {1}ms", search1, sw.ElapsedMilliseconds);
            sw.Reset();
            sw.Start();
            search1 = sortedList.Contains(searchWords1[2]);
            sw.Stop();
            Console.WriteLine("SortedSet<T> {0} ==> {1}ms", search1, sw.ElapsedMilliseconds);
        }
    
        private static string RandomString(int size)
        {
            var builder = new StringBuilder();
            char ch;
            for (int i = 0; i < size; i++)
            {
                ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
                builder.Append(ch);
            }
    
            return builder.ToString();
        }
    }
    

    在我的机器上,我得到了以下结果:

    Creating Lists...
    Lists created!
    List<T> True ==> 15ms
    HashSet<T> True ==> 0ms
    SortedSet<T> True ==> 0ms
    

    如您所见,与HashSet&lt;T&gt;SortedSet&lt;T&gt; 相比,List&lt;T&gt; 的速度非常慢。这些几乎是瞬间完成的。

    【讨论】:

    • 我的意思是我们有一个庞大的列表 searchWords1 ~ 100,000 个元素。所以我在第 1 篇文章中更新了关于将 .contain 函数与另一种方式进行比较的内容。我想问一下.Contain 的表现。它非常慢,所以我需要另一种方法来更改 .Contain 函数。
    猜你喜欢
    • 1970-01-01
    • 2019-01-27
    • 1970-01-01
    • 2016-11-26
    • 2010-10-27
    • 2013-10-20
    • 2011-11-23
    • 1970-01-01
    • 2010-10-17
    相关资源
    最近更新 更多