使用Linq查找连续重复的元素

时间:2009-06-18 12:19:29

标签: c# linq

假设我有一个包含Value类型对象的列表。 Value有一个Name属性:

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 个答案:

答案 0 :(得分: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);
    }

您可能想要考虑如何处理三胞胎等等 - 目前除了第一个之外的所有内容都会产生(与您的描述相符),但这可能不太正确。

答案 1 :(得分:4)

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

这应该有效并且相当容易维护:

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

此方法的一个小缺点是您遍历列表两次。

答案 2 :(得分:1)

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

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

答案 3 :(得分: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;

答案 4 :(得分: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);
    }
}

应该适用于任何事情 - 只需提供一个比较元素的功能

答案 5 :(得分:-1)

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

答案 6 :(得分:-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 ) )

这应该返回一个分组,其中key = name,values = {elements with name}

免责声明:我没有对上述情况进行测试,所以我可能会离开。