2009-06-18 45 views
7

我们假设我有一个包含Value类型对象的列表。 Value有一个Name属性:使用Linq查找连续重复元素

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" }, 
}; 

现在,我想所有的名单“重复”值(其中name属性是与前一个元素的name属性相同的元素)。
在这个例子中,我想要返回两个元素“world”和“a”(id = 2和5)的列表。

这个事件可能与linq? 当然,我可以如此卑鄙。像这样:

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

但由于我想在更复杂的上下文中使用此查询,也许有一个“linqish”解决方案。

回答

7

不会有任何建立在沿着这些线路,但如果你需要经常这样你可以滚定制的东西,但是很普通:

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

用法:

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

你可能想想想如何处理三胞胎等 - 目前除了第一个以外的所有东西都会被放弃(这符合你的描述),但这可能不是很正确。

+0

这是更高效的Zip方法。但我发现Zip方法读得更好一些(其清晰度更好) – 2009-06-18 12:39:54

+0

+1,这是一个很好的答案 – 2009-06-18 12:40:28

+0

工程就像一个魅力 – 2009-06-18 12:52:33

4

你可以实现一个Zip extension,然后用.Skip(1)压缩你的列表,然后选择匹配的行。

这应该工作,是相当易于维护:

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

这种方法的轻微缺点是,你遍历列表的两倍。

+0

的最佳解决方案,也是。 性能对我来说不是问题(只有几百个元素)。 – 2009-06-18 13:04:13

-1

您可以使用GroupBy扩展来执行此操作。

+1

您能否详细说明一些代码? – 2009-06-18 12:40:58

1

我认为这会工作(未经测试) - 这会给你重复的单词和它的索引。对于多次重复,您可以遍历此列表并检查连续索引。

var query = values.Where((v,i) => values.Count > i+1 && v == values[i+1]) 
        .Select((v,i) => new { Value = v, Index = i }); 
-1

像这样的事情

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; 

应该工作。然后,您可以使用结果在第二个查询:

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

这应返回与关键=名称,值= {具有名称的元素}

声明一个分组:我没有测试上面,所以我可能会离开。

1

这里还有一个简单的方法,如果ID始终是顺序为您的样品中,应该工作:

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

我知道这个问题是古老的,但我只是工作在相同的事情,所以....

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

应该适用于任何东西 - 只是提供了一个功能要素比较