แยกรายการเป็นรายการขนาดเล็กกว่า N


209

ฉันกำลังพยายามแบ่งรายการเป็นชุดรายการขนาดเล็ก

ปัญหาของฉัน:ฟังก์ชั่นการแยกรายการของฉันไม่ได้แบ่งออกเป็นรายการขนาดที่ถูกต้อง ควรแยกพวกมันออกเป็นรายการขนาด 30 แต่แยกมันออกเป็นรายการขนาด 114 หรือไม่

ฉันจะทำให้ฟังก์ชั่นแยกรายการออกเป็นจำนวน X รายการขนาด30 หรือน้อยกว่าได้อย่างไร

public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30) 
{       
    List<List<float[]>> list = new List<List<float[]>>();

    for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) {
        List <float[]> subLocat = new List <float[]>(locations); 

        if (subLocat.Count >= ((i*nSize)+nSize))
            subLocat.RemoveRange(i*nSize, nSize);
        else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize));

        Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString());
        list.Add (subLocat);
    }

    return list;
}

ถ้าฉันใช้ฟังก์ชั่นในรายการขนาด 144 แล้วผลลัพธ์คือ:

ดัชนี: 4, ขนาด: 120
ดัชนี: 3, ขนาด: 114
ดัชนี: 2, ขนาด: 114
ดัชนี: 1, ขนาด: 114
ดัชนี: 0, ขนาด: 114


1
หากแก้ปัญหา LINQ เป็นที่ยอมรับคำถามนี้อาจจะมีความช่วยเหลือบางส่วน

คำตอบเฉพาะของแซมซัฟฟรอนสำหรับคำถามก่อนหน้านั้น และถ้านี่คือการมอบหมายให้โรงเรียนฉันจะใช้รหัสของเขาและหยุด
jcolebrand

คำตอบ:


268
public static List<List<float[]>> SplitList(List<float[]> locations, int nSize=30)  
{        
    var list = new List<List<float[]>>(); 

    for (int i = 0; i < locations.Count; i += nSize) 
    { 
        list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i))); 
    } 

    return list; 
} 

รุ่นทั่วไป:

public static IEnumerable<List<T>> SplitList<T>(List<T> locations, int nSize=30)  
{        
    for (int i = 0; i < locations.Count; i += nSize) 
    { 
        yield return locations.GetRange(i, Math.Min(nSize, locations.Count - i)); 
    }  
} 

ดังนั้นหากฉันมีความยาวของรายการเป็นล้านล้านและฉันต้องการแยกออกเป็นลิสต์ขนาดเล็กความยาว 30 และจากลิสต์เล็ก ๆ ทุกตัวที่ฉันต้องการจะใช้ (1) จากนั้นฉันก็ยังสร้างรายการ 30 รายการที่ฉันทิ้งไป 29 รายการ สามารถทำได้อย่างชาญฉลาด!
Harald Coppoolse

ใช้งานได้จริงหรือ มันจะไม่ล้มเหลวในการแบ่งครั้งแรกเพราะคุณได้รับช่วง nSize เป็น nSize หรือไม่ ตัวอย่างเช่นถ้า nSize คือ 3 และอาร์เรย์ของฉันคือขนาด 5 ดังนั้นช่วงดัชนีแรกที่ส่งคืนคือGetRange(3, 3)
Matthew Pigram

2
@ MatthewPigram ทดสอบแล้วและใช้งานได้ Math.Min รับค่า min ดังนั้นหาก chunk สุดท้ายน้อยกว่า nSize (2 <3) มันจะสร้างรายการที่มีรายการที่เหลืออยู่
Phate01

1
@HaraldCoppoolse OP ไม่ขอเลือกเพียงเพื่อแยกรายการ
Phate01

@MatthewPigram การทำซ้ำครั้งแรก - GetRange (0,3), การทำซ้ำครั้งที่สอง - GetRange (3,2)
Serj-Tm

381

ฉันขอแนะนำให้ใช้วิธีส่วนขยายนี้เพื่อแยกรายการแหล่งข้อมูลไปยังรายการย่อยตามขนาดกลุ่มข้อมูลที่ระบุ:

/// <summary>
/// Helper methods for the lists.
/// </summary>
public static class ListExtensions
{
    public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize) 
    {
        return source
            .Select((x, i) => new { Index = i, Value = x })
            .GroupBy(x => x.Index / chunkSize)
            .Select(x => x.Select(v => v.Value).ToList())
            .ToList();
    }
}

ตัวอย่างเช่นถ้าคุณแยกรายการ 18 รายการโดย 5 รายการต่อชิ้นจะให้รายการย่อย 4 รายการโดยมีรายการต่อไปนี้ภายใน: 5-5-5-3


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

4
แน่นอน @Nick ฉันขอแนะนำโดยทั่วไปให้คิดก่อนทำอะไร การดึงข้อมูลด้วย LINQ ไม่ควรเป็นการใช้งานซ้ำหลายครั้ง โดยปกติคุณจะต้องแสดงรายการย่อยสำหรับการประมวลผลรายการตามแบทช์และ / หรือในแบบคู่ขนาน
Dmitry Pavlov

6
ฉันไม่คิดว่าหน่วยความจำและประสิทธิภาพควรเป็นปัญหาใหญ่ที่นี่ ฉันมีความต้องการแยกรายการที่มีมากกว่า 200,000 รายการเป็นรายการเล็ก ๆ ละประมาณ 3,000 รายการซึ่งนำฉันมาที่หัวข้อนี้และฉันทดสอบทั้งสองวิธีและพบว่าเวลาทำงานเกือบเท่าเดิม หลังจากนั้นฉันทดสอบการแยกรายการนั้นออกเป็นรายการที่มี 3 รายการแต่ละรายการและยังคงประสิทธิภาพอยู่ ฉันคิดว่าทางออกของ Serj-Tm นั้นตรงไปตรงมามากกว่าและมีการบำรุงรักษาที่ดีกว่า
เงียบ

2
โปรดทราบว่าอาจเป็นการดีที่สุดที่จะละทิ้งToList()s และปล่อยให้การประเมินที่ขี้เกียจทำมันเป็นเวทย์มนตร์
Yair Halberstadt

3
@DmitryPavlov ตลอดเวลานี้ฉันไม่เคยรู้เกี่ยวกับความสามารถในการฉายดัชนีเช่นนั้นในงบเลือก! ฉันคิดว่ามันเป็นคุณสมบัติใหม่จนกระทั่งฉันสังเกตเห็นว่าคุณโพสต์สิ่งนี้ในปี 2014 นั่นทำให้ฉันประหลาดใจจริงๆ! ขอบคุณที่แบ่งปันสิ่งนี้ นอกจากนี้จะไม่ดีกว่าหรือไม่ที่จะมีวิธีการขยายให้ใช้กับ IEnumerable และส่งคืน IEnumerable ด้วย?
Aydin

37

เกี่ยวกับ:

while(locations.Any())
{    
    list.Add(locations.Take(nSize).ToList());
    locations= locations.Skip(nSize).ToList();
}

จะใช้หน่วยความจำมากมายหรือไม่ แต่ละครั้งที่ location.Skip.ToList เกิดขึ้นฉันสงสัยว่ามีการจัดสรรหน่วยความจำเพิ่มเติมหรือไม่และรายการที่ไม่ได้ถูกอ้างอิงจะถูกอ้างอิงโดยรายการใหม่
Zasz

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

2
.Skip(n)วนซ้ำnองค์ประกอบทุกครั้งที่เรียกว่าในขณะที่มันอาจจะโอเคมันเป็นสิ่งสำคัญที่จะต้องพิจารณาสำหรับรหัสที่มีประสิทธิภาพที่สำคัญ stackoverflow.com/questions/20002975/…
Chakrava

@ Chakrava แน่ใจว่าโซลูชันของฉันไม่ควรใช้ในรหัสที่มีประสิทธิภาพ แต่ในประสบการณ์ของฉันคุณเขียนรหัสการทำงานก่อนแล้วกำหนดว่าประสิทธิภาพการทำงานที่สำคัญนั้นเป็นอย่างไร นี่ควรได้รับการประเมินเป็นกรณี ๆ ไป
Rafal

@Rafal ฉันเห็นด้วยฉันพบจำนวนมาก.Skip()ในฐานรหัส บริษัท ของฉันและในขณะที่พวกเขาอาจจะไม่ "ดีที่สุด" พวกเขาทำงานได้ดี สิ่งต่าง ๆ เช่นการทำงานของ DB นั้นใช้เวลานาน แต่ฉันคิดว่ามันเป็นสิ่งสำคัญที่จะต้องทราบว่า.Skip()"แตะ" แต่ละองค์ประกอบ <n ในทางแทนที่จะข้ามไปยังองค์ประกอบที่ n โดยตรง (เหมือนที่คุณคาดหวัง) หากตัววนซ้ำของคุณมีผลข้างเคียงจากการสัมผัสองค์ประกอบ.Skip()อาจเป็นสาเหตุของข้อบกพร่องที่หายาก
Chakrava

11

วิธีแก้ปัญหา Serj-Tm นั้นใช้ได้เช่นกันนี่เป็นรุ่นทั่วไปสำหรับวิธีการขยายสำหรับรายการ (ใส่ไว้ในคลาสแบบคงที่)

public static List<List<T>> Split<T>(this List<T> items, int sliceSize = 30)
{
    List<List<T>> list = new List<List<T>>();
    for (int i = 0; i < items.Count; i += sliceSize)
        list.Add(items.GetRange(i, Math.Min(sliceSize, items.Count - i)));
    return list;
} 

10

ฉันพบคำตอบที่ได้รับการยอมรับ (Serj-Tm) ที่แข็งแกร่งที่สุด แต่ฉันอยากจะแนะนำรุ่นทั่วไป

public static List<List<T>> splitList<T>(List<T> locations, int nSize = 30)
{
    var list = new List<List<T>>();

    for (int i = 0; i < locations.Count; i += nSize)
    {
        list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i)));
    }

    return list;
}

8

Library MoreLinq มีวิธีการที่เรียกว่า Batch

List<int> ids = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; // 10 elements
int counter = 1;
foreach(var batch in ids.Batch(2))
{
    foreach(var eachId in batch)
    {
        Console.WriteLine("Batch: {0}, Id: {1}", counter, eachId);
    }
    counter++;
}

ผลที่ได้คือ

Batch: 1, Id: 1
Batch: 1, Id: 2
Batch: 2, Id: 3
Batch: 2, Id: 4
Batch: 3, Id: 5
Batch: 3, Id: 6
Batch: 4, Id: 7
Batch: 4, Id: 8
Batch: 5, Id: 9
Batch: 5, Id: 0

ids แบ่งออกเป็น 5 ชิ้นมี 2 องค์ประกอบ


นี่จะต้องเป็นคำตอบที่ได้รับการยอมรับ หรือสูงกว่านี้เป็นอย่างน้อยในหน้านี้
Zar Shardan

7

ฉันมีวิธีการทั่วไปที่จะใช้ทุกประเภทรวมถึงการลอยและมันผ่านการทดสอบหน่วยหวังว่าจะช่วย:

    /// <summary>
    /// Breaks the list into groups with each group containing no more than the specified group size
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="values">The values.</param>
    /// <param name="groupSize">Size of the group.</param>
    /// <returns></returns>
    public static List<List<T>> SplitList<T>(IEnumerable<T> values, int groupSize, int? maxCount = null)
    {
        List<List<T>> result = new List<List<T>>();
        // Quick and special scenario
        if (values.Count() <= groupSize)
        {
            result.Add(values.ToList());
        }
        else
        {
            List<T> valueList = values.ToList();
            int startIndex = 0;
            int count = valueList.Count;
            int elementCount = 0;

            while (startIndex < count && (!maxCount.HasValue || (maxCount.HasValue && startIndex < maxCount)))
            {
                elementCount = (startIndex + groupSize > count) ? count - startIndex : groupSize;
                result.Add(valueList.GetRange(startIndex, elementCount));
                startIndex += elementCount;
            }
        }


        return result;
    }

