【问题标题】:Fast string comparison with list与列表的快速字符串比较
【发布时间】:2010-11-12 07:04:23
【问题描述】:

我需要一种快速的方法来确定给定的字符串是否在字符串列表中。

字符串列表直到运行时才知道,但此后不会改变。

我可以简单地将List<String> 称为strings,然后这样做:

if (strings.Contains(item))

但是,如果列表中有很多字符串,这将表现不佳。

我也可以使用HashSet<String>,但这需要在每个传入字符串以及Equals 上调用GetHashCode,如果有例如,这将是一种浪费。列表中只有 3 个字符串。我有没有提到这需要快速

我可以在设置时根据字符串的数量决定使用ListHashSet(例如,少于10 个字符串使用List,否则使用HashSet),就像HybridDictionary 中的逻辑一样。

由于字符串是 unicode,标准的 Trie 结构将不起作用,尽管 Radix 树/Patricia trie 可能。有没有带有基准的好的 C# 实现?

有些人提到绕过StringGetHashCode 并使用执行速度更快的哈希函数。是否有任何基准?

使用 LINQ 表达式本质上创建优化的 switch 语句是一种看起来非常有趣的新颖方法。

还有什么办法?设置成本并不重要,重要的是搜索速度。

如果重要的话,传入的字符串值将很少出现在列表中。

【问题讨论】:

  • 我已经更新了我的答案,以包含指向 Unicode 折叠尝试信息的链接。

标签: c# list string


【解决方案1】:

您可以使用trie 来保存字符串列表; Trys 是为快速重试而设计的。trieval。这是one example 在 C# 中实现 trie。

更新Powerpoint presentation on folded tries for UnicodeIfo on implementation of a folded trie for Unicode (not C#)

【讨论】:

  • 如果字符串只是 A-Z,甚至只是 ASCII,那么 trie 会很棒。但这些都是 unicode。
  • 来自我链接到的 Wikipedia 文章:“虽然它是最常见的,但尝试不需要由字符串键入。相同的算法可以很容易地适应任何结构的有序列表的类似功能,例如,数字列表上的排列,形状列表上的排列等。”所以你可以这样做,例如来自 Unicode 字符串的代码点。
  • 有一个 unicode 实现的链接吗?是的,我可以使用 GetBytes 并打开各个字节,但我怀疑这不会很好。
  • 这是另一个 - paste.lisp.org/display/12161 - C# char 类型是 16 位 Unicode 字符,您需要更多吗?
  • @Vinay,如果您阅读刚刚链接的代码,您会发现它仅支持 a-z、A-Z 和 0-9 字符,而不支持整个 unicode 字符范围。
【解决方案2】:

您是否考虑过改用 HashSet 类(在 .NET 3 中)?

【讨论】:

  • ... 这将再次调用每个传入字符串的 .GetHashCode 和 .Equals。
  • 您可以使用您选择的比较器使用重载构造一个 HashSet:HashSet(T) Constructor (IEqualityComparer(T)) msdn.microsoft.com/en-us/library/bb359100.aspx
【解决方案3】:
【解决方案4】:

关于“当列表很小”的问题;如果您不介意使用非泛型集合,System.Collections.Specialized.HybridDictionary 会这样做;它在小的时候封装了一个System.Collections.Specialized.ListDictionary,或者在它变大时封装了一个System.Collections.Hashtable (>10)。值得一看吗?


否则;您也许可以将HashSet<T> 与自定义比较器一起使用?那你可以选择GetHashCode()有多贵...

using System;
using System.Collections.Generic;

class CustomStringComparer : IEqualityComparer<string> {
    public bool Equals(string x, string y) {
        return string.Equals(x, y);
    }
    public int GetHashCode(string s) {
        return string.IsNullOrEmpty(s) ? 0 :
            s.Length + 273133 * (int)s[0];
    }
    private CustomStringComparer() { }
    public static readonly CustomStringComparer Default
        = new CustomStringComparer();
}
static class Program {
    static void Main() {
        HashSet<string> set = new HashSet<string>(
            new string[] { "abc", "def", "ghi" }, CustomStringComparer.Default);
        Console.WriteLine(set.Contains("abc"));
        Console.WriteLine(set.Contains("abcde"));
    }
}

【讨论】:

  • 这是个好主意,但是当您不知道列表中有多少个字符串时,进一步考虑选择正确的哈希函数是非常棘手的。如果它像你上面写的那样简单,那么与更大的列表会有很多冲突。
【解决方案5】:

也许HybridDictionary 是一个更好的选择。它的内部使用取决于集合中有多少项目。

【讨论】:

    【解决方案6】:

    顺便说一句,如果内存服务,当构造一个 String 时,它的 HashValue 会被预先计算并与 String 一起存储,作为这种类型的用例的优化。如果您使用的是字符数组或 StringBuilder,这显然不适用,但对于不可变的 String 则应如此。

    编辑:我不正确...Java 确实缓存了字符串的 HashCode,而 C# 没有。

    【讨论】:

    • 我认为在这种情况下内存不起作用。使用 Reflector 查看System.String 时,我看不到哈希码缓存的证据。
    • 你确实是对的。 Java 确实做到了这一点,我认为 C# 会移植这种做法。
    【解决方案7】:

    我最终这样做了:

    private static bool Contains(List<string> list, string value)
    {
        bool contains = null != list.Find(str => str.ToLower().Equals(value.ToLower()));
    
        return contains;
    }
    

    我猜你可以为List&lt;string&gt; 创建一个扩展方法,但这足以满足我的需要。

    【讨论】:

    • 我认为它的执行速度不够快,无法满足我的需求;)
    【解决方案8】:

    您可以使用字符串实习来非常快速地完成此操作。 构建列表时,您必须存储所需字符串的内部格式(string.Intern() 的结果)。然后,您需要与带有object.ReferenceEquals 的实习字符串进行比较 - 因为实习字符串具有相同的引用。

    List<string> BuildList() {
        List<string> result;
        foreach (string str from StringSource())
             result.Add(str.Intern());
        return result;
    }
    
    bool CheckList(List<string> list, string stringToFind) { // list must be interned for this to work!
        return list.Find(str => object.ReferenceEquals(str, stringToFind)) != null;
    }
    

    这将导致对每个列表进行四字节比较,并通过原始字符串。字符串的intern pool是专门为快速比较字符串和查找是否已经存在而构建的,所以intern操作应该很快。

    【讨论】:

    • 不幸的是,String.Intern 并没有那么快,并且会产生永久存储字符串的不良副作用,直到我的进程内存不足(此应用程序处理大量字符串)。此外,随后使用 ReferenceEquals 搜索列表将是 O(N) 操作。
    • 它比普通的字符串比较要快,但是是的,这对于处理大量字符串来说并不好。
    猜你喜欢
    • 2013-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多