gpt4 book ai didi

C#SortedList .ContainsKey成功添加键返回false

转载 作者:太空狗 更新时间:2023-10-29 21:50:40 25 4
gpt4 key购买 nike

检查下面的更新3
我发现我遇到的问题与.Net 4.0、4.0客户端和4.5的C#字符串比较器的一个已知严重问题有关,这将导致字符串列表的排序顺序不一致(导致输出依赖于其中的顺序)。输入和使用的排序算法)。该问题已于2012年12月报告给Microsoft,并以“无法解决”的方式关闭。可以使用一种解决方法,但是它慢得多,以致于大集合几乎不可行。

在实现不变的PatriciaTrie时,我想将其性能与System.Collections.Generic.SortedList进行比较。我使用以下文件https://github.com/rkapsi/patricia-trie/blob/master/src/test/resources/org/ardverk/collection/hamlet.txt创建了用于测试的输入单词表。

当使用Comparer<string>.DefaultStringComparer.InvariantCulture作为键比较器在C#SortedList中插入每个单词时,无法使用常规搜索方法检索成功插入的多个条目(例如ContainsKey返回false),但是键存在于通过迭代列表可以看到该列表。

更奇怪的是,比较器将从排序列表中检索的键与使用ContainsKey找不到的搜索键进行比较时返回值“0”。

下面的完整示例在我的系统上演示了此问题。

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;

class Program
{
static void Main(string[] args)
{
// the problem is possibly related to comparison.
var fail = true;
var comparer = fail ? StringComparer.InvariantCulture : StringComparer.Ordinal;

// read hamlet (contains duplicate words)
var words = File
.ReadAllLines("hamlet.txt")
.SelectMany(l => l.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
.Select(w => w.Trim())
.Where(w => !string.IsNullOrEmpty(w))
.Distinct(comparer)
.ToArray();

// insert hamlet's words in the sorted list.
var list = new SortedList<string, int>(comparer);
var ndx = 0;
foreach (var word in words)
list[word] = ndx++;

// search for each of the added words.
foreach (var keyToSearch in words)
{
if (!list.ContainsKey(keyToSearch))
{
// was inserted, but cannot be retrieved.
Console.WriteLine("Error - Key not found: \"{0}\"", keyToSearch);
// however when we iterate over the list, we see that the entry is present
var prefix = keyToSearch.Substring(0, Math.Min(keyToSearch.Length, 3));
foreach (var wordCloseToSearchKey in list.Keys.Where(s => s.StartsWith(prefix)))
{
// and using the SortedList's supplied comparison returns 0, signaling equality
var comparisonResult = list.Comparer.Compare(wordCloseToSearchKey, keyToSearch);
Console.WriteLine("{0} - comparison result = {1}", wordCloseToSearchKey, comparisonResult);
}
}
}

// Check that sort order of List.Keys is correct
var keys = list.Keys.ToArray();
BinarySearchAll("list.Keys", keys, list.Comparer);
CheckCorrectSortOrder("list.Keys", keys, list.Comparer);

// Check that sort order of Array.Sort(List.Keys) is correct
var arraySortedKeys = CopySortSearchAndCheck("Array.Sort(List.Keys)", keys, list.Comparer);

// Check that sort order of the Array.Sort(input words) is correct
var sortedInput = CopySortSearchAndCheck("Array.Sort(input words)", words, list.Comparer);

Console.ReadLine();
}

static string[] CopySortSearchAndCheck(string arrayDesc, string[] input, IComparer<string> comparer)
{
// copy input
var sortedInput = new string[input.Length];
Array.Copy(input, sortedInput, sortedInput.Length);

// sort it
Array.Sort(sortedInput, comparer);

// check that we can actually find the keys in the array using bin. search
BinarySearchAll(arrayDesc, sortedInput, comparer);

// check that sort order is correct
CheckCorrectSortOrder(arrayDesc, sortedInput, comparer);

return sortedInput;
}

static void BinarySearchAll(string arrayDesc, string[] sortedInput, IComparer<string> comparer)
{
// check that each key in the input can be found using bin. search
foreach (var word in sortedInput)
{
var ix = Array.BinarySearch(sortedInput, word, comparer);
if (ix < 0)
// and it appears it cannot!
Console.WriteLine("Error - {0} - Key not found: \"{1}\"", arrayDesc, word);
}
}

static void CheckCorrectSortOrder(string arrayDesc, string[] sortedKeys, IComparer<string> comparer)
{
for (int n = 0; n < sortedKeys.Length; n++)
{
for (int up = n + 1; up < sortedKeys.Length; up++)
{
var cmp = comparer.Compare(sortedKeys[n], sortedKeys[up]);
if (cmp >= 0)
{
Console.WriteLine(
"{0}[{1}] = \"{2}\" not < than {0}[{3}] = \"{4}\" - cmp = {5}",
arrayDesc, n, sortedKeys[n], up, sortedKeys[up], cmp);
}
}
for (int down = n - 1; down > 0; down--)
{
var cmp = comparer.Compare(sortedKeys[n], sortedKeys[down]);
if (cmp <= 0)
{
Console.WriteLine(
"{0}[{1}] = \"{2}\" not > than {0}[{3}] = \"{4}\" - cmp = {5}",
arrayDesc, n, sortedKeys[n], down, sortedKeys[down], cmp);
}
}
}
}
}

有人对此意外和奇怪的行为有解释吗?

当将SortedList使用的比较器更改为 StringComparer.Ordinal时(例如,在上述示例中将 fail更改为 false),问题消失了,这似乎指向一个比较问题,但是我不太明白为什么。

更新了
正如Sébastien指出的那样,.Net 3.5和3.5客户端配置文件中未显示此处描述的问题。它在.Net 4.0、4.0客户端和4.5上执行。

经过更多的挖掘后,我注意到,如果我从列表中获取排序后的键,并对这些键运行 Array.BinarySearch,它也会为使用 SortedList.ContainsKey找不到的相同键返回负(未找到)值。因此,这表明键的排序顺序不正确。

如果我从列表中获取已经排序的键,并使用 Array.Sort对其进行排序,则对于有问题的键,输出的排序顺序会有所不同。

因此,我添加了一个函数(使用列表的比较器)来检查给定数组的排序顺序是否正确(即,前一个键始终较小,而后一个键始终较大),并将输入限制为根据比较器。
我将此功能应用于3个不同的输入(均使用相同的比较器):
  • SortedList的Keys集合。
  • 这些键上Array.Sort的输出。
  • Array.Sort在文件输入中的输出。

