¿Alternativa más rápida a los bucles anidados?

85

Necesito crear una lista de combinaciones de números. Los números son bastante pequeños, por lo que puedo usar en bytelugar de int. Sin embargo, requiere muchos bucles anidados para obtener todas las combinaciones posibles. Me pregunto si hay una manera más eficiente de hacer lo que busco. El código hasta ahora es:

var data = new List<byte[]>();
for (byte a = 0; a < 2; a++)
for (byte b = 0; b < 3; b++)
for (byte c = 0; c < 4; c++)
for (byte d = 0; d < 3; d++)
for (byte e = 0; e < 4; e++)
for (byte f = 0; f < 3; f++)
for (byte g = 0; g < 3; g++)
for (byte h = 0; h < 4; h++)
for (byte i = 0; i < 2; i++)
for (byte j = 0; j < 4; j++)
for (byte k = 0; k < 4; k++)
for (byte l = 0; l < 3; l++)
for (byte m = 0; m < 4; m++)
{
    data.Add(new [] {a, b, c, d, e, f, g, h, i, j, k, l, m});
}

Estaba considerando usar algo como un BitArraypero no estoy seguro de cómo podría incorporarlo.

Cualquier recomendación sería muy apreciada. Alternativamente, ¿quizás esta es la forma más rápida de hacer lo que quiero?

EDITAR Un par de puntos rápidos (y disculpas, no los puse en la publicación original):

  • Los números y el orden de ellos (2, 3, 4, 3, 4, 3, 3, etc.) son muy importantes, por lo que usar una solución como Generar permutaciones usando LINQ no ayudará porque los máximos en cada 'columna' son diferente
  • No soy matemático, así que me disculpo si no estoy usando los términos técnicos como 'permutaciones' y 'combinaciones' correctamente :)
  • Yo no necesito para llenar todas estas combinaciones a la vez - no puedo agarrar uno u otro basado en un índice
  • Usar bytees más rápido que usar int, te lo garantizo . También es mucho mejor en el uso de la memoria tener más de 67m matrices de bytes en lugar de ints
  • Mi objetivo final aquí es buscar una alternativa más rápida a los bucles anidados.
  • Consideré usar programación paralela, pero debido a la naturaleza iterativa de lo que estoy tratando de lograr, no pude encontrar la manera de hacerlo con éxito (incluso con ConcurrentBag); sin embargo, estoy feliz de que me hayan demostrado que estoy equivocado :)

CONCLUSIÓN

Caramiriel ha proporcionado una buena microoptimización que reduce algo de tiempo en los bucles, por lo que he marcado esa respuesta como correcta. Eric también mencionó que es más rápido preasignar la Lista. Pero, en esta etapa, parece que los bucles anidados son de hecho la forma más rápida posible de hacer esto (deprimente, ¡lo sé!).

Si desea probar exactamente lo que estaba tratando de comparar StopWatch, vaya con 13 bucles contando hasta 4 en cada bucle, lo que hace aproximadamente 67m + líneas en la lista. En mi máquina (i5-3320M 2.6GHz) se necesitan alrededor de 2.2s para hacer la versión optimizada.

benpage
fuente
1
Intente usar linq y si está usando un procesador multinúcleo, entonces
Parrallel
1
según lo que veo, estas no son las permutaciones, sino las combinaciones de un par de conjuntos muy pequeños (2-4 elementos), ¿es así o realmente desea todas / algunas permutaciones de un conjunto?
Carsten
Supongo que ya buscaste bing.com/search?q=c%23+permutation+enumerable y por alguna razón (no mencionada en la publicación) decidiste en contra de respuestas existentes como stackoverflow.com/questions/4319049/… ... opciones que consideró y decidió no hacer para mejorar esta pregunta.
Alexei Levenkov
3
Si se trata de rendimiento: puede preasignar la lista (constructor) y desenrollar algunos bucles, pero creo que eso es todo ... además de precalcular y almacenar estos números. Los bucles (overhead) son probablemente los más costosos de todos, ya que hay una pequeña cantidad de operaciones dentro del cuerpo.
Caramiriel
5
@benpage: ¿Por qué necesita generar todas las combinaciones por adelantado? ¿Por qué no generar una combinación a partir de su índice cuando la necesite?
Pieter Witvoet

Respuestas:

61

Puede utilizar las propiedades de una estructura y asignar la estructura por adelantado. Corté algunos niveles en la muestra a continuación, pero estoy seguro de que podrá averiguar los detalles. Funciona entre 5 y 6 veces más rápido que el original (modo de lanzamiento).

El bloque:

struct ByteBlock
{
    public byte A;
    public byte B;
    public byte C;
    public byte D;
    public byte E;
}

El lazo:

var data = new ByteBlock[2*3*4*3*4];
var counter = 0;

var bytes = new ByteBlock();

for (byte a = 0; a < 2; a++)
{
    bytes.A = a;
    for (byte b = 0; b < 3; b++)
    {
        bytes.B = b;
        for (byte c = 0; c < 4; c++)
        {
            bytes.C = c;
            for (byte d = 0; d < 3; d++)
            {
                bytes.D = d;
                for (byte e = 0; e < 4; e++)
                {
                    bytes.E = e;
                    data[counter++] = bytes;
                }
            }
        }
    }
}

Es más rápido porque no asigna una nueva lista cada vez que la agrega a la lista. Además, dado que está creando esta lista, necesita una referencia a todos los demás valores (a, b, c, d, e). Puede asumir que cada valor solo se modifica una vez dentro del ciclo, por lo que podemos optimizarlo para hacerlo (localidad de datos).

Lea también los comentarios para conocer los efectos secundarios.

Editó la respuesta para usar un en T[]lugar de un List<T>.

Caramiriel
fuente
1
Es una estructura, por lo que debería estar bien =) son todos únicos. Se copia al llamar al List<T>.Addmétodo.
Caramiriel
4
Es incluso más rápido si ha asignado la capacidad a la Lista ()
Eric
5
Tenga cuidado con las excepciones de desbordamiento de pila al asignar demasiados objetos en la pila.
Andrei Tătar
7
@ Andrew No entiendo tu punto. Este código no es recursivo y tiene un uso mínimo de la pila.
CodesInChaos
3
@ Andrew: Eso es memoria insuficiente, no stackoverflow. Esto se debe a que el List<T>.Add()método va más allá del punto de lo que puede almacenar. Esto hará que cambie de tamaño (duplica su tamaño), lo que supera los 2 GB de memoria. Intente preasignar usando el nuevo List <ByteBlock> (maxPerLevel.Aggregate (1, (x, y) => x * y)), aunque ya es 'aleatorio' que necesita un bloque completo de 2GB de estos datos en la memoria. También tenga en cuenta que data.ToArray (); es caro porque mantiene elementos en la memoria dos veces en ese momento. [reformulado]
Caramiriel
33

Lo que está haciendo es contar (con base variable, pero aún contando).

Dado que está utilizando C #, supongo que no quiere jugar con el diseño de memoria útil y las estructuras de datos que le permiten optimizar realmente su código.

Así que aquí estoy publicando algo diferente, que puede que no se adapte a su caso, pero vale la pena señalarlo: en caso de que realmente acceda a la lista de manera dispersa, aquí hay una clase que le permite calcular el elemento i-ésimo en tiempo lineal (en lugar de que exponencial como las otras respuestas)

class Counter
{
    public int[] Radices;

    public int[] this[int n]
    {
        get 
        { 
            int[] v = new int[Radices.Length];
            int i = Radices.Length - 1;

            while (n != 0 && i >= 0)
            {
                //Hope C# has an IL-opcode for div-and-reminder like x86 do
                v[i] = n % Radices[i];
                n /= Radices[i--];
            }
            return v;
        }
    }
}

Puedes usar esta clase de esta manera

Counter c = new Counter();
c.Radices = new int[] { 2,3,4,3,4,3,3,4,2,4,4,3,4};

Ahora c[i]es lo mismo que su lista, nombrarlo l, l[i].

Como puede ver, puede evitar fácilmente todos esos bucles :) incluso cuando calcule previamente toda la lista en su totalidad, ya que simplemente puede implementar un contador Carry-Ripple.

Los contadores son un tema muy estudiado, le recomiendo encarecidamente que busque algo de literatura si lo siente.

Adán
fuente
4
Me gusta tu respuesta, pero decir que todas las demás respuestas son exponenciales no es cierto.
Galletas
1
¿Cuál es la velocidad en esto en comparación con la respuesta de Caramiriel?
John Odom
17
"C-kiddy- #", ¿en serio? Eso parece completamente fuera de lugar.
KChaloux
2
Y lo hace: Math.DivRem
Caramiriel
1
Creo que más allá de cierto nivel, la optimización es una cuestión de uso. Por ejemplo, si cada matriz se va a utilizar solo una vez, puede evitar la asignación de memoria intensiva, que es el cuello de botella crítico en mi opinión. Además, si desea calcular todo el valor, debe aprovechar el hecho de que realiza incrementos únicos (es decir, incremento de +1) evitando una división. Esto está más pensado como una prenda de respuesta "lista para usar" o un prototipo, realmente no intenté acelerarlo, simplemente me gusta de esta manera :)
14

Método 1

Una forma de hacerlo más rápido es especificar la capacidad si planea seguir usando List<byte[]>, de esta manera.

var data = new List<byte[]>(2 * 3 * 4 * 3 * 4 * 3 * 3 * 4 * 2 * 4 * 4 * 3 * 4);

Método 2

Además, puede usarlo System.Arraydirectamente para obtener un acceso más rápido. Recomiendo este enfoque si su pregunta insiste en que cada elemento se rellene físicamente en la memoria, por adelantado.

var data = new byte[2 * 3 * 4 * 3 * 4 * 3 * 3 * 4 * 2 * 4 * 4 * 3 * 4][];
int counter = 0;

for (byte a = 0; a < 2; a++)
    for (byte b = 0; b < 3; b++)
        for (byte c = 0; c < 4; c++)
            for (byte d = 0; d < 3; d++)
                for (byte e = 0; e < 4; e++)
                    for (byte f = 0; f < 3; f++)
                        for (byte g = 0; g < 3; g++)
                            for (byte h = 0; h < 4; h++)
                                for (byte i = 0; i < 2; i++)
                                    for (byte j = 0; j < 4; j++)
                                        for (byte k = 0; k < 4; k++)
                                            for (byte l = 0; l < 3; l++)
                                                for (byte m = 0; m < 4; m++)
                                                    data[counter++] = new[] { a, b, c, d, e, f, g, h, i, j, k, l, m };

Esto tarda 596 ms en completarse en mi computadora, que es aproximadamente un 10,4% más rápido que el código en cuestión (que tarda 658 ms).

Método 3

Alternativamente, puede utilizar la siguiente técnica para una inicialización de bajo costo que se adapte al acceso de manera dispersa. Esto es especialmente favorable cuando solo se pueden necesitar algunos elementos y se considera innecesario determinarlos todos por adelantado. Además, técnicas como estas pueden convertirse en la única opción viable cuando se trabaja con elementos más amplios cuando la memoria se agota.

En esta implementación, cada elemento debe determinarse de manera perezosa, sobre la marcha, al acceder. Naturalmente, esto tiene un costo de CPU adicional que se incurre durante el acceso.

class HypotheticalBytes
{
    private readonly int _c1, _c2, _c3, _c4, _c5, _c6, _c7, _c8, _c9, _c10, _c11, _c12;
    private readonly int _t0, _t1, _t2, _t3, _t4, _t5, _t6, _t7, _t8, _t9, _t10, _t11;

    public int Count
    {
        get { return _t0; }
    }

    public HypotheticalBytes(
        int c0, int c1, int c2, int c3, int c4, int c5, int c6, int c7, int c8, int c9, int c10, int c11, int c12)
    {
        _c1 = c1;
        _c2 = c2;
        _c3 = c3;
        _c4 = c4;
        _c5 = c5;
        _c6 = c6;
        _c7 = c7;
        _c8 = c8;
        _c9 = c9;
        _c10 = c10;
        _c11 = c11;
        _c12 = c12;
        _t11 = _c12 * c11;
        _t10 = _t11 * c10;
        _t9 = _t10 * c9;
        _t8 = _t9 * c8;
        _t7 = _t8 * c7;
        _t6 = _t7 * c6;
        _t5 = _t6 * c5;
        _t4 = _t5 * c4;
        _t3 = _t4 * c3;
        _t2 = _t3 * c2;
        _t1 = _t2 * c1;
        _t0 = _t1 * c0;
    }

