123 votes

Diviser une collection en n parties avec LINQ ?

Existe-t-il un moyen efficace de diviser une collection en 'n' parties avec LINQ ? Pas nécessairement même bien sûr

132voto

Hasan Khan Points 20723

Une solution purement linq et la plus simple est la suivante.

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

61voto

Jon Skeet Points 692016

EDIT : Ok, il semble que j'ai mal lu la question. Je l'ai lu comme "pièces de longueur n" plutôt que "n pièces". Doh ! J'envisage de supprimer la réponse...

(Réponse originale)

Je ne crois pas qu'il y ait une façon intégrée de partitionner, bien que j'aie l'intention d'en écrire une dans mon ensemble d'ajouts à LINQ to Objects. Marc Gravell a un mise en œuvre ici bien que je la modifierais probablement pour qu'elle renvoie une vue en lecture seule :

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

47voto

manu08 Points 1572
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));
    }
}

28voto

Mike Points 1832

Ok, je vais jeter mon chapeau dans le ring. Les avantages de mon algorithme :

  1. Pas d'opérateurs coûteux de multiplication, de division ou de modulus.
  2. Toutes les opérations sont O(1) (voir note ci-dessous)
  3. Fonctionne pour la source IEnumerable<> (pas besoin de la propriété Count)
  4. Simple

Le code :

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

Comme indiqué dans les commentaires ci-dessous, cette approche ne répond pas réellement à la question originale qui demandait un nombre fixe de sections de longueur approximativement égale. Cela dit, vous pouvez toujours utiliser mon approche pour résoudre la question originale en l'appelant de cette façon :

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

Utilisée de cette manière, l'approche n'est plus O(1) puisque l'opération Count() est O(N).

17voto

nawfal Points 13500

C'est la même chose que la réponse acceptée, mais une représentation beaucoup plus simple :

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

La méthode ci-dessus divise un IEnumerable<T> en un nombre N de morceaux de taille égale ou proche de la taille égale.

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

La méthode ci-dessus divise un IEnumerable<T> en morceaux de taille fixe souhaitée, le nombre total de morceaux étant sans importance - ce qui n'est pas le sujet de la question.

Le problème avec le Split en plus d'être plus lente, est qu'elle brouille le résultat dans le sens où le regroupement sera fait sur la base du iième multiple de N pour chaque position, ou en d'autres termes, vous n'obtiendrez pas les morceaux dans l'ordre original.

Presque toutes les réponses ici ne préservent pas l'ordre, ou concernent la partition et non la division, ou sont tout simplement fausses. Essayez ceci qui est plus rapide, préserve l'ordre mais est un peu plus verbeux :

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

La méthode équivalente pour un Partition opération ici

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