Швидша альтернатива вкладеним циклам?


85

Мені потрібно створити список комбінацій чисел. Цифри досить малі, тому я можу byteскоріше використовувати int. Однак для отримання всіх можливих комбінацій потрібно багато вкладених циклів. Мені цікаво, чи є більш ефективний спосіб робити те, що я хочу. Поки що код:

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

Я розглядав можливість використовувати щось на зразок, BitArrayале я не впевнений, як я міг це включити.

Будь-які рекомендації будуть вдячні. Як варіант, можливо, це найшвидший спосіб зробити те, що я хочу?

РЕДАГУВАТИ Кілька коротких пунктів (і вибачення, що я не розміщував їх у оригінальному дописі):

  • Числа та порядок їх (2, 3, 4, 3, 4, 3, 3 тощо) дуже важливі, тому використання такого рішення, як генерація перестановок за допомогою LINQ , не допоможе, оскільки максимуми в кожному "стовпці" дорівнюють інший
  • Я не математик, тому перепрошую, якщо я неправильно використовую такі технічні терміни, як "перестановки" та "комбінації" :)
  • Я дійсно потрібно заповнити всі ці комбінації відразу - я не можу просто взяти один або інший на основі індексу
  • Використання byteшвидше, ніж використання int, я гарантую це. Також набагато краще при використанні пам'яті мати 67 м + масиви байтів, а не ints
  • Моєю кінцевою метою тут є пошук швидшої альтернативи вкладеним циклам.
  • Я розглядав можливість використання паралельного програмування, але через ітеративний характер того, що я намагаюся досягти, я не зміг знайти спосіб зробити це успішно (навіть з ConcurrentBag), проте я радий, що мені довели, що я помиляюся :)

ВИСНОВОК

Caramiriel забезпечила хорошу мікрооптимізацію, яка збриває певний час із циклів, тому я позначив цю відповідь як правильну. Ерік також зазначив, що швидше попередньо розподілити Список. Але на цьому етапі здається, що вкладені цикли насправді є найшвидшим можливим способом зробити це (гнітюче, я знаю!).

Якщо ви хочете спробувати саме те, що я намагався порівняти StopWatch, використовуйте 13 петель, відлічуючи до 4 у кожному циклі - це складає близько 67 м + рядків у списку. На моїй машині (i5-3320M 2,6 ГГц) для оптимізованої версії потрібно близько 2,2 с.


1
Спробуйте скористатися linq і якщо ви використовуєте багатоядерний процесор, то Parrallel.for
Джалпеш Вадгама

1
виходячи з того, що я бачу, це не перестановки, а поєднання декількох дуже малих (2-4 елементи) наборів - це правильно, чи ви дійсно хочете всі / деякі перестановки одного набору?
Карстен

Припускаю, ви вже шукали bing.com/search?q=c%23+permutation+enumerable вже і з якоїсь причини (не згадується в повідомленні) вирішили не відповідати існуючим відповідям, таким як stackoverflow.com/questions/4319049/ ... ... Подумайте про перелік варіанти, які ви переглянули і вирішили не покращувати це питання.
Олексій Левенков

3
Якщо це стосується продуктивності: Ви можете попередньо розподілити список (конструктор) і розгорнути деякі цикли, але я думаю, що про це ... окрім попереднього розрахунку та збереження цих чисел. Петлі (накладні) є, мабуть, найдорожчими з усіх, оскільки всередині тіла відбувається невелика кількість операцій.
Caramiriel

5
@benpage: Чому потрібно генерувати всі комбінації заздалегідь? Чому б не створити комбінацію з її індексу, коли вона вам потрібна?
Пітер Вітвоєт

Відповіді:


61

Ви можете використовувати властивості структури і виділяти структуру заздалегідь. Я відрізав деякі рівні в наведеній нижче вибірці, але впевнений, ви зможете з’ясувати особливості. Працює приблизно в 5-6 разів швидше, ніж оригінал (режим випуску).

Блок:

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

Цикл:

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

Це швидше, оскільки він не виділяє новий список кожного разу, коли ви додаєте його до списку. Також, оскільки він створює цей список, йому потрібно посилання на кожне інше значення (a, b, c, d, e). Ви можете припустити, що кожне значення змінюється лише один раз усередині циклу, тому ми можемо оптимізувати його для цього (локалізація даних).

Також прочитайте коментарі щодо побічних ефектів.

Відредаговано відповідь, щоб використовувати T[]замість a List<T>.


1
Це структура, тому з вами повинно бути добре =) всі вони унікальні. Він копіюється під час виклику List<T>.Addметоду.
Caramiriel

4
Це ще швидше, якщо ви виділите потужність до Списку ()
Ерік,

5
Слідкуйте за винятками stackoverflow, виділяючи занадто багато об’єктів у стеку.
Андрій Татар,

7
@Andrew Я не розумію вашу думку. Цей код не є рекурсивним і має мінімальне використання стека.
CodesInChaos

