Bagaimana cara menginisialisasi List <T> ke ukuran tertentu (sebagai lawan dari kapasitas)?

111

.NET menawarkan wadah daftar umum yang kinerjanya hampir sama (lihat pertanyaan Kinerja Array vs. Daftar). Namun mereka sangat berbeda dalam inisialisasi.

Array sangat mudah diinisialisasi dengan nilai default, dan menurut definisi, array sudah memiliki ukuran tertentu:

string[] Ar = new string[10];

Yang memungkinkan seseorang untuk menetapkan item acak dengan aman, katakanlah:

Ar[5]="hello";

dengan daftar hal-hal yang lebih rumit. Saya dapat melihat dua cara untuk melakukan inisialisasi yang sama, yang keduanya tidak akan Anda sebut elegan:

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

atau

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

Apa cara yang lebih bersih?

EDIT: Jawaban sejauh ini mengacu pada kapasitas, yang merupakan sesuatu yang lain selain mengisi daftar. Misalnya, pada daftar yang baru dibuat dengan kapasitas 10 orang, tidak dapat dilakukanL[2]="somevalue"

EDIT 2: Orang-orang bertanya-tanya mengapa saya ingin menggunakan daftar dengan cara ini, karena ini bukan cara yang dimaksudkan untuk digunakan. Saya dapat melihat dua alasan:

  1. Seseorang dapat dengan cukup meyakinkan berpendapat bahwa daftar adalah array "generasi berikutnya", menambahkan fleksibilitas hampir tanpa penalti. Oleh karena itu seseorang harus menggunakannya secara default. Saya menunjukkan bahwa mereka mungkin tidak semudah diinisialisasi.

  2. Apa yang saya tulis saat ini adalah kelas dasar yang menawarkan fungsionalitas default sebagai bagian dari kerangka kerja yang lebih besar. Dalam fungsionalitas default yang saya tawarkan, ukuran List diketahui di tingkat lanjut dan oleh karena itu saya dapat menggunakan array. Namun, saya ingin menawarkan kelas dasar kesempatan untuk memperluasnya secara dinamis dan oleh karena itu saya memilih daftar.

Boaz
sumber
1
"EDIT: Jawaban sejauh ini mengacu pada kapasitas, yang lebih dari sekadar mengisi daftar. Misalnya, pada daftar yang baru saja dibuat dengan kapasitas 10, seseorang tidak dapat melakukan L [2] =" somevalue "" Mengingat ini modifikasi, mungkin Anda harus mengubah Judul Pertanyaan ...
aranasaurus
Tapi, apa gunanya mengisi daftar dengan nilai kosong, karena itulah yang coba dilakukan topicstarter?
Frederik Gheysels
1
Jika pemetaan posisi sangat penting, bukankah lebih masuk akal untuk menggunakan Dictionary <int, string>?
Greg D
7
Listbukan pengganti Array. Mereka memecahkan masalah yang sangat terpisah. Jika Anda menginginkan ukuran tetap, Anda menginginkan file Array. Jika Anda menggunakan a List, Anda Melakukannya dengan Salah.
Zenexer
5
Saya selalu menemukan jawaban yang mencoba dipaksakan dalam argumen seperti "Saya tidak mengerti mengapa saya perlu ..." menjengkelkan. Itu hanya berarti: Anda tidak bisa melihatnya. Itu tidak berarti apa-apa lagi. Saya menghargai bahwa orang ingin menyarankan pendekatan yang lebih baik untuk suatu masalah, tetapi itu harus diungkapkan dengan lebih rendah hati, misalnya "Apakah Anda yakin Anda memerlukan daftar? Mungkin jika Anda memberi tahu kami lebih banyak tentang masalah Anda ...". Dengan cara ini, pertanyaan menjadi menyenangkan, menarik, dan mendorong OP untuk meningkatkan pertanyaan mereka. Jadilah pemenang - jadilah rendah hati.
AnorZaken

Jawaban:

79

Saya tidak dapat mengatakan saya sangat membutuhkan ini - dapatkah Anda memberikan detail lebih lanjut tentang mengapa Anda menginginkan ini? Saya mungkin akan meletakkannya sebagai metode statis di kelas helper:

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

Anda bisa menggunakan Enumerable.Repeat(default(T), count).ToList()tetapi itu tidak efisien karena perubahan ukuran buffer.

Perhatikan bahwa jika Tmerupakan tipe referensi, itu akan menyimpan countsalinan referensi yang diteruskan untuk valueparameter - jadi semuanya akan merujuk ke objek yang sama. Itu mungkin atau mungkin tidak yang Anda inginkan, tergantung pada kasus penggunaan Anda.

EDIT: Seperti yang disebutkan di komentar, Anda dapat Repeatedmenggunakan loop untuk mengisi daftar jika Anda mau. Itu juga akan sedikit lebih cepat. Secara pribadi saya menemukan kode menggunakan Repeatlebih deskriptif, dan menduga bahwa di dunia nyata perbedaan kinerja tidak akan relevan, tetapi jarak tempuh Anda mungkin berbeda.

Jon Skeet
sumber
1
Saya menyadari ini adalah posting lama, tetapi saya penasaran. Tarif berulang jauh lebih buruk dibandingkan dengan loop for, sesuai bagian terakhir dari tautan ( dotnetperls.com/initialize-array ). Juga AddRange () memiliki kompleksitas O (n) sesuai msdn. Bukankah agak kontra produktif untuk menggunakan solusi yang diberikan alih-alih loop sederhana?
Jimmy
@ Jimmy: Kedua pendekatan akan menjadi O (n), dan saya menemukan pendekatan ini lebih deskriptif tentang apa yang saya coba capai. Jika Anda lebih suka loop, silakan gunakan.
Jon Skeet
@Jimmy: Perhatikan juga bahwa tolok ukur di sana menggunakan Enumerable.Repeat(...).ToArray(), yang bukan bagaimana saya menggunakannya.
Jon Skeet
1
Saya telah menggunakan dengan Enumerable.Repeat()cara berikut (diimplementasikan Pairseperti yang setara dengan C ++): Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)memperhatikan efek sampingnya, bahwa Listitu penuh dengan salinan yang direferensikan ke satu objek. Mengubah elemen seperti myList[i].First = 1mengubah setiap elemen secara keseluruhan List. Butuh waktu berjam-jam untuk menemukan bug ini. Apakah kalian tahu solusi untuk masalah ini (kecuali untuk hanya menggunakan loop umum dan menggunakan .Add(new Pair...)?
00zetti
1
@ Pac0, saya baru saja menambahkan jawaban di bawah. Pengeditan bisa ditolak.
Jeremy Ray Brown
136
List<string> L = new List<string> ( new string[10] );

sumber
3
secara pribadi saya pikir ini adalah cara terbersih - meskipun disebutkan dalam badan pertanyaan sebagai berpotensi kikuk - tidak tahu mengapa
hello_earth
4
+1: Cukup tekan situasi di mana saya membutuhkan daftar ukuran variabel untuk diinisialisasi dengan satu set tetap null sebelum mengisi melalui indeks (dan menambahkan ekstra setelahnya, jadi array tidak cocok). Jawaban ini mendapat suara saya karena praktis dan sederhana.
Pergi Coding
Jawaban yang luar biasa. @GoneCoding Saya baru saja bertanya-tanya apakah secara internal daftar yang baru diinisialisasi Lhanya akan menggunakan kembali memori string[10]sebagaimana adanya (penyimpanan cadangan dari daftar juga merupakan array) atau akan mengalokasikan memori baru sendiri dan kemudian menyalin isinya string[10]? string[10]akan mengumpulkan sampah secara otomatis jika Lmemilih rute selanjutnya.
RBT
3
@RBT Itulah satu-satunya downside ke pendekatan ini: Array tidak akan digunakan kembali, jadi untuk sementara Anda akan memiliki dua salinan dari array (List menggunakan array secara internal seperti yang Anda ketahui). Dalam kasus yang jarang terjadi, hal itu mungkin menjadi masalah jika Anda memiliki banyak elemen, atau batasan memori. Dan ya, array ekstra akan memenuhi syarat untuk pengumpulan sampah segera setelah konstruktor Daftar selesai (jika tidak, akan terjadi kebocoran memori yang mengerikan). Perhatikan bahwa memenuhi syarat tidak berarti "dikumpulkan segera", melainkan saat pengumpulan sampah dijalankan lagi.
AnorZaken
2
Jawaban ini mengalokasikan 10 string baru - lalu mengulanginya, menyalinnya sesuai kebutuhan. Jika Anda bekerja dengan array besar; maka jangan menganggap ini karena membutuhkan memori dua kali lipat daripada jawaban yang diterima.
UKMonkey
22

Gunakan konstruktor yang menggunakan int ("capacity") sebagai argumen:

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

EDIT: Saya harus menambahkan bahwa saya setuju dengan Frederik. Anda menggunakan Daftar dengan cara yang bertentangan dengan seluruh alasan di balik penggunaannya sejak awal.

EDIT2:

EDIT 2: Apa yang sedang saya tulis adalah kelas dasar yang menawarkan fungsionalitas default sebagai bagian dari kerangka kerja yang lebih besar. Dalam fungsionalitas default yang saya tawarkan, ukuran List diketahui di tingkat lanjut dan oleh karena itu saya dapat menggunakan array. Namun, saya ingin menawarkan kelas dasar kesempatan untuk memperluasnya secara dinamis dan oleh karena itu saya memilih daftar.

Mengapa ada orang yang perlu mengetahui ukuran List dengan semua nilai null? Jika tidak ada nilai riil dalam daftar, saya akan mengharapkan panjangnya menjadi 0. Bagaimanapun, fakta bahwa ini tidak jelas menunjukkan bahwa ini bertentangan dengan tujuan penggunaan kelas.

Ed S.
sumber
46
Jawaban ini tidak mengalokasikan 10 entri nol dalam daftar (yang merupakan persyaratan), ini hanya mengalokasikan ruang untuk 10 entri sebelum pengubahan ukuran daftar diperlukan (yaitu kapasitas), jadi ini tidak ada bedanya new List<string>()sejauh masalahnya . Bagus sekali untuk mendapatkan begitu banyak suara positif :)
Gone Coding
2
bahwa konstruktor kelebihan beban adalah nilai "kapasitas awal" bukan "ukuran" atau "panjang", dan juga tidak menginisialisasi item
Matt Wilko
Untuk menjawab "mengapa seseorang membutuhkan ini": Saya membutuhkan ini sekarang untuk struktur data pohon kloning yang dalam. Sebuah simpul mungkin atau mungkin tidak mengisi anak-anaknya, namun kelas simpul dasar saya harus dapat mengkloning dirinya sendiri dengan semua simpul anak itu. Bla, bla ini bahkan lebih rumit. Tetapi saya membutuhkan keduanya untuk mengisi daftar saya yang masih kosong melalui list[index] = obj;dan menggunakan beberapa kemampuan daftar lainnya.
Bitterblue
3
@ Bitterblue: Juga, segala jenis pemetaan yang dialokasikan sebelumnya di mana Anda mungkin tidak memiliki semua nilai di depan. Ada kegunaan pasti; Saya menulis jawaban ini di hari-hari saya yang kurang berpengalaman.
Ed S.
8

Buat array dengan jumlah item yang Anda inginkan terlebih dahulu, lalu konversikan ke dalam List.

int[] fakeArray = new int[10];

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

Mengapa Anda menggunakan List jika Anda ingin memulainya dengan nilai tetap? Saya dapat memahami bahwa-demi kinerja- Anda ingin memberikannya kapasitas awal, tetapi bukankah salah satu keuntungan dari list dibandingkan dengan array biasa yang dapat bertambah saat dibutuhkan?

Saat Anda melakukan ini:

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

Anda membuat daftar yang kapasitasnya 100 bilangan bulat. Ini berarti List Anda tidak perlu 'bertambah' sampai Anda menambahkan item ke-101. Array yang mendasari daftar akan diinisialisasi dengan panjang 100.

Frederik Gheysels
sumber
1
"Mengapa Anda menggunakan Daftar jika Anda ingin menginisialisasi dengan nilai tetap" Poin yang bagus.
Ed S.
7
Dia meminta daftar di mana setiap elemen diinisialisasi dan daftar itu memiliki ukuran, bukan hanya kapasitas. Jawaban ini tidak benar seperti sekarang.
Nic Foster
Pertanyaan itu membutuhkan jawaban, bukan kritik karena diminta. Terkadang ada alasan untuk melakukannya dengan cara ini, sebagai lawan menggunakan array. Jika tertarik dengan mengapa ini mungkin terjadi, seseorang dapat mengajukan pertanyaan Stack Overflow untuk itu.
Dino Dini
5

Menginisialisasi konten daftar seperti itu bukanlah tujuan daftar. Daftar dirancang untuk menampung objek. Jika Anda ingin memetakan nomor tertentu ke objek tertentu, pertimbangkan untuk menggunakan struktur pasangan nilai-kunci seperti tabel atau kamus hash daripada daftar.

Welbog
sumber
Ini tidak menjawab pertanyaan, tetapi hanya memberikan alasan untuk tidak ditanyakan.
Dino Dini
5

Anda tampaknya menekankan perlunya asosiasi posisi dengan data Anda, jadi bukankah array asosiatif lebih cocok?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";
Greg D
sumber
Ini menjawab pertanyaan yang berbeda dari yang ditanyakan.
Dino Dini
4

Jika Anda ingin menginisialisasi daftar dengan N elemen dari beberapa nilai tetap:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}
Amy B
sumber
Lihat jawaban John Skeet untuk kekhawatiran atas perubahan ukuran buffer dengan teknik ini.
Amy B
1

Anda dapat menggunakan Linq untuk secara cerdik menginisialisasi daftar Anda dengan nilai default. (Mirip dengan jawaban David B. )

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

Selangkah lebih maju dan inisialisasi setiap string dengan nilai berbeda "string 1", "string 2", "string 3", dll:

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();
James Lawruk
sumber
1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();
Henk
sumber
Bagaimana dengan List <string> temp2 = new List <string> (temp); Seperti yang sudah disarankan OP.
Ed S.
Ed - yang ini sebenarnya menjawab pertanyaan - yang bukan tentang kapasitas.
Boaz
Tetapi OP sudah menyatakan bahwa dia tidak menyukai solusi ini.
Ed S.
1

Jawaban yang diterima (yang memiliki tanda centang hijau) bermasalah.

Masalah:

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

Saya merekomendasikan mengubah baris di atas untuk melakukan salinan objek. Ada banyak artikel berbeda tentang itu:

Jika Anda ingin menginisialisasi setiap item dalam daftar Anda dengan konstruktor default, daripada NULL, tambahkan metode berikut:

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
sumber
1
Ini bukan "masalah" - ini adalah perilaku normal dalam menyalin referensi. Tanpa mengetahui situasinya, Anda tidak dapat mengetahui apakah itu cocok untuk menyalin objek. Pertanyaannya bukan tentang apakah daftar harus diisi dengan salinan atau tidak - ini tentang menginisialisasi daftar dengan kapasitas. Saya akan memperjelas jawaban saya dalam hal perilaku itu tidak memiliki, tapi aku benar-benar tidak berpikir itu dianggap sebagai masalah dalam konteks pertanyaan ini.
Jon Skeet
@ JonSkeet, saya membalas static helper, yang tidak masalah untuk tipe primitif tetapi bukan tipe referensi. Skenario di mana daftar diinisialisasi dengan item referensi yang sama tampaknya tidak benar. Dalam skenario itu, mengapa bahkan memiliki daftar jika setiap item di dalamnya menunjuk ke objek yang sama di heap.
Jeremy Ray Brown
Contoh skenario: Anda ingin mengisi daftar string, awalnya dengan entri "Tidak diketahui" untuk setiap elemen, lalu Anda mengubah daftar untuk elemen tertentu. Dalam hal ini, sangat wajar jika semua nilai "Tidak diketahui" menjadi referensi ke string yang sama. Tidak ada gunanya mengkloning string setiap kali. Mengisi string dengan beberapa referensi ke objek yang sama bukanlah "benar" atau "salah" dalam pengertian umum. Selama pembaca tahu apa perilakunya, terserah mereka untuk memutuskan apakah itu memenuhi kasus penggunaan spesifik mereka .
Jon Skeet
0

Pemberitahuan tentang IList: MSDN IList Keterangan : "Penerapan IList terbagi dalam tiga kategori: hanya baca, ukuran tetap, dan ukuran variabel. (...). Untuk versi umum antarmuka ini, lihat System.Collections.Generic.IList<T>."

IList<T>TIDAK mewarisi dari IList(tetapi List<T>menerapkan keduanya IList<T>dan IList), tetapi selalu berukuran variabel . Sejak .NET 4.5, kami juga memiliki IReadOnlyList<T>AFAIK, tidak ada List generik berukuran tetap yang akan menjadi apa yang Anda cari.

EricBDev
sumber
0

Ini adalah sampel yang saya gunakan untuk pengujian unit saya. Saya membuat daftar objek kelas. Kemudian saya menggunakan forloop untuk menambahkan jumlah 'X' objek yang saya harapkan dari layanan. Dengan cara ini Anda dapat menambahkan / menginisialisasi List untuk ukuran tertentu.

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


    }

Semoga saya bisa membantu kalian.

Abhay Shiro
sumber
-1

Agak terlambat tetapi solusi pertama yang Anda usulkan tampaknya jauh lebih bersih bagi saya: Anda tidak mengalokasikan memori dua kali. Bahkan pembuat Daftar perlu mengulang melalui array untuk menyalinnya; Ia bahkan tidak tahu sebelumnya hanya ada elemen nol di dalamnya.

1. - alokasikan N - loop N Biaya: 1 * alokasikan (N) + N * loop_iteration

2. - alokasikan N - alokasikan N + loop () Biaya: 2 * alokasikan (N) + N * loop_iteration

Namun alokasi List, loop mungkin lebih cepat karena List adalah kelas built-in, tetapi C # dikompilasi dengan jit sooo ...

Victor Drouin
sumber