Come faccio a scegliere una struttura di dati del dizionario funzionale?


10

Ho letto un po 'delle seguenti strutture di dati:

  • Bagwell's Ideal Hash Tries
  • Tabelle hash dinamiche di Larson
  • Alberi rosso-neri
  • Alberi Patricia

... e sono sicuro che ce ne sono molti altri là fuori. Ho visto ben poco in termini di ciò che ognuno è più adatto per, o perché sceglierei uno sopra l'altro. Quindi, ecco alcune domande in questo senso:

  1. Quali strutture di dati del dizionario funzionale sono importanti da conoscere?
  2. Quali sono i pro e i contro di questi approcci?
  3. Quando ha senso utilizzare una struttura di dati più imperativa?

I numeri 2 e 3 sono però i più importanti. :-)


Correlati: Cosa c'è di nuovo nelle strutture di dati puramente funzionali da Okasaki? (Questa domanda non è limitata ai dizionari.)
Tsuyoshi Ito,

Questa domanda (oltre alla voce numerata 3) ha la sensazione di una [grande lista].
Kaveh,

2
sarebbe utile sapere se la domanda collegata sopra risponde alle tue preoccupazioni, e se no perché no?
Suresh Venkat,

@Suresh - Che risponde # 1, ma 2 e 3 erano i più importanti. Sono principalmente alla ricerca di una visione d'insieme per poter determinare quali vale la pena studiare in modo più approfondito.
Jason il

2
ok. quindi potrebbe valere la pena modificare la domanda allora.
Suresh Venkat,

Risposte:


16

Non posso davvero rispondere al n. 2 senza perdersi (ci sono troppe dimensioni lungo le quali è possibile confrontare queste strutture), ma per il n. 3 la risposta è piuttosto semplice.

Utilizzare una struttura di dati imperativa se: (a) non esiste assolutamente alcun aliasing o (b) è davvero necessario utilizzare l'aliasing per una trasmissione efficiente.

Se non esiste alcun aliasing della struttura dei dati, non si sta sfruttando il fatto che le strutture di dati funzionali siano persistenti. Quindi non c'è motivo di pagare per i loro costi. Ci sono due avvertenze per questo consiglio. Innanzitutto, potresti preferire la semplicità di implementazione di una struttura di dati funzionale: implementare la cancellazione per un albero rosso-nero funzionale ti farà maledire, ma implementare la cancellazione in un albero rosso-nero imperativo con puntatori genitore ti lascerà a contemplare il suicidio. In secondo luogo, l'assegnazione può essere più costosa di quanto ci si aspetti in un linguaggio gc'd, poiché le scritture possono spostare le strutture di dati fuori dalla generazione giovane. Non abbiamo davvero una buona teoria degli effetti cache e gc, quindi non hai altra scelta che fare benchmark.

In secondo luogo, se hai bisogno di un canale di trasmissione, una struttura di dati condivisa è un modo eccellente per farlo. Con un aggiornamento a tempo costante, puoi dire arbitrariamente a molte altre persone che un valore è cambiato. (Questo è il motivo per cui union-find è una struttura di dati così eccezionale.) Con una configurazione puramente funzionale, o è necessario modificare tutte quelle altre persone o dare loro puntatori astratti in uno stato che si codifica manualmente (che è una specie di ottuso cose da fare).

Se o non vuoi ragionare sull'aliasing e sulla proprietà degli oggetti, o se hai bisogno di più versioni della stessa struttura di dati (hai bisogno sia di una nuova che di una vecchia versione, diciamo), usa semplicemente una struttura di dati funzionale.

Il posto in cui trovo più difficile seguire questi consigli è con gli algoritmi grafici. Ci sono molti algoritmi grafici imperativi davvero eleganti, ma spesso è il caso (diciamo, quando si scrivono compilatori) che si desidera anche la persistenza. Le persone in genere cercano di dividere la differenza e utilizzano l'algoritmo imperativo freddo, ma cercano di bloccare la versione sul lato per ottenere la persistenza. Questo è generalmente piuttosto orribile, pieno di bug e incline a perdere il vantaggio prestazionale dell'algoritmo imperativo.


2
che cos'è l'alias in questo contesto?
Suresh Venkat,

6
L'aliasing è quando hai più riferimenti allo stesso pezzo di dati. Se tali dati sono mutabili, il ragionamento su un programma che li utilizza deve prendere esplicitamente in considerazione tutti gli altri sottoprogrammi che possono accedervi e modificarli. Se quel dato è immutabile, allora puoi ragionare localmente su un programma che lo utilizza, ignorando l'aliasing, poiché sai che nessuno che può accedere ai dati può modificarlo.
Neel Krishnaswami,

"ma implementare la cancellazione in un imperativo albero rosso-nero con i puntatori dei genitori ti lascerà a contemplare il suicidio" Dai un'occhiata agli alberi rosso-neri inclinati a sinistra di Sedgewick. Il caso generale di cancellazione è ridotto a delete-min con un trucco standard, e lo stesso delete-min è molto semplice per gli alberi LLRB. Non sono necessari puntatori principali.
Per Vognsen,

1
"Questo è generalmente piuttosto orribile, pieno di bug e incline a perdere il vantaggio prestazionale dell'algoritmo imperativo." Il documento di Norman Ramsey sull'uso delle cerniere per controllare i diagrammi di flusso in un compilatore ottimizzato fornisce un esempio di compromesso convincente. Hai effettivamente un heap locale per supportare il ricablaggio sul posto facile ed efficiente dei riferimenti tra blocchi di base in un CFG, ma la manipolazione del contenuto dei blocchi di base è funzionale (o semi-funzionale, a seconda della tua visione filosofica delle cerniere).
Per Vognsen,

1

Quali strutture di dati del dizionario funzionale sono importanti da conoscere?

Gli alberi binari bilanciati in altezza e i loro tentativi sono un buon compromesso a tutto tondo. Anche:

  • Alberi Patricia.
  • Hash ci prova.

Quali sono i pro e i contro di questi approcci?

Gli alberi binari bilanciati in altezza e i loro tentativi sono un buon compromesso a tutto tondo per le chiavi atomiche. I tentativi sono gli stessi per i tasti che sono sequenze, ad esempio i tasti stringa.

Gli alberi Patricia possono essere più volte più veloci ma consentono solo chiavi intere.

I tentativi di hash possono essere molte volte più veloci degli alberi binari bilanciati, in particolare se l'hashing è più economico del confronto e il polimorfismo ha un sovraccarico (ad esempio stringhe su .NET) e la scrittura di puntatori nell'heap è veloce (ad esempio VM come JVM e CLR che sono state ottimizzato per le lingue imperative piuttosto che per quelle funzionali). I tentativi di hash consentono anche l'uso interno della mutazione come ottimizzazione.

Gli alberi rosso-neri sono meno importanti perché non hanno alcun vantaggio significativo sugli alberi equilibrati in altezza ma hanno il notevole svantaggio di non consentire l'unione, l'intersezione e la differenza efficienti.

Allo stesso modo, gli alberi delle dita non sono molto migliori nella pratica.

Quando ha senso utilizzare una struttura di dati più imperativa?

Quando il dizionario viene popolato una volta e quindi utilizzato solo per le ricerche, ovvero congelato.

Quando hai bisogno di prestazioni (una tabella hash decente come .NET Dictionaryè in genere 10-40 × più veloce di qualsiasi dizionario generico puramente funzionale).

Quando hai bisogno di un dizionario debole perché non esiste un dizionario debole puramente funzionale.

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.