J'essaie de diviser une liste en une série de listes plus petites.
Mon problème: ma fonction de fractionner les listes ne les divise pas en listes de la bonne taille. Il devrait les diviser en listes de taille 30 mais les diviser en listes de taille 114?
Comment puis-je diviser une liste en fonction de X listes de taille 30 ou moins ?
public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30)
{
List<List<float[]>> list = new List<List<float[]>>();
for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) {
List <float[]> subLocat = new List <float[]>(locations);
if (subLocat.Count >= ((i*nSize)+nSize))
subLocat.RemoveRange(i*nSize, nSize);
else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize));
Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString());
list.Add (subLocat);
}
return list;
}
Si j'utilise la fonction sur une liste de taille 144 alors la sortie est:
Index: 4, Taille: 120
Index: 3, Taille: 114
Index: 2, Taille: 114
Index: 1, Taille: 114
Index: 0, Taille: 114
Réponses:
Version générique:
la source
GetRange(3, 3)
Je suggérerais d'utiliser cette méthode d'extension pour découper la liste source en sous-listes par taille de bloc spécifiée:
Par exemple, si vous découpez la liste de 18 éléments par 5 éléments par bloc, cela vous donne la liste de 4 sous-listes avec les éléments suivants à l'intérieur: 5-5-5-3.
la source
ToList()
s et de laisser l'évaluation paresseuse faire sa magie.que diriez-vous:
la source
ToList
mais je ne prendrais pas la peine d'essayer de l'optimiser - c'est si trivial et peu probable qu'il y ait un goulot d'étranglement. Le principal avantage de cette implémentation est sa simplicité, elle est facile à comprendre. Si vous le souhaitez, vous pouvez utiliser la réponse acceptée, elle ne crée pas ces listes mais est un peu plus complexe..Skip(n)
itère sur lesn
éléments chaque fois qu'il est appelé, bien que cela puisse être correct, il est important de prendre en compte le code critique pour les performances. stackoverflow.com/questions/20002975/….Skip()
s dans la base de code de mon entreprise, et même s'ils ne sont pas «optimaux», ils fonctionnent très bien. Des choses comme les opérations DB prennent de toute façon beaucoup plus de temps. Mais je pense que c'est une chose importante à noter que.Skip()
"touche" chaque élément <n sur son chemin au lieu de sauter directement au n-élément (comme on pourrait s'y attendre). Si votre itérateur a des effets secondaires en touchant un élément, cela.Skip()
peut être la cause de bogues difficiles à trouver.La solution Serj-Tm est très bien, c'est aussi la version générique comme méthode d'extension pour les listes (mettez-la dans une classe statique):
la source
Je trouve la réponse acceptée (Serj-Tm) la plus robuste, mais je voudrais suggérer une version générique.
la source
La bibliothèque MoreLinq a une méthode appelée
Batch
Le résultat est
ids
sont divisés en 5 morceaux avec 2 éléments.la source
J'ai une méthode générique qui prendrait tous les types, y compris float, et elle a été testée à l'unité, j'espère que cela aide:
la source
values.Count()
entraînera une énumération complète, puisvalues.ToList()
une autre. Plus sûrvalues = values.ToList()
, c'est déjà matérialisé.Alors que de nombreuses réponses ci-dessus font le travail, elles échouent toutes horriblement sur une séquence sans fin (ou une très longue séquence). Ce qui suit est une implémentation entièrement en ligne qui garantit le meilleur temps et la complexité de mémoire possible. Nous ne répétons la source énumérable qu'une seule fois et utilisons return return pour une évaluation paresseuse. Le consommateur pourrait jeter la liste à chaque itération, ce qui rend l'empreinte mémoire égale à celle de la liste avec le
batchSize
nombre d'éléments.EDIT: Je viens de réaliser que l'OP demande de diviser un
List<T>
en plus petitList<T>
, donc mes commentaires concernant les énumérations infinies ne s'appliquent pas à l'OP, mais peuvent aider ceux qui se retrouvent ici. Ces commentaires étaient en réponse à d'autres solutions publiées qui utilisentIEnumerable<T>
comme entrée pour leur fonction, mais énumèrent la source énumérable plusieurs fois.la source
IEnumerable<IEnumerable<T>>
version est meilleure car elle n'implique pas beaucoup deList
construction.IEnumerable<IEnumerable<T>>
est que l'implémentation est susceptible de s'appuyer sur le consommateur énumérant entièrement chaque énumérable interne produit. Je suis sûr qu'une solution pourrait être formulée de manière à éviter ce problème, mais je pense que le code résultant pourrait devenir complexe assez rapidement. De plus, comme c'est paresseux, nous ne générons qu'une seule liste à la fois et l'allocation de mémoire se produit exactement une fois par liste, car nous connaissons la taille à l'avance.Ajout après un commentaire très utile de mhand à la fin
Réponse originale
Bien que la plupart des solutions puissent fonctionner, je pense qu'elles ne sont pas très efficaces. Supposons que vous ne souhaitiez que les premiers éléments des premiers morceaux. Ensuite, vous ne voudriez pas parcourir tous les éléments (zillion) de votre séquence.
Les éléments suivants seront au maximum énumérés deux fois: une fois pour la prise et une fois pour le saut. Il n'énumérera pas plus d'éléments que vous n'en utiliserez:
Combien de fois cela énumérera-t-il la séquence?
Supposons que vous divisiez votre source en morceaux de
chunkSize
. Vous énumérez uniquement les N premiers morceaux. À partir de chaque bloc énuméré, vous n'énumérerez que les premiers éléments M.Any obtiendra l'énumérateur, effectuez 1 MoveNext () et retourne la valeur retournée après avoir supprimé l'énumérateur. Cela se fera N fois
Selon la source de référence, cela fera quelque chose comme:
Cela ne fait pas grand-chose jusqu'à ce que vous commenciez à énumérer le morceau récupéré. Si vous récupérez plusieurs morceaux, mais décidez de ne pas énumérer le premier morceau, foreach n'est pas exécuté, comme votre débogueur vous le montrera.
Si vous décidez de prendre les premiers M éléments du premier bloc, le rendement est exécuté exactement M fois. Ça signifie:
Après le retour du premier morceau, nous sautons ce premier morceau:
Encore une fois: nous allons jeter un oeil à la source de référence pour trouver le
skipiterator
Comme vous le voyez, les
SkipIterator
appelsMoveNext()
une fois pour chaque élément du bloc. Ça n'appelle pasCurrent
.Donc, par morceau, nous voyons que ce qui suit est fait:
Prendre():
Si le contenu est énuméré: GetEnumerator (), un MoveNext et un Current par élément énuméré, Dispose enumerator;
Skip (): pour chaque bloc qui est énuméré (PAS le contenu du bloc): GetEnumerator (), MoveNext () fois chunkSize, pas de courant! Éliminer l'énumérateur
Si vous regardez ce qui se passe avec l'énumérateur, vous verrez qu'il y a beaucoup d'appels à MoveNext (), et uniquement des appels à
Current
pour les éléments TSource auxquels vous décidez réellement d'accéder.Si vous prenez N morceaux de taille chunkSize, alors les appels à MoveNext ()
Si vous décidez d'énumérer uniquement les premiers éléments M de chaque bloc récupéré, vous devez appeler MoveNext M fois par bloc énuméré.
Le total
Donc, si vous décidez d'énumérer tous les éléments de tous les morceaux:
Que MoveNext représente beaucoup de travail ou non, cela dépend du type de séquence source. Pour les listes et les tableaux, il s'agit d'un simple incrément d'index, avec peut-être une vérification hors plage.
Mais si votre IEnumerable est le résultat d'une requête de base de données, assurez-vous que les données sont réellement matérialisées sur votre ordinateur, sinon les données seront récupérées plusieurs fois. DbContext et Dapper transfèreront correctement les données au processus local avant de pouvoir y accéder. Si vous énumérez plusieurs fois la même séquence, elle n'est pas récupérée plusieurs fois. Dapper renvoie un objet qui est une liste, DbContext se souvient que les données sont déjà récupérées.
Cela dépend de votre référentiel s'il est sage d'appeler AsEnumerable () ou ToLists () avant de commencer à diviser les éléments en morceaux
la source
2*chunkSize
temps source ? Cela est mortel selon la source de l'énumérable (peut-être sauvegardé par DB ou autre source non mémorisée). Imaginez cet énumérable comme entréeEnumerable.Range(0, 10000).Select(i => DateTime.UtcNow)
- vous obtiendrez des moments différents chaque fois que vous énumérerez l'énumérable car il n'est pas mémoriséEnumerable.Range(0, 10).Select(i => DateTime.UtcNow)
. En invoquant,Any
vous recalculerez l'heure actuelle à chaque fois. Pas si mal pourDateTime.UtcNow
, mais considérons un énumérable soutenu par une connexion à la base de données / un curseur sql ou similaire. J'ai vu des cas où des milliers d'appels DB ont été émis parce que le développeur ne comprenait pas les répercussions potentielles des `` énumérations multiples d'un énumérable '' - ReSharper fournit également un indice à ce sujetla source
la source
Celui-ci, ça va? L'idée était de n'utiliser qu'une seule boucle. Et, qui sait, vous n'utilisez peut-être que des implémentations IList dans votre code et vous ne voulez pas transtyper en List.
la source
Un de plus
la source
la source
la source
J'avais rencontré ce même besoin et j'ai utilisé une combinaison des méthodes Skip () et Take () de Linq . Je multiplie le nombre que je prends par le nombre d'itérations jusqu'à présent, et cela me donne le nombre d'éléments à ignorer, puis je prends le groupe suivant.
la source
Sur la base de Dimitry Pavlov, je supprimerais
.ToList()
. Et évitez également la classe anonyme. Au lieu de cela, j'aime utiliser une structure qui ne nécessite pas d'allocation de mémoire en tas. (AValueTuple
ferait aussi du travail.)Cela peut être utilisé comme le suivant qui n'itère qu'une seule fois sur la collection et n'alloue pas non plus de mémoire significative.
Si une liste concrète est réellement nécessaire, je le ferais comme ceci:
la source