Thay thế nhanh hơn cho các vòng lặp lồng nhau?


85

Mình có nhu cầu tạo danh sách tổ hợp các số. Số lượng khá nhỏ nên tôi có thể sử dụng bytehơn là int. Tuy nhiên, nó yêu cầu nhiều vòng lặp lồng nhau để có được mọi kết hợp có thể. Tôi tự hỏi liệu có cách nào hiệu quả hơn để làm những gì tôi đang theo đuổi. Mã cho đến nay là:

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

Tôi đã xem xét sử dụng một cái gì đó giống như một BitArraynhưng tôi không chắc mình có thể kết hợp nó như thế nào.

Bất kỳ khuyến nghị sẽ được đánh giá rất cao. Ngoài ra, có lẽ đây là cách nhanh nhất để làm những gì tôi muốn?

CHỈNH SỬA Vài điểm nhanh (và xin lỗi tôi đã không đưa những điểm này vào bài viết gốc):

  • Các con số và thứ tự của chúng (2, 3, 4, 3, 4, 3, 3, v.v.) rất quan trọng, vì vậy việc sử dụng một giải pháp như Tạo hoán vị bằng LINQ sẽ không hữu ích vì số tối đa trong mỗi 'cột' là khác nhau
  • Tôi không phải là nhà toán học, vì vậy tôi xin lỗi nếu tôi không sử dụng các thuật ngữ kỹ thuật như 'hoán vị' và 'kết hợp' một cách chính xác :)
  • Tôi làm cần phải cư tất cả các kết hợp cùng một lúc - Tôi không thể chỉ cần lấy một hay cách khác dựa trên một chỉ số
  • Sử dụng bytenhanh hơn sử dụng int, tôi đảm bảo điều đó. Việc sử dụng bộ nhớ cũng tốt hơn rất nhiều khi có hơn 67 triệu mảng byte thay vì mảng int
  • Mục tiêu cuối cùng của tôi ở đây là tìm kiếm một giải pháp thay thế nhanh hơn cho các vòng lặp lồng nhau.
  • Tôi đã cân nhắc sử dụng lập trình song song, nhưng do tính chất lặp đi lặp lại của những gì tôi đang cố gắng đạt được, tôi không thể tìm ra cách thực hiện thành công (ngay cả với ConcurrentBag) - tuy nhiên tôi rất vui khi được chứng minh là sai :)

PHẦN KẾT LUẬN

Caramiriel đã cung cấp một tối ưu hóa vi mô tốt giúp loại bỏ một số thời gian khỏi các vòng lặp, vì vậy tôi đã đánh dấu câu trả lời đó là đúng. Eric cũng đề cập rằng việc phân bổ trước Danh sách sẽ nhanh hơn. Tuy nhiên, ở giai đoạn này, có vẻ như các vòng lặp lồng nhau trên thực tế là cách nhanh nhất có thể để thực hiện việc này (thật đáng buồn, tôi biết!).

Nếu bạn muốn thử chính xác những gì tôi đang cố gắng làm điểm chuẩn StopWatch, hãy sử dụng 13 vòng lặp đếm tối đa 4 trong mỗi vòng lặp - điều đó tạo ra khoảng hơn 67 triệu dòng trong danh sách. Trên máy của tôi (i5-3320M 2,6GHz), mất khoảng 2,2 giây để thực hiện phiên bản tối ưu hóa.


1
Cố gắng sử dụng LINQ và Nếu bạn đang sử dụng bộ vi xử lý đa lõi sau đó Parrallel.for
Jalpesh Vadgama

1
dựa trên những gì tôi thấy đây không phải là hoán vị mà là sự kết hợp của một vài tập hợp rất nhỏ (2-4 phần tử) là đúng hay bạn thực sự muốn tất cả / một số hoán vị của một tập hợp?
Carsten

Tôi cho rằng bạn đã tìm kiếm bing.com/search?q=c%23+permutation+enumerable rồi và vì một số lý do (không được đề cập trong bài đăng) đã quyết định chống lại các câu trả lời hiện có như stackoverflow.com/questions/4319049/… ... Hãy xem xét danh sách các tùy chọn bạn đã xem xét và quyết định chống lại để làm cho câu hỏi này tốt hơn.
Alexei Levenkov

3
Nếu đây là về hiệu suất: Bạn có thể định vị trước danh sách (hàm tạo) và hủy cuộn một số vòng lặp, nhưng tôi nghĩ đó là về nó ... ngoài việc tính toán trước và lưu trữ những con số này. Các vòng lặp (chi phí) có lẽ là tốn kém nhất trong số chúng, vì có một số lượng thấp các hoạt động bên trong cơ thể.
Caramiriel

5
@benpage: Tại sao bạn cần tạo tất cả các kết hợp từ trước? Tại sao không tạo kết hợp từ chỉ mục của nó khi bạn cần?
Pieter Witvoet

Câu trả lời:


61

Bạn có thể sử dụng các thuộc tính của một cấu trúc và phân bổ cấu trúc trước. Tôi đã cắt bỏ một số cấp độ trong mẫu bên dưới, nhưng tôi chắc chắn rằng bạn sẽ có thể tìm ra các chi tiết cụ thể. Chạy nhanh hơn khoảng 5-6 lần so với ban đầu (chế độ nhả).

Khối:

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

Vòng lặp:

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

Nó nhanh hơn vì nó không phân bổ danh sách mới mỗi khi bạn thêm nó vào danh sách. Cũng vì nó đang tạo danh sách này, nó cần tham chiếu đến mọi giá trị khác (a, b, c, d, e). Bạn có thể giả định rằng mỗi giá trị chỉ được sửa đổi một lần bên trong vòng lặp, vì vậy chúng tôi có thể tối ưu hóa nó để làm như vậy (cục bộ dữ liệu).

Cũng đọc các bình luận để biết tác dụng phụ.

Đã chỉnh sửa câu trả lời để sử dụng một T[]thay vì a List<T>.


1
Nó là một cấu trúc, vì vậy bạn sẽ không sao =) tất cả chúng đều là duy nhất. Nó được sao chép khi gọi List<T>.Addphương thức.
Caramiriel

4
Nó thậm chí còn nhanh hơn nếu bạn đã phân bổ dung lượng cho Danh sách ()
Eric

5
Đề phòng các trường hợp ngoại lệ stackoverflow khi phân bổ quá nhiều đối tượng trên ngăn xếp.
Andrei Tătar

7
@Andrew Tôi không hiểu ý bạn. Mã này không đệ quy và có mức sử dụng ngăn xếp tối thiểu.
CodesInChaos

3
@Andrew: Hết bộ nhớ, không phải stackoverflow. Điều này là do List<T>.Add()phương pháp này vượt ra ngoài quan điểm của những gì nó có thể lưu trữ. Điều này sẽ làm cho nó thay đổi kích thước (tăng gấp đôi kích thước), chiếm hơn 2GB bộ nhớ. Hãy thử phân bổ trước bằng cách sử dụng Danh sách mới <ByteBlock> (maxPerLevel.Aggregate (1, (x, y) => x * y)), mặc dù nó đã 'ngẫu nhiên' là bạn cần khối dữ liệu này đầy đủ 2GB trong bộ nhớ. Cũng lưu ý rằng data.ToArray (); đắt vì nó giữ các mục trong bộ nhớ gấp đôi tại thời điểm đó. [diễn đạt lại]
Caramiriel

33

Những gì bạn đang làm là đếm (với cơ số thay đổi, nhưng vẫn đếm).

Vì bạn đang sử dụng C #, tôi cho rằng bạn không muốn chơi với bố cục bộ nhớ hữu ích và cấu trúc dữ liệu cho phép bạn thực sự tối ưu hóa mã của mình.

Vì vậy, ở đây tôi đăng một cái gì đó khác, có thể không phù hợp với trường hợp của bạn, nhưng cần lưu ý: Trong trường hợp bạn thực sự truy cập danh sách theo kiểu thưa thớt, đây là một lớp cho phép bạn tính toán phần tử thứ i trong thời gian tuyến tính (đúng hơn hơn theo cấp số nhân như các câu trả lời khác)

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

