La lista delle matrici

A volte si desidera il dimensionamento flessibile e la facilità d'uso di un elenco collegato, ma è necessario avere l'indicizzazione diretta (a tempo costante) di un array. In questi casi, a Lista di array può fornire una ragionevole via di mezzo.

Panoramica

Lista di array è una collezione che implementa il IList interfaccia ma è supportato da una matrice piuttosto che da una lista collegata. Come una lista collegata, è possibile aggiungere un numero arbitrario di elementi (limitato solo dalla memoria disponibile), ma comportarsi come un array in tutti gli altri aspetti.

Definizione di classe

La classe ArrayList implementa il IList interfaccia. IList fornisce tutti i metodi e le proprietà di ICollection aggiungendo anche indicizzazione diretta e inserimento e rimozione basati su indice. Il seguente codice di esempio presenta gli stub generati utilizzando il comando Implement Interface di Visual Studio 2010.

Il seguente esempio di codice include anche tre aggiunte agli stub generati:

  • Una serie di T (_items). Questo array manterrà gli elementi nella raccolta.
  • Un costruttore predefinito che inizializza la matrice alla dimensione zero.
  • Un costruttore che accetta una lunghezza intera. Questa lunghezza diventerà la capacità predefinita dell'array. Ricorda che la capacità dell'array e del conteggio delle raccolte non sono la stessa cosa. Potrebbero esserci degli scenari quando si utilizza il costruttore non predefinito per consentire all'utente di fornire un suggerimento per il ridimensionamento Lista di array classe per ridurre al minimo il numero di volte in cui l'array interno deve essere riallocato.
public class ArrayList: System.Collections.Generic.IList T [] _items; public ArrayList (): this (0)  public ArrayList (int length) if (length < 0)  throw new ArgumentException("length");  _items = new T[length];  public int IndexOf(T item)  throw new NotImplementedException();  public void Insert(int index, T item)  throw new NotImplementedException();  public void RemoveAt(int index)  throw new NotImplementedException();  public T this[int index]  get  throw new NotImplementedException();  set  throw new NotImplementedException();   public void Add(T item)  throw new NotImplementedException();  public void Clear()  throw new NotImplementedException();  public bool Contains(T item)  throw new NotImplementedException();  public void CopyTo(T[] array, int arrayIndex)  throw new NotImplementedException();  public int Count  get  throw new NotImplementedException();   public bool IsReadOnly  get  throw new NotImplementedException();   public bool Remove(T item)  throw new NotImplementedException();  public System.Collections.Generic.IEnumerator GetEnumerator()  throw new NotImplementedException();  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()  throw new NotImplementedException();   

Inserimento

Aggiungere un oggetto a un Lista di array è dove mostra la differenza tra la matrice e la lista collegata. Ci sono due ragioni per questo. Il primo è quello Lista di array supporta l'inserimento di valori nel mezzo della raccolta, mentre un elenco collegato supporta l'aggiunta di elementi all'inizio o alla fine dell'elenco. Il secondo è che l'aggiunta di un elemento a un elenco collegato è sempre un'operazione O (1), ma l'aggiunta di elementi a un Lista di array è un'operazione O (1) o O (n).

Crescere la matrice

Quando gli oggetti vengono aggiunti alla raccolta, alla fine l'array interno potrebbe diventare pieno. Quando ciò accade, è necessario fare quanto segue:

  1. Assegna un più grande.
  2. Copia gli elementi dall'array più piccolo a quello più grande.
  3. Aggiorna l'array interno come array più grande.

L'unica domanda a cui dobbiamo rispondere a questo punto è quale dimensione dovrebbe assumere il nuovo array? La risposta a questa domanda è definita dal Lista di array politica di crescita.

Analizzeremo due criteri di crescita e, per ciascuno di essi, esamineremo la rapidità con cui l'array cresce e il suo impatto sulle prestazioni.

Raddoppio (mono e rotore)

Ci sono due implementazioni del Lista di array classe che possiamo guardare online: mono e rotore. Entrambi utilizzano un semplice algoritmo che raddoppia le dimensioni dell'array ogni volta che è necessaria un'allocazione. Se la matrice ha una dimensione pari a zero, la capacità predefinita è 16. L'algoritmo è:

size = size == 0? 1: dimensione * 2; 

Questo algoritmo ha meno allocazioni e copie di array, ma spreca più spazio in media rispetto all'approccio Java. In altre parole, è polarizzato verso più inserti O (1), che dovrebbero ridurre il numero di volte in cui la raccolta esegue l'operazione di allocazione e copia che richiede tempo. Ciò comporta un ingombro di memoria medio maggiore e, in media, più slot di array vuoti.

Crescita più lenta (Java)

Java usa un approccio simile ma aumenta la gamma un po 'più lentamente. L'algoritmo che usa per far crescere l'array è:

size = (size * 3) / 2 + 1; 

Questo algoritmo ha una curva di crescita più lenta, il che significa che è polarizzato verso un minore sovraccarico della memoria al costo di più allocazioni. Diamo un'occhiata alla curva di crescita per questi due algoritmi per un Lista di array con oltre 200.000 articoli aggiunti.

La curva di crescita per Mono / Rotor rispetto a Java per oltre 200.000 articoli

È possibile vedere in questo grafico che sono state necessarie 19 allocazioni per l'algoritmo di raddoppiamento per superare il limite di 200.000, mentre è necessario l'algoritmo più lento (Java) per 30 allocazioni per raggiungere lo stesso punto.

Quindi quale è corretto? Non c'è una risposta giusta o sbagliata. Il raddoppio esegue meno operazioni O (n), ma ha un sovraccarico di memoria in media. L'algoritmo di crescita più lento esegue più operazioni O (n) ma ha un sovraccarico di memoria inferiore. Per una raccolta di carattere generale, entrambi gli approcci sono accettabili. Il dominio del problema potrebbe avere requisiti specifici che ne rendono più attraente o potrebbe richiedere la creazione di un altro approccio. Indipendentemente dall'approccio che segui, i comportamenti fondamentali della collezione rimarranno invariati.

Nostro Lista di array la classe utilizzerà l'approccio del raddoppio (mono / rotore).

private void GrowArray () int newLength = _items.Length == 0? 16: _items.Length << 1; T[] newArray = new T[newLength]; _items.CopyTo(newArray, 0); _items = newArray;  

Inserire

Comportamento Aggiunge il valore fornito all'indice specificato nella raccolta. Se l'indice specificato è uguale o superiore a Contare, viene generata un'eccezione
Prestazione Sopra)

L'inserimento in un indice specifico richiede lo spostamento di tutti gli elementi dopo il punto di inserimento a destra di uno. Se il backing array è pieno, dovrà essere cresciuto prima di poter eseguire lo spostamento.

Nell'esempio seguente, c'è una matrice con una capacità di cinque elementi, quattro dei quali sono in uso. Il valore tre verrà inserito come terzo elemento dell'array (indice due).

La matrice prima dell'inserto (uno slot aperto alla fine) L'array dopo il passaggio a destra La matrice con il nuovo elemento aggiunto nello slot aperto
public public Insert (int index, T item) if (index> = Count) throw new IndexOutOfRangeException ();  if (_items.Length == this.Count) this.GrowArray ();  // Sposta tutti gli articoli seguendo lo slot di indice uno a destra. Array.Copy (_items, index, _items, index + 1, Count - index); _items [indice] = elemento; Conte ++;  

Inserisci

Comportamento Aggiunge il valore fornito alla fine della raccolta.
Prestazione O (1) quando la capacità dell'array è maggiore di Count; O (n) quando la crescita è necessaria.
pubblico vuoto Aggiungi (elemento T) if (_items.Length == Count) GrowArray ();  _items [Count ++] = item;  

cancellazione

RemoveAt

Comportamento Rimuove il valore nell'indice specificato.
Prestazione Sopra)

La rimozione di un indice è essenzialmente il contrario dell'operazione di inserimento. L'elemento viene rimosso dall'array e l'array viene spostato a sinistra.

L'array prima del valore 3 viene rimosso La matrice con il valore 3 rimosso L'array si è spostato a sinistra, liberando l'ultimo slot
pubblico vuoto RemoveAt (int index) if (index> = Count) throw new IndexOutOfRangeException ();  int shiftStart = indice + 1; se (shiftStart < Count)  // Shift all the items following index one slot to the left. Array.Copy(_items, shiftStart, _items, index, Count - shiftStart);  Count--;  

Rimuovere

Comportamento Rimuove il primo elemento della raccolta il cui valore corrisponde al valore fornito. ritorna vero se un valore è stato rimosso. Altrimenti ritorna falso.
Prestazione Sopra)
public bool Remove (T item) for (int i = 0; i < Count; i++)  if (_items[i].Equals(item))  RemoveAt(i); return true;   return false;  

indicizzazione

Indice di

Comportamento Restituisce il primo indice nella raccolta il cui valore è uguale al valore fornito. Restituisce -1 se non viene trovato alcun valore corrispondente.
Prestazione Sopra)
public int IndexOf (T item) for (int i = 0; i < Count; i++)  if (_items[i].Equals(item))  return i;   return -1;  

Articolo

Comportamento Ottiene o imposta il valore sull'indice specificato.
Prestazione O (1)
pubblico T questo [int index] get if (indice < Count)  return _items[index];  throw new IndexOutOfRangeException();  set  if (index < Count)  _items[index] = value;  else  throw new IndexOutOfRangeException();    

contiene

Comportamento Restituisce true se il valore fornito esiste nella raccolta. Altrimenti restituisce falso.
Prestazione Sopra)
public bool Contains (T item) return IndexOf (item)! = -1;  

Enumerazione

GetEnumerator

Comportamento Restituisce un IEnumerator istanza che consente di enumerare i valori dell'elenco di matrice in ordine dal primo all'ultimo.
Prestazione Il ritorno dell'istanza dell'enumeratore è un'operazione O (1). L'enumerazione di ogni elemento è un'operazione O (n).

Nota che non possiamo semplicemente rimandare al _elementi serie di GetEnumerator perché ciò restituirebbe anche gli articoli che al momento non sono pieni di dati.

pubblico System.Collections.Generic.IEnumerator GetEnumerator () for (int i = 0; i < Count; i++)  yield return _items[i];   System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()  return GetEnumerator();  

IList restante metodi

Chiaro

Comportamento Rimuove tutti gli elementi dall'elenco di array.
Prestazione O (1)

Ci sono due opzioni quando si implementa Chiaro. La matrice può essere lasciata da sola o può essere riallocata come una matrice di lunghezza 0. Questa implementazione rialloca un nuovo array con una lunghezza pari a zero. Un array più grande verrà assegnato quando un elemento viene aggiunto all'array usando il Inserisci o Inserire metodi.

Public void Clear () _items = new T [0]; Conteggio = 0;  

Copia a

Comportamento Copia il contenuto dell'array interno dall'inizio alla fine nell'array fornito a partire dall'indice dell'array specificato.
Prestazione Sopra)

Si noti che il metodo non si limita a rimandare a _elementi serie di Copia a metodo. Questo perché vogliamo solo copiare l'intervallo dall'indice 0 a Contare, non l'intera capacità dell'array. utilizzando Array.Copy ci consente di specificare il numero di elementi da copiare.

public void CopyTo (T [] array, int arrayIndex) Array.Copy (_items, 0, array, arrayIndex, Count);  

Contare

Comportamento Restituisce un numero intero che indica il numero di elementi attualmente nella raccolta. Quando la lista è vuota, il valore è 0.
Prestazione O (1)

Contare è semplicemente una proprietà implementata automaticamente con un getter pubblico e un setter privato. Il vero comportamento si verifica nelle funzioni che manipolano i contenuti della raccolta.

int pubblico count; set privato;  

IsReadOnly

Comportamento Restituisce false perché la raccolta non è di sola lettura.
Prestazione O (1)
public bool IsReadOnly get return false;  

Prossimo

Questo completa la terza parte sugli elenchi di array. Successivamente, passeremo allo stack e alle code.