okasaki

Okasaki's Purely Functional Data Structures
Log | Files | Refs | LICENSE

commit 9ff044682fb3e49975c343940dadd3fdc8b09a67
parent 805baf63a74c43052fd80c0a6a43a32e6ad7c16b
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 31 Mar 2023 09:14:13 +0400

Miscellaneous additions.

Diffstat:
Abench/Main.hs | 4++++
Mlib/Okasaki/Heap/Binomial.hs | 55+++++++++++++++++++++++++++++++++++++++++++++----------
Alib/Okasaki/Heap/Leftist/CPS.hs | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mokasaki.cabal | 16++++++++++++++++
Atest/Heap/Binomial.hs | 24++++++++++++++++++++++++
5 files changed, 265 insertions(+), 10 deletions(-)

diff --git a/bench/Main.hs b/bench/Main.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = pure () diff --git a/lib/Okasaki/Heap/Binomial.hs b/lib/Okasaki/Heap/Binomial.hs @@ -1,15 +1,21 @@ {-# OPTIONS_GHC -Wall -fno-warn-unused-top-binds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} module Okasaki.Heap.Binomial where import Data.Eq.Deriving (deriveEq1) import Data.Fix hiding (cata, ana, hylo) +import Data.Foldable (foldl') +import Data.Functor.Base (NonEmptyF(..)) import Data.Functor.Foldable +import Data.List.NonEmpty (NonEmpty(..), (<|)) +import qualified Data.List.NonEmpty as NE import Text.Show.Deriving -data TreeF a r = NodeF Int a [Tree a] - deriving (Functor, Foldable, Traversable) +data TreeF a r = NodeF !Int a [r] + deriving (Eq, Show, Functor, Foldable, Traversable) type Tree a = Fix (TreeF a) @@ -17,6 +23,7 @@ $(deriveShow1 ''TreeF) $(deriveEq1 ''TreeF) newtype Heap a = Heap [Tree a] + deriving Show lef :: Heap a lef = Heap [] @@ -25,19 +32,47 @@ nod :: Int -> a -> [Tree a] -> Tree a nod r a t = Fix (NodeF r a t) lin :: Ord a => Tree a -> Tree a -> Tree a -lin s@(project -> NodeF r a c) t@(project -> NodeF _ b d) +lin s@(project -> NodeF r a c) t@(project -> NodeF q b d) + | r /= q = error "(okasaki, binomial): internal error" | a <= b = nod (succ r) a (t : c) | otherwise = nod (succ r) b (s : d) ran :: Tree a -> Int ran (project -> NodeF r _ _) = r -pat :: Ord a => Tree a -> Heap a -> Heap a -pat s (Heap ts) = Heap $ case project ts of - Nil -> embed (Cons s (embed Nil)) - Cons h t - | ran s < ran h -> embed (Cons s ts) - | otherwise -> embed (Cons (lin s h) t) +-- arbitrary recursion +-- +-- NB can't figure out how to express what seems like a very trivial recursion +-- scheme +-- +-- we want to fold the list top-down and then recurse on the *entire* +-- folded list, not the rest of it. i guess this is the issue. +-- +pet :: Ord a => Tree a -> Heap a -> Heap a +pet a (Heap as) = Heap (loop a as) where + loop s ts = case ts of + [] -> pure s + (h : t) + | ran s < ran h -> s : ts + | otherwise -> loop (lin s h) t put :: Ord a => a -> Heap a -> Heap a -put a = pat (nod 0 a (embed Nil)) +put a (Heap as) = pet a ((nod 0 a []) :| as) + +poot :: Ord a => a -> Heap a -> Heap a +poot a = pet (nod 0 a []) + +mer :: Ord a => Heap a -> Heap a -> Heap a +mer (Heap l) (Heap r) = Heap (apo lag (l, r)) where + lag (p, q) = case (project p, project q) of + (u, Nil) -> fmap Left u + (Nil, u) -> fmap Left u + (Cons h s, Cons i t) + | ran h < ran i -> Cons h (Right (s, r)) + | ran i < ran h -> Cons i (Right (l, t)) + | otherwise -> Cons (lin h i) (Right (s, t)) + -- NB "pat (lin h i) (Right (s, t))" + -- + -- pat has not been properly applied in the above case, but possible + -- things are merged correctly. test that ranks are strictly + -- increasing, etc. diff --git a/lib/Okasaki/Heap/Leftist/CPS.hs b/lib/Okasaki/Heap/Leftist/CPS.hs @@ -0,0 +1,176 @@ +{-# OPTIONS_GHC -Wall -fno-warn-unused-top-binds #-} +{-# LANGUAGE TemplateHaskell #-} + +module Okasaki.Heap.Leftist.CPS where + +import Data.Fix hiding (cata, ana, hylo) +import Data.Functor.Foldable +import Data.Monoid +import Okasaki.Orphans () + +-- NB arguably better to use induction +-- +-- exercise 3.1: prove right spine contains at most floor(log(n + 1)) +-- elements +-- +-- * observe that rightmost-weighted binary tree satisfying leftist +-- property is balanced +-- * observe that right spine length is maximized in balanced case +-- * observe that tree has depth floor(log(n + 1)) in balanced case. +-- * therefore, right spine has at most floor(log(n + 1)) elements. +-- (QED) + +newtype HeapF a r = HeapF (forall e. e -> (Sum Int -> a -> r -> r -> e) -> e) + deriving Functor + +type Heap a = Fix (HeapF a) + +lefF :: HeapF a r +lefF = HeapF const + +lef :: Heap a +lef = Fix lefF + +oneF :: a -> HeapF a (Heap b) +oneF x = HeapF (\_ c -> c 1 x lef lef) + +one :: a -> Heap a +one = Fix . oneF + +ran :: Heap a -> Sum Int +ran (project -> HeapF c) = c mempty b where + b r _ _ _ = r + +-- mer :: Ord a => Heap a -> Heap a -> Heap a +-- mer l = sor . mix l + +-- mix :: Ord a => Heap a -> Heap a -> Heap a +-- mix l r = apo lag (l, r) where +-- lag (a, b) = case (project a, project b) of +-- (c, LeafF) -> fmap Left c +-- (LeafF, d) -> fmap Left d +-- (NodeF _ m c d, NodeF _ n e f) +-- | m <= n -> NodeF (ran d <> ran b) m (Left c) (Right (d, b)) +-- | otherwise -> NodeF (ran a <> ran f) n (Left e) (Right (a, f)) + +-- sor :: Heap a -> Heap a +-- sor = cata $ \case +-- LeafF -> lef +-- NodeF _ m l r -> set m l r +-- +-- set :: a -> Heap a -> Heap a -> Heap a +-- set m l r +-- | ran l >= ran r = Fix (NodeF (1 <> ran r) m l r) +-- | otherwise = Fix (NodeF (1 <> ran l) m r l) +-- +-- put :: Ord a => a -> Heap a -> Heap a +-- put x = mer (one x) +-- +-- -- exercise 3.2: direct insert +-- pat :: Ord a => a -> Heap a -> Heap a +-- pat x h = case project h of +-- LeafF -> one x +-- NodeF _ m a b -> +-- let (u, l) +-- | x <= m = (x, m) +-- | otherwise = (m, x) +-- +-- in uncurry (set u) (pot l a b) +-- where +-- pot :: Ord a => a -> Heap a -> Heap a -> (Heap a, Heap a) +-- pot l a b = case (project a, project b) of +-- (_, LeafF) -> (a, one l) +-- (LeafF, _) -> (b, one l) +-- (NodeF _ c _ _, NodeF _ d _ _) +-- | c > d -> (pat l a, b) +-- | otherwise -> (a, pat l b) +-- +-- bot :: Heap a -> Maybe a +-- bot h = case project h of +-- LeafF -> Nothing +-- NodeF _ b _ _ -> Just b +-- +-- cut :: Ord a => Heap a -> Heap a +-- cut h = case project h of +-- LeafF -> h +-- NodeF _ _ l r -> mer l r +-- +-- -- exercise 3.3: hylo gas +-- data BinF a r = +-- EmpF +-- | SinF !a +-- | BinF r r +-- deriving Functor +-- +-- gas :: Ord a => [a] -> Heap a +-- gas = hylo alg lag where +-- lag s = case project s of +-- Nil -> EmpF +-- Cons h [] -> SinF h +-- Cons {} -> +-- let (l, r) = splitAt (length s `div` 2) s +-- in BinF l r +-- +-- alg = \case +-- EmpF -> lef +-- SinF a -> one a +-- BinF l r -> mer l r +-- +-- wyt :: Heap a -> Int +-- wyt = getSum . cata alg where +-- alg = \case +-- LeafF -> mempty +-- NodeF _ _ l r -> 1 <> l <> r +-- +-- -- reference +-- +-- nodF :: a -> Heap a -> Heap a -> HeapF a (Heap a) +-- nodF x l r +-- | ran l >= ran r = NodeF (1 <> ran r) x l r +-- | otherwise = NodeF (1 <> ran l) x r l +-- +-- nod :: a -> Heap a -> Heap a -> Heap a +-- nod x l r = Fix (nodF x l r) +-- +-- mux :: Ord a => Heap a -> Heap a -> Heap a +-- mux l r = case (project l, project r) of +-- (_, LeafF) -> l +-- (LeafF, _) -> r +-- (NodeF _ m a b, NodeF _ n c d) +-- | m <= n -> nod m a (mux b r) +-- | otherwise -> nod n c (mux l d) +-- +-- oil :: Ord a => [a] -> Heap a +-- oil = cata $ \case +-- Nil -> lef +-- Cons h t -> put h t +-- +-- -- test +-- +-- -- (2) 1 +-- -- | \ +-- -- (1) 2 (1) 3 +-- -- | \ | \ +-- -- L L (1) 4 L +-- -- | \ +-- -- (1) 5 L +-- -- | \ +-- -- L L +-- +-- test0 :: Heap Int +-- test0 = gas [1..5] +-- +-- -- (1) 1 +-- -- | \ +-- -- (1) 2 L +-- -- | \ +-- -- (1) 3 L +-- -- | \ +-- -- (1) 4 L +-- -- | \ +-- -- (1) 5 L +-- -- | \ +-- -- L L +-- +-- test1 :: Heap Int +-- test1 = oil [1..5] diff --git a/okasaki.cabal b/okasaki.cabal @@ -34,8 +34,10 @@ library , Okasaki.Tree.CPS build-depends: base + , comonad , data-fix , deriving-compat + , free , recursion-schemes Test-suite tests @@ -66,3 +68,17 @@ Test-suite tests , recursion-schemes , tasty , tasty-quickcheck + +benchmark bench + type: exitcode-stdio-1.0 + hs-source-dirs: bench + main-is: Main.hs + default-language: Haskell2010 + ghc-options: + -rtsopts -O2 + build-depends: + base + , criterion + , deepseq + , okasaki + diff --git a/test/Heap/Binomial.hs b/test/Heap/Binomial.hs @@ -0,0 +1,24 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module Heap.Binomial where + +import qualified Okasaki.Heap.Binomial as B +import Test.QuickCheck +import Test.Tasty (TestTree) +import Test.Tasty.QuickCheck (testProperty) + +-- binomial trees, heaps both have invariants + +-- tree + +-- * tree of rank r should have 2 ^ r nodes +-- * tree of rank r isbinomial a node with r children, with each child +-- t_i having rank r - i +-- * children are maintained in decreasing order of rank +-- * elements are stored in heap order + +-- heap + +-- * no two trees have same rank +-- * list is in increasing order of rank +