ขอบคุณ สงสัยหรือไม่ว่าคุณสามารถอัปเดตความคิดเห็นด้วยข้อกำหนดพารามิเตอร์ maxCount ได้หรือไม่ ตาข่ายนิรภัยหรือไม่?
Andrew Jens

2
ระวังด้วยการแจกแจงจำนวนมากของการแจกแจง values.Count()จะทำให้เกิดการแจงนับเต็มและvalues.ToList()อีกอัน ปลอดภัยยิ่งขึ้นเพื่อvalues = values.ToList()ให้เป็นจริงแล้ว
mhand

7

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

public static IEnumerable<List<T>> BatchBy<T>(this IEnumerable<T> enumerable, int batchSize)
{
    using (var enumerator = enumerable.GetEnumerator())
    {
        List<T> list = null;
        while (enumerator.MoveNext())
        {
            if (list == null)
            {
                list = new List<T> {enumerator.Current};
            }
            else if (list.Count < batchSize)
            {
                list.Add(enumerator.Current);
            }
            else
            {
                yield return list;
                list = new List<T> {enumerator.Current};
            }
        }

        if (list?.Count > 0)
        {
            yield return list;
        }
    }
}

แก้ไข: ตอนนี้ตระหนักถึง OP ถามเกี่ยวกับการแบ่งเป็นList<T>เล็กList<T>ดังนั้นความคิดเห็นของฉันเกี่ยวกับอนันต์นับไม่ได้ใช้กับ OP แต่อาจช่วยคนอื่น ๆ ที่จบลงที่นี่ ความคิดเห็นเหล่านี้ตอบกลับไปยังโซลูชันที่โพสต์อื่น ๆ ซึ่งใช้IEnumerable<T>เป็นอินพุตของฟังก์ชัน แต่ยังระบุแหล่งที่มานับได้หลายครั้ง


ฉันคิดว่าIEnumerable<IEnumerable<T>>รุ่นนี้ดีกว่าเพราะไม่มีส่วนเกี่ยวข้องในการListก่อสร้างมากนัก
NetMage

@NetMage - ปัญหาหนึ่งที่มีIEnumerable<IEnumerable<T>>คือการใช้งานมีแนวโน้มที่จะพึ่งพาผู้บริโภคอย่างเต็มที่แจงนับแต่ละผลภายในนับ ฉันแน่ใจว่าวิธีแก้ปัญหาอาจถูกใช้ถ้อยคำเพื่อหลีกเลี่ยงปัญหานั้น แต่ฉันคิดว่ารหัสผลลัพธ์อาจซับซ้อนได้อย่างรวดเร็ว นอกจากนี้เนื่องจากมันขี้เกียจเราจะสร้างรายการเดียวในแต่ละครั้งและการจัดสรรหน่วยความจำจะเกิดขึ้นหนึ่งครั้งต่อรายการเท่านั้นเนื่องจากเรารู้ขนาดล่วงหน้า
มือ

คุณพูดถูก - การใช้งานของฉันใช้ตัวแจงนับชนิดใหม่ (ตัวระบุตำแหน่ง) ที่ติดตามตำแหน่งปัจจุบันของคุณที่ห่อหุ้มตัวแจงนับมาตรฐานและให้คุณเลื่อนไปที่ตำแหน่งใหม่
NetMage

6

นอกจากนี้หลังจากการแสดงความคิดเห็นที่มีประโยชน์มากของ mhand ในตอนท้าย

