Alternativa più veloce ai loop nidificati?


85

Ho bisogno di creare un elenco di combinazioni di numeri. I numeri sono piuttosto piccoli quindi posso usare bytepiuttosto che int. Tuttavia richiede molti cicli annidati per ottenere ogni possibile combinazione. Mi chiedo se esista un modo più efficiente per fare ciò che cerco. Il codice finora è:

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

Stavo pensando di utilizzare qualcosa come a, BitArrayma non sono sicuro di come potrei incorporarlo.

Ogni consiglio sarebbe molto apprezzato. In alternativa, forse questo è il modo più veloce per fare quello che voglio?

MODIFICA Un paio di brevi punti (e mi scuso per non averli inseriti nel post originale):

  • I numeri e l'ordine di essi (2, 3, 4, 3, 4, 3, 3 ecc.) Sono molto importanti, quindi l'utilizzo di una soluzione come Generare permutazioni usando LINQ non aiuta perché i massimi in ogni 'colonna' sono diverso
  • Non sono un matematico, quindi mi scuso se non utilizzo correttamente i termini tecnici come "permutazioni" e "combinazioni" :)
  • Io faccio bisogno di compilare tutte queste combinazioni in una sola volta - non posso prendere uno o l'altro sulla base di un indice di
  • Usare byteè più veloce dell'uso int, lo garantisco . È anche molto meglio sull'utilizzo della memoria avere più di 67 milioni di matrici di byte piuttosto che interi
  • Il mio obiettivo finale qui è cercare un'alternativa più veloce ai loop annidati.
  • Ho considerato l'utilizzo della programmazione parallela, ma a causa della natura iterativa di ciò che sto cercando di ottenere, non sono riuscito a trovare un modo per farlo con successo (anche con ConcurrentBag) - tuttavia sono felice di essere smentito :)

CONCLUSIONE

Caramiriel ha fornito una buona micro-ottimizzazione che riduce un po 'di tempo ai loop, quindi ho contrassegnato la risposta come corretta. Eric ha anche detto che è più veloce pre-allocare l'elenco. Ma, in questa fase, sembra che i cicli annidati siano in realtà il modo più veloce possibile per farlo (deprimente, lo so!).

Se vuoi provare esattamente quello con cui stavo cercando di fare il benchmark StopWatch, vai con 13 loop che contano fino a 4 in ogni loop - che fa circa 67m + linee nell'elenco. Sulla mia macchina (i5-3320M 2.6GHz) ci vogliono circa 2.2s per fare la versione ottimizzata.


1
Prova a usare linq e se stai usando un processore multicore allora Parrallel.for
Vadgama

1
in base a quello che vedo queste non sono le permutazioni ma le combinazioni di un paio di insiemi molto piccoli (2-4 elementi) è giusto o vuoi davvero tutte / alcune permutazioni di un insieme?
Carsten

Presumo che tu abbia già cercato bing.com/search?q=c%23+permutation+enumerable e per qualche motivo (non menzionato nel post) hai deciso di non rispondere a risposte esistenti come stackoverflow.com/questions/4319049/… ... Considera l'elenco opzioni che hai esaminato e che hai deciso di sconsigliare per migliorare questa domanda.
Alexei Levenkov

3
Se si tratta di prestazioni: è possibile preallocare l'elenco (costruttore) e srotolare alcuni cicli, ma penso che sia tutto qui ... a parte il precalcolo e la memorizzazione di questi numeri. I loop (overhead) sono probabilmente i più costosi di tutti, poiché ci sono poche operazioni all'interno del corpo.
Caramiriel

5
@benpage: Perché è necessario generare tutte le combinazioni in anticipo? Perché non generare una combinazione dal suo indice quando ne hai bisogno?
Pieter Witvoet

Risposte:


61

È possibile utilizzare le proprietà di una struttura e allocare la struttura in anticipo. Ho tagliato alcuni livelli nell'esempio qui sotto, ma sono sicuro che sarai in grado di capire le specifiche. Funziona circa 5-6 volte più velocemente dell'originale (modalità di rilascio).

Il blocco:

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

Il cappio:

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

È più veloce perché non assegna un nuovo elenco ogni volta che lo aggiungi all'elenco. Inoltre, poiché sta creando questo elenco, ha bisogno di un riferimento a ogni altro valore (a, b, c, d, e). Puoi presumere che ogni valore venga modificato solo una volta all'interno del ciclo, quindi possiamo ottimizzarlo per farlo (località dei dati).

Leggi anche i commenti per gli effetti collaterali.

Ho modificato la risposta per utilizzare un T[]invece di un List<T>.


1
È una struttura, quindi dovresti stare bene =) sono tutti unici. Viene copiato quando si chiama il List<T>.Addmetodo.
Caramiriel

4
È ancora più veloce se si alloca la capacità a List ()
Eric

5
Fai attenzione alle eccezioni di stackoverflow quando allocare troppi oggetti nello stack.
Andrei Tătar

7
@ Andrew Non capisco il tuo punto. Questo codice non è ricorsivo e ha un utilizzo minimo dello stack.
CodesInChaos

3
@Andrew: è esaurita la memoria, non lo stackoverflow. Ciò è dovuto al fatto che il List<T>.Add()metodo va oltre il punto di ciò che può memorizzare. Questo lo ridimensionerà (raddoppia le dimensioni), che supera i 2 GB di memoria. Prova a preallocare utilizzando il nuovo List <ByteBlock> (maxPerLevel.Aggregate (1, (x, y) => x * y)), anche se è già 'casuale' che hai bisogno di un blocco completo di 2 GB di questi dati in memoria. Notare inoltre che data.ToArray (); è costoso perché mantiene gli elementi in memoria due volte a quel punto. [riformulato]
Caramiriel

33

Quello che stai facendo è contare (con radice variabile, ma ancora contando).

Dato che stai usando C #, presumo che tu non voglia giocare con layout di memoria utili e strutture di dati che ti consentono di ottimizzare davvero il tuo codice.

Quindi qui sto postando qualcosa di diverso, che potrebbe non essere adatto al tuo caso, ma vale la pena notare: nel caso in cui effettivamente accedi all'elenco in modo scarno, qui una classe che ti consente di calcolare l'elemento i-esimo in tempo lineare (piuttosto che esponenziale come le altre risposte)

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

Puoi usare questa classe in questo modo

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

ora c[i]è la stessa come l'elenco, il nome l, l[i].

Come puoi vedere, puoi facilmente evitare tutti quei loop :) anche quando pre-calcoli l'intero elenco poiché puoi semplicemente implementare un contatore Carry-Ripple.

I contatori sono una materia molto studiata, consiglio vivamente di cercare un po 'di letteratura se ne hai voglia.


4
Mi piace la tua risposta, ma dire che tutte le altre risposte sono esponenziali non è vero.
Biscotti

1
Qual è la velocità su questo rispetto alla risposta di Caramiriel?
John Odom

17
"C-kiddy- #", davvero? Sembra del tutto fuori luogo.
KChaloux

2
E lo fa: Math.DivRem
Caramiriel

1
Penso che oltre un certo livello, l'ottimizzazione sia una questione di utilizzo. Ad esempio, se ogni array deve essere utilizzato una sola volta, è possibile evitare l'allocazione intensiva della memoria, che è il collo di bottiglia critico secondo me. Inoltre, se vuoi calcolare tutto il valore, dovresti sfruttare il fatto che fai incrementi singoli (es. Incremento +1) evitando una divisione. Questo è più inteso come una risposta "fuori dagli schemi" o un prototipo, non ho davvero provato ad accelerarlo, mi piace così :)

14

Metodo 1

Un modo per renderlo più veloce è specificare la capacità se prevedi di continuare a utilizzarlo List<byte[]>, in questo modo.

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

