10 votes

Quel est le moyen le plus simple de diviser une table de données en morceaux de taille fixe avec Linq ?

Mise à jour : Voici un question similaire


Supposons que j'ai un DataTable avec quelques milliers DataRows en elle.

J'aimerais décomposer le tableau en morceaux de rangées plus petites pour le traitement.

J'ai pensé que la capacité améliorée de C#3 à travailler avec des données pourrait aider.

C'est le squelette que j'ai jusqu'à présent :

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

Des suggestions sur ce qui devrait remplacer ChunkifyTableIntoSmallerChunksSomehow ?

Je suis vraiment intéressé de savoir comment quelqu'un pourrait faire cela avec les outils d'accès à C#3. Si la tentative d'application de ces outils est inappropriée, veuillez m'expliquer !


Mise à jour 3 (révision du chunking car je veux vraiment des tables, pas des ienumerables ; utilisation d'une méthode d'extension - merci Jacob) :

Mise en œuvre finale :

Méthode d'extension pour gérer le chunking :

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

Exemple d'utilisation de cette méthode d'extension, avec un exemple de résultat d'un test ad hoc :

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

7voto

mquander Points 32650

Cette méthode est assez lisible et n'itère qu'une seule fois dans la séquence, ce qui permet d'éviter les mauvaises performances liées à la répétition d'éléments redondants. Skip() / Take() appels :

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

5voto

Jacob Proffitt Points 8187

Cela semble être un cas d'utilisation idéal pour les méthodes Skip et Take de Linq, en fonction de ce que vous voulez réaliser avec le chunking. Ceci est complètement non testé, jamais entré dans un code IDE, mais votre méthode pourrait ressembler à quelque chose comme ceci.

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

0voto

Damian Powell Points 4156

Voici une approche qui pourrait fonctionner :

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

0voto

David Clarke Points 3165

Jacob a écrit

Cela semble être un cas d'utilisation idéal pour les méthodes Skip et Take de Linq, en fonction de ce que vous voulez réaliser avec le chunking. Ceci est complètement non testé, jamais entré dans un IDE mais votre méthode pourrait ressembler à à quelque chose comme ça.

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

Merci pour cet article Jacob - il m'est utile mais je pense que le test dans votre exemple devrait être <= et non <. Si vous utilisez < et que le nombre de lignes est inférieur à chunkSize la boucle n'est jamais entrée. De même, le dernier morceau partiel n'est pas capturé, seuls les morceaux complets le sont. Comme vous l'avez dit, l'exemple n'a pas été testé, etc. Il s'agit donc d'une simple information pour le cas où quelqu'un d'autre utiliserait votre code mot pour mot ;-)

0voto

Theodor Zoulias Points 1088

Voici une approche complètement différente. Aucune mémoire n'est allouée pour les chunks.

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

Exemple d'utilisation :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X