\subsection{Utility functions} \begin{haskelllisting} > module Haskore.General.Utility where > > import Control.Monad.Trans.State (state, runState, ) > import System.Random (RandomGen, randomR, randomRs, mkStdGen, ) > import Data.List.HT (segmentBefore, partition, ) > import Data.List (foldl', ) > import Data.Ratio ((%), denominator, numerator, Ratio, ) > import Data.Maybe (fromMaybe, listToMaybe, ) > import Data.Char (ord, chr, ) > import Data.Word (Word8, ) > import qualified Haskore.General.Map as Map \end{haskelllisting} \function{splitBy} takes a boolean test and a list; it divides up the list and turns it into a {\em list of sub-lists}; each sub-list consists of \begin{enumerate} \item one element for which the test is true (or the first element in the list), and \item all elements after that element for which the test is false. \end{enumerate} For example, \code{splitBy (>10) [27, 0, 2, 1, 15, 3, 42, 4]} yields \code{[ [27,0,2,1], [15,3], [42,4] ]}. \begin{haskelllisting} > splitBy :: (a -> Bool) -> [a] -> [[a]] > splitBy p = dropWhile null . segmentBefore p \end{haskelllisting} \function{segmentBefore} will have at most one empty list at the beginning, which is dropped by \function{dropWhile}. It should have signature segmentBefore :: (a -> Bool) -> [a] -> ([a], [(a, [a])]) or even better segmentBefore :: (a -> Bool) -> [a] -> AlternatingListUniform.T a [a] and could be implemented using Uniform.fromEitherList A variant of \function{foldr} and \function{foldr1} which works only for non-empty lists and initializes the accumulator depending on the last element of the list. \begin{haskelllisting} > foldrf :: (a -> b -> b) -> (a -> b) -> [a] -> b > foldrf f g = > let aux [] = error "foldrf: list must be non-empty" > aux (x:[]) = g x > aux (x:xs) = f x (aux xs) > in aux \end{haskelllisting} Randomly permutate a list. For this purpose we generate a random \type{Bool} value for each item of the list which specifies in what sublist it is inserted. Both sublists are then concatenated hereafter. By repeating this procedure several times the list should be somehow randomly ordered. Some notes about perfect shuffling from Oleg: \url{http://okmij.org/ftp/Haskell/misc.html#perfect-shuffle} \begin{haskelllisting} > shuffle :: RandomGen g => [a] -> g -> ([a],g) > shuffle x g0 = > let (choices,g1) = > runState (mapM (const (state (randomR (False,True)))) x) g0 > xc = zip x choices > in (map fst (uncurry (++) (partition snd xc)), g1) \end{haskelllisting} \function{flattenTuples2} flattens a list of pairs into a list. Similarly, \function{flattenTuples3} flattens a list of 3-tuples into a list, and so on. \begin{haskelllisting} > flattenTuples2 :: [(a,a)] -> [a] > flattenTuples3 :: [(a,a,a)] -> [a] > flattenTuples4 :: [(a,a,a,a)] -> [a] > > flattenTuples2 = concatMap (\(x,y) -> [x,y]) > flattenTuples3 = concatMap (\(x,y,z) -> [x,y,z]) > flattenTuples4 = concatMap (\(x,y,z,w) -> [x,y,z,w]) \end{haskelllisting} Choose the first element from a list, and return the default value, if the list is empty. \begin{haskelllisting} > headWithDefault :: a -> [a] -> a > headWithDefault deflt = fromMaybe deflt . listToMaybe \end{haskelllisting} Implementation with the partial function \function{head}, which is a bad thing. \begin{haskelllisting} headWithDefault deflt xs = head (xs ++ [deflt]) \end{haskelllisting} Compare \begin{haskelllisting} let (x,y) = splitInit [0..] in (last x, y) \end{haskelllisting} and \begin{haskelllisting} let as = [0..]; (x,y) = (init as, last as) in (last x, y) \end{haskelllisting} Variants of \function{zip} and \function{zip3} which check that all argument lists have the same length. \begin{haskelllisting} > zipWithMatch :: (a -> b -> c) -> [a] -> [b] -> [c] > zipWithMatch f (x:xs) (y:ys) = f x y : zipWithMatch f xs ys > zipWithMatch _ [] [] = [] > zipWithMatch _ _ _ = error "zipWithMatch: lengths of lists differ" > zipWithMatch3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d] > zipWithMatch3 f (x:xs) (y:ys) (z:zs) = f x y z : zipWithMatch3 f xs ys zs > zipWithMatch3 _ [] [] [] = [] > zipWithMatch3 _ _ _ _ = error "zipWithMatch3: lengths of lists differ" \end{haskelllisting} This is a variant of \function{maximum} which returns at least zero, i.e. always a non-negative number. This is necessary for determining the length of a parallel music composition where the empty list has zero duration. \begin{haskelllisting} > maximum0 :: (Ord a, Num a) => [a] -> a > maximum0 = foldl' max 0 \end{haskelllisting} Convert a mapping (i.e. list of pairs) to a function, and use this for a translation function, which translates every character in a by replacing it by looking it up in l2 and replacing it with the according character in l2. \begin{haskelllisting} > translate :: (Ord a) => [ a ] -> [ a ] -> [ a ] -> [ a ] > translate l1 l2 a = > if length l1 == length l2 > then let table = Map.fromList (zip l1 l2) > in map (\x -> Map.findWithDefault table x x) a > else error "translate: lists must have equal lengths" \end{haskelllisting} A random list of integers between 0 and n. \begin{haskelllisting} > randList :: Int -> [ Int ] > randList n = randomRs (0, n) (mkStdGen 0) \end{haskelllisting} Is one rational divisible by another one (i.e., is it a integer multiple of it)? \begin{haskelllisting} > divisible :: Integral a => Ratio a -> Ratio a -> Bool > divisible r1 r2 = > 0 == mod (numerator r1 * denominator r2) > (numerator r2 * denominator r1) \end{haskelllisting} Do the division. \begin{haskelllisting} > divide :: Integral a => Ratio a -> Ratio a -> a > divide r1 r2 = > let (q, r) = divideModulus r1 r2 > in if r == 0 > then q > else error "Utility.divide: rationals are indivisible" > modulus :: Integral a => Ratio a -> Ratio a -> Ratio a > modulus r1 r2 = snd (divideModulus r1 r2) > divideModulus :: Integral a => Ratio a -> Ratio a -> (a, Ratio a) > divideModulus r1 r2 = > let (q, r) = divMod (numerator r1 * denominator r2) > (numerator r2 * denominator r1) > in (q, r % (denominator r1 * denominator r2)) \end{haskelllisting} Also the GCD can be generalized to ratios: \begin{haskelllisting} > gcdDur :: Integral a => Ratio a -> Ratio a -> Ratio a > gcdDur x1 x2 = > let a = numerator x1 > b = denominator x1 > c = numerator x2 > d = denominator x2 > in gcd a c % lcm b d \end{haskelllisting} \begin{haskelllisting} > type ByteList = [Word8] > stringCharFromByte :: ByteList -> String > stringCharFromByte = map (chr . fromIntegral) > stringByteFromChar :: String -> ByteList > stringByteFromChar = map (fromIntegral . ord) \end{haskelllisting}