C#字符串中某个字符的第三个索引

14

有没有一种命令可以获取字符串中第三个字符的索引?例如:

error: file.ext: line 10: invalid command [test:)]

在上面的句子中,我想要获取第三个冒号的索引,即紧挨着数字10旁边的那个冒号。我知道可以使用string.IndexOf和string.LastIndexOf方法,但在这种情况下,我想要获取字符第三次出现时的索引。


2
也许需要一些正则表达式?http://msdn.microsoft.com/zh-cn/library/system.text.regularexpressions.aspx - Uwe Keim
如果你想获取第三个冒号后的字符串内容,也许你应该将字符串按冒号分割,并连接除前三个标记以外的任何内容...? - jishi
@jishi:我还需要使用索引来获取其他内容。 - Iceyoshi
@Uwe Keim:“现在[你]有两个问题。” - jason
10个回答

16

String.IndexOf 可以获取第一个字符的索引,但它有一些重载函数可以指定开始搜索的位置。因此,您可以使用第一个 IndexOf 的结果加一作为下一个 IndexOf 的起始点。然后只需累积足够次数的索引:

var offset = myString.IndexOf(':');
offset = myString.IndexOf(':', offset+1);
var result = myString.IndexOf(':', offset+1);

除非你知道myString中至少包含三个冒号,否则添加错误处理。


我唯一要添加的是,在每次调用 IndexOf 后检查 offset 是否为非负数(请注意,您输入了 Index ),并且我会在每个调用后使用不同的变量分别命名为 firstOffsetsecondOffsetthirdOffset。有些人可能认为这样做过分了,但他们只是抵制不良根深蒂固的习惯。 - jason
@Jason:Index -> IndexOf:修复。请注意检查-1结果。我认为这里使用三个单独的变量有些过度(如果我知道,即可以断言有足够的字符,我会在一个表达式中完成整个操作。但是我喜欢紧凑的代码,因为通常通过消除噪音更易读)。 - Richard
这是一个相当不错的获取第n个索引的方法。是的,字符串将始终包含至少3个冒号,因此此代码不应该有问题。 - Iceyoshi
@Richard:就像我说的,我认为这是一种不好的根深蒂固的习惯。三个变量更清晰,而且它并不会增加更多的噪音,因为只需要在第二行前面添加 int 并将 offset 更改为 xOffset,其中 x 分别为 firstsecondthird。此外,调试也更加友好。 - jason
@Jason:我认为这是主观的,我们可以有不同意见(如果我真的写代码,可能只会有一个偏移量和循环)。 - Richard

11

你可以编写类似下面的代码:

    public static int CustomIndexOf(this string source, char toFind, int position)
    {
        int index = -1;
        for (int i = 0; i < position; i++)
        {
            index = source.IndexOf(toFind, index + 1);

            if (index == -1)
                break;
        }

        return index;
    }

编辑:显然你必须按照以下方式使用它:

int colonPosition = myString.CustomIndexOf(',', 3);

1
如果字符串中只有1或2个字符怎么办?您应该添加if (index < 0) return index; - Nickolay Olshevsky

4
我猜您想将该字符串解析为不同的部分。
public static void Main() {
    var input = @"error: file.ext: line 10: invalid command [test (: ]";
    var splitted = input .Split(separator: new[] {": "}, count: 4, options: StringSplitOptions.None);

    var severity = splitted[0]; // "error"
    var filename = splitted[1]; // "file.ext"
    var line = splitted[2];     // "line 10"
    var message = splitted[3];  // "invalid command [test (: ]"
}

是的,这正是我想要做的事情(好吧,现在我已经完成了),但有可能只有三个冒号而不是四个(错误消息中不包含一个),同样也可能会有更多。在这种情况下,计数将会变化,我不确定该如何解决这个问题。 - Iceyoshi
count=4 表示您将检索最多 4 个字符串。最后一个字符串可能包含分隔符而不被拆分。我意识到我的示例没有展示出来,因为我使用“:”作为分隔符,但如果消息包含“:”,它仍然不会被拆分。 - sisve
我已经修改了示例代码,使得消息文本包含“:”,以显示它没有被分割。 - sisve

2

这个问题已经有几种非常好的回答了 - 但我决定尝试使用表达式来写。

private int? GetNthOccurrance(string inputString, char charToFind, int occurranceToFind)
{
    int totalOccurrances = inputString.ToCharArray().Count(c => c == charToFind);
    if (totalOccurrances < occurranceToFind || occurranceToFind <= 0)
    {
        return null;
    }

    var charIndex =
        Enumerable.Range(0, inputString.Length - 1)
            .Select(r => new { Position = r, Char = inputString[r], Count = 1 })
            .Where(r => r.Char == charToFind);


    return charIndex
        .Select(c => new
        {
            c.Position,
            c.Char,
            Count = charIndex.Count(c2 => c2.Position <= c.Position)
        })
        .Where(r => r.Count == occurranceToFind)
        .Select(r => r.Position)
        .First();
}

而且还有测试来证明它的有效性:
Assert.AreEqual(0, GetNthOccurrance(input, 'h', 1)); 
Assert.AreEqual(3, GetNthOccurrance(input, 'l', 2));
Assert.IsNull(GetNthOccurrance(input, 'z', 1));
Assert.IsNull(GetNthOccurrance(input, 'h', 10));

0
这里是一个递归实现(针对字符串而非字符)- 作为扩展方法,模仿框架方法的格式。
你所需要做的就是在扩展方法中将“string value”更改为“char value”,并相应地更新测试,它就可以工作了...如果有人感兴趣,我很乐意这样做并发布它。
public static int IndexOfNth(
    this string input, string value, int startIndex, int nth)
{
    if (nth < 1)
        throw new NotSupportedException("Param 'nth' must be greater than 0!");
    if (nth == 1)
        input.IndexOf(value, startIndex);
    return
        input.IndexOfNth(value, input.IndexOf(value, startIndex) + 1, --nth);
}

此外,这里有一些(MBUnit)单元测试可能会帮助您(证明它是正确的):

[Test]
public void TestIndexOfNthWorksForNth1()
{
    const string input = "foo<br />bar<br />baz<br />";
    Assert.AreEqual(3, input.IndexOfNth("<br />", 0, 1));
}

[Test]
public void TestIndexOfNthWorksForNth2()
{
    const string input = "foo<br />whatthedeuce<br />kthxbai<br />";
    Assert.AreEqual(21, input.IndexOfNth("<br />", 0, 2));
}

[Test]
public void TestIndexOfNthWorksForNth3()
{
    const string input = "foo<br />whatthedeuce<br />kthxbai<br />";
    Assert.AreEqual(34, input.IndexOfNth("<br />", 0, 3));
}

0
int pos = -1;
for ( int k = 0; k < 3; ++k )
{
  pos = s.indexOf( ':', pos+1 );
  // Check if pos < 0...
}

0
一个有点丑陋,但是可行的替代方法(与已发布的其他方法不同):
public int FindThirdColonIndex(string msg)
{       
    for (int i = 0, colonCount = 0; i < msg.Length; i++)
    {
        if (msg[i] == ':' && ++colonCount == 3) { return i; }
    }

    // Not found
    return -1;
}

0
请参考类似问题的答案: https://dev59.com/HWgu5IYBdhLWcg3wYWJX#46460083
它提供了一种方法,让您在指定字符串中查找特定字符的第n个出现位置的索引。

在您的具体情况下,可以这样实现:
int index = IndexOfNthCharacter("error: file.ext: line 10: invalid command [test:)]", 3, ':');

0
你可以调用.IndexOf(char, position)来从指定位置开始搜索,因此你需要调用它3次(但是,在每次调用后,你还应该检查是否找到了某些内容)。

是的,这就是我所做的,而且它运行良好(感谢其他人发布的另一段代码)。 - Iceyoshi

0

只需按字符拆分字符串即可。 这将给您一个数组,然后您可以使用它来定位您想要的内容。

var stringToSplit = "one_two_three_four";
var splitString = stringToSplit.Split("_");          
if (splitString.length > 3){
    var newString = $"{splitResult[0]}_{splitResult[1]}_{splitResult[2]}";
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接