Comment initialiser un List <T> à une taille donnée (par opposition à la capacité)?

112

.NET propose un conteneur de liste générique dont les performances sont presque identiques (voir la question Performances des tableaux par rapport aux listes). Cependant, ils sont assez différents dans l'initialisation.

Les tableaux sont très faciles à initialiser avec une valeur par défaut et, par définition, ils ont déjà une certaine taille:

string[] Ar = new string[10];

Ce qui permet d'attribuer en toute sécurité des éléments aléatoires, par exemple:

Ar[5]="hello";

avec la liste, les choses sont plus délicates. Je peux voir deux façons de faire la même initialisation, dont aucune n'est ce que vous appelleriez élégant:

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);

ou

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);

Quelle serait une manière plus propre?

EDIT: Les réponses jusqu'à présent se réfèrent à la capacité, qui est autre chose que pré-peupler une liste. Par exemple, sur une liste qui vient d'être créée avec une capacité de 10, on ne peut pas faireL[2]="somevalue"

EDIT 2: Les gens se demandent pourquoi je veux utiliser les listes de cette façon, car ce n'est pas la façon dont elles sont destinées à être utilisées. Je peux voir deux raisons:

  1. On pourrait affirmer de manière assez convaincante que les listes sont les tableaux de la «prochaine génération», ajoutant de la flexibilité avec presque aucune pénalité. Il faut donc les utiliser par défaut. Je souligne qu'ils ne seront peut-être pas aussi faciles à initialiser.

  2. Ce que j'écris actuellement est une classe de base offrant des fonctionnalités par défaut dans le cadre d'un cadre plus large. Dans la fonctionnalité par défaut que j'offre, la taille de la liste est connue à l'avance et j'aurais donc pu utiliser un tableau. Cependant, je souhaite offrir à toute classe de base la possibilité de l'étendre dynamiquement et j'opte donc pour une liste.

Boaz
la source
1
"EDIT: Les réponses jusqu'à présent se réfèrent à la capacité, ce qui est autre chose que pré-peupler une liste. Par exemple, sur une liste qui vient d'être créée avec une capacité de 10, on ne peut pas faire L [2] =" somevalue "" Compte tenu de cela modification, vous devriez peut-être reformuler le titre de la question ...
aranasaurus
Mais quelle est l'utilité de pré-remplir une liste avec des valeurs vides, car c'est ce que le topicstarter essaie de faire?
Frederik Gheysels
1
Si la cartographie positionnelle est aussi cruciale, ne serait-il pas plus logique d'utiliser un Dictionary <int, string>?
Greg D
7
Listne remplace pas Array. Ils résolvent des problèmes bien distincts. Si vous voulez une taille fixe, vous voulez un fichier Array. Si vous utilisez a List, vous ne le faites pas correctement.
Zenexer
5
Je trouve toujours des réponses qui essaient de marteler des arguments comme "Je ne vois pas pourquoi j'aurais jamais besoin ..." aggravant. Cela signifie simplement que: vous ne pouviez pas le voir. Cela ne veut pas nécessairement dire autre chose. Je respecte le fait que les gens veuillent suggérer de meilleures approches à un problème, mais il devrait être formulé plus humblement, par exemple: «Êtes-vous sûr d'avoir besoin d'une liste? Peut-être si vous nous en avez parlé davantage…». De cette façon, cela devient agréable, engageant et encourage le PO à améliorer sa question. Soyez un gagnant - soyez humble.
AnorZaken

Réponses:

79

Je ne peux pas dire que j'en ai besoin très souvent - pouvez-vous nous expliquer pourquoi vous le souhaitez? Je le mettrais probablement comme une méthode statique dans une classe d'assistance:

public static class Lists
{
    public static List<T> RepeatedDefault<T>(int count)
    {
        return Repeated(default(T), count);
    }

    public static List<T> Repeated<T>(T value, int count)
    {
        List<T> ret = new List<T>(count);
        ret.AddRange(Enumerable.Repeat(value, count));
        return ret;
    }
}

Vous pourriez utiliser, Enumerable.Repeat(default(T), count).ToList()mais ce serait inefficace en raison du redimensionnement de la mémoire tampon.

Notez que si Test un type de référence, il stockera des countcopies de la référence passée pour le valueparamètre - ainsi elles feront toutes référence au même objet. Cela peut ou non être ce que vous voulez, selon votre cas d'utilisation.

