提问人:Isaiah Shiner 提问时间:5/15/2018 最后编辑:Isaiah Shiner 更新时间:11/15/2023 访问量:443
使用自定义 StringComparer 的 IndexOf
IndexOf with custom StringComparer
问:
为什么需要 a 并且不允许更一般的 ,甚至只是 或 ?String.IndexOf(String, StringComparison)
StringComparison
StringComparer
IComparer<T>
IEqualityComparer<T>
我做了一个自定义来与几个词典一起使用,我想在我项目的其他部分使用它,但如果这些方法可以工作的话,我找不到一个好的方法来做到这一点。StringComparer
这是我做的比较器。它大致基于以下建议: 使用字符串实现自定义 IComparer
另请注意,ModifyString 是一个 WIP。我希望根据我正在比较的输入在那里添加更多内容。我也知道它很贵,但我只是在寻找一个解决方案 ATM,而不是性能。
public class CustomComparer : StringComparer
{
public override int Compare(string x, string y)
{
return StringComparer.Ordinal.Compare(ModifyString(x), ModifyString(y));
}
public override bool Equals(string x, string y)
{
if (ModifyString(x).Equals(ModifyString(y)))
return true;
else
return false;
}
public override int GetHashCode(string obj)
{
if (obj == null)
return 0;
else
return ModifyString(obj).GetHashCode();
}
private string ModifyString(string s)
{
//I know this code is expensive/naaive, your suggestions are welcome.
s = s.ToLowerInvariant();
s = s.Trim();
s = Regex.Replace(s, @"\s+", " ");//replaces all whitespace characters with a single space.
return s;
}
}
答:
使用一个方便的扩展似乎应该已经存在了,您可以编写一个扩展来使用 .正如注释中所建议的,所有可能的子字符串长度都会在每个位置进行测试,因为不能对自定义做出任何假设。IEnumerable
String
StringComparer
StringComparer
public static class IEnumerableExt {
public static T FirstOrDefault<T>(this IEnumerable<T> src, Func<T, bool> testFn, T defval) => src.Where(aT => testFn(aT)).DefaultIfEmpty(defval).First();
}
public static class StringExt {
public static int IndexOf(this string source, string match, StringComparer sc) {
return Enumerable.Range(0, source.Length) // for each position in the string
.FirstOrDefault(i => // find the first position where either
// match is Equal at this position for length of match (or to end of string) or
sc.Equals(source.Substring(i, Math.Min(match.Length, source.Length-i)), match) ||
// match is Equal to one of the substrings beginning at this position
Enumerable.Range(1, source.Length-i).Any(ml => sc.Equals(source.Substring(i, ml), match)),
-1 // else return -1 if no position matches
);
}
}
注意:修改为正确处理源子字符串和匹配字符串长度可能不相等的情况。
评论
如果有人需要一个可以与 ,以下是@NetMage发布的良好解决方案的微不足道的修改:IComparer<String>
public static int IndexOf(this string source, string match, IComparer<String> sc) {
return Enumerable.Range(0, source.Length) // for each position in the string
.FirstOrDefault(i => // find the first position where either
// match is Equal at this position for length of match (or to end of string) or
sc.Compare(source.Substring(i, Math.Min(match.Length, source.Length-i)), match) == 0 ||
// match is Equal to one of the substrings beginning at this position
Enumerable.Range(1, source.Length-i).Any(ml => sc.Compare(source.Substring(i, ml), match) == 0),
-1 // else return -1 if no position matches
);
}
基于以下基础的优化版本:ReadOnlySpan<char>
using Xunit;
namespace CustomIndexOf;
public delegate bool CharSpanEqualityComparer(ReadOnlySpan<char> a, ReadOnlySpan<char> b);
public static class TextProcessingExtensions
{
public static int IndexOf(this string source, string match, CharSpanEqualityComparer equal)
{
if (string.IsNullOrEmpty(source))
{
if (string.IsNullOrEmpty(match)) return 0;
return -1;
}
if (match.Length > source.Length) return -1;
if (match.Length == source.Length)
{
if (equal(source, match)) return 0;
return -1;
}
for (var i = 0; i <= source.Length - match.Length; ++i)
{
var span = source.AsSpan(i, match.Length);
if (equal(span, match)) return i;
}
return -1;
}
}
public class StringExtensionTests
{
[Theory]
[InlineData("God called the light “day”, and the darkness he called “night”.", "God", 0)]
[InlineData("God called the light “day”, and the darkness he called “night”.", "\"day\"", 21)]
public void IndexOfWithComparerTests(string source, string match, int expectedIndex)
{
Dictionary<char, char> charMap = new()
{
{ '“', '"' },
{ '”', '"' },
};
bool TestComparer(ReadOnlySpan<char> a, ReadOnlySpan<char> b)
{
if (a.Length != b.Length) return false;
for (var i = 0; i < a.Length; ++i)
{
var charA = charMap.GetValueOrDefault(a[i], a[i]);
var charB = charMap.GetValueOrDefault(b[i], b[i]);
if (charA != charB) return false;
}
return true;
}
var actualIndex = source.IndexOf(match, TestComparer);
Assert.Equal(expectedIndex, actualIndex);
}
}
这个版本也有点特定于我的自定义需求:只有当字符串具有相同的长度时,比较器才应该返回 True。但是,在其他一些自定义情况下,情况可能并非如此。例如,问题中的修整逻辑 - 此特定代码未涵盖它。但这种方法仍然有效,只是需要根据特定需求进行一些调整。
评论
"x"
"horse"
"x"
NullMatchesWhenTheStartStringBeginsWithTwoZeroesStringComparer
StringComparer
"horse".IndexOf("x", …)
你可以试试这个。C# 源代码以 URL 编码。