1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
|
{-# LANGUAGE TypeOperators #-}
-- | An implementation of a zipper-like non-empty list structure that tracks
-- an index position in the list (the 'focus').
module Data.List.PointedList where
import Prelude hiding (foldl, foldr, elem)
import Control.Applicative
import Control.Monad
import Data.Binary
import Data.Foldable hiding (find)
import Data.List hiding (length, foldl, foldr, find, elem)
import qualified Data.List as List
import Data.Traversable
-- | The implementation of the pointed list structure which tracks the current
-- position in the list structure.
data PointedList a = PointedList
{ _reversedPrefix :: [a]
, _focus :: a
, _suffix :: [a]
} deriving (Eq)
instance Binary a => Binary (PointedList a) where
put (PointedList x1 x2 x3) = do put x1; put x2; put x3
get = do liftM3 PointedList get get get
-- | Lens compatible with Control.Lens.
reversedPrefix :: Functor f => ([a] -> f [a]) -> PointedList a -> f (PointedList a)
reversedPrefix f (PointedList ls x rs) = (\ls' -> PointedList ls' x rs) <$> f ls
-- | Lens compatible with Control.Lens.
focus :: Functor f => (a -> f a) -> PointedList a -> f (PointedList a)
focus f (PointedList ls x rs) = (\x' -> PointedList ls x' rs) <$> f x
-- | Lens compatible with Control.Lens.
suffix :: Functor f => ([a] -> f [a]) -> PointedList a -> f (PointedList a)
suffix f (PointedList ls x rs) = (\rs' -> PointedList ls x rs') <$> f rs
-- | Lens compatible with Control.Lens.
-- Internally reversing the prefix list.
prefix :: Functor f => ([a] -> f [a]) -> PointedList a -> f (PointedList a)
prefix f (PointedList ls x rs) = (\ls' -> PointedList (reverse ls') x rs) <$> f (reverse ls)
instance (Show a) => Show (PointedList a) where
show (PointedList ls x rs) = show (reverse ls) ++ " " ++ show x ++ " " ++ show rs
instance Functor PointedList where
fmap f (PointedList ls x rs) = PointedList (map f ls) (f x) (map f rs)
instance Foldable PointedList where
foldr f z (PointedList ls x rs) = foldl (flip f) (foldr f z (x:rs)) ls
instance Traversable PointedList where
traverse f (PointedList ls x rs) = PointedList <$>
(reverse <$> traverse f (reverse ls)) <*> f x <*> traverse f rs
-- | Create a 'PointedList' with a single element.
singleton :: a -> PointedList a
singleton x = PointedList [] x []
-- | Possibly create a @'Just' 'PointedList'@ if the provided list has at least
-- one element; otherwise, return Nothing.
--
-- The provided list's head will be the focus of the list, and the rest of
-- list will follow on the right side.
fromList :: [a] -> Maybe (PointedList a)
fromList [] = Nothing
fromList (x:xs) = Just $ PointedList [] x xs
-- | Possibly create a @'Just' 'PointedList'@ if the provided list has at least
-- one element; otherwise, return Nothing.
--
-- The provided list's last element will be the focus of the list, following
-- the rest of the list in order, to the left.
fromListEnd :: [a] -> Maybe (PointedList a)
fromListEnd [] = Nothing
fromListEnd xs = Just $ PointedList xs' x []
where (x:xs') = reverse xs
-- | Replace the focus of the list, retaining the prefix and suffix.
replace :: a -> PointedList a -> PointedList a
replace x (PointedList ls _ rs) = PointedList ls x rs
-- replace = set focus
-- | Possibly move the focus to the next element in the list.
next :: PointedList a -> Maybe (PointedList a)
next (PointedList _ _ []) = Nothing
next p = (Just . tryNext) p -- GHC doesn't allow PL form here
-- | Attempt to move the focus to the next element, or 'error' if there are
-- no more elements.
tryNext :: PointedList a -> PointedList a
tryNext p@(PointedList _ _ [] ) = error "cannot move to next element"
tryNext (PointedList ls x (r:rs)) = PointedList (x:ls) r rs
-- | Possibly move the focus to the previous element in the list.
previous :: PointedList a -> Maybe (PointedList a)
previous (PointedList [] _ _ ) = Nothing
previous p = (Just . tryPrevious) p
-- | Attempt to move the focus to the previous element, or 'error' if there are
-- no more elements.
tryPrevious :: PointedList a -> PointedList a
tryPrevious p@(PointedList [] _ _ ) =
error "cannot move to previous element"
tryPrevious (PointedList (l:ls) x rs) = PointedList ls l (x:rs)
-- | An alias for 'insertRight'.
insert :: a -> PointedList a -> PointedList a
insert = insertRight
-- | Insert an element to the left of the focus, then move the focus to the new
-- element.
insertLeft :: a -> PointedList a -> PointedList a
insertLeft y (PointedList ls x rs) = PointedList ls y (x:rs)
-- | Insert an element to the right of the focus, then move the focus to the
-- new element.
insertRight :: a -> PointedList a -> PointedList a
insertRight y (PointedList ls x rs) = PointedList (x:ls) y rs
-- | An alias of 'deleteRight'.
delete :: PointedList a -> Maybe (PointedList a)
delete = deleteRight
-- | Possibly delete the element at the focus, then move the element on the
-- left to the focus. If no element is on the left, focus on the element to
-- the right. If the deletion will cause the list to be empty, return
-- 'Nothing'.
deleteLeft :: PointedList a -> Maybe (PointedList a)
deleteLeft (PointedList [] _ [] ) = Nothing
deleteLeft (PointedList (l:ls) _ rs) = Just $ PointedList ls l rs
deleteLeft (PointedList [] _ (r:rs)) = Just $ PointedList [] r rs
-- | Possibly delete the element at the focus, then move the element on the
-- right to the focus. If no element is on the right, focus on the element to
-- the left. If the deletion will cause the list to be empty, return
-- 'Nothing'.
deleteRight :: PointedList a -> Maybe (PointedList a)
deleteRight (PointedList [] _ [] ) = Nothing
deleteRight (PointedList ls _ (r:rs)) = Just $ PointedList ls r rs
deleteRight (PointedList (l:ls) _ []) = Just $ PointedList ls l []
-- | Delete all elements in the list except the focus.
deleteOthers :: PointedList a -> PointedList a
deleteOthers (PointedList _ b _) = PointedList [] b []
-- | The length of the list.
length :: PointedList a -> Int
length = foldr (const (+1)) 0
-- | Whether the focus is the first element.
atStart :: PointedList a -> Bool
atStart (PointedList [] _ _) = True
atStart _ = False
-- | Whether the focus is the last element.
atEnd :: PointedList a -> Bool
atEnd (PointedList _ _ []) = True
atEnd _ = False
-- | Create a 'PointedList' of variations of the provided 'PointedList', in
-- which each element is focused, with the provided 'PointedList' as the
-- focus of the sets.
positions :: PointedList a -> PointedList (PointedList a)
positions p@(PointedList ls x rs) = PointedList left p right
where left = unfoldr (\p -> fmap (join (,)) $ previous p) p
right = unfoldr (\p -> fmap (join (,)) $ next p) p
-- | Map over the 'PointedList's created via 'positions', such that @f@ is
-- called with each element of the list focused in the provided
-- 'PointedList'. An example makes this easier to understand:
--
-- > contextMap atStart (fromJust $ fromList [1..5])
contextMap :: (PointedList a -> b) -> PointedList a -> PointedList b
contextMap f z = fmap f $ positions z
-- | Create a @'PointedList' a@ of @(a, 'Bool')@, in which the boolean values
-- specify whether the current element has the focus. That is, all of the
-- booleans will be 'False', except the focused element.
withFocus :: PointedList a -> PointedList (a, Bool)
withFocus (PointedList a b c) =
PointedList (zip a (repeat False)) (b, True) (zip c (repeat False))
-- | Move the focus to the specified index. The first element is at index 0.
moveTo :: Int -> PointedList a -> Maybe (PointedList a)
moveTo n pl = moveN (n - (index pl)) pl
-- | Move the focus by @n@, relative to the current index. Negative values move
-- the focus backwards, positive values more forwards through the list.
moveN :: Int -> PointedList a -> Maybe (PointedList a)
moveN n pl@(PointedList left x right) = go n left x right
where
go n left x right = case compare n 0 of
GT -> case right of
[] -> Nothing
(r:rs) -> go (n-1) (x:left) r rs
LT -> case left of
[] -> Nothing
(l:ls) -> go (n+1) ls l (x:right)
EQ -> Just $ PointedList left x right
-- | Move the focus to the specified element, if it is present.
--
-- Patch with much faster algorithm provided by Runar Bjarnason for version
-- 0.3.2. Improved again by Runar Bjarnason for version 0.3.3 to support
-- infinite lists on both sides of the focus.
find :: Eq a => a -> PointedList a -> Maybe (PointedList a)
find x pl = find' ((x ==) . _focus) $ positions pl
where find' pred (PointedList a b c) =
if pred b then Just b
else List.find pred (merge a c)
merge [] ys = ys
merge (x:xs) ys = x : merge ys xs
-- | The index of the focus, leftmost is 0.
index :: PointedList a -> Int
index (PointedList a _ _) = Prelude.length a
|