¿Cómo cuento una cantidad de elementos en cada grupo en secuencia usando linq?

Por ejemplo, tengo una secuencia de enteros

1122211121

Me gustaría obtener un diccionario / clase anónima que muestre:

item | count
1    | 2
2    | 3
1    | 3
2    | 1
1    | 1

preguntado el 16 de mayo de 11 a las 17:05

¿Existe alguna condición para esto? -

3 Respuestas

        var test = new[] { 1, 2, 2, 2, 2, 1, 1, 3 };
        int previous = test.First();
        int idx = 0;
        test.Select(x =>
                x == previous ?
                new { orig = x, helper = idx } :
                new { orig = previous = x, helper = ++idx })
            .GroupBy(x => x.helper)
            .Select(group => new { number = group.First().orig, count = group.Count() });

inicialización de previous y idx podría hacerse en let cláusula si quieres ser aún más Linqy.

       from whatever in new[] { "i want to use linq everywhere" }
       let previous = test.First()
       let idx = 0
       from x in test
       ...

La programación funcional es agradable, pero en mi humilde opinión, este es un caso en el que en C # seguramente elegiría un enfoque más bien procedimental.

contestado el 16 de mayo de 11 a las 21:05

Puede envolver la programación procedimental en una extensión IEnumerable. Vea mi respuesta para una sugerencia. Es una buena abstracción y la biblioteca morelinq se basa en esta idea en muchos lugares. Para el ejemplo del OP, simplemente transformar una secuencia de entradas inmutables, la programación procedimental es una forma fácil de hacerlo, pero para secuencias de objetos mutables más complejos, puede ser más seguro usar una extensión IEnumerable. - marr75

Sí, estoy de acuerdo, pero agregar una dependencia a la biblioteca morelinq solo para usarla para esta pequeña cosa me parece una exageración. Si está trabajando en un código lleno de modismos funcionales, morelinq puede ser un camino a seguir, definitivamente. - steves

Gracias, esto es lo que necesito. - stask

Está buscando hacer algo como el operador "Lote" en el proyecto morelinq, luego generar el recuento de los grupos.

Desafortunadamente, el operador de lotes de morelinq solo toma un tamaño y devuelve cubos agrupados por ese tamaño (o lo hizo cuando estaba mirando morelinq). Para corregir esta deficiencia, tuve que escribir mi propia implementación por lotes.

private static IEnumerable<TResult> BatchImplementation<TSource, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, TSource, int, bool> breakCondition,
        Func<IEnumerable<TSource>, TResult> resultSelector
    )
{
    List<TSource> bucket = null;
    var lastItem = default(TSource);
    var count = 0;

    foreach (var item in source)
    {
        if (breakCondition(item, lastItem, count++))
        {
            if (bucket != null)
            {
                yield return resultSelector(bucket.Select(x => x));
            }

            bucket = new List<TSource>();
        }
        bucket.Add(item);
        lastItem = item;
    }

    // Return the last bucket with all remaining elements
    if (bucket.Count > 0)
    {
        yield return resultSelector(bucket.Select(x => x));
    }
}

Esta es la versión privada a la que expongo varias sobrecargas públicas que validan los parámetros de entrada. Querría que su breakCondition fuera algo de la forma:

Func<int, int, int, bool> breakCondition = x, y, z => x != y;

Esto debería darte, para tu secuencia de ejemplo: {1, 1}, {2, 2, 2}, {1, 1, 1}, {2}, {1}

A partir de aquí, tomar el primer elemento de cada secuencia y luego contar la secuencia es trivial.

Editar: para ayudar en la implementación -

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, TSource, int, bool> breakCondition
    )
{
    //Validate that source, breakCondition, and resultSelector are not null
    return BatchImplemenatation(source, breakCondition, x => x);
}

Su código sería entonces:

var sequence = {1, 1, 2, 2, 2, 1, 1, 1, 2, 1};
var batchedSequence = sequence.batch((x, y, z) => x != y);
//batchedSequence = {{1, 1}, {2, 2, 2}, {1, 1, 1}, {2}, {1}}
var counts = batchedSequence.Select(x => x.Count());
//counts = {2, 3, 3, 1, 1}
var items = batchedSequence.Select(x => x.First());
//items = {1, 2, 1, 2, 1}
var final = counts.Zip(items. (c, i) => {Item = i, Count = c});

No he compilado ni probado nada de esto, excepto el método privado y sus sobrecargas que uso en mi propia base de código, pero esto debería resolver su problema y cualquier problema similar que tenga.

contestado el 16 de mayo de 11 a las 21:05

Bueno ... un poco más corto (observe la llamada separada doble para lidiar con los recuentos de ocurrencias pares / impares):

    static void Main(string[] args)
    {
        string separatedDigits = Separate(Separate("1122211121"));

        foreach (var ano in separatedDigits.Split('|').Select(block => new { item = block.Substring(0, 1), count = block.Length }))
            Console.WriteLine(ano);

        Console.ReadKey();
    }

    static string Separate(string input)
    {
        return Regex.Replace(input, @"(\d)(?!\1)(\d)", "$1|$2");
    }
}

contestado el 18 de mayo de 11 a las 16:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.