Dovrei usare un elenco o un array?


22

Sto lavorando su un modulo di Windows per calcolare UPC per i numeri di articolo.

Ho creato con successo uno che gestirà un numero di articolo / UPC alla volta, ora voglio espandere e farlo per più numeri di articolo / UPC.

Ho iniziato e provato a usare un elenco, ma continuo a rimanere bloccato. Ho creato una classe di supporto:

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

}

Quindi ho iniziato con il mio codice, ma il problema è che il processo è incrementale, il che significa che ottengo il numero di articolo da una visualizzazione griglia tramite caselle di controllo e li inserisco nell'elenco. Quindi ottengo l'ultimo UPC dal database, rimuovo la cifra di controllo, quindi incremento il numero di uno e lo inserisco nell'elenco. Quindi calcolo la cifra di controllo per il nuovo numero e lo inserisco nell'elenco. E qui ottengo già un'eccezione di memoria esaurita. Ecco il codice che ho finora:

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, "", ""));
        }

È questo il modo giusto di farlo, usando un elenco o dovrei guardare in un modo diverso?


L'uso di un elenco va bene. Iterare l'elenco mentre lo si aggiunge è un modo infallibile per far esplodere il codice e indica un problema nella logica (o nella scrittura del codice). Inoltre, questo è il tuo bug e probabilmente non aiuterà i futuri visitatori. Votare per chiudere.
Telastyn,

2
Come nota a margine, tutti questi campi privati ​​(nella tua Codeclasse) sono ridondanti e nient'altro che rumore in realtà, { get; private set; }sarebbe sufficiente.
Konrad Morawski,

5
Il titolo della domanda per questo è davvero preciso? Questa non sembra davvero una domanda tra lista e matrice , ma piuttosto come Come posso migliorare la mia domanda di implementazione . Detto questo, se stai aggiungendo o rimuovendo elementi, vuoi un elenco (o altra struttura di dati flessibile). Le matrici sono davvero buone solo quando sai esattamente quanti elementi hai bisogno all'inizio.
KChaloux,

@KChaloux, è praticamente quello che volevo sapere. Un elenco è il modo giusto di procedere o avrei dovuto cercare un modo diverso di perseguire questo obiettivo? Quindi sembra che un elenco sia un buon modo, devo solo regolare la mia logica.
campagnolo_1

1
@Telastyn, non stavo chiedendo tanto di migliorare il mio codice quanto di mostrare cosa sto cercando di fare.
campagnolo_1

Risposte:


73

Espanderò il mio commento:

... se stai aggiungendo o rimuovendo elementi, vuoi un elenco (o altra struttura di dati flessibile). Le matrici sono davvero buone solo quando sai esattamente quanti elementi hai bisogno all'inizio.

Una rapida ripartizione

Le matrici sono valide quando si dispone di un numero fisso di elementi che è improbabile che cambi e si desidera accedervi in ​​modo non sequenziale.

  • Taglia unica
  • Accesso rapido - O (1)
  • Ridimensionamento lento - O (n) - deve copiare ogni elemento in un nuovo array!

