在给定文化的字符串中查找不区分大小写的第一个差异
Find first difference in strings case insensitive given culture
有很多方法可以比较两个字符串以找到它们不同的第一个索引,但如果我需要在任何给定的文化中不区分大小写,那么这些选项就会消失。
这是我能想到的进行这种比较的唯一方法:
static int FirstDiff(string str1, string str2)
{
for (int i = 1; i <= str1.Length && i <= str2.Length; i++)
if (!string.Equals(str1.Substring(0, i), str2.Substring(0, i), StringComparison.CurrentCultureIgnoreCase))
return i - 1;
return -1; // strings are identical
}
谁能想到一个更好的方法,不涉及那么多字符串分配?
出于测试目的:
// Turkish word 'open' contains the letter 'ı' which is the lowercase of 'I' in Turkish, but not English
string lowerCase = "açık";
string upperCase = "AÇIK";
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
FirstDiff(lowerCase, upperCase); // Should return 2
Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
FirstDiff(lowerCase, upperCase); // Should return -1
编辑: 检查每个字符的 ToUpper 和 ToLower 似乎适用于我能想到的任何示例。 但是它适用于所有文化吗?也许这个问题更适合语言学家。
您需要同时检查 ToLower 和 ToUpper。
private static int FirstDiff(string str1, string str2)
{
int length = Math.Min(str1.Length, str2.Length);
TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo;
for (int i = 0; i < length; ++i)
{
if (textInfo.ToUpper(str1[i]) != textInfo.ToUpper(str2[i]) ||
textInfo.ToLower(str1[i]) != textInfo.ToLower(str2[i]))
{
return i;
}
}
return str1.Length == str2.Length ? -1 : length;
}
您可以比较字符而不是字符串。这远未优化,而且相当快速和肮脏,但像这样的东西似乎有效
for (int i = 0; i < str1.Length && i < str2.Length; i++)
if (char.ToLower(str1[i]) != char.ToLower(str2[i]))
return i;
根据文档,这也适用于文化:https://docs.microsoft.com/en-us/dotnet/api/system.char.tolower?view=netframework-4.8
Casing rules are obtained from the current culture.
To convert a character to lowercase by using the casing conventions of the current culture, call the ToLower(Char, CultureInfo) method overload with a value of CurrentCulture for its culture parameter.
这里有一些不同的方法。字符串在技术上是 char
的数组,所以我将其与 LINQ
.
一起使用
var list1 = "Hellow".ToLower().ToList();
var list2 = "HeLio".ToLower().ToList();
var diffIndex = list1.Zip(list2, (item1, item2) => item1 == item2)
.Select((match, index) => new { Match = match, Index = index })
.Where(a => !a.Match)
.Select(a => a.Index).FirstOrDefault();
如果匹配,diffIndex
将为零。否则它将是第一个不匹配字符的索引。
编辑:
一个稍微改进的版本,可以随时转换为小写字母。而开头的ToList()
实在是多余。
var diffIndex = list1.Zip(list2, (item1, item2) => char.ToLower(item1) == char.ToLower(item2))
.Select((match, index) => new { Match = match, Index = index })
.Where(a => !a.Match)
.Select(a => a.Index).FirstOrDefault();
编辑2:
这是一个可以进一步缩短的工作版本。这是最佳答案,因为在前两个中,如果字符串匹配,您将得到 0。这里'如果字符串匹配你得到 null
否则得到索引。
var list1 = "Hellow";
var list2 = "HeLio";
var diffIndex = list1.Zip(list2, (item1, item2) => char.ToLower(item1) == char.ToLower(item2))
.Select((match, index) => new { Match = match, Index = index })
.FirstOrDefault(x => !x.Match)?.Index;
减少字符串分配次数的一种方法是减少进行比较的次数。在这种情况下,我们可以借鉴用于搜索数组的二进制搜索算法,并从比较长度为字符串一半的子字符串开始。然后我们继续添加或删除剩余索引的一半(取决于字符串是否相等),直到我们到达第一个不等式实例。
一般来说,这应该会加快搜索时间:
public static int FirstDiffBinarySearch(string str1, string str2)
{
// "Fail fast" checks
if (string.Equals(str1, str2, StringComparison.CurrentCultureIgnoreCase))
return -1;
if (str1 == null || str2 == null) return 0;
int min = 0;
int max = Math.Min(str1.Length, str2.Length);
int mid = (min + max) / 2;
while (min <= max)
{
if (string.Equals(str1.Substring(0, mid), str2.Substring(0, mid),
StringComparison.CurrentCultureIgnoreCase))
{
min = mid + 1;
}
else
{
max = mid - 1;
}
mid = (min + max) / 2;
}
return mid;
}
我想起了另一个奇怪的字符(或者更确切地说是 unicode 代码点):有些字符充当代理对,它们与任何文化都无关,除非这对字符彼此相邻出现。 有关 Unicode interpretation standards see the document that 的更多信息链接在他的评论中。
在尝试不同的解决方案时,我偶然发现了这个特殊的 class,我认为它最适合我的需要:System.Globalization.StringInfo
(MS Doc Example 显示了它与代理对的关系)
class 将字符串分成需要彼此才有意义的部分(而不是严格按字符)。然后我可以使用 string.Equals
和 return 不同的第一个片段的索引来按文化比较每个片段:
static int FirstDiff(string str1, string str2)
{
var si1 = StringInfo.GetTextElementEnumerator(str1);
var si2 = StringInfo.GetTextElementEnumerator(str2);
bool more1, more2;
while ((more1 = si1.MoveNext()) & (more2 = si2.MoveNext())) // single & to avoid short circuiting the right counterpart
if (!string.Equals(si1.Current as string, si2.Current as string, StringComparison.CurrentCultureIgnoreCase))
return si1.ElementIndex;
if (more1 || more2)
return si1.ElementIndex;
else
return -1; // strings are equivalent
}
有很多方法可以比较两个字符串以找到它们不同的第一个索引,但如果我需要在任何给定的文化中不区分大小写,那么这些选项就会消失。
这是我能想到的进行这种比较的唯一方法:
static int FirstDiff(string str1, string str2)
{
for (int i = 1; i <= str1.Length && i <= str2.Length; i++)
if (!string.Equals(str1.Substring(0, i), str2.Substring(0, i), StringComparison.CurrentCultureIgnoreCase))
return i - 1;
return -1; // strings are identical
}
谁能想到一个更好的方法,不涉及那么多字符串分配?
出于测试目的:
// Turkish word 'open' contains the letter 'ı' which is the lowercase of 'I' in Turkish, but not English
string lowerCase = "açık";
string upperCase = "AÇIK";
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
FirstDiff(lowerCase, upperCase); // Should return 2
Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
FirstDiff(lowerCase, upperCase); // Should return -1
编辑: 检查每个字符的 ToUpper 和 ToLower 似乎适用于我能想到的任何示例。 但是它适用于所有文化吗?也许这个问题更适合语言学家。
您需要同时检查 ToLower 和 ToUpper。
private static int FirstDiff(string str1, string str2)
{
int length = Math.Min(str1.Length, str2.Length);
TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo;
for (int i = 0; i < length; ++i)
{
if (textInfo.ToUpper(str1[i]) != textInfo.ToUpper(str2[i]) ||
textInfo.ToLower(str1[i]) != textInfo.ToLower(str2[i]))
{
return i;
}
}
return str1.Length == str2.Length ? -1 : length;
}
您可以比较字符而不是字符串。这远未优化,而且相当快速和肮脏,但像这样的东西似乎有效
for (int i = 0; i < str1.Length && i < str2.Length; i++)
if (char.ToLower(str1[i]) != char.ToLower(str2[i]))
return i;
根据文档,这也适用于文化:https://docs.microsoft.com/en-us/dotnet/api/system.char.tolower?view=netframework-4.8
Casing rules are obtained from the current culture.
To convert a character to lowercase by using the casing conventions of the current culture, call the ToLower(Char, CultureInfo) method overload with a value of CurrentCulture for its culture parameter.
这里有一些不同的方法。字符串在技术上是 char
的数组,所以我将其与 LINQ
.
var list1 = "Hellow".ToLower().ToList();
var list2 = "HeLio".ToLower().ToList();
var diffIndex = list1.Zip(list2, (item1, item2) => item1 == item2)
.Select((match, index) => new { Match = match, Index = index })
.Where(a => !a.Match)
.Select(a => a.Index).FirstOrDefault();
如果匹配,diffIndex
将为零。否则它将是第一个不匹配字符的索引。
编辑:
一个稍微改进的版本,可以随时转换为小写字母。而开头的ToList()
实在是多余。
var diffIndex = list1.Zip(list2, (item1, item2) => char.ToLower(item1) == char.ToLower(item2))
.Select((match, index) => new { Match = match, Index = index })
.Where(a => !a.Match)
.Select(a => a.Index).FirstOrDefault();
编辑2:
这是一个可以进一步缩短的工作版本。这是最佳答案,因为在前两个中,如果字符串匹配,您将得到 0。这里'如果字符串匹配你得到 null
否则得到索引。
var list1 = "Hellow";
var list2 = "HeLio";
var diffIndex = list1.Zip(list2, (item1, item2) => char.ToLower(item1) == char.ToLower(item2))
.Select((match, index) => new { Match = match, Index = index })
.FirstOrDefault(x => !x.Match)?.Index;
减少字符串分配次数的一种方法是减少进行比较的次数。在这种情况下,我们可以借鉴用于搜索数组的二进制搜索算法,并从比较长度为字符串一半的子字符串开始。然后我们继续添加或删除剩余索引的一半(取决于字符串是否相等),直到我们到达第一个不等式实例。
一般来说,这应该会加快搜索时间:
public static int FirstDiffBinarySearch(string str1, string str2)
{
// "Fail fast" checks
if (string.Equals(str1, str2, StringComparison.CurrentCultureIgnoreCase))
return -1;
if (str1 == null || str2 == null) return 0;
int min = 0;
int max = Math.Min(str1.Length, str2.Length);
int mid = (min + max) / 2;
while (min <= max)
{
if (string.Equals(str1.Substring(0, mid), str2.Substring(0, mid),
StringComparison.CurrentCultureIgnoreCase))
{
min = mid + 1;
}
else
{
max = mid - 1;
}
mid = (min + max) / 2;
}
return mid;
}
我想起了另一个奇怪的字符(或者更确切地说是 unicode 代码点):有些字符充当代理对,它们与任何文化都无关,除非这对字符彼此相邻出现。 有关 Unicode interpretation standards see the document that
在尝试不同的解决方案时,我偶然发现了这个特殊的 class,我认为它最适合我的需要:System.Globalization.StringInfo
(MS Doc Example 显示了它与代理对的关系)
class 将字符串分成需要彼此才有意义的部分(而不是严格按字符)。然后我可以使用 string.Equals
和 return 不同的第一个片段的索引来按文化比较每个片段:
static int FirstDiff(string str1, string str2)
{
var si1 = StringInfo.GetTextElementEnumerator(str1);
var si2 = StringInfo.GetTextElementEnumerator(str2);
bool more1, more2;
while ((more1 = si1.MoveNext()) & (more2 = si2.MoveNext())) // single & to avoid short circuiting the right counterpart
if (!string.Equals(si1.Current as string, si2.Current as string, StringComparison.CurrentCultureIgnoreCase))
return si1.ElementIndex;
if (more1 || more2)
return si1.ElementIndex;
else
return -1; // strings are equivalent
}