2012-11-20 101 views
5

我有一个排序的字符串数组。 鉴于识别的前缀字符串,我执行两个二进制搜索找到数组中包含以该前缀开头的单词的第一个和最后的位置:为什么(“abc”+ char.MaxValue).CompareTo(“abc”)== 0?

string [] words = {"aaa","abc","abcd","acd"}; 
string prefix = "abc"; 
int firstPosition = Array.BinarySearch<string>(words, prefix); 
int lastPosition = Array.BinarySearch<string>(words, prefix + char.MaxValue); 
if (firstPosition < 0) 
    firstPosition = ~firstPosition; 
if (lastPosition < 0) 
    lastPosition = ~lastPosition; 

运行这段代码中,我得到firstPosition和一位置都等于为1,而正确的答案是让lastPosition等于3(即指向第一个不匹配的单词)。 的二分查找方法使用CompareTo方法来比较的对象,我发现,

("abc"+char.MaxValue).CompareTo("abc")==0 

意味着这两个字符串被认为是相等的! 如果我更改代码

int lastPosition = Array.BinarySearch<string>(words, prefix + "z"); 

我得到正确的答案。 而且我发现,

("abc"+char.MaxValue)==("abc") 

正确的(相对于我的需要)返回false。

你能帮我解释一下CompareTo方法的行为吗?

我想让CompareTo方法的行为像==一样,以便BinarySearch方法为lastPosition返回3。

+4

无论BinarySearch在这里使用什么(fe'string.IndexOf'或'CompareTo')。它忽略了'Char.MaxValue'的十六进制值。在[** Jon Skeets博客**](http://csharpindepth.com/Articles/General/strings.aspx)中搜索'ignore',你会发现它是可能的。从[MSDN](http://msdn.microsoft.com/en-us/library/35f0x18w。aspx):_“CompareTo方法被设计用于排序或字母排序操作,当方法调用的主要目的是确定两个字符串是否相等时,不应使用它。要确定两个字符串是否相等,请调用等于方法。“_ –

+0

这段代码是更通用的函数的一部分,它对不同的数据类型执行”范围“搜索,前提是搜索的对象实现IComparable。出于这个原因,尽管二分查找是一个灵活的解决方案,但使用诸如前缀树等文本专用数据结构并不适合于此目的。我认为一个可以解决灵活性问题的解决方案是将字符串类型封装到一个在其CompareTo方法中实现“正确”比较的类型中。 – Esuli

回答

5

按照MSDN,string.CompareTo不应被用于检查两个字符串是否相等:

CompareTo方法被设计主要用于排序或按字母顺序排列的操作使用。当方法调用的主要目的是确定两个字符串是否相等时,不应使用它。要确定两个字符串是否相同,请调用Equals方法。

为了得到你想要的行为,你可以利用超负荷接受的IComparer<T>

int lastPosition = Array.BinarySearch<string>(words, prefix + char.MaxValue, 
               StringComparer.Ordinal); 

这将返回-4lastPosition,因为与数组中的前缀没有字符串。我不明白你为什么期望3在这种情况下...

+0

问题是它不是我的代码调用CompareTo方法,它是Array .BinarySearch方法。 – Esuli

+0

@Esuli:你可以改变它。请参阅更新。 –

+0

'if(lastPosition <0) lastPosition =〜lastPosition;' 将-4转换为3. StringComparer.Ordinal是正确的比较器。 现在我必须决定如何将它插入真实的代码而不会破坏通用的灵活性。 – Esuli

6

string.CompareTo()做一个当前的文化比较。它在内部使用StringComparer.CurrentCulture,而字符串等于运算符进行文化不变的比较。

例如,如果当前的文化是“DE”,你会得到与“SS”与“SS”相同的结果:

Console.WriteLine("ss".CompareTo("ß")); // => 0 
Console.WriteLine("ss" == "ß"); // => false 

你想要的是一个区域性不变相比,这您将通过使用StringComparer.Ordinal获得:

StringComparer.Ordinal.Compare("ss", "ß"); // => -108 
StringComparer.Ordinal.Compare("abc"+char.MaxValue, "abc"); // => 65535 
+0

戈德答案。作为一个补充:OP可能希望使用'StringComparer.OrdinalIgnoreCase.Compare(...,...)'或'String.Compare(...,...,StringComparison.OrdinalIgnoreCase)'来实现文化不变,不区分大小写的比较。 –

+0

感谢您提供有用的信息。 – Esuli

+0

@Abonbonanza正确。还有'string.CompareOrdinal(“abc \ uFFFF”,“abc”)'。但是,由于提问者调用了BinarySearch方法,他应该通过称为“StringComparer.Ordinal”的IComparer 实例。 –

相关问题