Какой простой способ разбить DataTable на куски фиксированного размера с помощью Linq? - PullRequest
7 голосов
/ 20 апреля 2009

Обновление: Вот похожий вопрос


Предположим, у меня есть 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 (пересмотрено разбиение на части, так как я действительно хочу таблицы, а не перечисляемые; переход с методом расширения - спасибо Джейкоб):

Окончательная реализация:

Метод расширения для обработки фрагментов:

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

Ответы [ 4 ]

6 голосов
/ 20 апреля 2009

Это вполне читабельно и повторяет последовательность только один раз, возможно, сохраняя вам довольно плохие характеристики производительности при повторных избыточных 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;
}
6 голосов
/ 20 апреля 2009

Это похоже на идеальный вариант использования методов пропуска и приема 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;
}
0 голосов
/ 29 апреля 2010

Иаков написал

Это похоже на идеальный вариант использования для Методы 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;
}

Спасибо за это, Джейкоб - полезно для меня, но я думаю, что тест в вашем примере должен быть <= не <. Если вы используете <и число строк меньше <em>chunkSize , цикл никогда не будет введен. Точно так же последний частичный фрагмент не захватывается, только полные фрагменты. Как вы заявили, пример не проверен и т. Д., Так что это просто FYI на тот случай, если кто-то другой дословно использует ваш код;

0 голосов
/ 26 сентября 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));
    }
}
...