2009-06-18 29 views
7

Supongamos que tengo una lista con objetos del tipo Value. Value tiene una propiedad Name:Utilice Linq para encontrar elementos que se repiten consecutivamente

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

Ahora quiero obtener una lista de todos "repetir" valores (elementos en los que la propiedad nombre era idéntica a la propiedad name del elemento anterior).
En este ejemplo, quiero una lista con los dos elementos "mundo" y "a" (id = 2 y 5) para devolver.

¿Es posible este evento con linq? Por supuesto que podría hacerlo muy bien. de esta manera:

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

pero como yo quiero utilizar esta consulta en un contexto más complejo, tal vez hay una solución "linqish".

Respuesta

7

No habrá nada construido en ese sentido, pero si necesita esta frecuencia con la que podría rodar algo a medida, pero bastante genérico:

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

Uso:

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

Es posible que desee para pensar en qué hacer con los trillizos, etc., actualmente todo lo que no sea el primero se obtendrá (que coincide con su descripción), pero puede que no sea del todo correcto.

+0

esto es más eficiente que el método Zip. pero me parece que el método Zip se lee un poco mejor (es mucho más claro lo que hace) –

+0

+1 por cierto, esta es una buena respuesta –

+0

Funciona como un encanto –

4

Puede implementar un Zip extension, luego Zip su lista con .Skip (1) y luego Seleccione las filas que coinciden.

Esto debería funcionar y ser bastante fácil de mantener:

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

La ligera desventaja de este método es que iterar a través de la lista dos veces.

+0

gran solución, también. El rendimiento no es un problema en mi caso (solo unos pocos cientos de elementos). –

-1

Puede usar la extensión GroupBy para hacer esto.

+1

¿Puede elaborar con algún código, por favor? –

1

Creo que esto funcionaría (no probado): esto le dará tanto la palabra repetida como su índice. Para repeticiones múltiples puede atravesar esta lista y verificar índices consecutivos.

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

Esto no me parece LINQy ... y no funciona en contra de un IEnumerable general ... –

+0

Bueno - Me gusta :) @ Sam: ¿qué quieres decir con que no es LINQy? Es bastante LINQy para mí :) (o si realmente quieres obtener techincal, Lambday ... que se puede hacer LINQy en una fracción de segundo) :) –

+0

@Pure, si los valores son puramente un IEnumerable (y no IList) entonces esto no no funciona, por lo que es una solución real y específica que solo funciona con IList. Sin embargo, coincide con la especificación y hace el trabajo. –

-1

Algo como esto

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; 

debería funcionar. A continuación, puede utilizar los resultados de una segunda consulta:

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

Esto debería devolver una agrupación con clave = nombre, valores = {elementos con nombre}

de responsabilidad: No he probado todo lo anterior, así que puede estar muy lejos.

+1

Esto extraerá cualquier cosa con duplicados, no solo repeticiones consecutivas. – tvanfosson

1

Aquí hay otro enfoque simple que debería funcionar si los identificadores son siempre secuencial como en su muestra:

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

Sé que esta pregunta es antigua pero yo estaba trabajando en lo mismo así que ....

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

debería funcionar para nada - sólo proporcionar una función para comparar los elementos

Cuestiones relacionadas