Gli elenchi collegati sono ottimizzati per aggiunte e rimozioni rapide alle estremità, ma sono lenti ad accedervi nel mezzo.

  • Dimensione variabile
  • Accesso lento a metà - O (n)
    • Deve attraversare ogni elemento a partire dalla testa per raggiungere l'indice desiderato
  • Accesso rapido alla testa - O (1)
  • Accesso potenzialmente veloce alla coda
    • O (1) se un riferimento è memorizzato alla fine della coda (come con un elenco doppiamente collegato)
    • O (n) se non viene memorizzato alcun riferimento (stessa complessità dell'accesso a un nodo nel mezzo)

Gli elenchi di array (come List<T>in C #!) Sono una combinazione dei due, con aggiunte abbastanza veloci e accesso casuale. List<T> sarà spesso la tua collezione preferita quando non sei sicuro di cosa usare.

  • Utilizza un array come struttura di supporto
  • È intelligente nel ridimensionamento: alloca il doppio del suo spazio corrente quando si esaurisce.
    • Questo porta a O (log n) ridimensionamenti, che è meglio che ridimensionare ogni volta che aggiungiamo / rimuoviamo
  • Accesso rapido - O (1)

Come funzionano le matrici

La maggior parte delle lingue modella gli array come dati contigui in memoria, di cui ogni elemento ha le stesse dimensioni. Diciamo che avevamo una matrice di ints (mostrata come [indirizzo: valore], usando indirizzi decimali perché sono pigro)

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

Ognuno di questi elementi è un numero intero a 32 bit, quindi sappiamo quanto spazio occupa in memoria (32 bit!). E conosciamo l'indirizzo di memoria del puntatore al primo elemento.

È banalmente facile ottenere il valore di qualsiasi altro elemento in quell'array:

  • Prendi l' indirizzo del primo elemento
  • Prendi l' offset di ogni elemento (le sue dimensioni in memoria)
  • Moltiplica l'offset per l'indice desiderato
  • Aggiungi il tuo risultato all'indirizzo del primo elemento

Diciamo che il nostro primo elemento è a '0'. Sappiamo che il nostro secondo elemento è a '32' (0 + (32 * 1)) e il nostro terzo elemento è a 64 (0 + (32 * 2)).

Il fatto che possiamo memorizzare tutti questi valori uno accanto all'altro in memoria significa che il nostro array è il più compatto possibile. Significa anche che tutti i nostri elementi devono stare insieme affinché le cose continuino a funzionare!

Non appena aggiungiamo o rimuoviamo un elemento, dobbiamo raccogliere tutto il resto e copiarlo in un nuovo posto in memoria, per assicurarci che non ci siano spazi vuoti tra gli elementi e che tutto abbia abbastanza spazio. Questo può essere molto lento , specialmente se lo fai ogni volta che vuoi aggiungere un singolo elemento.

Elenchi collegati

A differenza delle matrici, gli elenchi collegati non necessitano che tutti i loro elementi siano uno accanto all'altro in memoria. Sono composti da nodi, che memorizzano le seguenti informazioni:

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

L'elenco stesso mantiene un riferimento alla testa e alla coda (primo e ultimo nodo) nella maggior parte dei casi e talvolta tiene traccia delle sue dimensioni.

Se vuoi aggiungere un elemento alla fine dell'elenco, tutto quello che devi fare è prendere la coda e cambiarlo Nextper fare riferimento a un nuovo Nodecontenente il tuo valore. Rimuovere dalla fine è altrettanto semplice: basta determinare il Nextvalore del nodo precedente.

Sfortunatamente, se hai un LinkedList<T>con 1000 elementi e vuoi l'elemento 500, non c'è modo semplice di saltare direttamente al 500 ° elemento come in un array. Devi iniziare dalla testa e continuare fino al Nextnodo, fino a quando non lo hai fatto 500 volte.

Questo è il motivo per cui l'aggiunta e la rimozione da a LinkedList<T>è rapida (quando si lavora alle estremità), ma l'accesso al centro è lento.

Modifica : Brian sottolinea nei commenti che gli elenchi collegati hanno il rischio di causare un errore di pagina, a causa del fatto che non sono memorizzati nella memoria contigua. Questo può essere difficile da confrontare e può rendere gli elenchi collegati anche un po 'più lenti di quanto ci si possa aspettare, data la loro complessità temporale.

Il meglio di entrambi i mondi

List<T>scende a compromessi per entrambi T[]e LinkedList<T>presenta una soluzione ragionevolmente veloce e facile da usare nella maggior parte delle situazioni.

Internamente, List<T>è un array! Deve ancora saltare attraverso i cerchi di copiare i suoi elementi durante il ridimensionamento, ma tira alcuni trucchi accurati.

Per cominciare, l'aggiunta di un singolo elemento di solito non provoca la copia dell'array. List<T>si assicura che ci sia sempre abbastanza spazio per più elementi. Quando si esaurisce, invece di allocare un nuovo array interno con un solo nuovo elemento, alloca un nuovo array con diversi nuovi elementi (spesso il doppio di quello che contiene attualmente!).

Le operazioni di copia sono costose, quindi List<T>riducono il più possibile su di esse, consentendo comunque un accesso casuale veloce. Come effetto collaterale, potrebbe finire per sprecare leggermente più spazio di un array semplice o di un elenco collegato, ma di solito vale il compromesso.

TL; DR

Usa List<T>. Normalmente è quello che vuoi, e sembra essere corretto per te in questa situazione (dove stai chiamando .Add ()). Se non sei sicuro di ciò di cui hai bisogno, List<T>è un buon punto di partenza.

Le matrici vanno bene per le cose ad alte prestazioni "So di aver bisogno esattamente di elementi X". In alternativa, sono utili per una rapida, una tantum "Ho bisogno di raggruppare queste X cose che ho già definito insieme in modo da poterle scorrere".

Esistono diverse altre classi di raccolta. Stack<T>è come un elenco collegato che funziona solo dall'alto. Queue<T>funziona come un elenco first-in-first-out. Dictionary<T, U>è una mappatura associativa non ordinata tra chiavi e valori. Gioca con loro e conosci i punti di forza e di debolezza di ciascuno. Possono creare o rompere i tuoi algoritmi.


2
In alcuni casi, potrebbero esserci dei vantaggi nell'usare una combinazione di un array e intindicare il numero di elementi utilizzabili. Tra le altre cose, è possibile copiare in blocco più elementi da un array all'altro, ma in genere la copia tra elenchi richiede l'elaborazione di singoli elementi. Inoltre, gli elementi dell'array possono essere passati refa cose del genere Interlocked.CompareExchange, mentre gli elementi della lista no.
supercat

2
Vorrei poter dare più di un voto. Conoscevo le differenze del caso d'uso e come funzionavano le matrici / elenchi collegati, ma non ho mai saputo o pensato a come List<>funzionasse sotto il cofano.
Bobson,

1
L'aggiunta di un singolo elemento a un Elenco <T> è ammortizzata O (1); l'efficienza dell'aggiunta di elementi non è normalmente una giustificazione sufficiente per utilizzare un elenco collegato (e un Elenco circolare consente di aggiungere in primo piano E in indietro in O ammortizzato (1)). Le liste collegate hanno molte idiosincrasie delle prestazioni. Ad esempio, non essere archiviato in modo contiguo nella memoria significa che l'iterazione su un intero elenco collegato ha maggiori probabilità di innescare un errore di pagina ... e questo è difficile da confrontare. La giustificazione più grande per l'utilizzo di un elenco collegato è quando si desidera concatenare due elenchi (può essere fatto in O (1)) o aggiungere elementi al centro.
Brian,

1
Dovrei chiarire. Quando ho detto un elenco circolare, intendevo un elenco di array circolari, non un elenco circolare collegato. Il termine corretto sarebbe deque (coda doppia). Spesso sono implementati più o meno allo stesso modo di un Elenco (array sotto il cofano), con un'eccezione: esiste un valore intero interno, "primo" che indica quale indice dell'array è il primo elemento. Per aggiungere un elemento sul retro, basta sottrarre 1 dal "primo" (avvolgendolo alla lunghezza dell'array, se necessario). Per accedere a un elemento, è sufficiente accedere (index+first)%length.
Brian,

2
Ci sono alcune cose che non puoi fare con un Elenco che puoi fare con un array semplice, ad esempio passando un elemento indice come parametro ref.
Ian Goldby,

6

Mentre la risposta di KChaloux è eccezionale, vorrei sottolineare un'altra considerazione: List<T>è molto più potente di un array. I metodi di List<T>sono molto utili in molte circostanze: un array non ha questi metodi e potresti impiegare molto tempo per implementare soluzioni alternative.

Quindi, dal punto di vista dello sviluppo, uso quasi sempre List<T>perché quando ci sono requisiti aggiuntivi, sono spesso molto più facili da implementare quando si utilizza a List<T>.

Questo porta ad un problema finale: il mio codice (non conosco il tuo) contiene il 90% List<T>, quindi gli array non si adattano davvero. Quando li passo, devo chiamare il loro .toList()metodo e convertirli in un elenco - questo è fastidioso ed è così lento che si perde qualsiasi guadagno in termini di prestazioni derivante dall'uso di un array.


È vero, questo è un altro buon motivo per usare List <T>: fornisce molte più funzionalità integrate direttamente nella classe.
KChaloux,

1
LINQ non livella il campo aggiungendo molte di quelle funzionalità per qualsiasi IEnumerable (incluso un array)? È rimasto qualcosa nel moderno C # (4+) che puoi fare solo con un Elenco <T> e non con un array?
Dave,

1
@Dave L'estensione dell'array / elenco sembra una cosa del genere. Inoltre, direi che la sintassi per costruire / gestire un elenco è molto più gradevole rispetto agli array.
Christian Sauer,

2

Nessuno ha menzionato questa parte però: "E qui ottengo già un'eccezione di memoria esaurita". Ciò è dovuto interamente

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

È chiaro perché. Non so se intendevi aggiungere un altro elenco o se dovessi semplicemente archiviarlo ItemNumberList.Countcome variabile prima del ciclo per ottenere il risultato desiderato, ma questo è semplicemente rotto.

Programmers.SE è per "... interessato a domande concettuali sullo sviluppo del software ...", e le altre risposte lo hanno trattato come tale. Prova invece http://codereview.stackexchange.com , dove questa domanda potrebbe andare bene. Ma anche lì è orribile, dato che possiamo solo supporre che questo codice inizi da _Click, che non ha alcuna chiamata a multiValue1dove dici che si verifica l'errore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.