Bir liste mi yoksa bir dizi mi kullanmalıyım?


22

Öğe numaraları için UPC'yi hesaplamak üzere bir windows formu üzerinde çalışıyorum.

Bir kerede bir ürün numarası / UPC'yi başarılı bir şekilde oluşturacak bir tane oluşturdum, şimdi birden fazla ürün numarası / UPC için genişletmek ve yapmak istiyorum.

Bir liste kullanmaya başladım ve denedim, ancak sürekli takılıyorum. Bir yardımcı sınıf oluşturdum:

public class Codes
{
    private string incrementedNumber;
    private string checkDigit;
    private string wholeNumber;
    private string wholeCodeNumber;
    private string itemNumber;

    public Codes(string itemNumber, string incrementedNumber, string checkDigit, string wholeNumber, string wholeCodeNumber)
    {
        this.incrementedNumber = incrementedNumber;
        this.checkDigit = checkDigit;
        this.wholeNumber = wholeNumber;
        this.wholeCodeNumber = wholeCodeNumber;
        this.itemNumber = itemNumber;
    }

    public string ItemNumber
    {
        get { return itemNumber; }
        set { itemNumber = value; }
    }

    public string IncrementedNumber
    { 
        get { return incrementedNumber; }
        set { incrementedNumber = value; } 
    }

    public string CheckDigit
    {
        get { return checkDigit; }
        set { checkDigit = value; }
    }

    public string WholeNumber
    {
        get { return wholeNumber; }
        set { wholeNumber = value; }
    }

    public string WholeCodeNumber
    {
        get { return wholeCodeNumber; }
        set { wholeCodeNumber = value; }
    }

}

Sonra koduma başlamıştım, ancak sorun şu ki işlemin artımlı olduğu anlamına gelir, yani öğe numarasını bir ızgara görünümünden onay kutuları aracılığıyla alıp listeye koyarım. Sonra veri tabanından son UPC'yi alıyorum, checkdigit'i soydum, ardından sayıyı birer birer artırdım ve listeye koydum. Sonra yeni numaranın kontrol numarasını hesapladım ve listeye koydum. Ve burada zaten bir Bellek Dolu İstisnası alıyorum. İşte şimdiye kadar sahip olduğum kod:

List<Codes> ItemNumberList = new List<Codes>();


    private void buttonSearch2_Click(object sender, EventArgs e)
    {
        //Fill the datasets
        this.immasterTableAdapter.FillByWildcard(this.alereDataSet.immaster, (textBox5.Text));
        this.upccodeTableAdapter.FillByWildcard(this.hangtagDataSet.upccode, (textBox5.Text));
        this.uPCTableAdapter.Fill(this.uPCDataSet.UPC);
        string searchFor = textBox5.Text;
        int results = 0;
        DataRow[] returnedRows;
        returnedRows = uPCDataSet.Tables["UPC"].Select("ItemNumber = '" + searchFor + "2'");
        results = returnedRows.Length;
        if (results > 0)
        {
            MessageBox.Show("This item number already exists!");
            textBox5.Clear();
            //clearGrids();
        }
        else
        {
            //textBox4.Text = dataGridView1.Rows[0].Cells[1].Value.ToString();
            MessageBox.Show("Item number is unique.");
        }
    }

    public void checkMarks()
    {

        for (int i = 0; i < dataGridView7.Rows.Count; i++)
        {
            if ((bool)dataGridView7.Rows[i].Cells[3].FormattedValue)
            {
                {
                    ItemNumberList.Add(new Codes(dataGridView7.Rows[i].Cells[0].Value.ToString(), "", "", "", ""));
                }
            }
        }
    }

    public void multiValue1()
    {
        _value = uPCDataSet.UPC.Rows[uPCDataSet.UPC.Rows.Count - 1]["UPCNumber"].ToString();//get last UPC from database
        _UPCNumber = _value.Substring(0, 11);//strip out the check-digit
        _UPCNumberInc = Convert.ToInt64(_UPCNumber);//convert the value to a number

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            _UPCNumberInc = _UPCNumberInc + 1;
            _UPCNumberIncrement = Convert.ToString(_UPCNumberInc);//assign the incremented value to a new variable
            ItemNumberList.Add(new Codes("", _UPCNumberIncrement, "", "", ""));//**here I get the OutOfMemoreyException**
        }

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            long chkDigitOdd;
            long chkDigitEven;
            long chkDigitSubtotal;
            chkDigitOdd = Convert.ToInt64(_UPCNumberIncrement.Substring(0, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(2, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(4, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(6, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(8, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(10, 1));
            chkDigitOdd = (3 * chkDigitOdd);
            chkDigitEven = Convert.ToInt64(_UPCNumberIncrement.Substring(1, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(3, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(5, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(7, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(9, 1));
            chkDigitSubtotal = (300 - (chkDigitEven + chkDigitOdd));
            _chkDigit = chkDigitSubtotal.ToString();
            _chkDigit = _chkDigit.Substring(_chkDigit.Length - 1, 1);
            ItemNumberList.Add(new Codes("", "",_chkDigit, "", ""));
        }

Bir liste kullanarak, bu gitmek için doğru yolu mu, yoksa farklı bir şekilde mi bakmalıyım?


Bir liste kullanmak iyidir. Eklerken listeyi yinelemek kodunuzu patlatmak için kesin bir yoldur ve mantığınızda (veya kod yazarken) bir sorun olduğunu gösterir. Ayrıca, bu senin hatan ve gelecekteki ziyaretçilere yardım etmesi muhtemel değil. Kapatmak için oy kullanma.
Telastyn

2
Not olarak, bu özel alanların tümü ( Codesınıfınızdaki) gereksizdir ve gürültüden başka hiçbir şey { get; private set; }yeterli değildir.
Konrad Morawski

5
Bunun için soru başlığı gerçekten doğru mu? Bu gerçekten bir liste vs dizi soru gibi görünmüyor , ne kadar çok bir uygulama sorumu nasıl geliştirebilirim . Buna göre, eğer element ekliyor veya kaldırıyorsanız , bir liste (veya başka bir esnek veri yapısı) istiyorsunuz. Diziler, yalnızca başlangıçta kaç öğeye ihtiyacınız olduğunu tam olarak bildiğiniz zaman gerçekten iyidir.
KChaloux

@KChaloux, bilmek istediğim bu. Bir liste buna devam etmenin doğru yolu mu, yoksa bunu sürdürmek için farklı bir yola mı bakmalıyım? Görünüşe göre bir liste iyi bir yol, sadece mantığımı değiştirmem gerekiyor.
campagnolo_1

1
@Telastyn, ne yapmaya çalıştığımı göstermek için kodumu geliştirmek istememiştim.
campagnolo_1

Yanıtlar:


73

Yorumumu genişleteceğim:

... eleman ekliyor veya kaldırıyorsanız , bir liste (veya başka bir esnek veri yapısı) istiyorsunuz. Diziler, yalnızca başlangıçta kaç öğeye ihtiyacınız olduğunu tam olarak bildiğiniz zaman gerçekten iyidir.

Hızlı bir Arıza

Diziler , değişmesi muhtemel olmayan sabit sayıda öğeye sahip olduğunuzda ve sıralı olmayan bir şekilde erişmek istediğinizde iyidir .

  • Sabit Boyut
  • Hızlı Erişim - O (1)
  • Yavaş Yeniden Boyutlandır - O (n) - her elemanı yeni bir diziye kopyalamanız gerekiyor!

Bağlantılı Listeler , her iki uçta da hızlı eklemeler ve kaldırmalar için optimize edilmiştir, ancak ortada erişimi yavaştır.

  • Değişken boyutu
  • Ortada Yavaş Erişim - O (n)
    • İstenilen endekse ulaşmak için kafadan başlayarak her bir elemanı geçmesi gerekir
  • Kafada Hızlı Erişim - O (1)
  • Tail'de potansiyel olarak hızlı erişim
    • O (1) eğer bir referans kuyruk ucunda saklanmışsa (çift bağlantılı bir listede olduğu gibi)
    • O (n) eğer referans kaydedilmemişse (ortadaki bir düğüme erişim ile aynı karmaşıklık)

Dizi Listeleri ( List<T>C #! Gibi ), oldukça hızlı ilaveler ve rastgele erişime sahip olan ikisinin bir karışımıdır . List<T> Ne kullanacağınızdan emin değilseniz genellikle koleksiyonunuz olacaktır.

  • Bir diziyi destek yapısı olarak kullanır
  • Yeniden boyutlandırma konusunda akıllıdır - mevcut alanın iki katını tükendiğinde ayırır.
    • Bu, O (log n) yeniden boyutlandırmasına yol açar, bu her ekle / kaldırmamızda yeniden boyutlandırmaktan daha iyidir
  • Hızlı Erişim - O (1)

Diziler Nasıl Çalışır?

Çoğu dil modeli, her öğenin aynı boyutta olduğu bellekteki bitişik veriler olarak diziler oluşturur. Diyelim ki bir ints dizimiz vardı ([adres: değer] olarak gösterilir, ondalık adresleri kullanırım çünkü tembelim)

[0: 10][32: 20][64: 30][96: 40][128: 50][160: 60]

Bu öğelerin her biri 32 bit tam sayıdır, bu nedenle bellekte ne kadar yer kapladığını biliyoruz (32 bit!). İmlecin ilk etaptaki hafıza adresini biliyoruz.

Bu dizideki başka herhangi bir öğenin değerini elde etmek son derece kolaydır:

  • İlk elemanın adresini al
  • Her elemanın ofsetini alın (bellekteki boyutu)
  • Ofseti istenen indeks ile çarpın
  • Sonucunuzu ilk öğenin adresine ekleyin

Diyelim ki ilk elementimiz '0'da. İkinci elementimizin '32' (0 + (32 * 1)) olduğunu ve üçüncü elementimizin 64 (0 + (32 * 2)) olduğunu biliyoruz.

Tüm bu değerleri yan yana bellekte saklayabildiğimiz gerçeği, dizimizin mümkün olduğu kadar küçük olması anlamına geliyor. Aynı zamanda, tüm öğelerin çalışmaya devam etmesi için bir arada kalması gerektiği anlamına gelir !

Bir öğeyi eklediğimiz veya kaldırdığımız anda, öğelerin arasında boşluk olmadığından ve her şeyin yeterli yer kaldığından emin olmak için başka her şeyi almamız ve bunları bellekteki yeni bir yere kopyalamamız gerekir. Bu çok yavaş olabilir , özellikle de her seferinde tek bir eleman eklemek istediğinizde.

Bağlantılı Listeler

Dizilerin aksine, Bağlı Listeler, tüm öğelerinin bellekte yan yana olmalarına gerek duymaz. Aşağıdaki bilgileri depolayan düğümlerden oluşurlar:

Node<T> {
    Value : T      // Value of this element in the list
    Next : Node<T> // Reference to the node that comes next
}

Listenin kendisi çoğu durumda kafa ve kuyruğa (ilk ve son düğümler) yapılan bir referansı tutar ve bazen büyüklüğünü izler.

Listenin sonuna bir öğe eklemek istiyorsanız, yapmanız gereken tek şey kuyruğu almak ve değerini, değerinizi içeren Nextyeni bir başvuruda bulunmak üzere değiştirmek Node. Uçtan çıkarmak eşit derecede basittir - sadece Nextönceki düğümün değerini kaldırınız .

Ne yazık ki, LinkedList<T>1000 öğeli bir öğeniz varsa ve 500 öğesinin olmasını istiyorsanız, bir dizideki gibi 500. öğeye doğrudan atlamanın kolay bir yolu yoktur. En başlamak gerekir kafasına ve gidiş devam Nextbunu 500 kez yaptık kadar düğüm.

Bu nedenle ekleme ve çıkarma işlemlerinin LinkedList<T>hızlı bir şekilde gerçekleştirilmesi (uçlarda çalışırken), ancak orta noktaya erişimin yavaş olması.

Düzenleme : Brian, Bağlantılı Listeler'in bitişik bellekte saklanmadığından sayfa hatasına neden olma riski taşıdığına işaret eder. Bu kıyaslama yapmak zor olabilir ve Bağlantılı Listeleri, yalnızca zaman karmaşıklıkları göz önüne alındığında beklediğinizden biraz daha yavaş hale getirebilir.

Her iki dünyanın en iyisi

List<T>her ikisinden de ödün vermez T[]ve LinkedList<T>çoğu durumda oldukça hızlı ve kullanımı kolay bir çözüm sunar.

Dahili List<T>olarak bir dizidir! Yeniden boyutlandırırken öğelerini kopyalama çemberlerinin içinden atlamak zorunda kalır, ancak bazı güzel hileler çeker.

Yeni başlayanlar için, tek bir öğe eklemek genellikle dizinin kopyalanmasına neden olmaz. List<T>daha fazla eleman için her zaman yeterli alan olmasını sağlar. Bittiğinde, yalnızca bir yeni öğeye yeni bir iç dizi atamak yerine, birkaç yeni öğeye sahip yeni bir dizi tahsis eder (çoğunlukla şu anda sahip olduğu değerin iki katı!).

Kopyalama işlemleri pahalıdır, bu yüzden List<T>hızlı bir şekilde rasgele erişime izin verirken mümkün olduğu kadar keser. Bir yan etki olarak, düz bir diziden veya bağlantılı bir listeden biraz daha fazla alan boşa harcayabilir, ancak bu genellikle değişmeye değer.

TL; DR

Kullanın List<T>. Normalde istediğiniz şeydir ve bu durumda sizin için doğru gibi görünüyor (aradığınız yerde .Add ()). Neye ihtiyacınız olduğundan emin değilseniz, List<T>başlamak için iyi bir yer.

Diziler yüksek performans için iyidir, "tam olarak X elementine ihtiyacım olduğunu biliyorum" gibi şeyler. Alternatif olarak, hızlı, bire bir "Birlikte tanımladığım bu X şeylerini birlikte gruplayabilmem için onların üzerinde dolaşabilmeliyim" yapıları için faydalıdırlar.

Çok sayıda başka koleksiyon sınıfı var. Stack<T>sadece yukarıdan çalışan bağlantılı bir liste gibidir. Queue<T>ilk giren ilk çıkar listesi olarak çalışır. Dictionary<T, U>anahtarlar ve değerler arasında sırasız, ilişkisel bir eşlemedir. Onlarla oyna ve her birinin güçlü ve zayıf yanlarını tanı. Algoritmalarınızı yapabilir veya bozabilirler.


2
Bazı durumlarda, bir dizi ve intkullanılabilir elemanların sayısını belirten bir kombinasyonun kullanılmasının avantajları olabilir . Diğer şeylerin yanı sıra, birden çok öğeyi bir diziden diğerine toplu olarak kopyalamak mümkündür, ancak listeler arasında kopyalamak genellikle öğeleri ayrı ayrı işlemeyi gerektirir. Dahası, dizi öğeleri refgibi şeyler için geçirilirken Interlocked.CompareExchangeliste öğeleri olamaz.
Süpermen

2
Keşke birden fazla oy verebilseydim. Kullanım durumu farklılıklarını ve dizilerin / bağlantılı listelerin nasıl List<>çalıştığını biliyordum, ama başlığın altında nasıl çalıştığını asla bilmiyordum .
Bobson

1
Bir Listeye <T> tek bir eleman eklenmesi, itfa edilir O (1); Eleman ekleme verimliliği normalde bağlantılı bir listeyi kullanmak için yeterli bir gerekçe değildir (ve dairesel bir liste, amortize edilmiş O (1) 'te öne ve geriye eklemenizi sağlar). Bağlantılı listeler çok fazla performans özeliğine sahiptir. Örneğin, bitişik olarak bellekte saklanmamak, tüm bağlantılı bir listeyi yinelemenin bir sayfa hatasını tetiklemesinin daha muhtemel olduğu anlamına gelir ... ve bunun kıyaslanması zordur. Bağlantılı Liste kullanmanın en büyük nedeni, iki listeyi birleştirmek (O (1) 'de yapılabilir) veya ortasına eleman eklemek istediğiniz zamandır.
Brian

1
Aydınlatmalıyım. Dairesel bir liste deyince, dairesel bir bağlı liste değil, bir dairesel dizi listesi demek istedim. Doğru terim deque (çift uçlu kuyruk) olacaktır. Genellikle, bir istisna dışında, bir Listeyle aynı şekilde (başlık altındaki dizi) uygulanır: Dizinin hangi dizininin ilk öğe olduğunu belirten bir iç tamsayı değeri vardır. Arkaya bir eleman eklemek için, "ilk" den 1 çıkarmanız yeterlidir (gerekirse dizinin uzunluğunu saran). Bir elemana erişmek için erişmeniz yeterlidir (index+first)%length.
Brian,

2
Örneğin bir index öğesini ref parametresi olarak geçirmek gibi düz bir diziyle yapabileceğiniz bir List ile yapamayacağınız birkaç şey vardır.
Ian Goldby

6

KChaloux'un cevabı harika olsa da başka bir hususu da belirtmek isterim: Bir Dizi'denList<T> çok daha güçlü. Yöntemleri List<T>pek çok durumda çok kullanışlıdır - Bir Dizi bu yöntemlere sahip değildir ve geçici çözümleri uygulamak için çok zaman harcayabilirsiniz.

Bu nedenle, geliştirme perspektifinden neredeyse her zaman kullanıyorum, List<T>çünkü ek gereksinimler olduğunda, bunları kullanırken uygulanmaları çok daha kolaydır List<T>.

Bu son bir soruna yol açar: Kodum (seninkileri bilmiyorum)% 90'ı içeriyor List<T>, bu yüzden Diziler tam olarak uymuyor. Bunları geçerken, onların .toList()yöntemini çağırmam ve onları Listeye dönüştürmem gerekiyor - bu can sıkıcı ve o kadar yavaştır ki, Dizi kullanmaktan kaynaklanan herhangi bir performans kazancı kaybolur.


Bu doğru, bu <T> Listesini kullanmanız için iyi bir neden - doğrudan sınıfa dahil ettiğiniz için çok daha fazla işlevsellik sağlıyor.
KChaloux

1
LINQ, herhangi bir IEnumerable (bir dizi dahil) için bu işlevselliğin çoğunu ekleyerek alanı düzleştirmiyor mu? Modern C # (4+) 'da bir dizi <T> ile yapabileceğiniz ve bir dizi ile yapabileceğiniz herhangi bir şey var mı?
Dave

1
@Dave Dizi / Listeyi genişletmek böyle bir şey gibi görünüyor. Ayrıca, bir Listeyi oluşturmak / işlemek için kullanılan sözdizimin dizilerden daha hoş olduğunu söyleyebilirim.
Christian Sauer

2

Kimse bu kısımdan bahsetmedi: "Burada zaten bir Bellek Dışı İstisnası alıyorum." Tamamen nedeniyle olan

for (int i = 0; i < ItemNumberList.Count; i++)
{
    ItemNumberList.Add(new item());
}

Nedenini görmek çok açık. Farklı bir listeye eklemek isteyip istemediğinizi bilmiyorum ya da sadece ItemNumberList.Countistediğiniz sonucu almak için döngüden önce değişken olarak saklamalısınız, ancak bu sadece bozuldu.

Programcılar.SE, "... yazılım geliştirme hakkındaki kavramsal sorularla ilgileniyor ..." ve diğer cevaplar böyle davrandı. Bunun yerine, http://codereview.stackexchange.com adresini deneyin . Fakat orada bile felakettir, çünkü sadece bu kodun başladığını varsayabiliriz _Click, multiValue1ki bu hatanın gerçekleştiği yere çağrı yapmaz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.