获取C#字符串中第一个非空白字符的索引

Eri*_* J. 20 c#

有没有办法在C#中获取字符串中第一个非空白字符的索引(或更一般地说,第一个字符的索引与条件匹配)而不编写我自己的循环代码?

编辑

通过"编写我自己的循环代码",我真的意味着我正在寻找一个解决问题的紧凑表达式,而不会混淆我正在处理的逻辑.

对于这一点上的任何混淆,我道歉.

Hen*_*man 36

A string当然是IEnumerable<char>你可以使用Linq:

int offset = someString.TakeWhile(c => char.IsWhiteSpace(c)).Count();
Run Code Online (Sandbox Code Playgroud)

  • 我认为它会更好地匹配框架语义,如果它在失败时返回-1(即当字符串为空,或者只包含空白字符时). (2认同)
  • Resharper 建议: someString.TakeWhile(char.IsWhiteSpace).Count(); (2认同)

Dou*_*las 13

我喜欢定义自己的扩展方法,用于返回满足序列中自定义谓词的第一个元素的索引.

/// <summary>
/// Returns the index of the first element in the sequence 
/// that satisfies a condition.
/// </summary>
/// <typeparam name="TSource">
/// The type of the elements of <paramref name="source"/>.
/// </typeparam>
/// <param name="source">
/// An <see cref="IEnumerable{T}"/> that contains
/// the elements to apply the predicate to.
/// </param>
/// <param name="predicate">
/// A function to test each element for a condition.
/// </param>
/// <returns>
/// The zero-based index position of the first element of <paramref name="source"/>
/// for which <paramref name="predicate"/> returns <see langword="true"/>;
/// or -1 if <paramref name="source"/> is empty
/// or no element satisfies the condition.
/// </returns>
public static int IndexOf<TSource>(this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate)
{
    int i = 0;

    foreach (TSource element in source)
    {
        if (predicate(element))
            return i;

        i++;
    }

    return -1;
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用LINQ来解决原始问题:

string str = "   Hello World";
int i = str.IndexOf<char>(c => !char.IsWhiteSpace(c));
Run Code Online (Sandbox Code Playgroud)

  • @AaronAnodide:有效的观点,但我更自由地解释它意味着OP不想编写循环*每次*他/她需要获取满足条件的第一个字符的索引。几乎所有 LINQ 都是在内部使用循环编写的;您可以将上面的内容视为原始版本中遗漏的另一个 LINQ 方法。 (2认同)

Sam*_*ous 5

您可以使用String.IndexOfAny函数,该函数返回指定 Unicode 字符数组中第一次出现的任何字符。

或者,您可以使用String.TrimStart函数删除字符串开头的所有空白字符。第一个非空白字符的索引是原始字符串和修剪后的字符串的长度之差。

您甚至可以选择一组字符进行修剪:)

基本上,如果您正在寻找一组有限的字符(比如说数字),您应该使用第一种方法。

如果您试图忽略一组有限的字符(例如空格),您应该使用第二种方法。

最后一种方法是使用Linq方法:

string s = "        qsdmlkqmlsdkm";
Console.WriteLine(s.TrimStart());
Console.WriteLine(s.Length - s.TrimStart().Length);
Console.WriteLine(s.FirstOrDefault(c => !Char.IsWhiteSpace(c)));
Console.WriteLine(s.IndexOf(s.FirstOrDefault(c => !Char.IsWhiteSpace(c))));
Run Code Online (Sandbox Code Playgroud)

输出:

qsdmlkqmlsdkm
8
q
8
Run Code Online (Sandbox Code Playgroud)


Gen*_*e S 5

string s= "   \t  Test";
Array.FindIndex(s.ToCharArray(), x => !char.IsWhiteSpace(x));
Run Code Online (Sandbox Code Playgroud)

返回 6

要添加条件,只需执行...

Array.FindIndex(s.ToCharArray(), x => !char.IsWhiteSpace(x) && your condition);
Run Code Online (Sandbox Code Playgroud)


zet*_*t42 5

受到这种修剪字符串解决方案的启发,但使用以下方法效率更高ReadOnlySpan

string s = "   xyz";
int index = s.Length - s.AsSpan().TrimStart().Length;
// index is 3
Run Code Online (Sandbox Code Playgroud)

既不创建字符串的副本,也不创建字符串的副本,它们只是存储.AsSpan()对字符串字符和长度的引用。.TrimStart()

  • .AsSpan()是它的扩展方法String,它创建一个指向字符串第一个字符的范围。它的长度是字符串的总长度。
  • .TrimStart()是它的扩展方法ReadOnlySpan<char>,它创建一个指向第一个非空白字符的范围。它的长度是字符串总长度减去第一个非空白字符的位置。

此模式通常可用于跳过给定字符的任何列表:

string s = "foobar";
int index = s.Length - s.AsSpan().TrimStart("fo").Length;
// index is 3
Run Code Online (Sandbox Code Playgroud)

我使用BenchmarkDotNet我的基准代码)对该方法和本问答中的其他几个方法进行了基准测试:

方法

意思是

错误

标准差

正则表达式_编译 45.05 我们 0.043我们 0.034我们
ReadOnlySpan_Trim(这个答案) 50.24 我们 0.073我们 0.061 微秒
字符串修剪 94.64 我们 0.458 微秒 0.428我们
正则表达式_解释 114.41 我们 0.224我们 0.210我们
Regex_StaticMethod(请阅读下文!) 114.19 我们 0.056 微秒 0.046 微秒
第一次不匹配 150.58 我们 0.214我们 0.190我们
数组查找索引 200.40 我们 1.951 我们 1.730 我们
StringExt_IndexOfPredicate 336.31 我们 0.896 我们 0.838我们
Linq_TakeWhile 490.97 我们 0.994 我们 0.930我们

没想到RegEx_Compiled会是最快的。实际上RegEx_StaticMethod应该执行同样的操作RegEx_Compiled(因为静态Regex方法缓存已编译的模式),但是当 BenchmarkDotNet在每次测试运行时创建一个新进程时,该缓存没有任何效果。

基准String_Trim取决于第一个非空白字符之后有多少个字符,因为它会复制子字符串。对于短文本,性能可能接近ReadOnlySpan_Trim,但对于较长的文本,性能会差得多。该基准测试的输入文本包含 50k 非空白字符,因此已经存在显着差异。