Haskell In-Place Quicksort, ORD A => [A] -> io [a] -- algorithm campo con haskell campo con comparative-review campo con quick-sort camp codereview Relacionados El problema

Haskell in-place quicksort, Ord a => [a] -> IO [a]


5
vote

problema

Español

Como se señaló a continuación, esto es más o menos una traducción directa de http://rosettacode.org / wiki / classing_algorithms / quicksort # c . Estoy bastante satisfecho con su legibilidad en este momento en comparación con el código C; Pero aún así preguntándome cómo hacerlo más sucinto y claro. ¿Alguna idea / opinión?

  {- In-place quicksort algorithm. Adapted from http://rosettacode.org/wiki/Sorting_algorithms/Quicksort#C.  Main considerations:    - Type should be as close to [a] -> [a] as possible.    - Comparatively most of the time should be spent in comparing and     swapping members, and little in copying the list onto and out of the     function's stack frame. So don't worry about forcing user to deal     with mutable arrays/vectors; instead accept and return immutable     lists, and mutate internally.    - Since in-place, need to account for effect of changing members.     Therefore return IO [a].    - Work in IO monad rather than State/ST/others because want to be as     close to the bottom of the monad stack as possible--we will end up     at IO anyway ultimately.    - Internally use as an Array of IORefs, but we can avoid exposing that     to the user by sequencing all the effects into an IO [a].    - Internal functions all 'close over' a single shared Array Int (IORef     a). Is this a bad practice? My intuition is no, because we don't     expose that to the user.    - Haskell's ability to have let statements and monadic bindings spread     anywhere inside a 'do' block is really awesome. -}  import Control.Monad (forM_, mapM, when) import Data.Array ((!), Array, bounds, elems, listArray) import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef)  qsortInPlace :: Ord a => [a] -> IO [a] qsortInPlace xs = do   xs' <- mapM newIORef xs    let     upperBound = length xs - 1     ys = listArray (0, upperBound) xs'      swap i1 i2 = do       let         r1 = ys ! i1         r2 = ys ! i2       t <- readIORef r1       readIORef r2 >>= writeIORef r1       writeIORef r2 t      go lo hi       | hi <= lo = return ()       | otherwise = do         let pivotIndex = (lo + hi) `div` 2          pivot <- readIORef $ ys ! pivotIndex         chg <- newIORef lo         swap pivotIndex hi          forM_ [lo..(hi - 1)] $ i -> do           ys_i <- readIORef $ ys ! i           when (ys_i < pivot) $ do             chg' <- readIORef chg             swap i chg'             modifyIORef' chg (+1)          chg' <- readIORef chg         swap chg' hi          go lo $ chg' - 1         go (chg' + 1) hi    go 0 upperBound   mapM readIORef . elems $ ys  main :: IO () main = do   xs <- qsortInPlace [4, 5, 2, 3, 1, 2, 7, 1, 10]   print xs   

Intento 2

Tomando el consejo de Feuerbach y otros en el canal de Haskell IRC ( http: // tunes.org/~nef/logs/haskell/15.01.02 , 16:03:52 en adelante), he reimplido usando vectores mutables y el St Mónad. El código es algo simplificado y también me sorprende cómo funcionan los tipos de iAnef y Stref.

  {- In-place quicksort algorithm. Adapted from http://rosettacode.org/wiki/Sorting_algorithms/Quicksort#C.  Main considerations:    - Type should be as close to [a] -> [a] as possible.    - Comparatively most of the time should be spent in comparing and     swapping members, and little in copying the list onto and out of the     function's stack frame. So don't worry about forcing user to deal     with mutable arrays/vectors; instead accept and return immutable     lists, and mutate internally.    - Since in-place, need to account for effect of changing members.     Therefore internally use ST monad. The nice thing about ST is that     we can run it and return [a]. So we can actually have type [a] ->     [a]. The user never needs to know we did stateful computations.    - Internally use a mutable vector, which out of the box provides the     ability to swap its elements.    - Internal 'go' function 'closes over' a single shared mutable vector     (MVector) so we don't have to keep passing it back and forth between     function stack frames.    - Haskell's ability to have let statements and monadic bindings spread     anywhere inside a 'do' block is really awesome. Not to mention its     ability to encapsulate a complex series of monadic actions inside a     single 'variable'. -}  import Control.Monad (forM_, mapM, when) import Control.Monad.ST (runST) import Data.STRef (modifySTRef', newSTRef, readSTRef ) import qualified Data.Vector as V import qualified Data.Vector.Mutable as VM  qsortInPlace :: Ord a => [a] -> [a] qsortInPlace xs =   let     vAction = do       v <- V.thaw . V.fromList $ xs        let         go lo hi           | hi <= lo = return ()           | otherwise = do             let pivotIndex = (lo + hi) `div` 2              pivot <- VM.read v pivotIndex             chg <- newSTRef lo             VM.swap v pivotIndex hi              forM_ [lo..(hi - 1)] $ i -> do               v_i <- VM.read v i               when (v_i < pivot) $ do                 chg' <- readSTRef chg                 VM.swap v i chg'                 modifySTRef' chg (+1)              chg' <- readSTRef chg             VM.swap v chg' hi              go lo $ chg' - 1             go (chg' + 1) hi        go 0 $ length xs - 1       V.freeze v >>= return . V.toList    in runST vAction  main :: IO () main = print $ qsortInPlace [4, 5, 2, 3, 1, 2, 7, 1, 10]   
Original en ingles

As noted below, this is more or less a direct translation from http://rosettacode.org/wiki/Sorting_algorithms/Quicksort#C. I'm fairly satisfied with its readability right now compared to the C code; but still wondering how to make it more succinct and clear. Any ideas/opinions?

{- In-place quicksort algorithm. Adapted from http://rosettacode.org/wiki/Sorting_algorithms/Quicksort#C.  Main considerations:    - Type should be as close to [a] -> [a] as possible.    - Comparatively most of the time should be spent in comparing and     swapping members, and little in copying the list onto and out of the     function's stack frame. So don't worry about forcing user to deal     with mutable arrays/vectors; instead accept and return immutable     lists, and mutate internally.    - Since in-place, need to account for effect of changing members.     Therefore return IO [a].    - Work in IO monad rather than State/ST/others because want to be as     close to the bottom of the monad stack as possible--we will end up     at IO anyway ultimately.    - Internally use as an Array of IORefs, but we can avoid exposing that     to the user by sequencing all the effects into an IO [a].    - Internal functions all 'close over' a single shared Array Int (IORef     a). Is this a bad practice? My intuition is no, because we don't     expose that to the user.    - Haskell's ability to have let statements and monadic bindings spread     anywhere inside a 'do' block is really awesome. -}  import Control.Monad (forM_, mapM, when) import Data.Array ((!), Array, bounds, elems, listArray) import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef)  qsortInPlace :: Ord a => [a] -> IO [a] qsortInPlace xs = do   xs' <- mapM newIORef xs    let     upperBound = length xs - 1     ys = listArray (0, upperBound) xs'      swap i1 i2 = do       let         r1 = ys ! i1         r2 = ys ! i2       t <- readIORef r1       readIORef r2 >>= writeIORef r1       writeIORef r2 t      go lo hi       | hi <= lo = return ()       | otherwise = do         let pivotIndex = (lo + hi) `div` 2          pivot <- readIORef $ ys ! pivotIndex         chg <- newIORef lo         swap pivotIndex hi          forM_ [lo..(hi - 1)] $ \i -> do           ys_i <- readIORef $ ys ! i           when (ys_i < pivot) $ do             chg' <- readIORef chg             swap i chg'             modifyIORef' chg (+1)          chg' <- readIORef chg         swap chg' hi          go lo $ chg' - 1         go (chg' + 1) hi    go 0 upperBound   mapM readIORef . elems $ ys  main :: IO () main = do   xs <- qsortInPlace [4, 5, 2, 3, 1, 2, 7, 1, 10]   print xs 

Attempt 2

Taking the advice of Feuerbach and others on the Haskell IRC channel (http://tunes.org/~nef/logs/haskell/15.01.02, 16:03:52 onwards), I've reimplemented using mutable vectors and the ST monad. The code is somewhat simplified and I'm also struck by how similarly the IORef and STRef types work.

{- In-place quicksort algorithm. Adapted from http://rosettacode.org/wiki/Sorting_algorithms/Quicksort#C.  Main considerations:    - Type should be as close to [a] -> [a] as possible.    - Comparatively most of the time should be spent in comparing and     swapping members, and little in copying the list onto and out of the     function's stack frame. So don't worry about forcing user to deal     with mutable arrays/vectors; instead accept and return immutable     lists, and mutate internally.    - Since in-place, need to account for effect of changing members.     Therefore internally use ST monad. The nice thing about ST is that     we can run it and return [a]. So we can actually have type [a] ->     [a]. The user never needs to know we did stateful computations.    - Internally use a mutable vector, which out of the box provides the     ability to swap its elements.    - Internal 'go' function 'closes over' a single shared mutable vector     (MVector) so we don't have to keep passing it back and forth between     function stack frames.    - Haskell's ability to have let statements and monadic bindings spread     anywhere inside a 'do' block is really awesome. Not to mention its     ability to encapsulate a complex series of monadic actions inside a     single 'variable'. -}  import Control.Monad (forM_, mapM, when) import Control.Monad.ST (runST) import Data.STRef (modifySTRef', newSTRef, readSTRef ) import qualified Data.Vector as V import qualified Data.Vector.Mutable as VM  qsortInPlace :: Ord a => [a] -> [a] qsortInPlace xs =   let     vAction = do       v <- V.thaw . V.fromList $ xs        let         go lo hi           | hi <= lo = return ()           | otherwise = do             let pivotIndex = (lo + hi) `div` 2              pivot <- VM.read v pivotIndex             chg <- newSTRef lo             VM.swap v pivotIndex hi              forM_ [lo..(hi - 1)] $ \i -> do               v_i <- VM.read v i               when (v_i < pivot) $ do                 chg' <- readSTRef chg                 VM.swap v i chg'                 modifySTRef' chg (+1)              chg' <- readSTRef chg             VM.swap v chg' hi              go lo $ chg' - 1             go (chg' + 1) hi        go 0 $ length xs - 1       V.freeze v >>= return . V.toList    in runST vAction  main :: IO () main = print $ qsortInPlace [4, 5, 2, 3, 1, 2, 7, 1, 10] 
           

Lista de respuestas

1
 
vote

Código agradable y bien documentado. Algunas ideas:

En este caso, podría usarla unsafeThaw , a medida que creas un vector solo para descongelarlo. Esto le ahorrará copiarlo durante thaw , pero, por supuesto, debe tener cuidado y consciente de las consecuencias. Si usa matrices, otra opción es newListArray .

Al convertir de nuevo a una lista, para arreglos para las matrices, podría usarlo runSTArray3 que nuevamente ahorra copia de la matriz completa (a diferencia de el uso de freeze ). O en lugar de freeze seguido de toList , use getElems . Para vectores, hay unsafeFreeze también.

Mientras se usa un contador STRef para la parte media, sin duda funciona, es un poco de estilo imperativo para Haskell. El enfoque más común sería tener una función recursiva donde la pase como un argumento y devuélvalo.

Un poco relacionado está en bucle usando thaw0 sobre una lista. Probablemente, pasarlo como un argumento sería más rápido, ya que producir / consumir la lista es probable que cause la asignación de memoria (DE) dentro del bucle, aunque menos idiomático. Así que depende de cuáles son tus metas.

Finalmente, declarando funciones usando thaw1 INTERIOR thaw2 es de hecho conveniente, pero puede afectar la legibilidad, si son largos. Preferiría declararlos por separado para que la función principal sea simplemente "descongelarse" y luego la definición de thaw3 sigue (o precede). Si las funciones del ayudante no son recursivas (es decir, la recursión está oculta en el interior), estarán correctamente instalados según lo necesario por el compilador.

también thaw4 es equivalente a thaw5 o simplemente thaw6 .

Aquí hay una posible variante con las ideas anteriores, y algunos menores más:

  thaw7  

También sería bueno agregar una prueba QuickCheck .

 

Nice and well documented code. Some ideas:

In this case you could use unsafeThaw, as you create a vector only to thaw it. This will save you copying it during thaw, but of course you must be careful and aware of the consequences. If using arrays, another option is newListArray.

When converting back to a list, for arrays you could either use runSTArray which again saves copying the entire array (as opposed to using freeze). Or instead of freeze followed by toList, use getElems. For vectors, there is unsafeFreeze too.

While using a STRef counter for the middle part certainly works, it's a bit imperative style for Haskell. The more common approach would be to have a recursive function where you pass it as an argument and return it back.

Somewhat related is looping using forM_ over a list. Probably passing it as an argument would be faster, as producing/consuming the list is likely to cause memory (de)allocation inside the loop, although less idiomatic. So it depends on what are your goals.

Finally, declaring functions using let inside do is indeed often convenient, but can impact readability, if they're long. I'd prefer to declare them separately so that the main function is just "thaw - go - freeze" and then the definition of go follows (or precedes). If the helper functions aren't recursive (that is, the recursion is hidden inside), they'll be properly inlined as needed by the compiler.

Also x >>= return . f is equivalent to liftM f x or just f <$> x.

Here is a possible variant with the ideas above, and a few minor more:

{-# LANGUAGE BangPatterns #-} import Control.Monad (forM_, mapM, when) import Control.Monad.ST (runST) import Data.Functor ((<$>)) import Data.STRef (modifySTRef', newSTRef, readSTRef ) import qualified Data.Vector as V import qualified Data.Vector.Mutable as VM  qsortInPlace :: Ord a => [a] -> [a] qsortInPlace xs = runST $ do       v <- V.unsafeThaw . V.fromList $ xs       pass v 0 (length xs - 1)       V.toList <$> V.unsafeFreeze v   where     -- Hiding the recursion into the inner 'go' function is not just     -- convenient, it allows 'split' to be non-recursive, which allows     -- its inlining.     split v pivot lo hi = go lo lo       where         -- Bang patterns should help the GHC optimizer here.         go !chg !i | i >= hi = return chg                    | otherwise = do           v_i <- VM.read v i           if (v_i < pivot) then do               VM.swap v i chg               go (chg + 1) (i + 1)             else               go chg (i + 1)      pass v lo hi       | hi <= lo = return ()       | otherwise = do         let pivotIndex = (lo + hi) `div` 2          pivot <- VM.read v pivotIndex         VM.swap v pivotIndex hi          chg <- split v pivot lo hi         VM.swap v chg hi          pass v lo (chg - 1)         pass v (chg + 1) hi  main :: IO () main = print $ qsortInPlace [4, 5, 2, 3, 1, 2, 7, 1, 10] 

Also a good thing would be to add a QuickCheck test.

 
 

Relacionados problema

3  Implementación rápida en Python  ( Quicksort implementation in python ) 
He escrito una implementación de Quicksort en Python. Soy nuevo en Python. ¿Alguna sugerencia de mejora o crítica en mi uso de Python? def partition(a, lo,...

1  ¡Quicksort en Ruby!  ( Quicksort in ruby ) 
Me he estado enseñando a mí mismo ruby ​​este fin de semana. En primer lugar, soy consciente de que hay una función de tipo incorporada. He escrito este códig...

3  Tuercas y algoritmo de los pernos  ( Nuts and bolts algorithm ) 
Estoy tratando de aprender Golang. Este es un problema de los algoritmos de Robert Sededwick libro: tiene una pila mixta de n tuercas y n pernos y necesi...

9  Implementación rápida de Quicksort  ( Fast quicksort implementation ) 
Solo para fines de aprendizaje, escribí una implementación del algoritmo de Quicksort. Hice algunas modificaciones al algoritmo para que sea más rápido, que...

5  Código de Lisp Quicksort  ( Lisp quicksort code ) 
Realmente lo apreciaría si alguien pudiera revisar mi implementación de Quicksort. Además, generé mi lista dinámicamente y escribí un par de pruebas. Por supu...

0  ¿Por qué mi implementación de Bubblesort es más rápida que mi código de Quicksort? [cerrado]  ( Why is my bubblesort implementation faster than my quicksort code ) 
cerrado. Esta pregunta es off-topic . Actualmente no está aceptando respuestas. ¿Quieres ...

1  Revisa mi programa F # Quicksort  ( Review my f quicksort program ) 
OK ... Última clase del día y probablemente la más divertida. F # es realmente increíble. let rec quickSort list = match list with | [] -> [] ...

26  Implementación de Java de tipo rápido  ( Java implementation of quick sort ) 
Esta es mi implementación de Quicksort (algoritmo tomado de Libro Cormen). Esta es una implementación en su lugar. Por favor, háganos saber temas con esta o c...

10  Quicksort  ( Templated quicksort ) 
original quicksort.h #include <algorithm> namespace quicksort { template <typename iterator, typename value_type> struct traits { static iterato...

3  C ++ 11 Quicksort cualquier contenedor  ( C11 quicksort any container ) 
Como dice el título, Simple Quicksort para ayudarme a acostumbrarme a las plantillas e iteradoras de C ++. La principal preocupación es si hay una mejor maner...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos