แบ่งคอลเล็กชันออกเป็นส่วน "n" ด้วย LINQ หรือไม่


122

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

นั่นคือฉันต้องการแบ่งคอลเลกชันออกเป็นคอลเลกชั่นย่อยซึ่งแต่ละคอลเลกชั่นมีองค์ประกอบย่อยซึ่งคอลเลกชันสุดท้ายสามารถมอมแมมได้


1
Retagged: คำถามไม่มีส่วนเกี่ยวข้องกับ asp.net โปรดแท็กคำถามของคุณอย่างเหมาะสม

คุณต้องการให้พวกเขาแยกออกจากกันอย่างไรถ้าไม่ถึง (ยอมให้จบแน่นอน)?
Marc Gravell

1
ใครเชื่อมโยงกับคำถามนี้ จอห์นคือคุณหรือเปล่า :-) ทันใดนั้นคำตอบเหล่านี้ทั้งหมด :-)
Simon_Weaver


@Simon_Weaver ฉันพยายามชี้แจงสิ่งที่คุณถามตามคำตอบที่ยอมรับ ในความเป็นจริงมีหลายวิธีในการ "แยก" รายการรวมถึงการแยกองค์ประกอบแต่ละรายการออกเป็นองค์ประกอบและวางรายการเหล่านั้นไว้ในรายการที่เรียกว่า "คู่ขนาน"
jpaugh

คำตอบ:


127

linq บริสุทธิ์และวิธีแก้ปัญหาที่ง่ายที่สุดมีดังที่แสดงด้านล่าง

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int i = 0;
        var splits = from item in list
                     group item by i++ % parts into part
                     select part.AsEnumerable();
        return splits;
    }
}

3
คุณสามารถทำได้: เลือกส่วน AsEnumerable () แทนการเลือกส่วน (IEnumerable <T>) รู้สึกสง่างามมากขึ้น
tuinstoel

2
การดำเนินการโมดูลัสทั้งหมดเหล่านั้นอาจมีราคาแพงเล็กน้อยในรายการที่ยาว
Jonathan Allen

8
จะดีกว่าถ้าใช้ Select overload ที่มีดัชนี
Marc Gravell

1
ฉันได้เพิ่มการตอบสนองที่ใช้ไวยากรณ์การเลือกโอเวอร์โหลดและวิธีการผูกมัด
reustmd

1
.AsEnumerable()ไม่จำเป็น IGrouping <T> เป็น IEnumerable <T> อยู่แล้ว
Alex

58

แก้ไข: โอเคดูเหมือนว่าฉันจะอ่านคำถามผิด ฉันอ่านว่า "ชิ้นความยาว n" แทนที่จะเป็น "ชิ้นส่วน" Doh! กำลังพิจารณาลบคำตอบ ...

(คำตอบเดิม)

ฉันไม่เชื่อว่ามีวิธีการแบ่งพาร์ติชันในตัวแม้ว่าฉันตั้งใจจะเขียนหนึ่งในชุดการเพิ่ม LINQ to Objects Marc Gravell มีการใช้งานที่นี่แม้ว่าฉันอาจจะแก้ไขเพื่อส่งคืนมุมมองแบบอ่านอย่างเดียว:

public static IEnumerable<IEnumerable<T>> Partition<T>
    (this IEnumerable<T> source, int size)
{
    T[] array = null;
    int count = 0;
    foreach (T item in source)
    {
        if (array == null)
        {
            array = new T[size];
        }
        array[count] = item;
        count++;
        if (count == size)
        {
            yield return new ReadOnlyCollection<T>(array);
            array = null;
            count = 0;
        }
    }
    if (array != null)
    {             
        Array.Resize(ref array, count);
        yield return new ReadOnlyCollection<T>(array);
    }
}

Darn - เอาชนะฉันไปเลย ;-p
Marc Gravell

3
คุณไม่ชอบ "อาร์เรย์ [จำนวน ++]" เหล่านั้นจริงๆใช่ไหม ;-p
Marc Gravell

18
ขอบคุณที่ไม่ลบแม้ว่ามันจะไม่ใช่คำตอบสำหรับ OP แต่ฉันก็ต้องการสิ่งเดียวกัน - ชิ้นความยาว n :)
Gishu

2
@ เดจัน: ไม่มันไม่ได้ หมายเหตุการใช้yield return. ต้องมีหนึ่งชุดในหน่วยความจำในแต่ละครั้ง แต่นั่นคือทั้งหมดที่
Jon Skeet

1
@Dejan: ใช่ - ฉันไม่อยากเดาว่ามันโต้ตอบกับการแบ่งพาร์ติชัน Parallel LINQ อย่างไรบอกตามตรง :)
Jon Skeet

39
static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
            return list.Select((item, index) => new {index, item})
                       .GroupBy(x => x.index % parts)
                       .Select(x => x.Select(y => y.item));
    }
}

28
ฉันไม่ชอบ Linq สไตล์ SQL อย่างไม่มีเหตุผลดังนั้นนี่คือคำตอบที่ฉันชอบ
piedar

1
@ manu08 ผมได้พยายามรหัส ur var dept = {1,2,3,4,5}ฉันมีรายชื่อ หลังจากแยกผลที่ได้คือเหมือนdept1 = {1,3,5}และที่dept2 = { 2,4 } parts = 2แต่ผลลัพธ์ที่ฉันต้องการคือdept1 = {1,2,3}และdept2 = {4,5}
Karthik Arthik

3
ฉันมีปัญหาเดียวกันกับโมดูโลดังนั้นฉันจึงคำนวณความยาวคอลัมน์int columnLength = (int)Math.Ceiling((decimal)(list.Count()) / parts);จากนั้นจึงหารด้วย.GroupBy(x => x.index / columnLength). ข้อเสียอย่างหนึ่งคือ Count () แจกแจงรายการ
goodeye

24

ตกลงฉันจะโยนหมวกของฉันใส่แหวน ข้อดีของอัลกอริทึมของฉัน:

  1. ไม่มีตัวดำเนินการคูณหารหรือโมดูลัสราคาแพง
  2. การดำเนินการทั้งหมดเป็น O (1) (ดูหมายเหตุด้านล่าง)
  3. ใช้งานได้กับ IEnumerable <> source (ไม่จำเป็นต้องใช้คุณสมบัติ Count)
  4. ง่าย

รหัส:

public static IEnumerable<IEnumerable<T>>
  Section<T>(this IEnumerable<T> source, int length)
{
  if (length <= 0)
    throw new ArgumentOutOfRangeException("length");

  var section = new List<T>(length);

  foreach (var item in source)
  {
    section.Add(item);

    if (section.Count == length)
    {
      yield return section.AsReadOnly();
      section = new List<T>(length);
    }
  }

  if (section.Count > 0)
    yield return section.AsReadOnly();
}

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

myEnum.Section(myEnum.Count() / number_of_sections + 1)

เมื่อใช้ในลักษณะนี้วิธีการจะไม่ O (1) อีกต่อไปเนื่องจากการดำเนินการ Count () คือ O (N)


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

3
@ShadowChaser ตาม MSDN การล้าง LinkedList คือความซับซ้อน O (N) ดังนั้นมันจะทำลายเป้าหมายของฉันเกี่ยวกับ O (1) แน่นอนคุณสามารถโต้แย้งได้ว่า foreach คือ O (N) เพื่อเริ่มต้นด้วย ... :)
Mike

4
คำตอบของคุณถูกต้อง แต่คำถามนั้นผิด คำตอบของคุณให้จำนวนชิ้นที่ไม่ทราบจำนวนโดยมีขนาดคงที่สำหรับแต่ละชิ้น แต่ OP ต้องการฟังก์ชัน Split โดยให้จำนวนชิ้นคงที่โดยมีขนาดใด ๆ ต่อชิ้น (หวังว่าจะมีขนาดเท่ากันหรือใกล้เคียงกับขนาดที่เท่ากัน) อาจจะเหมาะกว่าที่นี่stackoverflow.com/questions/3773403/…
nawfal

1
@ คุณไมค์ได้เปรียบเทียบหรือไม่? ฉันหวังว่าคุณจะรู้ว่า O (1) ไม่ได้หมายความว่าเร็วขึ้น แต่หมายความว่าเวลาที่ต้องใช้ในการแบ่งพาร์ติชันไม่ได้ปรับขนาด ฉันแค่สงสัยว่าเหตุผลของคุณคืออะไรในการยึดติดกับ O (1) แบบสุ่มสี่สุ่มห้าในเมื่อมันอาจจะช้ากว่า O (n) อื่น ๆ สำหรับสถานการณ์ในชีวิตจริงทั้งหมด ฉันยังทดสอบมันเพื่อหารายชื่อความแข็งแกร่ง 10 ^ 8 ที่บ้าคลั่งและของฉันดูเหมือนจะยังเร็วกว่า ฉันหวังว่าคุณจะรู้ว่าไม่มีแม้แต่คอลเลกชันมาตรฐานที่สามารถบรรจุสินค้าได้ 10 ^ 12 ชิ้น ..
nawfal

1
@nawfal - ขอบคุณสำหรับการวิเคราะห์โดยละเอียดของคุณมันช่วยให้ฉันก้าวไปข้างหน้า โดยทั่วไปแล้วรายการที่เชื่อมโยงเป็นที่ทราบกันดีว่าเม็ดมีดปลายที่มีประสิทธิภาพซึ่งเป็นสาเหตุที่ฉันเลือกที่นี่ อย่างไรก็ตามฉันเพิ่งเปรียบเทียบและแน่นอนรายการ <> เร็วกว่ามาก ฉันสงสัยว่านี่เป็นรายละเอียดการใช้งาน. NET บางอย่างซึ่งอาจสมควรได้รับคำถาม StackOverflow แยกต่างหาก ฉันได้แก้ไขคำตอบเพื่อใช้รายการ <> ตามคำแนะนำของคุณ การจัดสรรความจุของรายการไว้ล่วงหน้าช่วยรับประกันว่าการแทรกปลายยังคงเป็น O (1) และเป็นไปตามเป้าหมายการออกแบบเดิมของฉัน ฉันยังเปลี่ยนไปใช้ .AsReadOnly () ในตัวใน. NET 4.5
Mike

16

สิ่งนี้เหมือนกับคำตอบที่ยอมรับ แต่เป็นตัวแทนที่ง่ายกว่ามาก:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}

วิธีการข้างต้นจะแบ่งIEnumerable<T>ชิ้นส่วนที่มีขนาดเท่ากันหรือใกล้เคียงกับขนาดที่เท่ากันออกเป็นN

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}

วิธีการข้างต้นจะแบ่งออกเป็นIEnumerable<T>ชิ้นส่วนของขนาดคงที่ที่ต้องการโดยมีจำนวนชิ้นทั้งหมดที่ไม่สำคัญซึ่งไม่ใช่สิ่งที่เป็นคำถาม

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

เกือบทุกคำตอบที่นี่ไม่ได้รักษาลำดับหรือเกี่ยวกับการแบ่งพาร์ติชั่นและไม่แยกหรือผิดอย่างชัดเจน ลองสิ่งนี้ที่เร็วกว่ารักษาคำสั่งซื้อ แต่มีรายละเอียดมากขึ้น:

public static IEnumerable<IEnumerable<T>> Split<T>(this ICollection<T> items, 
                                                   int numberOfChunks)
{
    if (numberOfChunks <= 0 || numberOfChunks > items.Count)
        throw new ArgumentOutOfRangeException("numberOfChunks");

    int sizePerPacket = items.Count / numberOfChunks;
    int extra = items.Count % numberOfChunks;

    for (int i = 0; i < numberOfChunks - extra; i++)
        yield return items.Skip(i * sizePerPacket).Take(sizePerPacket);

    int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket;
    int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1;
    for (int i = 0; i < extra; i++)
        yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount);
}

วิธีการเทียบเท่าสำหรับการPartitionดำเนินการที่นี่


6

ฉันใช้ฟังก์ชันพาร์ติชั่นที่ฉันโพสต์ไว้ก่อนหน้านี้ค่อนข้างบ่อย สิ่งเดียวที่แย่เกี่ยวกับเรื่องนี้คือการสตรีมไม่สมบูรณ์ นี่ไม่ใช่ปัญหาหากคุณทำงานกับองค์ประกอบไม่กี่อย่างในลำดับของคุณ ฉันต้องการโซลูชันใหม่เมื่อฉันเริ่มทำงานกับ 100.000+ องค์ประกอบในลำดับของฉัน

วิธีแก้ปัญหาต่อไปนี้ซับซ้อนกว่ามาก (และมีโค้ดมากกว่า!) แต่มีประสิทธิภาพมาก

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace LuvDaSun.Linq
{
    public static class EnumerableExtensions
    {
        public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int partitionSize)
        {
            /*
            return enumerable
                .Select((item, index) => new { Item = item, Index = index, })
                .GroupBy(item => item.Index / partitionSize)
                .Select(group => group.Select(item => item.Item)                )
                ;
            */

            return new PartitioningEnumerable<T>(enumerable, partitionSize);
        }

    }


    class PartitioningEnumerable<T> : IEnumerable<IEnumerable<T>>
    {
        IEnumerable<T> _enumerable;
        int _partitionSize;
        public PartitioningEnumerable(IEnumerable<T> enumerable, int partitionSize)
        {
            _enumerable = enumerable;
            _partitionSize = partitionSize;
        }

        public IEnumerator<IEnumerable<T>> GetEnumerator()
        {
            return new PartitioningEnumerator<T>(_enumerable.GetEnumerator(), _partitionSize);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    class PartitioningEnumerator<T> : IEnumerator<IEnumerable<T>>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitioningEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
            _enumerator.Dispose();
        }

        IEnumerable<T> _current;
        public IEnumerable<T> Current
        {
            get { return _current; }
        }
        object IEnumerator.Current
        {
            get { return _current; }
        }

        public void Reset()
        {
            _current = null;
            _enumerator.Reset();
        }

        public bool MoveNext()
        {
            bool result;

            if (_enumerator.MoveNext())
            {
                _current = new PartitionEnumerable<T>(_enumerator, _partitionSize);
                result = true;
            }
            else
            {
                _current = null;
                result = false;
            }

            return result;
        }

    }



    class PartitionEnumerable<T> : IEnumerable<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        public PartitionEnumerable(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new PartitionEnumerator<T>(_enumerator, _partitionSize);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    class PartitionEnumerator<T> : IEnumerator<T>
    {
        IEnumerator<T> _enumerator;
        int _partitionSize;
        int _count;
        public PartitionEnumerator(IEnumerator<T> enumerator, int partitionSize)
        {
            _enumerator = enumerator;
            _partitionSize = partitionSize;
        }

        public void Dispose()
        {
        }

        public T Current
        {
            get { return _enumerator.Current; }
        }
        object IEnumerator.Current
        {
            get { return _enumerator.Current; }
        }
        public void Reset()
        {
            if (_count > 0) throw new InvalidOperationException();
        }

        public bool MoveNext()
        {
            bool result;

            if (_count < _partitionSize)
            {
                if (_count > 0)
                {
                    result = _enumerator.MoveNext();
                }
                else
                {
                    result = true;
                }
                _count++;
            }
            else
            {
                result = false;
            }

            return result;
        }

    }
}

สนุก!


เวอร์ชันนี้ผิดสัญญาของ IEnumerator ไม่ถูกต้องที่จะโยน InvalidOperationException เมื่อมีการเรียกรีเซ็ต - ฉันเชื่อว่าวิธีการขยาย LINQ หลายวิธีอาศัยพฤติกรรมนี้
ShadowChaser

1
@ShadowChaser ฉันคิดว่า Reset () ควรโยน NotSupportedException และทุกอย่างจะดี จากเอกสาร MSDN: "วิธีการรีเซ็ตมีไว้สำหรับความสามารถในการทำงานร่วมกันของ COM ซึ่งไม่จำเป็นต้องนำไปใช้ แต่ผู้ใช้สามารถโยน NotSupportedException แทนได้"
toong

@toong ว้าวคุณพูดถูก ไม่แน่ใจว่าหลังจากนี้ฉันพลาดไปได้อย่างไร
ShadowChaser

มันคือรถ! ฉันจำไม่ได้อย่างแน่นอน แต่ (เท่าที่ฉันจำได้) มันทำตามขั้นตอนที่ไม่ต้องการและอาจนำไปสู่ผลข้างเคียงที่น่าเกลียด (ด้วย datareader สำหรับอดีต) ทางออกที่ดีที่สุดอยู่ที่นี่ (Jeppe Stig Nielsen): stackoverflow.com/questions/13709626/…
SalientBrain

4

กระทู้น่าสนใจ. ในการรับ Split / Partition เวอร์ชันสตรีมมิ่งเราสามารถใช้ตัวนับและลำดับผลตอบแทนจากตัวแจงนับโดยใช้วิธีการขยาย การแปลงรหัสจำเป็นเป็นรหัสการทำงานโดยใช้ผลตอบแทนเป็นเทคนิคที่มีประสิทธิภาพมาก

อันดับแรกส่วนขยายตัวแจงนับที่เปลี่ยนจำนวนองค์ประกอบเป็นลำดับขี้เกียจ:

public static IEnumerable<T> TakeFromCurrent<T>(this IEnumerator<T> enumerator, int count)
{
    while (count > 0)
    {
        yield return enumerator.Current;
        if (--count > 0 && !enumerator.MoveNext()) yield break;
    }
}

จากนั้นส่วนขยายที่แจกแจงได้ซึ่งแบ่งพาร์ติชันลำดับ:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> seq, int partitionSize)
{
    var enumerator = seq.GetEnumerator();

    while (enumerator.MoveNext())
    {
        yield return enumerator.TakeFromCurrent(partitionSize);
    }
}

ผลลัพธ์ที่ได้คือการใช้งานที่มีประสิทธิภาพสูงสตรีมมิ่งและขี้เกียจซึ่งอาศัยโค้ดที่เรียบง่ายมาก

สนุก!


ตอนแรกฉันตั้งโปรแกรมสิ่งเดียวกัน แต่รูปแบบจะแตกเมื่อเรียกรีเซ็ตบนอินสแตนซ์ <T> ที่ซ้อนกันของ IEnumerable
ShadowChaser

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

@ แบรดมันจะ "ล้มเหลว" อย่างที่คุณคาดหวังคล้ายกับปัญหาบางอย่างในเธรดนี้stackoverflow.com/questions/419019/… (เฉพาะstackoverflow.com/a/20953521/1037948 )
drzaus

4

ฉันใช้สิ่งนี้:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> instance, int partitionSize)
{
    return instance
        .Select((value, index) => new { Index = index, Value = value })
        .GroupBy(i => i.Index / partitionSize)
        .Select(i => i.Select(i2 => i2.Value));
}

กรุณาอธิบายเหตุผล ฉันใช้ฟังก์ชั่นนี้โดยไม่มีปัญหา!
Elmer

อ่านคำถามอีกครั้งและดูว่าคุณได้ n (เกือบ) ส่วนที่มีความยาวเท่ากันกับฟังก์ชันของคุณหรือไม่
มูฮัมหมัดฮาซันข่าน

@Elmer คำตอบของคุณถูกต้อง แต่คำถามนั้นผิด คำตอบของคุณให้จำนวนชิ้นที่ไม่ทราบจำนวนที่มีขนาดคงที่สำหรับแต่ละชิ้น (ตรงกับ Partition ชื่อที่คุณตั้งให้) แต่ OP ต้องการฟังก์ชัน Split โดยให้จำนวนชิ้นคงที่โดยมีขนาดใด ๆ ต่อชิ้น (หวังว่าจะมีขนาดเท่ากันหรือใกล้เคียงกับขนาดที่เท่ากัน) บางทีอาจจะเหมาะกว่าที่นี่stackoverflow.com/questions/3773403/…
nawfal

ฉันคิดว่าคุณสามารถเปลี่ยน i.Index / partitionSize เป็น i.Index% partitionSize และรับผลลัพธ์ที่ร้องขอ ฉันชอบสิ่งนี้มากกว่าคำตอบที่ได้รับการยอมรับเนื่องจากมีขนาดกะทัดรัดและอ่านง่ายกว่า
Jake Drew

2

นี่คือประสิทธิภาพของหน่วยความจำและชะลอการดำเนินการให้มากที่สุด (ต่อชุด) และทำงานในเวลาเชิงเส้น O (n)

    public static IEnumerable<IEnumerable<T>> InBatchesOf<T>(this IEnumerable<T> items, int batchSize)
    {
        List<T> batch = new List<T>(batchSize);
        foreach (var item in items)
        {
            batch.Add(item);

            if (batch.Count >= batchSize)
            {
                yield return batch;
                batch = new List<T>();
            }
        }

        if (batch.Count != 0)
        {
            //can't be batch size or would've yielded above
            batch.TrimExcess();
            yield return batch;
        }
    }

2

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

static public IList<T[]> GetChunks<T>(this IEnumerable<T> source, int batchsize)
{
    IList<T[]> result = null;
    if (source != null && batchsize > 0)
    {
        var list = source as List<T> ?? source.ToList();
        if (list.Count > 0)
        {
            result = new List<T[]>();
            for (var index = 0; index < list.Count; index += batchsize)
            {
                var rangesize = Math.Min(batchsize, list.Count - index);
                result.Add(list.GetRange(index, rangesize).ToArray());
            }
        }
    }
    return result ?? Enumerable.Empty<T[]>().ToList();
}

static public void TestGetChunks()
{
    var ids = Enumerable.Range(1, 163).Select(i => i.ToString());
    foreach (var chunk in ids.GetChunks(20))
    {
        Console.WriteLine("[{0}]", String.Join(",", chunk));
    }
}

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


1
   protected List<List<int>> MySplit(int MaxNumber, int Divider)
        {
            List<List<int>> lst = new List<List<int>>();
            int ListCount = 0;
            int d = MaxNumber / Divider;
            lst.Add(new List<int>());
            for (int i = 1; i <= MaxNumber; i++)
            {
                lst[ListCount].Add(i);
                if (i != 0 && i % d == 0)
                {
                    ListCount++;
                    d += MaxNumber / Divider;
                    lst.Add(new List<int>());
                }
            }
            return lst;
        }

1

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

คำตอบของฉันยังทำให้ส่วนที่เหลือกระจายไปในลักษณะที่เป็นมาตรฐานมากขึ้น

 static class Program
{          
    static void Main(string[] args)
    {
        var input = new List<String>();
        for (int k = 0; k < 18; ++k)
        {
            input.Add(k.ToString());
        }
        var result = splitListIntoSmallerLists(input, 15);            
        int i = 0;
        foreach(var resul in result){
            Console.WriteLine("------Segment:" + i.ToString() + "--------");
            foreach(var res in resul){
                Console.WriteLine(res);
            }
            i++;
        }
        Console.ReadLine();
    }

    private static List<List<T>> splitListIntoSmallerLists<T>(List<T> i_bigList,int i_numberOfSmallerLists)
    {
        if (i_numberOfSmallerLists <= 0)
            throw new ArgumentOutOfRangeException("Illegal value of numberOfSmallLists");

        int normalizedSpreadRemainderCounter = 0;
        int normalizedSpreadNumber = 0;
        //e.g 7 /5 > 0 ==> output size is 5 , 2 /5 < 0 ==> output is 2          
        int minimumNumberOfPartsInEachSmallerList = i_bigList.Count / i_numberOfSmallerLists;                        
        int remainder = i_bigList.Count % i_numberOfSmallerLists;
        int outputSize = minimumNumberOfPartsInEachSmallerList > 0 ? i_numberOfSmallerLists : remainder;
        //In case remainder > 0 we want to spread the remainder equally between the others         
        if (remainder > 0)
        {
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                normalizedSpreadNumber = (int)Math.Floor((double)i_numberOfSmallerLists / remainder);    
            }
            else
            {
                normalizedSpreadNumber = 1;
            }   
        }
        List<List<T>> retVal = new List<List<T>>(outputSize);
        int inputIndex = 0;            
        for (int i = 0; i < outputSize; ++i)
        {
            retVal.Add(new List<T>());
            if (minimumNumberOfPartsInEachSmallerList > 0)
            {
                retVal[i].AddRange(i_bigList.GetRange(inputIndex, minimumNumberOfPartsInEachSmallerList));
                inputIndex += minimumNumberOfPartsInEachSmallerList;
            }
            //If we have remainder take one from it, if our counter is equal to normalizedSpreadNumber.
            if (remainder > 0)
            {
                if (normalizedSpreadRemainderCounter == normalizedSpreadNumber-1)
                {
                    retVal[i].Add(i_bigList[inputIndex]);
                    remainder--;
                    inputIndex++;
                    normalizedSpreadRemainderCounter=0;
                }
                else
                {
                    normalizedSpreadRemainderCounter++;
                }
            }
        }
        return retVal;
    }      

}

