使用Linq将DataTable分解为固定大小的块的简洁方法是什么?
更新:这是一个类似的问题
假设我有一个DataTable
,里面有几千个DataRows
。
我想将表拆分成较小行的块进行处理。
我认为C#3改进的数据处理能力可能有所帮助。
这是我到目前为止的骨架:
DataTable Table = GetTonsOfData(); // Chunks should be any IEnumerable type var Chunks = ChunkifyTableIntoSmallerChunksSomehow; // ** help here! ** foreach(var Chunk in Chunks) { // Chunk should be any IEnumerable type ProcessChunk(Chunk); }
关于什么应该取代ChunkifyTableIntoSmallerChunksSomehow
任何建议?
我真的很感兴趣的是如何通过访问C#3工具来实现这一目标。 如果试图应用这些工具是不合适的,请解释!
更新3(修改后的块,因为我真的想要表,而不是ienumerables;使用扩展方法 – 感谢雅各布):
最终实施:
处理分块的扩展方法:
public static class HarenExtensions { public static IEnumerable 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; } }
对于Linq的Skip and Take方法来说,这似乎是一个理想的用例,具体取决于你想要通过分块实现的目标。 这是完全未经测试的,从未在IDE代码中输入,但您的方法可能看起来像这样。
private List> ChunkifyTable(DataTable table, int chunkSize) { List> chunks = new List>(); for (int i = 0; i < table.Rows.Count / chunkSize; i++) { chunks.Add(table.Rows.Skip(i * chunkSize).Take(chunkSize).ToList()); } return chunks; }
这是非常易读的,只能迭代序列一次,也许可以省去重复冗余Skip()
/ Take()
调用的相当糟糕的性能特征:
public IEnumerable> Chunkify(DataTable table, int size) { List chunk = new List (size); foreach (var row in table.Rows) { chunk.Add(row); if (chunk.Count == size) { yield return chunk; chunk = new List (size); } } if(chunk.Any()) yield return chunk; }
这是一种可行的方法:
public static class Extensions { public static IEnumerable> InPages(this IEnumerable 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 InPagesInternal (IEnumerator 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 (this IEnumerable 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)); } }
雅各布写道
对于Linq的Skip and Take方法来说,这似乎是一个理想的用例,具体取决于你想要通过分块实现的目标。 这是完全未经测试的,从未在IDE代码中输入,但您的方法可能看起来像这样。
private List> ChunkifyTable(DataTable table, int chunkSize) { List> chunks = new List>(); for (int i = 0; i < table.Rows.Count / chunkSize; i++) { chunks.Add(table.Rows.Skip(i * chunkSize).Take(chunkSize).ToList()); } return chunks; }
感谢Jacob - 对我有用,但我认为你的例子中的测试应该是<= not <。 如果使用<并且行数小于chunkSize ,则永远不会输入循环。 类似地,不捕获最后的部分块,仅捕获完整的块。 正如你所说,这个例子是未经测试的,所以这只是一个FYI,以防其他人逐字使用你的代码;-)