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?
CodePudding user response:
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>(new[] { 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;
}
}
CodePudding user response:
Actually you can do that with default 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
CodePudding user response:
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:
- Handling an empty sequence or a sequence with no zeros.
- Synthesizing an index to use.
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, decremement n and return index if it reaches 0.
return i;
return -1;
}