I\'m trying to make a function that returns the index of the Nth occurrence of a given char in a string.
Here is my attempt:
private int IndexOfNth(s
You could use the following method which will return the nth occurrence of the specified character within the designated string.
public static int IndexOfNthCharacter(string str, int n, char c) {
int index = -1;
if (!str.Contains(c.ToString()) || (str.Split(c).Length-1 < n)) {
return -1;
}
else {
for (int i = 0; i < str.Length; i++) {
if (n > 0) {
index++;
}
else {
return index;
}
if (str[i] == c) {
n--;
}
}
return index;
}
}
Note that if the character you are searching for does not exist within the string you are searching or the the occurrence number you are searching for is greater than what exists in the string then this method will return -1.
I tend to first think about how to access the collection using Linq.
// 0-based n.
char result = str
.Where(x => x == c)
.Skip(n)
.FirstOrDefault();
Then I'll unpack the linq and add the indexed iteration.
int foundCount = -1;
for(int position = 0; position < str.Length; position++)
{
char x = str[position];
if (x == c)
{
foundCount += 1;
// 0-based n
if (foundCount == n)
{
return position;
}
}
}
return -1;
Then I think about: what if this method returned all the indexes so I can query them:
public IEnumerable<int> IndexesOf(string str, char c)
{
for(int position = 0; position < str.Length; position++)
{
char x = str[position];
if (x == c)
{
yield return position;
}
}
}
Called by:
int position = IndexesOf(str, c)
.Skip(n) // 0-based n
.DefaultIfEmpty(-1)
.First();
First of all, I'd make it an extension method. This way, you can skip the otherwise obligatory null
check and also just call it on a string like you would do with IndexOf
, IndexOfAny
etc.
Then I'd make two methods of this. One to retrieve all indexes (IndexesOf
, that might come handy at sometime) and the other one (IndexOfNth
) uses the first function to check for the nth index:
using System;
using System.Collections.Generic; // # Necessary for IList<int>
using System.Linq; // # Necessary for IList<int>.ToArray()
/// <summary>
/// Returns all indexes of the specified <paramref name="value"/> in the current string.
/// </summary>
/// <param name="@this">The current string this method is operating on.</param>
/// <param name="value">The value to be searched.</param>
/// <returns><c>Null</c>, if <paramref name="value"/> is <c>null</c> or empty.
/// An array holding all indexes of <paramref name="value"/> in this string,
/// else.</returns>
static int[] IndexesOf(this string @this, string value)
{
// # Can't search for null or string.Empty, you can return what
// suits you best
if (string.IsNullOrEmpty(value))
return null;
// # Using a list instead of an array saves us statements to resize the
// array by ourselves
IList<int> indexes = new List<int>();
int startIndex = 0;
while (startIndex < @this.Length)
{
startIndex = @this.IndexOf(value, startIndex);
if (startIndex >= 0)
{
// # Add the found index to the result and increment it by length of value
// afterwards to keep searching AFTER the current position
indexes.Add(startIndex);
startIndex += value.Length;
}
else
{
// # Exit loop, if value does not occur in the remaining string
break;
}
}
// # Return an array to conform with other string operations.
return indexes.ToArray();
}
/// <summary>
/// Returns the indexes of the <paramref name="n"/>th occurrence of the specified
/// <paramref name="value"/> in the current string.
/// </summary>
/// <param name="@this">The current string this method is operating on.</param>
/// <param name="value">The value to be searched.</param>
/// <param name="n">The 1-based nth occurrence.</param>
/// <returns><c>-1</c>, if <paramref name="value"/> is <c>null</c> or empty -or-
/// <paramref name="n"/> is less than 1.</returns>
static int IndexOfNth(this string @this, string value, int n /* n is 1-based */)
{
// # You could throw an ArgumentException as well, if n is less than 1
if (string.IsNullOrEmpty(value) || n < 1)
return -1;
int[] indexes = @this.IndexesOf(value);
// # If there are n or more occurrences of 'value' in '@this'
// return the nth index.
if (indexes != null && indexes.Length >= n)
{
return indexes[n - 1];
}
return -1;
}
You can overload these using char value
instead of string value
in the signature and calling their respective counterparts passing value.ToString()
. Et voilá!
Surely, these methods can be refactored, e.g. using LINQ, make IndexesOf
recursive etc.