Jak zainicjować List <T> do danego rozmiaru (w przeciwieństwie do pojemności)?

112

.NET oferuje ogólny kontener listy, którego wydajność jest prawie identyczna (zobacz pytanie Wydajność tablic a listy). Jednak są one zupełnie inne w inicjalizacji.

Tablice są bardzo łatwe do zainicjowania z wartością domyślną iz definicji mają już określony rozmiar:

string[] Ar = new string[10];

Co pozwala na bezpieczne przypisywanie losowych przedmiotów, powiedzmy:

Ar[5]="hello";

z listą rzeczy są trudniejsze. Widzę dwa sposoby wykonania tej samej inicjalizacji, z których żaden nie jest tym, co nazwałbyś eleganckim:

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

lub

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

Jaki byłby czystszy sposób?

EDYCJA: dotychczasowe odpowiedzi odnoszą się do pojemności, co jest czymś innym niż wstępne wypełnienie listy. Na przykład na właśnie utworzonej liście o pojemności 10 nie można tego zrobićL[2]="somevalue"

EDYCJA 2: Ludzie zastanawiają się, dlaczego chcę używać list w ten sposób, skoro nie jest to sposób, w jaki mają być używane. Widzę dwa powody:

  1. Można by całkiem przekonująco argumentować, że listy są tablicami „nowej generacji”, dodającymi elastyczności prawie bez żadnych kar. Dlatego należy ich używać domyślnie. Zwracam uwagę, że mogą nie być tak łatwe do zainicjowania.

  2. To, co obecnie piszę, to klasa bazowa oferująca domyślną funkcjonalność jako część większego frameworka. W domyślnej funkcjonalności, którą oferuję, rozmiar listy jest znany w zaawansowanym i dlatego mogłem użyć tablicy. Chcę jednak zaoferować każdej klasie bazowej możliwość jej dynamicznego rozszerzenia, dlatego wybieram listę.

Boaz
źródło
1
"EDYTUJ: dotychczasowe odpowiedzi odnoszą się do pojemności, która jest czymś innym niż wstępne wypełnienie listy. Na przykład na właśnie utworzonej liście o pojemności 10 nie można zrobić L [2] =" jakaś wartość "" Biorąc pod uwagę to modyfikacja, być może powinieneś
przeformułować
Ale jaki jest pożytek z wstępnego wypełniania listy pustymi wartościami, ponieważ właśnie to próbuje zrobić osoba, która rozpoczyna temat?
Frederik Gheysels
1
Jeśli mapowanie pozycyjne jest tak istotne, czy nie miałoby większego sensu użycie Dictionary <int, string>?
Greg D
7
Listnie zastępuje Array. Rozwiązują wyraźnie oddzielne problemy. Jeśli chcesz mieć stały rozmiar, potrzebujesz Array. Jeśli używasz List, robisz to źle.
Zenexer
5
Zawsze znajduję odpowiedzi, które próbują wbijać się w argumenty typu „Nie rozumiem, dlaczego miałbym kiedykolwiek potrzebować…” irytujące. Znaczy tylko tyle: nie mogłeś tego zobaczyć. To niekoniecznie oznacza cokolwiek innego. Szanuję, że ludzie chcą zasugerować lepsze podejście do problemu, ale należy to sformułować bardziej pokornie, np. „Czy na pewno potrzebujesz listy? Może gdybyś opowiedział nam więcej o swoim problemie…”. W ten sposób staje się przyjemny, angażujący i zachęca PO do ulepszenia ich pytania. Bądź zwycięzcą - bądź pokorny.
AnorZaken

Odpowiedzi:

79

Nie mogę powiedzieć, że potrzebuję tego bardzo często - czy możesz podać więcej szczegółów, dlaczego tego chcesz? Prawdopodobnie umieściłbym to jako metodę statyczną w klasie pomocniczej:

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

Państwo mogli używać Enumerable.Repeat(default(T), count).ToList(), ale to byłoby nieefektywne ze względu na bufor zmiany rozmiaru.

Zauważ, że jeśli Tjest typem referencyjnym, będzie przechowywać countkopie referencji przekazanej dla valueparametru - więc wszystkie będą odnosić się do tego samego obiektu. To może, ale nie musi być tym, czego chcesz, w zależności od twojego przypadku użycia.

EDYCJA: Jak zauważono w komentarzach, jeśli chcesz, możesz Repeatedużyć pętli do wypełnienia listy. To też byłoby nieco szybsze. Osobiście uważam, że kod jest Repeatbardziej opisowy i podejrzewam, że w prawdziwym świecie różnica w wydajności byłaby nieistotna, ale przebieg może się różnić.

Jon Skeet
źródło
1
Zdaję sobie sprawę, że to stary post, ale jestem ciekawy. Opłaty za powtarzanie są znacznie gorsze w porównaniu do pętli for, zgodnie z ostatnią częścią linku ( dotnetperls.com/initialize-array ). Również AddRange () ma złożoność O (n) zgodnie z msdn. Czy użycie danego rozwiązania zamiast zwykłej pętli nie jest trochę nieproduktywne?
Jimmy,
@Jimmy: Oba podejścia będą O (n) i uważam, że to podejście lepiej opisuje to, co próbuję osiągnąć. Jeśli wolisz pętlę, możesz z niej skorzystać.
Jon Skeet
@Jimmy: Zauważ również, że używany jest benchmark Enumerable.Repeat(...).ToArray(), który nie jest tym, jak go używam.
Jon Skeet
1
Użyłem go Enumerable.Repeat()w następujący sposób (zaimplementowany Pairtak jak odpowiednik w C ++): Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)zauważając efekt uboczny, że Listbył pełen odwołań do kopii pojedynczego obiektu. Zmiana elementu jak myList[i].First = 1zmieniła każdy element w całości List. Znalezienie tego błędu zajęło mi wiele godzin. Czy znacie jakieś rozwiązanie tego problemu (oprócz zwykłego używania wspólnej pętli i używania .Add(new Pair...)?
00zetti
1
@ Pac0, właśnie dodałem odpowiedź poniżej. Zmiany mogą zostać odrzucone.
Jeremy Ray Brown
136
List<string> L = new List<string> ( new string[10] );

źródło
3
osobiście uważam, że to najczystszy sposób - chociaż wspomniano o tym w treści pytania jako potencjalnie niezdarny - nie wiem dlaczego
hello_earth
4
+1: Po prostu trafiłem w sytuację, w której potrzebowałem, aby lista zmiennych rozmiarów była zainicjowana stałym zestawem wartości null przed zapełnieniem przez indeks (i później dodaniem dodatków, więc tablica była nieodpowiednia). Ta odpowiedź daje mój głos za praktyczną i prostą.
Gone Coding
Fantastyczna odpowiedź. @GoneCoding Zastanawiałem się tylko, czy wewnętrznie nowo zainicjowana lista Lpo prostu ponownie wykorzysta pamięć w stanie, w string[10]jakim jest (magazyn zapasowy list jest również tablicą), czy też przydzieli nową własną pamięć, a następnie skopiuje zawartość string[10]? string[10]automatycznie zbiera elementy bezużyteczne, jeśli Lwybierze późniejszą trasę.
RBT
3
@RBT To jedyna wada tego podejścia: tablica nie zostanie ponownie wykorzystana, więc przez chwilę będziesz mieć dwie kopie tablicy (lista używa tablic wewnętrznie, o czym wydaje się być świadomy). W rzadkich przypadkach może to stanowić problem, jeśli masz ogromną liczbę elementów lub ograniczenia pamięci. I tak, dodatkowa tablica będzie kwalifikować się do czyszczenia pamięci, gdy tylko konstruktor List zostanie ukończony (w przeciwnym razie byłby to straszny wyciek pamięci). Należy zauważyć, że kwalifikujące się nie oznacza „zebrane od razu”, ale raczej przy następnym uruchomieniu czyszczenia pamięci.
AnorZaken
2
Ta odpowiedź przydziela 10 nowych ciągów - następnie iteruje je, kopiując je zgodnie z wymaganiami. Jeśli pracujesz z dużymi tablicami; to nawet nie myśl o tym, ponieważ wymaga to dwa razy więcej pamięci niż zaakceptowana odpowiedź.
UKMonkey
22

Użyj konstruktora, który przyjmuje jako argument int („capacity”):

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

EDYCJA: Dodam, że zgadzam się z Frederikiem. Używasz Listy w sposób, który jest sprzeczny z całym rozumowaniem stojącym za jej użyciem.

EDYCJA2:

EDYCJA 2: To, co obecnie piszę, to klasa bazowa oferująca domyślną funkcjonalność jako część większego frameworka. W domyślnej funkcjonalności, którą oferuję, rozmiar listy jest znany w zaawansowanym i dlatego mogłem użyć tablicy. Chcę jednak zaoferować każdej klasie bazowej możliwość jej dynamicznego rozszerzenia, dlatego wybieram listę.

Dlaczego ktoś miałby znać rozmiar listy zawierającej wszystkie wartości null? Gdyby na liście nie było żadnych rzeczywistych wartości, spodziewałbym się, że długość będzie równa 0. W każdym razie fakt, że jest on gęsty, dowodzi, że jest to sprzeczne z przeznaczeniem tej klasy.

Ed S.
źródło
46
Ta odpowiedź nie przydziela 10 pustych wpisów na liście (co było wymaganiem), po prostu przydziela miejsce na 10 wpisów, zanim będzie wymagana zmiana rozmiaru listy (tj. Pojemność), więc nie robi to nic innego, new List<string>()jeśli chodzi o problem . Dobra robota z otrzymaniem tylu pozytywnych głosów :)
Gone Coding
2
ten przeciążony konstruktor jest wartością „pojemności początkowej”, a nie „rozmiarem” lub „długością”, i też nie inicjalizuje elementów
Matt Wilko
Aby odpowiedzieć „dlaczego ktoś miałby tego potrzebować”: potrzebuję tego teraz do głębokiego klonowania drzewiastych struktur danych. Węzeł może, ale nie musi, zapełniać swoje dzieci, ale moja klasa węzła bazowego musi być w stanie sklonować się ze wszystkimi swoimi węzłami potomnymi. Bla, bla, to jest jeszcze bardziej skomplikowane. Ale potrzebuję zarówno wypełnić moją wciąż pustą listę przez, jak list[index] = obj;i użyć innych funkcji list.
Bitterblue
3
@Bitterblue: Ponadto wszelkiego rodzaju wstępnie przydzielone mapowania, w których możesz nie mieć wszystkich wartości z góry. Z pewnością istnieją zastosowania; Napisałem tę odpowiedź w moich mniej doświadczonych dniach.
Ed S.
8

