tas efficaces dans les langues purement fonctionnels
-
06-09-2019 - |
Question
Comme un exercice de Haskell, je suis en train de mettre en œuvre heapsort. Le tas est généralement mis en œuvre comme un tableau dans les langues impératives, mais ce serait extrêmement inefficace dans les langues purement fonctionnelles. J'ai donc regardé des tas binaires, mais tout ce que je trouve jusqu'à présent les décrit d'un point de vue impératif et les algorithmes présentés sont difficiles à traduire dans un cadre fonctionnel. Comment mettre en œuvre efficacement un tas dans un langage purement fonctionnel tel que Haskell?
Edit: Par efficace, je veux dire qu'il devrait encore être en O (n log n *), mais il ne doit pas battre un programme C. De plus, je voudrais utiliser la programmation purement fonctionnelle. Que d'autre serait le point de le faire dans Haskell?
La solution
Il y a un certain nombre d'implémentations de tas Haskell dans une annexe à Okasaki purement Structures de données fonctionnelles . (Le code source peut être téléchargé sur le lien. Le livre lui-même est digne d'être lue.) Aucun d'entre eux sont des tas binaires, en soi, mais le " gauchiste » est très similaire. Il a l'insertion O (log n), la suppression et la fusion des opérations. Il existe également des structures de données plus complexes comme biais des tas , tas binomial , et tas ébrasés qui ont de meilleures performances.
Autres conseils
Jon Fairbairn a affiché une heapsort fonctionnelle à la liste de diffusion Café Haskell en 1997:
http://www.mail-archive.com/haskell@ haskell.org/msg01788.html
Je reproduis ci-dessous, reformaté pour adapter cet espace. Je suis aussi un peu simplifié le code de merge_heap.
Je suis surpris treefold est pas le prélude norme car il est si utile. Traduit de la version que j'ai écrit en Ponder en Octobre 1992 - Jon Fairbairn
module Treefold where
-- treefold (*) z [a,b,c,d,e,f] = (((a*b)*(c*d))*(e*f))
treefold f zero [] = zero
treefold f zero [x] = x
treefold f zero (a:b:l) = treefold f zero (f a b : pairfold l)
where
pairfold (x:y:rest) = f x y : pairfold rest
pairfold l = l -- here l will have fewer than 2 elements
module Heapsort where
import Treefold
data Heap a = Nil | Node a [Heap a]
heapify x = Node x []
heapsort :: Ord a => [a] -> [a]
heapsort = flatten_heap . merge_heaps . map heapify
where
merge_heaps :: Ord a => [Heap a] -> Heap a
merge_heaps = treefold merge_heap Nil
flatten_heap Nil = []
flatten_heap (Node x heaps) = x:flatten_heap (merge_heaps heaps)
merge_heap heap Nil = heap
merge_heap node_a@(Node a heaps_a) node_b@(Node b heaps_b)
| a < b = Node a (node_b: heaps_a)
| otherwise = Node b (node_a: heaps_b)
Vous pouvez également utiliser la monade ST
, qui vous permet d'écrire du code impératif, mais exposer une interface purement fonctionnelle en toute sécurité.
Comme un exercice de Haskell, je mis en œuvre un heapsort impératif avec le ST Monad.
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad (forM, forM_)
import Control.Monad.ST (ST, runST)
import Data.Array.MArray (newListArray, readArray, writeArray)
import Data.Array.ST (STArray)
import Data.STRef (newSTRef, readSTRef, writeSTRef)
heapSort :: forall a. Ord a => [a] -> [a]
heapSort list = runST $ do
let n = length list
heap <- newListArray (1, n) list :: ST s (STArray s Int a)
heapSizeRef <- newSTRef n
let
heapifyDown pos = do
val <- readArray heap pos
heapSize <- readSTRef heapSizeRef
let children = filter (<= heapSize) [pos*2, pos*2+1]
childrenVals <- forM children $ \i -> do
childVal <- readArray heap i
return (childVal, i)
let (minChildVal, minChildIdx) = minimum childrenVals
if null children || val < minChildVal
then return ()
else do
writeArray heap pos minChildVal
writeArray heap minChildIdx val
heapifyDown minChildIdx
lastParent = n `div` 2
forM_ [lastParent,lastParent-1..1] heapifyDown
forM [n,n-1..1] $ \i -> do
top <- readArray heap 1
val <- readArray heap i
writeArray heap 1 val
writeSTRef heapSizeRef (i-1)
heapifyDown 1
return top
BTW je conteste que si ce n'est pas alors purement fonctionnel, il est inutile de le faire dans Haskell. Je pense que ma mise en œuvre du jouet est beaucoup plus agréable que ce que l'on atteindraient en C ++ avec des modèles, en passant autour de choses aux fonctions internes.
Et voici un Fibonacci Heap dans Haskell:
Voici le fichier pdf pour d'autres tas de k-aire sur la base du travail de Okasaki.
https://github.com/downloads/liuxinyu95/AlgoXY/kheap- en.pdf
Tout comme dans les algorithmes QuickSort efficaces écrits en Haskell, vous devez utiliser monades (transformateurs de l'État) pour faire des choses en place.
Les tableaux dans Haskell ne sont pas aussi extrêmement inefficace que vous pourriez penser, mais la pratique typique dans Haskell serait probablement de mettre en œuvre ce en utilisant les types de données ordinaires, comme ceci:
data Heap a = Empty | Heap a (Heap a) (Heap a)
fromList :: Ord a => [a] -> Heap a
toSortedList :: Ord a => Heap a -> [a]
heapSort = toSortedList . fromList
Si je résoudre ce problème, je pourrais commencer par bourrer les éléments de la liste dans un tableau, ce qui rend plus facile de les indexer pour la création de tas.
import Data.Array
fromList xs = heapify 0 where
size = length xs
elems = listArray (0, size - 1) xs :: Array Int a
heapify n = ...
Si vous utilisez un tas max binaire, vous pouvez garder une trace de la taille du tas que vous supprimez des éléments afin que vous puissiez trouver l'élément en bas à droite dans O (log N) temps. Vous pouvez également jeter un oeil à d'autres types de tas qui ne sont généralement pas mises en œuvre en utilisant des tableaux, comme des tas et des tas binomial fibonacci.
Une note finale sur les performances du tableau: dans Haskell il y a un compromis entre l'utilisation de tableaux statiques et en utilisant des tableaux mutables. Avec les tableaux statiques, vous devez créer de nouvelles copies des tableaux lorsque vous modifiez les éléments. Avec des tableaux mutables, le collecteur des ordures a du mal à garder les différentes générations d'objets séparés. Essayez mise en œuvre du heapsort en utilisant un starray et apprenez comment vous le souhaitez.
J'ai essayé de tas binaire de port standard dans les paramètres fonctionnels. Il y a un article décrit idée: une approche fonctionnelle standard binaire Heaps. Toutes les annonces de code source dans l'article sont en Scala. Mais il peut être porté très facile dans une autre langue fonctionnelle.
Voici une page contenant une version ML de HeapSort. Il est assez détaillé et devrait fournir un bon point de départ.
http://flint.cs.yale.edu /cs428/coq/doc/Reference-Manual021.html