Reputation: 33
Let's say I have an array like this:
char[] l = {'a', 'a', 'b', 'c'}
Now, what i want to accomplish is to compare the first element of the array with the second one, move forward (compare second one with third, and so on) if they match, but if they don't then take the first element that doesn't match with the next one. Doing something similar like this:
var l = l.TakeTheFirstOne((x, y) => x != y)
In this case the answer should be the char 'a'
at second position.
Upvotes: 3
Views: 822
Reputation: 30512
If efficiency is important: use Yield return in an extension function
Although the solution with the GroupBy / Select / Last / FirstOrDefault / Last might works, it is easy to see that the sequence is enumerated several times.
Whenever you think that LINQ is missing a function, why not extend IEnumerable
with your desired function? See Extension Methods demystified
// from a sequence, takes the first one that is unequal to the previous one
// and skips all following equal ones
// example: 2 2 2 5 4 4 2 will result in: 2 5 4 2
public static IEnumerable<TSource> TakeFirstOne<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw new ArgumentNullException(nameof(source));
TSource firstDifferentItem = source.FirstOrDefault();
if (firstDifferentItem != null)
{ // at least one item in the sequence
// return the first item
yield return firstDifferentItem;
// enumerate the other items, until you find a different one
foreach (TSource nextItem in source.Skip(1))
{
// if nextItem not equal: yield return
if (!firstDifferentItem.Equals(nextItem))
{
firstDifferentItem = nextItem;
yield return firstDifferentItem;
}
}
}
}
Usage:
char[] items = ...;
IEnumerable<char> differentItems = items.TakeFirstOne();
If you look closely, you'll see that the first element of the sequence is accessed two times(once for the FirstOrDefault
and once for the Skip
), all other elements are accessed exactly once.
If you want to optimize this even further, you'll have to enumerate yourself and remember how far you already have enumerated:
Version that enumerates exactly once
public static IEnumerable<TSource> TakeFirstOne<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw new ArgumentNullException(nameof(source));
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{ // there is at least one element in the section.
TSource firstDifferentItem = enumerator.Current;
yield return firstDifferentItem;
// continue enumerating:
while(enumerator.MoveNext())
{
// if Current not equal: yield return
if (!firstDifferentItem.Equals(enumerator.Current))
{
firstDifferentItem = enumerator.Current;
yield return firstDifferentItem;
}
}
}
}
}
TODO: Consider to add a version that takes a KeySelector (which property to check), an IEqualityComparer (when are two Keys equal?) and ElementSelector (which properties to return if the Keys are unequal?). As an example use Enumerable.ToDictionary
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> comparer
{
// select the key from the first element
// yield return elementSelector(sourceItem)
// check all other elements: get the key, if equal: skip. else:
// remember key and yield return elementSelector(sourceItem)
// use comparer to check for equal key
// use comparer == null, use EqualityComparer<TKey>.Default
}
Once you've got this one, let your other overloads call this one.
Upvotes: 0
Reputation: 13498
var answer = l.Select((item, index) => new {item, index})
.FirstOrDefault(x => (x.index != l.Length - 1) && x.item != l[x.index + 1])?.item;
Upvotes: 1
Reputation: 3952
Based on the select, this version will iterate over the matching items of the collection without the count condition.
char[] items = {'a', 'a', 'b', 'c'};
var position = items
.Skip(1)
.Select((character, index) => new {character, index})
.SkipWhile(t => t.character == items[t.index])
.FirstOrDefault()
?.index;
Upvotes: 0