Najpierw utwórz tablicę z żądaną liczbą elementów, a następnie przekonwertuj ją na listę.

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();
mini998
źródło
7

Dlaczego używasz listy, jeśli chcesz ją zainicjować ze stałą wartością? Rozumiem, że - ze względu na wydajność - chcesz nadać jej początkową pojemność, ale czy nie jest jedną z zalet listy w porównaniu ze zwykłą tablicą, którą może powiększać w razie potrzeby?

Kiedy to robisz:

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

Tworzysz listę o pojemności 100 liczb całkowitych. Oznacza to, że Twoja lista nie będzie musiała „rosnąć”, dopóki nie dodasz 101 pozycji. Podstawowa tablica listy zostanie zainicjowana z długością 100.

Frederik Gheysels
źródło
1
„Dlaczego używasz listy, jeśli chcesz ją zainicjować stałą wartością” Dobra uwaga.
Ed S.
7
Poprosił o listę, w której każdy element został zainicjowany, a lista miała rozmiar, a nie tylko pojemność. Ta odpowiedź jest niepoprawna w obecnej formie.
Nic Foster
Pytanie wymaga odpowiedzi, a nie krytyki. Czasami istnieje powód, aby zrobić to w ten sposób, w przeciwieństwie do korzystania z tablicy. Jeśli interesuje Cię, dlaczego tak się dzieje, możesz zadać pytanie o przepełnienie stosu.
Dino Dini
5

Inicjowanie zawartości takiej listy nie jest tak naprawdę tym, do czego służą listy. Listy są przeznaczone do przechowywania obiektów. Jeśli chcesz odwzorować określone liczby na określone obiekty, rozważ użycie struktury par klucz-wartość, takiej jak tabela skrótów lub słownik, zamiast listy.

Welbog
źródło
1
To nie odpowiada na pytanie, a jedynie podaje powód, dla którego nie należy go zadawać.
Dino Dini
5

Wydaje się, że podkreślasz potrzebę pozycyjnego powiązania z danymi, więc czy tablica asocjacyjna nie byłaby bardziej odpowiednia?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";
Greg D.
źródło
To jest odpowiedź na inne pytanie niż to, które zostało zadane.
Dino Dini
4

Jeśli chcesz zainicjować listę N elementami o stałej wartości:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}
Amy B.
źródło
Zobacz odpowiedź Johna Skeeta na temat zmiany rozmiaru bufora za pomocą tej techniki.
Amy B
1

Możesz użyć Linq, aby sprytnie zainicjować listę z wartością domyślną. (Podobnie jak odpowiedź Davida B. )

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

Idź o krok dalej i zainicjuj każdy ciąg odrębnymi wartościami „ciąg 1”, „ciąg 2”, „ciąg 3” itd .:

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();
James Lawruk
źródło
1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();
Henk
źródło
Co powiesz na List <string> temp2 = new List <string> (temp); Jak już sugerował PO.
Ed S.
Ed - ten właściwie odpowiada na pytanie - które nie dotyczy pojemności.
Boaz
Ale PO stwierdził już, że nie podoba mu się to rozwiązanie.
Ed S.
1

Zaakceptowana odpowiedź (ta z zielonym haczykiem) zawiera problem.

Problem:

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

Zalecam zmianę powyższej linii, aby wykonać kopię obiektu. Istnieje wiele różnych artykułów na ten temat:

Jeśli chcesz zainicjować każdy element na liście za pomocą domyślnego konstruktora, a nie NULL, dodaj następującą metodę:

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
źródło
1
To nie jest „problem” - to normalne zachowanie podczas kopiowania odniesień. Bez znajomości sytuacji nie możesz wiedzieć, czy kopiowanie obiektu jest odpowiednie. Nie chodzi o to, czy listy powinny być zapełniane kopiami - chodzi o zainicjowanie listy z pojemnością. Będę wyjaśnić moją odpowiedź, jeśli chodzi o zachowanie to musi mieć, ale ja naprawdę nie sądzę, że liczy się jako problem w kontekście tego pytania.
Jon Skeet,
@JonSkeet, odpowiadałem statycznemu pomocnikowi, który jest w porządku w przypadku typów pierwotnych, ale nie typów referencyjnych. Scenariusz, w którym lista jest inicjowana przy użyciu tego samego elementu, do którego się odwołuje, nie wydaje się właściwy. W tym scenariuszu po co w ogóle mieć listę, jeśli każdy jej element wskazuje na ten sam obiekt na stercie.
Jeremy Ray Brown
Przykładowy scenariusz: chcesz wypełnić listę ciągów, początkowo wpisem „Nieznany” dla każdego elementu, a następnie modyfikujesz listę pod kątem określonych elementów. W takim przypadku całkowicie uzasadnione jest, aby wszystkie wartości „Nieznane” były odniesieniami do tego samego ciągu. Klonowanie łańcucha za każdym razem nie miałoby sensu. Zapełnianie ciągu wieloma odwołaniami do tego samego obiektu nie jest „prawidłowe” ani „złe” w ogólnym sensie. Dopóki czytelnicy wiedzą, jakie jest zachowanie, do nich należy decyzja, czy spełnia ono ich konkretny przypadek użycia .
Jon Skeet,
0

Uwaga dotycząca IList: MSDN IList Uwagi : "Implementacje IList dzielą się na trzy kategorie: tylko do odczytu, o stałym rozmiarze i o zmiennym rozmiarze. (...). Ogólną wersję tego interfejsu można znaleźć w System.Collections.Generic.IList<T>."

IList<T>NIE dziedziczy po IList(ale List<T>implementuje oba IList<T>i IList), ale zawsze ma zmienny rozmiar . Od .NET 4.5 mamy również, IReadOnlyList<T>ale AFAIK, nie ma ogólnej listy o stałym rozmiarze, która byłaby tym, czego szukasz.

EricBDev
źródło
0

To jest próbka, której użyłem do mojego testu jednostkowego. Stworzyłem listę obiektów klasy. Następnie użyłem forloop, aby dodać liczbę obiektów „X”, których oczekuję od usługi. W ten sposób możesz dodać / zainicjować listę dla dowolnego rozmiaru.

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


    }

Mam nadzieję, że pomogłem wam.

Abhay Shiro
źródło
-1

Trochę późno, ale pierwsze zaproponowane przez ciebie rozwiązanie wydaje mi się o wiele czystsze: nie przydziela się pamięci dwukrotnie. Nawet constrcutor listy musi przejść przez tablicę, aby ją skopiować; nawet z góry nie wie, że w środku są tylko elementy zerowe.

1. - przydziel N - pętla N Koszt: 1 * przydziel (N) + N * loop_iteration

2. - przydziel N - przydziel N + pętlę () Koszt: 2 * przydziel (N) + N * loop_iteration

Jednak alokacja listy, pętle mogą być szybsze, ponieważ List jest klasą wbudowaną, ale C # jest skompilowany w jit, więc ...

Victor Drouin
źródło