2011-02-02 14 views
6

Tengo una lista de cadenas y quiero convertirlo en algún tipo de lista agrupada, por lo que los valores se agruparían por su ubicación en la lista (no agrupación normal, pero de una manera, que los mismos elementos están en un grupo solo si están juntos). Considere el siguiente ejemplo:'Inteligente' agrupamiento con LINQ

LinkedList<string> myList = new LinkedList<string>(); 
myList.AddLast("aaa"); 
myList.AddLast("aaa"); 
myList.AddLast("bbb"); 
myList.AddLast("bbb"); 
myList.AddLast("aaa"); 
myList.AddLast("aaa"); 
myList.AddLast("aaa"); 

LinkedList<MyTuple> groupedList = new LinkedList<MyTuple>(); 
groupedList.AddLast(new MyTuple("aaa", 2)); 
groupedList.AddLast(new MyTuple("bbb", 2)); 
groupedList.AddLast(new MyTuple("aaa", 3)); 

¿Se puede hacer esta transformación con LINQ o debería escribir el algoritmo de la forma habitual con los bucles?

+2

El segundo fragmento no se ejecutará ya que está agregando un valor con la misma clave dos veces. –

+0

Sí, lo vi, edité la pregunta :) – sventevit

Respuesta

4

El método de extensión de this answer prácticamente hace lo que pide (Microsoft también proporcionan una implementation to group contiguous items in a sequence):

public static IEnumerable<IGrouping<int, T>> 
    GroupConsecutive<T>(this IEnumerable<T> set, Func<T, T, bool> predicate) 
{ 
    var i = 0; 
    var k = 0; 
    var ranges = from e in set 
       let idx = ++i 
       let next = set.ElementAtOrDefault(idx) 
       let key = (predicate(e, next)) ? k : k++ 
       group e by key into g 
       select g; 
    return ranges; 
} 

Se puede usar de la siguiente manera:

void Main() 
{ 
    LinkedList<string> myList = new LinkedList<string>(); 
    myList.AddLast("aaa"); 
    myList.AddLast("aaa"); 
    myList.AddLast("bbb"); 
    myList.AddLast("bbb"); 
    myList.AddLast("aaa"); 
    myList.AddLast("aaa"); 
    myList.AddLast("aaa"); 
    IGrouping<int,string> ggg; 

    var groups=myList.GroupConsecutive((a,b)=>a==b); 

    ILookup<string,int> lookup=groups.ToLookup(g=>g.First(),g=>g.Count()); 

    foreach(var x in lookup["aaa"]) 
    { 
     Console.WriteLine(x); //outputs 2 then 3 
    } 
    foreach(var x in lookup["bbb"]) 
    { 
     Console.WriteLine(x); //outputs 2 
    } 

} 

en cuenta que el recipiente final es un ILookup que se comporta como un diccionario, pero permite almacenar varios valores en una sola tecla.

+0

Como está escrito, 'GroupConsecutive' llama al comparador con el 'predeterminado' la última vez. Eso parece malo (especialmente en mi caso, ya que estaba fallando con una NullReferenceException porque mi comparador no lo esperaba). –

+0

@ScottStafford: Me gustaría ir con la solución de MS vinculada aquí. Lo he usado con mucho éxito. – spender

1

Esto no es posible con el Diccionario. Un diccionario es asociativo (es decir, cada clave debe apuntar a uno y solo uno) y, por naturaleza, no está ordenado. Tendría que usar algo más para esa estructura de datos. Sin embargo, ¡no sería terriblemente duro!

Editar

Un List<Tuple<string, int>> debe hacer el truco:

List<KeyValuePair<string, int>> structure = new List<KeyValuePair<string, int>>(); 
structure.Add(new KeyValuePair<string, int>(myList[0], 1); 
for(int i = 0; i < myList.Count; i++) 
{ 
    if(myList[i] == structure[structure.Count-1].Key) 
    { 
     structure[structure.Count-1].Value += 1; 
    } 
    else 
    { 
     structure.Add(new KeyValuePair<string, int>(myList[i], 1); 
    } 
} 

Después de que usted debe (no probado!) Tiene lo que estás buscando.

Editar (un pensamiento más)

Si bien puede ser posible con LINQ (usando TakeWhile y cuenta ...), sigo pensando que tiene más sentido utilizar un bucle de aquí, es muy sencillo. Alguien más brillante de lo que podría probar y trabajar con Linq.

+0

Digamos, una 'Lista >'? –

+0

¡Cómo llegar! Estoy en una edición en este momento :) – Crisfole

+0

Creo que el contenedor "algo más" ya existe con la interfaz ILookup (ver mi respuesta) – spender

Cuestiones relacionadas