คำตอบเดิม

แม้ว่าวิธีแก้ปัญหาส่วนใหญ่อาจใช้งานได้ แต่ฉันคิดว่ามันไม่ได้มีประสิทธิภาพมากนัก สมมติว่าคุณต้องการรายการสองสามชิ้นแรกของชิ้นส่วนแรก ๆ เท่านั้น จากนั้นคุณจะไม่ต้องการวนซ้ำไอเท็ม (zillion) ทั้งหมดในลำดับของคุณ

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

public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>
    (this IEnumerable<TSource> source, int chunkSize)
{
    while (source.Any())                     // while there are elements left
    {   // still something to chunk:
        yield return source.Take(chunkSize); // return a chunk of chunkSize
        source = source.Skip(chunkSize);     // skip the returned chunk
    }
}

ลำดับนี้จะระบุลำดับกี่ครั้ง

สมมติว่าคุณแบ่งแหล่งที่มาของคุณเป็นชิ้นchunkSizeๆ คุณระบุเฉพาะ N ชิ้นแรกเท่านั้น คุณจะต้องระบุองค์ประกอบ M แรกเท่านั้น

While(source.Any())
{
     ...
}

Any จะได้รับ Enumerator ทำ 1 MoveNext () และส่งคืนค่าที่ส่งคืนหลังจากการกำจัด Enumerator สิ่งนี้จะทำ N ครั้ง

yield return source.Take(chunkSize);

ตามแหล่งอ้างอิงนี้จะทำสิ่งที่ชอบ:

public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
    return TakeIterator<TSource>(source, count);
}

static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    foreach (TSource element in source)
    {
        yield return element;
        if (--count == 0) break;
    }
}

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

หากคุณตัดสินใจที่จะใช้องค์ประกอบ M แรกของชิ้นแรกจากนั้นผลตอบแทนที่ได้จะถูกดำเนินการ M ครั้งแน่นอน หมายความว่า:

  • รับตัวแจงนับ
  • เรียกใช้ MoveNext () และ M ครั้งปัจจุบัน
  • กำจัดตัวแจงนับ

หลังจากได้รับผลตอบแทนก้อนแรกแล้วเราก็ข้ามกลุ่มแรกนี้:

source = source.Skip(chunkSize);

อีกครั้ง: เราจะดูที่แหล่งข้อมูลอ้างอิงเพื่อค้นหาskipiterator

static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
    using (IEnumerator<TSource> e = source.GetEnumerator()) 
    {
        while (count > 0 && e.MoveNext()) count--;
        if (count <= 0) 
        {
            while (e.MoveNext()) yield return e.Current;
        }
    }
}

ตามที่คุณเห็นการSkipIteratorเรียกMoveNext()หนึ่งครั้งสำหรับทุกองค์ประกอบใน Chunk มันไม่ได้เรียกCurrent

ดังนั้นต่อกลุ่มเราจะเห็นว่าทำต่อไปนี้:

  • ใด ๆ (): GetEnumerator; 1 MoveNext (); กำจัด Enumerator;
  • ใช้ ():

    • ไม่มีอะไรถ้าเนื้อหาของก้อนไม่แจกแจง
    • หากเนื้อหามีการแจกแจง: GetEnumerator () หนึ่ง MoveNext และหนึ่งในปัจจุบันต่อรายการที่ระบุจำหน่ายทิ้งตัวระบุ;

    • ข้าม (): สำหรับทุก ๆ ชิ้นที่มีการแจกแจง (ไม่ใช่เนื้อหาของก้อน): GetEnumerator (), MoveNext () เวลา chunkSize ไม่มีกระแส! กำจัดตัวแจงนับ

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

หากคุณใช้ chunkSize ขนาด N จำนวนแล้วโทรไปที่ MoveNext ()

  • ไม่มีเวลาใด ๆ ()
  • ยังไม่มีเวลาสำหรับ Take ตราบใดที่คุณยังไม่แจกแจง Chunks
  • ก้อนไม่มีครั้งขนาดสำหรับการข้าม ()