    public byte[] this[int index]
    {
        get
        {
            return new[]
            {
                (byte)(index / _t1),
                (byte)((index / _t2) % _c1),
                (byte)((index / _t3) % _c2),
                (byte)((index / _t4) % _c3),
                (byte)((index / _t5) % _c4),
                (byte)((index / _t6) % _c5),
                (byte)((index / _t7) % _c6),
                (byte)((index / _t8) % _c7),
                (byte)((index / _t9) % _c8),
                (byte)((index / _t10) % _c9),
                (byte)((index / _t11) % _c10),
                (byte)((index / _c12) % _c11),
                (byte)(index % _c12)
            };
        }
    }
}

Esto tarda 897 ms en completarse en mi computadora (también creando y agregando Arraycomo en el Método 2 ), que es aproximadamente un 36,3% más lento que el código en cuestión (que tarda 658 ms).

Galletas
fuente
1
Su segunda sugerencia también es un ahorro significativo en términos de consumo de memoria. (Pero me gustaría señalar que asume que la lista no debería cambiar)
Taemyr
Necesito que se cree la lista completa de una vez; no puedo hacer referencia a un índice dentro de la lista.
benpage
@Taemyr Gracias. Actualizaré para tener en cuenta eso en consecuencia. Si la implementación realmente insiste en que tiene la lista completa completada por adelantado, entonces esta tercera opción obviamente no funcionará para usted.
Galletas
3
@benpage ¿Por qué necesita la lista completa?
Taemyr
14

En mi máquina, esto genera las combinaciones en 222 ms vs 760 ms (los 13 bucles for):

private static byte[,] GenerateCombinations(byte[] maxNumberPerLevel)
{
    var levels = maxNumberPerLevel.Length;

    var periodsPerLevel = new int[levels];
    var totalItems = 1;
    for (var i = 0; i < levels; i++)
    {
        periodsPerLevel[i] = totalItems;
        totalItems *= maxNumberPerLevel[i];
    }

    var results = new byte[totalItems, levels];

    Parallel.For(0, levels, level =>
    {
        var periodPerLevel = periodsPerLevel[level];
        var maxPerLevel = maxNumberPerLevel[level];
        for (var i = 0; i < totalItems; i++)
            results[i, level] = (byte)(i / periodPerLevel % maxPerLevel);
    });

    return results;
}
Andrei Tătar
fuente
¡Esta es una respuesta genial! Desafortunadamente, funciona más lento que los bucles anidados. ¿Alguna posibilidad de que puedas editar usando TPL?
benpage
todavía un poco más lento, por desgracia.
benpage
1
@benpage Hay una manera fácil de hacerlo al menos 2 veces más rápido. Solo tiene que cambiar el tipo de resultado a int [,]. Esto asignará toda la memoria de la matriz en una llamada. No estoy seguro de cómo se ajusta a sus necesidades (cambiando el tipo de devolución).
Andrei Tătar
8
var numbers = new[] { 2, 3, 4, 3, 4, 3, 3, 4, 2, 4, 4, 3, 4 };
var result = (numbers.Select(i => Enumerable.Range(0, i))).CartesianProduct();

Usando el método de extensión en http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/

