Skip to content

Instantly share code, notes, and snippets.

@tonymorris

tonymorris/ZipX.hs

Last active Dec 23, 2015
Embed
What would you like to do?
zipx and unzipx bijective functions
{-# LANGUAGE NoImplicitPrelude #-}
module ZipX where
import Data.List(intercalate, unzip, concat, null, map, (++))
import Prelude(Show(..), Eq(..), Bool(..), String, (.), (||), not)
-- $setup
-- >>> import Test.QuickCheck(Arbitrary(..))
-- >>> import Control.Applicative(liftA2)
-- >>> import Prelude(Functor(..), Int, uncurry)
-- >>> import Data.Maybe(maybe)
-- >>> import Data.Either(either)
--
-- >>> instance Arbitrary a => Arbitrary (NonEmptyList a) where arbitrary = liftA2 NonEmptyList arbitrary arbitrary
--
-- >>> instance (Arbitrary a, Arbitrary b) => Arbitrary (ZipRest a b) where arbitrary = fmap (maybe ZipN (either ZipA ZipB)) arbitrary
--
-- >>> instance (Arbitrary a, Arbitrary b) => Arbitrary (ZipX a b) where arbitrary = liftA2 ZipX arbitrary arbitrary
data NonEmptyList a =
NonEmptyList a [a]
deriving Eq
toList ::
NonEmptyList a
-> [a]
toList (NonEmptyList a as) =
a : as
instance Show a => Show (NonEmptyList a) where
show =
show . toList
data ZipRest a b =
ZipA (NonEmptyList a)
| ZipB (NonEmptyList b)
| ZipN
deriving Eq
isEmptyRest ::
ZipRest a b
-> Bool
isEmptyRest ZipN =
True
isEmptyRest (ZipA _) =
False
isEmptyRest (ZipB _) =
False
instance (Show a, Show b) => Show (ZipRest a b) where
show (ZipA a) =
comma (\a' -> '(' : show a' ++ ",)") (toList a)
show (ZipB b) =
comma (\b' -> "(," ++ show b' ++ ")") (toList b)
show ZipN =
[]
data ZipX a b =
ZipX [(a, b)] (ZipRest a b)
deriving Eq
instance (Show a, Show b) => Show (ZipX a b) where
show (ZipX z r) =
concat
[
"["
, comma show z
, if null z || isEmptyRest r then [] else ","
, show r
, "]"
]
-- | Zip two lists, maintaining any suffixes if the two lists do not align
-- (are of unequal length). The two lists are recoverable from the result of
-- this (bijective) function.
--
-- An /inverse/ for @unzipx@.
--
-- >>> zipx [] [] -- zip two empty lists
-- []
--
-- >>> zipx [1] ['a'] -- zip two lists of equal length (1)
-- [(1,'a')]
--
-- >>> zipx [] ['a'] -- zip empty list with singleton list
-- [(,'a')]
--
-- >>> zipx [1] [] -- zip singleton list with empty list
-- [(1,)]
--
-- > zipx [] ['a', 'b', 'c', 'd'] -- zip empty list with substantially long list (4)
-- [(,'a'),(,'b'),(,'c'),(,'d')]
--
-- >>> zipx [1,2,3,4] [] -- zip substantially long list (4) with empty list
-- [(1,),(2,),(3,),(4,)]
--
-- >>> zipx [1,2,3,4] ['a', 'b', 'c', 'd'] -- zip two equally substantially long lists (4)
-- [(1,'a'),(2,'b'),(3,'c'),(4,'d')]
--
-- >>> zipx [1,2,3,4,5] ['a', 'b', 'c', 'd'] -- zip substantially long list (5) with substantially long list (4)
-- [(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,)]
--
-- >>> zipx [1,2,3,4] ['a', 'b', 'c', 'd', 'e'] -- zip substantially long list (4) with substantially long list (5)
-- [(1,'a'),(2,'b'),(3,'c'),(4,'d'),(,'e')]
--
-- unzipx is an inverse for zipx
-- prop> unzipx (zipx a b) == (a :: [Int], b :: [Int])
zipx ::
[a]
-> [b]
-> ZipX a b
zipx [] [] =
ZipX [] ZipN
zipx (a:as) [] =
ZipX [] (ZipA (NonEmptyList a as))
zipx [] (b:bs) =
ZipX [] (ZipB (NonEmptyList b bs))
zipx (a:as) (b:bs) =
let ZipX z r = zipx as bs
in ZipX ((a, b):z) r
-- | Unzip a list of pairs with possible list suffixes on each side of the pair.
-- (denoting unequal length). The input list is recoverable from the result of
-- this (bijective) function.
--
-- An /inverse/ for @zipx@.
--
-- >>> unzipx (ZipX [] ZipN) -- unzip an empty list with no suffixes
-- ([],[])
--
-- >>> unzipx (ZipX [(1, 'a')] ZipN) -- unzip a singleton list with no suffixes
-- ([1],"a")
--
-- >>> unzipx (ZipX [] (ZipA (NonEmptyList 1 [2,3]))) -- unzip an empty list with a left suffix
-- ([1,2,3],[])
--
-- >>> unzipx (ZipX [] (ZipB (NonEmptyList 'a' ['b','c']))) -- unzip an empty list with a right suffix
-- ([],"abc")
--
-- >>> unzipx (ZipX [(1, 'a')] (ZipA (NonEmptyList 2 [3,4]))) -- unzip a singleton list with a left suffix
-- ([1,2,3,4],"a")
--
-- >>> unzipx (ZipX [(1, 'a')] (ZipB (NonEmptyList 'b' ['c','d']))) -- unzip a singleton list with a right suffix
-- ([1],"abcd")
--
-- >>> unzipx (ZipX [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')] ZipN) -- unzip a substantial list (4) with no suffixes
-- ([1,2,3,4],"abcd")
--
-- >>> unzipx (ZipX [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')] (ZipA (NonEmptyList 5 [6,7]))) -- unzip a substantial list (4) with a right suffix
-- ([1,2,3,4,5,6,7],"abcd")
--
-- >>> unzipx (ZipX [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')] (ZipB (NonEmptyList 'e' ['f','g']))) -- unzip a substantial list (4) with a left suffix
-- ([1,2,3,4],"abcdefg")
--
-- zipx is an inverse for unzipx
-- prop> uncurry zipx (unzipx z) == (z :: ZipX Int Int)
unzipx ::
ZipX a b
-> ([a], [b])
unzipx (ZipX z r) =
let (as, bs) = unzip z
in case r of
ZipN -> (as, bs)
ZipA ar -> (as ++ toList ar, bs)
ZipB br -> (as, bs ++ toList br)
-- utility for Show instances
comma ::
(a -> String)
-> [a]
-> String
comma f =
intercalate "," . map f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment