Je veux prendre un IEnumerable<T>
et le diviser en morceaux de taille fixe.
Je l'ai, mais cela semble inélégant en raison de toute la création/copie de la liste:
private static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
List<T> partition = new List<T>(partitionSize);
foreach (T item in items)
{
partition.Add(item);
if (partition.Count == partitionSize)
{
yield return partition;
partition = new List<T>(partitionSize);
}
}
// Cope with items.Count % partitionSize != 0
if (partition.Count > 0) yield return partition;
}
Y a-t-il quelque chose de plus idiomatique?
EDIT: Bien que cela ait été marqué comme un doublon de Diviser le tableau en un tableau de sous-tablea ce n'est pas le cas - cette question concerne le fractionnement d'un tableau, alors qu'il s'agit de IEnumerable<T>
. De plus, cette question nécessite que la dernière sous-séquence soit complétée. Les deux questions sont étroitement liées mais ne sont pas les mêmes.
Vous pouvez essayer d'implémenter vous-même la méthode Batch mentionnée ci-dessus comme ceci:
static class MyLinqExtensions
{
public static IEnumerable<IEnumerable<T>> Batch<T>(
this IEnumerable<T> source, int batchSize)
{
using (var enumerator = source.GetEnumerator())
while (enumerator.MoveNext())
yield return YieldBatchElements(enumerator, batchSize - 1);
}
private static IEnumerable<T> YieldBatchElements<T>(
IEnumerator<T> source, int batchSize)
{
yield return source.Current;
for (int i = 0; i < batchSize && source.MoveNext(); i++)
yield return source.Current;
}
}
J'ai récupéré ce code depuis http://blogs.msdn.com/b/pfxteam/archive/2012/11/16/plinq-and-int32-maxvalue.aspx .
[~ # ~] mise à jour [~ # ~] : veuillez noter que cette implémentation évalue non seulement les lots mais aussi les articles à l'intérieur des lots, ce qui signifie produira uniquement des résultats corrects lorsque le lot est énuméré uniquement après que tous les lots précédents ont été énumérés. Par exemple:
public static void Main(string[] args)
{
var xs = Enumerable.Range(1, 20);
Print(xs.Batch(5).Skip(1)); // should skip first batch with 5 elements
}
public static void Print<T>(IEnumerable<IEnumerable<T>> batches)
{
foreach (var batch in batches)
{
Console.WriteLine($"[{string.Join(", ", batch)}]");
}
}
affichera:
[2, 3, 4, 5, 6] //only first element is skipped.
[7, 8, 9, 10, 11]
[12, 13, 14, 15, 16]
[17, 18, 19, 20]
Donc, si vous utilisez le cas suppose que le traitement par lots lorsque les lots sont évalués séquentiellement, alors la solution paresseuse ci-dessus fonctionnera, sinon si vous ne pouvez pas garantir un traitement par lots strictement séquentiel (par exemple lorsque vous souhaitez traiter des lots en parallèle), vous aurez probablement besoin d'une solution qui énumère avec impatience le contenu du lot, similaire à celui mentionné dans la question ci-dessus ou dans le MoreLINQ
C'est comme si vous vouliez deux blocs itérateurs ("yield return
méthodes "). J'ai écrit cette méthode d'extension:
static class Extensions
{
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
return new PartitionHelper<T>(items, partitionSize);
}
private sealed class PartitionHelper<T> : IEnumerable<IEnumerable<T>>
{
readonly IEnumerable<T> items;
readonly int partitionSize;
bool hasMoreItems;
internal PartitionHelper(IEnumerable<T> i, int ps)
{
items = i;
partitionSize = ps;
}
public IEnumerator<IEnumerable<T>> GetEnumerator()
{
using (var enumerator = items.GetEnumerator())
{
hasMoreItems = enumerator.MoveNext();
while (hasMoreItems)
yield return GetNextBatch(enumerator).ToList();
}
}
IEnumerable<T> GetNextBatch(IEnumerator<T> enumerator)
{
for (int i = 0; i < partitionSize; ++i)
{
yield return enumerator.Current;
hasMoreItems = enumerator.MoveNext();
if (!hasMoreItems)
yield break;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
Peut être?
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
return items.Select((item, inx) => new { item, inx })
.GroupBy(x => x.inx / partitionSize)
.Select(g => g.Select(x => x.item));
}
Il y en a un déjà implémenté aussi: morelinq's Batch.
Solution la plus folle (avec Extensions réactives ):
public static IEnumerable<IList<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
return items
.ToObservable() // Converting sequence to observable sequence
.Buffer(partitionSize) // Splitting it on spececified "partitions"
.ToEnumerable(); // Converting it back to ordinary sequence
}
Je sais que j'ai changé de signature mais de toute façon nous savons tous que nous aurons une collection de taille fixe en tant que morceau.
BTW si vous utiliserez le bloc itérateur, n'oubliez pas de diviser votre implémentation en deux méthodes pour valider les arguments avec impatience!
Pour une solution élégante, vous pouvez également consulter MoreLinq.Batch.
Il regroupe la séquence source dans des compartiments de taille.
Exemple:
int[] ints = new int[] {1,2,3,4,5,6};
var batches = ints.Batch(2); // batches -> [0] : 1,2 ; [1]:3,4 ; [2] :5,6
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items,
int partitionSize)
{
int i = 0;
return items.GroupBy(x => i++ / partitionSize).ToArray();
}
Vous pouvez le faire en utilisant une surcharge de Enumerable.GroupBy
et en profitant de la division entière.
return items.Select((element, index) => new { Element = element, Index = index })
.GroupBy(obj => obj.Index / partitionSize, (_, partition) => partition);
Qu'en est-il des classes de partitionnement dans l'espace de noms System.Collections.Concurrent ?