24

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

编辑

通过“编写我自己的循环代码”,我真正的意思是我正在寻找一个紧凑的表达式来解决问题,而不会使我正在处理的逻辑混乱。

对于在这一点上的任何混淆,我深表歉意。

4

11 回答 11

42

Astring当然是 anIEnumerable<char>所以你可以使用 Linq:

int offset = someString.TakeWhile(c => char.IsWhiteSpace(c)).Count();
于 2012-10-02T17:57:16.773 回答
18

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

/// <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;
}

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

string str = "   Hello World";
int i = str.IndexOf<char>(c => !char.IsWhiteSpace(c));
于 2012-10-02T17:56:15.937 回答
6
string s= "   \t  Test";
Array.FindIndex(s.ToCharArray(), x => !char.IsWhiteSpace(x));

返回 6

要添加条件,只需...

Array.FindIndex(s.ToCharArray(), x => !char.IsWhiteSpace(x) && your condition);
于 2012-10-02T18:08:43.443 回答
3

您可以使用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))));

输出:

qsdmlkqmlsdkm
8
q
8
于 2012-10-02T17:51:52.520 回答
3
var match = Regex.Match(" \t test  ", @"\S"); // \S means all characters that are not whitespace
if (match.Success)
{
    int index = match.Index;
    //do something with index
}
else
{
    //there were no non-whitespace characters, handle appropriately
}

如果您经常这样做,出于性能原因,您应该缓存Regex为此模式编译的内容,例如:

static readonly Regex nonWhitespace = new Regex(@"\S");

然后像这样使用它:

nonWhitespace.Match(" \t test  ");
于 2012-10-02T17:53:13.097 回答
3

由于这里有几种解决方案,我决定进行一些性能测试,看看每个解决方案的表现如何。决定将这些结果分享给有兴趣的人...

    int iterations = 1000000;
    int result = 0;
    string s= "   \t  Test";

    System.Diagnostics.Stopwatch watch = new Stopwatch();

    // Convert to char array and use FindIndex
    watch.Start();
    for (int i = 0; i < iterations; i++)
        result = Array.FindIndex(s.ToCharArray(), x => !char.IsWhiteSpace(x)); 
    watch.Stop();
    Console.WriteLine("Convert to char array and use FindIndex: " + watch.ElapsedMilliseconds);

    // Trim spaces and get index of first character
    watch.Restart();
    for (int i = 0; i < iterations; i++)
        result = s.IndexOf(s.TrimStart().Substring(0,1));
    watch.Stop();
    Console.WriteLine("Trim spaces and get index of first character: " + watch.ElapsedMilliseconds);

    // Use extension method
    watch.Restart();
    for (int i = 0; i < iterations; i++)
        result = s.IndexOf<char>(c => !char.IsWhiteSpace(c));
    watch.Stop();
    Console.WriteLine("Use extension method: " + watch.ElapsedMilliseconds);

    // Loop
    watch.Restart();
    for (int i = 0; i < iterations; i++)
    {   
        result = 0;
        foreach (char c in s)
        {
            if (!char.IsWhiteSpace(c))
                break;
            result++;
        }
    }
    watch.Stop();
    Console.WriteLine("Loop: " + watch.ElapsedMilliseconds);

结果以毫秒为单位......

其中 s = " \t Test"
转换为 char 数组并使用 FindIndex:154
修剪空格并获取第一个字符的索引:189
使用扩展方法:234
循环:146

其中 s = "Test"
转换为 char 数组并使用 FindIndex:39
修剪空格并获取第一个字符的索引:155
使用扩展方法:57
循环:15

其中 s = (1000 个无空格的字符串)
转换为 char 数组并使用 FindIndex:506
修剪空格并获取第一个字符的索引:534
使用扩展方法:51
循环:15

其中 s = (1000 以“\t Test”开头的字符串)
转换为 char 数组并使用 FindIndex:609
修剪空格并获取第一个字符的索引:1103
使用扩展方法:226
循环:146

得出你自己的结论,但我的结论是使用你最喜欢的那个,因为性能差异在现实世界的场景中是微不足道的。

于 2012-10-02T20:08:54.553 回答
2

这里有很多将字符串转换为数组的解决方案。这不是必需的,字符串中的单个字符可以像数组中的项一样被访问。

这是我应该非常有效的解决方案:

private static int FirstNonMatch(string s, Func<char, bool> predicate, int startPosition = 0)
{
    for (var i = startPosition; i < s.Length; i++)
        if (!predicate(s[i])) return i;

    return -1;
}

private static int LastNonMatch(string s, Func<char, bool> predicate, int startPosition)
{
    for (var i = startPosition; i >= 0; i--)
        if (!predicate(s[i])) return i;

    return -1;
}

要使用这些,请执行以下操作:

var x = FirstNonMatch(" asdf ", char.IsWhiteSpace);
var y = LastNonMatch(" asdf ", char.IsWhiteSpace, " asdf ".Length);
于 2016-04-12T11:42:06.073 回答
1

您可以修剪、获取第一个字符并使用 IndexOf。

于 2012-10-02T17:52:49.990 回答
1

有一个非常简单的解决方案

string test = "    hello world";
int pos = test.ToList<char>().FindIndex(x => char.IsWhiteSpace(x) == false);

pos 将是 4

您可以有更复杂的条件,例如:

pos = test.ToList<char>().FindIndex((x) =>
                {
                    if (x == 's') //Your complex conditions go here
                        return true;
                    else 
                        return false;
                }
            );
于 2016-02-19T10:12:41.387 回答
0

是的,你可以试试这个:

string stg = "   xyz";
int indx = (stg.Length - stg.Trim().Length);  
于 2012-10-02T18:00:14.577 回答
0

有些东西会在某处循环。为了完全控制什么是空白和什么不是空白,您可以使用 linq to objects 来执行循环:

int index = Array.FindIndex(
               s.ToCharArray(), 
               x => !(new [] { '\t', '\r', '\n', ' '}.Any(c => c == x)));
于 2012-10-02T18:05:40.723 回答