ทางเลือกอื่นที่เร็วกว่าสำหรับลูปแบบซ้อนกัน?


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

ฉันกำลังพิจารณาใช้บางอย่างเช่น a BitArrayแต่ฉันไม่แน่ใจว่าจะรวมเข้าด้วยกันได้อย่างไร

คำแนะนำใด ๆ จะได้รับการชื่นชมอย่างมาก หรือบางทีนี่อาจเป็นวิธีที่เร็วที่สุดในการทำสิ่งที่ฉันต้องการ?

แก้ไข ประเด็นด่วนสองสามข้อ (และขออภัยที่ฉันไม่ได้ใส่ไว้ในโพสต์ต้นฉบับ):

  • ตัวเลขและลำดับ (2, 3, 4, 3, 4, 3, 3 ฯลฯ ) มีความสำคัญมากดังนั้นการใช้วิธีแก้ปัญหาเช่นการสร้างการเรียงสับเปลี่ยนโดยใช้ LINQจะไม่ช่วยได้เนื่องจากค่าสูงสุดในแต่ละคอลัมน์คือ แตกต่างกัน
  • ฉันไม่ใช่นักคณิตศาสตร์ดังนั้นฉันต้องขออภัยหากฉันไม่ได้ใช้คำศัพท์ทางเทคนิคเช่น 'การเรียงสับเปลี่ยน' และ 'ชุดค่าผสม' อย่างถูกต้อง :)
  • ผมไม่จำเป็นต้องเติมทั้งหมดของชุดเหล่านี้ในครั้งเดียว - ฉันไม่สามารถเพียงแค่คว้าหนึ่งหรืออีกขึ้นอยู่กับดัชนี
  • ใช้byteเร็วกว่าใช้intผมรับประกันได้ นอกจากนี้ยังดีกว่ามากในการใช้หน่วยความจำที่มีอาร์เรย์ 67m + ของไบต์แทนที่จะเป็น ints
  • เป้าหมายสูงสุดของฉันคือการมองหาทางเลือกอื่นที่เร็วกว่าสำหรับการวนซ้ำแบบซ้อนกัน
  • ฉันพิจารณาใช้การเขียนโปรแกรมแบบขนาน แต่เนื่องจากลักษณะการทำซ้ำของสิ่งที่ฉันพยายามจะบรรลุฉันจึงไม่พบวิธีที่จะทำให้สำเร็จได้ (แม้จะมีConcurrentBag) - อย่างไรก็ตามฉันยินดีที่ได้รับการพิสูจน์ว่าผิด :)

บทสรุป

Caramiriel ได้จัดเตรียมการเพิ่มประสิทธิภาพไมโครที่ดีซึ่งช่วยลดเวลาในการวนซ้ำดังนั้นฉันจึงทำเครื่องหมายคำตอบนั้นว่าถูกต้อง Eric ยังกล่าวว่าการจัดสรรรายการล่วงหน้าเร็วกว่า แต่ในขั้นตอนนี้ดูเหมือนว่าลูปที่ซ้อนกันเป็นวิธีที่เร็วที่สุดในการทำสิ่งนี้ (น่าหดหู่ฉันรู้!)

หากคุณต้องการลองสิ่งที่ฉันพยายามเปรียบเทียบด้วยStopWatchให้ไปกับ 13 ลูปที่นับได้ถึง 4 ในแต่ละลูปซึ่งทำให้ประมาณ 67m + เส้นในรายการ ในเครื่องของฉัน (i5-3320M 2.6GHz) ใช้เวลาประมาณ 2.2 วินาทีในการทำเวอร์ชันที่ปรับให้เหมาะสม


1
ลองใช้ linq และถ้าคุณใช้โปรเซสเซอร์มัลติคอร์ให้ใช้ Parrallel สำหรับ
Jalpesh Vadgama

1
จากสิ่งที่ฉันเห็นสิ่งเหล่านี้ไม่ใช่การเรียงสับเปลี่ยน แต่การรวมกันของชุดเล็ก ๆ สองสามชุด (2-4 องค์ประกอบ) นั้นถูกต้องหรือคุณต้องการการเรียงสับเปลี่ยนทั้งหมด / บางส่วนของชุดเดียว ?
Carsten

