module Common (module Common, module Debug.Trace, module Colors) where import Colors import Debug.Trace(trace) import qualified Data.IntMap as IntMap import Data.Char(isSpace) import GHC.Float(formatRealFloat, FFFormat(FFFixed)) import Data.List import qualified Data.Map as Map -- | return a list of all cartesian products for a list of lists -- e.g. products [[1,2],[3,4]] = [[1,3],[1,4],[2,3],[2,4]] products :: [[a]] -> [[a]] products [] = [[]] products (xs:xss) = [ x:prod | x <- xs, prod <- products xss] trees :: Show a => String -> a -> a trees msg a = trace ("{" ++ msg ++ ":" ++ show a ++ "}") a foreach :: Monad m => [a] -> (a -> m b) -> m [b] foreach = flip mapM foreach_ :: Monad m => [a] -> (a -> m b) -> m () foreach_ list fun = do mapM fun list return () ifJust :: Monad m => Maybe a -> (a -> m b) -> m () ifJust ma f = case ma of Nothing -> return () Just a -> do { f a; return () } internalError :: String -> String -> String -> a internalError moduleName functionName errorString = error (moduleName ++ "." ++ functionName ++ ": " ++ errorString) parseDouble :: String -> Maybe Double parseDouble string = case reads (commasToDots . trim $ string) of ((double, []):_) -> Just double _ -> Nothing where commasToDots = map (\c -> if c == ',' then '.' else c) trim :: String -> String trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace -- | A NumberMap maps integers to integers type NumberMap = IntMap.IntMap Int -- | A NumberMap can be inverted (keys become values and values become keys) invertMap :: NumberMap -> NumberMap invertMap theMap = let list = IntMap.toList theMap invertedList = map (\(x, y) -> (y, x)) list in IntMap.fromList invertedList -- | commasAnd combines a list of strings to one string by placing -- commas in between and the word "and" just before the last element commasAnd :: [String] -> String commasAnd [] = "" commasAnd [x] = x commasAnd [x, y] = x ++ " and " ++ y commasAnd (x:xs) = x ++ ", " ++ commasAnd xs -- TODO: is niceFloat 2 0.0001 = "0.0" correct? (as opposed to "0.00") -- | niceFloat prints a floating-point value with maximum -- number of decimals niceFloat :: Int -> Double -> String niceFloat nrOfDigits f = let s = formatRealFloat FFFixed (Just nrOfDigits) f s' = reverse s -- s -- dropWhile (== '0') (reverse s) s'' = if head s' == '.' then '0':s' else s' in reverse s'' -- | niceFloatFix prints a floating-point value with fixed -- number of decimals niceFloatFix :: Int -> Double -> String niceFloatFix nrOfDigits f = let s = formatRealFloat FFFixed (Just nrOfDigits) f in if head s == '.' then '0':s else s -- Compute the average of a list of fractionals, with average [] equal to 0. average :: Fractional a => [a] -> a average [] = 0 average xs = (sum xs) / fromIntegral (length xs) -- | updateList changes the element at the given zero-based index in a list -- Example: updateList 2 "yes" ["no","maybe","often","always"] ==> -- ["no","maybe","yes","always"] updateList :: Int -> a -> [a] -> [a] updateList i x l = take i l ++ [x] ++ drop (i+1) l -- | groups splits a list into groups of given length. The -- last group might be shorter. -- Example: groups 3 [1..10] ==> [[1,2,3],[4,5,6],[7,8,9],[10]] groups :: Int -> [a] -> [[a]] groups _ [] = [] groups n xs = let (col, rest) = splitAt n xs in col: groups n rest swap :: (a, b) -> (b, a) swap (a, b) = (b, a) -- remove the extension from a file name (or path). removeExtension :: String -> String removeExtension filename = case break (=='.') $ reverse filename of (_ , _ {- dot -}:properName) -> reverse properName (_ , []) -> filename tabDelimited :: [[String]] -> String tabDelimited = unlines . map (concat . intersperse "\t") singleton :: a -> [a] singleton x = [x] -- | a version of Prelude.lookup that fails when the element is not present in the assoc-list unsafeLookup :: (Show k, Eq k) => k -> [(k,v)] -> v unsafeLookup x assocs = case lookup x assocs of Just v -> v Nothing -> internalError "Common" "unsafeLookup" ("element " ++ show x ++ " not in list.") -- | a version of Prelude.elemIndex that fails when the element is not present in the list unsafeElemIndex :: (Show a, Eq a) => a -> [a] -> Int unsafeElemIndex x xs = case elemIndex x xs of Just i -> i Nothing -> internalError "Common" "unsafeElemIndex" ("element " ++ show x ++ " not in list") -- Approximately equals (~=) :: Double -> Double -> Bool (~=) d1 d2 = abs (d1 - d2) < 0.000001 fst3 :: (a, b, c) -> a fst3 (a, _, _) = a snd3 :: (a, b, c) -> b snd3 (_, b, _) = b thd3 :: (a, b, c) -> c thd3 (_, _, c) = c safeIndex :: String -> [a] -> Int -> a safeIndex msg xs i | i >= 0 && i < length xs = xs !! i | otherwise = internalError "Common" "safeIndex" msg -- reorderList [0,2,1] "hoi" ==> "hio" reorderList :: Show a => [Int] -> [a] -> [a] reorderList order xs | sort order /= [0..length xs-1] = internalError "Common" "reorderList" ("order = " ++ show order ++ ", list = " ++ show xs) | otherwise = [ xs !! i | i <- order ] infix 6 /\ infix 7 >< (/\) :: ( a -> b ) -> ( a -> c ) -> a -> (b,c) (/\) f1 f2 a = (f1 a, f2 a) (><) :: ( a -> c ) -> ( b -> d ) -> (a,b) -> (c,d) (><) f1 f2 (a, b) = (f1 a, f2 b) diag :: a -> (a,a) diag a = (a, a) int2name :: Int -> String int2name n | n<=0 = error "Unexpected number; a positive number was expected." | otherwise = aux (n-1) where aux m = (if q == 0 then [] else int2name q) ++ [toEnum (r + fromEnum 'a')] where (q, r) = m `divMod` 26 takeJust :: String -> Maybe a -> a takeJust erro (Just res) = res takeJust erro Nothing = error erro removeQuotes :: String -> String removeQuotes ('"':str) = init str removeQuotes str = str spaces2underscores :: String -> String spaces2underscores = map f where f ' ' = '_' f c = c indent :: Int -> String -> String indent n = unlines' . map (replicate n '\t' ++) . lines -- | Similar to 'unlines' but without an @'\n'@ in the end. unlines' :: [String] -> String unlines' = foldr1 (\str1 str2 -> str1 ++ '\n':str2) infix 9 !+! (!+!) :: [a] -> [Int] -> [a] (!+!) l li = map (l !!) li -- | 'foldl' for compositions foldlCont :: (a -> b -> a) -> [b] -> a -> a foldlCont f l init = foldl f init l -- | Filter repeated elements only, mentioning the number of repetitions. repeateds :: Ord a => [a] -> [(a, Int)] repeateds = Map.toList . Map.filter (>1) . Map.fromListWith (+) . map (\x -> (x,1)) -- | Test for repeated elements. haveRepeateds :: Ord a => [a] -> Bool haveRepeateds = any ( (/=1) . snd) . repeateds