Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Linq to find consecutively repeating elements

Tags:

c#

linq

Let's assume I have a list with objects of type Value. Value has a Name property:

private List<Value> values = new List<Value> {
    new Value { Id = 0, Name = "Hello" },
    new Value { Id = 1, Name = "World" },
    new Value { Id = 2, Name = "World" },
    new Value { Id = 3, Name = "Hello" },
    new Value { Id = 4, Name = "a" },
    new Value { Id = 5, Name = "a" },
};

Now I want to get a list of all "repeating" values (elements where the name property was identical with the name property of the previous element).
In this example I want a list with the two elements "world" and "a" (id = 2 and 5) to be returned.

Is this event possible with linq? Of course I could so smth. like this:

List<Value> tempValues = new List<Value>();
String lastName = String.Empty();
foreach (var v in values)
{
    if (v.Name == lastName) tempValues.Add(v);
    lastName = v.Name;
}

but since I want to use this query in a more complex context, maybe there is a "linqish" solution.

like image 499
Jürgen Steinblock Avatar asked Jun 18 '09 12:06

Jürgen Steinblock


1 Answers

There won't be anything built in along those lines, but if you need this frequently you could roll something bespoke but fairly generic:

static IEnumerable<TSource> WhereRepeated<TSource>(
    this IEnumerable<TSource> source)
{
    return WhereRepeated<TSource,TSource>(source, x => x);
}
static IEnumerable<TSource> WhereRepeated<TSource, TValue>(
    this IEnumerable<TSource> source, Func<TSource, TValue> selector)
{
    using (var iter = source.GetEnumerator())
    {
        if (iter.MoveNext())
        {
            var comparer = EqualityComparer<TValue>.Default;
            TValue lastValue = selector(iter.Current);
            while (iter.MoveNext())
            {
                TValue currentValue = selector(iter.Current);
                if (comparer.Equals(lastValue, currentValue))
                {
                    yield return iter.Current;
                }
                lastValue = currentValue;
            }
        }
    }
}

Usage:

    foreach (Value value in values.WhereRepeated(x => x.Name))
    {
        Console.WriteLine(value.Name);
    }

You might want to think about what to do with triplets etc - currently everything except the first will be yielded (which matches your description), but that might not be quite right.

like image 139
Marc Gravell Avatar answered Oct 05 '22 20:10

Marc Gravell