J'ai lu un peu sur les structures de données suivantes:
- Essais de hachage idéaux de Bagwell
- Tables de hachage dynamiques de Larson
- Arbres rouges-noirs
- Arbres Patricia
... et je suis sûr qu'il y en a beaucoup d'autres. J'ai vu très peu de choses sur ce que chacun est le mieux adapté, ou pourquoi je choisirais l'un plutôt que l'autre. Voici donc quelques questions dans ce sens:
- Quelles structures de données de dictionnaire fonctionnelles sont importantes à connaître?
- Quels sont les avantages et les inconvénients de ces approches?
- Quand est-il judicieux d'utiliser une structure de données plus impérative?
Les numéros 2 et 3 sont cependant les plus importants. :-)
Réponses:
Je ne peux pas vraiment répondre # 2 sans me perdre (il y a trop de dimensions le long desquelles vous pouvez comparer ces structures), mais pour # 3 la réponse est assez simple.
Utilisez une structure de données impérative si: (a) il n'y a absolument aucun alias, ou (b) vous avez vraiment besoin d'utiliser l'alias pour une diffusion efficace.
S'il n'y a aucun alias de votre structure de données, vous ne profitez pas du fait que les structures de données fonctionnelles sont persistantes. Il n'y a donc aucune raison de payer leur coût. Il y a deux mises en garde à ce conseil. Tout d'abord, vous préférerez peut-être la simplicité de mise en œuvre d'une structure de données fonctionnelle: la mise en œuvre de la suppression pour un arbre rouge-noir fonctionnel vous fera malédire, mais la mise en œuvre de la suppression dans un arbre rouge-noir impératif avec des pointeurs parent vous laissera envisager le suicide. Deuxièmement, l'affectation peut être plus coûteuse que ce à quoi vous vous attendez dans un langage gc'd, car les écritures peuvent faire sortir les structures de données de la jeune génération. Nous n'avons vraiment pas une bonne théorie des effets de cache et de gc, vous n'avez donc pas d'autre choix que de faire un benchmarking.
Deuxièmement, si vous avez besoin d'un canal de diffusion, une structure de données partagée est un excellent moyen de le faire. Avec une mise à jour à temps constant, vous pouvez indiquer arbitrairement à de nombreuses autres personnes qu'une valeur a changé. (C'est pourquoi union-find est une excellente structure de données.) Avec une configuration purement fonctionnelle, vous devez soit modifier toutes ces autres personnes, soit leur donner des pointeurs abstraits dans un état que vous codez manuellement (ce qui est une sorte d'obus chose à faire).
Si vous ne voulez pas raisonner sur l'alias et la propriété des objets, ou si vous avez besoin de plusieurs versions de la même structure de données (vous avez besoin d'une nouvelle et d'une ancienne version, par exemple), alors utilisez simplement une structure de données fonctionnelle.
L'endroit où je trouve le plus difficile à suivre ces conseils est avec les algorithmes de graphes. Il existe de nombreux algorithmes de graphes impératifs vraiment élégants, mais il arrive souvent (par exemple, lors de l'écriture de compilateurs) que vous souhaitiez également la persistance. Les gens essaient généralement de diviser la différence et utilisent l'algorithme impératif cool mais essaient de boulonner le versioning sur le côté pour obtenir la persistance. C'est généralement assez horrible, plein de bugs, et enclin à perdre l'avantage de performance de l'algorithme impératif.
la source
Les arbres binaires équilibrés en hauteur et leurs essais sont un bon compromis tous azimuts. Aussi:
Les arbres binaires à hauteur équilibrée et leurs essais sont un bon compromis global pour les clés atomiques. Les essais sont les mêmes pour les clés qui sont des séquences, par exemple les clés de chaîne.
Les arbres Patricia peuvent être plusieurs fois plus rapides mais ne permettent que des clés entières.
Les tentatives de hachage peuvent être plusieurs fois plus rapides que les arbres binaires équilibrés, en particulier si le hachage est moins cher que la comparaison et que le polymorphisme a un surcoût (par exemple des chaînes sur .NET) et que l'écriture de pointeurs dans le tas est rapide (par exemple, les machines virtuelles comme la JVM et le CLR qui ont été optimisé pour les langages impératifs plutôt que les langages fonctionnels). Les tentatives de hachage permettent également l'utilisation interne de la mutation comme optimisation.
Les arbres rouge-noir sont moins importants car ils ne présentent pas d'avantages significatifs par rapport aux arbres à hauteur équilibrée mais présentent l'inconvénient majeur de ne pas permettre une union, une intersection et une différence efficaces.
De même, les arbres à doigts ne sont pas beaucoup mieux en pratique.
Lorsque votre dictionnaire est rempli une fois, puis utilisé uniquement pour les recherches, c'est-à-dire gelé.
Lorsque vous avez besoin de performances (une table de hachage décente comme le .NET
Dictionary
est généralement 10 à 40 × plus rapide que tout dictionnaire générique purement fonctionnel).Lorsque vous avez besoin d'un dictionnaire faible car il n'existe pas de dictionnaire faible purement fonctionnel connu.
la source