หากคุณตัดสินใจที่จะระบุเฉพาะองค์ประกอบ M แรกของชิ้นข้อมูลที่ดึงมาทั้งหมดคุณจะต้องเรียกใช้ MoveNext M คูณต่อ Chunk ที่ระบุ

ผลรวม

MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)

ดังนั้นหากคุณตัดสินใจที่จะระบุองค์ประกอบทั้งหมดของชิ้นทั้งหมด:

MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once

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

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

ขึ้นอยู่กับพื้นที่เก็บข้อมูลของคุณไม่ว่าจะเป็นการดีที่จะเรียก AsEnumerable () หรือ ToLists () ก่อนที่คุณจะเริ่มแบ่งรายการใน Chunks


สิ่งนี้จะไม่แจกแจงสองครั้งต่อชุดหรือไม่ ดังนั้นเราจะแจกแจง2*chunkSizeเวลาที่มาจริง ๆ นี่เป็นอันตรายถึงตายได้ขึ้นอยู่กับแหล่งที่มาของจำนวนที่แจกแจง (อาจเป็นฐานข้อมูลสำรองหรือแหล่งข้อมูลอื่นที่ไม่ได้บันทึกความจำ) ลองนึกภาพสิ่งนี้นับเป็นข้อมูลเข้าEnumerable.Range(0, 10000).Select(i => DateTime.UtcNow)- คุณจะได้รับเวลาที่แตกต่างกันทุกครั้งที่คุณระบุสิ่งนับได้เนื่องจากมันไม่ได้
จดบันทึก

พิจารณา: Enumerable.Range(0, 10).Select(i => DateTime.UtcNow). โดยเรียกAnyคุณจะ recomputing เวลาปัจจุบันในแต่ละครั้ง ไม่เลวสำหรับDateTime.UtcNowแต่ให้พิจารณาการสำรองข้อมูลโดยการเชื่อมต่อฐานข้อมูล / เคอร์เซอร์ sql หรือคล้ายกัน ฉันได้เห็นกรณีที่มีการเรียก DB หลายพันครั้งเนื่องจากผู้พัฒนาไม่เข้าใจถึงผลกระทบที่อาจเกิดขึ้นจาก 'การแจกแจงหลายค่าของการนับจำนวน' - ReSharperให้คำแนะนำสำหรับสิ่งนี้ด้วย
มือ

4
public static IEnumerable<IEnumerable<T>> SplitIntoSets<T>
    (this IEnumerable<T> source, int itemsPerSet) 
{
    var sourceList = source as List<T> ?? source.ToList();
    for (var index = 0; index < sourceList.Count; index += itemsPerSet)
    {
        yield return sourceList.Skip(index).Take(itemsPerSet);
    }
}

3
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems)
{
    return items.Select((item, index) => new { item, index })
                .GroupBy(x => x.index / maxItems)
                .Select(g => g.Select(x => x.item));
}

2

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

private IEnumerable<IList<T>> SplitList<T>(IList<T> list, int totalChunks)
{
    IList<T> auxList = new List<T>();
    int totalItems = list.Count();

    if (totalChunks <= 0)
    {
        yield return auxList;
    }
    else 
    {
        for (int i = 0; i < totalItems; i++)
        {               
            auxList.Add(list[i]);           

            if ((i + 1) % totalChunks == 0)
            {
                yield return auxList;
                auxList = new List<T>();                
            }

            else if (i == totalItems - 1)
            {
                yield return auxList;
            }
        }
    }   
}

1

อีกหนึ่ง

public static IList<IList<T>> SplitList<T>(this IList<T> list, int chunkSize)
{
    var chunks = new List<IList<T>>();
    List<T> chunk = null;
    for (var i = 0; i < list.Count; i++)
    {
        if (i % chunkSize == 0)
        {
            chunk = new List<T>(chunkSize);
            chunks.Add(chunk);
        }
        chunk.Add(list[i]);
    }
    return chunks;
}

