วิธีที่สะอาดในการแบ่ง DataTable ออกเป็นชิ้นขนาดคงที่ด้วย Linq คืออะไร

อัปเดต: นี่คือคำถามที่คล้ายกัน


สมมติว่าฉันมี DataTable โดยมี DataRows สองสามพันอยู่ในนั้น

ฉันต้องการแบ่งตารางออกเป็นแถวเล็กๆ เพื่อประมวลผล

ฉันคิดว่าความสามารถที่ดีขึ้นในการทำงานกับข้อมูลของ C#3 อาจช่วยได้

นี่คือโครงกระดูกที่ฉันมี:

DataTable Table = GetTonsOfData();

// Chunks should be any IEnumerable<Chunk> type
var Chunks = ChunkifyTableIntoSmallerChunksSomehow; // ** help here! **

foreach(var Chunk in Chunks)
{
   // Chunk should be any IEnumerable<DataRow> type
   ProcessChunk(Chunk);
}

มีข้อเสนอแนะเกี่ยวกับสิ่งที่ควรแทนที่ ChunkifyTableIntoSmallerChunksSomehow หรือไม่

ฉันสนใจจริงๆ ว่าใครบางคนจะทำสิ่งนี้ด้วยเครื่องมือเข้าถึง C#3 ได้อย่างไร หากการพยายามใช้เครื่องมือเหล่านี้ไม่เหมาะสม โปรดอธิบาย!


อัปเดต 3 (แก้ไขส่วนที่ฉันต้องการตารางจริงๆ ไม่ใช่นับไม่ถ้วน ใช้วิธีการขยาย - ขอบคุณ Jacob):

การใช้งานขั้นสุดท้าย:

วิธีการขยายเพื่อจัดการกับการแบ่งส่วน:

public static class HarenExtensions
{
    public static IEnumerable<DataTable> Chunkify(this DataTable table, int chunkSize)
    {
        for (int i = 0; i < table.Rows.Count; i += chunkSize)
        {
            DataTable Chunk = table.Clone();

            foreach (DataRow Row in table.Select().Skip(i).Take(chunkSize))
            {
                Chunk.ImportRow(Row);
            }

            yield return Chunk;
        }
    }
}

ตัวอย่างผู้บริโภคของวิธีการขยายนั้น พร้อมเอาต์พุตตัวอย่างจากการทดสอบเฉพาะกิจ:

class Program
{
    static void Main(string[] args)
    {
        DataTable Table = GetTonsOfData();

        foreach (DataTable Chunk in Table.Chunkify(100))
        {
            Console.WriteLine("{0} - {1}", Chunk.Rows[0][0], Chunk.Rows[Chunk.Rows.Count - 1][0]);
        }

        Console.ReadLine();
    }

    static DataTable GetTonsOfData()
    {
        DataTable Table = new DataTable();
        Table.Columns.Add(new DataColumn());

        for (int i = 0; i < 1000; i++)
        {
            DataRow Row = Table.NewRow();
            Row[0] = i;

            Table.Rows.Add(Row);
        }

        return Table;
    }
}

person Michael Haren    schedule 20.04.2009    source แหล่งที่มา
comment
ฉันไม่ได้ใช้สิ่งนี้เพื่อเพจ แต่ฉันเพิ่งตระหนักถึงแอปพลิเคชันแบบขนาน หากคุณพบข้อมูลซ้ำที่ดีที่ใช้ที่นี่ โปรดแจ้งให้เราทราบ แล้วฉันจะปิดคำถาม   -  person Michael Haren    schedule 20.04.2009
comment
คุณสามารถอินไลน์ได้โดยการสร้างวิธีการขยายที่ทำข้างต้น จากนั้นคุณสามารถใช้ var Chunks = from chunk ใน table.Chunkify select chunk;   -  person Jacob Proffitt    schedule 20.04.2009


คำตอบ (5)


ดูเหมือนว่าจะเป็นกรณีการใช้งานในอุดมคติสำหรับวิธี Skip and Take ของ Linq ขึ้นอยู่กับสิ่งที่คุณต้องการบรรลุด้วยการแยกชิ้นส่วน สิ่งนี้ยังไม่ผ่านการทดสอบโดยสิ้นเชิง ไม่เคยป้อนรหัส IDE แต่วิธีการของคุณอาจมีลักษณะเช่นนี้

