NSA - Non Solo Amiga

SOFTWARE => Linguaggi di programmazione e scripting => Topic aperto da: clros - 06 Novembre 2011, 13:10:10

Titolo: [C++] Inserimento non ordinato in container STL
Inserito da: clros - 06 Novembre 2011, 13:10:10
Ciao a tutti,

se voglio inserire qualcosa in un container STL (esempio un set oppure un(a) map), l'elemento viene inserito sulla base di una funzione di confronto (es: std::less<>).
E' possibile mantenere l'ordine degli elementi inseriti in base all'ordine di inserimento stesso nel programma?

es:

Codice: [Seleziona]
int main()
{
    set<int, less<int> > s;
    set<int, less<int> >::iterator i;

    s.insert(4);
    s.insert(0);
    s.insert(-9);
    s.insert(7);
    s.insert(-2);
    s.insert(4);
    s.insert(2);

    cout << "The set contains the elements: ";
    for (i=s.cbegin(); i!=s.cend(); i++)
                 cout << *i << ' ';
    cout << endl;
}

In questo caso otterrei un output "ordinato".

Ma se volessi ottenere l'output nello stesso ordine in cui ho inserito gli elementi?
(In pratica vorrei che il contenitore non ordini in nessun modo quello che inserisco...)
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: TheKaneB - 06 Novembre 2011, 15:53:47
map e set sono implementati in modo da riordinare gli elementi per un lookup rapido in fase di ricerca. Se vuoi gli elementi nell'ordine in cui li hai inseriti allora stai cercando una coda (Queue).

Puoi usare ad esempio una linked list in cui inserisci in testa (push_front) e cancelli in coda (back, pop_back).

Se hai bisono sia della mappa che della lista ordinata, allora considera la possibilità di gestire simultaneamente le due strutture dati, incapsulandole in un container custom, oppure valuta se il tuo design non sia in qualche modo fallato in origine (sarebbe utile conoscere il contesto di utilizzo di tale struttura).
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: clros - 06 Novembre 2011, 17:54:13
Citazione da: "TheKaneB"
map e set sono implementati in modo da riordinare gli elementi per un lookup rapido in fase di ricerca. Se vuoi gli elementi nell'ordine in cui li hai inseriti allora stai cercando una coda (Queue).

Puoi usare ad esempio una linked list in cui inserisci in testa (push_front) e cancelli in coda (back, pop_back).

Se hai bisono sia della mappa che della lista ordinata, allora considera la possibilità di gestire simultaneamente le due strutture dati, incapsulandole in un container custom, oppure valuta se il tuo design non sia in qualche modo fallato in origine (sarebbe utile conoscere il contesto di utilizzo di tale struttura).

Nessun contesto per adesso, mi chiedevo solo come mai non sia possibile una cosa del genere.
In ogni caso, una Queue, non credo si comporti come un set (nessuna duplicazione degli elementi inseriti)...
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: TheKaneB - 06 Novembre 2011, 18:16:38
No infatti, sono strutture diverse con relative caratteristiche peculiari.
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: TheKaneB - 06 Novembre 2011, 21:01:09
Quello che mi viene in mente è la seguente soluzione (in pseudo-C++)

Codice: [Seleziona]
template< typename K, typename T >
class MyContainer
{
public:
MyContainer()
{
mHistoryCursor = mHistory.begin();
}

T* put(K key, T value)
{
DictionaryType::iterator it = mDictionary.find(key);

if (it == mDictionary.end() )
{
T* tmp = &(mDictionary[key] = value);
mHistory->push_front(tmp);
return tmp;
}

return null;
}

// Versione non ordinata
T* get(K key) const
{
DictionaryType::iterator it = mDictionary.find(key);
if (it != mDictionary.end())
return (*it)->second;
else
return null;
}

// Versione ordinata
T* getNext()
{
if (mHistoryCursor != mHistory.end())
{
T* ret = *(++mHistoryCursor);
return ret;
}
else
return null;
}

void resetCursor()
{
mHistoryCursor = mHistory.begin();
}

private:
list< T* > mHistory;
list< T* >::iterator mHistoryCursor;
typedef hash_map< K, T > DictionaryType;
DictionaryType mDictionary;

};

Non l'ho compilato, nè testato, è solo un pezzo di codice scritto di getto per darti un'idea in linea di massima di come sia possibile implementare una struttura dati complessa a partire da due più semplici. In questo caso effettuo gli inserimenti nella hash_map solamente se la chiave non è già mappata (ma potresti voler inserire ugualmente, in modo da aggiornare la vecchia coppia key-value con un nuovo value, dipende dalle tue esigenze). Una volta inserito nella hash_map, metto un puntatore a questo elemento anche nella linked list.
Ho fatto una list di puntatori a value, ma potrei anche fare una list di map::iterator, in base a quello che ti serve.
Se volessi implementare l'interfaccia standard dei container STL allora sarebbe più sensato implementare i vari tipi di iteratori (invece del Cursor, molto basilare, che ho mostrato qui).
E' una cosa fondamentale che la lista non contenga una copia di "value" ma un puntatore. In questo modo, se il codice utente prende un reference ad un oggetto del container, sei sicuro che i dati in esso presenti saranno sempre consistenti, altrimenti potrebbe verificarsi che uno modifica la map e la lista rimane non aggiornata o viceversa.

Ripeto un'altra volta: ho buttato giù un po' di "pseudo c++" solo per illustrare la tecnica, quasi sicuramente questo codice non funzionerà perchè non so nemmeno se compila  :geek:
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: cdimauro - 07 Novembre 2011, 06:33:22
Citazione da: "TheKaneB"
map e set sono implementati in modo da riordinare gli elementi per un lookup rapido in fase di ricerca.
Purtroppo non è così. L'STL prevede l'utilizzo di un albero in entrambi i casi per "tenere ordinati" gli elementi (sic.), per cui la ricerca impiega tempo O(log(n)).

E' una scelta del tutto priva di senso perché un insieme è per definizione disordinato e non avrebbe di certo bisogno di tenere ordinati i suoi elementi. Idem le mappe.

Insieme e mappe nascono e sono usati principalmente per testare se un elemento esiste nell'insieme oppure no, e recuperare il valore associato. Sono queste le operazioni che andrebbero velocizzate (assieme all'inserimento) e difatti le implementazioni di queste strutture dati in altri linguaggi richiedono generalmente tempo O(1).

Non so cosa passasse nella mente di Stroustrup per decidere diversamente, ma a mio avviso si è trattato di una scelta veramente stupida.
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: TheKaneB - 07 Novembre 2011, 10:09:25
Esiste comunque anche la struttura hash map, ma occupa più memoria
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: cdimauro - 07 Novembre 2011, 10:16:09
Ricordavo soltanto la hash. Buono a sapersi.
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: clros - 07 Novembre 2011, 16:11:20
Citazione da: "dsar"
Citazione da: "clros"
In ogni caso, una Queue, non credo si comporti come un set (nessuna duplicazione degli elementi inseriti)...
Le STL del C++ non implementano l'unordered set? I cointaner di Ada offrono entrambe le implementazioni (che però si chiamano hashed e ordered). Anche Java mi pare offra l'alternativa.

Però non credo che li inserisca come dici tu (ordinati dall'inserimento), semplicemente non ti garantisce l'ordine che dipende dal tipo di implementazione, quindi non dovresti fare alcuna supposizione sull'ordinamento. Il modo più elementare (e veloce) per implementare l'unordered set è l'hash table, più disordinato di così!

L'unordered_set (di sicuro l'unordered_map) è implementato nel C++11.
Però..hai ragione; non li inserisce in base all'inserimento ma proprio usando una hash table, quindi li "ordina" in base all'algoritmo di hashing usato...
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: clros - 07 Novembre 2011, 16:15:27
Citazione da: "cdimauro"
[...]

Non so cosa passasse nella mente di Stroustrup per decidere diversamente, ma a mio avviso si è trattato di una scelta veramente stupida.