  • (2)和(3)的输出与(1)相同且不同。但是,再次对(2)和(3)的Array.Sort输出执行Array.BinarySearch无法找到相同的键(返回<0)。同样,检查正确排序顺序的功能还表明,对于所有3种情况,所涉及的有问题的 key 周围的排序顺序都是不正确的。

    在这一点上,我只是希望我所做的事情非常愚蠢,并且有一个简单的解释。希望有人可以向我指出。

    该示例代码已通过我的其他故障排除实验进行了更新,可以在 http://imgur.com/DU8SCsA中找到输出的屏幕截图。

    更新2
    好的,我已将问题范围缩小到在我看来,.Net 4.0中引入的C#字符串比较器是一个非常严重的问题。

    总而言之,假设我们有3个值:a1,a2和a3。为了使任何排序都能正常工作,我们希望如果 a1 < a2a2 < a3能够使比较保持一致,那么下面的内容也适用: a1 < a3

    但是,对于C#字符串比较器而言,情况并非如此(至少对于 Comparer<string>.DefaultStringComparer.InvariantCulture而言)。

    下面的小程序说明了这个确切的问题:
    class Program
    {
    static void Main(string[] args)
    {
    var comparer = StringComparer.InvariantCulture;
    var a1 = "A";
    var a2 = "a\'";
    var a3 = "\'a";
    PrintComparison("a1", a1, "a2", a2, comparer);
    PrintComparison("a2", a2, "a3", a3, comparer);
    PrintComparison("a1", a1, "a3", a3, comparer);
    Console.ReadLine();
    }
    public static void PrintComparison(string firstSymbol, string first, string secondSymbol, string second, IComparer<string> comparer)
    {
    var cmp = comparer.Compare(first, second);
    var result = cmp == 0 ? "=" : cmp < 0 ? "<" : ">";
    Console.WriteLine("{0} {1} {2} ({3} {1} {4})", firstSymbol, result, secondSymbol, first, second);
    }
    }

    这是它的输出:
    a1 < a2   (A < a')
    a2 < a3 (a' < 'a)
    a1 > a3 (A > 'a)

    结论似乎是依赖于使用C#字符串编译器确定的排序顺序是不安全的,还是我遗漏了一些东西?

    更新3
    该问题似乎已于2012年12月报告给MS,并以“无法解决”状态关闭,这令人失望。请参阅下面评论中发布的链接(由于信誉有限,我无法在此处发布)。这也列出了一种变通方法,我已实现该变通方法并用来验证它是否确实解决了使用标准比较器观察到的问题。
    public class WorkAroundStringComparer : StringComparer
    {
    private static readonly Func<CompareInfo, string, CompareOptions, int> _getHashCodeOfString;
    private readonly CompareInfo _compareInfo;
    private readonly CompareOptions _compareOptions;

    static WorkAroundStringComparer()
    {
    // Need this internal method to compute hashcode
    // as an IEqualityComparer implementation.
    _getHashCodeOfString = BuildGetHashCodeOfStringDelegate();
    }

    static Func<CompareInfo, string, CompareOptions, int> BuildGetHashCodeOfStringDelegate()
    {
    var compareInfoType = typeof(CompareInfo);
    var argTypes = new[] { typeof(string), typeof(CompareOptions) };
    var flags = BindingFlags.NonPublic | BindingFlags.Instance;
    var methods = compareInfoType.GetMethods(flags).ToArray(); ;
    var method = compareInfoType.GetMethod("GetHashCodeOfString", flags, null, argTypes, null);

    var instance = Expression.Parameter(compareInfoType, "instance");
    var stringArg = Expression.Parameter(typeof(string), "string");
    var optionsArg = Expression.Parameter(typeof(CompareOptions), "options");
    var methodCall = Expression.Call(instance, method, stringArg, optionsArg);
    var expr = Expression.Lambda<Func<CompareInfo, string, CompareOptions, int>>(methodCall, instance, stringArg, optionsArg);
    return expr.Compile();
    }

    public WorkAroundStringComparer()
    : this(CultureInfo.InvariantCulture)
    {
    }

    public WorkAroundStringComparer(CultureInfo cultureInfo, CompareOptions compareOptions = CompareOptions.None)
    {
    if (cultureInfo == null)
    throw new ArgumentNullException("cultureInfo");
    this._compareInfo = cultureInfo.CompareInfo;
    this._compareOptions = compareOptions;
    }

    public override int Compare(string x, string y)
    {
    if (ReferenceEquals(x, y))
    return 0;
    if (ReferenceEquals(x, null))
    return -1;
    if (ReferenceEquals(y, null))
    return 1;

    var sortKeyFor_x = _compareInfo.GetSortKey(x, _compareOptions);
    var sortKeyFor_y = _compareInfo.GetSortKey(y, _compareOptions);
    return SortKey.Compare(sortKeyFor_x, sortKeyFor_y);
    }

    public override bool Equals(string x, string y)
    {
    return Compare(x, y) == 0;
    }

    public override int GetHashCode(string obj)
    {
    return _getHashCodeOfString(_compareInfo, obj, _compareOptions);
    }
    }

    这种解决方法的问题在于,对于相当大的集合来说,它几乎是不实际的,因为它比例如慢了一个数量级。 StringComparer.InvariantCulture

    使用两个比较器对给定单词列表进行1000次排序所需的时间:
    StringComparer.InvariantCulture : 00:00:15.3120013
    WorkAroundStringComparer : 00:01:35.8322409

    因此,我仍然希望微软重新考虑,或者有人知道可行的替代方案。否则,剩下的唯一选择就是使用 StringComparer.Ordinal退回。

    最佳答案

    可能与.Net Framework 4/4.5有关吗?我已经为.Net 3.5修改了您的示例,如下所示:

    var words = ReadFile("hamlet.txt");

    //...

    private static string[] ReadFile(string path)
    {
    List<string> lines = new List<string>();
    using (StreamReader sr = new StreamReader(path))
    {
    string text = sr.ReadToEnd();
    lines.Add(text);
    }

    return lines.SelectMany(l => l.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries).Select(w => w.Trim()))
    .Where(w => !(w.ToCharArray().All(c => c == ' ')))
    .ToArray();
    }

    并且两个比较器在使用.Net 3.5的XP上都可以正常工作。

    关于C#SortedList <string,TValue> .ContainsKey成功添加键返回false,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17599084/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com