Metodo 2

Inoltre, puoi utilizzare System.Arraydirettamente per ottenere un accesso più rapido. Consiglio questo approccio se la tua domanda insiste sul fatto che ogni elemento sia fisicamente popolato nella memoria, in anticipo.

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

Questo richiede 596 ms per completare sul mio computer, che è circa il 10,4% più veloce del codice in questione (che richiede 658 ms).

Metodo 3

In alternativa, è possibile utilizzare la seguente tecnica per l'inizializzazione a basso costo che si adatta all'accesso in modo scarno. Ciò è particolarmente favorevole quando possono essere necessari solo alcuni elementi e determinarli tutti in anticipo non è considerato necessario. Inoltre, tecniche come queste possono diventare l'unica opzione praticabile quando si lavora con elementi più vasti quando la memoria si esaurisce.

In questa implementazione ogni elemento deve essere determinato pigramente, al volo, al momento dell'accesso. Naturalmente, questo ha un costo di CPU aggiuntiva che viene sostenuto durante l'accesso.

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

Questo richiede 897 ms per essere completato sul mio computer (anche creazione e aggiunta di un Arraycome nel Metodo 2 ), che è circa il 36,3% più lento del codice in questione (che richiede 658 ms).


1
Il tuo secondo suggerimento è anche un risparmio significativo in termini di consumo di memoria. (Ma vorrei notare che si presume che l'elenco non debba cambiare)
Taemyr

Ho bisogno dell'intero elenco creato in una sola volta: non posso fare riferimento a un indice all'interno dell'elenco.
benpage

@ Taemyr Thanks. Aggiornerò per notare che di conseguenza. Se l'implementazione insiste davvero che l'intero elenco venga popolato in anticipo, questa terza opzione ovviamente non funzionerà per te.
Biscotti

3
@benpage Perché è necessario che l'elenco venga compilato?
Taemyr

14

Sulla mia macchina, questo genera le combinazioni in 222 ms contro 760 ms (i 13 per i cicli):

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

Questa è un'ottima risposta! Sfortunatamente funziona più lentamente dei cicli annidati. C'è qualche possibilità che potresti modificare usando TPL?
benpage

purtroppo ancora un po 'più lento.
benpage

1
@benpage C'è un modo semplice per renderlo almeno 2 volte più veloce. Devi solo cambiare il tipo di risultati in int [,]. Ciò allocherà l'intera memoria dell'array in una chiamata. Non sono sicuro di come questo si adatti alle tue esigenze (modifica del tipo di reso).
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();

Utilizzando il metodo di estensione su 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;
}

1
funziona molto più lentamente :(
benpage

8

List ha un array internamente in cui memorizza i valori, con una lunghezza fissa. Quando chiami List.Add controlla se c'è abbastanza spazio. Quando non è possibile aggiungere il nuovo elemento, creerà un nuovo array di dimensioni maggiori, copierà tutti gli elementi precedenti, quindi aggiungerà uno nuovo. Questo richiede alcuni cicli.

Poiché conosci già il numero di elementi, puoi creare l'elenco della dimensione corretta, che dovrebbe essere già molto più veloce.

Inoltre, non sei sicuro di come accedere ai valori, ma potresti creare questa cosa e salvare l'immagine nel codice (caricarla dal disco sarà probabilmente più lento di quello che stai facendo ora. Quante volte leggi / scrivi su questo cosa?


In realtà ho provato a pre-allocare un array normale e che ci crediate o no, è più lento. Come ho detto sopra, questo deve essere creato al volo, non posso calcolarlo una volta e lasciarlo.
benpage

veramente? wow, stai funzionando con le ottimizzazioni abilitate, giusto? (solo chiedendo)
Carsten

Ah, questo è un altro problema, gli array regolari [x, y] sono piacevoli da usare ma un array di array sarà più veloce. stackoverflow.com/questions/597720/… a causa di come sono implementati sotto il cofano in IL
gjvdkamp

5

Ecco un modo diverso che richiede solo 2 loop. L'idea è di aumentare il primo elemento e se quel numero supera, aumentare quello successivo.

Invece di visualizzare i dati, puoi usare currentValues.Clone e aggiungere la versione clonata al tuo elenco. Per me questo è andato più velocemente della tua versione.

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());
  • Spero che questo codice funzioni, l'ho convertito da vb

