Node
using the Branch3
construction, and attach them to a deeper finger tree: -- <| . : infixr 5 <| (<|) :: a -> FingerTree a -> FingerTree a -- #1. , 1 x <| Empty = Single x -- #2: , 1 -- , 'Affix a' x <| Single y = Deep [x] Empty [y] -- : 4 , -- 2 , -- , x <| Deep [a, b, c, d] deeper suffix = Deep [x, a] (node <| deeper) suffix where node = Branch3 bcd -- : x <| tree = tree { prefix = affixPrepend x $ prefix tree }
infixl 5 |> (|>) :: FingerTree a -> a -> FingerTree a Empty |> y = Single y Single x |> y = Deep [x] Empty [y] Deep prefix deeper [a, b, c, d] |> y = Deep prefix (deeper |> node) [d, y] where node = Branch3 abc tree |> y = tree { suffix = affixAppend y $ suffix tree }
> let empty = Empty > 't' <| empty |> 'x' |> 'y' |> 'z' |> 'w' |> 'm' Deep {prefix = One 't', deeper = Single (Branch3 'x' 'y' 'z'), suffix = Two 'w' 'm'}
O(lg n)
, where n
is the number of elements.m
additions into a seriesEmpty
or Single x
), or if we can immediately add an element, modifying the prefix, the addition takes O(1)
time. The recursive case (when the affix is ​​4 in length) is more complicated to calculate. However, when we add to an affix of length 4, we instantly rebalance a tree with an affix of length 2. So, we know that the next operation will be instantaneous and take a constant time, and there is no need to go to another level. From this we can deduce that no more than half of the join operations do not need a recursive case of switching to the second level of the finger tree. And after each recursive case, there must be at least one non-recursive one. Similar logic applies to the second layer: we know that only a quarter of operations can be possible to go deep into the third layer. Continuing this logic, the nth layer of the finger tree can be visited only in one of every 2n-1
actions. So, the total time for all m
additions in the worst case will beT=m+1/2m+1/4m+1/8m+⋯
,2m
(which is easy to see if you notice the formula of a geometric series). In the real case, O(m)
time will be spent for m
additions, and although in the worst case the time of one operation will be O(lg n)
, where n
is the number of elements in the tree, the amortized time for this case will be only O(1)
for any addition from the beginning or from the end.(|>)
and from the end (<|)
elements in a sequence (sequence) based on a finger tree. But, in addition to adding elements, there is an urgent need to look at them and delete them. Both of these operations are based on a more universal operation — a preview, which we will implement below.viewr
) and a view on the left ( viewl
). Each of them takes one element from the end (left or right, respectively) and returns the element along with the rest of the finger tree. -- , `Maybe`, -- , . listViewL :: [a] -> Maybe (a, [a]) listViewL [] = Nothing listViewL (x:xs) = Just (x, xs)
-- -- Maybe (a, FingerTree a). data View a = Nil | View a (FingerTree a) deriving Show viewl :: FingerTree a -> View a viewl Empty = Nil -- viewl (Single x) = View x Empty -- viewl (Deep prefix deeper suffix) = View first $ Deep (fromList rest) deeper suffix where -- , , -- first:rest = toList prefix
> viewl empty Nil > viewl exampleTree View 't' (Deep {prefix = One 'h', deeper = Deep {prefix = Two (Branch2 'i' 's') (Branch2 'i' 's'), deeper = Empty, suffix = Two (Branch3 'n' 'o' 't') (Branch2 'a' 't')}, suffix = Three 'r' 'e' 'e'})
> let View _ rest = viewl exampleTree > viewl rest -- 1 4 !
Deep
, we simply remove the element from the left prefix. But this will work only for as long as we have at least 1 element, but as soon as we want to pull the element from the inside, the tree will be wrong. In the block described above, we tried to use viewl to create a finger tree, which would contain 0 elements in the prefix, which of course illegally and instantly causes an error.viewl
for a deeper finger tree to get the Node a
branch. This Node a
contains 2 or 3 values, so we can safely pull out one prefix element, and instead replace it with the prefix contained in Node a
, so the transformation always guarantees the size of the affix between 1 and 4 elements. The following implementation of viewl works for all cases (we still use the View a
data structure described earlier): -- viewl :: FingerTree a -> View a viewl Empty = Nil -- viewl (Single x) = View x Empty -- -- Deep -- -- , viewl (Deep [x] deeper suffix) = View x rest where rest = -- : case viewl deeper of -- View node rest' -> -- Deep (fromList $ toList node) rest' suffix -- -- -- Nil -> case suffix of [x] -> Single x [x, y] -> Deep [x] Empty [y] -- 2 , , -- . -- - [x, y, z] -> Deep [x, y] Empty [z] [x, y, z, w] -> Deep [x, y, z] Empty [w] -- , Deep -- viewl (Deep prefix deeper suffix) = View first $ Deep (fromList rest) deeper suffix where first:rest = toList prefix
viewl
implementation, our crash test will work perfectly > let View _ rest = viewl exampleTree > viewl rest View 'h' (Deep {prefix = Two 'i' 's', deeper = Deep {prefix = One (Branch2 'i' 's'), deeper = Empty, suffix = Two (Branch3 'n' 'o' 't') (Branch2 'a' 't')}, suffix = Three 'r' 'e' 'e'})
viewl
, viewr
implemented almost completely the same, only using prefixes instead of suffixes and vice versa. viewr :: FingerTree a -> View a viewr Empty = Nil viewr (Single x) = View x Empty viewr (Deep prefix deeper [x]) = View x rest where rest = case viewr deeper of -- , View node rest' -> Deep prefix rest' (fromList $ toList node) -- , Nil -> case prefix of [x] -> Single x [x, y] -> Deep [x] Empty [y] [x, y, z] -> Deep [x] Empty [y, z] [x, y, z, w] -> Deep [x] Empty [y, z, w] viewr (Deep prefix deeper suffix) = View suffixLast $ Deep prefix deeper (fromList suffixInit) where suffixLast = last $ toList suffix suffixInit = init $ toList suffix
viewl
, we can view the end of the sequence > viewr exampleTree View 'e' (Deep {prefix = Two 't' 'h', deeper = Deep {prefix = Two (Branch2 'i' 's') (Branch2 'i' 's'), deeper = Empty, suffix = Two (Branch3 'n' 'o' 't') (Branch2 'a' 't')}, suffix = Two 'r' 'e'})
head
, tail
, last
, init
, and null
for lists treeHead :: FingerTree a -> a treeHead tree = case viewl tree of Nil -> error "no elements in tree" View x _ -> x treeTail :: FingerTree a -> FingerTree a treeTail tree = case viewl tree of Nil -> error "no elements in tree" View _ xs -> xs treeLast :: FingerTree a -> a treeLast tree = case viewr tree of Nil -> error "no elements in tree" View x _ -> x treeInit :: FingerTree a -> FingerTree a treeInit tree = case viewr tree of Nil -> error "no elements in tree" View _ xs -> xs isEmpty :: FingerTree a -> Bool isEmpty tree = case viewl tree of Nil -> True _ -> False
-- . instance IsList (FingerTree a) where type Item (FingerTree a) = a toList tree = case viewl tree of Nil -> [] View x xs -> x : toList xs fromList = foldr (<|) Empty
> [1..6] :: FingerTree Int Deep {prefix = Two 1 2, deeper = Single (Branch3 3 4 5), suffix = One 6}
-- >< . (><) :: FingerTree a -> FingerTree a -> FingerTree a left >< Empty = left left >< right = let View first rest = viewl right in (left |> first) >< rest
|>
takes O(1)
amortized time, but this procedure will be done O(m)
times, where m
is the number of elements in the right tree, which we pass to the function ><
. As a result, the total time O(m)
- linearly depends on the number of elements that we attach. nodes :: [a] -> [Node a] nodes xs = case xs of [] -> error "not enough elements for nodes" [x] -> error "not enough elements for nodes" [x, y] -> [Branch2 xy] [x, y, z] -> [Branch3 xyz] x:y:rest -> Branch2 xy : nodes rest
Branch3
on the left side. As a result, we are sure that the function of nodes, if given a list of elements, always create a list of n/2
nodes. This we need in the future.concatWithMiddle
). The connector takes two FingerTree a
values ​​for the connection, as well as a list of items between the two trees. (><) :: FingerTree a -> FingerTree a -> FingerTree a left >< right = concatWithMiddle left [] right concatWithMiddle :: FingerTree a -> [a] -> FingerTree a -> FingerTree a concatWithMiddle = unimplemented where unimplemented = error "Soon to come!"
concatWithMiddle
- implement concatWithMiddle
. We need a special case if we have trees with constructors Empty
or Single
. In these cases, the compound is reduced to O(1)
addition.Deep
. Here we will also return Deep
along with:concatWithMiddle
concatWithMiddle
. While deep finger trees store Node a
nodes instead of
values, we use the helper function to create a list of nodes that we pass recursively to the concatWithMiddle
function. It certainly sounds weird, but it should be readable from code: concatWithMiddle :: FingerTree a -> [a] -> FingerTree a -> FingerTree a -- : concatWithMiddle Empty [] right = right concatWithMiddle Empty (x:xs) right = x <| concatWithMiddle Empty xs right concatWithMiddle (Single y) xs right = y <| concatWithMiddle Empty xs right concatWithMiddle left [] Empty = left concatWithMiddle left xs Empty = concatWithMiddle left (init xs) Empty |> last xs concatWithMiddle left xs (Single y) = concatWithMiddle left xs Empty |> y -- : concatWithMiddle left mid right = Deep (prefix left) deeper' (suffix right) where -- concatWithMiddle , deeper' = concatWithMiddle (deeper left) mid' (deeper right) -- , -- , concatWithMiddle. mid' = nodes $ (toList $ suffix left) ++ mid ++ (toList $ prefix right) -- >< . (><) :: FingerTree a -> FingerTree a -> FingerTree a left >< right = concatWithMiddle left [] right
exampleTree >< exampleTree
: > putStrLn $ toList $ exampleTree >< exampleTree thisisnotatreethisisnotatree > putStrLn $ toList $ concatWithMiddle exampleTree " " exampleTree thisisnotatree thisisnotatree
concatWithMiddle
, we still find a ton of attachments! Each time we recurse inside the layer, we add elements to the median list (from unused affixes). Is it possible to achieve O(m)
after this in the base case?O(1)
amortized time. The magic here is in the use of nodes. As we showed, when the function of nodes was determined, nodes guarantee the output list of no more than half of the elements of the input list. Using this property, we can easily track that the maximum length of the average list can be any during the counting.concatWithMiddle
an empty list in our definition ><
. At each step, we add 2 affixes (suffix and prefix) to the middle list before adding nodes to it. We know that affixes have no more than 4 elements, which means that for the first iteration we will add no more than 8 elements, the output length after the function of nodes will be close to 8, but no more than 8. Indeed, if we start with a list less than or equal to 8 elements, then the length is divided in half, at the output we get again 8 or less elements. You can look at the example when we start with an empty middle list, and we will add 8 elements per step.O(1)
(constant), as a result we can confidently say that the base case occupies O(1)
amortized execution time, since there will be no more than 8 connections.><
function, is recursion. Each recursive step, when we look into the deep trees of both the right and left trees of the concatWithMiddle
function concatWithMiddle
, we proceed to the base case immediately when at least one of the trees does not contain a Deeper
constructor. The depth of these trees grows logarithmically with the growth of elements in it, if the finger tree consists of n
elements, then its depth will be O(lg n)
. And the runtime is proportional to the minimum depth of both trees, our asymptotic time limit is O(min(lg n,lg m))=O(lg(min(m,n)))
, where n
and m
Source: https://habr.com/ru/post/243205/
All Articles