Let us say we have a List<int> with content like [0,0,0,0,1,1,1,1,0,0,0,1,2,2,0,0,2,2] and we want to have the index of the nth number that is not zero.
For example, GetNthNotZero(3) should return 6.
It would be easy with a for loop, but I feel there should be a LINQ to accomplish that. Is that possible with a LINQ statement?
There isn't an out of the box method, but have you considered writing your own extension method to provide something similar to LINQ's FindIndex()?
class Program
{
static void Main(string[] args)
{
var list = new List<int>{ 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 2, 2, 0, 0, 2, 2 };
var index = list.FindNthIndex(x => x > 0, 3);
}
}
public static class IEnumerableExtensions
{
public static int FindNthIndex<T>(this IEnumerable<T> enumerable, Predicate<T> match, int count)
{
var index = 0;
foreach (var item in enumerable)
{
if (match.Invoke(item))
count--;
if (count == 0)
return index;
index++;
}
return -1;
}
}
Actually you can do that with standard LINQ, you can use:
List<int> sequence = new List<int>{0,0,0,0,1,1,1,1,0,0,0,1,2,2,0,0,2,2};
int index = sequence.Select((x, ix) => (Item:x, Index:ix))
.Where(x => x.Item != 0)
.Skip(2) // you want the 3rd, so skip 2
.Select(x => x.Index)
.DefaultIfEmpty(-1) // if there is no third matching condition you get -1
.First(); // result: 6
This is certainly possible, but the Linq approach will make it much more complicated. This is one of those cases where an explicit loop is much better.
Two significant complications arising from using Linq are:
A Linq solution might look like this (but note that there are probably many different possible approaches using Linq):
using System;
using System.Collections.Generic;
using System.Linq;
public static class Program
{
public static void Main()
{
var ints = new List<int> { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 2, 2, 0, 0, 2, 2 };
Console.WriteLine(IndexOfNthNotZero(ints, 3)); // 6
Console.WriteLine(IndexOfNthNotZero(Enumerable.Repeat(0, 10), 3)); // -1
Console.WriteLine(IndexOfNthNotZero(ints, 100)); // -1
Console.WriteLine(IndexOfNthNotZero(Array.Empty<int>(), 0)); // -1
}
public static int IndexOfNthNotZero(IEnumerable<int> sequence, int n)
{
return sequence
.Select((v, i) => (value:v, index:i)) // Synthesize the value and index.
.Where(item => item.value != 0) // Choose only the non-zero value.
.Skip(n-1) // Skip to the nth value.
.FirstOrDefault((value:0, index:-1)).index; // Handle missing data by supplying a default index of -1.
}
}
Note that this implementation returns -1 to indicate that a suitable value was not found.
Compare that with a simple loop implementation and I think you'll agree it's better to use a simple loop!
public static int IndexOfNthNotZero(IReadOnlyList<int> sequence, int n)
{
for (int i = 0; i < sequence.Count; ++i)
if (sequence[i] != 0 && --n == 0) // If element matches, decrement n and return index if it reaches 0.
return i;
return -1;
}
Or alternatively if you prefer (avoiding predecrement):
public static int IndexOfNthNotZero(IReadOnlyList<int> sequence, int n)
{
for (int i = 0, numberOfMatches = 0; i < sequence.Count; ++i)
{
if (sequence[i] != 0) // If condition matches
if (++numberOfMatches == n) // Increment number of matches, and if it reaches n
return i; // then return the current index
}
return -1;
}