packages feed

fixplate-0.1.7: tests/TestSuite/Trie.hs

-- | Tests for tries

module TestSuite.Trie where

--------------------------------------------------------------------------------

import Test.Tasty
import Test.Tasty.QuickCheck
import Test.QuickCheck

import Prelude hiding ( mapM , mapM_ , concat , concatMap , lookup )

import Control.Applicative
import Control.Monad hiding ( mapM , mapM_ , forM , forM_ )
import Data.Foldable hiding ( toList )
import Data.Traversable
import Data.List ( sort , group , nubBy , nub , (\\) , foldl' )

import Data.Map (Map)
import qualified Data.Map as Map 

import Data.Generics.Fixplate.Base
-- import Data.Generics.Fixplate.Misc
import Data.Generics.Fixplate.Attributes
import Data.Generics.Fixplate.Morphisms
import Data.Generics.Fixplate.Traversals
import Data.Generics.Fixplate.Trie as Trie

import TestSuite.Tools
import TestSuite.Misc

--------------------------------------------------------------------------------

testgroup_Trie :: TestTree
testgroup_Trie = testGroup "Trie"
  [ testProperty "difference"        prop_difference
  , testProperty "differenceWith"    prop_differenceWith
  , testProperty "union"             prop_union
  , testProperty "intersection"      prop_intersection

  , testProperty "universeBag"       prop_unibag_naive
  , testProperty "universeBag /2"    prop_unibag_naive_2
  , testProperty "christmasTree"     prop_christmasTree
  , testProperty "christmasTree /2"  prop_christmasTree_2
  , testProperty "christmasTree /3"  prop_christmasTree_3

  , testProperty "fromList"          prop_fromList_naive
  , testProperty "bag"               prop_bag
  , testProperty "bag /b"            prop_bag_b
  , testProperty "toList . fromList" prop_fromList_toList
  , testProperty "multiSetToList"    prop_multiSetToList_b

  , testProperty "insert"            prop_insert
  , testProperty "delete"            prop_delete
  , testProperty "update"            prop_update
  , testProperty "delete . insert"   prop_insert_delete
  , testProperty "insert . delete"   prop_delete_insert
  , testProperty "lookup"            prop_lookup
  , testProperty "lookup /notfound"  prop_lookup_notfound
  , testProperty "singleton"         prop_singleton
  ]

--------------------------------------------------------------------------------
-- Tests

{-
runtests_Trie :: IO ()
runtests_Trie = do
  quickCheck prop_difference
  quickCheck prop_differenceWith
  quickCheck prop_union
  quickCheck prop_intersection

  quickCheck prop_unibag_naive
  quickCheck prop_unibag_naive_2
  quickCheck prop_christmasTree
  quickCheck prop_christmasTree_2
  quickCheck prop_christmasTree_3

  quickCheck prop_fromList_naive
  quickCheck prop_bag
  quickCheck prop_bag_b
  quickCheck prop_fromList_toList
  quickCheck prop_multiSetToList_b
  quickCheck prop_insert
  quickCheck prop_delete
  quickCheck prop_update
  quickCheck prop_insert_delete
  quickCheck prop_delete_insert
  quickCheck prop_lookup
  quickCheck prop_lookup_notfound
  quickCheck prop_singleton
-}

--------------------

newtype Multiplicity = Multiplicity { unMultiplicity :: Int } deriving (Eq,Ord,Show)

instance Arbitrary Multiplicity where
  arbitrary = do
    n <- choose (1, 7)
    return (Multiplicity n)

newtype MultiSet = MultiSet { unMultiSet :: [(Multiplicity, FixT Label)] } deriving (Eq,Ord,Show)

instance Arbitrary MultiSet where arbitrary = MultiSet <$> arbitrary

multiSetToList :: MultiSet -> [FixT Label]
multiSetToList (MultiSet mxs) = go mxs where
  go [] = []
  go ((Multiplicity n, x):rest) = replicate n x ++ go rest

multiSetToList_b :: MultiSet -> [FixT Label]
multiSetToList_b (MultiSet mxs) = go mxs [] where
  go [] [] = []
  go [] ys = go ys []
  go ((Multiplicity n, x):rest) ys = if n>0 
    then x : go rest ( (Multiplicity (n-1), x) : ys )
    else go rest ys

newtype FiniteMap = FiniteMap { unFiniteMap :: [(FixT Label,Char)] } deriving (Eq,Ord,Show)

instance Arbitrary FiniteMap where arbitrary = (FiniteMap . nubBy (equating fst)) <$> arbitrary

type TrieT = Trie (TreeF Label) Char

finiteMap :: FiniteMap -> TrieT
finiteMap (FiniteMap fmap) = fromList fmap

--------------------

fromListNaive :: (Traversable f, OrdF f) => [(Mu f, a)] -> Trie f a
fromListNaive ts = Prelude.foldl worker Trie.empty ts where
  worker trie (tree,value) = Trie.insertWith id const tree value trie 

universeBagNaive :: (Functor f, Foldable f, OrdF f) => Mu f -> Trie f Int
universeBagNaive = bag . universe

mapBag :: Ord a => [a] -> Map a Int
mapBag xs = Data.List.foldl' f Map.empty xs where
  f old x = Map.insertWith (+) x 1 old

--------------------
    
prop_unibag_naive :: FixT Label -> Bool
prop_unibag_naive tree = toList (universeBag tree) == toList (universeBagNaive tree)

prop_unibag_naive_2 :: FixT Bool -> Bool
prop_unibag_naive_2 tree = toList (universeBag tree) == toList (universeBagNaive tree)

prop_fromList_naive :: FiniteMap -> Bool
prop_fromList_naive (FiniteMap list) = toList (fromList list) == toList (fromListNaive list)

prop_bag :: MultiSet -> Bool
prop_bag mset = (sort $ toList $ bag $ multiSetToList mset) == sort (map f $ unMultiSet mset) where
  f (Multiplicity k, x) = (x,k)

prop_bag_b :: MultiSet -> Bool
prop_bag_b mset = (sort $ toList $ bag $ multiSetToList_b mset) == sort (map f $ unMultiSet mset) where
  f (Multiplicity k, x) = (x,k)

prop_fromList_toList :: FiniteMap -> Bool
prop_fromList_toList (FiniteMap list) = sort (toList (fromList list)) == sort list

prop_multiSetToList_b :: MultiSet -> Bool
prop_multiSetToList_b mset = toList (bag (multiSetToList mset)) == toList (bag (multiSetToList_b mset)) 

prop_insert :: FixT Label -> Char -> FiniteMap -> Bool
prop_insert key ch (FiniteMap list) = sort (toList (insert key ch trie)) == sort ((key,ch) : toList trie) where
  trie = fromList list

prop_delete :: Int -> FiniteMap -> Bool
prop_delete i (FiniteMap list) = (n==0) || (toList (delete key trie) == toList trie \\ [(key,value)]) where
  trie = fromList list
  n = length list
  k = mod i n
  (key,value) = list!!k

prop_update :: Char -> Int -> FiniteMap -> Bool
prop_update new i (FiniteMap list) = (n==0) || (toList (update f key trie) == replace (toList trie)) where
  trie = fromList list
  n = length list
  k = mod i n
  (key,value) = list!!k
  replace [] = []
  replace (this@(k,x):rest) = if k==key 
    then case f x of 
      Nothing -> rest
      Just y  -> (k,y) : replace rest
    else this : replace rest    
  f old = if old < 'A' then Nothing else Just new

prop_insert_delete :: FixT Label -> Char -> FiniteMap -> Bool
prop_insert_delete key ch (FiniteMap list) = toList (delete key (insert key ch trie)) == toList trie where
  trie = delete key (fromList list)   -- !

prop_delete_insert :: Int -> FiniteMap -> Bool
prop_delete_insert i (FiniteMap list) = (n==0) || (toList (insert key value (delete key trie)) == toList trie) where
  trie = fromList list
  n = length list
  k = mod i n
  (key,value) = list!!k

prop_lookup :: Int -> FiniteMap -> Bool
prop_lookup i (FiniteMap list) = (n==0) || (Just value == lookup key trie) where
  trie = fromList list
  n = length list
  k = mod i n
  (key,value) = list!!k

prop_lookup_notfound :: FixT Label -> FiniteMap -> Bool
prop_lookup_notfound key (FiniteMap list) = lookup key trie == Nothing where
  trie = delete key (fromList list)   -- !#endif

prop_singleton :: FixT Label -> Char -> Bool
prop_singleton tree ch = toList (singleton tree ch) == [(tree,ch)]

prop_intersection :: MultiSet -> Bool
prop_intersection mset = {- trace ("--"++show n++"--") -} (itrie == imap) where

  list = multiSetToList_b mset
  n = length list
  k = div n 3
  l = div (2*n) 3
  xs = take l list
  ys = drop k list

  itrie = sort $     toList $     intersectionWith (+) (   bag xs) (   bag ys)
  imap  = sort $ Map.toList $ Map.intersectionWith (+) (mapBag xs) (mapBag ys) 

prop_union :: MultiSet -> Bool
prop_union mset = {- trace ("--"++show n++"--") -} (utrie == umap) where

  list = multiSetToList_b mset
  n = length list
  k = div n 3
  l = div (2*n) 3
  xs = take l list
  ys = drop k list

  utrie = sort $     toList $     unionWith (+) (   bag xs) (   bag ys)
  umap  = sort $ Map.toList $ Map.unionWith (+) (mapBag xs) (mapBag ys) 

prop_difference :: MultiSet -> Bool
prop_difference mset = {- trace ("--"++show [length xs , length ys, length dtrie]++"--") -} (dtrie == dmap) where

  list = multiSetToList_b mset
  n = length list
  k = div n 3
  l = div (2*n) 3
  xs = take l list
  ys = drop k list

  dtrie = sort $     toList $     difference (   bag xs) (   bag ys)
  dmap  = sort $ Map.toList $ Map.difference (mapBag xs) (mapBag ys) 

prop_differenceWith :: MultiSet -> Bool
prop_differenceWith mset = {- trace ("--"++show [length xs , length ys, length dtrie]++"--") -} (dtrie == dmap) where

  list = multiSetToList_b mset
  n = length list
  k = div n 3
  l = div (2*n) 3
  xs = take l list
  ys = drop k list

  f x y = if y<=2 then Just (x+1) else Nothing

  dtrie = sort $     toList $     differenceWith f (   bag xs) (   bag ys)
  dmap  = sort $ Map.toList $ Map.differenceWith f (mapBag xs) (mapBag ys) 

prop_christmasTree :: FixT Label -> Bool
prop_christmasTree tree = toList (attribute (christmasTree tree)) == toList (universeBag tree)

prop_christmasTree_3 :: FixT Bool -> Bool
prop_christmasTree_3 tree = toList (attribute (christmasTree tree)) == toList (universeBag tree)

-- we reduce the labels so that there is more chance for collisions
prop_christmasTree_2 :: Bool -> FixT Label -> Bool
prop_christmasTree_2 b tree0 = toList (attribute (christmasTree tree)) == toList (universeBag tree) where
  tree = transform f tree0
  f = if b 
    then \(Fix (TreeF (Label label) ts)) -> Fix $ TreeF (Label (take 1 label)) ts
    else \(Fix (TreeF (Label label) ts)) -> Fix $ TreeF (Label ""            ) ts

--------------------------------------------------------------------------------