Existe-t-il un moyen efficace de diviser une collection en 'n' parties avec LINQ ? Pas nécessairement même bien sûr
- Diviser une liste en sous-listes avec LINQ (5 réponses )
Réponses
Trop de publicités?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;
}
}
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);
}
}
Ok, je vais jeter mon chapeau dans le ring. Les avantages de mon algorithme :
- Pas d'opérateurs coûteux de multiplication, de division ou de modulus.
- Toutes les opérations sont O(1) (voir note ci-dessous)
- Fonctionne pour la source IEnumerable<> (pas besoin de la propriété Count)
- 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).
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