0

หากคำสั่งซื้อในส่วนเหล่านี้ไม่สำคัญมากคุณสามารถลองสิ่งนี้:

int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int n = 3;

var result =
   array.Select((value, index) => new { Value = value, Index = index }).GroupBy(i => i.Index % n, i => i.Value);

// or
var result2 =
   from i in array.Select((value, index) => new { Value = value, Index = index })
   group i.Value by i.Index % n into g
   select g;

อย่างไรก็ตามสิ่งเหล่านี้ไม่สามารถส่งไปยัง IEnumerable <IEnumerable <int>> ด้วยเหตุผลบางประการ ...


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

0

นี่คือรหัสของฉันดีและสั้น

 <Extension()> Public Function Chunk(Of T)(ByVal this As IList(Of T), ByVal size As Integer) As List(Of List(Of T))
     Dim result As New List(Of List(Of T))
     For i = 0 To CInt(Math.Ceiling(this.Count / size)) - 1
         result.Add(New List(Of T)(this.GetRange(i * size, Math.Min(size, this.Count - (i * size)))))
     Next
     Return result
 End Function

0

นี่คือวิธีของฉันแสดงรายการและแบ่งแถวตามคอลัมน์

  int repat_count=4;

  arrItems.ForEach((x, i) => {
    if (i % repat_count == 0) 
        row = tbo.NewElement(el_tr, cls_min_height);
    var td = row.NewElement(el_td);
    td.innerHTML = x.Name;
  });

0

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

List<int> sequence = new List<int>();
for (int i = 0; i < 2000; i++)
{
     sequence.Add(i);
}
int splitIndex = 900;
List<List<int>> splitted = new List<List<int>>();
while (sequence.Count != 0)
{
    splitted.Add(sequence.Take(splitIndex).ToList() );
    sequence.RemoveRange(0, Math.Min(splitIndex, sequence.Count));
}

ลองครั้งต่อไป: var nrs = Enumerable.Range (1,2000) .ToList ();
MBoros

0

นี่คือการปรับแต่งเล็กน้อยสำหรับจำนวนรายการแทนที่จะเป็นจำนวนชิ้นส่วน:

public static class MiscExctensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int nbItems)
    {
        return (
            list
            .Select((o, n) => new { o, n })
            .GroupBy(g => (int)(g.n / nbItems))
            .Select(g => g.Select(x => x.o))
        );
    }
}

-1
int[] items = new int[] { 0,1,2,3,4,5,6,7,8,9, 10 };

int itemIndex = 0;
int groupSize = 2;
int nextGroup = groupSize;

var seqItems = from aItem in items
               group aItem by 
                            (itemIndex++ < nextGroup) 
                            ? 
                            nextGroup / groupSize
                            :
                            (nextGroup += groupSize) / groupSize
                            into itemGroup
               select itemGroup.AsEnumerable();

-1

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

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

using System.Collections.Generic;

public static class EnumerableExtensions
{
    /// <summary>
    /// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once
    /// </summary>
    /// <typeparam name="T">The element type</typeparam>
    /// <param name="enumerable">The source enumerable</param>
    /// <param name="pageSize">The number of elements to return in each page</param>
    /// <returns></returns>
    public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int pageSize)
    {
        var enumerator = enumerable.GetEnumerator();

        while (enumerator.MoveNext())
        {
            var indexWithinPage = new IntByRef { Value = 0 };

            yield return SubPartition(enumerator, pageSize, indexWithinPage);

            // Continue iterating through any remaining items in the page, to align with the start of the next page
            for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++)
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }
            }
        }
    }

    private static IEnumerable<T> SubPartition<T>(IEnumerator<T> enumerator, int pageSize, IntByRef index)
    {
        for (; index.Value < pageSize; index.Value++)
        {
            yield return enumerator.Current;

            if (!enumerator.MoveNext())
            {
                yield break;
            }
        }
    }

    private class IntByRef
    {
        public int Value { get; set; }
    }
}

นี่ใช้ไม่ได้เลย! ที่ดีที่สุดอยู่ที่นี่stackoverflow.com/questions/13709626/… ! ดูความคิดเห็น
SalientBrain
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.