RollingList

Nel post precedente ho parlato di come utilizzare le classi di base del framework per realizzare una collection con le seguenti caratteristiche:

  • all'inserimento di un nuovo elemento questo viene posizionato all'inizio della lista (i più recenti verranno visualizzati per primi)
  • la lista deve contenere al massimo n elementi: l'inserimento di un nuovo elemento, raggiunto il limite n, deve prevedere la rimozione dell'elemento più vecchio presente nella lista (ovvero quello inserito da più tempo)
  • garantire ottime performance

Il commento di rsaccav mi ha fatto convinto ad implementare una collezione generica "from scratch", così da colmare il vuoto lasciato in System.Collection.Generic.

Con estrema fantasia l'ho battezzata RollingList<T>

Il principio è piuttosto semplice: la lista si basa su un array con dimensione fissa pari al limite di elementi da conservare (Capacity; la proprietà deve essere impostata nel costruttore o comunque *prima* dell'inserimento del primo elemento) associato ad un cursore (backward) che definisce la posizione di inserimento/sostituzione e che consente di enumerare gli elementi nell'ordine corretto.

Il codice completo della classe:

using System;
using System.Collections;
using System.Threading;

public class RollingList<T> : ICollection
{  
    private int _cursor;
    private int _size;
    private int _capacity;
    private T[] _items; 
    private object _syncRoot; 
  
    public RollingList(int capacity)
    {       
        Capacity = capacity;           
    }

    public int Capacity
    {
        get { return _capacity; }
        set
        {
            if (value <= 0)
                throw new ArgumentOutOfRangeException("value", "Capacity must be greater than zero");
            if (_capacity > 0)
                throw new ArgumentException("Capacity has just been defined", "value");
            _capacity = value;
            _items = new T[_capacity];
            _cursor = (_capacity - 1);
        }
    }  

    public void Add(T item)
    {
        if (_capacity <= 0)
            throw new NotSupportedException("Capacity is not greater than zero");
        _items[_cursor--] = item;
        if (_size < _capacity)
            _size++;
        if (_cursor < 0)
            _cursor = (_capacity - 1);
    } 

    public void Clear()
    {
        Array.Clear(_items, 0, _size);
        _size = 0;
        _cursor = (_capacity - 1);
    } 

    public T[] ToArray()
    {
        T[] destinationArray = new T[_size]; 
        if (_size == 0)
            return destinationArray;
        int cursorPosition = (_cursor + 1 == _capacity) ? 0 : _cursor + 1;
        Array.Copy(_items, cursorPosition, destinationArray, 0, (_capacity - cursorPosition));
        if (_size == _capacity && cursorPosition > 0)
            Array.Copy(_items, 0, destinationArray, (_capacity - cursorPosition), cursorPosition);
        return destinationArray;
    }
 
    public void CopyTo(Array array, int index)
    {
        Array.Copy(ToArray(), 0, array, index, _size);
    } 

    public int Count
    {
        get { return _size; }
    } 

    public bool IsSynchronized
    {
        get { return false; }
    } 

    public object SyncRoot
    {
        get
        {
            if (_syncRoot == null)
            {
                Interlocked.CompareExchange(ref _syncRoot, new object(), null);
            }
            return _syncRoot;
        } 
    } 

    public IEnumerator GetEnumerator()
    {
        return ToArray().GetEnumerator();
    }
}

Questa implementazione è sicuramente migliorabile (ad esempio, oltre a ICollection potrebbe essere utile implementare l'interfaccia ICollection<T>, supportare la serializzazione, ecc.) ma è decisamente preferibile rispetto all'uso di Stack<T>, Queue<T> o LinkedList<T> in quanto:

  1.  semplifica il codice di inserimento di elementi (le operazioni di IN/OUT al raggiungimento della capacità massima definita sono gestite dalla collection stessa, quindi non richiedono codice di controllo aggiuntivo)
  2. semplifica la lettura grazie ad una corretta enumerazione nativa degli elementi
  3. permette di avere performance notevolmente più elevate:
//
// RollingList performance test
//
int capacity = 10000;
int max = 1000000000;
DateTime start = DateTime.Now;
RollingList<int> list = new RollingList<int>(capacity);
for (int i = 1; i <= max; i++)
{
    list.Add(i);
}
foreach (int item in list)
{
    // do something with "item"
}
Console.WriteLine("RollingList: max={0} -> {1}",
    max.ToString("#,###"),
    DateTime.Now.Subtract(start));

Produce:

RollingList: max=1.000.000.000 -> 00:00:11.5610000

Ovvero un tempo di esecuzione che è circa un terzo rispetto a Queue<T>!!!

HTH

Nella stessa categoria

Commenti
»»»» m.casati scrive:
RollingList<T>

BUG FIX
Ho modificato il metodo ToArray: la versione precedente generava un'eccezione se la lista non conteneva elementi.
HTH
08/08/2008 ore 17.22
andrewz scrive:
RollingList<T>

Ottimo! Bel lavoro Matteo!
07/08/2008 ore 12.31 | 7 risposte
»»»» m.casati scrive:
RE: RollingList<T>

Come sempre sei troppo buono, grazie!
Spero possa essere utile a qualcuno (a me è servita eccome!) visto che, alla fine, è questo lo scopo del blog e, più in generale, della community.

Aspetto feedback
07/08/2008 ore 14.01 | 2 risposte
andrewz scrive:
RE: RollingList<T>

m.casati ha scritto:
Come sempre sei troppo buono, grazie!
Spero possa essere utile a qualcuno (a me è servita eccome!) visto che, alla fine, è questo lo scopo del blog e, più in generale, della community.


Mi servirà a breve

Ciao
07/08/2008 ore 14.24
Daniele Bochicchio scrive:
Re: RollingList<T>

m.casati [Staff] wrote:
Spero possa essere utile a qualcuno (a me è servita eccome!) visto che, alla fine, è questo lo scopo del blog e, più in generale, della community.

l'articolo è sempre lì ad aspettarti...
07/08/2008 ore 16.37 | 1 risposta
»»»» m.casati scrive:
Re: RollingList<T>

Tranquillo, ci sto lavorando, ma me la prendo comoda visto che le pubblicazioni sono sospese
07/08/2008 ore 17.38 | 1 risposta
dops scrive:
Re: RollingList<T>

Domanda: perchè non hai ereditato da Queue<T> ?, mi sembra che ti sarebbe bastato implementare un solo metodo per arrivare allo scopo.. mi sembra..
09/08/2008 ore 2.26 | 1 risposta
»»»» m.casati scrive:
Re: RollingList<T>

dops ha scritto:
perchè non hai ereditato da Queue<T> ?, mi sembra che ti sarebbe bastato implementare un solo metodo per arrivare allo scopo

A me non sembra, ma forse non ho capito cosa intendi. Puoi postare del codice per chiarire meglio?
11/08/2008 ore 15.01 | 1 risposta
dops scrive:
Re: RollingList<T>

vorrei provarci ma non ne ho il tempo, ripeto, è una impressione, ma forse mi sbaglio, quando mi servirà questo tipo di oggetto ci proverò
11/08/2008 ore 16.07

Aggiungi un nuovo commento »»»
Per inserire un commento, devi registrarti alla nostra community.

© 1998-2008 - Code and fun - Il blog di Matteo Casati

TagCloud
BLOG INFO
  • Post: 37
  • Commenti: 48
  • TrackBacks: 12
  • Feed blog e contenuti tecnici: RSS
  • Feed blog: RSS Atom OPML
CATEGORIE
I PIÙ LETTI DEL MESE
IN EVIDENZA