Che io sappia, STL non l'ha progettata/scritta Stroustrup...
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: cdimauro - 07 Novembre 2011, 18:19:16
Boh. Penso che l'abbia comunque seguita. Ne era così contento nel '95 (se non ricordo male), quando la presentò a un seminario alla New York University (all'epoca ero lì in stage universitario :D).
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: clros - 07 Novembre 2011, 18:31:42
Citazione da: "cdimauro"
Boh. Penso che l'abbia comunque seguita. Ne era così contento nel '95 (se non ricordo male), quando la presentò a un seminario alla New York University (all'epoca ero lì in stage universitario :D).

Non ne sono sicuro...ricordo che le prime implementazioni erano della SGI (Silicon Graphics), ma magari dico una cavolata.
Cmq è possibile che Stroustrup l'abbia seguita come scrivi tu...
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: cdimauro - 07 Novembre 2011, 18:38:56
Vero, sembra che sia arrivata molto tardi al comitato (di cui faceva parte Stroustrup) e siano poi stati fatti dei cambiamenti.

Boh. Magari la contentezza di Stroustrup era per il fatto che il C++ aveva una sua libreria standard. :D
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: clros - 07 Novembre 2011, 18:49:17
Citazione da: "cdimauro"
Vero, sembra che sia arrivata molto tardi al comitato (di cui faceva parte Stroustrup) e siano poi stati fatti dei cambiamenti.

Boh. Magari la contentezza di Stroustrup era per il fatto che il C++ aveva una sua libreria standard. :D
:D  :D  :D
OT
Cmq: letto adesso su Wikipedia:

"Le STL (Standard Template Library) sono state progettate e sviluppate presso la Hewlett-Packard da Alexander Stepanov e Meng Lee e sono state incluse nello standard ANSI/ISO nel 1995."

Su Wikipedia inglese invece riporta che l'attuale STL è basata sull'implementazione di SGI: "More specifically, the C++ Standard Library is based on the STL published by SGI."
Mi sembra cmq di capire che l'mplementazione della SGI si basi su quella di HP...
/OT
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: clros - 15 Novembre 2011, 23:17:08
Ok, alla fine, trovando un po' di tempo libero, sono riuscito ad implementare un set con la capacità di restituire gli elementi in base all'ordine di inserimento, basandomi sul listato di esempio fornito da TheKaneB.

Un'altra domanda (OT): com'è possibile implementare un iteratore nella mia classe? (E' una cosa che non ho mai fatto!)
Attualmente uso un metodo per scorrere gli elementi molto simile a quando suggerito da TheKaneB, ma non è il massimo..
Titolo: Re: [C++] Inserimento non ordinato in container STL
Inserito da: TheKaneB - 16 Novembre 2011, 09:10:57
un Iteratore non è altro che un tipo, in cui bisogna implementare l'operatore di dereferenziazione *, e gli operatori di incremento e decremento ++ e -- (non per forza entrambi, se la tua struttura dati è problematica puoi differenziare in forward iterator e reverse iterator). Il contenitore, poi, restituirà due iteratori, rispettivamente begin() e end(), che serviranno per puntare alla testa e alla coda della tua struttura dati. Per essere precisi dovrà restituire 4 iteratori, begin() normale, begin() const, end() normale, end() const. Le varianti "const" ritorneranno un const_iterator, che è uguale all'iteratore normale eccetto che i suoi metodi sono tutti const, cioè danno errore di compilazione se tenti di usarli per modficare il contenuto del container. Tra un po' spiegherò il perchè.

Se l'iteratore è una classe, puoi usare l'operator overloading per implementare queste funzioni. Ma può capitare che l'iteratore sia un tipo di base.

Ad esempio in un Vector<T> (che sostanzialmente incapsula un array ad allocazione dinamica), l'iteratore può essere semplicemente un puntatore come T*.

Infatti internamente troviamo l'array vero e proprio T* m_Data e gli operatori * ++ e -- sono già forniti dal linguaggio (aritmetica dei puntatori).

Nel caso di una lista concatenata, invece, l'operatore * può essere implementato con un banale return this->m_element->value mentre il ++ potrebbe essere un banale this->m_element = this->m_element->next; return this->m_element;.

Nella tua struttura, in particolare, potresti replicare esattamente l'implementazione dell'iteratore delle liste concatenate, perchè l'oggetto da scorrere è la lista chiamata History.

Questo articolo spiega come fare un iteratore usando i template della libreria Boost http://accu.org/index.php/journals/1527 (http://accu.org/index.php/journals/1527)
Qui ulteriori esempi:
http://msdn.microsoft.com/en-us/magazine/cc301955.aspx (http://msdn.microsoft.com/en-us/magazine/cc301955.aspx)
http://accu.org/index.php/journals/389 (http://accu.org/index.php/journals/389) <-- questo qui sembra un buon articolo, abbastanza dettagliato

Un'altra cosa che ti consiglio di approfondire, è l'uso massiccio dei metodi const quando possibile. Questo ti da 2 vantaggi: permette al compilatore di ottimizzare meglio (compare @dsar ti spiegherà il perchè meglio di quanto saprei fare io :lol: ), e ti consente di individuare a compile time eventuali bug subdoli. Ad esempio se tenti di modificare l'istanza di un oggetto ottenuto tramite getter, faresti meglio ad esplicitare la richiesta usando un setter. Impostando tutti i getter a const (e soprattutto che ritornino dei const reference ad oggetti invece di oggetti by-value, a meno che non siano tipi primitivi), scarichi sul compilatore l'onere di scovare sovrascritture involontarie sugli oggetti ottenuti.

Questo crea qualche grattacapo all'inizio, ma su grandi progetti aiuta enormemente a scrivere codice robusto e il più possibile facile da debuggare.