2010-02-08 5 views
19

Je travaille sur un petit projet de Haskell qui nécessite un tampon circulaire. J'ai réussi à créer une mémoire tampon en utilisant des tableaux qui ont une rotation O (1), mais qui nécessite bien sûr O (N) pour l'insertion/la suppression. J'ai trouvé une implémentation en utilisant des listes qui semblent prendre O (1) pour l'insertion et la suppression, mais comme elle maintient une liste gauche et droite, croiser une certaine frontière quand la rotation prendra le temps O (N). Dans un langage impératif, je pourrais implémenter un tampon circulaire doublement lié avec O (1) insertion, suppression et rotation. Je pense que ce n'est pas possible dans un langage purement fonctionnel comme Haskell, mais j'aimerais savoir si je me trompe.O (1) tampon circulaire en haskell?

+1

Si « traversant une certaine frontière » lors de la rotation prend O (n), quel est le coût quand il ne traverse pas la frontière? Si c'est O (1) et que vous avez seulement une probabilité 1/N de traverser la frontière, la rotation prend O (1) en moyenne. – finnw

+0

A droite, mais en effectuant une opération séquentielle, vous avez la garantie de la traverser à un moment donné, et la complexité temporelle est importante pour chaque rotation, car cela finira probablement par être une application soft-realtime. – Edward

+0

Je n'ai jamais utilisé un tampon circulaire; Est-il facile de donner une brève description de ce que fait votre tampon? Dans votre application devrait-il "écraser" des éléments? – jberryman

Répondre

4

La monade ST permet de décrire et d'exécuter des algorithmes impératifs dans Haskell. Vous pouvez utiliser STRefs pour les pointeurs modifiables de votre liste doublement chaînée.

Les algorithmes autonomes décrits à l'aide de ST sont exécutés à l'aide de runST. Différentes exécutions runST peuvent ne pas partager ST structures de données (STRef, STArray, ..).

Si l'algorithme n'est pas "autonome" et que la structure de données doit être maintenue avec les opérations d'E/S effectuées entre ses utilisations, vous pouvez utiliser stToIO pour y accéder dans la monade IO.

En ce qui concerne si c'est purement fonctionnel ou non - je suppose que ce n'est pas?

10

Si vous pouvez traiter amorti O (1) opérations, vous pouvez probablement utiliser soit Data.Sequence de l'emballage conteneurs ou Data.Dequeue du paquet dequeue. Le premier utilise finger trees, tandis que le second utilise la "Dequeue du banquier" du Purely Functional Data Structures d'Okasaki (une version antérieure en ligne here).

2

Il semble que vous ayez besoin de quelque chose d'un peu plus compliqué que cela (puisque vous avez mentionné des listes à double liaison), mais peut-être que cela vous aidera. Cette fonction agit comme map sur une liste cyclique mutable:

mapOnCycling f = concat . tail . iterate (map f) 

utilisation comme:

*Main> (+1) `mapOnCycling` [3,2,1] 

[4,3,2,5,4,3,6,5,4,7,6,5,8,7,6,9,8,7,10,9...] 

Et voici celui qui agit comme mapAccumL:

mapAccumLOnCycling f acc xs = 
    let (acc', xs') = mapAccumL f acc xs 
    in xs' ++ mapAccumLOnCycling f acc' xs' 

Quoi qu'il en soit, si vous vous souciez d'élaborer encore plus sur ce que votre structure de données doit être capable de "faire", je serais vraiment intéressé à en entendre parler.

EDIT: comme camccann mentionné, vous pouvez utiliser Data.Sequence pour ce qui, selon la documentation devrait vous donner de la complexité de temps O1 (est-il une telle chose comme O1 amorti le temps?) Pour l'affichage ou l'ajout d'éléments à la fois aux côtés gauche et droit de la séquence, ainsi que de modifier les extrémités le long du chemin. Si cela aura la performance dont vous avez besoin, je ne suis pas sûr.

Vous pouvez traiter "l'emplacement actuel" comme l'extrémité gauche de la séquence. Ici nous naviguons d'avant en arrière le long d'une séquence, produisant une liste infinie de valeurs.Désolé si elle ne compile pas, je n'ai pas GHC au moment:

shuttle (viewl-> a <: as) = a : shuttle $ rotate (a+1 <| as) 
    where rotate | even a = rotateForward 
       | otherwise = rotateBack 
      rotateBack (viewr-> as' :> a') = a' <| as' 
      rotateForward (viewl-> a' <: as') = as' |> a' 
+1

Voir le commentaire sur la question d'origine pour une fonctionnalité plus spécifique. – Edward

+0

Mis à jour ma réponse, qui je pense vous donne ce que vous recherchez dans une solution purement fonctionnelle – jberryman

+3

Par ailleurs, amortis O (1) est parfaitement raisonnable - cela signifie simplement que des opérations coûteuses peuvent se produire, mais avec une fréquence inversement proportionnelle à leur coût. Par exemple, une opération peut être O (1) la plupart du temps et O (N) occasionnellement, mais tant que cette dernière n'est pas plus commune qu'une fois sur N opérations, la complexité amortie est toujours O (1). C'est génial pour la plupart des objectifs, mais pas tellement pour soft-realtime que par les commentaires sur la question ici ... –