ฉันคิดว่าคุณได้ค้นหาbing.com/search?q=c%23+permutation+enumerableแล้วและด้วยเหตุผลบางประการ (ไม่ได้กล่าวถึงในโพสต์) ได้ตัดสินใจกับคำตอบที่มีอยู่เช่นstackoverflow.com/questions/4319049/… ... พิจารณารายชื่อ ตัวเลือกที่คุณดูและตัดสินใจไม่เห็นด้วยเพื่อทำให้คำถามนี้ดีขึ้น
Alexei Levenkov

3
หากนี่เกี่ยวกับประสิทธิภาพ: คุณสามารถจัดสรรรายการล่วงหน้า (ตัวสร้าง) และคลายลูปบางส่วนได้ แต่ฉันคิดว่าเกี่ยวกับเรื่องนี้ ... นอกเหนือจากการคำนวณล่วงหน้าและการจัดเก็บตัวเลขเหล่านี้ ลูป (เหนือศีรษะ) น่าจะมีราคาแพงที่สุดในบรรดาทั้งหมดเนื่องจากมีการดำเนินการภายในร่างกายจำนวนน้อย
Caramiriel

5
@benpage: ทำไมคุณต้องสร้างชุดค่าผสมทั้งหมดขึ้นด้านหน้า? ทำไมไม่สร้างชุดค่าผสมจากดัชนีเมื่อคุณต้องการ?
Pieter Witvoet

คำตอบ:


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[]แทนList<T>.


1
มันเป็นโครงสร้างดังนั้นคุณควรจะโอเค =) มันไม่เหมือนใคร มันถูกคัดลอกเมื่อเรียกใช้List<T>.Addเมธอด
Caramiriel

4
เร็วยิ่งขึ้นถ้าคุณจัดสรรความจุให้กับรายการ ()
Eric

5
ระวังข้อยกเว้นของstackoverflowเมื่อจัดสรรวัตถุมากเกินไปในสแตก
Andrei Tătar

7
@ แอนดรูฉันไม่เข้าใจประเด็นของคุณ รหัสนี้ไม่เรียกซ้ำและมีการใช้งานสแต็กน้อยที่สุด
CodesInChaos

3
@ แอนดรูว์: นั่นคือหน่วยความจำไม่เพียงพอไม่ใช่สแต็กโอเวอร์โฟลว์ นี่เป็นเพราะList<T>.Add()วิธีการไปไกลกว่าจุดที่สามารถจัดเก็บได้ สิ่งนี้จะทำให้มันปรับขนาดได้ (เพิ่มเป็นสองเท่า) ซึ่งจะมีหน่วยความจำมากกว่า 2GB ลองจัดสรรล่วงหน้าโดยใช้ List <ByteBlock> ใหม่ (maxPerLevel.Aggregate (1, (x, y) => x * y)) แม้ว่าจะเป็นแบบ 'สุ่ม' อยู่แล้วซึ่งคุณต้องการบล็อก 2GB เต็มของข้อมูลนี้ในหน่วยความจำ โปรดทราบว่า data ToArray (); มีราคาแพงเนื่องจากเก็บรายการไว้ในหน่วยความจำสองครั้ง ณ จุดนั้น [rephrased]
Caramiriel

33

สิ่งที่คุณกำลังทำคือการนับ (ด้วยตัวแปรรัศมี แต่ยังคงนับอยู่)

เนื่องจากคุณกำลังใช้ C # ผมถือว่าคุณไม่ต้องการที่จะเล่นกับประโยชน์รูปแบบหน่วยความจำและโครงสร้างข้อมูลที่ช่วยให้คุณจริงๆเพิ่มประสิทธิภาพของรหัสของคุณ

ที่นี่ฉันกำลังโพสต์สิ่งที่แตกต่างออกไปซึ่งอาจไม่เหมาะกับกรณีของคุณ แต่ก็น่าสังเกต: ในกรณีที่คุณเข้าถึงรายการแบบกระจัดกระจายนี่คือคลาสที่ให้คุณคำนวณองค์ประกอบ i-th ในเวลาเชิงเส้น (แทน กว่าเลขชี้กำลังเป็นคำตอบอื่น ๆ )

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]เป็นเช่นเดียวกับรายการของคุณชื่อมัน,ll[i]

อย่างที่คุณเห็นคุณสามารถหลีกเลี่ยงลูปเหล่านั้นได้อย่างง่ายดาย :) แม้ว่าคุณจะคำนวณรายการทั้งหมดล่วงหน้าทั้งหมดเนื่องจากคุณสามารถใช้ตัวนับ Carry-Ripple ได้