private List<List<DataRow>> ChunkifyTable(DataTable table, int chunkSize)
{
    List<List<DataRow>> chunks = new List<List<DataRow>>();
    for (int i = 0; i < table.Rows.Count / chunkSize; i++)
    {
        chunks.Add(table.Rows.Skip(i * chunkSize).Take(chunkSize).ToList());
    }
    
    return chunks;
}
person Jacob Proffitt    schedule 20.04.2009
comment
@Jacob: ขอบคุณสำหรับข้อเสนอแนะ ฉันสร้างตารางส่งคืนนี้และใช้ IEnumerable กับคีย์เวิร์ด Yield ฉันยังเปลี่ยนเป็นวิธีการขยายตามที่คุณแนะนำด้วย ใช้งานได้ดี! - person Michael Haren; 20.04.2009
comment
สิ่งนี้ใช้งานไม่ได้สำหรับชุดข้อมูลที่ไม่สามารถหารด้วย chunkSize ได้เท่าๆ กัน 1/10 = 0 เมื่อใช้ ints ดังนั้นส่วนสุดท้ายของตารางจึงถูกตัดออก - person N_A; 20.02.2015
comment
ใช่คุณมีประเด็น อย่างน้อยที่สุด เมื่อจำนวนแถวน้อยกว่า chunksize มันจะล้มเหลวในการทดสอบครั้งแรก (เนื่องจาก i จะเป็นศูนย์และไม่ใช่ ‹ ศูนย์ ดังนั้น for loop จะล้มเหลวในการป้อน) ฉันเพิ่มขึ้นหลังจากการประเมิน ดังนั้น 11/10 จะทำงานสองครั้งและรับทั้งหมด 11 - person Jacob Proffitt; 20.02.2015
comment
นี่คือโค้ดเวอร์ชัน c# และ vb.net โดยไม่ใช้ LINQ ซึ่งใช้ได้กับฉัน แยกข้อมูลออกเป็นชิ้น ๆ - person Jeff D; 13.04.2017

สิ่งนี้สามารถอ่านได้ค่อนข้างมากและวนซ้ำตามลำดับเพียงครั้งเดียวเท่านั้น บางทีอาจช่วยให้คุณประหยัดคุณลักษณะประสิทธิภาพที่ไม่ดีของการเรียก Skip() / Take() ที่ซ้ำซ้อนซ้ำซ้อน:

public IEnumerable<IEnumerable<DataRow>> Chunkify(DataTable table, int size)
{
    List<DataRow> chunk = new List<DataRow>(size);

    foreach (var row in table.Rows)
    {
        chunk.Add(row);
        if (chunk.Count == size)
        {
            yield return chunk;
            chunk = new List<DataRow>(size);
        }
    }

    if(chunk.Any()) yield return chunk;
}
person mqp    schedule 20.04.2009
comment
นี่เป็นแนวทางที่สามารถอ่านได้มากที่สุดอย่างแน่นอน แต่จะบังคับให้สร้างรายการในหน่วยความจำสำหรับแต่ละส่วน ก็ถือว่าเหมาะสมกับชิ้นเล็กๆ แต่ไม่เหมาะกับชิ้นใหญ่ นอกจากนี้ ฉันจะล้างชิ้นส่วนแทนที่จะสร้างชิ้นใหม่ทุกครั้ง ฉันคิดว่าทางออกที่ดีที่สุดน่าจะสร้างตัววนซ้ำซึ่งมีตัววนซ้ำย่อย แต่นั่นไม่ใช่เรื่องง่ายที่จะอ่านอย่างแน่นอน - person Damian Powell; 26.09.2009

ต่อไปนี้เป็นแนวทางที่อาจใช้ได้ผล:

public static class Extensions
{
    public static IEnumerable<IEnumerable<T>> InPages<T>(this IEnumerable<T> enumOfT, int pageSize)
    {
        if (null == enumOfT) throw new ArgumentNullException("enumOfT");
        if (pageSize < 1) throw new ArgumentOutOfRangeException("pageSize");
        var enumerator = enumOfT.GetEnumerator();
        while (enumerator.MoveNext())
        {
            yield return InPagesInternal(enumerator, pageSize);
        }
    }
    private static IEnumerable<T> InPagesInternal<T>(IEnumerator<T> enumeratorOfT, int pageSize)
    {
        var count = 0;
        while (true)
        {
            yield return enumeratorOfT.Current;
            if (++count >= pageSize) yield break;
            if (false == enumeratorOfT.MoveNext()) yield break;
        }
    }
    public static string Join<T>(this IEnumerable<T> enumOfT, object separator)
    {
        var sb = new StringBuilder();
        if (enumOfT.Any())
        {
            sb.Append(enumOfT.First());
            foreach (var item in enumOfT.Skip(1))
            {
                sb.Append(separator).Append(item);
            }
        }
        return sb.ToString();
    }
}
[TestFixture]
public class Tests
{
    [Test]
    public void Test()
    {
        // Arrange
        var ints = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        var expected = new[]
        {
            new[] { 1, 2, 3 },
            new[] { 4, 5, 6 },
            new[] { 7, 8, 9 },
            new[] { 10      },
        };

        // Act
        var pages = ints.InPages(3);

        // Assert
        var expectedString = (from x in expected select x.Join(",")).Join(" ; ");
        var pagesString = (from x in pages select x.Join(",")).Join(" ; ");

        Console.WriteLine("Expected : " + expectedString);
        Console.WriteLine("Pages    : " + pagesString);

        Assert.That(pagesString, Is.EqualTo(expectedString));
    }
}
person Damian Powell    schedule 26.09.2009

เจค็อบเขียน

ดูเหมือนว่าจะเป็นกรณีการใช้งานในอุดมคติสำหรับวิธี Skip and Take ของ Linq ขึ้นอยู่กับสิ่งที่คุณต้องการบรรลุด้วยการแยกชิ้นส่วน สิ่งนี้ยังไม่ผ่านการทดสอบโดยสิ้นเชิง ไม่เคยป้อนรหัส IDE แต่วิธีการของคุณอาจมีลักษณะเช่นนี้

private List<List<DataRow>> ChunkifyTable(DataTable table, int chunkSize)
{
    List<List<DataRow>> chunks = new List<List<DaraRow>>();
    for (int i = 0; i < table.Rows.Count / chunkSize; i++)
    {
        chunks.Add(table.Rows.Skip(i * chunkSize).Take(chunkSize).ToList());
    }

    return chunks;
}

ขอบคุณสำหรับ Jacob นี้ - มีประโยชน์สำหรับฉัน แต่ฉันคิดว่าการทดสอบในตัวอย่างของคุณควรเป็น ‹= ไม่ใช่ ‹ หากคุณใช้ ‹ และจำนวนแถวน้อยกว่า chunkSize จะไม่มีการวนซ้ำ ในทำนองเดียวกันไม่ได้จับชิ้นส่วนสุดท้าย แต่จะจับได้เฉพาะชิ้นส่วนที่เต็มเท่านั้น ตามที่คุณระบุไว้ ตัวอย่างยังไม่ผ่านการทดสอบ ฯลฯ ดังนั้นนี่เป็นเพียง FYI ในกรณีที่มีคนอื่นใช้รหัสของคุณแบบคำต่อคำ ;-)

person David Clarke    schedule 28.04.2010

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

public static IEnumerable<IEnumerable<DataRow>> Chunkify(
    this DataTable dataTable, int chunkSize)
{
    for (int i = 0; i < dataTable.Rows.Count; i += chunkSize)
    {
        yield return GetChunk(i, Math.Min(i + chunkSize, dataTable.Rows.Count));
    }
    IEnumerable<DataRow> GetChunk(int from, int toExclusive)
    {
        for (int j = from; j < toExclusive; j++)
        {
            yield return dataTable.Rows[j];
        }
    }
}

ตัวอย่างการใช้งาน:

var dataTable = GetTonsOfData();
foreach (var chunk in dataTable.Chunkify(1000))
{
    Console.WriteLine($"Processing chunk of {chunk.Count()} rows");
    foreach (var dataRow in chunk)
    {
        Console.WriteLine(dataRow[0]);
    }
}
person Theodor Zoulias    schedule 25.07.2019