1
public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize)
    {           
        var result = new List<List<T>>();
        for (int i = 0; i < source.Count; i += chunkSize)
        {
            var rows = new List<T>();
            for (int j = i; j < i + chunkSize; j++)
            {
                if (j >= source.Count) break;
                rows.Add(source[j]);
            }
            result.Add(rows);
        }
        return result;
    }

0
List<int> list =new List<int>(){1,2,3,4,5,6,7,8,9,10,12};
Dictionary<int,List<int>> dic = new Dictionary <int,List<int>> ();
int batchcount = list.Count/2; //To List into two 2 parts if you want three give three
List<int> lst = new List<int>();
for (int i=0;i<list.Count; i++)
{
lstdocs.Add(list[i]);
if (i % batchCount == 0 && i!=0)
{
Dic.Add(threadId, lstdocs);
lst = new List<int>();**strong text**
threadId++;
}
}
Dic.Add(threadId, lstdocs);

2
คุณควรอธิบายคำตอบของคุณแทนที่จะให้เพียงแค่ข้อมูลโค้ดเท่านั้น
Kevin

0

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

        var categories = Properties.Settings.Default.MovementStatsCategories;
        var items = summariesWithinYear
            .Select(s =>  s.sku).Distinct().ToList();

        //need to run by chunks of 10,000
        var count = items.Count;
        var counter = 0;
        var numToTake = 10000;

        while (count > 0)
        {
            var itemsChunk = items.Skip(numToTake * counter).Take(numToTake).ToList();
            counter += 1;

            MovementHistoryUtilities.RecordMovementHistoryStatsBulk(itemsChunk, categories, nLogger);

            count -= numToTake;
        }

0

ขึ้นอยู่กับDimitry Pavlov answere.ToList()ฉันจะลบ และยังหลีกเลี่ยงคลาสที่ไม่ระบุชื่อ แต่ฉันชอบใช้โครงสร้างที่ไม่ต้องการการจัดสรรหน่วยความจำฮีป (A ValueTupleจะทำงานด้วย)

public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
    if (source is null)
    {
        throw new ArgumentNullException(nameof(source));
    }
    if (chunkSize <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(chunkSize), chunkSize, "The argument must be greater than zero.");
    }

    return source
        .Select((x, i) => new ChunkedValue<TSource>(x, i / chunkSize))
        .GroupBy(cv => cv.ChunkIndex)
        .Select(g => g.Select(cv => cv.Value));
} 

[StructLayout(LayoutKind.Auto)]
[DebuggerDisplay("{" + nameof(ChunkedValue<T>.ChunkIndex) + "}: {" + nameof(ChunkedValue<T>.Value) + "}")]
private struct ChunkedValue<T>
{
    public ChunkedValue(T value, int chunkIndex)
    {
        this.ChunkIndex = chunkIndex;
        this.Value = value;
    }

    public int ChunkIndex { get; }

    public T Value { get; }
}

สิ่งนี้สามารถใช้งานได้ดังต่อไปนี้ซึ่งทำซ้ำเฉพาะคอลเลกชันครั้งเดียวและยังไม่ได้จัดสรรหน่วยความจำที่สำคัญใด ๆ

int chunkSize = 30;
foreach (var chunk in collection.ChunkBy(chunkSize))
{
    foreach (var item in chunk)
    {
        // your code for item here.
    }
}

หากต้องการรายการคอนกรีตจริง ๆ แล้วฉันจะทำเช่นนี้:

int chunkSize = 30;
var chunkList = new List<List<T>>();
foreach (var chunk in collection.ChunkBy(chunkSize))
{
    // create a list with the correct capacity to be able to contain one chunk
    // to avoid the resizing (additional memory allocation and memory copy) within the List<T>.
    var list = new List<T>(chunkSize);
    list.AddRange(chunk);
    chunkList.Add(list);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.