public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
    // base case: 
    IEnumerable<IEnumerable<T>> result =
        new[] { Enumerable.Empty<T>() };
    foreach (var sequence in sequences)
    {
        // don't close over the loop variable (fixed in C# 5 BTW)
        var s = sequence;
        // recursive case: use SelectMany to build 
        // the new product out of the old one 
        result =
            from seq in result
            from item in s
            select seq.Concat(new[] { item });
    }
    return result;
}
Eric
fuente
1
esto corre mucho más lento :(
benpage
8

List tiene una matriz internamente donde almacena sus valores, con una longitud fija. Cuando llames a List.Add comprueba si hay suficiente espacio. Cuando no pueda agregar el nuevo elemento, creará una nueva matriz de mayor tamaño, copiará todos los elementos anteriores y luego agregará uno nuevo. Esto lleva bastantes ciclos.

Como ya conoce la cantidad de elementos, puede crear la lista del tamaño correcto, que ya debería ser mucho más rápido.

Además, no estoy seguro de cómo accede a los valores, pero puede crear esta cosa y guardar la imagen en código (cargarla desde el disco probablemente será más lento de lo que está haciendo ahora. ¿Cuántas veces lee / escribe en este ¿cosa?

gjvdkamp
fuente
De hecho, he intentado preasignar una matriz regular y, lo creas o no, es más lento. Como dije anteriormente, esto debe crearse sobre la marcha, no puedo calcularlo una vez y dejarlo.
benpage
¿De Verdad? wow, estás ejecutando con optimizaciones habilitadas, ¿verdad? (solo preguntando)
Carsten
Ah, ese es otro problema, las matrices regulares [x, y] son ​​agradables de usar pero una matriz de matrices será más rápida. stackoverflow.com/questions/597720/… debido a cómo se implementan bajo el capó en IL
gjvdkamp
5

Aquí hay una forma diferente que solo necesita 2 bucles. La idea es aumentar el primer elemento y si ese número supera, aumentar el siguiente.

En lugar de mostrar los datos, puede usar currentValues.Clone y agregar esa versión clonada a su lista. Para mí, esto funcionó más rápido que tu versión.

byte[] maxValues = {2, 3, 4};
byte[] currentValues = {0, 0, 0};

do {
    Console.WriteLine("{0}, {1}, {2}", currentValues[0], currentValues[1], currentValues[2]);

    currentValues[0] += 1;

    for (int i = 0; i <= maxValues.Count - 2; i++) {
        if (currentValues[i] < maxValues[i]) {
            break;
        }

        currentValues[i] = 0;
        currentValues[i + 1] += 1;
    }

// Stop the whole thing if the last number is over
// } while (currentValues[currentValues.Length-1] < maxValues[maxValues.Length-1]);
} while (currentValues.Last() < maxValues.Last());
  • Espero que este código funcione, lo convertí de vb
the_lotus
fuente
3

Todos sus números son constantes de tiempo de compilación.

¿Qué hay de desenrollar todos los bucles en una lista (usando su programa para escribir código):

data.Add(new [] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
data.Add(new [] {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
etc.

Eso debería al menos eliminar la sobrecarga de los bucles for (si los hay).

No estoy demasiado familiarizado con C #, pero parece haber algunos medios para serializar objetos. ¿Qué pasa si solo genera esa lista y la serializa de alguna forma? Sin embargo, no estoy seguro de si la deserialización es más rápida que crear la Lista y agregar los elementos.

nulo
fuente
¡La serialización es un enfoque realmente genial fuera de la caja!
Joel B
Desafortunadamente, los máximos en la lista son dinámicos, no puedo escribir esto de forma estática. ¡Buena idea!
benpage
2

¿Necesita que el resultado sea una matriz de matrices? Con la configuración actual, la longitud de las matrices internas es fija y podría reemplazarse con estructuras. Esto permitiría que todo se reservara como un bloque continuo de memoria y proporcionaría un acceso más fácil a los elementos (no estoy seguro de cómo usará esto más adelante).

El siguiente enfoque es mucho más rápido (41 ms frente a 1071 ms para el original en mi caja):

struct element {
    public byte a;
    public byte b;
    public byte c;
    public byte d;
    public byte e;
    public byte f;
    public byte g;
    public byte h;
    public byte i;
    public byte j;
    public byte k;
    public byte l;
    public byte m;
}

element[] WithStruct() {
    var t = new element[3981312];
    int z = 0;
    for (byte a = 0; a < 2; a++)
    for (byte b = 0; b < 3; b++)
    for (byte c = 0; c < 4; c++)
    for (byte d = 0; d < 3; d++)
    for (byte e = 0; e < 4; e++)
    for (byte f = 0; f < 3; f++)
    for (byte g = 0; g < 3; g++)
    for (byte h = 0; h < 4; h++)
    for (byte i = 0; i < 2; i++)
    for (byte j = 0; j < 4; j++)
    for (byte k = 0; k < 4; k++)
    for (byte l = 0; l < 3; l++)
    for (byte m = 0; m < 4; m++)
    {
        t[z].a = a;
        t[z].b = b;
        t[z].c = c;
        t[z].d = d;
        t[z].e = e;
        t[z].f = f;
        t[z].g = g;
        t[z].h = h;
        t[z].i = i;
        t[z].j = j;
        t[z].k = k;
        t[z].l = l;
        t[z].m = m;
        z++;
    }
    return t;
}
gjvdkamp
fuente
Buena idea; de hecho, eso es lo que hice en mi proyecto del mundo real; simplemente no lo puse en la solución original por simplicidad. Principalmente buscaba una mejor alternativa a los bucles anidados.
benpage
1

¿Qué hay de usar Parallel.For()para ejecutarlo? (Felicitaciones de optimización de estructuras a @Caramiriel ). Modifiqué ligeramente los valores (a es 5 en lugar de 2) para tener más confianza en los resultados.

    var data = new ConcurrentStack<List<Bytes>>();
    var sw = new Stopwatch();

    sw.Start();

    Parallel.For(0, 5, () => new List<Bytes>(3*4*3*4*3*3*4*2*4*4*3*4),
      (a, loop, localList) => {
        var bytes = new Bytes();
        bytes.A = (byte) a;
        for (byte b = 0; b < 3; b++) {
          bytes.B = b;
          for (byte c = 0; c < 4; c++) {
            bytes.C = c; 
            for (byte d = 0; d < 3; d++) {
              bytes.D = d; 
              for (byte e = 0; e < 4; e++) {
                bytes.E = e; 
                for (byte f = 0; f < 3; f++) {
                  bytes.F = f; 
                  for (byte g = 0; g < 3; g++) {
                    bytes.G = g; 
                    for (byte h = 0; h < 4; h++) {
                      bytes.H = h; 
                      for (byte i = 0; i < 2; i++) {
                        bytes.I = i; 
                        for (byte j = 0; j < 4; j++) {
                          bytes.J = j; 
                          for (byte k = 0; k < 4; k++) {
                            bytes.K = k; 
                            for (byte l = 0; l < 3; l++) {
                              bytes.L = l;
                              for (byte m = 0; m < 4; m++) {
                                bytes.M = m;
                                localList.Add(bytes);
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }


        return localList;
      }, x => {
        data.Push(x);
    });

    var joinedData = _join(data);

_join() es un método privado, definido como:

private static IList<Bytes> _join(IEnumerable<IList<Bytes>> data) {
  var value = new List<Bytes>();
  foreach (var d in data) {
    value.AddRange(d);
  }
  return value;
}

En mi sistema, esta versión se ejecuta aproximadamente 6 veces más rápido (1,718 segundos frente a 0,266 segundos).

jdphenix
fuente
1
Esto está prácticamente garantizado para brindarle un intercambio falso y probablemente será muchas veces más lento.
gjvdkamp
No está mal, desafortunadamente funciona más lento que los bucles for. FWIW ¡Lo intenté con TODOS Parallel.Fory VS se estrelló!
benpage
@gjvdkamp He actualizado mi respuesta con una versión paralela que creo que elimina el problema de compartir falso.
jdphenix
0

Algunos de sus números encajan completamente en un número entero de bits, por lo que puede "empaquetarlos" con el número de nivel superior:

for (byte lm = 0; lm < 12; lm++)
{
    ...
    t[z].l = (lm&12)>>2;
    t[z].m = lm&3;
    ...
}

Por supuesto, esto hace que el código sea menos legible, pero guardó un bucle. Esto se puede hacer cada vez que uno de los números sea una potencia de dos, que en su caso es siete veces.

Fabien Dupont
fuente
Me gustaría saber más sobre esta respuesta, ¿podría ampliarla?
benpage
Perdón por la respuesta tardía. m va de 0 a 3, que en binario hace de 00 a 11, l de 0 a 2, que hace de 00 a 10, por lo que si los imprime por separado, esto sería: 00 00 00 01 00 10 00 11 01 00 .. .10 11 Puede fusionar estos juntos en un solo número de 4 bits, yendo de 0000 a 1011, y seleccionar los bits apropiados usando una máscara lm & 3 hace el biwise y entre lm y (11) b lm & 12 hace lo mismo con lm y (1100) b, luego cambiamos dos bits para obtener el número "real". Por cierto, acabo de darme cuenta de que es suficiente hacer lm >> 2 en este caso.
Fabien Dupont
0

Aquí hay otra solución. Fuera de VS, se ejecuta tan rápido como 437.5 ms, que es un 26% más rápido que el código original (593.7 en mi computadora):

static List<byte[]> Combinations(byte[] maxs)
{
  int length = maxs.Length;
  int count = 1; // 3981312;
  Array.ForEach(maxs, m => count *= m);
  byte[][] data = new byte[count][];
  byte[] counters = new byte[length];

  for (int r = 0; r < count; r++)
  {
    byte[] row = new byte[length];
    for (int c = 0; c < length; c++)
      row[c] = counters[c];
    data[r] = row;

    for (int i = length - 1; i >= 0; i--)
    {
      counters[i]++;
      if (counters[i] == maxs[i])
        counters[i] = 0;
      else
        break;
    }
  }

  return data.ToList();
}
Henrik
fuente