tags:

views:

442

answers:

7

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);
}

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

+2  A: 

You could implement a Zip extension, then Zip your list with .Skip(1) and then Select the rows that match.

This should work and be fairly easy to maintain:

values
  .Skip(1)
  .Zip(items, (first,second) => first.Name==second.Name?first:null)
  .Where(i => i != null);

The slight disadvantage of this method is that you iterate through the list twice.

Sam Saffron
Great solution, too.performance is not problem in my case (just a few hundred elements).
SchlaWiener
A: 

You could use the GroupBy extension to do this.

Derek Ekins
Can you elaborate with some code, please?
Pure.Krome
+4  A: 

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.

Marc Gravell
this is more efficient that the Zip method. but I find the Zip method reads a bit better (its a lot clearer what it does)
Sam Saffron
+1 by the way, this is a good answer
Sam Saffron
Works like a charm
SchlaWiener
+1  A: 

I think this would work (untested) -- this will give you both the repeated word and it's index. For multiple repeats you could traverse this list and check for consecutive indices.

 var query = values.Where( (v,i) => values.Count > i+1 && v == values[i+1] )
                   .Select( (v,i) => new { Value = v, Index = i } );
tvanfosson
This doesn't feel to LINQy to me ... and does not work against a general IEnumerable ...
Sam Saffron
Nice - I like :) @ Sam : what do u mean t's not LINQy? It's pretty LINQy to me :) (or if u really want to get techincal, Lambday .. which can be made LINQy in a split sec) :)
Pure.Krome
@Pure, if values is purely an IEnumerable (and not IList) then this does not work, so it is a real specific solution that only works with IList. It does however match the spec and get the job done.
Sam Saffron
You might use the ElementAt() extension to work with a generic enumerable. I'd worry about optimizing later for other cases when I had them, though.
tvanfosson
A: 

Something like this

var dupsNames = 
  from v in values
  group v by v.Name into g
  where g.Count > 1 // If a group has only one element, just ignore it
  select g.Key;

should work. You can then use the results in a second query:

dupsNames.Select( d => values.Where( v => v.Name == d ) )

This should return a grouping with key=name, values = { elements with name }

Disclaimer: I did not test the above, so I may be way off.

Lennaert
This will pull anything with duplicates, not just consecutive repeats.
tvanfosson
+1  A: 

Here's another simple approach that should work if the IDs are always sequential as in your sample:

var data = from v2 in values
            join v1 in values on v2.Id equals v1.Id + 1
            where v1.Name == v2.Name
            select v2;
Chris W
A: 

I know this question is ancient but I was just working on the same thing so ....

static class utils
{
    public static IEnumerable<T> FindConsecutive<T>(this IEnumerable<T> data, Func<T,T,bool> comparison)
    {
        return Enumerable.Range(0, data.Count() - 1)
        .Select( i => new { a=data.ElementAt(i), b=data.ElementAt(i+1)})
        .Where(n => comparison(n.a, n.b)).Select(n => n.a);
    }
}

Should work for anything - just provide a function to compare the elements

apocalypse9