mucchi efficienti in lingue puramente funzionali
-
06-09-2019 - |
Domanda
Come un esercizio di Haskell, sto cercando di implementare Heapsort. Il mucchio di solito è implementato come un array in linguaggi imperativi, ma questo sarebbe estremamente inefficiente in linguaggi puramente funzionali. Così ho guardato cumuli binari, ma tutto quello che ho trovato finora li descrive dal punto di vista fondamentale e gli algoritmi presentati sono difficili da tradurre in un ambiente funzionale. Come implementare in modo efficiente un mucchio in un linguaggio puramente funzionale come Haskell?
Modifica: Con efficiente voglio dire che dovrebbe essere ancora in O (n * log n), ma non ha bisogno di battere un programma C. Inoltre, mi piacerebbe usare programmazione puramente funzionale. Che altro sarebbe il punto di farlo in Haskell?
Soluzione
Ci sono una serie di implementazioni Haskell heap in appendice al Okasaki di puramente funzionale Strutture dati . (Il codice sorgente può essere scaricato al link. Il libro stesso è ben la pena di leggere.) Nessuno di loro sono cumuli binari, di per sé, ma il " di sinistra" mucchio è molto simile. Ha O (log n) di inserimento, cancellazione, e operazioni di unione. Ci sono anche strutture dati più complesse, come skew cumuli , cumuli binomio , e cumuli strombatura che avere prestazioni migliori.
Altri suggerimenti
Jon Fairbairn ha registrato un heapsort funzionale alla mailing list Haskell Cafe nel 1997:
http://www.mail-archive.com/haskell@ haskell.org/msg01788.html
riproduco qui sotto, riformattato per adattarsi a questo spazio. Ho anche un po 'semplificato il codice di merge_heap.
Sono treefold sorpreso non è nel preludio di serie dal momento che è così utile. Tradotto dalla versione che ho scritto in Ponder nell'ottobre 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)
Si potrebbe anche usare la monade ST
, che permette di scrivere codice imperativo, ma esporre un'interfaccia puramente funzionale in modo sicuro.
Come un esercizio di Haskell, ho implementato un heapsort imperativo con la ST Monade.
{-# 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 ho concorso che se non è puramente funzionale, allora non v'è alcun punto nel farlo in Haskell. Credo che la mia implementazione giocattolo è molto più bello di quello che ci si ottenere in C ++ con i modelli, passando intorno roba per le funzioni interne.
E qui è un mucchio di Fibonacci in Haskell:
Ecco il file pdf per alcuni altri mucchi k-ary basati sul lavoro di Okasaki.
https://github.com/downloads/liuxinyu95/AlgoXY/kheap- en.pdf
Proprio come in algoritmi efficienti quicksort scritto in Haskell, è necessario utilizzare monadi (trasformatori di stato) per fare cose sul posto.
Array a Haskell non sono così enormemente inefficiente come si potrebbe pensare, ma la pratica tipica in Haskell sarebbe probabilmente per implementare questo utilizzando i tipi di dati comuni, in questo modo:
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
Se io fossi la soluzione di questo problema, potrei iniziare da ripieno gli elementi della lista in un array, rendendo più facile per indicizzarli per la creazione di heap.
import Data.Array
fromList xs = heapify 0 where
size = length xs
elems = listArray (0, size - 1) xs :: Array Int a
heapify n = ...
Se stai usando un max heap binario, si potrebbe desiderare di tenere traccia della dimensione del mucchio, come si rimuovono gli elementi in modo da poter trovare l'elemento in basso a destra in O (log N) tempo. Si potrebbe anche dare un'occhiata in altri tipi di cumuli che non sono tipicamente implementate utilizzando gli array, come mucchi binomiali e cumuli di Fibonacci.
Una nota finale sulle prestazioni dell'array: in Haskell c'è un compromesso tra l'utilizzo di matrici statiche e usando array mutevoli. Con gli array statici, è necessario creare nuove copie di array quando si modificano gli elementi. Con gli array mutevoli, il garbage collector ha difficoltà a tenere diverse generazioni di oggetti separati. Provate l'attuazione del heapsort utilizzando uno STArray e vedere come ti piace.
Ho cercato di porto heap binario di serie nelle impostazioni funzionali. C'è un articolo con l'idea descritta: un approccio funzionale alla standard binario Heaps . Tutti gli annunci di codice sorgente in questo articolo sono a Scala. Ma potrebbe essere portato molto facile in qualsiasi altra lingua funzionale.
Questa è una pagina contenente una versione ML di Heapsort. E 'abbastanza dettagliato e dovrebbe fornire un buon punto di partenza.
http://flint.cs.yale.edu /cs428/coq/doc/Reference-Manual021.html