3

Tutti i tuoi numeri sono costanti del tempo di compilazione.

Che ne dici di srotolare tutti i loop in un elenco (usando il tuo programma per scrivere codice):

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.

Questo dovrebbe almeno eliminare l'overhead dei cicli for (se ce ne sono).

Non ho molta familiarità con C #, ma sembra che ci siano alcuni mezzi per serializzare gli oggetti. E se avessi appena generato quell'elenco e lo avessi serializzato in qualche forma? Tuttavia, non sono sicuro che la deserializzazione sia più rapida della creazione dell'elenco e dell'aggiunta degli elementi.


La serializzazione è un ottimo modo di pensare fuori dagli schemi!
Joel B

Purtroppo i massimi nell'elenco sono dinamici, non posso digitarli staticamente. Buona idea però!
benpage

2

È necessario che il risultato sia un array di array? Con la configurazione corrente la lunghezza degli array interni è fissa e potrebbe essere sostituita con strutture. Ciò consentirebbe di riservare l'intera cosa come un blocco continuo di memoria e fornisce un accesso più facile agli elementi (non sono sicuro di come usi questa cosa in seguito).

L'approccio di seguito è molto più veloce (41 ms contro 1071 ms dell'originale sulla mia scatola):

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

Buona idea - in effetti, questo è ciò che ho fatto nel mio progetto nel mondo reale - semplicemente non l'ho inserito nella soluzione originale per semplicità. Stavo principalmente cercando un'alternativa migliore ai loop annidati.
benpage

1

Che ne dici di usarlo Parallel.For()per eseguirlo? (Complimenti per l'ottimizzazione della struttura a @Caramiriel ). Ho leggermente modificato i valori (a è 5 invece di 2) quindi sono più fiducioso nei risultati.

    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() è un metodo privato, definito come:

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

Sul mio sistema, questa versione viene eseguita circa 6 volte più velocemente (1,718 secondi contro 0,266 secondi).


1
Questo è praticamente garantito per darti false condivisioni e probabilmente sarà molte volte più lento.
gjvdkamp

Non male, sfortunatamente funziona più lentamente dei cicli for. FWIW l'ho provato con ALL se Parallel.ForVS si è bloccato!
benpage

@gjvdkamp ho aggiornato la mia risposta con una versione parallela che credo elimini il falso problema di condivisione.
jdphenix

0

Alcuni dei tuoi numeri si adattano interamente a un numero intero di bit, quindi puoi "impacchettarli" con il numero di livello superiore:

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

Ovviamente questo rende il codice meno leggibile, ma hai salvato un ciclo. Questo può essere fatto ogni volta che uno dei numeri è una potenza di due, che è sette volte nel tuo caso.


Vorrei saperne di più su questa risposta: potresti approfondirla?
benpage

Scusa per la risposta tardiva. m va da 0 a 3, che in binario fa da 00 a 11, l da 0 a 2, che fa da 00 a 10, quindi se li stampi separatamente, questo farebbe: 00 00 00 01 00 10 00 11 01 00 .. . e (1100) b quindi spostiamo di due bit per avere il numero "reale". A proposito, mi sono appena reso conto che è sufficiente fare lm >> 2 in questo caso.
Fabien Dupont

0

Ecco un'altra soluzione. Al di fuori di VS funziona alla velocità di 437,5 ms, il 26% più veloce del codice originale (593,7 sul mio computer):

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();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.