2009-06-18 32 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); 
    } 
} 

應該適用於任何東西 - 只是提供了一個功能要素比較