14

is there a command that can get the third index of a character in a string? For example:

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

In the above sentence, I want to the index of the 3rd colon, the one next to the 10. How could I do that? I know of string.IndexOf and string.LastIndexOf, but in this case I want to get the index of a character when it is used the third time.

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
Iceyoshi
  • 623
  • 3
  • 10
  • 14

10 Answers10

16

String.IndexOf will get you the index of the first, but has overloads giving a starting point. So you can use a the result of the first IndexOf plus one as the starting point for the next. And then just accumulate indexes a sufficient number of times:

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

Add error handling unless you know that myString contains at least three colons.

Richard
  • 106,783
  • 21
  • 203
  • 265
  • The only thing I would add is checks that `offset` is non-negative after each call to `IndexOf` (note that you typed `Index`), and I would use a different variable after each invocation named `firstOffset`, `secondOffset` and `thirdOffset` respectively. Some will consider this overkill, but they are just resisting bad ingrained habits. – jason Jan 02 '11 at 14:48
  • @Jason: `Index` -> `IndexOf`: fixing. See final note re. checks for -1 result. And I think that three separate variables here is overkill (if I knew, i.e. could assert, there were sufficient characters I would do the whole thing in one expression. But then I like compact code as it is often more readable by removing noise.) – Richard Jan 02 '11 at 15:03
  • This is a pretty good method of getting the nth index. And yes the string will always contain at least 3 colons so there shouldn't be a problem with this code. – Iceyoshi Jan 02 '11 at 15:12
  • @Richard: Like I said, I think it's a bad ingrained habit. Three variables is more clear and it's no more noise as it only requires the addition of `int` to the preface of the second line and changing `offset` to `xOffset` where `x` is `first`, `second` or `third` accordingly. Additionally, debugging is a tad more friendly. – jason Jan 02 '11 at 15:45
  • @Jason: I think it is subjective and agree to disagree (writing this for real I would likely only have one offset and loop). – Richard Jan 02 '11 at 16:50
11

You could write something like:

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

EDIT: Obviously you have to use it as follows:

int colonPosition = myString.CustomIndexOf(',', 3);
as-cii
  • 12,819
  • 4
  • 41
  • 43
4

I am guessing you want to parse that string into different parts.

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 (: ]"
}
sisve
  • 19,501
  • 3
  • 53
  • 95
  • Yeah, it's what I want to do (well I've done it now) but there's a possibility there could be just 3 colons instead of 4 (the error message wouldn't contain one), and likewise there could be more. In that case count would vary and I'm not sure how I could overcome this problem. – Iceyoshi Jan 02 '11 at 15:10
  • count=4 means that you will retrieve _up to_ 4 strings. The last string may contain the separator without being split. I realize my example does not show that since I use ": " as a separator, but if the message were to contain ": " it would still not have been split. – sisve Jan 02 '11 at 16:00
  • I've changed the example code so that the message text contains ": ", to show that it was not split. – sisve Jan 02 '11 at 16:04
2

This has already been answered several very good ways - but I decided to try and write it using Expressions.

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();
}

and Tests to prove it too:

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));
Community
  • 1
  • 1
0

You can call .IndexOf(char, position) to search from desired position, thus you should call it 3 times (but, after each call you should also check if something is found).

Nickolay Olshevsky
  • 13,706
  • 1
  • 34
  • 48
0
int pos = -1;
for ( int k = 0; k < 3; ++k )
{
  pos = s.indexOf( ':', pos+1 );
  // Check if pos < 0...
}
Flinsch
  • 4,296
  • 1
  • 20
  • 29
0

A little ugly, but an alternative approach (to the others already posted) that works:

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;
}
Scott
  • 17,127
  • 5
  • 53
  • 64
0

Please see this answer on a similar question: https://stackoverflow.com/a/46460083/7673306
It provides a method for you to find the index of nth occurrence of a specific character within a designated string.

In your specific case it would be implemented like so:

int index = IndexOfNthCharacter("error: file.ext: line 10: invalid command [test:)]", 3, ':');
FrostyOnion
  • 856
  • 7
  • 10
0

Here is a recursive implementation (for string not char) - as an extension method, mimicing the format of the framework method(s).

All you need to do is change 'string value' to 'char value' in the extension method and update the tests accordingly and it will work... I'm happy to do that and post it if anyone is interested?

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

Also, here are some (MBUnit) unit tests that might help you (to prove it is correct):

[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));
}
Tod Thomson
  • 4,773
  • 2
  • 33
  • 33
0

Simply split the string by the char. This gives you an array that you can then use to target what you want.

var stringToSplit = "one_two_three_four";
var splitString = stringToSplit.Split("_");          
if (splitString.length > 3){
    var newString = $"{splitResult[0]}_{splitResult[1]}_{splitResult[2]}";
}
Alex Stephens
  • 3,017
  • 1
  • 36
  • 41