Reputation: 31723
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.
Upvotes: 7
Views: 4118
Reputation: 7288
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
Upvotes: 1
Reputation: 3314
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;
Upvotes: 1
Reputation: 131102
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.
Upvotes: 4
Reputation: 2465
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.
Upvotes: -1
Reputation: 532445
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 } );
Upvotes: 1
Reputation: 1062512
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.
Upvotes: 7