3
@Andrew: Це не вистачає пам'яті, а не stackoverflow. Це тому, що List<T>.Add()метод виходить за межі того, що він може зберігати. Це зробить його розмір (подвоєним), що перевищує 2 Гб пам’яті. Спробуйте попередньо розподілити за допомогою нового списку <ByteBlock> (maxPerLevel.Aggregate (1, (x, y) => x * y)), хоча це вже `` випадково '', що вам потрібен повний блок 2 ГБ цих даних у пам'яті. Також зверніть увагу, що data.ToArray (); є дорогим, оскільки на той момент він зберігає елементи в пам'яті двічі. [перефразовано]
Caramiriel

33

Те, що ви робите, - це підрахунок (із змінним радіусом, але все одно підрахунок).

Оскільки ви використовуєте C #, я припускаю, що ви не хочете грати з корисним розташуванням пам'яті та структурами даних, які дозволяють вам реально оптимізувати ваш код.

Отже, тут я публікую щось інше, що може не відповідати вашому випадку, але варто зауважити: Якщо ви дійсно отримуєте доступ до списку в скудному вигляді, тут клас, який дозволяє обчислювати i-й елемент за лінійний час (скоріше ніж експоненціальні, як інші відповіді)

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

Ви можете використовувати цей клас таким чином

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

Тепер c[i]така ж , як в списку, назвіть його l, l[i].

Як бачите, ви можете легко уникнути всіх цих петель :) навіть під час попереднього обчислення всього списку в цілому, оскільки ви можете просто реалізувати лічильник Carry-Ripple.

Лічильники - дуже вивчена тема, і я настійно раджу вам пошукати літературу, якщо хочете.


4
Мені подобається ваша відповідь, але твердження, що всі інші відповіді експоненціальні, не відповідає дійсності.
Печиво

1
Яка швидкість у цьому порівняно з відповіддю Карамірієла?
Джон Одом

17
"C-kiddy- #", справді? Це здається абсолютно непокликаним.
KChaloux

2
І це робить: Math.DivRem
Caramiriel

1
Я думаю, що, незважаючи на якийсь рівень, оптимізація є питанням використання. Наприклад, якщо кожен масив використовуватиметься лише один раз, ви можете уникнути інтенсивного розподілу пам'яті, що, на мій погляд, є критичним вузьким місцем. Крім того, якщо ви хочете обчислити все значення, вам слід використати той факт, що ви робите одиничні прирости (тобто +1 приріст), уникаючи поділу. Це більше задумано як "нестандартний" одяг або прототип, я насправді не намагався його пришвидшити, мені просто подобається так :)

14

Спосіб 1

Один із способів зробити це швидшим - вказати потужність, якщо ви плануєте продовжувати користуватися List<byte[]>, наприклад.

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

Спосіб 2

Крім того, ви можете використовувати System.Arrayбезпосередньо для швидшого доступу. Я рекомендую такий підхід, якщо ваше запитання наполягає на тому, щоб кожен елемент фізично заповнювався в пам'яті заздалегідь.

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

На моєму комп’ютері потрібно 596 мс, що приблизно на 10,4% швидше, ніж відповідний код (що займає 658 мс).

Спосіб 3

Крім того, ви можете використовувати наступний прийом для низької вартості ініціалізації, яка підходить для доступу рідко. Це особливо сприятливо, коли можуть знадобитися лише деякі елементи, і їх попереднє визначення вважається непотрібним. Більше того, подібні методи можуть стати єдиним життєздатним варіантом при роботі з більш обширними елементами, коли пам'яті не вистачає.

У цій реалізації кожен елемент залишається визначатися ліниво, на льоту, під час доступу. Звичайно, це пов’язано з додатковим процесором, який виникає під час доступу.

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

На моєму комп’ютері потрібно 897 мс (також створення та додавання до методу,Array як у способі 2 ), що приблизно на 36,3% повільніше, ніж розглянутий код (що займає 658 мс).


1
Ваша друга пропозиція - це також значна економія з точки зору споживання пам'яті. (Але я б зазначив, що передбачається, що перелік не повинен змінюватися)
Темир

Мені потрібен весь список, створений одночасно - я не можу посилатися на індекс у списку.
benpage

@Taemyr Дякую. Я оновлю, щоб зазначити, що відповідно. Якщо реалізація справді наполягає на тому, що у вас весь список заповнений заздалегідь, то цей 3-й варіант, очевидно, не буде працювати для вас.
Печиво

3
@benpage Навіщо потрібен заповнений список?
Темир

14

На моїй машині це генерує комбінації за 222 мс проти 760 мс (13 для циклів):

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

Це чудова відповідь! На жаль, він працює повільніше, ніж вкладені цикли. Будь-який шанс ви могли редагувати за допомогою TPL?
benpage

все-таки трохи повільніше, на жаль.
benpage

1
@benpage Існує простий спосіб зробити це принаймні в 2 рази швидшим. Вам просто потрібно змінити тип результатів на int [,]. Це виділить всю пам’ять масиву за один виклик. Я не впевнений, як це відповідає вашим потребам (зміна типу повернення).
Андрій Татар,

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

Використовуючи метод розширення за адресою 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
це працює набагато повільніше :(
benpage

8

Список має внутрішній масив, де він зберігає свої значення з фіксованою довжиною. Коли ви викликаєте List.Add, він перевіряє, чи достатньо місця. Коли він не може додати новий елемент, він створить новий масив більшого розміру, скопіює всі попередні елементи, а потім додасть, а потім новий. Це займає досить багато циклів.

Оскільки ви вже знаєте кількість елементів, ви можете створити список правильного розміру, який вже повинен бути набагато швидшим.

Крім того, не впевнений, як ви отримуєте доступ до значень, але ви можете створити цю річ і зберегти зображення в коді (завантаження його з диска, ймовірно, буде повільнішим, ніж те, що ви робите зараз. Скільки разів ви читаєте / пишете до цього річ?


Я насправді намагався попередньо розподілити звичайний масив і вірите чи ні, це повільніше. Як я вже говорив вище, це потрібно створювати на льоту, я не можу обчислити один раз і залишити це.
benpage

справді? вау - ви працюєте з увімкненою оптимізацією, так? (просто запитую)
Карстен

А, це ще одна проблема, регулярні масиви [x, y] приємно використовувати, але масив масивів буде швидшим. stackoverflow.com/questions/597720/… через те, як вони реалізовані під капотом в Іллінойсі
gjvdkamp

5

Ось інший спосіб, який потребує лише 2 циклу. Ідея полягає в тому, щоб збільшити перший елемент, і якщо це число перевищить, то збільшити наступний.

Замість відображення даних ви можете використовувати currentValues.Clone та додати клоновану версію до свого списку. Для мене це працювало швидше, ніж ваша версія.

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());
  • Сподіваюся, цей код працює, я перетворив його з vb

3

Всі ваші числа складають постійну часу компіляції.

А як щодо розгортання всіх циклів у списку (за допомогою вашої програми для написання коду):

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.

Це має принаймні забрати накладні витрати на петлі for (якщо такі є).

Я не надто знайомий з C #, але, схоже, є деякі засоби серіалізації об'єктів. Що робити, якщо ви просто створили цей Список і серіалізували його в якійсь формі? Я не впевнений, що десеріалізація відбувається швидше, ніж створення Списку та додавання елементів.


Серіалізація - це справді чудове мислення поза рамками!
Джоел Б,

На жаль, максимум у списку динамічний, я не можу статично ввести це. Хороша ідея!
benpage

2

Вам потрібен результат, щоб це був масив масивів? При поточній установці довжина внутрішніх масивів фіксована і може бути замінена конструкціями. Це дозволило б зарезервувати всю річ як один суцільний блок пам’яті та забезпечило легший доступ до елементів (не впевнений, як ви використовуєте цю річ латерон).

Підхід нижче набагато швидший (41 мс проти 1071 мс для оригіналу на моїй коробці):

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

Хороша ідея - насправді, це власне те, що я зробив у своєму реальному проекті - я просто не помістив його в оригінальне рішення через простоту. Я в основному шукав кращої альтернативи вкладеним циклам.
benpage

1

А як щодо використання Parallel.For()для його запуску? (Структура оптимізації для @Caramiriel ). Я трохи змінив значення (а дорівнює 5 замість 2), тому я впевненіший у результатах.

    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() є приватним методом, який визначається як:

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

У моїй системі ця версія працює приблизно в 6 разів швидше (1,718 секунди проти 0,266 секунди).


1
Це майже гарантовано дасть вам помилковий обмін і, ймовірно, буде в рази повільнішим.
gjvdkamp

Непогано - на жаль, він працює повільніше, ніж цикли for. FWIW Я спробував це з ALL Parallel.Fors і VS розбився!
benpage

@gjvdkamp Я оновив свою відповідь паралельною версією, яка, на мою думку, усуває проблему помилкового обміну.
jdphenix

0

Деякі з ваших чисел цілком вміщуються в ціле число бітів, тому ви можете "упакувати" їх номером верхнього рівня:

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

Звичайно, це робить код менш читабельним, але ви зберегли один цикл. Це можна зробити кожного разу, коли одне з чисел має ступінь двійки, що у вашому випадку становить сім разів.


Я хотів би дізнатись більше про цю відповідь - чи можете ви розширити її?
benpage

Вибачте за пізню відповідь. m переходить від 0 до 3, що в двійковому вигляді складає 00 до 11, l від 0 до 2, що робить від 00 до 10, тому, якщо ви друкуєте їх окремо, це буде робити: 00 00 00 01 00 10 00 11 01 00 .. . 10 11 Ви можете об'єднати їх у єдину кількість 4 бітів, переходячи від 0000 до 1011, і вибрати відповідні біти за допомогою маски lm & 3 робить двобіс і між lm та (11) b lm & 12 робить те саме з lm і (1100) b, тоді ми зміщуємо на два біти, щоб отримати "справжнє" число. До речі, щойно зрозумів, що в цьому випадку досить зробити lm >> 2.
Fabien Dupont

0

Ось ще одне рішення. Поза VS він працює лише за 437,5 мс, що на 26% швидше, ніж вихідний код (593,7 на моєму комп'ютері):

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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.