Saltar al contenido

Dividir una lista en grupos de números consecutivos

Al fin después de tanto batallar pudimos encontrar la respuesta de esta traba que algunos los usuarios de esta web han presentado. Si deseas aportar algún detalle puedes compartir tu información.

Solución:

Aquí hay un método de extensión tomado de http://bugsquash.blogspot.com/2010/01/grouping-consecutive-integers-in-c.html

public static IEnumerable> GroupConsecutive(this IEnumerable list) 
    var group = new List();
    foreach (var i in list) 
        if (group.Count == 0 
    yield return group;

Puedes usarlo así:

var numbers = new[]  1, 2, 3, 4, 6, 7, 9 ;
var groups = numbers.GroupConsecutive();

Una vez que se lanza C # 7, esto puede hacerse aún más eficiente con el uso de Span para evitar la creación de nuevas listas.


Esta versión actualizada lo hace sin asignar listas.

public static class EnumerableExtensions

    public static IEnumerable> GroupConsecutive(this IEnumerable list)
    
        if (list.Any())
        
            var count = 1;
            var startNumber = list.First();
            int last = startNumber;

            foreach (var i in list.Skip(1))
            
                if (i < last)
                
                    throw new ArgumentException($"List is not sorted.", nameof(list));
                
                if (i - last == 1)
                    count += 1;
                else
                
                    yield return Enumerable.Range(startNumber, count);
                    startNumber = i;
                    count = 1;
                
                last = i;
            
            yield return Enumerable.Range(startNumber, count);
        
    

Aquí está mi sugerencia para un método de extensión usando iteradores:

public static IEnumerable> GroupConsecutive(this IEnumerable src) 
    var more = false; // compiler can't figure out more is assigned before use
    IEnumerable ConsecutiveSequence(IEnumerator csi) 
        int prevCurrent;
        do
            yield return (prevCurrent = csi.Current);
        while ((more = csi.MoveNext()) && csi.Current-prevCurrent == 1);
    

    var si = src.GetEnumerator();
    if (si.MoveNext()) 
        do
            // have to process to compute outside level  
            yield return ConsecutiveSequence(si).ToList();
        while (more);
    

Debo decir que el algoritmo Python es muy impresionante, aquí hay una implementación C # del mismo:

public static IEnumerable> GroupConsecutive(this IEnumerable iterable, Func ordering = null) 
    ordering = ordering ?? (n => n);
    foreach (var tg in iterable
                         .Select((e, i) => (e, i))
                         .GroupBy(t => t.i - ordering(t.e)))
        yield return tg.Select(t => t.e);

Aquí hay una implementación de una línea en C # del algoritmo de Python:

public static IEnumerable> GroupConsecutive(this IEnumerable iterable, Func ordering = null) => 
    iterable
      .Select((e, i) => (e, i))
      .GroupBy(
        t => t.i - (ordering ?? (n => n))(t.e),
        (k,tg) => tg.Select(t => t.e));

NOTA: C # 8 con contexto de anotación anulable habilitado debería usar Func? en ambos métodos de Python. También podrías usar ??= asignar ordering.

La implementación correcta de @Bradley Uffner y el método iterador sin asignación de @NetMage es así:

public static IEnumerable> GroupConsecutive(this IEnumerable source)

    using (var e = source.GetEnumerator())
    
        for (bool more = e.MoveNext(); more; )
        
            int first = e.Current, last = first, next;
            while ((more = e.MoveNext()) && (next = e.Current) > last && next - last == 1)
                last = next;
            yield return Enumerable.Range(first, last - first + 1);
        
    

Funciona correctamente incluso para la entrada desordenada, itera la secuencia de origen solo una vez y maneja correctamente todos los casos de esquina y el desbordamiento / subdesbordamiento de enteros. El único caso en el que falla es para un recuento de rango consecutivo mayor que int.MaxValue.

Pero mirando su pregunta de seguimiento, probablemente la siguiente implementación se adapte mejor a sus necesidades:

public static IEnumerable<(int First, int Last)> ConsecutiveRanges(this IEnumerable source)

    using (var e = source.GetEnumerator())
    
        for (bool more = e.MoveNext(); more;)
        
            int first = e.Current, last = first, next;
            while ((more = e.MoveNext()) && (next = e.Current) > last && next - last == 1)
                last = next;
            yield return (first, last);
        
    

Aquí puedes ver las reseñas y valoraciones de los lectores

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)


Tags : / /

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *