2 Module : Gargantext.Prelude
3 Description : Specific Prelude of the project
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
13 {-# OPTIONS_GHC -fno-warn-orphans #-}
14 {-# OPTIONS_GHC -fno-warn-type-defaults #-}
16 module Gargantext.Prelude
17 ( module Gargantext.Prelude
19 , module GHC.Err.Located
27 , headMay, lastMay, sortWith
32 import Control.Monad.Base (MonadBase(..))
34 import GHC.Exts (sortWith)
35 import GHC.Err.Located (undefined)
36 import GHC.Real (round)
37 import Data.Map (Map, lookup)
38 import Data.Maybe (isJust, fromJust, maybe)
39 import Data.Monoid (Monoid, mempty)
40 import Data.Semigroup (Semigroup, (<>))
41 import Data.Text (Text, pack)
42 import Data.Typeable (Typeable)
43 import Protolude ( Bool(True, False), Int, Int64, Double, Integer
44 , Fractional, Num, Maybe(Just,Nothing)
45 , Enum, Bounded, Float
48 , pure, (>>=), (=<<), (<*>), (<$>), (<&>), (>>)
50 , Ord, Integral, Foldable, RealFrac, Monad, filter
51 , reverse, map, mapM, zip, drop, take, zipWith
52 , sum, fromIntegral, length, fmap, foldl, foldl'
53 , takeWhile, sqrt, identity
54 , abs, min, max, maximum, minimum, return, snd, truncate
55 , (+), (*), (/), (-), (.), ($), (&), (**), (^), (<), (>), log
56 , Eq, (==), (>=), (<=), (<>), (/=), xor
57 , (&&), (||), not, any, all
60 , elem, die, mod, div, const, either
61 , curry, uncurry, repeat
69 import qualified Protolude as Protolude (writeFile)
71 import Prelude (Enum, Bounded, minBound, maxBound, putStrLn)
72 -- TODO import functions optimized in Utils.Count
73 -- import Protolude hiding (head, last, all, any, sum, product, length)
74 -- import Gargantext.Utils.Count
75 import Data.Map.Strict (insertWith)
76 import Data.String.Conversions (cs)
77 import Safe (headMay, lastMay, initMay, tailMay)
78 import Text.Read (Read())
79 import Text.Show (Show(), show)
80 import qualified Control.Monad as M
81 import qualified Data.List as L hiding (head, sum)
82 import qualified Data.Map as M
83 import qualified Data.Set as Set
84 import qualified Data.Vector as V
85 import System.FilePath.Posix (takeDirectory)
86 import System.Directory (createDirectoryIfMissing)
88 printDebug :: (Show a, MonadBase IO m) => [Char] -> a -> m ()
89 printDebug msg x = liftBase . putStrLn $ msg <> " " <> show x
90 -- printDebug _ _ = pure ()
92 saveAsFileDebug :: (Show a, MonadBase IO m) => [Char] -> a -> m ()
93 saveAsFileDebug fname x = do
94 let dir = takeDirectory fname
95 _ <- liftBase $ createDirectoryIfMissing True dir
96 liftBase . Protolude.writeFile fname $ pack $ show x
99 -- | splitEvery n == chunkAlong n n
100 splitEvery :: Int -> [a] -> [[a]]
103 let (h,t) = L.splitAt n xs
104 in h : splitEvery n t
109 -- | Function to split a range into chunks
110 -- if step == grain then linearity (splitEvery)
111 -- elif step < grain then overlapping
112 -- else dotted with holes
113 -- TODO FIX BUG if Steps*Grain /= length l
114 -- chunkAlong 10 10 [1..15] == [1..10]
115 -- BUG: what about the rest of (divMod 15 10)?
116 -- TODO: chunkAlongNoRest or chunkAlongWithRest
117 -- default behavior: NoRest
119 chunkAlong :: Eq a => Grain -> Step -> [a] -> [[a]]
120 chunkAlong a b l = case a >= length l of
122 False -> chunkAlong' a b l
124 chunkAlong' :: Eq a => Grain -> Step -> [a] -> [[a]]
125 chunkAlong' a b l = case a > 0 && b > 0 of
126 True -> chunkAlong'' a b l
127 False -> panic "ChunkAlong: Parameters should be > 0 and Grain > Step"
129 chunkAlong'' :: Eq a => Int -> Int -> [a] -> [[a]]
130 chunkAlong'' a b l = filter (/= []) $ only (while dropAlong)
133 while = takeWhile (\x -> length x >= a)
134 dropAlong = L.scanl (\x _y -> drop b x) l ([1..] :: [Integer])
136 -- | Optimized version (Vector)
137 chunkAlongV :: Int -> Int -> V.Vector a -> V.Vector (V.Vector a)
138 chunkAlongV a b l = only (while dropAlong)
140 only = V.map (V.take a)
141 while = V.takeWhile (\x -> V.length x >= a)
142 dropAlong = V.scanl (\x _y -> V.drop b x) l (V.fromList [1..])
144 -- | TODO Inverse of chunk ? unchunkAlong ?
145 -- unchunkAlong :: Int -> Int -> [[a]] -> [a]
146 -- unchunkAlong = undefined
149 -- splitAlong [2,3,4] ("helloworld" :: [Char]) == ["he", "llo", "worl", "d"]
150 splitAlong :: [Int] -> [Char] -> [[Char]]
151 splitAlong _ [] = [] -- No list? done
152 splitAlong [] xs = [xs] -- No place to split at? Return the remainder
153 splitAlong (x:xs) ys = take x ys : splitAlong xs (drop x ys)
154 -- take until our split spot, recurse with next split spot and list remainder
156 takeWhileM :: (Monad m) => (a -> Bool) -> [m a] -> m [a]
157 takeWhileM _ [] = return []
158 takeWhileM p (a:as) = do
162 vs <- takeWhileM p as
167 -- To select the right algorithme according to the type:
168 -- https://github.com/mikeizbicki/ifcxt
170 sumSimple :: Num a => [a] -> a
171 sumSimple = L.foldl' (+) 0
173 -- | https://en.wikipedia.org/wiki/Kahan_summation_algorithm
174 sumKahan :: Num a => [a] -> a
175 sumKahan = snd . L.foldl' go (0,0)
177 go (c,t) i = ((t'-t)-y,t')
182 -- | compute part of the dict
183 count2map :: (Ord k, Foldable t) => t k -> Map k Double
184 count2map xs = M.map (/ (fromIntegral (length xs))) (count2map' xs)
186 -- | insert in a dict
187 count2map' :: (Ord k, Foldable t) => t k -> Map k Double
188 count2map' xs = L.foldl' (\x y -> insertWith (+) y 1 x) M.empty xs
190 trunc :: (RealFrac a, Integral c, Integral b) => b -> a -> c
191 trunc n = truncate . (* 10^n)
193 trunc' :: Int -> Double -> Double
194 trunc' n x = fromIntegral $ truncate $ (x * 10^n)
196 ------------------------------------------------------------------------
197 bool2num :: Num a => Bool -> a
201 bool2double :: Bool -> Double
202 bool2double = bool2num
204 bool2int :: Bool -> Int
206 ------------------------------------------------------------------------
208 -- Normalizing && scaling data
209 scale :: [Double] -> [Double]
212 scaleMinMax :: [Double] -> [Double]
213 scaleMinMax xs = map (\x -> (x - mi / (ma - mi + 1) )) xs'
219 scaleNormalize :: [Double] -> [Double]
220 scaleNormalize xs = map (\x -> (x - v / (m + 1))) xs'
226 normalize :: [Double] -> [Double]
227 normalize as = normalizeWith identity as
229 normalizeWith :: Fractional b => (a -> b) -> [a] -> [b]
230 normalizeWith extract bs = map (\x -> x/(sum bs')) bs'
234 -- Zip functions to add
235 zipFst :: ([b] -> [a]) -> [b] -> [(a, b)]
236 zipFst f xs = zip (f xs) xs
238 zipSnd :: ([a] -> [b]) -> [a] -> [(a, b)]
239 zipSnd f xs = zip xs (f xs)
242 maximumWith :: (Ord a1, Foldable t) => (a2 -> a1) -> t a2 -> a2
243 maximumWith f = L.maximumBy (compare `on` f)
245 -- | To get all combinations of a list with no
246 -- repetition and apply a function to the resulting list of pairs
247 listToCombi :: forall a b. (a -> b) -> [a] -> [(b,b)]
248 listToCombi f l = [ (f x, f y) | (x:rest) <- L.tails l, y <- rest ]
250 ------------------------------------------------------------------------
251 -- Empty List Sugar Error Handling
252 -- TODO add Garg Monad Errors
254 listSafe1 :: Text -> ([a] -> Maybe a)
256 listSafe1 s f e xs = maybe (panic $ h <> e) identity (f xs)
258 h = "[ERR][Gargantext] Empty list for " <> s <> " in "
260 head' :: Text -> [a] -> a
261 head' = listSafe1 "head" headMay
263 last' :: Text -> [a] -> a
264 last' = listSafe1 "last" lastMay
266 ------------------------------------------------------------------------
268 listSafeN :: Text -> ([a] -> Maybe [a])
269 -> Text -> [a] -> [a]
270 listSafeN s f e xs = maybe (panic $ h <> e) identity (f xs)
272 h = "[ERR][Gargantext] Empty list for " <> s <> " in "
274 tail' :: Text -> [a] -> [a]
275 tail' = listSafeN "tail" tailMay
277 init' :: Text -> [a] -> [a]
278 init' = listSafeN "init" initMay
280 ------------------------------------------------------------------------
281 --- Some Statistics sugar functions
282 -- Exponential Average
283 eavg :: [Double] -> Double
284 eavg (x:xs) = a*x + (1-a)*(eavg xs)
289 mean :: Fractional a => [a] -> a
290 mean xs = sum xs / fromIntegral (length xs)
292 sumMaybe :: Num a => [Maybe a] -> Maybe a
293 sumMaybe = fmap sum . M.sequence
295 variance :: Floating a => [a] -> a
296 variance xs = sum ys / (fromIntegral (length xs) - 1)
299 ys = map (\x -> (x - m) ** 2) xs
301 deviation :: Floating a => [a] -> a
302 deviation = sqrt . variance
304 movingAverage :: (Eq b, Fractional b) => Int -> [b] -> [b]
305 movingAverage steps xs = map mean $ chunkAlong steps 1 xs
307 -----------------------------------------------------------------------
308 -----------------------------------------------------------------------
309 --- Map in Map = Map2
310 -- To avoid Map (a,a) b
311 type Map2 a b = Map a (Map a b)
322 -----------------------------------------------------------------------
323 foldM' :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
324 foldM' _ z [] = return z
325 foldM' f z (x:xs) = do
327 z' `seq` foldM' f z' xs
329 -----------------------------------------------------------------------
330 -- | Instance for basic numerals
331 -- See the difference between Double and (Int Or Integer)
332 instance Monoid Double where
335 instance Semigroup Double where
339 instance Monoid Int where
342 instance Semigroup Int where
345 instance Monoid Integer where
348 instance Semigroup Integer where
351 ------------------------------------------------------------------------
353 hasDuplicates :: Ord a => [a] -> Bool
354 hasDuplicates = hasDuplicatesWith Set.empty
356 hasDuplicatesWith :: Ord a => Set a -> [a] -> Bool
357 hasDuplicatesWith _seen [] =
358 False -- base case: empty lists never contain duplicates
359 hasDuplicatesWith seen (x:xs) =
360 -- If we have seen the current item before, we can short-circuit; otherwise,
361 -- we'll add it the the set of previously seen items and process the rest of the
362 -- list against that.
363 x `Set.member` seen || hasDuplicatesWith (Set.insert x seen) xs