nonempty-vector-0.3: test/Test/Data/Vector/NonEmpty/Scans.hs
-- |
-- Module : Test.Data.Vector.NonEmpty.Scans
-- Copyright : (c) 2019-2026 Emily Pillmore
-- License : BSD-style
--
-- Maintainer : Emily Pillmore <emilypi@cohomolo.gy>
-- Stability : experimental
-- Portability : non-portable
--
-- Properties for prefix sums (scans) and transformations.
--
-- Scan functions are arbitrary, hence neither associative nor commutative,
-- so the models pin down scan direction and argument order.
--
module Test.Data.Vector.NonEmpty.Scans
( tests
) where
import qualified Data.List as List
import qualified Data.Vector as V
import Data.Vector.NonEmpty (NonEmptyVector)
import qualified Data.Vector.NonEmpty as NEV
import Test.Data.Vector.NonEmpty.Util
import Test.QuickCheck
import Test.Tasty
import Test.Tasty.QuickCheck
tests :: TestTree
tests = testGroup "Scans"
[ testGroup "Prefix sums"
[ testProperty "prop_prescanl" prop_prescanl
, testProperty "prop_prescanl'" prop_prescanl'
, testProperty "prop_postscanl" prop_postscanl
, testProperty "prop_postscanl'" prop_postscanl'
, testProperty "prop_scanl" prop_scanl
, testProperty "prop_scanl'" prop_scanl'
, testProperty "prop_scanl1" prop_scanl1
, testProperty "prop_scanl1'" prop_scanl1'
, testProperty "prop_iscanl" prop_iscanl
, testProperty "prop_iscanl'" prop_iscanl'
, testProperty "prop_prescanr" prop_prescanr
, testProperty "prop_prescanr'" prop_prescanr'
, testProperty "prop_postscanr" prop_postscanr
, testProperty "prop_postscanr'" prop_postscanr'
, testProperty "prop_scanr" prop_scanr
, testProperty "prop_scanr'" prop_scanr'
, testProperty "prop_scanr1" prop_scanr1
, testProperty "prop_scanr1'" prop_scanr1'
, testProperty "prop_iscanr" prop_iscanr
, testProperty "prop_iscanr'" prop_iscanr'
]
, testGroup "Intersperse tests"
[ testProperty "prop_intersperse" prop_intersperse
, testProperty "prop_intersperse_length" prop_intersperse_length
, testProperty "prop_intersperse_first_last" prop_intersperse_first_last
, testProperty "prop_intersperse_separator" prop_intersperse_separator
, testProperty "prop_intersperse_reconstruction" prop_intersperse_reconstruction
, testProperty "prop_intersperse_singleton" prop_intersperse_singleton
, testProperty "prop_explicit_examples" prop_explicit_examples
]
]
type V = NonEmptyVector Int
-- ---------------------------------------------------------------------- --
-- Models
-- | Left scan over @(index, element)@ pairs.
--
iscanlList :: (Int -> a -> b -> a) -> a -> [b] -> [a]
iscanlList f z = List.scanl (\acc (i, b) -> f i acc b) z . List.zip [0 ..]
-- | Right scan over @(index, element)@ pairs.
--
iscanrList :: (Int -> a -> b -> b) -> b -> [a] -> [b]
iscanrList f z = List.scanr (\(i, a) acc -> f i a acc) z . List.zip [0 ..]
-- ---------------------------------------------------------------------- --
-- Left scans
prop_prescanl :: Fun (Int, Int) Int -> Int -> V -> Property
prop_prescanl (Fn2 f) z v =
elems (NEV.prescanl f z v)
=== List.take (NEV.length v) (List.scanl f z (elems v))
prop_prescanl' :: Fun (Int, Int) Int -> Int -> V -> Property
prop_prescanl' (Fn2 f) z v =
elems (NEV.prescanl' f z v)
=== List.take (NEV.length v) (List.scanl f z (elems v))
prop_postscanl :: Fun (Int, Int) Int -> Int -> V -> Property
prop_postscanl (Fn2 f) z v =
elems (NEV.postscanl f z v) === List.drop 1 (List.scanl f z (elems v))
prop_postscanl' :: Fun (Int, Int) Int -> Int -> V -> Property
prop_postscanl' (Fn2 f) z v =
elems (NEV.postscanl' f z v) === List.drop 1 (List.scanl f z (elems v))
prop_scanl :: Fun (Int, Int) Int -> Int -> V -> Property
prop_scanl (Fn2 f) z v = elems (NEV.scanl f z v) === List.scanl f z (elems v)
prop_scanl' :: Fun (Int, Int) Int -> Int -> V -> Property
prop_scanl' (Fn2 f) z v = elems (NEV.scanl' f z v) === List.scanl f z (elems v)
prop_scanl1 :: Fun (Int, Int) Int -> V -> Property
prop_scanl1 (Fn2 f) v = elems (NEV.scanl1 f v) === List.scanl1 f (elems v)
prop_scanl1' :: Fun (Int, Int) Int -> V -> Property
prop_scanl1' (Fn2 f) v = elems (NEV.scanl1' f v) === List.scanl1 f (elems v)
prop_iscanl :: Fun (Int, Int, Int) Int -> Int -> V -> Property
prop_iscanl (Fn3 f) z v =
elems (NEV.iscanl f z v) === iscanlList f z (elems v)
prop_iscanl' :: Fun (Int, Int, Int) Int -> Int -> V -> Property
prop_iscanl' (Fn3 f) z v =
elems (NEV.iscanl' f z v) === iscanlList f z (elems v)
-- ---------------------------------------------------------------------- --
-- Right scans
prop_prescanr :: Fun (Int, Int) Int -> Int -> V -> Property
prop_prescanr (Fn2 f) z v =
elems (NEV.prescanr f z v) === List.drop 1 (List.scanr f z (elems v))
prop_prescanr' :: Fun (Int, Int) Int -> Int -> V -> Property
prop_prescanr' (Fn2 f) z v =
elems (NEV.prescanr' f z v) === List.drop 1 (List.scanr f z (elems v))
prop_postscanr :: Fun (Int, Int) Int -> Int -> V -> Property
prop_postscanr (Fn2 f) z v =
elems (NEV.postscanr f z v)
=== List.take (NEV.length v) (List.scanr f z (elems v))
prop_postscanr' :: Fun (Int, Int) Int -> Int -> V -> Property
prop_postscanr' (Fn2 f) z v =
elems (NEV.postscanr' f z v)
=== List.take (NEV.length v) (List.scanr f z (elems v))
prop_scanr :: Fun (Int, Int) Int -> Int -> V -> Property
prop_scanr (Fn2 f) z v = elems (NEV.scanr f z v) === List.scanr f z (elems v)
prop_scanr' :: Fun (Int, Int) Int -> Int -> V -> Property
prop_scanr' (Fn2 f) z v = elems (NEV.scanr' f z v) === List.scanr f z (elems v)
prop_scanr1 :: Fun (Int, Int) Int -> V -> Property
prop_scanr1 (Fn2 f) v = elems (NEV.scanr1 f v) === List.scanr1 f (elems v)
prop_scanr1' :: Fun (Int, Int) Int -> V -> Property
prop_scanr1' (Fn2 f) v = elems (NEV.scanr1' f v) === List.scanr1 f (elems v)
prop_iscanr :: Fun (Int, Int, Int) Int -> Int -> V -> Property
prop_iscanr (Fn3 f) z v =
elems (NEV.iscanr f z v) === iscanrList f z (elems v)
prop_iscanr' :: Fun (Int, Int, Int) Int -> Int -> V -> Property
prop_iscanr' (Fn3 f) z v =
elems (NEV.iscanr' f z v) === iscanrList f z (elems v)
-- ---------------------------------------------------------------------- --
-- Intersperse properties
prop_intersperse :: Int -> V -> Property
prop_intersperse sep v =
elems (NEV.intersperse sep v) === List.intersperse sep (elems v)
-- For vectors with length > 1, interspersing should result in length 2*n - 1
prop_intersperse_length :: Int -> V -> Bool
prop_intersperse_length sep v =
let result = NEV.intersperse sep v
originalLen = NEV.length v
in if originalLen > 1
then NEV.length result == 2 * originalLen - 1
else NEV.length result == originalLen
-- The first and last elements of the result should match the original vector
prop_intersperse_first_last :: Int -> V -> Bool
prop_intersperse_first_last sep v =
let result = NEV.intersperse sep v
in NEV.head result == NEV.head v && NEV.last result == NEV.last v
-- Every odd-indexed element should be the separator (for vectors with length > 1)
prop_intersperse_separator :: Int -> V -> Bool
prop_intersperse_separator sep v =
let result = NEV.intersperse sep v
in NEV.length v <= 1 ||
all (\i -> result NEV.! (2*i+1) == sep) [0 .. NEV.length v - 2]
-- If we remove the separator, we should get back the original vector
prop_intersperse_reconstruction :: Int -> V -> Bool
prop_intersperse_reconstruction sep v =
let result = NEV.intersperse sep v
resultVector = NEV.toVector result
reconstructed = V.ifilter (\i _ -> i `mod` 2 == 0) resultVector
in V.toList reconstructed == NEV.toList v
-- For singleton vectors, interspersing should not change the vector
prop_intersperse_singleton :: Int -> Int -> Bool
prop_intersperse_singleton x sep =
let singleton = NEV.singleton x
result = NEV.intersperse sep singleton
in result == singleton
-- Explicit test cases for edge cases and examples combined into one property
prop_explicit_examples :: Property
prop_explicit_examples =
let normalCase = NEV.intersperse (0 :: Int) (NEV.unsafeFromList [1,2,3 :: Int])
=== NEV.unsafeFromList [1,0,2,0,3 :: Int]
singletonCase = NEV.intersperse (0 :: Int) (NEV.singleton (5 :: Int))
=== NEV.singleton (5 :: Int)
in normalCase .&&. singletonCase