Bạn có thể sử dụng lớp này theo cách này

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

hiện nay c[i]cũng giống như danh sách của bạn, đặt tên cho nó l, l[i].

Như bạn có thể thấy, bạn có thể dễ dàng tránh tất cả các vòng lặp đó :) ngay cả khi bạn tính toán trước tất cả danh sách vì bạn có thể đơn giản triển khai bộ đếm Carry-Ripple.

Bộ đếm là một chủ đề được nghiên cứu rất nhiều, tôi thực sự khuyên bạn nên tìm kiếm một số tài liệu nếu bạn cảm thấy.


4
Tôi thích câu trả lời của bạn, nhưng nói rằng tất cả các câu trả lời khác là cấp số nhân là sai sự thật.
Bánh quy

1
Tốc độ trên này là bao nhiêu so với câu trả lời của Caramiriel?
John Odom

17
"C-kiddy- #", thật không? Điều đó dường như hoàn toàn không được mong đợi.
KChaloux

2
Và nó thực hiện: Math.DivRem
Caramiriel

1
Tôi nghĩ rằng ở một mức độ nào đó, Tối ưu hóa là một vấn đề sử dụng. Ví dụ, nếu mỗi mảng chỉ được sử dụng một lần, bạn có thể tránh được việc cấp phát bộ nhớ nhiều, đây là điểm nghẽn quan trọng theo quan điểm của tôi. Hơn nữa, nếu bạn muốn tính toán tất cả giá trị, bạn nên khai thác thực tế là bạn thực hiện các bước tăng đơn lẻ (tức là số gia +1) để tránh một phép chia. Này được nhiều dự định như là một "out of the box" answear hoặc một nguyên mẫu, tôi không thật sự cố gắng để tăng tốc độ nó lên, tôi giống như nó theo cách này :)

14

Phương pháp 1

Một cách để làm cho nó nhanh hơn là chỉ định dung lượng nếu bạn định tiếp tục sử dụng List<byte[]>, như thế này.

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

Phương pháp 2

Hơn nữa, bạn có thể sử dụng System.Arraytrực tiếp để truy cập nhanh hơn. Tôi khuyên bạn nên tiếp cận cách tiếp cận này nếu câu hỏi của bạn khẳng định rằng mọi phần tử được điền từ trước trong bộ nhớ.

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

Quá trình này mất 596 mili giây để hoàn thành trên máy tính của tôi, nhanh hơn khoảng 10,4% so với mã được đề cập (mất 658 mili giây).

Phương pháp 3

Ngoài ra, bạn có thể sử dụng kỹ thuật sau để khởi tạo chi phí thấp phù hợp với truy cập ít. Điều này đặc biệt thuận lợi khi có thể chỉ cần một số yếu tố và xác định trước tất cả chúng được coi là không cần thiết. Hơn nữa, các kỹ thuật như thế này có thể trở thành lựa chọn khả thi duy nhất khi làm việc với các phần tử lớn hơn khi bộ nhớ thiếu.

Trong quá trình triển khai này, mọi phần tử sẽ được xác định một cách lười biếng, nhanh chóng, khi truy cập. Đương nhiên, điều này đi kèm với chi phí CPU bổ sung phát sinh trong quá trình truy cập.

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

Quá trình này mất 897 mili giây để hoàn thành trên máy tính của tôi (cũng tạo và thêm vào một Arraynhư trong Phương pháp 2 ), chậm hơn khoảng 36,3% so với mã được đề cập (mất 658 mili giây).


1
Gợi ý thứ hai của bạn cũng là một sự tiết kiệm đáng kể về tiêu thụ bộ nhớ. (Nhưng tôi xin lưu ý rằng nó giả định rằng danh sách không nên thay đổi)
Taemyr

Tôi cần toàn bộ danh sách được tạo cùng một lúc - Tôi không thể tham chiếu đến chỉ mục trong danh sách.
benpage

@Taemyr Cảm ơn. Tôi sẽ cập nhật để lưu ý rằng phù hợp. Nếu việc triển khai thực sự khẳng định rằng bạn có toàn bộ danh sách được điền từ trước, thì tùy chọn thứ 3 này rõ ràng sẽ không phù hợp với bạn.
Bánh quy

3
@benpage Tại sao bạn cần điền danh sách?
Taemyr

14

Trên máy của tôi, điều này tạo ra các kết hợp trong 222 mili giây so với 760 mili giây (13 vòng lặp 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;
}

Đây là một câu trả lời tuyệt vời! Thật không may là nó chạy chậm hơn các vòng lặp lồng nhau. Bạn có thể chỉnh sửa bằng TPL không?
benpage

Rất tiếc là vẫn còn chậm hơn một chút.
benpage

1
@benpage Có một cách dễ dàng để làm cho nó nhanh hơn ít nhất 2 lần. Bạn chỉ cần thay đổi kiểu kết quả thành int [,]. Điều này sẽ cấp phát toàn bộ bộ nhớ mảng trong một lần gọi. Tôi không chắc điều đó phù hợp với nhu cầu của bạn như thế nào (thay đổi kiểu trả hàng).
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();

Sử dụng phương thức mở rộng tại 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
cái này chạy chậm hơn rất nhiều :(
benpage

8

Danh sách có một mảng bên trong nơi nó lưu trữ các giá trị, với độ dài cố định. Khi bạn gọi List.Add, nó sẽ kiểm tra xem có đủ dung lượng không. Khi không thể thêm phần tử mới, nó sẽ tạo một mảng mới có kích thước lớn hơn, sao chép tất cả các phần tử trước đó, sau đó thêm phần tử mới. Điều này mất một vài chu kỳ.

Vì bạn đã biết số lượng phần tử, bạn có thể tạo danh sách có kích thước chính xác, sẽ nhanh hơn rất nhiều.

Ngoài ra, không chắc bạn truy cập các giá trị như thế nào, nhưng bạn có thể tạo thứ này và lưu hình ảnh trong mã (tải nó từ đĩa có thể sẽ chậm hơn so với những gì bạn đang làm. Bạn đọc / ghi nó bao nhiêu lần Điều?


Tôi thực sự đã thử phân bổ trước một mảng thông thường và tin hay không thì tùy, nó chậm hơn. Như tôi đã nói ở trên, cái này cần được tạo ngay lập tức, tôi không thể tính toán một lần rồi bỏ.
benpage

có thật không? wow - bạn đang chạy với tối ưu hóa được bật phải không? (chỉ hỏi)
Carsten

À đó là một vấn đề khác, các mảng thông thường [x, y] rất dễ sử dụng nhưng một mảng nhiều mảng sẽ nhanh hơn. stackoverflow.com/questions/597720/... vì cách họ đang implemeted dưới mui xe trong IL
gjvdkamp

5

Đây là một cách khác mà chỉ cần 2 vòng lặp. Ý tưởng là tăng phần tử đầu tiên và nếu con số đó tăng hơn so với tăng phần tử tiếp theo.

Thay vì hiển thị dữ liệu, bạn có thể sử dụng currentValues.Clone và thêm phiên bản sao chép đó vào danh sách của mình. Đối với tôi điều này chạy nhanh hơn phiên bản của bạ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());
  • Hy vọng mã này hoạt động, tôi đã chuyển đổi nó từ vb

3

Tất cả các số của bạn là hằng số thời gian biên dịch.

Điều gì về việc hủy cuộn tất cả các vòng vào một danh sách (sử dụng chương trình của bạn để viết mã):

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.

Điều đó ít nhất sẽ loại bỏ chi phí của các vòng lặp for (nếu có).

Tôi không quen thuộc với C # quá nhiều, nhưng dường như có một số phương tiện để tuần tự hóa các đối tượng. Điều gì sẽ xảy ra nếu bạn vừa tạo Danh sách đó và tuần tự hóa nó dưới một số hình thức? Tuy nhiên, tôi không chắc liệu quá trình giải mã hóa có nhanh hơn khi tạo Danh sách và thêm các phần tử hay không.


Serialization là một tư duy thực sự tuyệt vời bên ngoài phương pháp tiếp cận hộp!
Joel B

Rất tiếc, giá trị tối đa trong danh sách là động, tôi không thể nhập tĩnh. Ý tưởng tốt mặc dù!
benpage

2

Bạn có cần kết quả là một mảng của mảng không? Với thiết lập hiện tại, độ dài của các mảng bên trong là cố định và có thể được thay thế bằng các cấu trúc. Điều này sẽ cho phép toàn bộ thứ được dự trữ dưới dạng một khối bộ nhớ liên tục và cung cấp khả năng truy cập dễ dàng hơn vào các phần tử (không chắc bạn sử dụng thứ này như thế nào lateron).

Cách tiếp cận bên dưới nhanh hơn nhiều (41ms so với 1071ms đối với bản gốc trên hộp của tôi):

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

Ý tưởng hay - trên thực tế, đó thực sự là những gì tôi đã làm trong dự án thế giới thực của mình - tôi chỉ không đưa nó vào giải pháp ban đầu vì đơn giản. Tôi chủ yếu tìm kiếm một giải pháp thay thế tốt hơn cho các vòng lặp lồng nhau.
benpage

1

Điều gì về việc sử dụng Parallel.For()để chạy nó? (Kudo tối ưu hóa cấu trúc thành @Caramiriel ). Tôi đã sửa đổi một chút các giá trị (a là 5 thay vì 2) để tôi tự tin hơn vào kết quả.

    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() là một phương thức riêng tư, được định nghĩa là:

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

Trên hệ thống của tôi, phiên bản này chạy nhanh hơn khoảng 6 lần (1,718 giây so với 0,266 giây).


1
Điều này được đảm bảo khá nhiều để cung cấp cho bạn chia sẻ sai và có thể sẽ chậm hơn nhiều lần.
gjvdkamp

Không tồi - tiếc là nó chạy chậm hơn vòng lặp for. FWIW Tôi đã thử nó với TẤT CẢ Parallel.Forvà VS bị rơi!
benpage

@gjvdkamp Tôi đã cập nhật câu trả lời của mình bằng một phiên bản song song mà tôi tin rằng loại bỏ được vấn đề chia sẻ sai.
jdphenix

0

Một số số của bạn hoàn toàn phù hợp với một số nguyên bit, vì vậy bạn có thể "đóng gói" chúng bằng số cấp trên:

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

Tất nhiên, điều này làm cho mã khó đọc hơn, nhưng bạn đã lưu một vòng lặp. Điều này có thể được thực hiện mỗi khi một trong các số là lũy thừa của hai, trong trường hợp của bạn là bảy lần.


Tôi muốn biết thêm về câu trả lời này - bạn có thể mở rộng về nó không?
benpage

Xin lỗi vì trả lời muộn. m đi từ 0 đến 3, trong hệ nhị phân tạo thành 00 thành 11, l từ 0 thành 2, làm cho 00 thành 10, vì vậy nếu bạn in chúng riêng biệt, điều này sẽ tạo thành: 00 00 00 01 00 10 00 11 01 00 .. . 10 11 Bạn có thể ghép chúng lại với nhau thành một số 4 bit, đi từ 0000 đến 1011 và chọn các bit thích hợp bằng cách sử dụng mặt nạ lm & 3 làm cho hai chiều và giữa lm và (11) b lm & 12 làm cho tương tự với lm và (1100) b thì chúng ta dịch chuyển hai bit để có số "thực". Nhân tiện, chỉ cần nhận ra rằng thật tuyệt vời để làm lm >> 2 trong trường hợp này.
Fabien Dupont

0

Đây là một giải pháp khác. Bên ngoài VS, nó chạy nhanh tới 437,5 ms, nhanh hơn 26% so với mã gốc (593,7 trên máy tính của tôi):

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();
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.