ตัวนับเป็นหัวข้อที่มีการศึกษามากฉันขอแนะนำอย่างยิ่งให้ค้นหาวรรณกรรมบางเรื่องหากคุณรู้สึก


4
ฉันชอบคำตอบของคุณ แต่การบอกว่าคำตอบอื่น ๆ ทั้งหมดเป็นเลขชี้กำลังนั้นไม่เป็นความจริง
บิสกิต

1
ความเร็วของสิ่งนี้เป็นเท่าใดเมื่อเทียบกับคำตอบของ Caramiriel?
John Odom

17
"C-kiddy- #" จริงหรือ? ดูเหมือนจะไม่มีเหตุผลเลย
KChaloux

2
และมันก็ทำ: Math.DivRem
Caramiriel

1
ฉันคิดว่าในระดับหนึ่งการเพิ่มประสิทธิภาพเป็นเรื่องของการใช้งาน ตัวอย่างเช่นหากต้องใช้ทุกอาร์เรย์เพียงครั้งเดียวคุณสามารถหลีกเลี่ยงการจัดสรรหน่วยความจำที่เข้มข้นซึ่งเป็นปัญหาคอขวดที่สำคัญในความคิดของฉัน นอกจากนี้หากคุณต้องการคำนวณมูลค่าทั้งหมดคุณควรใช้ประโยชน์จากข้อเท็จจริงที่ว่าคุณเพิ่มทีละครั้ง (เช่นเพิ่มทีละ +1) โดยหลีกเลี่ยงการหาร นี่มีจุดประสงค์เพื่อเป็น answear "นอกกรอบ" หรือเป็นเครื่องต้นแบบฉันไม่ได้พยายามเร่งความเร็ว แต่ชอบวิธีนี้ :)

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 ms ในคอมพิวเตอร์ของฉันซึ่งเร็วกว่ารหัสที่เป็นปัญหาประมาณ10.4% (ซึ่งใช้เวลา 658ms)

วิธีที่ 3

หรือคุณสามารถใช้เทคนิคต่อไปนี้สำหรับการเริ่มต้นต้นทุนต่ำที่เหมาะสมกับการเข้าถึงแบบเบาบาง สิ่งนี้เป็นประโยชน์อย่างยิ่งเมื่ออาจจำเป็นต้องใช้เพียงบางองค์ประกอบและการพิจารณาล่วงหน้าทั้งหมดถือว่าไม่จำเป็น ยิ่งไปกว่านั้นเทคนิคเช่นนี้อาจกลายเป็นทางเลือกเดียวที่ใช้งานได้เมื่อทำงานกับองค์ประกอบที่กว้างใหญ่กว่าเมื่อหน่วยความจำทำงานสั้น

ในการนำไปใช้งานนี้ทุกองค์ประกอบจะถูกปล่อยให้ถูกกำหนดอย่างเกียจคร้านในทันทีเมื่อเข้าถึง โดยปกติแล้วจะมีค่าใช้จ่ายของ CPU เพิ่มเติมที่เกิดขึ้นระหว่างการเข้าถึง

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
คำแนะนำที่สองของคุณยังช่วยประหยัดได้อย่างมากในแง่ของการใช้หน่วยความจำ (แต่ฉันจะทราบว่าสมมติว่ารายการไม่ควรเปลี่ยนแปลง)
Taemyr

ฉันต้องการสร้างรายการทั้งหมดในครั้งเดียว - ฉันไม่สามารถอ้างถึงดัชนีภายในรายการได้
benpage

@Taemyr ขอบคุณ ฉันจะอัปเดตเพื่อทราบว่าตามนั้น หากการติดตั้งใช้งานจริงยืนยันว่าคุณมีรายการทั้งหมดที่แสดงไว้ล่วงหน้าตัวเลือกที่ 3 นี้จะไม่เหมาะกับคุณ
บิสกิต

3
@benpage ทำไมคุณถึงต้องมีรายชื่ออยู่?
Taemyr

14

ในเครื่องของฉันสิ่งนี้จะสร้างชุดค่าผสมใน 222 ms เทียบกับ 760 ms (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 [,] สิ่งนี้จะจัดสรรหน่วยความจำอาร์เรย์ทั้งหมดในการโทรครั้งเดียว ฉันไม่แน่ใจว่าจะเหมาะกับความต้องการของคุณอย่างไร (การเปลี่ยนประเภทการคืนสินค้า)
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();

ใช้วิธีการขยายที่ 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 มีอาร์เรย์ภายในที่เก็บค่าไว้โดยมีความยาวคงที่ เมื่อคุณเรียกรายการเพิ่มจะตรวจสอบว่ามีพื้นที่เพียงพอหรือไม่ เมื่อไม่สามารถเพิ่มองค์ประกอบใหม่ได้มันจะสร้างอาร์เรย์ใหม่ที่มีขนาดใหญ่ขึ้นให้คัดลอกองค์ประกอบก่อนหน้าทั้งหมดจากนั้นเพิ่มองค์ประกอบใหม่ การดำเนินการนี้ใช้เวลาไม่กี่รอบ

เนื่องจากคุณทราบจำนวนองค์ประกอบแล้วคุณสามารถสร้างรายการขนาดที่ถูกต้องซึ่งน่าจะเร็วกว่ามาก

นอกจากนี้ไม่แน่ใจว่าคุณเข้าถึงค่าได้อย่างไร แต่คุณสามารถสร้างสิ่งนี้ขึ้นมาและบันทึกภาพในโค้ดได้ (การโหลดจากดิสก์อาจช้ากว่าที่คุณกำลังทำอยู่คุณอ่าน / เขียนสิ่งนี้กี่ครั้ง สิ่ง?


ฉันได้ลองจัดสรรอาร์เรย์ปกติล่วงหน้าแล้วและเชื่อหรือไม่ว่ามันช้าลง ดังที่ฉันได้กล่าวไว้ข้างต้นสิ่งนี้จะต้องถูกสร้างขึ้นทันทีฉันไม่สามารถคำนวณได้ครั้งเดียวและปล่อยทิ้งไว้
benpage

จริงๆ? ว้าว - คุณกำลังทำงานโดยเปิดใช้งานการเพิ่มประสิทธิภาพใช่ไหม (แค่ถาม)
Carsten

นั่นเป็นอีกปัญหาหนึ่งอาร์เรย์ปกติ [x, y] ใช้งานได้ดี แต่อาร์เรย์อาร์เรย์จะเร็วกว่า stackoverflow.com/questions/597720/…เพราะวิธีการดำเนินการภายใต้ประทุนใน IL
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.

อย่างน้อยก็ควรนำค่าใช้จ่ายของลูปออกไป (ถ้ามี)

ฉันไม่คุ้นเคยกับ C # มากนัก แต่ดูเหมือนจะมีวิธีการทำให้เป็นอนุกรมของวัตถุ จะเกิดอะไรขึ้นถ้าคุณเพิ่งสร้างรายการนั้นและทำให้เป็นอนุกรมในรูปแบบใดรูปแบบหนึ่ง? ฉันไม่แน่ใจว่าการ deserialization นั้นเร็วกว่าหรือไม่ในการสร้างรายการและเพิ่มองค์ประกอบ


การทำให้เป็นอนุกรมเป็นการคิดนอกกรอบที่ยอดเยี่ยมจริงๆ!
Joel B

น่าเสียดายที่ค่าสูงสุดในรายการเป็นแบบไดนามิกฉันไม่สามารถพิมพ์สิ่งนี้แบบคงที่ได้ เป็นความคิดที่ดี!
benpage

2

คุณต้องการให้ผลลัพธ์เป็นอาร์เรย์อาร์เรย์หรือไม่? ด้วยการตั้งค่าปัจจุบันความยาวของอาร์เรย์ด้านในได้รับการแก้ไขและสามารถแทนที่ด้วยโครงสร้าง สิ่งนี้จะช่วยให้ทุกสิ่งถูกสงวนไว้เป็นหน่วยความจำต่อเนื่องหนึ่งบล็อกและช่วยให้เข้าถึงองค์ประกอบได้ง่ายขึ้น (ไม่แน่ใจว่าคุณใช้สิ่งนี้ในภายหลังอย่างไร)

แนวทางด้านล่างเร็วกว่ามาก (41ms เทียบกับ 1071ms สำหรับต้นฉบับในกล่องของฉัน):

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 ) ฉันแก้ไขค่าเล็กน้อย (a คือ 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

ไม่เลว - น่าเสียดายที่มันทำงานช้ากว่าสำหรับลูป 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 ms ซึ่งเร็วกว่ารหัสเดิม 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();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.