EDIT: Comme indiqué dans les commentaires, vous pouvez Repeatedutiliser une boucle pour remplir la liste si vous le souhaitez. Ce serait aussi un peu plus rapide. Personnellement, je trouve le code Repeatplus descriptif et je soupçonne que dans le monde réel, la différence de performance ne serait pas pertinente, mais votre kilométrage peut varier.

Jon Skeet
la source
1
Je me rends compte que c'est un ancien post, mais je suis curieux. Enumerable.Repeat coûte bien pire par rapport à une boucle for, selon la dernière partie du lien ( dotnetperls.com/initialize-array ). AddRange () a également une complexité O (n) selon msdn. N'est-il pas un peu contre-productif d'utiliser la solution donnée au lieu d'une simple boucle?
Jimmy
@Jimmy: Les deux approches seront O (n), et je trouve que cette approche est plus descriptive de ce que j'essaie de réaliser. Si vous préférez une boucle, n'hésitez pas à l'utiliser.
Jon Skeet
@Jimmy: Notez également que le benchmark qu'il utilise Enumerable.Repeat(...).ToArray(), ce n'est pas comme ça que je l'utilise.
Jon Skeet
1
J'ai utilisé le Enumerable.Repeat()de la manière suivante (implémentée Paircomme l'équivalent C ++): en Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)remarquant l'effet secondaire, que le Listétait plein de copies référencées vers un seul objet. Changer un élément comme myList[i].First = 1changer chaque élément dans l'ensemble List. Il m'a fallu des heures pour trouver ce bug. Connaissez-vous une solution à ce problème (sauf pour utiliser une boucle commune et l'utiliser .Add(new Pair...)?
00zetti
1
@ Pac0, je viens d'ajouter une réponse ci-dessous. Les modifications peuvent être refusées.
Jeremy Ray Brown
136
List<string> L = new List<string> ( new string[10] );

la source
3
personnellement, je pense que c'est la manière la plus propre - bien qu'elle ait été mentionnée dans le corps de la question comme potentiellement maladroite - je ne sais pas pourquoi
bonjour_earth
4
+1: Il suffit de frapper une situation où j'avais besoin d'une liste de taille variable à initialiser avec un ensemble fixe de valeurs nulles avant de remplir via un index (et d'ajouter des extras par la suite, donc un tableau ne convenait pas). Cette réponse obtient mon vote pour être pratique et simple.
Fini le codage le
Réponse fabuleuse. @GoneCoding Je me demandais simplement si en interne la liste nouvellement initialisée Lréutiliserait simplement la mémoire de string[10]telle quelle (le stockage de sauvegarde d'une liste est également un tableau) ou allouerait-elle une nouvelle mémoire et copierait ensuite le contenu de string[10]? string[10]récupérera automatiquement les déchets si Lsélectionne la dernière route.
RBT
3
@RBT C'est le seul inconvénient de cette approche: le tableau ne sera pas réutilisé, vous aurez donc momentanément deux copies du tableau (List utilise des tableaux en interne, comme vous semblez le savoir). Dans de rares cas, cela peut poser un problème si vous avez un grand nombre d'éléments ou des contraintes de mémoire. Et oui, le tableau supplémentaire sera éligible pour le garbage collection dès que le constructeur List sera terminé (cela aurait été une horrible fuite de mémoire sinon). Notez que éligible ne signifie pas «collecté tout de suite», mais plutôt la prochaine fois que le garbage collection s'exécute.
AnorZaken
2
Cette réponse alloue 10 nouvelles chaînes - puis les itère en les copiant selon les besoins. Si vous travaillez avec de grands tableaux; alors ne considérez même pas cela car il a besoin de deux fois plus de mémoire que la réponse acceptée.
UKMonkey
22

Utilisez le constructeur qui prend un int ("capacité") comme argument:

List<string> = new List<string>(10);

EDIT: Je dois ajouter que je suis d'accord avec Frederik. Vous utilisez la Liste d'une manière qui va à l'encontre de tout le raisonnement derrière son utilisation en premier lieu.

EDIT2:

EDIT 2: Ce que j'écris actuellement est une classe de base offrant des fonctionnalités par défaut dans le cadre d'un cadre plus large. Dans la fonctionnalité par défaut que j'offre, la taille de la liste est connue à l'avance et j'aurais donc pu utiliser un tableau. Cependant, je souhaite offrir à toute classe de base la possibilité de l'étendre dynamiquement et j'opte donc pour une liste.

Pourquoi quelqu'un aurait-il besoin de connaître la taille d'une liste avec toutes les valeurs nulles? S'il n'y a pas de valeurs réelles dans la liste, je m'attendrais à ce que la longueur soit égale à 0. Quoi qu'il en soit, le fait que ce soit maladroit démontre que cela va à l'encontre de l'utilisation prévue de la classe.

Ed S.
la source
46
Cette réponse n'attribue pas 10 entrées nulles dans la liste (ce qui était l'exigence), elle alloue simplement de l'espace pour 10 entrées avant qu'un redimensionnement de la liste ne soit requis (c'est-à-dire la capacité), donc cela ne fait rien de différent en ce new List<string>()qui concerne le problème . Bien joué pour avoir obtenu autant de votes positifs :)
Gone Coding
2
ce constructeur surchargé est la valeur de "capacité initiale" et non la "taille" ou la "longueur", et il n'initialise pas non plus les éléments
Matt Wilko
Pour répondre "pourquoi quelqu'un aurait besoin de ça": j'en ai besoin maintenant pour le clonage profond des structures de données d'arbre. Un nœud peut ou non peupler ses enfants, mais ma classe de nœuds de base doit pouvoir se cloner avec tous ses nœuds enfants. Bla, bla c'est encore plus compliqué. Mais j'ai besoin à la fois de remplir ma liste encore vide via list[index] = obj;et d'utiliser d'autres fonctionnalités de liste.
Bitterblue
3
@Bitterblue: De plus, toute sorte de mappage pré-alloué où vous ne pouvez pas avoir toutes les valeurs à l'avance. Il y a certainement des utilisations; J'ai écrit cette réponse dans mes jours moins expérimentés.
Ed S.
8

Créez d'abord un tableau avec le nombre d'éléments souhaités, puis convertissez le tableau en liste.

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();
mini998
la source
7

Pourquoi utilisez-vous une liste si vous souhaitez l'initialiser avec une valeur fixe? Je peux comprendre que - pour des raisons de performances - vous vouliez lui donner une capacité initiale, mais n'est-ce pas l'un des avantages d'une liste par rapport à une matrice régulière qu'elle peut augmenter en cas de besoin?

Lorsque vous faites ceci:

List<int> = new List<int>(100);

Vous créez une liste dont la capacité est de 100 entiers. Cela signifie que votre liste n'aura pas besoin de «grandir» tant que vous n'aurez pas ajouté le 101e élément. Le tableau sous-jacent de la liste sera initialisé avec une longueur de 100.

Frederik Gheysels
la source
1
"Pourquoi utilisez-vous une liste si vous voulez l'initialiser avec une valeur fixe" Un bon point.
Ed S.
7
Il a demandé une liste dans laquelle chaque élément était initialisé et la liste avait une taille, pas seulement une capacité. Cette réponse est incorrecte telle qu'elle est actuellement.
Nic Foster
La question exige une réponse, pas une critique d'être posée. Parfois, il y a une raison de le faire de cette façon, par opposition à l'utilisation d'un tableau. Si vous êtes intéressé par les raisons pour lesquelles cela pourrait être le cas, vous pouvez poser une question Stack Overflow.
Dino Dini le
5

Initialiser le contenu d'une liste comme celle-là n'est pas vraiment à quoi servent les listes. Les listes sont conçues pour contenir des objets. Si vous souhaitez mapper des nombres particuliers à des objets particuliers, envisagez d'utiliser une structure de paires clé-valeur telle qu'une table de hachage ou un dictionnaire au lieu d'une liste.

Welbog
la source
Cela ne répond pas à la question, mais donne simplement une raison pour qu'elle ne soit pas posée.
Dino Dini le
5

Vous semblez insister sur la nécessité d'une association positionnelle avec vos données, alors un tableau associatif ne serait-il pas plus approprié?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";
Greg D
la source
Cela répond à une question différente de celle posée.
Dino Dini le
4

Si vous souhaitez initialiser la liste avec N éléments d'une valeur fixe:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}
Amy B
la source
Voir la réponse de John Skeet concernant le redimensionnement de la mémoire tampon par cette technique.
Amy B
1

Vous pouvez utiliser Linq pour initialiser intelligemment votre liste avec une valeur par défaut. (Similaire à la réponse de David B. )

var defaultStrings = (new int[10]).Select(x => "my value").ToList();

Allez un peu plus loin et initialisez chaque chaîne avec des valeurs distinctes "chaîne 1", "chaîne 2", "chaîne 3", etc:

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();
James Lawruk
la source
1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();
Henk
la source
Que diriez-vous de List <string> temp2 = new List <string> (temp); Comme l'OP déjà suggéré.
Ed S.
Ed - celui-ci répond en fait à la question - qui ne concerne pas la capacité.
Boaz
Mais le PO a déjà déclaré qu'il n'aimait pas cette solution.
Ed S.
1

La réponse acceptée (celle avec la coche verte) a un problème.

Le problème:

var result = Lists.Repeated(new MyType(), sizeOfList);
// each item in the list references the same MyType() object
// if you edit item 1 in the list, you are also editing item 2 in the list

Je recommande de changer la ligne ci-dessus pour effectuer une copie de l'objet. Il existe de nombreux articles différents à ce sujet:

Si vous souhaitez initialiser chaque élément de votre liste avec le constructeur par défaut, plutôt que NULL, ajoutez la méthode suivante:

public static List<T> RepeatedDefaultInstance<T>(int count)
    {
        List<T> ret = new List<T>(count);
        for (var i = 0; i < count; i++)
        {
            ret.Add((T)Activator.CreateInstance(typeof(T)));
        }
        return ret;
    }
Jeremy Ray Brown
la source
1
Ce n'est pas un "problème" - c'est le comportement normal de la copie de références. Sans connaître la situation, vous ne pouvez pas savoir s'il convient de copier l'objet. La question n'est pas de savoir si les listes doivent ou non être remplies de copies - il s'agit d'initialiser une liste avec une capacité. Je vais préciser ma réponse en termes de comportement , il ne dispose, mais je ne pense vraiment pas qu'il compte comme un problème dans le contexte de cette question.
Jon Skeet
@JonSkeet, je répondais à l'assistant statique, ce qui convient aux types primitifs mais pas aux types de référence. Un scénario où une liste est initialisée avec le même élément référencé ne semble pas correct. Dans ce scénario, pourquoi même avoir la liste si chaque élément qu'elle contient pointe vers le même objet sur le tas.
Jeremy Ray Brown
Exemple de scénario: vous souhaitez remplir une liste de chaînes, initialement avec une entrée «Inconnu» pour chaque élément, puis vous modifiez la liste pour des éléments spécifiques. Dans ce cas, il est tout à fait raisonnable que toutes les valeurs "Inconnu" soient des références à la même chaîne. Il serait inutile de cloner la chaîne à chaque fois. Remplir une chaîne avec plusieurs références au même objet n'est pas «correct» ou «faux» dans un sens général. Tant que les lecteurs savent quel est le comportement, c'est à eux de décider s'il répond à leur cas d'utilisation spécifique .
Jon Skeet
0

Remarque concernant IList: MSDN IList Remarques : «Les implémentations IList se répartissent en trois catégories: lecture seule, taille fixe et taille variable. (...). Pour la version générique de cette interface, reportez-vous à System.Collections.Generic.IList<T>

IList<T>n'hérite PAS de IList(mais List<T>implémente les deux IList<T>et IList), mais est toujours de taille variable . Depuis .NET 4.5, nous avons également IReadOnlyList<T>mais AFAIK, il n'y a pas de liste générique de taille fixe qui serait ce que vous recherchez.

EricBDev
la source
0

Ceci est un exemple que j'ai utilisé pour mon test unitaire. J'ai créé une liste d'objets de classe. Ensuite, j'ai utilisé forloop pour ajouter le nombre "X" d'objets que j'attends du service. De cette façon, vous pouvez ajouter / initialiser une liste pour n'importe quelle taille donnée.

public void TestMethod1()
    {
        var expected = new List<DotaViewer.Interface.DotaHero>();
        for (int i = 0; i < 22; i++)//You add empty initialization here
        {
            var temp = new DotaViewer.Interface.DotaHero();
            expected.Add(temp);
        }
        var nw = new DotaHeroCsvService();
        var items = nw.GetHero();

        CollectionAssert.AreEqual(expected,items);


    }

J'espère que j'ai été utile pour vous les gars.

Abhay Shiro
la source
-1

Un peu tard mais la première solution que vous proposez me semble bien plus propre: vous n'allouez pas deux fois la mémoire. Même le constructeur de liste a besoin de parcourir le tableau pour le copier; il ne sait même pas d'avance qu'il n'y a que des éléments nuls à l'intérieur.

1. - allouer N - boucle N Coût: 1 * allouer (N) + N * loop_iteration

2. - allouer N - allouer N + loop () Coût: 2 * allouer (N) + N * loop_iteration

Cependant, l'allocation et les boucles de List pourraient être plus rapides puisque List est une classe intégrée, mais C # est compilé jit sooo ...

Victor Drouin
la source