packages feed

javelin 0.1.1.0 → 0.1.2.0

raw patch · 26 files changed

+7617/−7586 lines, 26 filesdep ~basedep ~deepseqPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, deepseq

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,10 +1,14 @@-# Revision history for javelin
-
-## Release 0.1.1.0
-
-* Added the `Data.Series.Index.indexed` function
-* Replace all INLINE pragmas for INLINABLE, which will improve compilation speed and performance.
-
-## Release 0.1.0.0
-
-* This is the first version of `javelin` and associated packages.
+# Revision history for javelin++## Release 0.1.2.0++* Fixed an issue where `Series` could be corrupted while using `aggregateWith`.++## Release 0.1.1.0++* Added the `Data.Series.Index.indexed` function+* Replace all INLINE pragmas for INLINABLE, which will improve compilation speed and performance.++## Release 0.1.0.0++* This is the first version of `javelin` and associated packages.
LICENSE view
@@ -1,20 +1,20 @@-Copyright (c) Laurent P. René de Cotret
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be included
-in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+Copyright (c) Laurent P. René de Cotret++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
benchmarks/Comparison.hs view
@@ -1,201 +1,201 @@--- This benchmarking script is forked from
--- https://github.com/haskell-perf/dictionaries/blob/master/Time.hs
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-{-# LANGUAGE BangPatterns              #-}
-{-# LANGUAGE ExistentialQuantification #-}
-
-module Main (main) where
-
-import           Control.DeepSeq  ( NFData, force )
-import qualified Control.Foldl    as Fold
-import           Control.Monad    ( when )
-import           Criterion.Main   ( defaultMainWith, defaultConfig, bench, bgroup, env, nf )
-import           Criterion.Types  ( Config(csvFile) )
-import           Data.List        ( foldl' )
-import qualified Data.Map.Lazy
-import qualified Data.Map.Strict
-import           Data.MonoTraversable ( ofoldlUnwrap )
-import           Data.Set         ( Set )
-import qualified Data.Set         as Set 
-import qualified Data.Series
-import qualified Data.Series.Unboxed
-import qualified Data.Series.Index as Index
-import qualified Data.Vector
-import qualified Data.Vector.Unboxed
-import           System.Directory ( doesFileExist, removeFile )
-import           System.Random    ( mkStdGen, Random(randoms) )
-
-data Lookup =
-  forall f. (NFData (f Int)) =>
-            Lookup String
-                   ([(Int, Int)] -> f Int)
-                   (Int -> f Int ->  Maybe Int)
-
-data Sum =
-  forall f. (NFData (f Int)) =>
-            Sum String ([(Int, Int)] -> f Int) (f Int -> Int)
-
-data Fold =
-  forall f. (NFData (f Double)) =>
-            Fold String ([(Int, Double)] -> f Double) (f Double -> Double)
-
-data Mappend = 
-  forall f. (NFData (f Int), Monoid (f Int)) =>
-            Mappend String 
-                   ([(Int, Int)] -> f Int)
-
-data SliceByKeys =
-  forall f. (NFData (f Int), Monoid (f Int)) =>
-            SliceByKeys String 
-                   ([(Int, Int)] -> f Int)
-                   (Set Int -> f Int -> f Int)
-
-
-
-
-main :: IO ()
-main = do
-  let fp = "out.csv"
-  exists <- doesFileExist fp
-  when exists (removeFile fp)
-  defaultMainWith
-    defaultConfig {csvFile = Just fp}
-    [ bgroup
-        "Lookup Int (Randomized)"
-        (lookupRandomized
-           [ Lookup "Data.Map.Lazy" Data.Map.Lazy.fromList Data.Map.Lazy.lookup
-           , Lookup
-               "Data.Map.Strict"
-               Data.Map.Strict.fromList
-               Data.Map.Strict.lookup
-           , Lookup
-               "Data.Series"
-               Data.Series.fromList
-               (flip Data.Series.at)
-           , Lookup 
-                "Data.Vector"
-                (Data.Vector.fromList . map fst)
-                (\ix -> Data.Vector.find (==ix))
-           , Lookup
-               "Data.Series.Unboxed"
-               Data.Series.Unboxed.fromList
-               (flip Data.Series.Unboxed.at)
-           , Lookup 
-                "Data.Vector.Unboxed"
-                (Data.Vector.Unboxed.fromList . map fst)
-                (\ix -> Data.Vector.Unboxed.find (==ix))
-           ])
-    , bgroup
-        "Sum Int (Randomized)"
-        (sumRandomized
-           [ Sum "Data.Map.Lazy"   Data.Map.Lazy.fromList sum
-           , Sum "Data.Map.Strict" Data.Map.Strict.fromList sum
-           , Sum "Data.Series" Data.Series.fromList sum
-           , Sum "Data.Vector" (Data.Vector.fromList . map snd) sum
-           , Sum "Data.Series.Unboxed"  Data.Series.Unboxed.fromList Data.Series.Unboxed.sum
-           , Sum "Data.Vector.Unboxed" (Data.Vector.Unboxed.fromList . map snd) Data.Vector.Unboxed.sum
-           ])
-    , bgroup
-        "Fold mean (Randomized)"
-        (foldRandomized
-           [ Fold "Data.Map.Lazy"   Data.Map.Lazy.fromList (Fold.fold Fold.mean)
-           , Fold "Data.Map.Strict" Data.Map.Strict.fromList (Fold.fold Fold.mean)
-           , Fold "Data.Series" Data.Series.fromList (Data.Series.fold Fold.mean)
-           , Fold "Data.Vector" (Data.Vector.fromList . map snd) (Fold.fold Fold.mean)
-           , Fold "Data.Series.Unboxed"  Data.Series.Unboxed.fromList (Data.Series.Unboxed.fold Fold.mean)
-           , Fold "Data.Vector.Unboxed" (Data.Vector.Unboxed.fromList . map snd) (Fold.purely ofoldlUnwrap Fold.mean)
-           ])
-    , bgroup
-      "Mappend Int (Randomized)"
-      ( mappendRandomized 
-          [ Mappend "Data.Map.Lazy" Data.Map.Lazy.fromList
-          , Mappend "Data.Map.Strict" Data.Map.Strict.fromList
-          , Mappend "Data.Series" Data.Series.fromList
-          , Mappend "Data.Vector" (Data.Vector.fromList . map snd)
-          , Mappend "Data.Series.Unboxed" Data.Series.Unboxed.fromList
-          , Mappend "Data.Vector.Unboxed" (Data.Vector.Unboxed.fromList . map snd)
-          ])
-    , bgroup
-      "Slice by keys (Randomized)"
-      ( sliceByKeyRandomized 
-          [ SliceByKeys "Data.Map.Lazy" 
-                        Data.Map.Lazy.fromList
-                        (flip Data.Map.Lazy.restrictKeys)
-          , SliceByKeys "Data.Map.Strict" 
-                        Data.Map.Strict.fromList
-                        (flip Data.Map.Strict.restrictKeys)
-          , SliceByKeys "Data.Series" 
-                        Data.Series.fromList
-                        (\ks xs -> xs `Data.Series.select` Index.fromSet ks)
-          , SliceByKeys "Data.Series.Unboxed" 
-                        Data.Series.Unboxed.fromList
-                        (\ks xs -> xs `Data.Series.Unboxed.select` Index.fromSet ks)
-          ])
-    ]
-
-  where
-    lookupRandomized funcs =
-      [ env
-        (let list = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])
-             !elems = force (fromList list)
-          in pure (list, elems))
-        (\(~(list, elems)) ->
-           bench (title ++ ":" ++ show i) $
-           nf
-             (foldl'
-                  (\_ k ->
-                     case func k elems of
-                       Just !v -> v
-                       Nothing -> 0)
-                  0)
-             (map fst list))
-      | i <- [10, 100, 1000, 10000]
-      , Lookup title fromList func <- funcs
-      ]
-    sumRandomized funcs =
-      [ env
-        (let list = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])
-             !elems = force (fromList list)
-          in pure (list, elems))
-        (\(~(_, elems)) ->
-           bench (title ++ ":" ++ show i) $
-           nf func elems)
-      | i <- [10, 100, 1000, 10000, 100000, 1000000]
-      , Sum title fromList func <- funcs
-      ]
-    foldRandomized funcs =
-      [ env
-        (let list = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])
-             !elems = force (fromList list)
-          in pure (list, elems))
-        (\(~(_, elems)) ->
-           bench (title ++ ":" ++ show i) $
-           nf func elems)
-      | i <- [10, 100, 1000, 10000, 100000, 1000000]
-      , Fold title fromList func <- funcs
-      ]
-    mappendRandomized funcs =
-      [ env
-        (let list1 = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])
-             list2 = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])
-             !elems1 = force (fromList list1)
-             !elems2 = force (fromList list2)
-          in pure (elems1, elems2))
-        (\(~(elems1, elems2)) ->
-           bench (title ++ ":" ++ show i) $
-           nf mconcat [elems1, elems2])
-      | i <- [10, 100, 1000, 10000, 100000, 1000000]
-      , Mappend title fromList <- funcs
-      ]
-    sliceByKeyRandomized funcs = 
-      [ env
-        (let list = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])
-             keys  = Set.fromList $ take (round ((fromIntegral i / 10) :: Double)) (randoms (mkStdGen 0) :: [Int])
-             !elems = force (fromList list)
-          in pure (keys, elems))
-        (\(~(keys, elems)) ->
-           bench (title ++ ":" ++ show i) $
-           nf (slice keys) elems)
-      | i <- [10, 100, 1000, 10000, 100000, 1000000]
-      , SliceByKeys title fromList slice <- funcs
+-- This benchmarking script is forked from+-- https://github.com/haskell-perf/dictionaries/blob/master/Time.hs+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE BangPatterns              #-}+{-# LANGUAGE ExistentialQuantification #-}++module Main (main) where++import           Control.DeepSeq  ( NFData, force )+import qualified Control.Foldl    as Fold+import           Control.Monad    ( when )+import           Criterion.Main   ( defaultMainWith, defaultConfig, bench, bgroup, env, nf )+import           Criterion.Types  ( Config(csvFile) )+import           Data.List        ( foldl' )+import qualified Data.Map.Lazy+import qualified Data.Map.Strict+import           Data.MonoTraversable ( ofoldlUnwrap )+import           Data.Set         ( Set )+import qualified Data.Set         as Set +import qualified Data.Series+import qualified Data.Series.Unboxed+import qualified Data.Series.Index as Index+import qualified Data.Vector+import qualified Data.Vector.Unboxed+import           System.Directory ( doesFileExist, removeFile )+import           System.Random    ( mkStdGen, Random(randoms) )++data Lookup =+  forall f. (NFData (f Int)) =>+            Lookup String+                   ([(Int, Int)] -> f Int)+                   (Int -> f Int ->  Maybe Int)++data Sum =+  forall f. (NFData (f Int)) =>+            Sum String ([(Int, Int)] -> f Int) (f Int -> Int)++data Fold =+  forall f. (NFData (f Double)) =>+            Fold String ([(Int, Double)] -> f Double) (f Double -> Double)++data Mappend = +  forall f. (NFData (f Int), Monoid (f Int)) =>+            Mappend String +                   ([(Int, Int)] -> f Int)++data SliceByKeys =+  forall f. (NFData (f Int), Monoid (f Int)) =>+            SliceByKeys String +                   ([(Int, Int)] -> f Int)+                   (Set Int -> f Int -> f Int)+++++main :: IO ()+main = do+  let fp = "out.csv"+  exists <- doesFileExist fp+  when exists (removeFile fp)+  defaultMainWith+    defaultConfig {csvFile = Just fp}+    [ bgroup+        "Lookup Int (Randomized)"+        (lookupRandomized+           [ Lookup "Data.Map.Lazy" Data.Map.Lazy.fromList Data.Map.Lazy.lookup+           , Lookup+               "Data.Map.Strict"+               Data.Map.Strict.fromList+               Data.Map.Strict.lookup+           , Lookup+               "Data.Series"+               Data.Series.fromList+               (flip Data.Series.at)+           , Lookup +                "Data.Vector"+                (Data.Vector.fromList . map fst)+                (\ix -> Data.Vector.find (==ix))+           , Lookup+               "Data.Series.Unboxed"+               Data.Series.Unboxed.fromList+               (flip Data.Series.Unboxed.at)+           , Lookup +                "Data.Vector.Unboxed"+                (Data.Vector.Unboxed.fromList . map fst)+                (\ix -> Data.Vector.Unboxed.find (==ix))+           ])+    , bgroup+        "Sum Int (Randomized)"+        (sumRandomized+           [ Sum "Data.Map.Lazy"   Data.Map.Lazy.fromList sum+           , Sum "Data.Map.Strict" Data.Map.Strict.fromList sum+           , Sum "Data.Series" Data.Series.fromList sum+           , Sum "Data.Vector" (Data.Vector.fromList . map snd) sum+           , Sum "Data.Series.Unboxed"  Data.Series.Unboxed.fromList Data.Series.Unboxed.sum+           , Sum "Data.Vector.Unboxed" (Data.Vector.Unboxed.fromList . map snd) Data.Vector.Unboxed.sum+           ])+    , bgroup+        "Fold mean (Randomized)"+        (foldRandomized+           [ Fold "Data.Map.Lazy"   Data.Map.Lazy.fromList (Fold.fold Fold.mean)+           , Fold "Data.Map.Strict" Data.Map.Strict.fromList (Fold.fold Fold.mean)+           , Fold "Data.Series" Data.Series.fromList (Data.Series.fold Fold.mean)+           , Fold "Data.Vector" (Data.Vector.fromList . map snd) (Fold.fold Fold.mean)+           , Fold "Data.Series.Unboxed"  Data.Series.Unboxed.fromList (Data.Series.Unboxed.fold Fold.mean)+           , Fold "Data.Vector.Unboxed" (Data.Vector.Unboxed.fromList . map snd) (Fold.purely ofoldlUnwrap Fold.mean)+           ])+    , bgroup+      "Mappend Int (Randomized)"+      ( mappendRandomized +          [ Mappend "Data.Map.Lazy" Data.Map.Lazy.fromList+          , Mappend "Data.Map.Strict" Data.Map.Strict.fromList+          , Mappend "Data.Series" Data.Series.fromList+          , Mappend "Data.Vector" (Data.Vector.fromList . map snd)+          , Mappend "Data.Series.Unboxed" Data.Series.Unboxed.fromList+          , Mappend "Data.Vector.Unboxed" (Data.Vector.Unboxed.fromList . map snd)+          ])+    , bgroup+      "Slice by keys (Randomized)"+      ( sliceByKeyRandomized +          [ SliceByKeys "Data.Map.Lazy" +                        Data.Map.Lazy.fromList+                        (flip Data.Map.Lazy.restrictKeys)+          , SliceByKeys "Data.Map.Strict" +                        Data.Map.Strict.fromList+                        (flip Data.Map.Strict.restrictKeys)+          , SliceByKeys "Data.Series" +                        Data.Series.fromList+                        (\ks xs -> xs `Data.Series.select` Index.fromSet ks)+          , SliceByKeys "Data.Series.Unboxed" +                        Data.Series.Unboxed.fromList+                        (\ks xs -> xs `Data.Series.Unboxed.select` Index.fromSet ks)+          ])+    ]++  where+    lookupRandomized funcs =+      [ env+        (let list = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])+             !elems = force (fromList list)+          in pure (list, elems))+        (\(~(list, elems)) ->+           bench (title ++ ":" ++ show i) $+           nf+             (foldl'+                  (\_ k ->+                     case func k elems of+                       Just !v -> v+                       Nothing -> 0)+                  0)+             (map fst list))+      | i <- [10, 100, 1000, 10000]+      , Lookup title fromList func <- funcs+      ]+    sumRandomized funcs =+      [ env+        (let list = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])+             !elems = force (fromList list)+          in pure (list, elems))+        (\(~(_, elems)) ->+           bench (title ++ ":" ++ show i) $+           nf func elems)+      | i <- [10, 100, 1000, 10000, 100000, 1000000]+      , Sum title fromList func <- funcs+      ]+    foldRandomized funcs =+      [ env+        (let list = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])+             !elems = force (fromList list)+          in pure (list, elems))+        (\(~(_, elems)) ->+           bench (title ++ ":" ++ show i) $+           nf func elems)+      | i <- [10, 100, 1000, 10000, 100000, 1000000]+      , Fold title fromList func <- funcs+      ]+    mappendRandomized funcs =+      [ env+        (let list1 = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])+             list2 = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])+             !elems1 = force (fromList list1)+             !elems2 = force (fromList list2)+          in pure (elems1, elems2))+        (\(~(elems1, elems2)) ->+           bench (title ++ ":" ++ show i) $+           nf mconcat [elems1, elems2])+      | i <- [10, 100, 1000, 10000, 100000, 1000000]+      , Mappend title fromList <- funcs+      ]+    sliceByKeyRandomized funcs = +      [ env+        (let list = take i (zip (randoms (mkStdGen 0) :: [Int]) [1 ..])+             keys  = Set.fromList $ take (round ((fromIntegral i / 10) :: Double)) (randoms (mkStdGen 0) :: [Int])+             !elems = force (fromList list)+          in pure (keys, elems))+        (\(~(keys, elems)) ->+           bench (title ++ ":" ++ show i) $+           nf (slice keys) elems)+      | i <- [10, 100, 1000, 10000, 100000, 1000000]+      , SliceByKeys title fromList slice <- funcs       ]
benchmarks/Operations.hs view
@@ -1,70 +1,70 @@-
-import           Control.DeepSeq    ( rnf )
-import           Control.Exception  ( evaluate )
-import           Criterion.Main     ( bench, whnf, defaultMain )
-
-import           Data.Foldable      ( Foldable(foldl') )
-import           Data.Set           ( Set )     
-import qualified Data.Set           as Set
-import           Data.Series        ( Series )
-import qualified Data.Series        as Series
-import qualified Data.Series.Index  as Index
-
-
-main :: IO ()
-main = do
-    let srs1        = Series.fromList $ zip [0..] [1::Int .. 2^(12::Int)]
-        srs2        = Series.fromList $ zip [0,2..] [1::Int .. 2^(12::Int)]
-        elems      = Index.toSet $ Series.index srs1
-        small      = Set.fromAscList  [1::Int ..  2^(8::Int)]
-        elems_even = Set.fromDistinctAscList  [2::Int, 4..2^(12::Int)]
-        elems_odd  = Set.fromDistinctAscList  [1::Int, 3..2^(12::Int)]
-    evaluate $ rnf [elems, small, elems_even, elems_odd]
-    evaluate $ rnf [srs1, srs2]
-    defaultMain
-        [ bench "at" $ whnf (at elems_even) srs1
-        , bench "iat" $ whnf (iat elems_even) srs1
-        , bench "select" $ whnf (select elems_odd) srs1
-        , bench "mappend" $ whnf (mappend' srs1) srs2
-        , bench "zipWithMatched" $ whnf (zipWithMatched srs1) srs1
-        , bench "group by ... aggregate with ..." $ whnf (groupbyagg small) srs1
-        , bench "group by ... fold with ..." $ whnf (groupbyfold small) srs1
-        ]
-
-at :: Set Int -> Series Int Int -> Int
-at xs s = foldl' go 0 xs
-    where
-        go n x = case s `Series.at` x of 
-            Just _ -> n + 1
-            Nothing -> n
-
-iat :: Set Int -> Series Int Int -> Int
-iat xs s = foldl' go 0 xs
-    where
-        go n x = case s `Series.iat` x of 
-            Just _ -> n + 1
-            Nothing -> n
-  
-select :: Set Int -> Series Int Int -> Int
-select ks s = foldl' go 0 ks
-    where
-        go n k = n + length (s `Series.select` ((k-100) `Series.to` (k+100)))
-
-
-mappend' :: Series Int Int -> Series Int Int -> Int
-mappend' xs ys = sum $ xs <> ys
-
-
-zipWithMatched :: Series Int Int -> Series Int Int -> Int
-zipWithMatched xs ys = length $ Series.zipWithMatched (+) xs ys
-
-
-groupbyagg :: Set Int -> Series Int Int -> Int
-groupbyagg ks s = foldl' go 0 ks
-    where
-        go n k = n + product (s `Series.groupBy` (`mod` (k + 1)) `Series.aggregateWith` sum)
-
-groupbyfold :: Set Int -> Series Int Int -> Int
-groupbyfold ks s = foldl' go 0 ks
-    where
++import           Control.DeepSeq    ( rnf )+import           Control.Exception  ( evaluate )+import           Criterion.Main     ( bench, whnf, defaultMain )++import           Data.Foldable      ( Foldable(foldl') )+import           Data.Set           ( Set )     +import qualified Data.Set           as Set+import           Data.Series        ( Series )+import qualified Data.Series        as Series+import qualified Data.Series.Index  as Index+++main :: IO ()+main = do+    let srs1        = Series.fromList $ zip [0..] [1::Int .. 2^(12::Int)]+        srs2        = Series.fromList $ zip [0,2..] [1::Int .. 2^(12::Int)]+        elems      = Index.toSet $ Series.index srs1+        small      = Set.fromAscList  [1::Int ..  2^(8::Int)]+        elems_even = Set.fromDistinctAscList  [2::Int, 4..2^(12::Int)]+        elems_odd  = Set.fromDistinctAscList  [1::Int, 3..2^(12::Int)]+    evaluate $ rnf [elems, small, elems_even, elems_odd]+    evaluate $ rnf [srs1, srs2]+    defaultMain+        [ bench "at" $ whnf (at elems_even) srs1+        , bench "iat" $ whnf (iat elems_even) srs1+        , bench "select" $ whnf (select elems_odd) srs1+        , bench "mappend" $ whnf (mappend' srs1) srs2+        , bench "zipWithMatched" $ whnf (zipWithMatched srs1) srs1+        , bench "group by ... aggregate with ..." $ whnf (groupbyagg small) srs1+        , bench "group by ... fold with ..." $ whnf (groupbyfold small) srs1+        ]++at :: Set Int -> Series Int Int -> Int+at xs s = foldl' go 0 xs+    where+        go n x = case s `Series.at` x of +            Just _ -> n + 1+            Nothing -> n++iat :: Set Int -> Series Int Int -> Int+iat xs s = foldl' go 0 xs+    where+        go n x = case s `Series.iat` x of +            Just _ -> n + 1+            Nothing -> n+  +select :: Set Int -> Series Int Int -> Int+select ks s = foldl' go 0 ks+    where+        go n k = n + length (s `Series.select` ((k-100) `Series.to` (k+100)))+++mappend' :: Series Int Int -> Series Int Int -> Int+mappend' xs ys = sum $ xs <> ys+++zipWithMatched :: Series Int Int -> Series Int Int -> Int+zipWithMatched xs ys = length $ Series.zipWithMatched (+) xs ys+++groupbyagg :: Set Int -> Series Int Int -> Int+groupbyagg ks s = foldl' go 0 ks+    where+        go n k = n + product (s `Series.groupBy` (`mod` (k + 1)) `Series.aggregateWith` sum)++groupbyfold :: Set Int -> Series Int Int -> Int+groupbyfold ks s = foldl' go 0 ks+    where         go n k = n + product (s `Series.groupBy` (`mod` (k + 1)) `Series.foldWith` (+))
javelin.cabal view
@@ -1,136 +1,136 @@-cabal-version:      3.0
-name:               javelin
-version:            0.1.1.0
-synopsis:           Labeled one-dimensional arrays
-license:            MIT
-license-file:       LICENSE
-author:             Laurent P. René de Cotret
-maintainer:         laurent.decotret@outlook.com
-category:           Data, Data Structures, Data Science
-build-type:         Simple
-extra-doc-files:    CHANGELOG.md
-                    files/aapl.txt
-tested-with:        GHC ==9.8.1 
-                     || ==9.6.3
-                     || ==9.4.7  
-description:
-        
-        This package implements 'Series', labeled one-dimensional arrays
-        combining properties from maps and arrays.
-        
-        To get started, the important modules are:
-        
-        ["Data.Series"] Boxed series of arbitrary types.
-        
-        ["Data.Series.Unboxed"] Series of unboxed data types for better performance, at the cost of flexibility.
-        
-        ["Data.Series.Generic"] Generic interface to manipulate any type of 'Series'.
-        
-        ["Data.Series.Index"] Index containing series keys.
-        
-        To get started, please take a look at the tutorial ("Data.Series.Tutorial").
-        
-
-common common
-    default-language: GHC2021
-    ghc-options: -Wall
-                 -Wcompat
-                 -Widentities
-                 -Wincomplete-uni-patterns
-                 -Wincomplete-record-updates
-                 -Wredundant-constraints
-                 -fhide-source-paths
-                 -Wpartial-fields
-
-library
-    import:           common
-    hs-source-dirs:   src
-    exposed-modules:  Data.Series
-                      Data.Series.Generic
-                      Data.Series.Generic.Internal
-                      Data.Series.Index
-                      Data.Series.Index.Internal
-                      Data.Series.Tutorial
-                      Data.Series.Unboxed
-    other-modules:    Data.Series.Generic.Aggregation
-                      Data.Series.Generic.Definition
-                      Data.Series.Generic.Scans
-                      Data.Series.Generic.View
-                      Data.Series.Generic.Zip
-                      Data.Series.Index.Definition
-    build-depends:    base                >=4.15.0.0 && <4.20,
-                      containers          >=0.6      && <0.8,
-                      deepseq             >=1.4      && <1.6,
-                      foldl               ^>=1.4,
-                      indexed-traversable ^>=0.1,
-                      vector              >=0.12.3.0 && <0.14,
-                      vector-algorithms   ^>=0.9
-
-test-suite javelin-test
-    import:           common
-    type:             exitcode-stdio-1.0
-    hs-source-dirs:   test
-    main-is:          Main.hs
-    other-modules:    Test.Data.Series
-                      Test.Data.Series.Index
-                      Test.Data.Series.Generic.Aggregation
-                      Test.Data.Series.Generic.Definition
-                      Test.Data.Series.Generic.View
-                      Test.Data.Series.Generic.Zip
-    build-depends:    base,
-                      containers,
-                      foldl,
-                      hedgehog,
-                      HUnit,
-                      javelin,
-                      tasty,
-                      tasty-hedgehog,
-                      tasty-hspec,
-                      tasty-hunit,
-                      vector
-
-
--- Running the 'comparison-containers' benchmark is expected
--- to be done in conjunction with the cabal.project.profiling project file:
--- > cabal bench comparison-containers --project=cabal.project.profiling
-benchmark comparison-containers
-    import:           common
-    type:             exitcode-stdio-1.0
-    ghc-options:      -rtsopts
-    hs-source-dirs:   benchmarks
-    main-is:          Comparison.hs
-    build-depends:    base,
-                      containers,
-                      foldl,
-                      mono-traversable,
-                      javelin,
-                      vector, 
-                      criterion, 
-                      deepseq, 
-                      random, 
-                      directory
-
-
--- Running the 'operations' benchmark is expected
--- to be done in conjunction with the cabal.project.profiling project file:
--- > cabal bench operations --project=cabal.project.profiling
-benchmark operations
-    import:           common
-    type:             exitcode-stdio-1.0
-    ghc-options:      -rtsopts
-    hs-source-dirs:   benchmarks
-    main-is:          Operations.hs
-    build-depends:    base,
-                      containers,
-                      deepseq,
-                      foldl,
-                      javelin,
-                      criterion
-
-
-executable bench-report
-    import:           common
-    main-is:          bench-report.hs
-    hs-source-dirs:   scripts
-    build-depends:    base, 
-                      csv ^>=0.1
+cabal-version:      3.0+name:               javelin+version:            0.1.2.0+synopsis:           Labeled one-dimensional arrays+license:            MIT+license-file:       LICENSE+author:             Laurent P. René de Cotret+maintainer:         laurent.decotret@outlook.com+category:           Data, Data Structures, Data Science+build-type:         Simple+extra-doc-files:    CHANGELOG.md+                    files/aapl.txt+tested-with:        GHC ==9.8.1 +                     || ==9.6.3+                     || ==9.4.7  +description:+        +        This package implements 'Series', labeled one-dimensional arrays+        combining properties from maps and arrays.+        +        To get started, the important modules are:+        +        ["Data.Series"] Boxed series of arbitrary types.+        +        ["Data.Series.Unboxed"] Series of unboxed data types for better performance, at the cost of flexibility.+        +        ["Data.Series.Generic"] Generic interface to manipulate any type of 'Series'.+        +        ["Data.Series.Index"] Index containing series keys.+        +        To get started, please take a look at the tutorial ("Data.Series.Tutorial").+        ++common common+    default-language: GHC2021+    ghc-options: -Wall+                 -Wcompat+                 -Widentities+                 -Wincomplete-uni-patterns+                 -Wincomplete-record-updates+                 -Wredundant-constraints+                 -fhide-source-paths+                 -Wpartial-fields++library+    import:           common+    hs-source-dirs:   src+    exposed-modules:  Data.Series+                      Data.Series.Generic+                      Data.Series.Generic.Internal+                      Data.Series.Index+                      Data.Series.Index.Internal+                      Data.Series.Tutorial+                      Data.Series.Unboxed+    other-modules:    Data.Series.Generic.Aggregation+                      Data.Series.Generic.Definition+                      Data.Series.Generic.Scans+                      Data.Series.Generic.View+                      Data.Series.Generic.Zip+                      Data.Series.Index.Definition+    build-depends:    base                >=4.15.0.0 && <4.20,+                      containers          >=0.6      && <0.8,+                      deepseq             >=1.4      && <1.6,+                      foldl               ^>=1.4,+                      indexed-traversable ^>=0.1,+                      vector              >=0.12.3.0 && <0.14,+                      vector-algorithms   ^>=0.9++test-suite javelin-test+    import:           common+    type:             exitcode-stdio-1.0+    hs-source-dirs:   test+    main-is:          Main.hs+    other-modules:    Test.Data.Series+                      Test.Data.Series.Index+                      Test.Data.Series.Generic.Aggregation+                      Test.Data.Series.Generic.Definition+                      Test.Data.Series.Generic.View+                      Test.Data.Series.Generic.Zip+    build-depends:    base,+                      containers,+                      foldl,+                      hedgehog,+                      HUnit,+                      javelin,+                      tasty,+                      tasty-hedgehog,+                      tasty-hspec,+                      tasty-hunit,+                      vector+++-- Running the 'comparison-containers' benchmark is expected+-- to be done in conjunction with the cabal.project.profiling project file:+-- > cabal bench comparison-containers --project=cabal.project.profiling+benchmark comparison-containers+    import:           common+    type:             exitcode-stdio-1.0+    ghc-options:      -rtsopts+    hs-source-dirs:   benchmarks+    main-is:          Comparison.hs+    build-depends:    base,+                      containers,+                      foldl,+                      mono-traversable,+                      javelin,+                      vector, +                      criterion, +                      deepseq, +                      random, +                      directory+++-- Running the 'operations' benchmark is expected+-- to be done in conjunction with the cabal.project.profiling project file:+-- > cabal bench operations --project=cabal.project.profiling+benchmark operations+    import:           common+    type:             exitcode-stdio-1.0+    ghc-options:      -rtsopts+    hs-source-dirs:   benchmarks+    main-is:          Operations.hs+    build-depends:    base,+                      containers,+                      deepseq,+                      foldl,+                      javelin,+                      criterion+++executable bench-report+    import:           common+    main-is:          bench-report.hs+    hs-source-dirs:   scripts+    build-depends:    base, +                      csv ^>=0.1
scripts/bench-report.hs view
@@ -1,100 +1,100 @@--- This script has been forked from:
--- https://github.com/haskell-perf/sets/blob/master/Report.hs
-{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
-module Main (main) where
-
-import Data.Function        ( on )
-import Data.List            ( groupBy, intercalate, nub )
-import System.Environment   ( getArgs )
-import Text.CSV             ( parseCSVFromFile )
-import Text.Printf          ( printf )
-
-main :: IO ()
-main = do
-  from:to:_ <- getArgs
-  reportFromCsv from to
-
-reportFromCsv :: FilePath -> FilePath -> IO ()
-reportFromCsv from to = do
-  result <- parseCSVFromFile from
-  case result of
-    Right (_:rows) -> do
-      writeFile to
-        (unlines
-           (map
-              format
-              (filter
-                 (not . all (all null))
-                 (groupBy (on (==) (takeWhile (/= '/') . concat . take 1)) rows))))
-    _ -> error "Couldn't parse csv"
-
-format :: [[String]] -> String
-format rows =
-  ("## " ++ takeWhile (/= '/') (concat (concat (take 1 (drop 1 rows))))) ++
-  "\n\n" ++
-  unlines
-    [ "|Name|" ++ intercalate "|" scales ++ "|"
-    , "|" ++ concat (replicate (1 + length scales) "---|")
-    ] ++
-  unlines
-    (map
-       (\name ->
-          "|" ++ name ++ "|" ++ intercalate "|" (valuesByName name) ++ "|")
-       names)
-  where
-    valuesByName name =
-      map
-        (\row@(_:avg:_) ->
-           let scale = rowScale row
-           in float (valuesByScale scale) (read avg))
-        (filter ((== name) . rowName) rows)
-    valuesByScale scale =
-      map (\(_:avg:_) -> read avg) (filter ((== scale) . rowScale) rows)
-    names = nub (map rowName rows)
-    scales = nub (map rowScale rows)
-    rowName row =
-      let s =
-            takeWhile
-              (/= ':')
-              (dropWhile (== '/') (dropWhile (/= '/') (concat (take 1 row))))
-      in s
-    rowScale row =
-      let scale = dropWhile (== ':') (dropWhile (/= ':') (concat (take 1 row)))
-      in scale
-
-float :: [Double] -> Double -> String
-float others x = let (scale, ext) = secs (mean others)
-                 in with (x * scale) ext
-
--- | Convert a number of seconds to a string.  The string will consist
--- of four decimal places, followed by a short description of the time
--- units.
-secs :: Double -> (Double, String)
-secs k
-    | k >= 1     = 1    `pair` "s"
-    | k >= 1e-3  = 1e3  `pair` "ms"
-    | k >= 1e-6  = 1e6  `pair` "μs"
-    | k >= 1e-9  = 1e9  `pair` "ns"
-    | k >= 1e-12 = 1e12 `pair` "ps"
-    | k >= 1e-15 = 1e15 `pair` "fs"
-    | k >= 1e-18 = 1e18 `pair` "as"
-    | otherwise = error "Bad scale"
-  where pair= (,)
-
-with :: Double -> String -> String
-with (t :: Double) (u :: String)
-    | t >= 1e9  = printf "%.4g %s" t u
-    | t >= 1e3  = printf "%.0f %s" t u
-    | t >= 1e2  = printf "%.1f %s" t u
-    | t >= 1e1  = printf "%.2f %s" t u
-    | otherwise = printf "%.3f %s" t u
-
--- | Simple rolling average.
-mean :: [Double] -> Double
-mean =
-    snd .
-    foldr
-        (\x (cnt,avg) ->
-              ( cnt + 1
-              , (x + avg * cnt) / (cnt + 1)))
+-- This script has been forked from:+-- https://github.com/haskell-perf/sets/blob/master/Report.hs+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}+module Main (main) where++import Data.Function        ( on )+import Data.List            ( groupBy, intercalate, nub )+import System.Environment   ( getArgs )+import Text.CSV             ( parseCSVFromFile )+import Text.Printf          ( printf )++main :: IO ()+main = do+  from:to:_ <- getArgs+  reportFromCsv from to++reportFromCsv :: FilePath -> FilePath -> IO ()+reportFromCsv from to = do+  result <- parseCSVFromFile from+  case result of+    Right (_:rows) -> do+      writeFile to+        (unlines+           (map+              format+              (filter+                 (not . all (all null))+                 (groupBy (on (==) (takeWhile (/= '/') . concat . take 1)) rows))))+    _ -> error "Couldn't parse csv"++format :: [[String]] -> String+format rows =+  ("## " ++ takeWhile (/= '/') (concat (concat (take 1 (drop 1 rows))))) +++  "\n\n" +++  unlines+    [ "|Name|" ++ intercalate "|" scales ++ "|"+    , "|" ++ concat (replicate (1 + length scales) "---|")+    ] +++  unlines+    (map+       (\name ->+          "|" ++ name ++ "|" ++ intercalate "|" (valuesByName name) ++ "|")+       names)+  where+    valuesByName name =+      map+        (\row@(_:avg:_) ->+           let scale = rowScale row+           in float (valuesByScale scale) (read avg))+        (filter ((== name) . rowName) rows)+    valuesByScale scale =+      map (\(_:avg:_) -> read avg) (filter ((== scale) . rowScale) rows)+    names = nub (map rowName rows)+    scales = nub (map rowScale rows)+    rowName row =+      let s =+            takeWhile+              (/= ':')+              (dropWhile (== '/') (dropWhile (/= '/') (concat (take 1 row))))+      in s+    rowScale row =+      let scale = dropWhile (== ':') (dropWhile (/= ':') (concat (take 1 row)))+      in scale++float :: [Double] -> Double -> String+float others x = let (scale, ext) = secs (mean others)+                 in with (x * scale) ext++-- | Convert a number of seconds to a string.  The string will consist+-- of four decimal places, followed by a short description of the time+-- units.+secs :: Double -> (Double, String)+secs k+    | k >= 1     = 1    `pair` "s"+    | k >= 1e-3  = 1e3  `pair` "ms"+    | k >= 1e-6  = 1e6  `pair` "μs"+    | k >= 1e-9  = 1e9  `pair` "ns"+    | k >= 1e-12 = 1e12 `pair` "ps"+    | k >= 1e-15 = 1e15 `pair` "fs"+    | k >= 1e-18 = 1e18 `pair` "as"+    | otherwise = error "Bad scale"+  where pair= (,)++with :: Double -> String -> String+with (t :: Double) (u :: String)+    | t >= 1e9  = printf "%.4g %s" t u+    | t >= 1e3  = printf "%.0f %s" t u+    | t >= 1e2  = printf "%.1f %s" t u+    | t >= 1e1  = printf "%.2f %s" t u+    | otherwise = printf "%.3f %s" t u++-- | Simple rolling average.+mean :: [Double] -> Double+mean =+    snd .+    foldr+        (\x (cnt,avg) ->+              ( cnt + 1+              , (x + avg * cnt) / (cnt + 1)))         (0, 0)
src/Data/Series.hs view
@@ -1,1361 +1,1361 @@------------------------------------------------------------------------------
--- |
--- Module      :  Data.Series
--- Copyright   :  (c) Laurent P. René de Cotret
--- License     :  MIT
--- Maintainer  :  laurent.decotret@outlook.com
--- Portability :  portable
---
--- This module contains data structures and functions to work with 'Series' capable of holding any Haskell value. 
--- For better performance, at the cost of less flexibility, see the "Data.Series.Unboxed".
---
--- = Introduction to series
---
--- A 'Series' of type @Series k a@ is a labeled array of values of type @a@,
--- indexed by keys of type @k@.
---
--- Like `Data.Map.Strict.Map` from the @containers@ package, 'Series' support efficient:
---
---      * random access by key ( \(O(\log n)\) );
---      * slice by key ( \(O(\log n)\) ).
---
--- Like `Data.Vector.Vector`, they support efficient:
---
---      * random access by index ( \(O(1)\) );
---      * slice by index ( \(O(1)\) );
---      * numerical operations.
---
--- This module re-exports most of the content of "Data.Series.Generic", with type signatures 
--- specialized to the boxed container type `Data.Vector.Vector`.
---
--- For better performance (at the cost of more constraints), especially when it comes to numerical calculations, prefer to
--- use "Data.Series.Unboxed", which contains an implementation of series specialized to the unboxed container type `Data.Vector.Unboxed.Vector`.
- 
-module Data.Series (
-    Series, index, values,
-
-    -- * Building/converting 'Series'
-    singleton, fromIndex,
-    -- ** Lists
-    fromList, toList,
-    -- ** Vectors
-    fromVector, toVector,
-    -- ** Handling duplicates
-    Occurrence, fromListDuplicates, fromVectorDuplicates,
-    -- ** Strict Maps
-    fromStrictMap, toStrictMap,
-    -- ** Lazy Maps
-    fromLazyMap, toLazyMap,
-    -- ** Ad-hoc conversion with other data structures
-    IsSeries(..),
-    -- ** Conversion between 'Series' types
-    G.convert,
-
-    -- * Mapping and filtering
-    map, mapWithKey, mapIndex, concatMap,
-    take, takeWhile, drop, dropWhile, filter, filterWithKey,
-    -- ** Mapping with effects
-    mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_, traverseWithKey,
-
-    -- * Combining series
-    zipWith, zipWithMatched, zipWithKey,
-    zipWith3, zipWithMatched3, zipWithKey3,
-    ZipStrategy, skipStrategy, mapStrategy, constStrategy, zipWithStrategy, zipWithStrategy3,
-    zipWithMonoid, esum, eproduct, unzip, unzip3,
-
-    -- * Index manipulation
-    require, catMaybes, dropIndex,
-
-    -- * Accessors
-    -- ** Bulk access
-    select, selectWhere, Range, to, from, upto, Selection, 
-    -- ** Single-element access
-    at, iat,
-
-    -- * Replacing values
-    replace, (|->), (<-|),
-
-    -- * Scans
-    forwardFill,
-
-    -- * Grouping and windowing operations
-    groupBy, Grouping, aggregateWith, foldWith, 
-    windowing, expanding,
-
-    -- * Folds
-    fold, foldM, foldWithKey, foldMWithKey, foldMapWithKey,
-    -- ** Specialized folds
-    G.mean, G.variance, G.std,
-    length, null, all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn, 
-    argmin, argmax,
-
-    -- * Scans
-    postscanl, prescanl,
-
-    -- * Displaying 'Series'
-    display, displayWith,
-    noLongerThan,
-    DisplayOptions(..), G.defaultDisplayOptions
-) where
-
-import           Control.Foldl       ( Fold, FoldM )
-import qualified Data.Map.Lazy       as ML
-import qualified Data.Map.Strict     as MS
-import           Data.Series.Index   ( Index )
-import           Data.Series.Generic ( IsSeries(..), Range, Selection, ZipStrategy, Occurrence, DisplayOptions(..)
-                                     , to, from, upto, skipStrategy, mapStrategy, constStrategy, noLongerThan
-                                     )
-import qualified Data.Series.Generic as G
-import           Data.Vector         ( Vector )
-
-import           Prelude             hiding ( map, concatMap, zipWith, zipWith3, filter, take, takeWhile, drop, dropWhile, last, unzip, unzip3
-                                            , length, null, all, any, and, or, sum, product, maximum, minimum, 
-                                            )
-
--- $setup
--- >>> import qualified Data.Series as Series
--- >>> import qualified Data.Series.Index as Index
-
-infixl 1 `select` 
-infix 6 |->, <-|
-
--- | A series is a labeled array of values of type @a@,
--- indexed by keys of type @k@.
---
--- Like @Data.Map@ and @Data.HashMap@, they support efficient:
---
---      * random access by key ( \(O(\log n)\) );
---      * slice by key ( \(O(\log n)\) ).
---
--- Like @Data.Vector.Vector@, they support efficient:
---
---      * random access by index ( \(O(1)\) );
---      * slice by index ( \(O(1)\) );
---      * numerical operations.
-type Series = G.Series Vector
-
-
-index :: Series k a -> Index k
-{-# INLINABLE index #-}
-index = G.index
-
-
-values :: Series k a -> Vector a
-{-# INLINABLE values #-}
-values = G.values
-
-
--- | Create a 'Series' with a single element.
-singleton :: k -> a -> Series k a
-{-# INLINABLE singleton #-}
-singleton = G.singleton
-
-
--- | \(O(n)\) Generate a 'Series' by mapping every element of its index.
---
--- >>> fromIndex (const (0::Int)) $ Index.fromList ['a','b','c','d']
--- index | values
--- ----- | ------
---   'a' |      0
---   'b' |      0
---   'c' |      0
---   'd' |      0
-fromIndex :: (k -> a) -> Index k -> Series k a
-{-# INLINABLE fromIndex #-}
-fromIndex = G.fromIndex
-
-
--- | Construct a series from a list of key-value pairs. There is no
--- condition on the order of pairs.
---
--- >>> let xs = fromList [('b', 0::Int), ('a', 5), ('d', 1) ]
--- >>> xs
--- index | values
--- ----- | ------
---   'a' |      5
---   'b' |      0
---   'd' |      1
---
--- If you need to handle duplicate keys, take a look at `fromListDuplicates`.
-fromList :: Ord k => [(k, a)] -> Series k a
-{-# INLINABLE fromList #-}
-fromList = G.fromList
-
-
--- | Construct a series from a list of key-value pairs.
--- Contrary to `fromList`, values at duplicate keys are preserved. To keep each
--- key unique, an `Occurrence` number counts up.
---
--- >>> let xs = fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]
--- >>> xs
---   index | values
---   ----- | ------
--- ('a',0) |      5
--- ('b',0) |      0
--- ('d',0) |      1
--- ('d',1) |     -4
--- ('d',2) |      7
-fromListDuplicates :: Ord k => [(k, a)] -> Series (k, Occurrence) a
-{-# INLINABLE fromListDuplicates #-}
-fromListDuplicates = G.fromListDuplicates
-
-
--- | Construct a list from key-value pairs. The elements are in order sorted by key:
---
--- >>> let xs = Series.fromList [ ('b', 0::Int), ('a', 5), ('d', 1) ]
--- >>> xs
--- index | values
--- ----- | ------
---   'a' |      5
---   'b' |      0
---   'd' |      1
--- >>> toList xs
--- [('a',5),('b',0),('d',1)]
-toList :: Series k a -> [(k, a)]
-{-# INLINABLE toList #-}
-toList = G.toList
-
-
--- | Construct a 'Vector' of key-value pairs. The elements are in order sorted by key. 
-toVector :: Series k a -> Vector (k, a)
-{-# INLINABLE toVector #-}
-toVector = G.toVector
-
-
--- | Construct a 'Series' from a 'Vector' of key-value pairs. There is no
--- condition on the order of pairs. Duplicate keys are silently dropped. If you
--- need to handle duplicate keys, see 'fromVectorDuplicates'.
---
--- Note that due to differences in sorting,
--- @'Series.fromList'@ and @'Series.fromVector' . 'Vector.fromList'@ 
--- may not be equivalent if the input list contains duplicate keys.
-fromVector :: Ord k => Vector (k, a) -> Series k a
-{-# INLINABLE fromVector #-}
-fromVector = G.fromVector
-
-
--- | Construct a series from a 'Vector' of key-value pairs.
--- Contrary to 'fromVector', values at duplicate keys are preserved. To keep each
--- key unique, an 'Occurrence' number counts up.
---
--- >>> import qualified Data.Vector as Vector
--- >>> let xs = fromVectorDuplicates $ Vector.fromList [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]
--- >>> xs
---   index | values
---   ----- | ------
--- ('a',0) |      5
--- ('b',0) |      0
--- ('d',0) |      1
--- ('d',1) |     -4
--- ('d',2) |      7
-fromVectorDuplicates :: Ord k => Vector (k, a) -> Series (k, Occurrence) a
-{-# INLINABLE fromVectorDuplicates #-}
-fromVectorDuplicates = G.fromVectorDuplicates
-
-
--- | Convert a series into a lazy @Map@.
-toLazyMap :: Series k a -> ML.Map k a
-{-# INLINABLE toLazyMap #-}
-toLazyMap = G.toLazyMap
-
-
--- | Construct a series from a lazy @Map@.
-fromLazyMap :: ML.Map k a -> Series k a
-{-# INLINABLE fromLazyMap #-}
-fromLazyMap = G.fromLazyMap
-
-
--- | Convert a series into a strict @Map@.
-toStrictMap :: Series k a -> MS.Map k a
-{-# INLINABLE toStrictMap #-}
-toStrictMap = G.toStrictMap
-
-
--- | Construct a series from a strict @Map@.
-fromStrictMap :: MS.Map k a -> Series k a
-{-# INLINABLE fromStrictMap #-}
-fromStrictMap = G.fromStrictMap
-
-
--- | \(O(n)\) Map every element of a 'Series'.
-map :: (a -> b) -> Series k a -> Series k b
-{-# INLINABLE map #-}
-map = G.map
-
-
--- | \(O(n)\) Map every element of a 'Series', possibly using the key as well.
-mapWithKey :: (k -> a -> b) -> Series k a -> Series k b
-{-# INLINABLE mapWithKey #-}
-mapWithKey = G.mapWithKey
-
-
--- | \(O(n \log n)\).
--- Map each key in the index to another value. Note that the resulting series
--- may have less elements, because each key must be unique.
---
--- In case new keys are conflicting, the first element is kept.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> import qualified Data.List
--- >>> xs `mapIndex` (Data.List.take 1)
--- index | values
--- ----- | ------
---   "L" |      4
---   "P" |      1
-mapIndex :: (Ord k, Ord g) => Series k a -> (k -> g) -> Series g a
-{-# INLINABLE mapIndex #-}
-mapIndex = G.mapIndex
-
-
--- | Map a function over all the elements of a 'Series' and concatenate the result into a single 'Series'.
-concatMap :: Ord k 
-          => (a -> Series k b) 
-          -> Series k a 
-          -> Series k b
-{-# INLINABLE concatMap #-}
-concatMap = G.concatMap
-
-
--- | \(O(n)\) Apply the monadic action to every element of a series and its
--- index, yielding a series of results.
-mapWithKeyM :: (Monad m, Ord k) => (k -> a -> m b) -> Series k a -> m (Series k b)
-{-# INLINABLE mapWithKeyM #-}
-mapWithKeyM = G.mapWithKeyM
-
-
--- | \(O(n)\) Apply the monadic action to every element of a series and its
--- index, discarding the results.
-mapWithKeyM_ :: Monad m => (k -> a -> m b) -> Series k a -> m ()
-{-# INLINABLE mapWithKeyM_ #-}
-mapWithKeyM_ = G.mapWithKeyM_
-
-
--- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, 
--- yielding a series of results.
-forWithKeyM :: (Monad m, Ord k) => Series k a -> (k -> a -> m b) -> m (Series k b)
-{-# INLINABLE forWithKeyM #-}
-forWithKeyM = G.forWithKeyM
-
-
--- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, 
--- discarding the results.
-forWithKeyM_ :: Monad m => Series k a -> (k -> a -> m b) -> m ()
-{-# INLINABLE forWithKeyM_ #-}
-forWithKeyM_ = G.forWithKeyM_
-
-
--- | \(O(n)\) Traverse a 'Series' with an Applicative action, taking into account both keys and values. 
-traverseWithKey :: (Applicative t, Ord k)
-                => (k -> a -> t b) 
-                -> Series k a 
-                -> t (Series k b)
-{-# INLINABLE traverseWithKey #-}
-traverseWithKey = G.traverseWithKey
-
-
--- | \(O(\log n)\) @'take' n xs@ returns at most @n@ elements of the 'Series' @xs@.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- "Vienna" |      5
--- >>> take 2 xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
-take :: Int -> Series k a -> Series k a
-{-# INLINABLE take #-}
-take = G.take
-
-
--- | \(O(n)\) Returns the longest prefix (possibly empty) of the input 'Series' that satisfy a predicate.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- "Vienna" |      5
-
--- >>> takeWhile (>1) xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
-takeWhile :: (a -> Bool) -> Series k a -> Series k a
-takeWhile = G.takeWhile
-
-
--- | \(O(\log n)\) @'drop' n xs@ drops at most @n@ elements from the 'Series' @xs@.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- "Vienna" |      5
--- >>> drop 2 xs
---    index | values
---    ----- | ------
---  "Paris" |      1
--- "Vienna" |      5
-drop :: Int -> Series k a -> Series k a
-{-# INLINABLE drop #-}
-drop = G.drop
-
-
--- | \(O(n)\) Returns the complement of `takeWhile`.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- "Vienna" |      5
-
--- >>> dropWhile (>1) xs
---    index | values
---    ----- | ------
---  "Paris" |      1
--- "Vienna" |      5
-dropWhile :: (a -> Bool) -> Series k a -> Series k a
-dropWhile = G.dropWhile
-
-
--- | Apply a function elementwise to two series, matching elements
--- based on their keys. For keys present only in the left or right series, 
--- the value 'Nothing' is returned.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]
--- >>> zipWith (+) xs ys
---   index |  values
---   ----- |  ------
--- "alpha" | Just 10
---  "beta" | Just 12
--- "delta" | Nothing
--- "gamma" | Nothing
---
--- To only combine elements where keys are in both series, see 'zipWithMatched'.
-zipWith :: (Ord k) 
-        => (a -> b -> c) -> Series k a -> Series k b -> Series k (Maybe c)
-zipWith = G.zipWith 
-{-# INLINABLE zipWith #-}
-
-
-
--- | Apply a function elementwise to three series, matching elements
--- based on their keys. For keys present only in the left or right series, 
--- the value 'Nothing' is returned.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int),  ("beta", 1),   ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11),  ("delta", 13) ]
--- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ]
--- >>> zipWith3 (\x y z -> x + y + z) xs ys zs
---     index |  values
---     ----- |  ------
---   "alpha" | Just 30
---    "beta" | Nothing
---   "delta" | Nothing
--- "epsilon" | Nothing
---   "gamma" | Nothing
---
--- To only combine elements where keys are in all series, see 'zipWithMatched3'
-zipWith3 :: (Ord k) 
-         => (a -> b -> c -> d) 
-         -> Series k a 
-         -> Series k b 
-         -> Series k c 
-         -> Series k (Maybe d)
-{-# INLINABLE zipWith3 #-}
-zipWith3 = G.zipWith3
-
-
--- | Apply a function elementwise to two series, matching elements
--- based on their keys. Keys present only in the left or right series are dropped.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]
--- >>> zipWithMatched (+) xs ys
---   index | values
---   ----- | ------
--- "alpha" |     10
---  "beta" |     12
---
--- To combine elements where keys are in either series, see 'zipWith'.
-zipWithMatched :: Ord k => (a -> b -> c) -> Series k a -> Series k b -> Series k c
-{-# INLINABLE zipWithMatched #-}
-zipWithMatched = G.zipWithMatched
-
-
--- | Apply a function elementwise to three series, matching elements
--- based on their keys. Keys not present in all three series are dropped.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int),  ("beta", 1),   ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11),  ("delta", 13) ]
--- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ]
--- >>> zipWithMatched3 (\x y z -> x + y + z) xs ys zs
---   index | values
---   ----- | ------
--- "alpha" |     30
-zipWithMatched3 :: (Ord k) 
-                => (a -> b -> c -> d) 
-                -> Series k a 
-                -> Series k b 
-                -> Series k c
-                -> Series k d
-{-# INLINABLE zipWithMatched3 #-}
-zipWithMatched3 = G.zipWithMatched3
-
-
--- | Apply a function elementwise to two series, matching elements
--- based on their keys. Keys present only in the left or right series are dropped.
---
--- To combine elements where keys are in either series, see 'zipWith'
-zipWithKey :: (Ord k) 
-           => (k -> a -> b -> c) -> Series k a -> Series k b -> Series k c
-{-# INLINABLE zipWithKey #-}
-zipWithKey = G.zipWithKey
-
-
--- | Apply a function elementwise to three series, matching elements
--- based on their keys. Keys present only in the left or right series are dropped.
---
--- To combine elements where keys are in any series, see 'zipWith3'
-zipWithKey3 :: (Ord k) 
-            => (k -> a -> b -> c -> d) 
-            -> Series k a 
-            -> Series k b 
-            -> Series k c
-            -> Series k d
-{-# INLINABLE zipWithKey3 #-}
-zipWithKey3 = G.zipWithKey3
-
-
--- | Zip two 'Series' with a combining function, applying a `ZipStrategy` when one key is present in one of the 'Series' but not both.
---
--- In the example below, we want to set the value to @-100@ (via @`constStrategy` (-100)@) for keys which are only present 
--- in the left 'Series', and drop keys (via `skipStrategy`) which are only present in the `right 'Series'  
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]
--- >>> zipWithStrategy (+) (constStrategy (-100)) skipStrategy  xs ys
---   index | values
---   ----- | ------
--- "alpha" |     10
---  "beta" |     12
--- "gamma" |   -100
---
--- Note that if you want to drop keys missing in either 'Series', it is faster to use @`zipWithMatched` f@ 
--- than using @`zipWithStrategy` f skipStrategy skipStrategy@.
-zipWithStrategy :: (Ord k) 
-               => (a -> b -> c)     -- ^ Function to combine values when present in both series
-               -> ZipStrategy k a c -- ^ Strategy for when the key is in the left series but not the right
-               -> ZipStrategy k b c -- ^ Strategy for when the key is in the right series but not the left
-               -> Series k a
-               -> Series k b 
-               -> Series k c
-{-# INLINABLE zipWithStrategy #-}
-zipWithStrategy = G.zipWithStrategy
-
-
--- | Zip three 'Series' with a combining function, applying a 'ZipStrategy' when one key is 
--- present in one of the 'Series' but not all of the others.
---
--- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched3' f@ 
--- than using @'zipWithStrategy3' f skipStrategy skipStrategy skipStrategy@.
-zipWithStrategy3 :: (Ord k) 
-                => (a -> b -> c -> d) -- ^ Function to combine values when present in all series
-                -> ZipStrategy k a d  -- ^ Strategy for when the key is in the left series but not in all the others
-                -> ZipStrategy k b d  -- ^ Strategy for when the key is in the center series but not in all the others
-                -> ZipStrategy k c d  -- ^ Strategy for when the key is in the right series but not in all the others
-                -> Series k a
-                -> Series k b 
-                -> Series k c
-                -> Series k d
-{-# INLINABLE zipWithStrategy3 #-}
-zipWithStrategy3 = G.zipWithStrategy3
-
-
--- | Zip two 'Series' with a combining function. The value for keys which are missing from
--- either 'Series' is replaced with the appropriate `mempty` value.
---
--- >>> import Data.Monoid ( Sum(..) )
--- >>> let xs = Series.fromList [ ("2023-01-01", Sum (1::Int)), ("2023-01-02", Sum 2) ]
--- >>> let ys = Series.fromList [ ("2023-01-01", Sum (5::Int)), ("2023-01-03", Sum 7) ]
--- >>> Series.zipWith (<>) xs ys
---        index |                  values
---        ----- |                  ------
--- "2023-01-01" | Just (Sum {getSum = 6})
--- "2023-01-02" |                 Nothing
--- "2023-01-03" |                 Nothing
--- >>> zipWithMonoid (<>) xs ys
---        index |           values
---        ----- |           ------
--- "2023-01-01" | Sum {getSum = 6}
--- "2023-01-02" | Sum {getSum = 2}
--- "2023-01-03" | Sum {getSum = 7}
-zipWithMonoid :: ( Monoid a, Monoid b, Ord k) 
-              => (a -> b -> c)
-              -> Series k a
-              -> Series k b 
-              -> Series k c
-zipWithMonoid = G.zipWithMonoid
-{-# INLINABLE zipWithMonoid #-}
-
-
--- | Elementwise sum of two 'Series'. Elements missing in one or the other 'Series' is considered 0. 
---
--- >>> let xs = Series.fromList [ ("2023-01-01", (1::Int)), ("2023-01-02", 2) ]
--- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]
--- >>> xs `esum` ys
---        index | values
---        ----- | ------
--- "2023-01-01" |      6
--- "2023-01-02" |      2
--- "2023-01-03" |      7
-esum :: (Ord k, Num a) 
-     => Series k a 
-     -> Series k a
-     -> Series k a
-esum = G.esum
-{-# INLINABLE esum #-}
-
-
--- | Elementwise product of two 'Series'. Elements missing in one or the other 'Series' is considered 1. 
---
--- >>> let xs = Series.fromList [ ("2023-01-01", (2::Int)), ("2023-01-02", 3) ]
--- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]
--- >>> xs `eproduct` ys
---        index | values
---        ----- | ------
--- "2023-01-01" |     10
--- "2023-01-02" |      3
--- "2023-01-03" |      7
-eproduct :: (Ord k, Num a) 
-         => Series k a 
-         -> Series k a
-         -> Series k a
-eproduct = G.eproduct
-{-# INLINABLE eproduct #-}
-
-
--- | \(O(n)\) Unzip a 'Series' of 2-tuples.
-unzip :: Series k (a, b)
-      -> ( Series k a
-         , Series k b
-         )
-unzip = G.unzip
-{-# INLINABLE unzip #-}
-
-
--- | \(O(n)\) Unzip a 'Series' of 3-tuples.
-unzip3 :: Series k (a, b, c)
-       -> ( Series k a
-          , Series k b
-          , Series k c
-          )
-unzip3 = G.unzip3
-{-# INLINABLE unzip3 #-}
-
-
--- | Require a series to have a specific `Index`.
--- Contrary to @select@, all keys in the `Index` will be present in the resulting series.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> xs `require` Index.fromList ["Paris", "Lisbon", "Taipei"]
---    index |  values
---    ----- |  ------
--- "Lisbon" |  Just 4
---  "Paris" |  Just 1
--- "Taipei" | Nothing
-require :: Ord k => Series k a -> Index k -> Series k (Maybe a)
-{-# INLINABLE require #-}
-require = G.require 
-
-
--- | \(O(n)\) Drop the index of a series by replacing it with an `Int`-based index. Values will
--- be indexed from 0.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> dropIndex xs
--- index | values
--- ----- | ------
---     0 |      4
---     1 |      2
---     2 |      1
-dropIndex :: Series k a -> Series Int a
-{-# INLINABLE dropIndex #-}
-dropIndex = G.dropIndex
-
-
--- | Filter elements. Only elements for which the predicate is @True@ are kept. 
--- Notice that the filtering is done on the values, not on the keys.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> filter (>2) xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
---
--- See also 'filterWithKey'.
-filter :: Ord k => (a -> Bool) -> Series k a -> Series k a
-{-# INLINABLE filter #-}
-filter = G.filter
-
-
--- | Filter elements, taking into account the corresponding key. Only elements for which 
--- the predicate is @True@ are kept. 
-filterWithKey :: Ord k 
-              => (k -> a -> Bool) 
-              -> Series k a 
-              -> Series k a
-{-# INLINABLE filterWithKey #-}
-filterWithKey = G.filterWithKey
-
-
--- | Drop elements which are not available (NA). 
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> let ys = xs `require` Index.fromList ["Paris", "London", "Lisbon", "Toronto"]
--- >>> ys
---     index |  values
---     ----- |  ------
---  "Lisbon" |  Just 4
---  "London" |  Just 2
---   "Paris" |  Just 1
--- "Toronto" | Nothing
--- >>> catMaybes ys
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
-catMaybes :: Ord k => Series k (Maybe a) -> Series k a
-{-# INLINABLE catMaybes #-}
-catMaybes = G.catMaybes
-
-
--- | Select a subseries. There are a few ways to do this.
---
--- The first way to do this is to select a sub-series based on random keys. For example,
--- selecting a subseries from an `Index`:
---
--- >>> let xs = Series.fromList [('a', 10::Int), ('b', 20), ('c', 30), ('d', 40)]
--- >>> xs `select` Index.fromList ['a', 'd']
--- index | values
--- ----- | ------
---   'a' |     10
---   'd' |     40
---
--- The second way to select a sub-series is to select all keys in a range:
---
--- >>> xs `select` 'b' `to` 'c'
--- index | values
--- ----- | ------
---   'b' |     20
---   'c' |     30
---
--- Note that with `select`, you'll always get a sub-series; if you ask for a key which is not
--- in the series, it'll be ignored:
---
--- >>> xs `select` Index.fromList ['a', 'd', 'e']
--- index | values
--- ----- | ------
---   'a' |     10
---   'd' |     40
---
--- See `require` if you want to ensure that all keys are present.
-select :: (Selection s, Ord k) => Series k a -> s k -> Series k a
-select = G.select
-
-
--- | Select a sub-series from a series matching a condition.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> xs `selectWhere` (fmap (>1) xs)
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
-selectWhere :: Ord k => Series k a -> Series k Bool -> Series k a
-{-# INLINABLE selectWhere #-}
-selectWhere = G.selectWhere
-
-
--- | \(O(\log n)\). Extract a single value from a series, by key.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs `at` "Paris"
--- Just 1
--- >>> xs `at` "Sydney"
--- Nothing
-at :: Ord k => Series k a -> k -> Maybe a
-{-# INLINABLE at #-}
-at = G.at
-
-
--- | \(O(1)\). Extract a single value from a series, by index.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> xs `iat` 0
--- Just 4
--- >>> xs `iat` 3
--- Nothing
-iat :: Series k a -> Int -> Maybe a
-{-# INLINABLE iat #-}
-iat = G.iat
-
-
--- | Replace values in the right series from values in the left series at matching keys.
--- Keys not in the right series are unaffected.
--- 
--- See `(|->)` and `(<-|)`, which might be more readable.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> let ys = Series.singleton "Paris" (99::Int)
--- >>> ys `replace` xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |     99
-replace :: Ord k => Series k a -> Series k a -> Series k a
-{-# INLINABLE replace #-}
-replace = G.replace
-
-
--- | Replace values in the right series from values in the left series at matching keys.
--- Keys not in the right series are unaffected.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> let ys = Series.singleton "Paris" (99::Int)
--- >>> ys |-> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |     99
-(|->) :: (Ord k) => Series k a -> Series k a -> Series k a
-{-# INLINABLE (|->) #-}
-(|->) = (G.|->)
-
-
--- | Replace values in the left series from values in the right series at matching keys.
--- Keys not in the left series are unaffected.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> let ys = Series.singleton "Paris" (99::Int)
--- >>> xs <-| ys
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |     99
-(<-|) :: (Ord k) => Series k a -> Series k a -> Series k a
-{-# INLINABLE (<-|) #-}
-(<-|) = (G.<-|)
-
-
--- | \(O(n)\) Replace all instances of 'Nothing' with the last previous
--- value which was not 'Nothing'.
---
--- >>> let xs = Series.fromList (zip [0..] [Just 1, Just 2,Nothing, Just 3]) :: Series Int (Maybe Int)
--- >>> xs
--- index |  values
--- ----- |  ------
---     0 |  Just 1
---     1 |  Just 2
---     2 | Nothing
---     3 |  Just 3
--- >>> forwardFill 0 xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      2
---     3 |      3
---
--- If the first entry of the series is missing, the first input to 'forwardFill' will be used:
---
--- >>> let ys = Series.fromList (zip [0..] [Nothing, Just 2,Nothing, Just 3]) :: Series Int (Maybe Int)
--- >>> ys
--- index |  values
--- ----- |  ------
---     0 | Nothing
---     1 |  Just 2
---     2 | Nothing
---     3 |  Just 3
--- >>> forwardFill 0 ys
--- index | values
--- ----- | ------
---     0 |      0
---     1 |      2
---     2 |      2
---     3 |      3
-forwardFill :: a -- ^ Until the first non-'Nothing' is found, 'Nothing' will be filled with this value.
-            -> Series v (Maybe a)
-            -> Series v a
-{-# INLINABLE forwardFill #-}
-forwardFill = G.forwardFill
-
-
--- | \(O(n)\) Execute a 'Fold' over a 'Series'.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Double
--- >>> xs
--- index | values
--- ----- | ------
---     0 |    1.0
---     1 |    2.0
---     2 |    3.0
---     3 |    4.0
--- >>> import Control.Foldl (variance)
--- >>> fold variance xs
--- 1.25
---
--- See also 'foldM' for monadic folds, and 'foldWithKey' to take keys into
--- account while folding.
-fold :: Fold a b -> Series k a -> b
-fold = G.fold
-{-# INLINABLE fold #-}
-
-
--- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series'.
---
--- See also 'fold' for pure folds, and 'foldMWithKey' to take keys into
--- account while folding.
-foldM :: (Monad m) 
-      => FoldM m a b  
-      -> Series k a 
-      -> m b
-foldM = G.foldM
-{-# INLINABLE foldM #-}
-
-
--- | \(O(n)\) Execute a 'Fold' over a 'Series', taking keys into account.
-foldWithKey :: Fold (k, a) b -> Series k a -> b
-foldWithKey = G.foldWithKey
-{-# INLINABLE foldWithKey #-}
-
-
--- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series', where the 'FoldM' takes keys into account.
-foldMWithKey :: (Monad m) 
-             => FoldM m (k, a) b  
-             -> Series k a 
-             -> m b
-foldMWithKey = G.foldMWithKey
-{-# INLINABLE foldMWithKey #-}
-
-
--- | \(O(n)\) Map each element and associated key of the structure to a monoid and combine
--- the results.
-foldMapWithKey :: Monoid m => (k -> a -> m) -> Series k a -> m
-{-# INLINABLE foldMapWithKey #-}
-foldMapWithKey = G.foldMapWithKey
-
-
--- | Group values in a 'Series' by some grouping function (@k -> g@).
--- The provided grouping function is guaranteed to operate on a non-empty 'Series'.
---
--- This function is expected to be used in conjunction with 'aggregateWith':
--- 
--- >>> import Data.Maybe ( fromMaybe )
--- >>> type Date = (Int, String)
--- >>> month :: (Date -> String) = snd
--- >>> :{ 
---     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)
---                              , ((2021, "January"), -5)
---                              , ((2020, "June")   , 20)
---                              , ((2021, "June")   , 25) 
---                              ]
---      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)
--- :}
---     index | values
---     ----- | ------
--- "January" |     -5
---    "June" |     20
-groupBy :: Series k a      -- ^ Grouping function
-        ->(k -> g)         -- ^ Input series
-        -> Grouping k g a  -- ^ Grouped series
-{-# INLINABLE groupBy #-}
-groupBy = G.groupBy
-
--- | Representation of a 'Series' being grouped.
-type Grouping k g a = G.Grouping k g Vector a
-
-
--- | Aggregate groups resulting from a call to 'groupBy':
--- 
--- >>> import Data.Maybe ( fromMaybe )
--- >>> type Date = (Int, String)
--- >>> month :: (Date -> String) = snd
--- >>> :{ 
---     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)
---                              , ((2021, "January"), -5)
---                              , ((2020, "June")   , 20)
---                              , ((2021, "June")   , 25) 
---                              ]
---      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)
--- :}
---     index | values
---     ----- | ------
--- "January" |     -5
---    "June" |     20
---
--- If you want to aggregate groups using a binary function, see 'foldWith' which
--- may be much faster.
-aggregateWith :: (Ord g) 
-              => Grouping k g a 
-              -> (Series k a -> b) 
-              -> Series g b
-{-# INLINABLE aggregateWith #-}
-aggregateWith = G.aggregateWith
-
-
--- | Aggregate each group in a 'Grouping' using a binary function.
--- While this is not as expressive as 'aggregateWith', users looking for maximum
--- performance should use 'foldWith' as much as possible.
-foldWith :: Ord g 
-         => Grouping k g a
-         -> (a -> a -> a)
-         -> Series g a
-{-# INLINABLE foldWith #-}
-foldWith = G.foldWith
-
-
--- | Expanding window aggregation.
---
--- >>> import qualified Data.Series as Series 
--- >>> :{ 
---     let (xs :: Series.Series Int Int) 
---          = Series.fromList [ (1, 0)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 3)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in (xs `expanding` sum) :: Series.Series Int Int 
--- :}
--- index | values
--- ----- | ------
---     1 |      0
---     2 |      1
---     3 |      3
---     4 |      6
---     5 |     10
---     6 |     15
-expanding :: Series k a        -- ^ Series vector
-          -> (Series k a -> b) -- ^ Aggregation function
-          -> Series k b        -- ^ Resulting vector
-{-# INLINABLE expanding #-}
-expanding = G.expanding
-
-
--- | General-purpose window aggregation.
---
--- >>> import qualified Data.Series as Series 
--- >>> :{ 
---     let (xs :: Series.Series Int Int) 
---          = Series.fromList [ (1, 0)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 3)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in windowing (\k -> k `to` (k+2)) sum xs
--- :}
--- index | values
--- ----- | ------
---     1 |      3
---     2 |      6
---     3 |      9
---     4 |     12
---     5 |      9
---     6 |      5
-windowing :: Ord k
-          => (k -> Range k)
-          -> (Series k a -> b)
-          -> Series k a
-          -> Series k b
-{-# INLINABLE windowing #-}
-windowing = G.windowing
-
-
--- | \(O(1)\) Test whether a 'Series' is empty.
-null :: Series k a -> Bool
-{-# INLINABLE null #-}
-null = G.null
-
-
--- |\(O(1)\) Extract the length of a 'Series'.
-length :: Series k a -> Int
-{-# INLINABLE length #-}
-length = G.length
-
-
--- | \(O(n)\) Check if all elements satisfy the predicate.
-all :: (a -> Bool) -> Series k a -> Bool
-{-# INLINABLE all #-}
-all = G.all
-
-
--- | \(O(n)\) Check if any element satisfies the predicate.
-any :: (a -> Bool) -> Series k a -> Bool
-{-# INLINABLE any #-}
-any = G.any
-
-
--- | \(O(n)\) Check if all elements are 'True'.
-and :: Series k Bool -> Bool
-{-# INLINABLE and #-}
-and = G.and
-
-
--- | \(O(n)\) Check if any element is 'True'.
-or :: Series k Bool -> Bool
-{-# INLINABLE or #-}
-or = G.or
-
-
--- | \(O(n)\) Compute the sum of the elements.
-sum :: (Num a) => Series k a -> a
-{-# INLINABLE sum #-}
-sum = G.sum
-
-
--- | \(O(n)\) Compute the product of the elements.
-product :: (Num a) => Series k a -> a
-{-# INLINABLE product #-}
-product = G.product
-
-
--- | \(O(n)\) Yield the maximum element of the series. In case of a tie, the first occurrence wins.
--- If the 'Series' is empty, @Nothing@ is returned.
---
--- See also 'argmax'.
-maximum :: (Ord a) => Series k a -> Maybe a
-{-# INLINABLE maximum #-}
-maximum = G.maximum
-
-
--- | \(O(n)\) @'maximumOn' f xs@ teturns the maximum element of the series @xs@, as determined by the function @f@.
--- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned.
-maximumOn :: (Ord b) => (a -> b) -> Series k a -> Maybe a
-{-# INLINABLE maximumOn #-}
-maximumOn = G.maximumOn
-
-
--- | \(O(n)\) Yield the minimum element of the series. In case of a tie, the first occurrence wins.
--- If the 'Series' is empty, @Nothing@ is returned.
---
--- See also 'argmin'.
-minimum :: (Ord a) => Series k a -> Maybe a
-{-# INLINABLE minimum #-}
-minimum = G.minimum
-
-
--- | \(O(n)\) @'minimumOn' f xs@ teturns the minimum element of the series @xs@, as determined by the function @f@.
--- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned.
-minimumOn :: (Ord b) => (a -> b) -> Series k a -> Maybe a
-{-# INLINABLE minimumOn #-}
-minimumOn = G.minimumOn
-
-
--- | \(O(n)\) Find the index of the maximum element in the input series.
--- If the input series is empty, 'Nothing' is returned.
---
--- The index of the first occurrence of the maximum element is returned.
---
--- >>> :{ 
---     let (xs :: Series Int Int) 
---          = Series.fromList [ (1, 0)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 7)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in argmax xs 
--- :}
--- Just 4
-argmax :: Ord a => Series k a -> Maybe k
-argmax = G.argmax
-{-# INLINABLE argmax #-}
-
-
--- | \(O(n)\) Find the index of the minimum element in the input series.
--- If the input series is empty, 'Nothing' is returned.
---
--- The index of the first occurrence of the minimum element is returned.
--- >>> :{ 
---     let (xs :: Series Int Int) 
---          = Series.fromList [ (1, 1)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 0)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in argmin xs 
--- :}
--- Just 4
-argmin :: Ord a => Series k a -> Maybe k
-argmin = G.argmin
-{-# INLINABLE argmin #-}
-
-
--- | \(O(n)\) Left-to-right postscan.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int
--- >>> xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      3
---     3 |      4
--- >>> postscanl (+) 0 xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      3
---     2 |      6
---     3 |     10
-postscanl :: (a -> b -> a) -> a -> Series k b -> Series k a
-{-# INLINABLE postscanl #-}
-postscanl = G.postscanl
-
-
--- | \(O(n)\) Left-to-right prescan.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int
--- >>> xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      3
---     3 |      4
--- >>> prescanl (+) 0 xs
--- index | values
--- ----- | ------
---     0 |      0
---     1 |      1
---     2 |      3
---     3 |      6
-prescanl :: (a -> b -> a) -> a -> Series k b -> Series k a
-{-# INLINABLE prescanl #-}
-prescanl = G.prescanl
-
-
--- | Display a 'Series' using default 'DisplayOptions'.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int
--- >>> putStrLn $ display xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      3
---   ... |    ...
---     4 |      5
---     5 |      6
---     6 |      7
-display :: (Show k, Show a) 
-        => Series k a 
-        -> String
-display = G.display
-
-
--- | Display a 'Series' using customizable 'DisplayOptions'.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int
--- >>> import Data.List (replicate)
--- >>> :{
---     let opts = DisplayOptions { maximumNumberOfRows  = 4
---                               , indexHeader = "keys"
---                               , valuesHeader = "vals"
---                               , keyDisplayFunction   = (\i -> replicate i 'x') `noLongerThan` 5
---                               , valueDisplayFunction = (\i -> replicate i 'o') 
---                               }
---      in putStrLn $ displayWith opts xs
--- :}
---   keys |    vals
---  ----- |  ------
---        |       o
---      x |      oo
---    ... |     ...
---  xxxxx |  oooooo
--- xxx... | ooooooo
-displayWith :: DisplayOptions k a
-            -> Series k a 
-            -> String
+-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Series+-- Copyright   :  (c) Laurent P. René de Cotret+-- License     :  MIT+-- Maintainer  :  laurent.decotret@outlook.com+-- Portability :  portable+--+-- This module contains data structures and functions to work with 'Series' capable of holding any Haskell value. +-- For better performance, at the cost of less flexibility, see the "Data.Series.Unboxed".+--+-- = Introduction to series+--+-- A 'Series' of type @Series k a@ is a labeled array of values of type @a@,+-- indexed by keys of type @k@.+--+-- Like `Data.Map.Strict.Map` from the @containers@ package, 'Series' support efficient:+--+--      * random access by key ( \(O(\log n)\) );+--      * slice by key ( \(O(\log n)\) ).+--+-- Like `Data.Vector.Vector`, they support efficient:+--+--      * random access by index ( \(O(1)\) );+--      * slice by index ( \(O(1)\) );+--      * numerical operations.+--+-- This module re-exports most of the content of "Data.Series.Generic", with type signatures +-- specialized to the boxed container type `Data.Vector.Vector`.+--+-- For better performance (at the cost of more constraints), especially when it comes to numerical calculations, prefer to+-- use "Data.Series.Unboxed", which contains an implementation of series specialized to the unboxed container type `Data.Vector.Unboxed.Vector`.+ +module Data.Series (+    Series, index, values,++    -- * Building/converting 'Series'+    singleton, fromIndex,+    -- ** Lists+    fromList, toList,+    -- ** Vectors+    fromVector, toVector,+    -- ** Handling duplicates+    Occurrence, fromListDuplicates, fromVectorDuplicates,+    -- ** Strict Maps+    fromStrictMap, toStrictMap,+    -- ** Lazy Maps+    fromLazyMap, toLazyMap,+    -- ** Ad-hoc conversion with other data structures+    IsSeries(..),+    -- ** Conversion between 'Series' types+    G.convert,++    -- * Mapping and filtering+    map, mapWithKey, mapIndex, concatMap,+    take, takeWhile, drop, dropWhile, filter, filterWithKey,+    -- ** Mapping with effects+    mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_, traverseWithKey,++    -- * Combining series+    zipWith, zipWithMatched, zipWithKey,+    zipWith3, zipWithMatched3, zipWithKey3,+    ZipStrategy, skipStrategy, mapStrategy, constStrategy, zipWithStrategy, zipWithStrategy3,+    zipWithMonoid, esum, eproduct, unzip, unzip3,++    -- * Index manipulation+    require, catMaybes, dropIndex,++    -- * Accessors+    -- ** Bulk access+    select, selectWhere, Range, to, from, upto, Selection, +    -- ** Single-element access+    at, iat,++    -- * Replacing values+    replace, (|->), (<-|),++    -- * Scans+    forwardFill,++    -- * Grouping and windowing operations+    groupBy, Grouping, aggregateWith, foldWith, +    windowing, expanding,++    -- * Folds+    fold, foldM, foldWithKey, foldMWithKey, foldMapWithKey,+    -- ** Specialized folds+    G.mean, G.variance, G.std,+    length, null, all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn, +    argmin, argmax,++    -- * Scans+    postscanl, prescanl,++    -- * Displaying 'Series'+    display, displayWith,+    noLongerThan,+    DisplayOptions(..), G.defaultDisplayOptions+) where++import           Control.Foldl       ( Fold, FoldM )+import qualified Data.Map.Lazy       as ML+import qualified Data.Map.Strict     as MS+import           Data.Series.Index   ( Index )+import           Data.Series.Generic ( IsSeries(..), Range, Selection, ZipStrategy, Occurrence, DisplayOptions(..)+                                     , to, from, upto, skipStrategy, mapStrategy, constStrategy, noLongerThan+                                     )+import qualified Data.Series.Generic as G+import           Data.Vector         ( Vector )++import           Prelude             hiding ( map, concatMap, zipWith, zipWith3, filter, take, takeWhile, drop, dropWhile, last, unzip, unzip3+                                            , length, null, all, any, and, or, sum, product, maximum, minimum, +                                            )++-- $setup+-- >>> import qualified Data.Series as Series+-- >>> import qualified Data.Series.Index as Index++infixl 1 `select` +infix 6 |->, <-|++-- | A series is a labeled array of values of type @a@,+-- indexed by keys of type @k@.+--+-- Like @Data.Map@ and @Data.HashMap@, they support efficient:+--+--      * random access by key ( \(O(\log n)\) );+--      * slice by key ( \(O(\log n)\) ).+--+-- Like @Data.Vector.Vector@, they support efficient:+--+--      * random access by index ( \(O(1)\) );+--      * slice by index ( \(O(1)\) );+--      * numerical operations.+type Series = G.Series Vector+++index :: Series k a -> Index k+{-# INLINABLE index #-}+index = G.index+++values :: Series k a -> Vector a+{-# INLINABLE values #-}+values = G.values+++-- | Create a 'Series' with a single element.+singleton :: k -> a -> Series k a+{-# INLINABLE singleton #-}+singleton = G.singleton+++-- | \(O(n)\) Generate a 'Series' by mapping every element of its index.+--+-- >>> fromIndex (const (0::Int)) $ Index.fromList ['a','b','c','d']+-- index | values+-- ----- | ------+--   'a' |      0+--   'b' |      0+--   'c' |      0+--   'd' |      0+fromIndex :: (k -> a) -> Index k -> Series k a+{-# INLINABLE fromIndex #-}+fromIndex = G.fromIndex+++-- | Construct a series from a list of key-value pairs. There is no+-- condition on the order of pairs.+--+-- >>> let xs = fromList [('b', 0::Int), ('a', 5), ('d', 1) ]+-- >>> xs+-- index | values+-- ----- | ------+--   'a' |      5+--   'b' |      0+--   'd' |      1+--+-- If you need to handle duplicate keys, take a look at `fromListDuplicates`.+fromList :: Ord k => [(k, a)] -> Series k a+{-# INLINABLE fromList #-}+fromList = G.fromList+++-- | Construct a series from a list of key-value pairs.+-- Contrary to `fromList`, values at duplicate keys are preserved. To keep each+-- key unique, an `Occurrence` number counts up.+--+-- >>> let xs = fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]+-- >>> xs+--   index | values+--   ----- | ------+-- ('a',0) |      5+-- ('b',0) |      0+-- ('d',0) |      1+-- ('d',1) |     -4+-- ('d',2) |      7+fromListDuplicates :: Ord k => [(k, a)] -> Series (k, Occurrence) a+{-# INLINABLE fromListDuplicates #-}+fromListDuplicates = G.fromListDuplicates+++-- | Construct a list from key-value pairs. The elements are in order sorted by key:+--+-- >>> let xs = Series.fromList [ ('b', 0::Int), ('a', 5), ('d', 1) ]+-- >>> xs+-- index | values+-- ----- | ------+--   'a' |      5+--   'b' |      0+--   'd' |      1+-- >>> toList xs+-- [('a',5),('b',0),('d',1)]+toList :: Series k a -> [(k, a)]+{-# INLINABLE toList #-}+toList = G.toList+++-- | Construct a 'Vector' of key-value pairs. The elements are in order sorted by key. +toVector :: Series k a -> Vector (k, a)+{-# INLINABLE toVector #-}+toVector = G.toVector+++-- | Construct a 'Series' from a 'Vector' of key-value pairs. There is no+-- condition on the order of pairs. Duplicate keys are silently dropped. If you+-- need to handle duplicate keys, see 'fromVectorDuplicates'.+--+-- Note that due to differences in sorting,+-- @'Series.fromList'@ and @'Series.fromVector' . 'Vector.fromList'@ +-- may not be equivalent if the input list contains duplicate keys.+fromVector :: Ord k => Vector (k, a) -> Series k a+{-# INLINABLE fromVector #-}+fromVector = G.fromVector+++-- | Construct a series from a 'Vector' of key-value pairs.+-- Contrary to 'fromVector', values at duplicate keys are preserved. To keep each+-- key unique, an 'Occurrence' number counts up.+--+-- >>> import qualified Data.Vector as Vector+-- >>> let xs = fromVectorDuplicates $ Vector.fromList [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]+-- >>> xs+--   index | values+--   ----- | ------+-- ('a',0) |      5+-- ('b',0) |      0+-- ('d',0) |      1+-- ('d',1) |     -4+-- ('d',2) |      7+fromVectorDuplicates :: Ord k => Vector (k, a) -> Series (k, Occurrence) a+{-# INLINABLE fromVectorDuplicates #-}+fromVectorDuplicates = G.fromVectorDuplicates+++-- | Convert a series into a lazy @Map@.+toLazyMap :: Series k a -> ML.Map k a+{-# INLINABLE toLazyMap #-}+toLazyMap = G.toLazyMap+++-- | Construct a series from a lazy @Map@.+fromLazyMap :: ML.Map k a -> Series k a+{-# INLINABLE fromLazyMap #-}+fromLazyMap = G.fromLazyMap+++-- | Convert a series into a strict @Map@.+toStrictMap :: Series k a -> MS.Map k a+{-# INLINABLE toStrictMap #-}+toStrictMap = G.toStrictMap+++-- | Construct a series from a strict @Map@.+fromStrictMap :: MS.Map k a -> Series k a+{-# INLINABLE fromStrictMap #-}+fromStrictMap = G.fromStrictMap+++-- | \(O(n)\) Map every element of a 'Series'.+map :: (a -> b) -> Series k a -> Series k b+{-# INLINABLE map #-}+map = G.map+++-- | \(O(n)\) Map every element of a 'Series', possibly using the key as well.+mapWithKey :: (k -> a -> b) -> Series k a -> Series k b+{-# INLINABLE mapWithKey #-}+mapWithKey = G.mapWithKey+++-- | \(O(n \log n)\).+-- Map each key in the index to another value. Note that the resulting series+-- may have less elements, because each key must be unique.+--+-- In case new keys are conflicting, the first element is kept.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> import qualified Data.List+-- >>> xs `mapIndex` (Data.List.take 1)+-- index | values+-- ----- | ------+--   "L" |      4+--   "P" |      1+mapIndex :: (Ord k, Ord g) => Series k a -> (k -> g) -> Series g a+{-# INLINABLE mapIndex #-}+mapIndex = G.mapIndex+++-- | Map a function over all the elements of a 'Series' and concatenate the result into a single 'Series'.+concatMap :: Ord k +          => (a -> Series k b) +          -> Series k a +          -> Series k b+{-# INLINABLE concatMap #-}+concatMap = G.concatMap+++-- | \(O(n)\) Apply the monadic action to every element of a series and its+-- index, yielding a series of results.+mapWithKeyM :: (Monad m, Ord k) => (k -> a -> m b) -> Series k a -> m (Series k b)+{-# INLINABLE mapWithKeyM #-}+mapWithKeyM = G.mapWithKeyM+++-- | \(O(n)\) Apply the monadic action to every element of a series and its+-- index, discarding the results.+mapWithKeyM_ :: Monad m => (k -> a -> m b) -> Series k a -> m ()+{-# INLINABLE mapWithKeyM_ #-}+mapWithKeyM_ = G.mapWithKeyM_+++-- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, +-- yielding a series of results.+forWithKeyM :: (Monad m, Ord k) => Series k a -> (k -> a -> m b) -> m (Series k b)+{-# INLINABLE forWithKeyM #-}+forWithKeyM = G.forWithKeyM+++-- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, +-- discarding the results.+forWithKeyM_ :: Monad m => Series k a -> (k -> a -> m b) -> m ()+{-# INLINABLE forWithKeyM_ #-}+forWithKeyM_ = G.forWithKeyM_+++-- | \(O(n)\) Traverse a 'Series' with an Applicative action, taking into account both keys and values. +traverseWithKey :: (Applicative t, Ord k)+                => (k -> a -> t b) +                -> Series k a +                -> t (Series k b)+{-# INLINABLE traverseWithKey #-}+traverseWithKey = G.traverseWithKey+++-- | \(O(\log n)\) @'take' n xs@ returns at most @n@ elements of the 'Series' @xs@.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- "Vienna" |      5+-- >>> take 2 xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+take :: Int -> Series k a -> Series k a+{-# INLINABLE take #-}+take = G.take+++-- | \(O(n)\) Returns the longest prefix (possibly empty) of the input 'Series' that satisfy a predicate.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- "Vienna" |      5++-- >>> takeWhile (>1) xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+takeWhile :: (a -> Bool) -> Series k a -> Series k a+takeWhile = G.takeWhile+++-- | \(O(\log n)\) @'drop' n xs@ drops at most @n@ elements from the 'Series' @xs@.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- "Vienna" |      5+-- >>> drop 2 xs+--    index | values+--    ----- | ------+--  "Paris" |      1+-- "Vienna" |      5+drop :: Int -> Series k a -> Series k a+{-# INLINABLE drop #-}+drop = G.drop+++-- | \(O(n)\) Returns the complement of `takeWhile`.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- "Vienna" |      5++-- >>> dropWhile (>1) xs+--    index | values+--    ----- | ------+--  "Paris" |      1+-- "Vienna" |      5+dropWhile :: (a -> Bool) -> Series k a -> Series k a+dropWhile = G.dropWhile+++-- | Apply a function elementwise to two series, matching elements+-- based on their keys. For keys present only in the left or right series, +-- the value 'Nothing' is returned.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]+-- >>> zipWith (+) xs ys+--   index |  values+--   ----- |  ------+-- "alpha" | Just 10+--  "beta" | Just 12+-- "delta" | Nothing+-- "gamma" | Nothing+--+-- To only combine elements where keys are in both series, see 'zipWithMatched'.+zipWith :: (Ord k) +        => (a -> b -> c) -> Series k a -> Series k b -> Series k (Maybe c)+zipWith = G.zipWith +{-# INLINABLE zipWith #-}++++-- | Apply a function elementwise to three series, matching elements+-- based on their keys. For keys present only in the left or right series, +-- the value 'Nothing' is returned.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int),  ("beta", 1),   ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11),  ("delta", 13) ]+-- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ]+-- >>> zipWith3 (\x y z -> x + y + z) xs ys zs+--     index |  values+--     ----- |  ------+--   "alpha" | Just 30+--    "beta" | Nothing+--   "delta" | Nothing+-- "epsilon" | Nothing+--   "gamma" | Nothing+--+-- To only combine elements where keys are in all series, see 'zipWithMatched3'+zipWith3 :: (Ord k) +         => (a -> b -> c -> d) +         -> Series k a +         -> Series k b +         -> Series k c +         -> Series k (Maybe d)+{-# INLINABLE zipWith3 #-}+zipWith3 = G.zipWith3+++-- | Apply a function elementwise to two series, matching elements+-- based on their keys. Keys present only in the left or right series are dropped.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]+-- >>> zipWithMatched (+) xs ys+--   index | values+--   ----- | ------+-- "alpha" |     10+--  "beta" |     12+--+-- To combine elements where keys are in either series, see 'zipWith'.+zipWithMatched :: Ord k => (a -> b -> c) -> Series k a -> Series k b -> Series k c+{-# INLINABLE zipWithMatched #-}+zipWithMatched = G.zipWithMatched+++-- | Apply a function elementwise to three series, matching elements+-- based on their keys. Keys not present in all three series are dropped.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int),  ("beta", 1),   ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11),  ("delta", 13) ]+-- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ]+-- >>> zipWithMatched3 (\x y z -> x + y + z) xs ys zs+--   index | values+--   ----- | ------+-- "alpha" |     30+zipWithMatched3 :: (Ord k) +                => (a -> b -> c -> d) +                -> Series k a +                -> Series k b +                -> Series k c+                -> Series k d+{-# INLINABLE zipWithMatched3 #-}+zipWithMatched3 = G.zipWithMatched3+++-- | Apply a function elementwise to two series, matching elements+-- based on their keys. Keys present only in the left or right series are dropped.+--+-- To combine elements where keys are in either series, see 'zipWith'+zipWithKey :: (Ord k) +           => (k -> a -> b -> c) -> Series k a -> Series k b -> Series k c+{-# INLINABLE zipWithKey #-}+zipWithKey = G.zipWithKey+++-- | Apply a function elementwise to three series, matching elements+-- based on their keys. Keys present only in the left or right series are dropped.+--+-- To combine elements where keys are in any series, see 'zipWith3'+zipWithKey3 :: (Ord k) +            => (k -> a -> b -> c -> d) +            -> Series k a +            -> Series k b +            -> Series k c+            -> Series k d+{-# INLINABLE zipWithKey3 #-}+zipWithKey3 = G.zipWithKey3+++-- | Zip two 'Series' with a combining function, applying a `ZipStrategy` when one key is present in one of the 'Series' but not both.+--+-- In the example below, we want to set the value to @-100@ (via @`constStrategy` (-100)@) for keys which are only present +-- in the left 'Series', and drop keys (via `skipStrategy`) which are only present in the `right 'Series'  +--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]+-- >>> zipWithStrategy (+) (constStrategy (-100)) skipStrategy  xs ys+--   index | values+--   ----- | ------+-- "alpha" |     10+--  "beta" |     12+-- "gamma" |   -100+--+-- Note that if you want to drop keys missing in either 'Series', it is faster to use @`zipWithMatched` f@ +-- than using @`zipWithStrategy` f skipStrategy skipStrategy@.+zipWithStrategy :: (Ord k) +               => (a -> b -> c)     -- ^ Function to combine values when present in both series+               -> ZipStrategy k a c -- ^ Strategy for when the key is in the left series but not the right+               -> ZipStrategy k b c -- ^ Strategy for when the key is in the right series but not the left+               -> Series k a+               -> Series k b +               -> Series k c+{-# INLINABLE zipWithStrategy #-}+zipWithStrategy = G.zipWithStrategy+++-- | Zip three 'Series' with a combining function, applying a 'ZipStrategy' when one key is +-- present in one of the 'Series' but not all of the others.+--+-- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched3' f@ +-- than using @'zipWithStrategy3' f skipStrategy skipStrategy skipStrategy@.+zipWithStrategy3 :: (Ord k) +                => (a -> b -> c -> d) -- ^ Function to combine values when present in all series+                -> ZipStrategy k a d  -- ^ Strategy for when the key is in the left series but not in all the others+                -> ZipStrategy k b d  -- ^ Strategy for when the key is in the center series but not in all the others+                -> ZipStrategy k c d  -- ^ Strategy for when the key is in the right series but not in all the others+                -> Series k a+                -> Series k b +                -> Series k c+                -> Series k d+{-# INLINABLE zipWithStrategy3 #-}+zipWithStrategy3 = G.zipWithStrategy3+++-- | Zip two 'Series' with a combining function. The value for keys which are missing from+-- either 'Series' is replaced with the appropriate `mempty` value.+--+-- >>> import Data.Monoid ( Sum(..) )+-- >>> let xs = Series.fromList [ ("2023-01-01", Sum (1::Int)), ("2023-01-02", Sum 2) ]+-- >>> let ys = Series.fromList [ ("2023-01-01", Sum (5::Int)), ("2023-01-03", Sum 7) ]+-- >>> Series.zipWith (<>) xs ys+--        index |                  values+--        ----- |                  ------+-- "2023-01-01" | Just (Sum {getSum = 6})+-- "2023-01-02" |                 Nothing+-- "2023-01-03" |                 Nothing+-- >>> zipWithMonoid (<>) xs ys+--        index |           values+--        ----- |           ------+-- "2023-01-01" | Sum {getSum = 6}+-- "2023-01-02" | Sum {getSum = 2}+-- "2023-01-03" | Sum {getSum = 7}+zipWithMonoid :: ( Monoid a, Monoid b, Ord k) +              => (a -> b -> c)+              -> Series k a+              -> Series k b +              -> Series k c+zipWithMonoid = G.zipWithMonoid+{-# INLINABLE zipWithMonoid #-}+++-- | Elementwise sum of two 'Series'. Elements missing in one or the other 'Series' is considered 0. +--+-- >>> let xs = Series.fromList [ ("2023-01-01", (1::Int)), ("2023-01-02", 2) ]+-- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]+-- >>> xs `esum` ys+--        index | values+--        ----- | ------+-- "2023-01-01" |      6+-- "2023-01-02" |      2+-- "2023-01-03" |      7+esum :: (Ord k, Num a) +     => Series k a +     -> Series k a+     -> Series k a+esum = G.esum+{-# INLINABLE esum #-}+++-- | Elementwise product of two 'Series'. Elements missing in one or the other 'Series' is considered 1. +--+-- >>> let xs = Series.fromList [ ("2023-01-01", (2::Int)), ("2023-01-02", 3) ]+-- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]+-- >>> xs `eproduct` ys+--        index | values+--        ----- | ------+-- "2023-01-01" |     10+-- "2023-01-02" |      3+-- "2023-01-03" |      7+eproduct :: (Ord k, Num a) +         => Series k a +         -> Series k a+         -> Series k a+eproduct = G.eproduct+{-# INLINABLE eproduct #-}+++-- | \(O(n)\) Unzip a 'Series' of 2-tuples.+unzip :: Series k (a, b)+      -> ( Series k a+         , Series k b+         )+unzip = G.unzip+{-# INLINABLE unzip #-}+++-- | \(O(n)\) Unzip a 'Series' of 3-tuples.+unzip3 :: Series k (a, b, c)+       -> ( Series k a+          , Series k b+          , Series k c+          )+unzip3 = G.unzip3+{-# INLINABLE unzip3 #-}+++-- | Require a series to have a specific `Index`.+-- Contrary to @select@, all keys in the `Index` will be present in the resulting series.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> xs `require` Index.fromList ["Paris", "Lisbon", "Taipei"]+--    index |  values+--    ----- |  ------+-- "Lisbon" |  Just 4+--  "Paris" |  Just 1+-- "Taipei" | Nothing+require :: Ord k => Series k a -> Index k -> Series k (Maybe a)+{-# INLINABLE require #-}+require = G.require +++-- | \(O(n)\) Drop the index of a series by replacing it with an `Int`-based index. Values will+-- be indexed from 0.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> dropIndex xs+-- index | values+-- ----- | ------+--     0 |      4+--     1 |      2+--     2 |      1+dropIndex :: Series k a -> Series Int a+{-# INLINABLE dropIndex #-}+dropIndex = G.dropIndex+++-- | Filter elements. Only elements for which the predicate is @True@ are kept. +-- Notice that the filtering is done on the values, not on the keys.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> filter (>2) xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+--+-- See also 'filterWithKey'.+filter :: Ord k => (a -> Bool) -> Series k a -> Series k a+{-# INLINABLE filter #-}+filter = G.filter+++-- | Filter elements, taking into account the corresponding key. Only elements for which +-- the predicate is @True@ are kept. +filterWithKey :: Ord k +              => (k -> a -> Bool) +              -> Series k a +              -> Series k a+{-# INLINABLE filterWithKey #-}+filterWithKey = G.filterWithKey+++-- | Drop elements which are not available (NA). +--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> let ys = xs `require` Index.fromList ["Paris", "London", "Lisbon", "Toronto"]+-- >>> ys+--     index |  values+--     ----- |  ------+--  "Lisbon" |  Just 4+--  "London" |  Just 2+--   "Paris" |  Just 1+-- "Toronto" | Nothing+-- >>> catMaybes ys+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+catMaybes :: Ord k => Series k (Maybe a) -> Series k a+{-# INLINABLE catMaybes #-}+catMaybes = G.catMaybes+++-- | Select a subseries. There are a few ways to do this.+--+-- The first way to do this is to select a sub-series based on random keys. For example,+-- selecting a subseries from an `Index`:+--+-- >>> let xs = Series.fromList [('a', 10::Int), ('b', 20), ('c', 30), ('d', 40)]+-- >>> xs `select` Index.fromList ['a', 'd']+-- index | values+-- ----- | ------+--   'a' |     10+--   'd' |     40+--+-- The second way to select a sub-series is to select all keys in a range:+--+-- >>> xs `select` 'b' `to` 'c'+-- index | values+-- ----- | ------+--   'b' |     20+--   'c' |     30+--+-- Note that with `select`, you'll always get a sub-series; if you ask for a key which is not+-- in the series, it'll be ignored:+--+-- >>> xs `select` Index.fromList ['a', 'd', 'e']+-- index | values+-- ----- | ------+--   'a' |     10+--   'd' |     40+--+-- See `require` if you want to ensure that all keys are present.+select :: (Selection s, Ord k) => Series k a -> s k -> Series k a+select = G.select+++-- | Select a sub-series from a series matching a condition.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> xs `selectWhere` (fmap (>1) xs)+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+selectWhere :: Ord k => Series k a -> Series k Bool -> Series k a+{-# INLINABLE selectWhere #-}+selectWhere = G.selectWhere+++-- | \(O(\log n)\). Extract a single value from a series, by key.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs `at` "Paris"+-- Just 1+-- >>> xs `at` "Sydney"+-- Nothing+at :: Ord k => Series k a -> k -> Maybe a+{-# INLINABLE at #-}+at = G.at+++-- | \(O(1)\). Extract a single value from a series, by index.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> xs `iat` 0+-- Just 4+-- >>> xs `iat` 3+-- Nothing+iat :: Series k a -> Int -> Maybe a+{-# INLINABLE iat #-}+iat = G.iat+++-- | Replace values in the right series from values in the left series at matching keys.+-- Keys not in the right series are unaffected.+-- +-- See `(|->)` and `(<-|)`, which might be more readable.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> let ys = Series.singleton "Paris" (99::Int)+-- >>> ys `replace` xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |     99+replace :: Ord k => Series k a -> Series k a -> Series k a+{-# INLINABLE replace #-}+replace = G.replace+++-- | Replace values in the right series from values in the left series at matching keys.+-- Keys not in the right series are unaffected.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> let ys = Series.singleton "Paris" (99::Int)+-- >>> ys |-> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |     99+(|->) :: (Ord k) => Series k a -> Series k a -> Series k a+{-# INLINABLE (|->) #-}+(|->) = (G.|->)+++-- | Replace values in the left series from values in the right series at matching keys.+-- Keys not in the left series are unaffected.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> let ys = Series.singleton "Paris" (99::Int)+-- >>> xs <-| ys+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |     99+(<-|) :: (Ord k) => Series k a -> Series k a -> Series k a+{-# INLINABLE (<-|) #-}+(<-|) = (G.<-|)+++-- | \(O(n)\) Replace all instances of 'Nothing' with the last previous+-- value which was not 'Nothing'.+--+-- >>> let xs = Series.fromList (zip [0..] [Just 1, Just 2,Nothing, Just 3]) :: Series Int (Maybe Int)+-- >>> xs+-- index |  values+-- ----- |  ------+--     0 |  Just 1+--     1 |  Just 2+--     2 | Nothing+--     3 |  Just 3+-- >>> forwardFill 0 xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      2+--     3 |      3+--+-- If the first entry of the series is missing, the first input to 'forwardFill' will be used:+--+-- >>> let ys = Series.fromList (zip [0..] [Nothing, Just 2,Nothing, Just 3]) :: Series Int (Maybe Int)+-- >>> ys+-- index |  values+-- ----- |  ------+--     0 | Nothing+--     1 |  Just 2+--     2 | Nothing+--     3 |  Just 3+-- >>> forwardFill 0 ys+-- index | values+-- ----- | ------+--     0 |      0+--     1 |      2+--     2 |      2+--     3 |      3+forwardFill :: a -- ^ Until the first non-'Nothing' is found, 'Nothing' will be filled with this value.+            -> Series v (Maybe a)+            -> Series v a+{-# INLINABLE forwardFill #-}+forwardFill = G.forwardFill+++-- | \(O(n)\) Execute a 'Fold' over a 'Series'.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Double+-- >>> xs+-- index | values+-- ----- | ------+--     0 |    1.0+--     1 |    2.0+--     2 |    3.0+--     3 |    4.0+-- >>> import Control.Foldl (variance)+-- >>> fold variance xs+-- 1.25+--+-- See also 'foldM' for monadic folds, and 'foldWithKey' to take keys into+-- account while folding.+fold :: Fold a b -> Series k a -> b+fold = G.fold+{-# INLINABLE fold #-}+++-- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series'.+--+-- See also 'fold' for pure folds, and 'foldMWithKey' to take keys into+-- account while folding.+foldM :: (Monad m) +      => FoldM m a b  +      -> Series k a +      -> m b+foldM = G.foldM+{-# INLINABLE foldM #-}+++-- | \(O(n)\) Execute a 'Fold' over a 'Series', taking keys into account.+foldWithKey :: Fold (k, a) b -> Series k a -> b+foldWithKey = G.foldWithKey+{-# INLINABLE foldWithKey #-}+++-- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series', where the 'FoldM' takes keys into account.+foldMWithKey :: (Monad m) +             => FoldM m (k, a) b  +             -> Series k a +             -> m b+foldMWithKey = G.foldMWithKey+{-# INLINABLE foldMWithKey #-}+++-- | \(O(n)\) Map each element and associated key of the structure to a monoid and combine+-- the results.+foldMapWithKey :: Monoid m => (k -> a -> m) -> Series k a -> m+{-# INLINABLE foldMapWithKey #-}+foldMapWithKey = G.foldMapWithKey+++-- | Group values in a 'Series' by some grouping function (@k -> g@).+-- The provided grouping function is guaranteed to operate on a non-empty 'Series'.+--+-- This function is expected to be used in conjunction with 'aggregateWith':+-- +-- >>> import Data.Maybe ( fromMaybe )+-- >>> type Date = (Int, String)+-- >>> month :: (Date -> String) = snd+-- >>> :{ +--     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)+--                              , ((2021, "January"), -5)+--                              , ((2020, "June")   , 20)+--                              , ((2021, "June")   , 25) +--                              ]+--      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)+-- :}+--     index | values+--     ----- | ------+-- "January" |     -5+--    "June" |     20+groupBy :: Series k a      -- ^ Grouping function+        ->(k -> g)         -- ^ Input series+        -> Grouping k g a  -- ^ Grouped series+{-# INLINABLE groupBy #-}+groupBy = G.groupBy++-- | Representation of a 'Series' being grouped.+type Grouping k g a = G.Grouping k g Vector a+++-- | Aggregate groups resulting from a call to 'groupBy':+-- +-- >>> import Data.Maybe ( fromMaybe )+-- >>> type Date = (Int, String)+-- >>> month :: (Date -> String) = snd+-- >>> :{ +--     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)+--                              , ((2021, "January"), -5)+--                              , ((2020, "June")   , 20)+--                              , ((2021, "June")   , 25) +--                              ]+--      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)+-- :}+--     index | values+--     ----- | ------+-- "January" |     -5+--    "June" |     20+--+-- If you want to aggregate groups using a binary function, see 'foldWith' which+-- may be much faster.+aggregateWith :: (Ord g) +              => Grouping k g a +              -> (Series k a -> b) +              -> Series g b+{-# INLINABLE aggregateWith #-}+aggregateWith = G.aggregateWith+++-- | Aggregate each group in a 'Grouping' using a binary function.+-- While this is not as expressive as 'aggregateWith', users looking for maximum+-- performance should use 'foldWith' as much as possible.+foldWith :: Ord g +         => Grouping k g a+         -> (a -> a -> a)+         -> Series g a+{-# INLINABLE foldWith #-}+foldWith = G.foldWith+++-- | Expanding window aggregation.+--+-- >>> import qualified Data.Series as Series +-- >>> :{ +--     let (xs :: Series.Series Int Int) +--          = Series.fromList [ (1, 0)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 3)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in (xs `expanding` sum) :: Series.Series Int Int +-- :}+-- index | values+-- ----- | ------+--     1 |      0+--     2 |      1+--     3 |      3+--     4 |      6+--     5 |     10+--     6 |     15+expanding :: Series k a        -- ^ Series vector+          -> (Series k a -> b) -- ^ Aggregation function+          -> Series k b        -- ^ Resulting vector+{-# INLINABLE expanding #-}+expanding = G.expanding+++-- | General-purpose window aggregation.+--+-- >>> import qualified Data.Series as Series +-- >>> :{ +--     let (xs :: Series.Series Int Int) +--          = Series.fromList [ (1, 0)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 3)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in windowing (\k -> k `to` (k+2)) sum xs+-- :}+-- index | values+-- ----- | ------+--     1 |      3+--     2 |      6+--     3 |      9+--     4 |     12+--     5 |      9+--     6 |      5+windowing :: Ord k+          => (k -> Range k)+          -> (Series k a -> b)+          -> Series k a+          -> Series k b+{-# INLINABLE windowing #-}+windowing = G.windowing+++-- | \(O(1)\) Test whether a 'Series' is empty.+null :: Series k a -> Bool+{-# INLINABLE null #-}+null = G.null+++-- |\(O(1)\) Extract the length of a 'Series'.+length :: Series k a -> Int+{-# INLINABLE length #-}+length = G.length+++-- | \(O(n)\) Check if all elements satisfy the predicate.+all :: (a -> Bool) -> Series k a -> Bool+{-# INLINABLE all #-}+all = G.all+++-- | \(O(n)\) Check if any element satisfies the predicate.+any :: (a -> Bool) -> Series k a -> Bool+{-# INLINABLE any #-}+any = G.any+++-- | \(O(n)\) Check if all elements are 'True'.+and :: Series k Bool -> Bool+{-# INLINABLE and #-}+and = G.and+++-- | \(O(n)\) Check if any element is 'True'.+or :: Series k Bool -> Bool+{-# INLINABLE or #-}+or = G.or+++-- | \(O(n)\) Compute the sum of the elements.+sum :: (Num a) => Series k a -> a+{-# INLINABLE sum #-}+sum = G.sum+++-- | \(O(n)\) Compute the product of the elements.+product :: (Num a) => Series k a -> a+{-# INLINABLE product #-}+product = G.product+++-- | \(O(n)\) Yield the maximum element of the series. In case of a tie, the first occurrence wins.+-- If the 'Series' is empty, @Nothing@ is returned.+--+-- See also 'argmax'.+maximum :: (Ord a) => Series k a -> Maybe a+{-# INLINABLE maximum #-}+maximum = G.maximum+++-- | \(O(n)\) @'maximumOn' f xs@ teturns the maximum element of the series @xs@, as determined by the function @f@.+-- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned.+maximumOn :: (Ord b) => (a -> b) -> Series k a -> Maybe a+{-# INLINABLE maximumOn #-}+maximumOn = G.maximumOn+++-- | \(O(n)\) Yield the minimum element of the series. In case of a tie, the first occurrence wins.+-- If the 'Series' is empty, @Nothing@ is returned.+--+-- See also 'argmin'.+minimum :: (Ord a) => Series k a -> Maybe a+{-# INLINABLE minimum #-}+minimum = G.minimum+++-- | \(O(n)\) @'minimumOn' f xs@ teturns the minimum element of the series @xs@, as determined by the function @f@.+-- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned.+minimumOn :: (Ord b) => (a -> b) -> Series k a -> Maybe a+{-# INLINABLE minimumOn #-}+minimumOn = G.minimumOn+++-- | \(O(n)\) Find the index of the maximum element in the input series.+-- If the input series is empty, 'Nothing' is returned.+--+-- The index of the first occurrence of the maximum element is returned.+--+-- >>> :{ +--     let (xs :: Series Int Int) +--          = Series.fromList [ (1, 0)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 7)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in argmax xs +-- :}+-- Just 4+argmax :: Ord a => Series k a -> Maybe k+argmax = G.argmax+{-# INLINABLE argmax #-}+++-- | \(O(n)\) Find the index of the minimum element in the input series.+-- If the input series is empty, 'Nothing' is returned.+--+-- The index of the first occurrence of the minimum element is returned.+-- >>> :{ +--     let (xs :: Series Int Int) +--          = Series.fromList [ (1, 1)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 0)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in argmin xs +-- :}+-- Just 4+argmin :: Ord a => Series k a -> Maybe k+argmin = G.argmin+{-# INLINABLE argmin #-}+++-- | \(O(n)\) Left-to-right postscan.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int+-- >>> xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      3+--     3 |      4+-- >>> postscanl (+) 0 xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      3+--     2 |      6+--     3 |     10+postscanl :: (a -> b -> a) -> a -> Series k b -> Series k a+{-# INLINABLE postscanl #-}+postscanl = G.postscanl+++-- | \(O(n)\) Left-to-right prescan.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int+-- >>> xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      3+--     3 |      4+-- >>> prescanl (+) 0 xs+-- index | values+-- ----- | ------+--     0 |      0+--     1 |      1+--     2 |      3+--     3 |      6+prescanl :: (a -> b -> a) -> a -> Series k b -> Series k a+{-# INLINABLE prescanl #-}+prescanl = G.prescanl+++-- | Display a 'Series' using default 'DisplayOptions'.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int+-- >>> putStrLn $ display xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      3+--   ... |    ...+--     4 |      5+--     5 |      6+--     6 |      7+display :: (Show k, Show a) +        => Series k a +        -> String+display = G.display+++-- | Display a 'Series' using customizable 'DisplayOptions'.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int+-- >>> import Data.List (replicate)+-- >>> :{+--     let opts = DisplayOptions { maximumNumberOfRows  = 4+--                               , indexHeader = "keys"+--                               , valuesHeader = "vals"+--                               , keyDisplayFunction   = (\i -> replicate i 'x') `noLongerThan` 5+--                               , valueDisplayFunction = (\i -> replicate i 'o') +--                               }+--      in putStrLn $ displayWith opts xs+-- :}+--   keys |    vals+--  ----- |  ------+--        |       o+--      x |      oo+--    ... |     ...+--  xxxxx |  oooooo+-- xxx... | ooooooo+displayWith :: DisplayOptions k a+            -> Series k a +            -> String displayWith = G.displayWith
src/Data/Series/Generic.hs view
@@ -1,98 +1,98 @@-{-# LANGUAGE NoImplicitPrelude #-}
------------------------------------------------------------------------------
--- |
--- Module      :  Data.Series.Generic
--- Copyright   :  (c) Laurent P. René de Cotret
--- License     :  MIT
--- Maintainer  :  laurent.decotret@outlook.com
--- Portability :  portable
---
--- This module contains data structures and functions to work with any type of 'Series', 
--- including boxed and unboxed types.
---
--- Use the definitions in this module if you want to support all types of 'Series' at once.
-module Data.Series.Generic (
-    -- * Definition
-    Series(index, values),
-    convert,
-
-    -- * Building/converting 'Series'
-    singleton, fromIndex,
-    -- ** Lists
-    fromList, toList,
-    -- ** Vectors
-    fromVector, toVector,
-    -- ** Handling duplicates
-    Occurrence, fromListDuplicates, fromVectorDuplicates,
-    -- ** Strict Maps
-    fromStrictMap, toStrictMap,
-    -- ** Lazy Maps
-    fromLazyMap, toLazyMap,
-    -- ** Ad-hoc conversion with other data structures
-    IsSeries(..),
-
-    -- * Mapping and filtering
-    map, mapWithKey, mapIndex, concatMap, filter, filterWithKey, 
-    take, takeWhile, drop, dropWhile,
-    -- ** Mapping with effects
-    mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_, traverseWithKey,
-
-    -- * Folding
-    fold, foldM, foldWithKey, foldMWithKey, foldMap, foldMapWithKey,
-    -- ** Specialized folds
-    mean, variance, std, 
-    length, null, all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn,
-    argmax, argmin,
-
-    -- * Scans
-    postscanl, prescanl, forwardFill,
-
-    -- * Combining series
-    zipWith, zipWithMatched, zipWithKey,
-    zipWith3, zipWithMatched3, zipWithKey3,
-    ZipStrategy, skipStrategy, mapStrategy, constStrategy, zipWithStrategy, zipWithStrategy3,
-    zipWithMonoid, esum, eproduct, unzip, unzip3,
-
-    -- * Index manipulation
-    require, requireWith, catMaybes, dropIndex,
-
-    -- * Accessors
-    -- ** Bulk access
-    select, selectWhere, Range, to, from, upto, Selection, 
-    -- ** Single-element access
-    at, iat,
-
-    -- * Replacement
-    replace, (|->), (<-|),
-
-    -- * Grouping and windowing operations
-    groupBy, Grouping, aggregateWith, foldWith, 
-    windowing, expanding,
-
-    -- * Displaying 'Series'
-    display, displayWith,
-    noLongerThan,
-    DisplayOptions(..), defaultDisplayOptions
-) where
-
-import Control.Foldl                    ( mean, variance, std )
-import Data.Series.Generic.Aggregation  ( groupBy, Grouping, aggregateWith, foldWith
-                                        , windowing, expanding, all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn
-                                        , argmax, argmin,
-                                        )
-import Data.Series.Generic.Definition   ( Series(index, values), IsSeries(..), Occurrence, convert, singleton, fromIndex, fromStrictMap
-                                        , toStrictMap, fromLazyMap, toLazyMap, fromList, fromListDuplicates, toList
-                                        , fromVector, fromVectorDuplicates, toVector
-                                        , map, mapWithKey, mapIndex, concatMap, length, null, take, takeWhile, drop, dropWhile
-                                        , mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_, traverseWithKey, fold, foldM
-                                        , foldWithKey, foldMWithKey, foldMap, foldMapWithKey
-                                        , display, displayWith, noLongerThan, DisplayOptions(..), defaultDisplayOptions
-                                        )
-import Data.Series.Generic.Scans        ( postscanl, prescanl, forwardFill )
-import Data.Series.Generic.View         ( Range, Selection, at, iat, select, selectWhere, to, from, upto, filter, filterWithKey, require, requireWith
-                                        , catMaybes, dropIndex,
-                                        )
-import Data.Series.Generic.Zip          ( zipWith, zipWithMatched, zipWithKey, zipWith3, zipWithMatched3, zipWithKey3, replace
-                                        , (|->), (<-|), zipWithStrategy, zipWithStrategy3, ZipStrategy, skipStrategy, mapStrategy, constStrategy
-                                        , zipWithMonoid, esum, eproduct, unzip, unzip3
-                                        )
+{-# LANGUAGE NoImplicitPrelude #-}+-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Series.Generic+-- Copyright   :  (c) Laurent P. René de Cotret+-- License     :  MIT+-- Maintainer  :  laurent.decotret@outlook.com+-- Portability :  portable+--+-- This module contains data structures and functions to work with any type of 'Series', +-- including boxed and unboxed types.+--+-- Use the definitions in this module if you want to support all types of 'Series' at once.+module Data.Series.Generic (+    -- * Definition+    Series(index, values),+    convert,++    -- * Building/converting 'Series'+    singleton, fromIndex,+    -- ** Lists+    fromList, toList,+    -- ** Vectors+    fromVector, toVector,+    -- ** Handling duplicates+    Occurrence, fromListDuplicates, fromVectorDuplicates,+    -- ** Strict Maps+    fromStrictMap, toStrictMap,+    -- ** Lazy Maps+    fromLazyMap, toLazyMap,+    -- ** Ad-hoc conversion with other data structures+    IsSeries(..),++    -- * Mapping and filtering+    map, mapWithKey, mapIndex, concatMap, filter, filterWithKey, +    take, takeWhile, drop, dropWhile,+    -- ** Mapping with effects+    mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_, traverseWithKey,++    -- * Folding+    fold, foldM, foldWithKey, foldMWithKey, foldMap, foldMapWithKey,+    -- ** Specialized folds+    mean, variance, std, +    length, null, all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn,+    argmax, argmin,++    -- * Scans+    postscanl, prescanl, forwardFill,++    -- * Combining series+    zipWith, zipWithMatched, zipWithKey,+    zipWith3, zipWithMatched3, zipWithKey3,+    ZipStrategy, skipStrategy, mapStrategy, constStrategy, zipWithStrategy, zipWithStrategy3,+    zipWithMonoid, esum, eproduct, unzip, unzip3,++    -- * Index manipulation+    require, requireWith, catMaybes, dropIndex,++    -- * Accessors+    -- ** Bulk access+    select, selectWhere, Range, to, from, upto, Selection, +    -- ** Single-element access+    at, iat,++    -- * Replacement+    replace, (|->), (<-|),++    -- * Grouping and windowing operations+    groupBy, Grouping, aggregateWith, foldWith, +    windowing, expanding,++    -- * Displaying 'Series'+    display, displayWith,+    noLongerThan,+    DisplayOptions(..), defaultDisplayOptions+) where++import Control.Foldl                    ( mean, variance, std )+import Data.Series.Generic.Aggregation  ( groupBy, Grouping, aggregateWith, foldWith+                                        , windowing, expanding, all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn+                                        , argmax, argmin,+                                        )+import Data.Series.Generic.Definition   ( Series(index, values), IsSeries(..), Occurrence, convert, singleton, fromIndex, fromStrictMap+                                        , toStrictMap, fromLazyMap, toLazyMap, fromList, fromListDuplicates, toList+                                        , fromVector, fromVectorDuplicates, toVector+                                        , map, mapWithKey, mapIndex, concatMap, length, null, take, takeWhile, drop, dropWhile+                                        , mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_, traverseWithKey, fold, foldM+                                        , foldWithKey, foldMWithKey, foldMap, foldMapWithKey+                                        , display, displayWith, noLongerThan, DisplayOptions(..), defaultDisplayOptions+                                        )+import Data.Series.Generic.Scans        ( postscanl, prescanl, forwardFill )+import Data.Series.Generic.View         ( Range, Selection, at, iat, select, selectWhere, to, from, upto, filter, filterWithKey, require, requireWith+                                        , catMaybes, dropIndex,+                                        )+import Data.Series.Generic.Zip          ( zipWith, zipWithMatched, zipWithKey, zipWith3, zipWithMatched3, zipWithKey3, replace+                                        , (|->), (<-|), zipWithStrategy, zipWithStrategy3, ZipStrategy, skipStrategy, mapStrategy, constStrategy+                                        , zipWithMonoid, esum, eproduct, unzip, unzip3+                                        )
src/Data/Series/Generic/Aggregation.hs view
@@ -1,326 +1,332 @@-module Data.Series.Generic.Aggregation ( 
-    -- * Grouping
-    Grouping,
-    groupBy,
-    aggregateWith,
-    foldWith,
-
-    -- * Windowing
-    expanding,
-    windowing,
-
-    -- * Folding
-    all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn,
-    argmax, argmin,
-) where
-
-import qualified Data.List 
-import qualified Data.Map.Strict                as Map
-import           Data.Ord                       ( Down(..) )
-import           Data.Series.Generic.Definition ( Series(..) )
-import qualified Data.Series.Generic.Definition as GSeries
-import           Data.Series.Generic.View       ( Range, slice, select )
-import qualified Data.Vector                    as Boxed
-import           Data.Vector.Generic            ( Vector )
-import qualified Data.Vector.Generic            as Vector
-import           Prelude                        hiding ( last, null, length, all, any, and, or, sum, product, maximum, minimum )
-
--- $setup
--- >>> import qualified Data.Series as Series
--- >>> import qualified Data.Set as Set
-
--- | Group values in a 'Series' by some grouping function (@k -> g@).
--- The provided grouping function is guaranteed to operate on a non-empty 'Series'.
---
--- This function is expected to be used in conjunction with @aggregate@:
--- 
--- >>> import Data.Maybe ( fromMaybe )
--- >>> type Date = (Int, String)
--- >>> month :: (Date -> String) = snd
--- >>> :{ 
---     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)
---                              , ((2021, "January"), -5)
---                              , ((2020, "June")   , 20)
---                              , ((2021, "June")   , 25) 
---                              ]
---      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)
--- :}
---     index | values
---     ----- | ------
--- "January" |     -5
---    "June" |     20
-groupBy :: Series v k a       -- ^ Input series
-        -> (k -> g)           -- ^ Grouping function
-        -> Grouping k g v a   -- ^ Grouped series
-{-# INLINABLE groupBy #-}
-groupBy = MkGrouping
-
-
--- | Representation of a 'Series' being grouped.
-data Grouping k g v a 
-    = MkGrouping (Series v k a)  (k -> g)
-
-
--- | Aggregate groups resulting from a call to 'groupBy':
--- 
--- >>> import Data.Maybe ( fromMaybe )
--- >>> type Date = (Int, String)
--- >>> month :: (Date -> String) = snd
--- >>> :{ 
---     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)
---                              , ((2021, "January"), -5)
---                              , ((2020, "June")   , 20)
---                              , ((2021, "June")   , 25) 
---                              ]
---      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)
--- :}
---     index | values
---     ----- | ------
--- "January" |     -5
---    "June" |     20
---
--- If you want to aggregate groups using a binary function, see 'foldWith' which
--- may be much faster.
-aggregateWith :: (Ord g, Vector v a, Vector v b) 
-              => Grouping k g v a 
-              -> (Series v k a -> b) 
-              -> Series v g b
-{-# INLINABLE aggregateWith #-}
-aggregateWith (MkGrouping xs by) f
-    = GSeries.fromStrictMap 
-    $ fmap (f . GSeries.fromDistinctAscList)
-    -- We're using a list fold to limit the number of 
-    -- type constraints. This is about as fast as it is 
-    -- with a Vector fold
-    $ Data.List.foldl' acc mempty 
-    $ GSeries.toList xs
-    where
-        acc !m (key, val) = Map.insertWith (<>) (by key) (Data.List.singleton (key, val)) m
-
-
--- | Fold over each group in a 'Grouping' using a binary function.
--- While this is not as expressive as 'aggregateWith', users looking for maximum
--- performance should use 'foldWith' as much as possible.
---
--- >>> type Date = (Int, String)
--- >>> month :: (Date -> String) = snd
--- >>> :{ 
---     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)
---                              , ((2021, "January"), -5)
---                              , ((2020, "June")   , 20)
---                              , ((2021, "June")   , 25) 
---                              ]
---      in xs `groupBy` month `foldWith` min
--- :}
---     index | values
---     ----- | ------
--- "January" |     -5
---    "June" |     20
-foldWith :: (Ord g, Vector v a) 
-         => Grouping k g v a
-         -> (a -> a -> a)
-         -> Series v g a
-{-# INLINABLE foldWith #-}
-foldWith (MkGrouping xs by) f 
-    = GSeries.fromStrictMap 
-    -- We're using a list fold to limit the number of 
-    -- type constraints. This is about as fast as it is 
-    -- with a Vector fold
-    $ Data.List.foldl' acc mempty 
-    $ GSeries.toList xs
-    where
-        acc !m (key, val) = Map.insertWith f (by key) val m
-
-
--- | Expanding window aggregation.
---
--- >>> import qualified Data.Series as Series 
--- >>> :{ 
---     let (xs :: Series.Series Int Int) 
---          = Series.fromList [ (1, 0)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 3)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in (xs `expanding` sum) :: Series.Series Int Int 
--- :}
--- index | values
--- ----- | ------
---     1 |      0
---     2 |      1
---     3 |      3
---     4 |      6
---     5 |     10
---     6 |     15
-expanding :: (Vector v a, Vector v b) 
-          => Series v k a        -- ^ Series vector
-          -> (Series v k a -> b) -- ^ Aggregation function
-          -> Series v k b        -- ^ Resulting vector
-{-# INLINABLE expanding #-}
-expanding vs f = MkSeries (index vs) $ Vector.unfoldrExactN (GSeries.length vs) go 0
-    where
-        -- Recall that `slice` does NOT include the right index
-        go ix = (f $ slice 0 (ix + 1) vs, ix + 1)
-
-
--- | General-purpose window aggregation.
---
--- >>> import qualified Data.Series as Series 
--- >>> import           Data.Series ( to )
--- >>> :{ 
---     let (xs :: Series.Series Int Int) 
---          = Series.fromList [ (1, 0)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 3)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in windowing (\k -> k `to` (k + 2)) sum xs
--- :}
--- index | values
--- ----- | ------
---     1 |      3
---     2 |      6
---     3 |      9
---     4 |     12
---     5 |      9
---     6 |      5
-windowing :: (Ord k, Vector v a, Vector v b)
-          => (k -> Range k)
-          -> (Series v k a -> b)
-          -> Series v k a
-          -> Series v k b
-{-# INLINABLE windowing #-}
-windowing range agg series 
-    = GSeries.mapWithKey (\k _ -> agg $ series `select` range k) series
-
-
--- | \(O(n)\) Check if all elements satisfy the predicate.
-all :: Vector v a => (a -> Bool) -> Series v k a -> Bool
-{-# INLINABLE all #-}
-all f = Vector.all f . values
-
-
--- | \(O(n)\) Check if any element satisfies the predicate.
-any :: Vector v a => (a -> Bool) -> Series v k a -> Bool
-{-# INLINABLE any #-}
-any f = Vector.any f . values
-
-
--- | \(O(n)\) Check if all elements are 'True'.
-and :: Vector v Bool => Series v k Bool -> Bool
-{-# INLINABLE and #-}
-and = Vector.and . values
-
-
--- | \(O(n)\) Check if any element is 'True'.
-or :: Vector v Bool => Series v k Bool -> Bool
-{-# INLINABLE or #-}
-or = Vector.or . values
-
-
--- | \(O(n)\) Compute the sum of the elements.
-sum :: (Num a, Vector v a) => Series v k a -> a
-{-# INLINABLE sum #-}
-sum = Vector.sum . values
-
-
--- | \(O(n)\) Compute the product of the elements.
-product :: (Num a, Vector v a) => Series v k a -> a
-{-# INLINABLE product #-}
-product = Vector.product . values
-
-
-nothingIfEmpty :: Vector v a 
-               => (Series v k a -> b) -> (Series v k a -> Maybe b)
-nothingIfEmpty f xs = if GSeries.null xs then Nothing else Just (f xs) 
-
-
--- | \(O(n)\) Yield the maximum element of the series. In case of a tie, the first occurrence wins.
-maximum :: (Ord a, Vector v a) => Series v k a -> Maybe a
-{-# INLINABLE maximum #-}
-maximum = nothingIfEmpty $ Vector.maximum . values
-
-
--- | \(O(n)\) @'maximumOn' f xs@ teturns the maximum element of the series @xs@, as determined by the function @f@.
--- In case of a tie, the first occurrence wins.
--- If the 'Series' is empty, @Nothing@ is returned.
-maximumOn :: (Ord b, Vector v a) => (a -> b) -> Series v k a -> Maybe a
-{-# INLINABLE maximumOn #-}
-maximumOn f = nothingIfEmpty $ Vector.maximumOn f . values
-
-
--- | \(O(n)\) Yield the minimum element of the series. In case of a tie, the first occurrence wins.
--- If the 'Series' is empty, @Nothing@ is returned.
-minimum :: (Ord a, Vector v a) => Series v k a -> Maybe a
-{-# INLINABLE minimum #-}
-minimum = nothingIfEmpty $ Vector.minimum . values
-
-
--- | \(O(n)\) @'minimumOn' f xs@ teturns the minimum element of the series @xs@, as determined by the function @f@.
--- In case of a tie, the first occurrence wins.
--- If the 'Series' is empty, @Nothing@ is returned.
-minimumOn :: (Ord b, Vector v a) => (a -> b) -> Series v k a -> Maybe a
-{-# INLINABLE minimumOn #-}
-minimumOn f = nothingIfEmpty $ Vector.minimumOn f . values
-
-
--- | \(O(n)\) Find the index of the maximum element in the input series.
--- If the input series is empty, 'Nothing' is returned.
---
--- The index of the first occurrence of the maximum element is returned.
---
--- >>> import qualified Data.Series as Series 
--- >>> :{ 
---     let (xs :: Series.Series Int Int) 
---          = Series.fromList [ (1, 0)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 7)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in argmax xs 
--- :}
--- Just 4
-argmax :: (Ord a, Vector v a)
-       => Series v k a
-       -> Maybe k
-{-# INLINABLE argmax #-}
-argmax xs | GSeries.null xs = Nothing
-          | otherwise = Just 
-                      . fst 
-                      -- We're forcing the use of boxed vectors in order to
-                      -- reduce the constraints on the vector instance
-                      . Boxed.maximumOn snd 
-                      . GSeries.toVector
-                      . GSeries.convert
-                      $ xs
-
-
--- | \(O(n)\) Find the index of the minimum element in the input series.
--- If the input series is empty, 'Nothing' is returned.
---
--- The index of the first occurrence of the minimum element is returned.
---
--- >>> import qualified Data.Series as Series 
--- >>> :{ 
---     let (xs :: Series.Series Int Int) 
---          = Series.fromList [ (1, 1)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 0)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in argmin xs 
--- :}
--- Just 4
-argmin :: (Ord a, Vector v a, Vector v (Down a))
-       => Series v k a
-       -> Maybe k
-{-# INLINABLE argmin #-}
+module Data.Series.Generic.Aggregation ( +    -- * Grouping+    Grouping,+    groupBy,+    aggregateWith,+    foldWith,++    -- * Windowing+    expanding,+    windowing,++    -- * Folding+    all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn,+    argmax, argmin,+) where++import qualified Data.List +import qualified Data.Map.Strict                as Map+import           Data.Ord                       ( Down(..) )+import           Data.Series.Generic.Definition ( Series(..) )+import qualified Data.Series.Generic.Definition as GSeries+import           Data.Series.Generic.View       ( Range, slice, select )+import qualified Data.Vector                    as Boxed+import           Data.Vector.Generic            ( Vector )+import qualified Data.Vector.Generic            as Vector+import           Prelude                        hiding ( last, null, length, all, any, and, or, sum, product, maximum, minimum )++-- $setup+-- >>> import qualified Data.Series as Series+-- >>> import qualified Data.Set as Set++-- | Group values in a 'Series' by some grouping function (@k -> g@).+-- The provided grouping function is guaranteed to operate on a non-empty 'Series'.+--+-- This function is expected to be used in conjunction with @aggregate@:+-- +-- >>> import Data.Maybe ( fromMaybe )+-- >>> type Date = (Int, String)+-- >>> month :: (Date -> String) = snd+-- >>> :{ +--     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)+--                              , ((2021, "January"), -5)+--                              , ((2020, "June")   , 20)+--                              , ((2021, "June")   , 25) +--                              ]+--      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)+-- :}+--     index | values+--     ----- | ------+-- "January" |     -5+--    "June" |     20+groupBy :: Series v k a       -- ^ Input series+        -> (k -> g)           -- ^ Grouping function+        -> Grouping k g v a   -- ^ Grouped series+{-# INLINABLE groupBy #-}+groupBy = MkGrouping+++-- | Representation of a 'Series' being grouped.+data Grouping k g v a +    = MkGrouping (Series v k a)  (k -> g)+++-- | Aggregate groups resulting from a call to 'groupBy':+-- +-- >>> import Data.Maybe ( fromMaybe )+-- >>> type Date = (Int, String)+-- >>> month :: (Date -> String) = snd+-- >>> :{ +--     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)+--                              , ((2021, "January"), -5)+--                              , ((2020, "June")   , 20)+--                              , ((2021, "June")   , 25) +--                              ]+--      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)+-- :}+--     index | values+--     ----- | ------+-- "January" |     -5+--    "June" |     20+--+-- If you want to aggregate groups using a binary function, see 'foldWith' which+-- may be much faster.+aggregateWith :: (Ord g, Vector v a, Vector v b) +              => Grouping k g v a +              -> (Series v k a -> b) +              -> Series v g b+{-# INLINABLE aggregateWith #-}+aggregateWith (MkGrouping xs by) f+    = GSeries.fromStrictMap +    -- Using `fromDistinctAscList` is predicated on a particular structure+    -- created by the `acc` function below.+    -- This is rather unsafe, and has been the source of bugs in the past+    $ fmap (f . GSeries.fromDistinctAscList)+    -- We're using a list fold to limit the number of +    -- type constraints. This is about as fast as it is +    -- with a Vector fold+    $ Data.List.foldl' acc mempty +    $ GSeries.toList xs+    where+        acc !m (key, val) = Map.insertWith (flip (<>)) -- Flipping arguments to ensure that keys are ordered as expected+                                           (by key) +                                           (Data.List.singleton (key, val)) +                                           m+++-- | Fold over each group in a 'Grouping' using a binary function.+-- While this is not as expressive as 'aggregateWith', users looking for maximum+-- performance should use 'foldWith' as much as possible.+--+-- >>> type Date = (Int, String)+-- >>> month :: (Date -> String) = snd+-- >>> :{ +--     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)+--                              , ((2021, "January"), -5)+--                              , ((2020, "June")   , 20)+--                              , ((2021, "June")   , 25) +--                              ]+--      in xs `groupBy` month `foldWith` min+-- :}+--     index | values+--     ----- | ------+-- "January" |     -5+--    "June" |     20+foldWith :: (Ord g, Vector v a) +         => Grouping k g v a+         -> (a -> a -> a)+         -> Series v g a+{-# INLINABLE foldWith #-}+foldWith (MkGrouping xs by) f +    = GSeries.fromStrictMap +    -- We're using a list fold to limit the number of +    -- type constraints. This is about as fast as it is +    -- with a Vector fold+    $ Data.List.foldl' acc mempty +    $ GSeries.toList xs+    where+        acc !m (key, val) = Map.insertWith f (by key) val m+++-- | Expanding window aggregation.+--+-- >>> import qualified Data.Series as Series +-- >>> :{ +--     let (xs :: Series.Series Int Int) +--          = Series.fromList [ (1, 0)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 3)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in (xs `expanding` sum) :: Series.Series Int Int +-- :}+-- index | values+-- ----- | ------+--     1 |      0+--     2 |      1+--     3 |      3+--     4 |      6+--     5 |     10+--     6 |     15+expanding :: (Vector v a, Vector v b) +          => Series v k a        -- ^ Series vector+          -> (Series v k a -> b) -- ^ Aggregation function+          -> Series v k b        -- ^ Resulting vector+{-# INLINABLE expanding #-}+expanding vs f = MkSeries (index vs) $ Vector.unfoldrExactN (GSeries.length vs) go 0+    where+        -- Recall that `slice` does NOT include the right index+        go ix = (f $ slice 0 (ix + 1) vs, ix + 1)+++-- | General-purpose window aggregation.+--+-- >>> import qualified Data.Series as Series +-- >>> import           Data.Series ( to )+-- >>> :{ +--     let (xs :: Series.Series Int Int) +--          = Series.fromList [ (1, 0)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 3)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in windowing (\k -> k `to` (k + 2)) sum xs+-- :}+-- index | values+-- ----- | ------+--     1 |      3+--     2 |      6+--     3 |      9+--     4 |     12+--     5 |      9+--     6 |      5+windowing :: (Ord k, Vector v a, Vector v b)+          => (k -> Range k)+          -> (Series v k a -> b)+          -> Series v k a+          -> Series v k b+{-# INLINABLE windowing #-}+windowing range agg series +    = GSeries.mapWithKey (\k _ -> agg $ series `select` range k) series+++-- | \(O(n)\) Check if all elements satisfy the predicate.+all :: Vector v a => (a -> Bool) -> Series v k a -> Bool+{-# INLINABLE all #-}+all f = Vector.all f . values+++-- | \(O(n)\) Check if any element satisfies the predicate.+any :: Vector v a => (a -> Bool) -> Series v k a -> Bool+{-# INLINABLE any #-}+any f = Vector.any f . values+++-- | \(O(n)\) Check if all elements are 'True'.+and :: Vector v Bool => Series v k Bool -> Bool+{-# INLINABLE and #-}+and = Vector.and . values+++-- | \(O(n)\) Check if any element is 'True'.+or :: Vector v Bool => Series v k Bool -> Bool+{-# INLINABLE or #-}+or = Vector.or . values+++-- | \(O(n)\) Compute the sum of the elements.+sum :: (Num a, Vector v a) => Series v k a -> a+{-# INLINABLE sum #-}+sum = Vector.sum . values+++-- | \(O(n)\) Compute the product of the elements.+product :: (Num a, Vector v a) => Series v k a -> a+{-# INLINABLE product #-}+product = Vector.product . values+++nothingIfEmpty :: Vector v a +               => (Series v k a -> b) -> (Series v k a -> Maybe b)+nothingIfEmpty f xs = if GSeries.null xs then Nothing else Just (f xs) +++-- | \(O(n)\) Yield the maximum element of the series. In case of a tie, the first occurrence wins.+maximum :: (Ord a, Vector v a) => Series v k a -> Maybe a+{-# INLINABLE maximum #-}+maximum = nothingIfEmpty $ Vector.maximum . values+++-- | \(O(n)\) @'maximumOn' f xs@ teturns the maximum element of the series @xs@, as determined by the function @f@.+-- In case of a tie, the first occurrence wins.+-- If the 'Series' is empty, @Nothing@ is returned.+maximumOn :: (Ord b, Vector v a) => (a -> b) -> Series v k a -> Maybe a+{-# INLINABLE maximumOn #-}+maximumOn f = nothingIfEmpty $ Vector.maximumOn f . values+++-- | \(O(n)\) Yield the minimum element of the series. In case of a tie, the first occurrence wins.+-- If the 'Series' is empty, @Nothing@ is returned.+minimum :: (Ord a, Vector v a) => Series v k a -> Maybe a+{-# INLINABLE minimum #-}+minimum = nothingIfEmpty $ Vector.minimum . values+++-- | \(O(n)\) @'minimumOn' f xs@ teturns the minimum element of the series @xs@, as determined by the function @f@.+-- In case of a tie, the first occurrence wins.+-- If the 'Series' is empty, @Nothing@ is returned.+minimumOn :: (Ord b, Vector v a) => (a -> b) -> Series v k a -> Maybe a+{-# INLINABLE minimumOn #-}+minimumOn f = nothingIfEmpty $ Vector.minimumOn f . values+++-- | \(O(n)\) Find the index of the maximum element in the input series.+-- If the input series is empty, 'Nothing' is returned.+--+-- The index of the first occurrence of the maximum element is returned.+--+-- >>> import qualified Data.Series as Series +-- >>> :{ +--     let (xs :: Series.Series Int Int) +--          = Series.fromList [ (1, 0)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 7)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in argmax xs +-- :}+-- Just 4+argmax :: (Ord a, Vector v a)+       => Series v k a+       -> Maybe k+{-# INLINABLE argmax #-}+argmax xs | GSeries.null xs = Nothing+          | otherwise = Just +                      . fst +                      -- We're forcing the use of boxed vectors in order to+                      -- reduce the constraints on the vector instance+                      . Boxed.maximumOn snd +                      . GSeries.toVector+                      . GSeries.convert+                      $ xs+++-- | \(O(n)\) Find the index of the minimum element in the input series.+-- If the input series is empty, 'Nothing' is returned.+--+-- The index of the first occurrence of the minimum element is returned.+--+-- >>> import qualified Data.Series as Series +-- >>> :{ +--     let (xs :: Series.Series Int Int) +--          = Series.fromList [ (1, 1)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 0)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in argmin xs +-- :}+-- Just 4+argmin :: (Ord a, Vector v a, Vector v (Down a))+       => Series v k a+       -> Maybe k+{-# INLINABLE argmin #-} argmin = argmax . GSeries.map Down
src/Data/Series/Generic/Definition.hs view
@@ -1,832 +1,832 @@-{-# LANGUAGE DerivingStrategies    #-}
-{-# LANGUAGE QuantifiedConstraints #-}
-{-# LANGUAGE RecordWildCards       #-}
-{-# LANGUAGE TypeFamilies          #-}
-{-# LANGUAGE UndecidableInstances  #-}
-
-module Data.Series.Generic.Definition ( 
-    Series(..),
-
-    convert,
-
-    -- * Basic interface
-    singleton,
-    headM, lastM, map, mapWithKey, mapIndex, concatMap, fold, foldM, 
-    foldWithKey, foldMWithKey, foldMap, bifoldMap, foldMapWithKey, 
-    length, null, take, takeWhile, drop, dropWhile,
-    mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_,
-    traverseWithKey,
-
-    fromIndex,
-    -- * Conversion to/from Series
-    IsSeries(..),
-    -- ** Conversion to/from Maps
-    fromStrictMap,
-    toStrictMap,
-    fromLazyMap,
-    toLazyMap,
-    -- ** Conversion to/from list
-    fromList,
-    toList,
-    -- *** Unsafe construction
-    fromDistinctAscList,
-    -- ** Conversion to/from vectors
-    fromVector,
-    toVector,
-    -- *** Unsafe construction
-    fromDistinctAscVector,
-    -- ** Handling duplicates
-    Occurrence, fromListDuplicates, fromVectorDuplicates,
-
-    -- * Displaying 'Series'
-    display, displayWith,
-    noLongerThan,
-    DisplayOptions(..), defaultDisplayOptions
-) where
-
-import           Control.DeepSeq        ( NFData(rnf) )
-import           Control.Foldl          ( Fold(..), FoldM(..) )
-import           Control.Monad.ST       ( runST )
-import           Data.Bifoldable        ( Bifoldable )
-import qualified Data.Bifoldable        as Bifoldable
-import qualified Data.Foldable          as Foldable
-import           Data.Foldable.WithIndex ( FoldableWithIndex(..))
-import           Data.Function          ( on )
-import           Data.Functor.WithIndex ( FunctorWithIndex(imap) )
-
-import           Data.IntMap.Strict     ( IntMap )
-import qualified Data.IntMap.Strict     as IntMap
-import qualified Data.List              as List
-import qualified Data.Map.Lazy          as ML
-import           Data.Map.Strict        ( Map )
-import qualified Data.Map.Strict        as MS
-import           Data.Sequence          ( Seq )
-import qualified Data.Sequence          as Seq
-import           Data.Semigroup         ( Semigroup(..) )
-import           Data.Series.Index      ( Index )
-import qualified Data.Series.Index      as Index
-import qualified Data.Series.Index.Internal as Index.Internal
-import           Data.Set               ( Set )
-import qualified Data.Set               as Set
-import           Data.Traversable.WithIndex ( TraversableWithIndex(..) )
-import qualified Data.Vector            as Boxed
-import           Data.Vector.Algorithms.Intro ( sortUniqBy, sortBy )
-import           Data.Vector.Generic    ( Vector )
-import qualified Data.Vector.Generic    as Vector
-import qualified Data.Vector.Generic.Mutable as GM
-import qualified Data.Vector.Unboxed         as U
-import qualified Data.Vector.Unboxed.Mutable as UM
- 
-import           Prelude                hiding ( take, takeWhile, drop, dropWhile, map, concatMap, foldMap, sum, length, null )
-import qualified Prelude                as P
-
-
-
--- | A @Series v k a@ is a labeled array of type @v@ filled with values of type @a@,
--- indexed by keys of type @k@.
---
--- Like 'Data.Map.Strict.Map', they support efficient:
---
---      * random access by key ( \(O(\log n)\) );
---      * slice by key ( \(O(\log n)\) ).
---
--- Like 'Data.Vector.Vector', they support efficient:
---
---      * random access by index ( \(O(1)\) );
---      * slice by index ( \(O(1)\) );
---      * numerical operations.
---
-data Series v k a 
-    -- The reason the index is a set of keys is that we *want* keys to be ordered.
-    -- This allows for efficient slicing of the underlying values, because
-    -- if @k1 < k2@, then the values are also at indices @ix1 < ix2@.
-    = MkSeries { index  :: Index k -- ^ The 'Index' of a series, which contains its (unique) keys in ascending order.
-               , values :: v a     -- ^ The values of a series, in the order of its (unique) keys.
-               }
-
-
--- | \(O(n)\) Convert between two types of 'Series'.
-convert :: (Vector v1 a, Vector v2 a) => Series v1 k a -> Series v2 k a
-{-# INLINABLE convert #-}
-convert (MkSeries ix vs) = MkSeries ix $ Vector.convert vs 
-
-
--- | \(O(1)\) Create a 'Series' with a single element.
-singleton :: Vector v a => k -> a -> Series v k a
-{-# INLINABLE singleton #-}
-singleton k v = MkSeries (Index.singleton k) $ Vector.singleton v
-
-
--- | \(O(n)\) Generate a 'Series' by mapping every element of its index.
-fromIndex :: (Vector v a) 
-          => (k -> a) -> Index k -> Series v k a
-{-# INLINABLE fromIndex #-}
-fromIndex f ix = MkSeries ix $ Vector.convert 
-                             $ Boxed.map f -- Using boxed vector to prevent a (Vector v k) constraint
-                             $ Index.toAscVector ix
-
-
--- | The 'IsSeries' typeclass allow for ad-hoc definition
--- of conversion functions, converting to / from 'Series'.
-class IsSeries t v k a where
-    -- | Construct a 'Series' from some container of key-values pairs. There is no
-    -- condition on the order of pairs. Duplicate keys are silently dropped. If you
-    -- need to handle duplicate keys, see 'fromListDuplicates' or 'fromVectorDuplicates'.
-    toSeries    :: t -> Series v k a
-
-    -- | Construct a container from key-value pairs of a 'Series'. 
-    -- The elements are returned in ascending order of keys. 
-    fromSeries  :: Series v k a -> t
-
-
-instance (Ord k, Vector v a) => IsSeries [(k, a)] v k a where
-    -- | Construct a series from a list of key-value pairs. There is no
-    -- condition on the order of pairs.
-    --
-    -- >>> let xs = toSeries [('b', 0::Int), ('a', 5), ('d', 1) ]
-    -- >>> xs
-    -- index | values
-    -- ----- | ------
-    --   'a' |      5
-    --   'b' |      0
-    --   'd' |      1
-    --
-    -- If you need to handle duplicate keys, take a look at `fromListDuplicates`.
-    toSeries :: [(k, a)] -> Series v k a
-    toSeries = toSeries . MS.fromList
-    {-# INLINABLE toSeries #-}
-
-    -- | Construct a list from key-value pairs. The elements are in order sorted by key:
-    --
-    -- >>> let xs = Series.toSeries [ ('b', 0::Int), ('a', 5), ('d', 1) ]
-    -- >>> xs
-    -- index | values
-    -- ----- | ------
-    --   'a' |      5
-    --   'b' |      0
-    --   'd' |      1
-    -- >>> fromSeries xs
-    -- [('a',5),('b',0),('d',1)]
-    fromSeries :: Series v k a -> [(k, a)]
-    fromSeries (MkSeries ks vs)= zip (Index.toAscList ks) (Vector.toList vs)
-    {-# INLINABLE fromSeries #-}
-
-
--- | Construct a 'Series' from a list of key-value pairs. There is no
--- condition on the order of pairs. Duplicate keys are silently dropped. If you
--- need to handle duplicate keys, see 'fromListDuplicates'.
-fromList :: (Vector v a, Ord k) => [(k, a)] -> Series v k a
-{-# INLINABLE fromList #-}
-fromList = toSeries
-
-
--- | \(O(n)\) Build a 'Series' from a list of pairs, where the first elements of the pairs (the keys)
--- are distinct elements in ascending order. The precondition that the keys be unique and sorted is not checked.
-fromDistinctAscList :: (Vector v a) => [(k, a)] -> Series v k a
-fromDistinctAscList xs 
-    = let (!ks, !vs) = unzip xs 
-       in MkSeries (Index.Internal.fromDistinctAscList ks) (Vector.fromListN (List.length vs) vs)
-
-
--- | Integer-like, non-negative number that specifies how many occurrences
--- of a key is present in a 'Series'.
---
--- The easiest way to convert from an 'Occurrence' to another integer-like type
--- is the 'fromIntegral' function.
-newtype Occurrence = MkOcc Int
-    deriving (Eq, Enum, Num, Ord, Integral, Real)
-    deriving newtype (Show, U.Unbox) 
-
--- Occurrence needs to be an 'U.Unbox' type
--- so that 'fromVectorDuplicates' works with unboxed vectors
--- and series.
-newtype instance UM.MVector s Occurrence = MV_Occ (UM.MVector s Int)
-newtype instance U.Vector Occurrence = V_Occ (U.Vector Int)
-deriving instance GM.MVector UM.MVector Occurrence
-deriving instance Vector U.Vector Occurrence 
-
-
--- | Construct a series from a list of key-value pairs.
--- Contrary to 'fromList', values at duplicate keys are preserved. To keep each
--- key unique, an 'Occurrence' number counts up.
-fromListDuplicates :: (Vector v a, Ord k) => [(k, a)] -> Series v (k, Occurrence) a
-{-# INLINABLE fromListDuplicates #-}
-fromListDuplicates = convert . fromVectorDuplicates . Boxed.fromList
-
-
--- | Construct a list from key-value pairs. The elements are in order sorted by key. 
-toList :: Vector v a => Series v k a -> [(k, a)]
-{-# INLINABLE toList #-}
-toList (MkSeries ks vs) = zip (Index.toAscList ks) (Vector.toList vs)
-
-
-instance (Ord k) => IsSeries (Boxed.Vector (k, a)) Boxed.Vector k a where
-    toSeries = fromVector
-    {-# INLINABLE toSeries #-}
-
-    fromSeries = toVector
-    {-# INLINABLE fromSeries #-}
-
-
-instance (Ord k, U.Unbox a, U.Unbox k) => IsSeries (U.Vector (k, a)) U.Vector k a where
-    toSeries :: U.Vector (k, a) -> Series U.Vector k a
-    toSeries = fromVector
-    {-# INLINABLE toSeries #-}
-
-    fromSeries :: Series U.Vector k a -> U.Vector (k, a)
-    fromSeries = toVector
-    {-# INLINABLE fromSeries #-}
-
-
--- | Construct a 'Series' from a 'Vector' of key-value pairs. There is no
--- condition on the order of pairs. Duplicate keys are silently dropped. If you
--- need to handle duplicate keys, see 'fromVectorDuplicates'.
---
--- Note that due to differences in sorting,
--- 'Series.fromList' and @'Series.fromVector' . 'Vector.fromList'@
--- may not be equivalent if the input list contains duplicate keys.
-fromVector :: (Ord k, Vector v k, Vector v a, Vector v (k, a))
-           => v (k, a) -> Series v k a
-{-# INLINABLE fromVector #-}
-fromVector vec = let (indexVector, valuesVector) = Vector.unzip $ runST $ do
-                        mv <- Vector.thaw vec
-                        -- Note that we're using this particular flavor of `sortUniqBy`
-                        -- because it both sorts AND removes duplicate keys
-                        destMV <- sortUniqBy (compare `on` fst) mv
-                        v <- Vector.freeze destMV
-                        pure (Vector.force v)
-              in MkSeries (Index.Internal.fromDistinctAscVector indexVector) valuesVector
-
-
--- | \(O(n)\) Build a 'Series' from a vector of pairs, where the first elements of the pairs (the keys)
--- are distinct elements in ascending order. The precondition that the keys be unique and sorted is not checked.
-fromDistinctAscVector :: (Vector v k, Vector v a, Vector v (k, a))
-                      => v (k, a) -> Series v k a
-fromDistinctAscVector xs 
-    = let (ks, vs) = Vector.unzip xs 
-       in MkSeries (Index.Internal.fromDistinctAscVector ks) vs
-
-
--- | Construct a 'Series' from a 'Vector' of key-value pairs, where there may be duplicate keys. 
--- There is no condition on the order of pairs.
-fromVectorDuplicates :: (Ord k, Vector v k, Vector v a, Vector v (k, a), Vector v (k, Occurrence))
-                     => v (k, a) -> Series v (k, Occurrence) a
-{-# INLINABLE fromVectorDuplicates #-}
-fromVectorDuplicates vec 
-    = let (indexVector, valuesVector) 
-            = Vector.unzip $ runST $ do
-                mv <- Vector.thaw vec
-                sortBy (compare `on` fst) mv
-                v <- Vector.freeze mv
-                pure (Vector.force v)
-        in MkSeries (Index.Internal.fromDistinctAscVector (occurences indexVector)) valuesVector
-    where
-        occurences vs 
-            | Vector.null vs        = Vector.empty
-            | Vector.length vs == 1 = Vector.map (,0) vs
-            | otherwise             = Vector.scanl f (Vector.head vs, 0) (Vector.tail vs)
-            where
-                f (lastKey, lastOcc) newKey 
-                    | lastKey == newKey = (newKey, lastOcc + 1)
-                    | otherwise         = (newKey, 0)
-
-
--- | Construct a 'Vector' of key-value pairs. The elements are in order sorted by key. 
-toVector :: (Vector v a, Vector v k, Vector v (k, a)) 
-         => Series v k a -> v (k, a)
-{-# INLINABLE toVector #-}
-toVector (MkSeries ks vs) = Vector.zip (Index.toAscVector ks) vs
-
-
-instance (Vector v a) => IsSeries (Map k a) v k a where
-    toSeries :: Map k a -> Series v k a
-    toSeries mp = MkSeries 
-                { index  = Index.fromSet $ MS.keysSet mp
-                , values = Vector.fromListN (MS.size mp) $ MS.elems mp
-                }
-    {-# INLINABLE toSeries #-}
-
-    fromSeries :: Series v k a -> Map k a
-    fromSeries (MkSeries ks vs)
-        = MS.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)
-    {-# INLINABLE fromSeries #-}
-
-
-toLazyMap :: (Vector v a) => Series v k a -> Map k a
-{-# INLINABLE toLazyMap #-}
-toLazyMap = fromSeries
-
-
--- | Construct a series from a lazy 'Data.Map.Lazy.Map'.
-fromLazyMap :: (Vector v a) => ML.Map k a -> Series v k a
-{-# INLINABLE fromLazyMap #-}
-fromLazyMap = toSeries
-
-
--- | Convert a series into a strict 'Data.Map.Strict.Map'.
-toStrictMap :: (Vector v a) => Series v k a -> Map k a
-{-# INLINABLE toStrictMap #-}
-toStrictMap (MkSeries ks vs) = MS.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)
-
-
--- | Construct a series from a strict 'Data.Map.Strict.Map'.
-fromStrictMap :: (Vector v a) => MS.Map k a -> Series v k a
-{-# INLINABLE fromStrictMap #-}
-fromStrictMap mp = MkSeries { index  = Index.toIndex $ MS.keysSet mp
-                            , values = Vector.fromListN (MS.size mp) $ MS.elems mp
-                            }
-
-
-instance (Vector v a) => IsSeries (IntMap a) v Int a where
-    toSeries :: IntMap a -> Series v Int a
-    toSeries im = MkSeries 
-                { index  = Index.toIndex $ IntMap.keysSet im
-                , values = Vector.fromListN (IntMap.size im)  $ IntMap.elems im 
-                }
-    {-# INLINABLE toSeries #-}
-
-    fromSeries :: Series v Int a -> IntMap a
-    fromSeries (MkSeries ks vs) 
-        = IntMap.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)
-    {-# INLINABLE fromSeries #-}
-
-
-instance (Ord k, Vector v a) => IsSeries (Seq (k, a)) v k a where
-    toSeries :: Seq (k, a) -> Series v k a
-    toSeries = toSeries . Foldable.toList
-    {-# INLINABLE toSeries #-}
-
-    fromSeries :: Series v k a -> Seq (k, a)
-    fromSeries = Seq.fromList . fromSeries
-    {-# INLINABLE fromSeries #-}
-
-
-instance (Vector v a) => IsSeries (Set (k, a)) v k a where
-    toSeries :: Set (k, a) -> Series v k a
-    toSeries = fromDistinctAscList . Set.toAscList
-    {-# INLINABLE toSeries #-}
-
-    fromSeries :: Series v k a -> Set (k, a)
-    fromSeries = Set.fromDistinctAscList . toList
-    {-# INLINABLE fromSeries #-}
-
-
--- | Get the first value of a 'Series'. If the 'Series' is empty,
--- this function returns 'Nothing'.
-headM :: Vector v a => Series v k a -> Maybe a
-{-# INLINABLE headM #-}
-headM (MkSeries _ vs) = Vector.headM vs
-
-
--- | Get the last value of a 'Series'. If the 'Series' is empty,
--- this function returns 'Nothing'.
-lastM :: Vector v a => Series v k a -> Maybe a
-{-# INLINABLE lastM #-}
-lastM (MkSeries _ vs) = Vector.lastM vs
-
-
--- | \(O(\log n)\) @'take' n xs@ returns at most @n@ elements of the 'Series' @xs@.
-take :: Vector v a => Int -> Series v k a -> Series v k a
-{-# INLINABLE take #-}
-take n (MkSeries ks vs) 
-    -- Index.take is O(log n) while Vector.take is O(1)
-    = MkSeries (Index.take n ks) (Vector.take n vs)
-
-
--- | \(O(\log n)\) @'drop' n xs@ drops at most @n@ elements from the 'Series' @xs@.
-drop :: Vector v a => Int -> Series v k a -> Series v k a
-{-# INLINABLE drop #-}
-drop n (MkSeries ks vs) 
-    -- Index.drop is O(log n) while Vector.drop is O(1)
-    = MkSeries (Index.drop n ks) (Vector.drop n vs)
-
-
--- | \(O(n)\) Returns the longest prefix (possibly empty) of the input 'Series' that satisfy a predicate.
-takeWhile :: Vector v a => (a -> Bool) -> Series v k a -> Series v k a
-{-# INLINABLE takeWhile #-}
-takeWhile f (MkSeries ix vs) = let taken = Vector.takeWhile f vs
-                 in MkSeries { index  = Index.take (Vector.length taken) ix
-                             , values = taken 
-                             }
-
-
--- | \(O(n)\) Returns the complement of 'takeWhile'.
-dropWhile :: Vector v a => (a -> Bool) -> Series v k a -> Series v k a
-{-# INLINABLE dropWhile #-}
-dropWhile f (MkSeries ix vs) = let dropped = Vector.dropWhile f vs
-                 in MkSeries { index  = Index.drop (Index.size ix - Vector.length dropped) ix
-                             , values = dropped
-                             }
-
-
--- | \(O(n)\) Map every element of a 'Series'.
-map :: (Vector v a, Vector v b) 
-    => (a -> b) -> Series v k a -> Series v k b
-{-# INLINABLE map #-}
-map f (MkSeries ix xs) = MkSeries ix $ Vector.map f xs
-
-
--- | \(O(n)\) Map every element of a 'Series', possibly using the key as well.
-mapWithKey :: (Vector v a, Vector v b) 
-           => (k -> a -> b) -> Series v k a -> Series v k b
-{-# INLINABLE mapWithKey #-}
-mapWithKey f (MkSeries ix xs) 
-    -- We're using boxed vectors to map because we don't want any restrictions
-    -- on the index type, i.e. we don't want the constraint Vector v k
-    = let vs = Boxed.zipWith f (Index.toAscVector ix) (Vector.convert xs)
-       in MkSeries ix (Vector.convert vs)
-
-
--- | \(O(n \log n)\).
--- Map each key in the index to another value. Note that the resulting series
--- may have less elements, because each key must be unique.
---
--- In case new keys are conflicting, the first element is kept.
-mapIndex :: (Vector v a, Ord k, Ord g) => Series v k a -> (k -> g) -> Series v g a
-{-# INLINABLE mapIndex #-}
-mapIndex (MkSeries index values) f
-    -- Note that the order in which items are kept appears to be backwards;
-    -- See the examples for Data.Map.Strict.fromListWith
-    = let mapping   = MS.fromListWith (\_ x -> x) $ [(f k, k) | k <- Index.toAscList index]
-          newvalues = fmap (\k -> values Vector.! Index.Internal.findIndex k index) mapping
-       in toSeries newvalues
-
-
--- | Map a function over all the elements of a 'Series' and concatenate the result into a single 'Series'.
-concatMap :: (Vector v a, Vector v k, Vector v b, Vector v (k, a), Vector v (k, b), Ord k) 
-          => (a -> Series v k b) 
-          -> Series v k a 
-          -> Series v k b
-{-# INLINABLE concatMap #-}
-concatMap f = fromVector 
-            . Vector.concatMap (toVector . f . snd) 
-            . toVector
-
-
-instance (Vector v a, Ord k) => Semigroup (Series v k a) where
-    {-# INLINABLE (<>) #-}
-    (<>) :: Series v k a -> Series v k a -> Series v k a
-    -- Despite all my effort, merging via conversion to Map remains fastest.
-    xs <> ys = toSeries $ toStrictMap xs <> toStrictMap ys
-
-    {-# INLINABLE sconcat #-}
-    sconcat = toSeries . sconcat . fmap toStrictMap
-
-
-instance (Vector v a, Ord k) => Monoid (Series v k a) where
-    {-# INLINABLE mempty #-}
-    mempty :: Series v k a
-    mempty = MkSeries mempty Vector.empty
-
-    {-# INLINABLE mappend #-}
-    mappend :: Series v k a -> Series v k a -> Series v k a
-    mappend = (<>)
-
-    {-# INLINABLE mconcat #-}
-    mconcat :: [Series v k a] -> Series v k a
-    mconcat = toSeries . mconcat . fmap toStrictMap
-
-
-instance (Vector v a, Eq k, Eq a) => Eq (Series v k a) where
-    {-# INLINABLE (==) #-}
-    (==) :: Series v k a -> Series v k a -> Bool
-    (MkSeries ks1 vs1) == (MkSeries ks2 vs2) = (ks1 == ks2) && (vs1 `Vector.eq` vs2)
-
-
-instance (Vector v a, Ord (v a), Ord k, Ord a) => Ord (Series v k a) where
-    {-# INLINABLE compare #-}
-    compare :: Series v k a -> Series v k a -> Ordering
-    compare (MkSeries ks1 vs1) (MkSeries ks2 vs2) = compare (ks1, vs1) (ks2, vs2)
-
-
-instance (Functor v) => Functor (Series v k) where
-    {-# INLINABLE fmap #-}
-    fmap :: (a -> b) -> Series v k a -> Series v k b
-    fmap f (MkSeries ks vs) = MkSeries ks (fmap f vs)
-
-
-instance (forall a. Vector v a, Functor v) => FunctorWithIndex k (Series v k) where
-    {-# INLINABLE imap #-}
-    imap :: (k -> a -> b) -> Series v k a -> Series v k b
-    imap = mapWithKey
-
-
--- Inlining all methods in 'Foldable'
--- is important in order for folds over a boxed
--- Series to have performance characteristics
--- be as close as possible to boxed vectors 
-instance (Foldable v) => Foldable (Series v k) where
-    {-# INLINABLE fold #-}
-    fold :: Monoid m => Series v k m -> m
-    fold = Foldable.fold . values
-
-    {-# INLINABLE foldMap #-}
-    foldMap :: (Monoid m) => (a -> m) -> Series v k a -> m
-    foldMap f = Foldable.foldMap f . values
-
-    {-# INLINABLE foldMap' #-}
-    foldMap' :: (Monoid m) => (a -> m) -> Series v k a -> m
-    foldMap' f = Foldable.foldMap f . values
-
-    {-# INLINABLE foldr #-}
-    foldr :: (a -> b -> b) -> b -> Series v k a -> b
-    foldr f i = Foldable.foldr f i . values
-
-    {-# INLINABLE foldr' #-}
-    foldr' :: (a -> b -> b) -> b -> Series v k a -> b
-    foldr' f i = Foldable.foldr' f i . values
-
-    {-# INLINABLE foldl #-}
-    foldl :: (b -> a -> b) -> b -> Series v k a -> b
-    foldl f i = Foldable.foldl f i . values
-
-    {-# INLINABLE foldl' #-}
-    foldl' :: (b -> a -> b) -> b -> Series v k a -> b
-    foldl' f i = Foldable.foldl' f i . values
-
-    {-# INLINABLE foldr1 #-}
-    foldr1 :: (a -> a -> a) -> Series v k a -> a
-    foldr1 f = Foldable.foldr1 f . values
-
-    {-# INLINABLE foldl1 #-}
-    foldl1 :: (a -> a -> a) -> Series v k a -> a
-    foldl1 f = Foldable.foldl1 f . values
-
-    {-# INLINABLE toList #-}
-    toList :: Series v k a -> [a]
-    toList = Foldable.toList . values
-
-    {-# INLINABLE null #-}
-    null :: Series v k a -> Bool
-    null = Foldable.null . values
-
-    {-# INLINABLE length #-}
-    length :: Series v k a -> Int
-    length = Foldable.length . values
-
-    {-# INLINABLE elem #-}
-    elem :: Eq a => a -> Series v k a -> Bool
-    elem e = Foldable.elem e . values
-
-    {-# INLINABLE maximum #-}
-    maximum :: Ord a => Series v k a -> a
-    maximum = Foldable.maximum . values
-
-    {-# INLINABLE minimum #-}
-    minimum :: Ord a => Series v k a -> a
-    minimum = Foldable.minimum . values
-
-    {-# INLINABLE sum #-}
-    sum :: Num a => Series v k a -> a
-    sum = Foldable.sum . values
-
-    {-# INLINABLE product #-}
-    product :: Num a => Series v k a -> a
-    product = Foldable.product . values
-
-
-instance (forall a. Vector v a, Vector v k, Foldable v, Functor v) => FoldableWithIndex k (Series v k) where
-    {-# INLINABLE ifoldMap #-}
-    ifoldMap :: Monoid m => (k -> a -> m) -> Series v k a -> m
-    ifoldMap = foldMapWithKey
-
-
-instance (Foldable v) => Bifoldable (Series v) where
-    {-# INLINABLE bifoldMap #-}
-    bifoldMap :: Monoid m => (k -> m) -> (a -> m) -> Series v k a -> m
-    bifoldMap fk fv (MkSeries ks vs) = P.foldMap fk ks <> Foldable.foldMap fv vs
-
-
-instance (Traversable v) => Traversable (Series v k) where
-    {-# INLINABLE traverse #-}
-    traverse :: Applicative f
-             => (a -> f b) -> Series v k a -> f (Series v k b)
-    traverse f (MkSeries ix vs) = MkSeries ix <$> traverse f vs
-
-
-instance (forall a. Vector v a, Functor v, Foldable v, Ord k, Traversable v) => TraversableWithIndex k (Series v k) where
-    {-# INLINABLE itraverse #-}
-    itraverse :: Applicative f => (k -> a -> f b) -> Series v k a -> f (Series v k b)
-    itraverse = traverseWithKey
-
-
--- | \(O(n)\) Execute a 'Fold' over a 'Series'.
---
--- See also 'foldM' for monadic folds, and 'foldWithKey' to take keys into
--- account while folding.
-fold :: Vector v a 
-     => Fold a b  
-     -> Series v k a 
-     -> b
-fold (Fold step init' extract) 
-    = extract . Vector.foldl' step init' . values
-{-# INLINABLE fold #-}
-
-
--- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series'.
---
--- See also 'fold' for pure folds, and 'foldMWithKey' to take keys into
--- account while folding.
-foldM :: (Monad m, Vector v a)
-      => FoldM m a b  
-      -> Series v k a 
-      -> m b
-foldM (FoldM step init' extract) xs
-    = init' >>= \i -> Vector.foldM' step i (values xs) >>= extract
-{-# INLINABLE foldM #-}
-
-
--- | \(O(n)\) Execute a 'Fold' over a 'Series', where the 'Fold' takes keys into account.
-foldWithKey :: (Vector v a, Vector v k, Vector v (k, a)) 
-            => Fold (k, a) b  
-            -> Series v k a 
-            -> b
-foldWithKey (Fold step init' extract) 
-    = extract . Vector.foldl' step init' . toVector
-{-# INLINABLE foldWithKey #-}
-
-
--- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series', where the 'FoldM' takes keys into account.
-foldMWithKey :: (Monad m, Vector v a, Vector v k, Vector v (k, a)) 
-             => FoldM m (k, a) b
-             -> Series v k a 
-             -> m b
-foldMWithKey (FoldM step init' extract) xs
-    = init' >>= \i -> Vector.foldM' step i (toVector xs) >>= extract
-{-# INLINABLE foldMWithKey #-}
-
-
--- | \(O(n)\) Fold over elements in a 'Series'.
-foldMap :: (Monoid m, Vector v a) => (a -> m) -> Series v k a -> m
-{-# INLINABLE foldMap #-}
-foldMap f = Vector.foldMap f . values
-
-
--- | \(O(n)\) Fold over pairs of keys and elements in a 'Series'.
--- See also 'bifoldMap'.
-foldMapWithKey :: (Monoid m, Vector v a, Vector v k, Vector v (k, a)) => (k -> a -> m) -> Series v k a -> m
-{-# INLINABLE foldMapWithKey #-}
-foldMapWithKey f = Vector.foldMap (uncurry f) . toVector
-
-
--- | \(O(n)\) Fold over keys and elements separately in a 'Series'.
--- See also 'foldMapWithKey'.
-bifoldMap :: (Vector v a, Monoid m) => (k -> m) -> (a -> m) -> Series v k a -> m
-{-# INLINABLE bifoldMap #-}
-bifoldMap fk fv (MkSeries ks vs) = P.foldMap fk ks <> Vector.foldMap fv vs
-
-
--- | \(O(1)\) Extract the length of a 'Series'.
-length :: Vector v a => Series v k a -> Int
-{-# INLINABLE length #-}
-length = Vector.length . values
-
-
--- | \(O(1)\) Test whether a 'Series' is empty.
-null :: Vector v a => Series v k a -> Bool
-{-# INLINABLE null #-}
-null = Vector.null . values
-
-
--- | \(O(n)\) Apply the monadic action to every element of a series and its
--- index, yielding a series of results.
-mapWithKeyM :: (Vector v a, Vector v b, Monad m, Ord k) 
-            => (k -> a -> m b) -> Series v k a -> m (Series v k b)
-{-# INLINABLE mapWithKeyM #-}
-mapWithKeyM f xs = let f' (key, val) = (key,) <$> f key val
-           in fmap fromList $ traverse f' $ toList xs
-
-
--- | \(O(n)\) Apply the monadic action to every element of a series and its
--- index, discarding the results.
-mapWithKeyM_ :: (Vector v a, Monad m) 
-             => (k -> a -> m b) -> Series v k a -> m ()
-{-# INLINABLE mapWithKeyM_ #-}
-mapWithKeyM_ f xs = let f' (key, val) = (key,) <$> f key val
-           in mapM_ f' $ toList xs
-
-
--- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, 
--- yielding a series of results.
-forWithKeyM :: (Vector v a, Vector v b, Monad m, Ord k) => Series v k a -> (k -> a -> m b) -> m (Series v k b)
-{-# INLINABLE forWithKeyM #-}
-forWithKeyM = flip mapWithKeyM
-
-
--- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, 
--- discarding the results.
-forWithKeyM_ :: (Vector v a, Monad m) => Series v k a -> (k -> a -> m b) -> m ()
-{-# INLINABLE forWithKeyM_ #-}
-forWithKeyM_ = flip mapWithKeyM_
-
-
--- | \(O(n)\) Traverse a 'Series' with an Applicative action, taking into account both keys and values. 
-traverseWithKey :: (Applicative t, Ord k, Traversable v, Vector v a, Vector v b, Vector v k, Vector v (k, a),  Vector v (k, b))
-                => (k -> a -> t b) 
-                -> Series v k a 
-                -> t (Series v k b)
-{-# INLINABLE traverseWithKey #-}
-traverseWithKey f = fmap fromVector 
-                  . traverse (\(k, x) -> (k,) <$> f k x) 
-                  . toVector
-
-
-instance (NFData (v a), NFData k) => NFData (Series v k a) where
-    rnf :: Series v k a -> ()
-    rnf (MkSeries ks vs) = rnf ks `seq` rnf vs
-
-
-instance (Vector v a, Ord k, Show k, Show a) => Show (Series v k a) where
-    show :: Series v k a -> String
-    show = display
-
-
--- | Options controlling how to display 'Series' in the 'displayWith' function.
--- Default options are provided by 'defaultDisplayOptions'.
---
--- To help with creating 'DisplayOptions', see 'noLongerThan'.
-data DisplayOptions k a
-    = DisplayOptions
-    { maximumNumberOfRows  :: Int
-    -- ^ Maximum number of rows shown. These rows will be distributed evenly
-    -- between the start of the 'Series' and the end. 
-    , indexHeader          :: String
-    -- ^ Header of the index column.
-    , valuesHeader         :: String
-    -- ^ Header of the values column.
-    , keyDisplayFunction   :: k -> String
-    -- ^ Function used to display keys from the 'Series'. Use 'noLongerThan'
-    -- to control the width of the index column.
-    , valueDisplayFunction :: a -> String
-    -- ^ Function used to display values from the 'Series'. Use 'noLongerThan'
-    -- to control the width of the values column.
-    }
-
-
--- | Default 'Series' display options.
-defaultDisplayOptions :: (Show k, Show a) => DisplayOptions k a
-defaultDisplayOptions 
-    = DisplayOptions { maximumNumberOfRows  = 6
-                     , indexHeader          = "index"
-                     , valuesHeader         = "values"
-                     , keyDisplayFunction   = show
-                     , valueDisplayFunction = show
-                     }
-
-
--- | This function modifies existing functions to limit the width of its result.
---
--- >>> let limit7 = (show :: Int -> String) `noLongerThan` 7
--- >>> limit7 123456789
--- "123456..."
-noLongerThan :: (a -> String) -> Int -> (a -> String)
-noLongerThan f len x 
-    = let raw = f x
-       in if List.length raw <= max 0 len
-        then raw
-        else List.take (List.length raw - 3) raw <> "..."
-
-
--- | Display a 'Series' using default 'DisplayOptions'.
-display :: (Vector v a, Show k, Show a) 
-        => Series v k a 
-        -> String
-display = displayWith defaultDisplayOptions
-
-
--- | Display a 'Series' using customizable 'DisplayOptions'.
-displayWith :: (Vector v a) 
-            => DisplayOptions k a
-            -> Series v k a 
-            -> String
-displayWith DisplayOptions{..} xs
-    = formatGrid $ if length xs > max 0 maximumNumberOfRows
-        then let headlength = max 0 maximumNumberOfRows `div` 2
-                 taillength = max 0 maximumNumberOfRows - headlength
-              in mconcat [ [ (keyDisplayFunction k, valueDisplayFunction v) | (k, v) <- toList $ take headlength xs]
-                         , [ ("...", "...") ]
-                         , [ (keyDisplayFunction k, valueDisplayFunction v) | (k, v) <- toList $ drop (length xs - taillength) xs]
-                         ] 
-        else [ (keyDisplayFunction k, valueDisplayFunction v) | (k, v) <- toList xs ]
-
-    where
-        -- | Format a grid represented by a list of rows, where every row is a list of items
-        -- All columns will have a fixed width
-        formatGrid :: [ (String, String) ] -- List of rows
-                   -> String
-        formatGrid rows = mconcat $ List.intersperse "\n" 
-                                  $ [ pad indexWidth k <> " | " <> pad valuesWidth v 
-                                    | (k, v) <- rows'
-                                    ] 
-            where
-                rows' = [ (indexHeader, valuesHeader) ] <> [ ("-----", "------")] <> rows
-                (indexCol, valuesCol) = unzip rows'
-                width col = maximum (P.length <$> col)
-                indexWidth = width indexCol
-                valuesWidth = width valuesCol
-
-                -- | Pad a string to a minimum of @n@ characters wide.
-                pad :: Int -> String -> String 
-                pad n s
-                    | n <= P.length s = s
-                    | otherwise     = replicate (n - P.length s) ' ' <> s
+{-# LANGUAGE DerivingStrategies    #-}+{-# LANGUAGE QuantifiedConstraints #-}+{-# LANGUAGE RecordWildCards       #-}+{-# LANGUAGE TypeFamilies          #-}+{-# LANGUAGE UndecidableInstances  #-}++module Data.Series.Generic.Definition ( +    Series(..),++    convert,++    -- * Basic interface+    singleton,+    headM, lastM, map, mapWithKey, mapIndex, concatMap, fold, foldM, +    foldWithKey, foldMWithKey, foldMap, bifoldMap, foldMapWithKey, +    length, null, take, takeWhile, drop, dropWhile,+    mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_,+    traverseWithKey,++    fromIndex,+    -- * Conversion to/from Series+    IsSeries(..),+    -- ** Conversion to/from Maps+    fromStrictMap,+    toStrictMap,+    fromLazyMap,+    toLazyMap,+    -- ** Conversion to/from list+    fromList,+    toList,+    -- *** Unsafe construction+    fromDistinctAscList,+    -- ** Conversion to/from vectors+    fromVector,+    toVector,+    -- *** Unsafe construction+    fromDistinctAscVector,+    -- ** Handling duplicates+    Occurrence, fromListDuplicates, fromVectorDuplicates,++    -- * Displaying 'Series'+    display, displayWith,+    noLongerThan,+    DisplayOptions(..), defaultDisplayOptions+) where++import           Control.DeepSeq        ( NFData(rnf) )+import           Control.Foldl          ( Fold(..), FoldM(..) )+import           Control.Monad.ST       ( runST )+import           Data.Bifoldable        ( Bifoldable )+import qualified Data.Bifoldable        as Bifoldable+import qualified Data.Foldable          as Foldable+import           Data.Foldable.WithIndex ( FoldableWithIndex(..))+import           Data.Function          ( on )+import           Data.Functor.WithIndex ( FunctorWithIndex(imap) )++import           Data.IntMap.Strict     ( IntMap )+import qualified Data.IntMap.Strict     as IntMap+import qualified Data.List              as List+import qualified Data.Map.Lazy          as ML+import           Data.Map.Strict        ( Map )+import qualified Data.Map.Strict        as MS+import           Data.Sequence          ( Seq )+import qualified Data.Sequence          as Seq+import           Data.Semigroup         ( Semigroup(..) )+import           Data.Series.Index      ( Index )+import qualified Data.Series.Index      as Index+import qualified Data.Series.Index.Internal as Index.Internal+import           Data.Set               ( Set )+import qualified Data.Set               as Set+import           Data.Traversable.WithIndex ( TraversableWithIndex(..) )+import qualified Data.Vector            as Boxed+import           Data.Vector.Algorithms.Intro ( sortUniqBy, sortBy )+import           Data.Vector.Generic    ( Vector )+import qualified Data.Vector.Generic    as Vector+import qualified Data.Vector.Generic.Mutable as GM+import qualified Data.Vector.Unboxed         as U+import qualified Data.Vector.Unboxed.Mutable as UM+ +import           Prelude                hiding ( take, takeWhile, drop, dropWhile, map, concatMap, foldMap, sum, length, null )+import qualified Prelude                as P++++-- | A @Series v k a@ is a labeled array of type @v@ filled with values of type @a@,+-- indexed by keys of type @k@.+--+-- Like 'Data.Map.Strict.Map', they support efficient:+--+--      * random access by key ( \(O(\log n)\) );+--      * slice by key ( \(O(\log n)\) ).+--+-- Like 'Data.Vector.Vector', they support efficient:+--+--      * random access by index ( \(O(1)\) );+--      * slice by index ( \(O(1)\) );+--      * numerical operations.+--+data Series v k a +    -- The reason the index is a set of keys is that we *want* keys to be ordered.+    -- This allows for efficient slicing of the underlying values, because+    -- if @k1 < k2@, then the values are also at indices @ix1 < ix2@.+    = MkSeries { index  :: Index k -- ^ The 'Index' of a series, which contains its (unique) keys in ascending order.+               , values :: v a     -- ^ The values of a series, in the order of its (unique) keys.+               }+++-- | \(O(n)\) Convert between two types of 'Series'.+convert :: (Vector v1 a, Vector v2 a) => Series v1 k a -> Series v2 k a+{-# INLINABLE convert #-}+convert (MkSeries ix vs) = MkSeries ix $ Vector.convert vs +++-- | \(O(1)\) Create a 'Series' with a single element.+singleton :: Vector v a => k -> a -> Series v k a+{-# INLINABLE singleton #-}+singleton k v = MkSeries (Index.singleton k) $ Vector.singleton v+++-- | \(O(n)\) Generate a 'Series' by mapping every element of its index.+fromIndex :: (Vector v a) +          => (k -> a) -> Index k -> Series v k a+{-# INLINABLE fromIndex #-}+fromIndex f ix = MkSeries ix $ Vector.convert +                             $ Boxed.map f -- Using boxed vector to prevent a (Vector v k) constraint+                             $ Index.toAscVector ix+++-- | The 'IsSeries' typeclass allow for ad-hoc definition+-- of conversion functions, converting to / from 'Series'.+class IsSeries t v k a where+    -- | Construct a 'Series' from some container of key-values pairs. There is no+    -- condition on the order of pairs. Duplicate keys are silently dropped. If you+    -- need to handle duplicate keys, see 'fromListDuplicates' or 'fromVectorDuplicates'.+    toSeries    :: t -> Series v k a++    -- | Construct a container from key-value pairs of a 'Series'. +    -- The elements are returned in ascending order of keys. +    fromSeries  :: Series v k a -> t+++instance (Ord k, Vector v a) => IsSeries [(k, a)] v k a where+    -- | Construct a series from a list of key-value pairs. There is no+    -- condition on the order of pairs.+    --+    -- >>> let xs = toSeries [('b', 0::Int), ('a', 5), ('d', 1) ]+    -- >>> xs+    -- index | values+    -- ----- | ------+    --   'a' |      5+    --   'b' |      0+    --   'd' |      1+    --+    -- If you need to handle duplicate keys, take a look at `fromListDuplicates`.+    toSeries :: [(k, a)] -> Series v k a+    toSeries = toSeries . MS.fromList+    {-# INLINABLE toSeries #-}++    -- | Construct a list from key-value pairs. The elements are in order sorted by key:+    --+    -- >>> let xs = Series.toSeries [ ('b', 0::Int), ('a', 5), ('d', 1) ]+    -- >>> xs+    -- index | values+    -- ----- | ------+    --   'a' |      5+    --   'b' |      0+    --   'd' |      1+    -- >>> fromSeries xs+    -- [('a',5),('b',0),('d',1)]+    fromSeries :: Series v k a -> [(k, a)]+    fromSeries (MkSeries ks vs)= zip (Index.toAscList ks) (Vector.toList vs)+    {-# INLINABLE fromSeries #-}+++-- | Construct a 'Series' from a list of key-value pairs. There is no+-- condition on the order of pairs. Duplicate keys are silently dropped. If you+-- need to handle duplicate keys, see 'fromListDuplicates'.+fromList :: (Vector v a, Ord k) => [(k, a)] -> Series v k a+{-# INLINABLE fromList #-}+fromList = toSeries+++-- | \(O(n)\) Build a 'Series' from a list of pairs, where the first elements of the pairs (the keys)+-- are distinct elements in ascending order. The precondition that the keys be unique and sorted is not checked.+fromDistinctAscList :: (Vector v a) => [(k, a)] -> Series v k a+fromDistinctAscList xs +    = let (!ks, !vs) = unzip xs +       in MkSeries (Index.Internal.fromDistinctAscList ks) (Vector.fromListN (List.length vs) vs)+++-- | Integer-like, non-negative number that specifies how many occurrences+-- of a key is present in a 'Series'.+--+-- The easiest way to convert from an 'Occurrence' to another integer-like type+-- is the 'fromIntegral' function.+newtype Occurrence = MkOcc Int+    deriving (Eq, Enum, Num, Ord, Integral, Real)+    deriving newtype (Show, U.Unbox) ++-- Occurrence needs to be an 'U.Unbox' type+-- so that 'fromVectorDuplicates' works with unboxed vectors+-- and series.+newtype instance UM.MVector s Occurrence = MV_Occ (UM.MVector s Int)+newtype instance U.Vector Occurrence = V_Occ (U.Vector Int)+deriving instance GM.MVector UM.MVector Occurrence+deriving instance Vector U.Vector Occurrence +++-- | Construct a series from a list of key-value pairs.+-- Contrary to 'fromList', values at duplicate keys are preserved. To keep each+-- key unique, an 'Occurrence' number counts up.+fromListDuplicates :: (Vector v a, Ord k) => [(k, a)] -> Series v (k, Occurrence) a+{-# INLINABLE fromListDuplicates #-}+fromListDuplicates = convert . fromVectorDuplicates . Boxed.fromList+++-- | Construct a list from key-value pairs. The elements are in order sorted by key. +toList :: Vector v a => Series v k a -> [(k, a)]+{-# INLINABLE toList #-}+toList (MkSeries ks vs) = zip (Index.toAscList ks) (Vector.toList vs)+++instance (Ord k) => IsSeries (Boxed.Vector (k, a)) Boxed.Vector k a where+    toSeries = fromVector+    {-# INLINABLE toSeries #-}++    fromSeries = toVector+    {-# INLINABLE fromSeries #-}+++instance (Ord k, U.Unbox a, U.Unbox k) => IsSeries (U.Vector (k, a)) U.Vector k a where+    toSeries :: U.Vector (k, a) -> Series U.Vector k a+    toSeries = fromVector+    {-# INLINABLE toSeries #-}++    fromSeries :: Series U.Vector k a -> U.Vector (k, a)+    fromSeries = toVector+    {-# INLINABLE fromSeries #-}+++-- | Construct a 'Series' from a 'Vector' of key-value pairs. There is no+-- condition on the order of pairs. Duplicate keys are silently dropped. If you+-- need to handle duplicate keys, see 'fromVectorDuplicates'.+--+-- Note that due to differences in sorting,+-- 'Series.fromList' and @'Series.fromVector' . 'Vector.fromList'@+-- may not be equivalent if the input list contains duplicate keys.+fromVector :: (Ord k, Vector v k, Vector v a, Vector v (k, a))+           => v (k, a) -> Series v k a+{-# INLINABLE fromVector #-}+fromVector vec = let (indexVector, valuesVector) = Vector.unzip $ runST $ do+                        mv <- Vector.thaw vec+                        -- Note that we're using this particular flavor of `sortUniqBy`+                        -- because it both sorts AND removes duplicate keys+                        destMV <- sortUniqBy (compare `on` fst) mv+                        v <- Vector.freeze destMV+                        pure (Vector.force v)+              in MkSeries (Index.Internal.fromDistinctAscVector indexVector) valuesVector+++-- | \(O(n)\) Build a 'Series' from a vector of pairs, where the first elements of the pairs (the keys)+-- are distinct elements in ascending order. The precondition that the keys be unique and sorted is not checked.+fromDistinctAscVector :: (Vector v k, Vector v a, Vector v (k, a))+                      => v (k, a) -> Series v k a+fromDistinctAscVector xs +    = let (ks, vs) = Vector.unzip xs +       in MkSeries (Index.Internal.fromDistinctAscVector ks) vs+++-- | Construct a 'Series' from a 'Vector' of key-value pairs, where there may be duplicate keys. +-- There is no condition on the order of pairs.+fromVectorDuplicates :: (Ord k, Vector v k, Vector v a, Vector v (k, a), Vector v (k, Occurrence))+                     => v (k, a) -> Series v (k, Occurrence) a+{-# INLINABLE fromVectorDuplicates #-}+fromVectorDuplicates vec +    = let (indexVector, valuesVector) +            = Vector.unzip $ runST $ do+                mv <- Vector.thaw vec+                sortBy (compare `on` fst) mv+                v <- Vector.freeze mv+                pure (Vector.force v)+        in MkSeries (Index.Internal.fromDistinctAscVector (occurences indexVector)) valuesVector+    where+        occurences vs +            | Vector.null vs        = Vector.empty+            | Vector.length vs == 1 = Vector.map (,0) vs+            | otherwise             = Vector.scanl f (Vector.head vs, 0) (Vector.tail vs)+            where+                f (lastKey, lastOcc) newKey +                    | lastKey == newKey = (newKey, lastOcc + 1)+                    | otherwise         = (newKey, 0)+++-- | Construct a 'Vector' of key-value pairs. The elements are in order sorted by key. +toVector :: (Vector v a, Vector v k, Vector v (k, a)) +         => Series v k a -> v (k, a)+{-# INLINABLE toVector #-}+toVector (MkSeries ks vs) = Vector.zip (Index.toAscVector ks) vs+++instance (Vector v a) => IsSeries (Map k a) v k a where+    toSeries :: Map k a -> Series v k a+    toSeries mp = MkSeries +                { index  = Index.fromSet $ MS.keysSet mp+                , values = Vector.fromListN (MS.size mp) $ MS.elems mp+                }+    {-# INLINABLE toSeries #-}++    fromSeries :: Series v k a -> Map k a+    fromSeries (MkSeries ks vs)+        = MS.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)+    {-# INLINABLE fromSeries #-}+++toLazyMap :: (Vector v a) => Series v k a -> Map k a+{-# INLINABLE toLazyMap #-}+toLazyMap = fromSeries+++-- | Construct a series from a lazy 'Data.Map.Lazy.Map'.+fromLazyMap :: (Vector v a) => ML.Map k a -> Series v k a+{-# INLINABLE fromLazyMap #-}+fromLazyMap = toSeries+++-- | Convert a series into a strict 'Data.Map.Strict.Map'.+toStrictMap :: (Vector v a) => Series v k a -> Map k a+{-# INLINABLE toStrictMap #-}+toStrictMap (MkSeries ks vs) = MS.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)+++-- | Construct a series from a strict 'Data.Map.Strict.Map'.+fromStrictMap :: (Vector v a) => MS.Map k a -> Series v k a+{-# INLINABLE fromStrictMap #-}+fromStrictMap mp = MkSeries { index  = Index.toIndex $ MS.keysSet mp+                            , values = Vector.fromListN (MS.size mp) $ MS.elems mp+                            }+++instance (Vector v a) => IsSeries (IntMap a) v Int a where+    toSeries :: IntMap a -> Series v Int a+    toSeries im = MkSeries +                { index  = Index.toIndex $ IntMap.keysSet im+                , values = Vector.fromListN (IntMap.size im)  $ IntMap.elems im +                }+    {-# INLINABLE toSeries #-}++    fromSeries :: Series v Int a -> IntMap a+    fromSeries (MkSeries ks vs) +        = IntMap.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)+    {-# INLINABLE fromSeries #-}+++instance (Ord k, Vector v a) => IsSeries (Seq (k, a)) v k a where+    toSeries :: Seq (k, a) -> Series v k a+    toSeries = toSeries . Foldable.toList+    {-# INLINABLE toSeries #-}++    fromSeries :: Series v k a -> Seq (k, a)+    fromSeries = Seq.fromList . fromSeries+    {-# INLINABLE fromSeries #-}+++instance (Vector v a) => IsSeries (Set (k, a)) v k a where+    toSeries :: Set (k, a) -> Series v k a+    toSeries = fromDistinctAscList . Set.toAscList+    {-# INLINABLE toSeries #-}++    fromSeries :: Series v k a -> Set (k, a)+    fromSeries = Set.fromDistinctAscList . toList+    {-# INLINABLE fromSeries #-}+++-- | Get the first value of a 'Series'. If the 'Series' is empty,+-- this function returns 'Nothing'.+headM :: Vector v a => Series v k a -> Maybe a+{-# INLINABLE headM #-}+headM (MkSeries _ vs) = Vector.headM vs+++-- | Get the last value of a 'Series'. If the 'Series' is empty,+-- this function returns 'Nothing'.+lastM :: Vector v a => Series v k a -> Maybe a+{-# INLINABLE lastM #-}+lastM (MkSeries _ vs) = Vector.lastM vs+++-- | \(O(\log n)\) @'take' n xs@ returns at most @n@ elements of the 'Series' @xs@.+take :: Vector v a => Int -> Series v k a -> Series v k a+{-# INLINABLE take #-}+take n (MkSeries ks vs) +    -- Index.take is O(log n) while Vector.take is O(1)+    = MkSeries (Index.take n ks) (Vector.take n vs)+++-- | \(O(\log n)\) @'drop' n xs@ drops at most @n@ elements from the 'Series' @xs@.+drop :: Vector v a => Int -> Series v k a -> Series v k a+{-# INLINABLE drop #-}+drop n (MkSeries ks vs) +    -- Index.drop is O(log n) while Vector.drop is O(1)+    = MkSeries (Index.drop n ks) (Vector.drop n vs)+++-- | \(O(n)\) Returns the longest prefix (possibly empty) of the input 'Series' that satisfy a predicate.+takeWhile :: Vector v a => (a -> Bool) -> Series v k a -> Series v k a+{-# INLINABLE takeWhile #-}+takeWhile f (MkSeries ix vs) = let taken = Vector.takeWhile f vs+                 in MkSeries { index  = Index.take (Vector.length taken) ix+                             , values = taken +                             }+++-- | \(O(n)\) Returns the complement of 'takeWhile'.+dropWhile :: Vector v a => (a -> Bool) -> Series v k a -> Series v k a+{-# INLINABLE dropWhile #-}+dropWhile f (MkSeries ix vs) = let dropped = Vector.dropWhile f vs+                 in MkSeries { index  = Index.drop (Index.size ix - Vector.length dropped) ix+                             , values = dropped+                             }+++-- | \(O(n)\) Map every element of a 'Series'.+map :: (Vector v a, Vector v b) +    => (a -> b) -> Series v k a -> Series v k b+{-# INLINABLE map #-}+map f (MkSeries ix xs) = MkSeries ix $ Vector.map f xs+++-- | \(O(n)\) Map every element of a 'Series', possibly using the key as well.+mapWithKey :: (Vector v a, Vector v b) +           => (k -> a -> b) -> Series v k a -> Series v k b+{-# INLINABLE mapWithKey #-}+mapWithKey f (MkSeries ix xs) +    -- We're using boxed vectors to map because we don't want any restrictions+    -- on the index type, i.e. we don't want the constraint Vector v k+    = let vs = Boxed.zipWith f (Index.toAscVector ix) (Vector.convert xs)+       in MkSeries ix (Vector.convert vs)+++-- | \(O(n \log n)\).+-- Map each key in the index to another value. Note that the resulting series+-- may have less elements, because each key must be unique.+--+-- In case new keys are conflicting, the first element is kept.+mapIndex :: (Vector v a, Ord k, Ord g) => Series v k a -> (k -> g) -> Series v g a+{-# INLINABLE mapIndex #-}+mapIndex (MkSeries index values) f+    -- Note that the order in which items are kept appears to be backwards;+    -- See the examples for Data.Map.Strict.fromListWith+    = let mapping   = MS.fromListWith (\_ x -> x) $ [(f k, k) | k <- Index.toAscList index]+          newvalues = fmap (\k -> values Vector.! Index.Internal.findIndex k index) mapping+       in toSeries newvalues+++-- | Map a function over all the elements of a 'Series' and concatenate the result into a single 'Series'.+concatMap :: (Vector v a, Vector v k, Vector v b, Vector v (k, a), Vector v (k, b), Ord k) +          => (a -> Series v k b) +          -> Series v k a +          -> Series v k b+{-# INLINABLE concatMap #-}+concatMap f = fromVector +            . Vector.concatMap (toVector . f . snd) +            . toVector+++instance (Vector v a, Ord k) => Semigroup (Series v k a) where+    {-# INLINABLE (<>) #-}+    (<>) :: Series v k a -> Series v k a -> Series v k a+    -- Despite all my effort, merging via conversion to Map remains fastest.+    xs <> ys = toSeries $ toStrictMap xs <> toStrictMap ys++    {-# INLINABLE sconcat #-}+    sconcat = toSeries . sconcat . fmap toStrictMap+++instance (Vector v a, Ord k) => Monoid (Series v k a) where+    {-# INLINABLE mempty #-}+    mempty :: Series v k a+    mempty = MkSeries mempty Vector.empty++    {-# INLINABLE mappend #-}+    mappend :: Series v k a -> Series v k a -> Series v k a+    mappend = (<>)++    {-# INLINABLE mconcat #-}+    mconcat :: [Series v k a] -> Series v k a+    mconcat = toSeries . mconcat . fmap toStrictMap+++instance (Vector v a, Eq k, Eq a) => Eq (Series v k a) where+    {-# INLINABLE (==) #-}+    (==) :: Series v k a -> Series v k a -> Bool+    (MkSeries ks1 vs1) == (MkSeries ks2 vs2) = (ks1 == ks2) && (vs1 `Vector.eq` vs2)+++instance (Vector v a, Ord (v a), Ord k, Ord a) => Ord (Series v k a) where+    {-# INLINABLE compare #-}+    compare :: Series v k a -> Series v k a -> Ordering+    compare (MkSeries ks1 vs1) (MkSeries ks2 vs2) = compare (ks1, vs1) (ks2, vs2)+++instance (Functor v) => Functor (Series v k) where+    {-# INLINABLE fmap #-}+    fmap :: (a -> b) -> Series v k a -> Series v k b+    fmap f (MkSeries ks vs) = MkSeries ks (fmap f vs)+++instance (forall a. Vector v a, Functor v) => FunctorWithIndex k (Series v k) where+    {-# INLINABLE imap #-}+    imap :: (k -> a -> b) -> Series v k a -> Series v k b+    imap = mapWithKey+++-- Inlining all methods in 'Foldable'+-- is important in order for folds over a boxed+-- Series to have performance characteristics+-- be as close as possible to boxed vectors +instance (Foldable v) => Foldable (Series v k) where+    {-# INLINABLE fold #-}+    fold :: Monoid m => Series v k m -> m+    fold = Foldable.fold . values++    {-# INLINABLE foldMap #-}+    foldMap :: (Monoid m) => (a -> m) -> Series v k a -> m+    foldMap f = Foldable.foldMap f . values++    {-# INLINABLE foldMap' #-}+    foldMap' :: (Monoid m) => (a -> m) -> Series v k a -> m+    foldMap' f = Foldable.foldMap f . values++    {-# INLINABLE foldr #-}+    foldr :: (a -> b -> b) -> b -> Series v k a -> b+    foldr f i = Foldable.foldr f i . values++    {-# INLINABLE foldr' #-}+    foldr' :: (a -> b -> b) -> b -> Series v k a -> b+    foldr' f i = Foldable.foldr' f i . values++    {-# INLINABLE foldl #-}+    foldl :: (b -> a -> b) -> b -> Series v k a -> b+    foldl f i = Foldable.foldl f i . values++    {-# INLINABLE foldl' #-}+    foldl' :: (b -> a -> b) -> b -> Series v k a -> b+    foldl' f i = Foldable.foldl' f i . values++    {-# INLINABLE foldr1 #-}+    foldr1 :: (a -> a -> a) -> Series v k a -> a+    foldr1 f = Foldable.foldr1 f . values++    {-# INLINABLE foldl1 #-}+    foldl1 :: (a -> a -> a) -> Series v k a -> a+    foldl1 f = Foldable.foldl1 f . values++    {-# INLINABLE toList #-}+    toList :: Series v k a -> [a]+    toList = Foldable.toList . values++    {-# INLINABLE null #-}+    null :: Series v k a -> Bool+    null = Foldable.null . values++    {-# INLINABLE length #-}+    length :: Series v k a -> Int+    length = Foldable.length . values++    {-# INLINABLE elem #-}+    elem :: Eq a => a -> Series v k a -> Bool+    elem e = Foldable.elem e . values++    {-# INLINABLE maximum #-}+    maximum :: Ord a => Series v k a -> a+    maximum = Foldable.maximum . values++    {-# INLINABLE minimum #-}+    minimum :: Ord a => Series v k a -> a+    minimum = Foldable.minimum . values++    {-# INLINABLE sum #-}+    sum :: Num a => Series v k a -> a+    sum = Foldable.sum . values++    {-# INLINABLE product #-}+    product :: Num a => Series v k a -> a+    product = Foldable.product . values+++instance (forall a. Vector v a, Vector v k, Foldable v, Functor v) => FoldableWithIndex k (Series v k) where+    {-# INLINABLE ifoldMap #-}+    ifoldMap :: Monoid m => (k -> a -> m) -> Series v k a -> m+    ifoldMap = foldMapWithKey+++instance (Foldable v) => Bifoldable (Series v) where+    {-# INLINABLE bifoldMap #-}+    bifoldMap :: Monoid m => (k -> m) -> (a -> m) -> Series v k a -> m+    bifoldMap fk fv (MkSeries ks vs) = P.foldMap fk ks <> Foldable.foldMap fv vs+++instance (Traversable v) => Traversable (Series v k) where+    {-# INLINABLE traverse #-}+    traverse :: Applicative f+             => (a -> f b) -> Series v k a -> f (Series v k b)+    traverse f (MkSeries ix vs) = MkSeries ix <$> traverse f vs+++instance (forall a. Vector v a, Functor v, Foldable v, Ord k, Traversable v) => TraversableWithIndex k (Series v k) where+    {-# INLINABLE itraverse #-}+    itraverse :: Applicative f => (k -> a -> f b) -> Series v k a -> f (Series v k b)+    itraverse = traverseWithKey+++-- | \(O(n)\) Execute a 'Fold' over a 'Series'.+--+-- See also 'foldM' for monadic folds, and 'foldWithKey' to take keys into+-- account while folding.+fold :: Vector v a +     => Fold a b  +     -> Series v k a +     -> b+fold (Fold step init' extract) +    = extract . Vector.foldl' step init' . values+{-# INLINABLE fold #-}+++-- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series'.+--+-- See also 'fold' for pure folds, and 'foldMWithKey' to take keys into+-- account while folding.+foldM :: (Monad m, Vector v a)+      => FoldM m a b  +      -> Series v k a +      -> m b+foldM (FoldM step init' extract) xs+    = init' >>= \i -> Vector.foldM' step i (values xs) >>= extract+{-# INLINABLE foldM #-}+++-- | \(O(n)\) Execute a 'Fold' over a 'Series', where the 'Fold' takes keys into account.+foldWithKey :: (Vector v a, Vector v k, Vector v (k, a)) +            => Fold (k, a) b  +            -> Series v k a +            -> b+foldWithKey (Fold step init' extract) +    = extract . Vector.foldl' step init' . toVector+{-# INLINABLE foldWithKey #-}+++-- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series', where the 'FoldM' takes keys into account.+foldMWithKey :: (Monad m, Vector v a, Vector v k, Vector v (k, a)) +             => FoldM m (k, a) b+             -> Series v k a +             -> m b+foldMWithKey (FoldM step init' extract) xs+    = init' >>= \i -> Vector.foldM' step i (toVector xs) >>= extract+{-# INLINABLE foldMWithKey #-}+++-- | \(O(n)\) Fold over elements in a 'Series'.+foldMap :: (Monoid m, Vector v a) => (a -> m) -> Series v k a -> m+{-# INLINABLE foldMap #-}+foldMap f = Vector.foldMap f . values+++-- | \(O(n)\) Fold over pairs of keys and elements in a 'Series'.+-- See also 'bifoldMap'.+foldMapWithKey :: (Monoid m, Vector v a, Vector v k, Vector v (k, a)) => (k -> a -> m) -> Series v k a -> m+{-# INLINABLE foldMapWithKey #-}+foldMapWithKey f = Vector.foldMap (uncurry f) . toVector+++-- | \(O(n)\) Fold over keys and elements separately in a 'Series'.+-- See also 'foldMapWithKey'.+bifoldMap :: (Vector v a, Monoid m) => (k -> m) -> (a -> m) -> Series v k a -> m+{-# INLINABLE bifoldMap #-}+bifoldMap fk fv (MkSeries ks vs) = P.foldMap fk ks <> Vector.foldMap fv vs+++-- | \(O(1)\) Extract the length of a 'Series'.+length :: Vector v a => Series v k a -> Int+{-# INLINABLE length #-}+length = Vector.length . values+++-- | \(O(1)\) Test whether a 'Series' is empty.+null :: Vector v a => Series v k a -> Bool+{-# INLINABLE null #-}+null = Vector.null . values+++-- | \(O(n)\) Apply the monadic action to every element of a series and its+-- index, yielding a series of results.+mapWithKeyM :: (Vector v a, Vector v b, Monad m, Ord k) +            => (k -> a -> m b) -> Series v k a -> m (Series v k b)+{-# INLINABLE mapWithKeyM #-}+mapWithKeyM f xs = let f' (key, val) = (key,) <$> f key val+           in fmap fromList $ traverse f' $ toList xs+++-- | \(O(n)\) Apply the monadic action to every element of a series and its+-- index, discarding the results.+mapWithKeyM_ :: (Vector v a, Monad m) +             => (k -> a -> m b) -> Series v k a -> m ()+{-# INLINABLE mapWithKeyM_ #-}+mapWithKeyM_ f xs = let f' (key, val) = (key,) <$> f key val+           in mapM_ f' $ toList xs+++-- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, +-- yielding a series of results.+forWithKeyM :: (Vector v a, Vector v b, Monad m, Ord k) => Series v k a -> (k -> a -> m b) -> m (Series v k b)+{-# INLINABLE forWithKeyM #-}+forWithKeyM = flip mapWithKeyM+++-- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, +-- discarding the results.+forWithKeyM_ :: (Vector v a, Monad m) => Series v k a -> (k -> a -> m b) -> m ()+{-# INLINABLE forWithKeyM_ #-}+forWithKeyM_ = flip mapWithKeyM_+++-- | \(O(n)\) Traverse a 'Series' with an Applicative action, taking into account both keys and values. +traverseWithKey :: (Applicative t, Ord k, Traversable v, Vector v a, Vector v b, Vector v k, Vector v (k, a),  Vector v (k, b))+                => (k -> a -> t b) +                -> Series v k a +                -> t (Series v k b)+{-# INLINABLE traverseWithKey #-}+traverseWithKey f = fmap fromVector +                  . traverse (\(k, x) -> (k,) <$> f k x) +                  . toVector+++instance (NFData (v a), NFData k) => NFData (Series v k a) where+    rnf :: Series v k a -> ()+    rnf (MkSeries ks vs) = rnf ks `seq` rnf vs+++instance (Vector v a, Ord k, Show k, Show a) => Show (Series v k a) where+    show :: Series v k a -> String+    show = display+++-- | Options controlling how to display 'Series' in the 'displayWith' function.+-- Default options are provided by 'defaultDisplayOptions'.+--+-- To help with creating 'DisplayOptions', see 'noLongerThan'.+data DisplayOptions k a+    = DisplayOptions+    { maximumNumberOfRows  :: Int+    -- ^ Maximum number of rows shown. These rows will be distributed evenly+    -- between the start of the 'Series' and the end. +    , indexHeader          :: String+    -- ^ Header of the index column.+    , valuesHeader         :: String+    -- ^ Header of the values column.+    , keyDisplayFunction   :: k -> String+    -- ^ Function used to display keys from the 'Series'. Use 'noLongerThan'+    -- to control the width of the index column.+    , valueDisplayFunction :: a -> String+    -- ^ Function used to display values from the 'Series'. Use 'noLongerThan'+    -- to control the width of the values column.+    }+++-- | Default 'Series' display options.+defaultDisplayOptions :: (Show k, Show a) => DisplayOptions k a+defaultDisplayOptions +    = DisplayOptions { maximumNumberOfRows  = 6+                     , indexHeader          = "index"+                     , valuesHeader         = "values"+                     , keyDisplayFunction   = show+                     , valueDisplayFunction = show+                     }+++-- | This function modifies existing functions to limit the width of its result.+--+-- >>> let limit7 = (show :: Int -> String) `noLongerThan` 7+-- >>> limit7 123456789+-- "123456..."+noLongerThan :: (a -> String) -> Int -> (a -> String)+noLongerThan f len x +    = let raw = f x+       in if List.length raw <= max 0 len+        then raw+        else List.take (List.length raw - 3) raw <> "..."+++-- | Display a 'Series' using default 'DisplayOptions'.+display :: (Vector v a, Show k, Show a) +        => Series v k a +        -> String+display = displayWith defaultDisplayOptions+++-- | Display a 'Series' using customizable 'DisplayOptions'.+displayWith :: (Vector v a) +            => DisplayOptions k a+            -> Series v k a +            -> String+displayWith DisplayOptions{..} xs+    = formatGrid $ if length xs > max 0 maximumNumberOfRows+        then let headlength = max 0 maximumNumberOfRows `div` 2+                 taillength = max 0 maximumNumberOfRows - headlength+              in mconcat [ [ (keyDisplayFunction k, valueDisplayFunction v) | (k, v) <- toList $ take headlength xs]+                         , [ ("...", "...") ]+                         , [ (keyDisplayFunction k, valueDisplayFunction v) | (k, v) <- toList $ drop (length xs - taillength) xs]+                         ] +        else [ (keyDisplayFunction k, valueDisplayFunction v) | (k, v) <- toList xs ]++    where+        -- | Format a grid represented by a list of rows, where every row is a list of items+        -- All columns will have a fixed width+        formatGrid :: [ (String, String) ] -- List of rows+                   -> String+        formatGrid rows = mconcat $ List.intersperse "\n" +                                  $ [ pad indexWidth k <> " | " <> pad valuesWidth v +                                    | (k, v) <- rows'+                                    ] +            where+                rows' = [ (indexHeader, valuesHeader) ] <> [ ("-----", "------")] <> rows+                (indexCol, valuesCol) = unzip rows'+                width col = maximum (P.length <$> col)+                indexWidth = width indexCol+                valuesWidth = width valuesCol++                -- | Pad a string to a minimum of @n@ characters wide.+                pad :: Int -> String -> String +                pad n s+                    | n <= P.length s = s+                    | otherwise     = replicate (n - P.length s) ' ' <> s
src/Data/Series/Generic/Internal.hs view
@@ -1,27 +1,27 @@------------------------------------------------------------------------------
--- |
--- Module      :  Data.Series.Generic.Internal
--- Copyright   :  (c) Laurent P. René de Cotret
--- License     :  MIT
--- Maintainer  :  laurent.decotret@outlook.com
--- Portability :  portable
---
--- = WARNING
---
--- This module is considered __internal__. Using the 'Series' constructor
--- directly may result in loss or corruption of data if not handled carefully.
---
--- The Package Versioning Policy still applies.
-
-module Data.Series.Generic.Internal ( 
-    -- * Constructor
-    Series(..),
-    -- * Unsafe construction
-    fromDistinctAscList,
-    fromDistinctAscVector,
-    -- * Unsafe selection
-    selectSubset
-) where
-
-import Data.Series.Generic.Definition   ( Series(..), fromDistinctAscList, fromDistinctAscVector )
+-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Series.Generic.Internal+-- Copyright   :  (c) Laurent P. René de Cotret+-- License     :  MIT+-- Maintainer  :  laurent.decotret@outlook.com+-- Portability :  portable+--+-- = WARNING+--+-- This module is considered __internal__. Using the 'Series' constructor+-- directly may result in loss or corruption of data if not handled carefully.+--+-- The Package Versioning Policy still applies.++module Data.Series.Generic.Internal ( +    -- * Constructor+    Series(..),+    -- * Unsafe construction+    fromDistinctAscList,+    fromDistinctAscVector,+    -- * Unsafe selection+    selectSubset+) where++import Data.Series.Generic.Definition   ( Series(..), fromDistinctAscList, fromDistinctAscVector ) import Data.Series.Generic.View         ( selectSubset )
src/Data/Series/Generic/Scans.hs view
@@ -1,112 +1,112 @@-
-module Data.Series.Generic.Scans (
-    postscanl,
-    prescanl,
-
-    -- * Filling missing data
-    forwardFill,
-) where
-
-import           Data.Series.Generic.Definition ( Series(..) )
-
-import           Data.Vector.Generic            ( Vector )
-import qualified Data.Vector.Generic            as Vector    
-
--- $setup
--- >>> import qualified Data.Series.Generic ( Series )
--- >>> import qualified Data.Series.Generic as Series
--- >>> import qualified Data.Series.Index as Index
-
--- | \(O(n)\) Left-to-right postscan.
---
--- >>> import qualified Data.Vector as V 
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series V.Vector Int Int
--- >>> xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      3
---     3 |      4
--- >>> postscanl (+) 0 xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      3
---     2 |      6
---     3 |     10
-postscanl :: (Vector v a, Vector v b) => (a -> b -> a) -> a -> Series v k b -> Series v k a
-{-# INLINABLE postscanl #-}
-postscanl f s (MkSeries ix vs) = MkSeries ix $ Vector.postscanl f s vs
-
-
--- | \(O(n)\) Left-to-right prescan.
---
--- >>> import qualified Data.Vector as V 
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series V.Vector Int Int
--- >>> xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      3
---     3 |      4
--- >>> prescanl (+) 0 xs
--- index | values
--- ----- | ------
---     0 |      0
---     1 |      1
---     2 |      3
---     3 |      6
-prescanl :: (Vector v a, Vector v b) => (a -> b -> a) -> a -> Series v k b -> Series v k a
-{-# INLINABLE prescanl #-}
-prescanl f s (MkSeries ix vs) = MkSeries ix $ Vector.prescanl f s vs
-
-
--- | \(O(n)\) Replace all instances of 'Nothing' with the last previous
--- value which was not 'Nothing'.
---
--- >>> import qualified Data.Vector as V 
--- >>> let xs = Series.fromList (zip [0..] [Just 1, Just 2,Nothing, Just 3]) :: Series V.Vector Int (Maybe Int)
--- >>> xs
--- index |  values
--- ----- |  ------
---     0 |  Just 1
---     1 |  Just 2
---     2 | Nothing
---     3 |  Just 3
--- >>> forwardFill 0 xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      2
---     3 |      3
---
--- If the first entry of the series is missing, the first input to 'forwardFill' will be used:
---
--- >>> let ys = Series.fromList (zip [0..] [Nothing, Just 2,Nothing, Just 3]) :: Series V.Vector Int (Maybe Int)
--- >>> ys
--- index |  values
--- ----- |  ------
---     0 | Nothing
---     1 |  Just 2
---     2 | Nothing
---     3 |  Just 3
--- >>> forwardFill 0 ys
--- index | values
--- ----- | ------
---     0 |      0
---     1 |      2
---     2 |      2
---     3 |      3
-forwardFill :: (Vector v a, Vector v (Maybe a))
-            => a -- ^ Until the first non-'Nothing' is found, 'Nothing' will be filled with this value.
-            -> Series v k (Maybe a)
-            -> Series v k a
-{-# INLINABLE forwardFill #-}
-forwardFill = postscanl go
-    where
-        go :: a -> Maybe a -> a
-        go lastValid Nothing = lastValid
-        go _        (Just v) = v
++module Data.Series.Generic.Scans (+    postscanl,+    prescanl,++    -- * Filling missing data+    forwardFill,+) where++import           Data.Series.Generic.Definition ( Series(..) )++import           Data.Vector.Generic            ( Vector )+import qualified Data.Vector.Generic            as Vector    ++-- $setup+-- >>> import qualified Data.Series.Generic ( Series )+-- >>> import qualified Data.Series.Generic as Series+-- >>> import qualified Data.Series.Index as Index++-- | \(O(n)\) Left-to-right postscan.+--+-- >>> import qualified Data.Vector as V +-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series V.Vector Int Int+-- >>> xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      3+--     3 |      4+-- >>> postscanl (+) 0 xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      3+--     2 |      6+--     3 |     10+postscanl :: (Vector v a, Vector v b) => (a -> b -> a) -> a -> Series v k b -> Series v k a+{-# INLINABLE postscanl #-}+postscanl f s (MkSeries ix vs) = MkSeries ix $ Vector.postscanl f s vs+++-- | \(O(n)\) Left-to-right prescan.+--+-- >>> import qualified Data.Vector as V +-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series V.Vector Int Int+-- >>> xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      3+--     3 |      4+-- >>> prescanl (+) 0 xs+-- index | values+-- ----- | ------+--     0 |      0+--     1 |      1+--     2 |      3+--     3 |      6+prescanl :: (Vector v a, Vector v b) => (a -> b -> a) -> a -> Series v k b -> Series v k a+{-# INLINABLE prescanl #-}+prescanl f s (MkSeries ix vs) = MkSeries ix $ Vector.prescanl f s vs+++-- | \(O(n)\) Replace all instances of 'Nothing' with the last previous+-- value which was not 'Nothing'.+--+-- >>> import qualified Data.Vector as V +-- >>> let xs = Series.fromList (zip [0..] [Just 1, Just 2,Nothing, Just 3]) :: Series V.Vector Int (Maybe Int)+-- >>> xs+-- index |  values+-- ----- |  ------+--     0 |  Just 1+--     1 |  Just 2+--     2 | Nothing+--     3 |  Just 3+-- >>> forwardFill 0 xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      2+--     3 |      3+--+-- If the first entry of the series is missing, the first input to 'forwardFill' will be used:+--+-- >>> let ys = Series.fromList (zip [0..] [Nothing, Just 2,Nothing, Just 3]) :: Series V.Vector Int (Maybe Int)+-- >>> ys+-- index |  values+-- ----- |  ------+--     0 | Nothing+--     1 |  Just 2+--     2 | Nothing+--     3 |  Just 3+-- >>> forwardFill 0 ys+-- index | values+-- ----- | ------+--     0 |      0+--     1 |      2+--     2 |      2+--     3 |      3+forwardFill :: (Vector v a, Vector v (Maybe a))+            => a -- ^ Until the first non-'Nothing' is found, 'Nothing' will be filled with this value.+            -> Series v k (Maybe a)+            -> Series v k a+{-# INLINABLE forwardFill #-}+forwardFill = postscanl go+    where+        go :: a -> Maybe a -> a+        go lastValid Nothing = lastValid+        go _        (Just v) = v
src/Data/Series/Generic/View.hs view
@@ -1,336 +1,336 @@-module Data.Series.Generic.View (
-    -- * Accessing a single element
-    (!),
-    at,
-    iat,
-
-    -- * Bulk access
-    select,
-    slice,
-    selectWhere,
-    selectSubset,
-    Selection,
-
-    -- * Resizing
-    require,
-    requireWith,
-    filter,
-    filterWithKey,
-    catMaybes,
-    dropIndex,
-
-    -- * Creating and accessing ranges
-    Range(..),
-    to,
-    from,
-    upto,
-) where
-
-
-import           Data.Functor           ( (<&>) )
-import           Data.Series.Index      ( Index )
-import qualified Data.Series.Index      as Index
-import qualified Data.Series.Index.Internal as Index.Internal
-import           Data.Maybe             ( fromJust, isJust )
-import           Data.Series.Generic.Definition ( Series(..) )
-import qualified Data.Series.Generic.Definition as G
-import           Data.Set               ( Set )
-import qualified Data.Set               as Set
-import qualified Data.Vector            as Boxed
-import           Data.Vector.Generic    ( Vector )
-import qualified Data.Vector.Generic    as Vector
-
-import           Prelude                hiding ( filter )
-
--- $setup
--- >>> import qualified Data.Series as Series
--- >>> import qualified Data.Series.Index as Index 
-
-infixr 9 `to` -- Ensure that @to@ binds strongest
-infixl 1 `select` 
-
-
--- | \(O(1)\). Extract a single value from a series, by index. 
--- An exception is thrown if the index is out-of-bounds.
---
--- A safer alternative is @iat@, which returns 'Nothing' if the index is
--- out-of-bounds.
-(!) :: Vector v a => Series v k a -> Int -> a
-(MkSeries _ vs) ! ix = (Vector.!) vs ix
-
-
--- | \(O(\log n)\). Extract a single value from a series, by key.
-at :: (Vector v a, Ord k) => Series v k a -> k -> Maybe a
-at (MkSeries ks vs) k = Index.lookupIndex k ks <&> Vector.unsafeIndex vs 
-{-# INLINABLE at #-}
-
-
--- | \(O(1)\). Extract a single value from a series, by index.
-iat :: Vector v a => Series v k a -> Int -> Maybe a
-iat (MkSeries _ vs) =  (Vector.!?) vs
-{-# INLINABLE iat #-}
-
-
--- | Require a series with a new index.
--- Contrary to 'select', all keys in @'Index' k@ will be present in the re-indexed series.
-require :: (Vector v a, Vector v (Maybe a), Ord k) 
-        => Series v k a -> Index k -> Series v k (Maybe a)
-{-# INLINABLE require #-}
-require = requireWith (const Nothing) Just
-
-
--- | Generalization of 'require', which maps missing keys to values.
--- This is particularly useful for 'Vector' instances which don't support 'Maybe', like "Data.Vector.Unboxed".
-requireWith :: (Vector v a, Vector v b, Ord k)
-            => (k -> b)  -- ^ Function to apply to keys which are missing from the input series, but required in the input index
-            -> (a -> b)  -- ^ Function to apply to values which are in the input series and input index.
-            -> Series v k a 
-            -> Index k 
-            -> Series v k b
-{-# INLINABLE requireWith #-}
-requireWith replacement f xs ss 
-    = let existingKeys = index xs `Index.intersection` ss
-          newKeys      = ss `Index.difference` existingKeys
-       in G.map f (xs `selectSubset` existingKeys) <> MkSeries newKeys (Vector.fromListN (Index.size newKeys) (replacement <$> Index.toAscList newKeys))
-
-
--- | \(O(n)\) Drop the index of a series by replacing it with an @Int@-based index. Values will
--- be indexed from 0.
-dropIndex :: Series v k a -> Series v Int a
-{-# INLINABLE dropIndex #-}
-dropIndex (MkSeries ks vs) = MkSeries (Index.Internal.fromDistinctAscList [0..Index.size ks - 1]) vs
-
-
--- | Filter elements. Only elements for which the predicate is @True@ are kept. 
--- Notice that the filtering is done on the values, not on the keys; see 'filterWithKey'
--- to filter while taking keys into account.
-filter :: (Vector v a, Vector v Int, Ord k) 
-       => (a -> Bool) -> Series v k a -> Series v k a
-{-# INLINABLE filter #-}
-filter predicate xs@(MkSeries ks vs) 
-    = let indicesToKeep = Vector.findIndices predicate vs
-          keysToKeep = Index.Internal.fromDistinctAscList [Index.Internal.elemAt ix ks | ix <- Vector.toList indicesToKeep]
-       in xs `select` keysToKeep
-
-
--- | Filter elements, taking into account the corresponding key. Only elements for which 
--- the predicate is @True@ are kept. 
-filterWithKey :: (Vector v a, Vector v Int, Vector v Bool, Ord k) 
-              => (k -> a -> Bool) 
-              -> Series v k a 
-              -> Series v k a
-{-# INLINABLE filterWithKey #-}
-filterWithKey predicate xs = xs `selectWhere` G.mapWithKey predicate xs
-
-
--- | \(O(n)\) Only keep elements which are @'Just' v@. 
-catMaybes :: (Vector v a, Vector v (Maybe a), Vector v Int, Ord k) 
-       => Series v k (Maybe a) -> Series v k a
-{-# INLINABLE catMaybes #-}
-catMaybes = G.map fromJust . filter isJust
-
-
--- | Datatype representing an /inclusive/ range of keys, which can either be bounded
--- or unbounded. The canonical ways to construct a 'Range' are to use 'to', 'from', and 'upto':
---
--- >>> 'a' `to` 'z'
--- Range (from 'a' to 'z')
--- >>> from 'd'
--- Range (from 'd')
--- >>> upto 'q'
--- Range (up to 'q')
---
--- A 'Range' can be used to efficiently select a sub-series with 'select'.
-data Range k 
-    = BoundedRange k k
-    | From k
-    | UpTo k
-    deriving (Eq)
-
-
-instance Show k => Show (Range k) where
-    show :: Range k -> String
-    show (BoundedRange start stop) = mconcat ["Range (from ", show start, " to ", show stop, ")"]
-    show (From start) = mconcat ["Range (from ", show start, ")"]
-    show (UpTo stop) = mconcat ["Range (up to ", show stop, ")"]
-
-
--- | Find the keys which are in range. In case of an empty 'Series',
--- the returned value is 'Nothing'.
-keysInRange :: Ord k => Series v k a -> Range k -> Maybe (k, k)
-{-# INLINABLE keysInRange #-}
-keysInRange (MkSeries ks _) rng
-    = let inrange = inRange rng
-       in if Set.null inrange 
-            then Nothing
-            else Just (Set.findMin inrange, Set.findMax inrange)
-    where
-        inRange (BoundedRange start stop)  = Set.takeWhileAntitone (<= stop) 
-                                           $ Set.dropWhileAntitone (< start) $ Index.toSet ks
-        inRange (From start)               = Set.dropWhileAntitone (< start) $ Index.toSet ks
-        inRange (UpTo stop)                = Set.takeWhileAntitone (<= stop) $ Index.toSet ks
-
-
--- | Create a bounded 'Range' which can be used for slicing. This function
--- is expected to be used in conjunction with 'select'.
---
--- For unbound ranges, see 'from' and 'upto'.
-to :: Ord k => k -> k -> Range k
-to k1 k2 = BoundedRange (min k1 k2) (max k1 k2)
-
-
--- | Create an unbounded 'Range' which can be used for slicing. 
--- This function is expected to be used in conjunction with 'select'. 
---
--- For bound ranges, see 'to'.
-from :: k -> Range k
-from = From
-
-
--- | Create an unbounded 'Range' which can be used for slicing. This function
--- is expected to be used in conjunction with 'select'. 
---
--- For bound ranges, see 'to'.
-upto :: k -> Range k
-upto = UpTo
-
-
--- | Class for datatypes which can be used to select sub-series using 'select'.
---
--- There are two use-cases for 'select':
---
---  * Bulk random-access (selecting from an 'Index' of keys);
---  * Bulk ordered access (selecting from a 'Range' of keys).
---
--- See the documentation for 'select'.
-class Selection s where
-    -- | Select a subseries. There are two main ways to do this.
-    --
-    -- The first way to do this is to select a sub-series based on keys:
-    --
-    -- >>> let xs = Series.fromList [('a', 10::Int), ('b', 20), ('c', 30), ('d', 40)]
-    -- >>> xs `select` Index.fromList ['a', 'd']
-    -- index | values
-    -- ----- | ------
-    --   'a' |     10
-    --   'd' |     40
-    --
-    -- The second way to select a sub-series is to select all keys in a range:
-    --
-    -- >>> xs `select` 'b' `to` 'c'
-    -- index | values
-    -- ----- | ------
-    --   'b' |     20
-    --   'c' |     30
-    --
-    -- Such ranges can also be unbounded. (i.e. all keys smaller or larger than some key), like so:
-    --
-    -- >>> xs `select` upto 'c'
-    -- index | values
-    -- ----- | ------
-    --   'a' |     10
-    --   'b' |     20
-    --   'c' |     30
-    -- >>> xs `select` from 'c'
-    -- index | values
-    -- ----- | ------
-    --   'c' |     30
-    --   'd' |     40
-    --
-    -- Note that with 'select', you'll always get a sub-series; if you ask for a key which is not
-    -- in the series, it'll be ignored:
-    --
-    -- >>> xs `select` Index.fromList ['a', 'd', 'e']
-    -- index | values
-    -- ----- | ------
-    --   'a' |     10
-    --   'd' |     40
-    --
-    -- See 'require' if you want to ensure that all keys are present.
-    select :: (Vector v a, Ord k) => Series v k a -> s k -> Series v k a
-
-
-instance Selection Index where
-    -- | Select all keys in 'Index' from a series. Keys which are not
-    -- in the series are ignored.
-    select :: (Vector v a, Ord k) => Series v k a -> Index k -> Series v k a
-    {-# INLINABLE select #-}
-    select xs ss
-        = let selectedKeys = index xs `Index.intersection` ss
-            -- Surprisingly, using `Vector.backpermute` does not
-            -- perform as well as `Vector.map (Vector.unsafeIndex vs)`
-            -- for large Series
-           in xs `selectSubset` selectedKeys
-
-
--- | Selecting a sub-series from a 'Set' is a convenience
--- function. Internally, the 'Set' is converted to an index first.
-instance Selection Set where
-    select :: (Vector v a, Ord k) => Series v k a -> Set k -> Series v k a
-    {-# INLINABLE select #-}
-    select xs = select xs . Index.fromSet
-
-
--- | Selecting a sub-series from a list is a convenience
--- function. Internally, the list is converted to an index first.
-instance Selection [] where
-    select :: (Vector v a, Ord k) => Series v k a -> [k] -> Series v k a
-    {-# INLINABLE select #-}
-    select xs = select xs . Index.fromList
-
-
--- | Selecting a sub-series based on a @Range@ is most performant.
--- Constructing a @Range@ is most convenient using the 'to' function.
-instance Selection Range where
-    select :: (Vector v a, Ord k) => Series v k a -> Range k -> Series v k a
-    {-# INLINABLE select #-}
-    select series rng = case keysInRange series rng of 
-        Nothing              -> mempty
-        Just (kstart, kstop) -> let indexOf xs k = Index.Internal.findIndex k (index xs)
-                                 in slice (series `indexOf` kstart) (1 + series `indexOf` kstop) series
-
-
--- | Select a sub-series from a series matching a condition.
-selectWhere :: (Vector v a, Vector v Int, Vector v Bool, Ord k) => Series v k a -> Series v k Bool -> Series v k a
-{-# INLINABLE selectWhere #-}
-selectWhere xs ys = xs `select` Index.fromSet keysWhereTrue
-    where
-        (MkSeries _ cond) = ys `select` index xs
-        whereValuesAreTrue = Set.fromDistinctAscList $ Vector.toList (Vector.findIndices id cond)
-        keysWhereTrue = Set.mapMonotonic (`Index.Internal.elemAt` index xs) whereValuesAreTrue
-
-
--- | Implementation of `select` where the selection keys are known
--- to be a subset of the series. This precondition is NOT checked.
---
--- This is a performance optimization and therefore is not normally exposed.
-selectSubset :: (Vector v a, Ord k) => Series v k a -> Index k -> Series v k a
-{-# INLINABLE selectSubset #-}
-selectSubset (MkSeries ks vs) ss
-    -- TODO: 
-    --   Is it possible to scan over the series once
-    --   while filtering away on keys? Initial attempts did not lead
-    --   to performance improvements, but I can't imagine that calling
-    --   `Index.Internal.findIndex` repeatedly is efficient
-    --
-    --   Maybe use Data.Series.Index.indexed to traverse the index once?
-    = MkSeries ss $ Boxed.convert
-                  $ Boxed.map (Vector.unsafeIndex vs . (`Index.Internal.findIndex` ks))
-                  $ Index.toAscVector ss
-
-
--- | \(O(\log n)\) Yield a subseries based on integer indices. The end index is not included.
-slice :: Vector v a
-      => Int -- ^ Start index
-      -> Int -- ^ End index, which is not included
-      -> Series v k a 
-      -> Series v k a
-{-# INLINABLE slice #-}
-slice start stop (MkSeries ks vs) 
-    = let stop' = min (Vector.length vs) stop
-    -- Index.take is O(log n) while Vector.slice is O(1)
-    in MkSeries { index  = Index.take (stop' - start) $ Index.drop start ks
-                , values = Vector.slice start (stop' - start) vs
-                }
-
-
+module Data.Series.Generic.View (+    -- * Accessing a single element+    (!),+    at,+    iat,++    -- * Bulk access+    select,+    slice,+    selectWhere,+    selectSubset,+    Selection,++    -- * Resizing+    require,+    requireWith,+    filter,+    filterWithKey,+    catMaybes,+    dropIndex,++    -- * Creating and accessing ranges+    Range(..),+    to,+    from,+    upto,+) where+++import           Data.Functor           ( (<&>) )+import           Data.Series.Index      ( Index )+import qualified Data.Series.Index      as Index+import qualified Data.Series.Index.Internal as Index.Internal+import           Data.Maybe             ( fromJust, isJust )+import           Data.Series.Generic.Definition ( Series(..) )+import qualified Data.Series.Generic.Definition as G+import           Data.Set               ( Set )+import qualified Data.Set               as Set+import qualified Data.Vector            as Boxed+import           Data.Vector.Generic    ( Vector )+import qualified Data.Vector.Generic    as Vector++import           Prelude                hiding ( filter )++-- $setup+-- >>> import qualified Data.Series as Series+-- >>> import qualified Data.Series.Index as Index ++infixr 9 `to` -- Ensure that @to@ binds strongest+infixl 1 `select` +++-- | \(O(1)\). Extract a single value from a series, by index. +-- An exception is thrown if the index is out-of-bounds.+--+-- A safer alternative is @iat@, which returns 'Nothing' if the index is+-- out-of-bounds.+(!) :: Vector v a => Series v k a -> Int -> a+(MkSeries _ vs) ! ix = (Vector.!) vs ix+++-- | \(O(\log n)\). Extract a single value from a series, by key.+at :: (Vector v a, Ord k) => Series v k a -> k -> Maybe a+at (MkSeries ks vs) k = Index.lookupIndex k ks <&> Vector.unsafeIndex vs +{-# INLINABLE at #-}+++-- | \(O(1)\). Extract a single value from a series, by index.+iat :: Vector v a => Series v k a -> Int -> Maybe a+iat (MkSeries _ vs) =  (Vector.!?) vs+{-# INLINABLE iat #-}+++-- | Require a series with a new index.+-- Contrary to 'select', all keys in @'Index' k@ will be present in the re-indexed series.+require :: (Vector v a, Vector v (Maybe a), Ord k) +        => Series v k a -> Index k -> Series v k (Maybe a)+{-# INLINABLE require #-}+require = requireWith (const Nothing) Just+++-- | Generalization of 'require', which maps missing keys to values.+-- This is particularly useful for 'Vector' instances which don't support 'Maybe', like "Data.Vector.Unboxed".+requireWith :: (Vector v a, Vector v b, Ord k)+            => (k -> b)  -- ^ Function to apply to keys which are missing from the input series, but required in the input index+            -> (a -> b)  -- ^ Function to apply to values which are in the input series and input index.+            -> Series v k a +            -> Index k +            -> Series v k b+{-# INLINABLE requireWith #-}+requireWith replacement f xs ss +    = let existingKeys = index xs `Index.intersection` ss+          newKeys      = ss `Index.difference` existingKeys+       in G.map f (xs `selectSubset` existingKeys) <> MkSeries newKeys (Vector.fromListN (Index.size newKeys) (replacement <$> Index.toAscList newKeys))+++-- | \(O(n)\) Drop the index of a series by replacing it with an @Int@-based index. Values will+-- be indexed from 0.+dropIndex :: Series v k a -> Series v Int a+{-# INLINABLE dropIndex #-}+dropIndex (MkSeries ks vs) = MkSeries (Index.Internal.fromDistinctAscList [0..Index.size ks - 1]) vs+++-- | Filter elements. Only elements for which the predicate is @True@ are kept. +-- Notice that the filtering is done on the values, not on the keys; see 'filterWithKey'+-- to filter while taking keys into account.+filter :: (Vector v a, Vector v Int, Ord k) +       => (a -> Bool) -> Series v k a -> Series v k a+{-# INLINABLE filter #-}+filter predicate xs@(MkSeries ks vs) +    = let indicesToKeep = Vector.findIndices predicate vs+          keysToKeep = Index.Internal.fromDistinctAscList [Index.Internal.elemAt ix ks | ix <- Vector.toList indicesToKeep]+       in xs `select` keysToKeep+++-- | Filter elements, taking into account the corresponding key. Only elements for which +-- the predicate is @True@ are kept. +filterWithKey :: (Vector v a, Vector v Int, Vector v Bool, Ord k) +              => (k -> a -> Bool) +              -> Series v k a +              -> Series v k a+{-# INLINABLE filterWithKey #-}+filterWithKey predicate xs = xs `selectWhere` G.mapWithKey predicate xs+++-- | \(O(n)\) Only keep elements which are @'Just' v@. +catMaybes :: (Vector v a, Vector v (Maybe a), Vector v Int, Ord k) +       => Series v k (Maybe a) -> Series v k a+{-# INLINABLE catMaybes #-}+catMaybes = G.map fromJust . filter isJust+++-- | Datatype representing an /inclusive/ range of keys, which can either be bounded+-- or unbounded. The canonical ways to construct a 'Range' are to use 'to', 'from', and 'upto':+--+-- >>> 'a' `to` 'z'+-- Range (from 'a' to 'z')+-- >>> from 'd'+-- Range (from 'd')+-- >>> upto 'q'+-- Range (up to 'q')+--+-- A 'Range' can be used to efficiently select a sub-series with 'select'.+data Range k +    = BoundedRange k k+    | From k+    | UpTo k+    deriving (Eq)+++instance Show k => Show (Range k) where+    show :: Range k -> String+    show (BoundedRange start stop) = mconcat ["Range (from ", show start, " to ", show stop, ")"]+    show (From start) = mconcat ["Range (from ", show start, ")"]+    show (UpTo stop) = mconcat ["Range (up to ", show stop, ")"]+++-- | Find the keys which are in range. In case of an empty 'Series',+-- the returned value is 'Nothing'.+keysInRange :: Ord k => Series v k a -> Range k -> Maybe (k, k)+{-# INLINABLE keysInRange #-}+keysInRange (MkSeries ks _) rng+    = let inrange = inRange rng+       in if Set.null inrange +            then Nothing+            else Just (Set.findMin inrange, Set.findMax inrange)+    where+        inRange (BoundedRange start stop)  = Set.takeWhileAntitone (<= stop) +                                           $ Set.dropWhileAntitone (< start) $ Index.toSet ks+        inRange (From start)               = Set.dropWhileAntitone (< start) $ Index.toSet ks+        inRange (UpTo stop)                = Set.takeWhileAntitone (<= stop) $ Index.toSet ks+++-- | Create a bounded 'Range' which can be used for slicing. This function+-- is expected to be used in conjunction with 'select'.+--+-- For unbound ranges, see 'from' and 'upto'.+to :: Ord k => k -> k -> Range k+to k1 k2 = BoundedRange (min k1 k2) (max k1 k2)+++-- | Create an unbounded 'Range' which can be used for slicing. +-- This function is expected to be used in conjunction with 'select'. +--+-- For bound ranges, see 'to'.+from :: k -> Range k+from = From+++-- | Create an unbounded 'Range' which can be used for slicing. This function+-- is expected to be used in conjunction with 'select'. +--+-- For bound ranges, see 'to'.+upto :: k -> Range k+upto = UpTo+++-- | Class for datatypes which can be used to select sub-series using 'select'.+--+-- There are two use-cases for 'select':+--+--  * Bulk random-access (selecting from an 'Index' of keys);+--  * Bulk ordered access (selecting from a 'Range' of keys).+--+-- See the documentation for 'select'.+class Selection s where+    -- | Select a subseries. There are two main ways to do this.+    --+    -- The first way to do this is to select a sub-series based on keys:+    --+    -- >>> let xs = Series.fromList [('a', 10::Int), ('b', 20), ('c', 30), ('d', 40)]+    -- >>> xs `select` Index.fromList ['a', 'd']+    -- index | values+    -- ----- | ------+    --   'a' |     10+    --   'd' |     40+    --+    -- The second way to select a sub-series is to select all keys in a range:+    --+    -- >>> xs `select` 'b' `to` 'c'+    -- index | values+    -- ----- | ------+    --   'b' |     20+    --   'c' |     30+    --+    -- Such ranges can also be unbounded. (i.e. all keys smaller or larger than some key), like so:+    --+    -- >>> xs `select` upto 'c'+    -- index | values+    -- ----- | ------+    --   'a' |     10+    --   'b' |     20+    --   'c' |     30+    -- >>> xs `select` from 'c'+    -- index | values+    -- ----- | ------+    --   'c' |     30+    --   'd' |     40+    --+    -- Note that with 'select', you'll always get a sub-series; if you ask for a key which is not+    -- in the series, it'll be ignored:+    --+    -- >>> xs `select` Index.fromList ['a', 'd', 'e']+    -- index | values+    -- ----- | ------+    --   'a' |     10+    --   'd' |     40+    --+    -- See 'require' if you want to ensure that all keys are present.+    select :: (Vector v a, Ord k) => Series v k a -> s k -> Series v k a+++instance Selection Index where+    -- | Select all keys in 'Index' from a series. Keys which are not+    -- in the series are ignored.+    select :: (Vector v a, Ord k) => Series v k a -> Index k -> Series v k a+    {-# INLINABLE select #-}+    select xs ss+        = let selectedKeys = index xs `Index.intersection` ss+            -- Surprisingly, using `Vector.backpermute` does not+            -- perform as well as `Vector.map (Vector.unsafeIndex vs)`+            -- for large Series+           in xs `selectSubset` selectedKeys+++-- | Selecting a sub-series from a 'Set' is a convenience+-- function. Internally, the 'Set' is converted to an index first.+instance Selection Set where+    select :: (Vector v a, Ord k) => Series v k a -> Set k -> Series v k a+    {-# INLINABLE select #-}+    select xs = select xs . Index.fromSet+++-- | Selecting a sub-series from a list is a convenience+-- function. Internally, the list is converted to an index first.+instance Selection [] where+    select :: (Vector v a, Ord k) => Series v k a -> [k] -> Series v k a+    {-# INLINABLE select #-}+    select xs = select xs . Index.fromList+++-- | Selecting a sub-series based on a @Range@ is most performant.+-- Constructing a @Range@ is most convenient using the 'to' function.+instance Selection Range where+    select :: (Vector v a, Ord k) => Series v k a -> Range k -> Series v k a+    {-# INLINABLE select #-}+    select series rng = case keysInRange series rng of +        Nothing              -> mempty+        Just (kstart, kstop) -> let indexOf xs k = Index.Internal.findIndex k (index xs)+                                 in slice (series `indexOf` kstart) (1 + series `indexOf` kstop) series+++-- | Select a sub-series from a series matching a condition.+selectWhere :: (Vector v a, Vector v Int, Vector v Bool, Ord k) => Series v k a -> Series v k Bool -> Series v k a+{-# INLINABLE selectWhere #-}+selectWhere xs ys = xs `select` Index.fromSet keysWhereTrue+    where+        (MkSeries _ cond) = ys `select` index xs+        whereValuesAreTrue = Set.fromDistinctAscList $ Vector.toList (Vector.findIndices id cond)+        keysWhereTrue = Set.mapMonotonic (`Index.Internal.elemAt` index xs) whereValuesAreTrue+++-- | Implementation of `select` where the selection keys are known+-- to be a subset of the series. This precondition is NOT checked.+--+-- This is a performance optimization and therefore is not normally exposed.+selectSubset :: (Vector v a, Ord k) => Series v k a -> Index k -> Series v k a+{-# INLINABLE selectSubset #-}+selectSubset (MkSeries ks vs) ss+    -- TODO: +    --   Is it possible to scan over the series once+    --   while filtering away on keys? Initial attempts did not lead+    --   to performance improvements, but I can't imagine that calling+    --   `Index.Internal.findIndex` repeatedly is efficient+    --+    --   Maybe use Data.Series.Index.indexed to traverse the index once?+    = MkSeries ss $ Boxed.convert+                  $ Boxed.map (Vector.unsafeIndex vs . (`Index.Internal.findIndex` ks))+                  $ Index.toAscVector ss+++-- | \(O(\log n)\) Yield a subseries based on integer indices. The end index is not included.+slice :: Vector v a+      => Int -- ^ Start index+      -> Int -- ^ End index, which is not included+      -> Series v k a +      -> Series v k a+{-# INLINABLE slice #-}+slice start stop (MkSeries ks vs) +    = let stop' = min (Vector.length vs) stop+    -- Index.take is O(log n) while Vector.slice is O(1)+    in MkSeries { index  = Index.take (stop' - start) $ Index.drop start ks+                , values = Vector.slice start (stop' - start) vs+                }++
src/Data/Series/Generic/Zip.hs view
@@ -1,463 +1,463 @@-module Data.Series.Generic.Zip (
-    zipWith, zipWithMatched, zipWithKey,
-    zipWith3, zipWithMatched3, zipWithKey3,
-    replace, (|->), (<-|),
-    
-    -- * Generalized zipping with strategies
-    zipWithStrategy,
-    zipWithStrategy3,
-    ZipStrategy,
-    skipStrategy,
-    mapStrategy,
-    constStrategy,
-
-    -- * Special case of zipping monoids
-    zipWithMonoid,
-    esum, eproduct,
-
-    -- * Unzipping
-    unzip, unzip3,
-) where
-
-import qualified Data.Map.Strict                as Map
-import           Data.Monoid                    ( Sum(..), Product(..) )
-import           Data.Series.Generic.Definition ( Series(MkSeries, index, values) )
-import qualified Data.Series.Generic.Definition as G
-import           Data.Series.Generic.View       ( selectSubset, requireWith )
-import           Data.Vector.Generic            ( Vector )
-import qualified Data.Vector.Generic            as Vector
-import qualified Data.Series.Index              as Index
-import qualified Data.Series.Index.Internal     as Index.Internal
-import           Prelude                        hiding ( zipWith, zipWith3, unzip, unzip3 ) 
-
--- $setup
--- >>> import qualified Data.Series as Series
-
-infix 6 |->, <-|
-
--- | Apply a function elementwise to two series, matching elements
--- based on their keys. For keys present only in the left or right series, 
--- the value 'Nothing' is returned.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]
--- >>> zipWith (+) xs ys
---   index |  values
---   ----- |  ------
--- "alpha" | Just 10
---  "beta" | Just 12
--- "delta" | Nothing
--- "gamma" | Nothing
---
--- To only combine elements where keys are in both series, see 'zipWithMatched'
-zipWith :: (Vector v a, Vector v b, Vector v c, Vector v (Maybe c), Ord k) 
-        => (a -> b -> c) -> Series v k a -> Series v k b -> Series v k (Maybe c)
-zipWith f left right
-    = let matched = zipWithMatched f left right
-          matchedKeys   = index matched
-          allKeys       = index left `Index.union` index right
-          unmatchedKeys = allKeys `Index.difference` matchedKeys
-          unmatched     = MkSeries unmatchedKeys (Vector.replicate (Index.size unmatchedKeys) Nothing)
-       in G.map Just matched <> unmatched
-{-# INLINABLE zipWith #-}
-
-
--- | Apply a function elementwise to three series, matching elements
--- based on their keys. For keys present only in the left or right series, 
--- the value 'Nothing' is returned.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int),  ("beta", 1),   ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11),  ("delta", 13) ]
--- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ]
--- >>> zipWith3 (\x y z -> x + y + z) xs ys zs
---     index |  values
---     ----- |  ------
---   "alpha" | Just 30
---    "beta" | Nothing
---   "delta" | Nothing
--- "epsilon" | Nothing
---   "gamma" | Nothing
---
--- To only combine elements where keys are in all series, see 'zipWithMatched3'
-zipWith3 :: (Vector v a, Vector v b, Vector v c, Vector v d, Vector v (Maybe d), Ord k) 
-         => (a -> b -> c -> d) 
-         -> Series v k a 
-         -> Series v k b 
-         -> Series v k c 
-         -> Series v k (Maybe d)
-zipWith3 f left center right
-    = let matched       = zipWithMatched3 f left center right
-          matchedKeys   = index matched
-          allKeys       = index left `Index.union` index center `Index.union` index right
-          unmatchedKeys = allKeys `Index.difference` matchedKeys
-          unmatched     = MkSeries unmatchedKeys (Vector.replicate (Index.size unmatchedKeys) Nothing)
-       in G.map Just matched <> unmatched
-{-# INLINABLE zipWith3 #-}
-
-
-
--- | Apply a function elementwise to two series, matching elements
--- based on their keys. Keys present only in the left or right series are dropped.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]
--- >>> zipWithMatched (+) xs ys
---   index | values
---   ----- | ------
--- "alpha" |     10
---  "beta" |     12
---
--- To combine elements where keys are in either series, see 'zipWith'. To combine
--- three series, see 'zipWithMatched3'.
-zipWithMatched :: (Vector v a, Vector v b, Vector v c, Ord k) 
-               => (a -> b -> c) -> Series v k a -> Series v k b -> Series v k c
-zipWithMatched f left right
-    = let matchedKeys   = index left `Index.intersection` index right
-          -- Recall that `selectSubset` is a performance optimization
-          -- and is generally unsafe to use; however, in this case, we know
-          -- that `matchedKeys` are subsets of the index of both series
-          (MkSeries _ !xs) = left  `selectSubset` matchedKeys
-          (MkSeries _ !ys) = right `selectSubset` matchedKeys
-          -- The following construction relies on the fact that keys are always sorted
-       in MkSeries matchedKeys $ Vector.zipWith f xs ys
-{-# INLINABLE zipWithMatched #-}
-
-
--- | Apply a function elementwise to three series, matching elements
--- based on their keys. Keys not present in all three series are dropped.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int),  ("beta", 1),   ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11),  ("delta", 13) ]
--- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ]
--- >>> zipWithMatched3 (\x y z -> x + y + z) xs ys zs
---   index | values
---   ----- | ------
--- "alpha" |     30
-zipWithMatched3 :: (Vector v a, Vector v b, Vector v c, Vector v d, Ord k) 
-                => (a -> b -> c -> d) 
-                -> Series v k a 
-                -> Series v k b 
-                -> Series v k c
-                -> Series v k d
-zipWithMatched3 f left center right
-    = let matchedKeys   = index left `Index.intersection` index center `Index.intersection` index right
-          -- Recall that `selectSubset` is a performance optimization
-          -- and is generally unsafe to use; however, in this case, we know
-          -- that `matchedKeys` are subsets of the index of all series
-          (MkSeries _ !xs) = left   `selectSubset` matchedKeys
-          (MkSeries _ !ys) = center `selectSubset` matchedKeys
-          (MkSeries _ !zs) = right  `selectSubset` matchedKeys
-          -- The following construction relies on the fact that keys are always sorted
-       in MkSeries matchedKeys $ Vector.zipWith3 f xs ys zs
-{-# INLINABLE zipWithMatched3 #-}
-
-
--- | Apply a function elementwise to two series, matching elements
--- based on their keys. Keys present only in the left or right series are dropped.
--- 
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]
--- >>> zipWithKey (\k x y -> length k + x + y) xs ys
---   index | values
---   ----- | ------
--- "alpha" |     15
---  "beta" |     16
---
--- To combine elements where keys are in either series, see 'zipWith'
-zipWithKey :: (Vector v a, Vector v b, Vector v c, Vector v k, Ord k) 
-           => (k -> a -> b -> c) -> Series v k a -> Series v k b -> Series v k c
-zipWithKey f left right
-    = let matchedKeys   = index left `Index.intersection` index right
-          -- Recall that `selectSubset` is a performance optimization
-          -- and is generally unsafe to use; however, in this case, we know
-          -- that `matchedKeys` are subsets of the index of both series
-          (MkSeries _ xs) = left  `selectSubset` matchedKeys
-          (MkSeries _ ys) = right `selectSubset` matchedKeys
-          ks              = Index.toAscVector matchedKeys
-          -- The following construction relies on the fact that keys are always sorted
-       in  MkSeries matchedKeys $ Vector.zipWith3 f ks xs ys
-{-# INLINABLE zipWithKey #-}
-
-
--- | Apply a function elementwise to three series, matching elements
--- based on their keys. Keys not present in all series are dropped.
--- 
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]
--- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("beta", 7), ("delta", 5) ]
--- >>> zipWithKey3 (\k x y z -> length k + x + y + z) xs ys zs
---   index | values
---   ----- | ------
--- "alpha" |     35
---  "beta" |     23
-
-zipWithKey3 :: (Vector v a, Vector v b, Vector v c, Vector v d, Vector v k, Ord k) 
-            => (k -> a -> b -> c -> d) 
-            -> Series v k a 
-            -> Series v k b 
-            -> Series v k c
-            -> Series v k d
-zipWithKey3 f left center right
-    = let matchedKeys   = index left `Index.intersection` index right
-          -- Recall that `selectSubset` is a performance optimization
-          -- and is generally unsafe to use; however, in this case, we know
-          -- that `matchedKeys` are subsets of the index of all series
-          (MkSeries _ xs) = left   `selectSubset` matchedKeys
-          (MkSeries _ ys) = center `selectSubset` matchedKeys
-          (MkSeries _ zs) = right  `selectSubset` matchedKeys
-          ks              = Index.toAscVector matchedKeys
-          -- The following construction relies on the fact that keys are always sorted
-       in  MkSeries matchedKeys $ Vector.zipWith4 f ks xs ys zs
-{-# INLINABLE zipWithKey3 #-}
-
-
--- | Replace values from the right series with values from the left series at matching keys.
--- Keys in the right series but not in the right series are unaffected.
-replace :: (Vector v a, Vector v Int, Ord k) 
-        => Series v k a -> Series v k a -> Series v k a
-{-# INLINABLE replace #-}
-xs `replace` ys 
-    = let keysToReplace = index xs `Index.intersection` index ys
-          iixs          = Index.toAscVector $ Index.Internal.mapMonotonic (\k -> Index.Internal.findIndex k (index ys)) keysToReplace
-       in MkSeries (index ys) $ Vector.update_ (values ys) iixs (values (xs `selectSubset` keysToReplace))
-
-
--- | Infix version of 'replace'
-(|->) :: (Vector v a, Vector v Int, Ord k)
-      => Series v k a -> Series v k a -> Series v k a
-{-# INLINABLE (|->) #-}
-(|->) = replace
-
-
--- | Flipped version of '|->',
-(<-|) :: (Vector v a, Vector v Int, Ord k) 
-      => Series v k a -> Series v k a -> Series v k a
-{-# INLINABLE (<-|)  #-}
-(<-|) = flip replace
-
-
--- | A 'ZipStrategy' is a function which is used to decide what to do when a key is missing from one
--- of two 'Series' being zipped together with 'zipWithStrategy'.
---
--- If a 'ZipStrategy' returns 'Nothing', the key is dropped.
--- If a 'ZipStrategy' returns @'Just' v@ for key @k@, then the value @v@ is inserted at key @k@.
---
--- For example, the most basic 'ZipStrategy' is to skip over any key which is missing from the other series.
--- Such a strategy can be written as @skip key value = 'Nothing'@ (see 'skipStrategy').
-type ZipStrategy k a b = (k -> a -> Maybe b)
-
-
--- | This 'ZipStrategy' drops keys which are not present in both 'Series'.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]
--- >>> zipWithStrategy (+) skipStrategy skipStrategy xs ys
---   index | values
---   ----- | ------
--- "alpha" |     10
---  "beta" |     12
-skipStrategy :: ZipStrategy k a b
-skipStrategy _ _ = Nothing
-{-# INLINABLE skipStrategy #-}
-
-
--- | This 'ZipStrategy' sets the value at keys which are not present in both 'Series' 
--- to the some mapping from the value present in one of the series. See the example below.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 5::Int), ("beta", 6), ("delta", 7) ]
--- >>> zipWithStrategy (+) (mapStrategy id) (mapStrategy (*10)) xs ys
---   index | values
---   ----- | ------
--- "alpha" |      5
---  "beta" |      7
--- "delta" |     70
--- "gamma" |      2
-mapStrategy :: (a -> b) -> ZipStrategy k a b
-mapStrategy f _ x = Just (f x)
-{-# INLINABLE mapStrategy #-}
-
-
--- | This 'ZipStrategy' sets a constant value at keys which are not present in both 'Series'.
---
--- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]
--- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]
--- >>> zipWith (+) xs ys
---   index |  values
---   ----- |  ------
--- "alpha" | Just 10
---  "beta" | Just 12
--- "delta" | Nothing
--- "gamma" | Nothing
--- >>> zipWithStrategy (+) (constStrategy (-100)) (constStrategy 200)  xs ys
---   index | values
---   ----- | ------
--- "alpha" |     10
---  "beta" |     12
--- "delta" |    200
--- "gamma" |   -100
-constStrategy :: b -> ZipStrategy k a b
-constStrategy v = mapStrategy (const v)
-{-# INLINABLE constStrategy #-}
-
-
--- | Zip two 'Series' with a combining function, applying a 'ZipStrategy' when one key is present in one of the 'Series' but not both.
---
--- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched' f@ 
--- than using @'zipWithStrategy' f skipStrategy skipStrategy@.
-zipWithStrategy :: (Vector v a, Vector v b, Vector v c, Ord k) 
-                => (a -> b -> c)     -- ^ Function to combine values when present in both series
-                -> ZipStrategy k a c -- ^ Strategy for when the key is in the left series but not the right
-                -> ZipStrategy k b c -- ^ Strategy for when the key is in the right series but not the left
-                -> Series v k a
-                -> Series v k b 
-                -> Series v k c
-zipWithStrategy f whenLeft whenRight left right 
-    = let onlyLeftKeys  = index left  `Index.difference` index right
-          onlyRightKeys = index right `Index.difference` index left
-          -- Recall that `selectSubset` is a performance optimization
-          -- and is generally unsafe to use; however, in this case, we know
-          -- that `matchedKeys` are subsets of the index of both series
-          leftZip =  applyStrategy whenLeft  $ left  `selectSubset` onlyLeftKeys
-          rightZip = applyStrategy whenRight $ right `selectSubset` onlyRightKeys
-          
-        in zipWithMatched f left right <> leftZip <> rightZip
-    where
-        -- Application of the 'ZipStrategy' is done on a `Map` rather than
-        -- the 'Series' directly to keep the type contraints of `zipWithStrategy` to
-        -- a minimum. Recall that unboxed 'Series' cannot contain `Maybe a`.  
-        applyStrategy strat = G.toSeries 
-                            . Map.mapMaybeWithKey strat
-                            . G.fromSeries
-{-# INLINABLE zipWithStrategy #-}
-
-
--- | Zip three 'Series' with a combining function, applying a 'ZipStrategy' when one key is 
--- present in one of the 'Series' but not all of the others.
---
--- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched3' f@ 
--- than using @'zipWithStrategy3' f skipStrategy skipStrategy skipStrategy@.
-zipWithStrategy3 :: (Vector v a, Vector v b, Vector v c, Vector v d, Ord k) 
-                => (a -> b -> c -> d) -- ^ Function to combine values when present in all series
-                -> ZipStrategy k a d  -- ^ Strategy for when the key is in the left series but not in all the others
-                -> ZipStrategy k b d  -- ^ Strategy for when the key is in the center series but not in all the others
-                -> ZipStrategy k c d  -- ^ Strategy for when the key is in the right series but not in all the others
-                -> Series v k a
-                -> Series v k b 
-                -> Series v k c
-                -> Series v k d
-zipWithStrategy3 f whenLeft whenCenter whenRight left center right 
-    = let onlyLeftKeys  = index left    `Index.difference` (index center `Index.union` index right)
-          onlyCenterKeys = index center `Index.difference` (index left   `Index.union` index right)
-          onlyRightKeys = index right   `Index.difference` (index center `Index.union` index left)
-          -- Recall that `selectSubset` is a performance optimization
-          -- and is generally unsafe to use; however, in this case, we know
-          -- that `matchedKeys` are subsets of the index of all series
-          leftZip =  applyStrategy whenLeft  $ left     `selectSubset` onlyLeftKeys
-          centerZip = applyStrategy whenCenter $ center `selectSubset` onlyCenterKeys
-          rightZip = applyStrategy whenRight $ right    `selectSubset` onlyRightKeys
-          
-        in zipWithMatched3 f left center right <> leftZip <> centerZip <> rightZip
-    where
-        -- Application of the 'ZipStrategy' is done on a `Map` rather than
-        -- the 'Series' directly to keep the type contraints of `zipWithStrategy` to
-        -- a minimum. Recall that unboxed 'Series' cannot contain `Maybe a`.  
-        applyStrategy strat = G.toSeries 
-                            . Map.mapMaybeWithKey strat
-                            . G.fromSeries
-{-# INLINABLE zipWithStrategy3 #-}
-
-
--- | Zip two 'Series' with a combining function. The value for keys which are missing from
--- either 'Series' is replaced with the appropriate 'mempty' value.
---
--- >>> import Data.Monoid ( Sum(..) )
--- >>> let xs = Series.fromList [ ("2023-01-01", Sum (1::Int)), ("2023-01-02", Sum 2) ]
--- >>> let ys = Series.fromList [ ("2023-01-01", Sum (5::Int)), ("2023-01-03", Sum 7) ]
--- >>> zipWith (<>) xs ys
---        index |                  values
---        ----- |                  ------
--- "2023-01-01" | Just (Sum {getSum = 6})
--- "2023-01-02" |                 Nothing
--- "2023-01-03" |                 Nothing
--- >>> zipWithMonoid (<>) xs ys
---        index |           values
---        ----- |           ------
--- "2023-01-01" | Sum {getSum = 6}
--- "2023-01-02" | Sum {getSum = 2}
--- "2023-01-03" | Sum {getSum = 7}
-zipWithMonoid :: ( Monoid a, Monoid b
-                 , Vector v a, Vector v b, Vector v c
-                 , Ord k
-                 ) 
-              => (a -> b -> c)
-              -> Series v k a
-              -> Series v k b 
-              -> Series v k c
-zipWithMonoid f left right 
-    = let fullindex = index left `Index.union` index right
-          (MkSeries ix ls) = requireWith (const mempty) id left  fullindex
-          (MkSeries _ rs)  = requireWith (const mempty) id right fullindex          
-        in MkSeries ix $ Vector.zipWith f ls rs
-{-# INLINABLE zipWithMonoid #-}
-
-
--- | Elementwise sum of two 'Series'. Elements missing in one or the other 'Series' is considered 0. 
---
--- >>> let xs = Series.fromList [ ("2023-01-01", (1::Int)), ("2023-01-02", 2) ]
--- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]
--- >>> xs `esum` ys
---        index | values
---        ----- | ------
--- "2023-01-01" |      6
--- "2023-01-02" |      2
--- "2023-01-03" |      7
-esum :: (Ord k, Num a, Vector v a, Vector v (Sum a)) 
-     => Series v k a 
-     -> Series v k a
-     -> Series v k a
-esum ls rs = G.map getSum $ zipWithMonoid (<>) (G.map Sum ls) (G.map Sum rs)
-{-# INLINABLE esum #-}
-
-
--- | Elementwise product of two 'Series'. Elements missing in one or the other 'Series' is considered 1. 
---
--- >>> let xs = Series.fromList [ ("2023-01-01", (2::Int)), ("2023-01-02", 3) ]
--- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]
--- >>> xs `eproduct` ys
---        index | values
---        ----- | ------
--- "2023-01-01" |     10
--- "2023-01-02" |      3
--- "2023-01-03" |      7
-eproduct :: (Ord k, Num a, Vector v a, Vector v (Product a)) 
-         => Series v k a 
-         -> Series v k a
-         -> Series v k a
-eproduct ls rs = G.map getProduct $ zipWithMonoid (<>) (G.map Product ls) (G.map Product rs)
-{-# INLINABLE eproduct #-}
-
-
--- | \(O(n)\) Unzip a 'Series' of 2-tuples.
-unzip :: (Vector v a, Vector v b, Vector v (a, b)) 
-      => Series v k (a, b)
-      -> ( Series v k a
-         , Series v k b
-         )
-unzip (MkSeries ix vs) 
-    = let (left, right) = Vector.unzip vs
-       in (MkSeries ix left, MkSeries ix right)
-{-# INLINABLE unzip #-}
-
-
--- | \(O(n)\) Unzip a 'Series' of 3-tuples.
-unzip3 :: (Vector v a, Vector v b, Vector v c, Vector v (a, b, c)) 
-       => Series v k (a, b, c)
-       -> ( Series v k a
-          , Series v k b
-          , Series v k c
-          )
-unzip3 (MkSeries ix vs) 
-    = let (left, center, right) = Vector.unzip3 vs
-       in (MkSeries ix left, MkSeries ix center, MkSeries ix right)
-{-# INLINABLE unzip3 #-}
+module Data.Series.Generic.Zip (+    zipWith, zipWithMatched, zipWithKey,+    zipWith3, zipWithMatched3, zipWithKey3,+    replace, (|->), (<-|),+    +    -- * Generalized zipping with strategies+    zipWithStrategy,+    zipWithStrategy3,+    ZipStrategy,+    skipStrategy,+    mapStrategy,+    constStrategy,++    -- * Special case of zipping monoids+    zipWithMonoid,+    esum, eproduct,++    -- * Unzipping+    unzip, unzip3,+) where++import qualified Data.Map.Strict                as Map+import           Data.Monoid                    ( Sum(..), Product(..) )+import           Data.Series.Generic.Definition ( Series(MkSeries, index, values) )+import qualified Data.Series.Generic.Definition as G+import           Data.Series.Generic.View       ( selectSubset, requireWith )+import           Data.Vector.Generic            ( Vector )+import qualified Data.Vector.Generic            as Vector+import qualified Data.Series.Index              as Index+import qualified Data.Series.Index.Internal     as Index.Internal+import           Prelude                        hiding ( zipWith, zipWith3, unzip, unzip3 ) ++-- $setup+-- >>> import qualified Data.Series as Series++infix 6 |->, <-|++-- | Apply a function elementwise to two series, matching elements+-- based on their keys. For keys present only in the left or right series, +-- the value 'Nothing' is returned.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]+-- >>> zipWith (+) xs ys+--   index |  values+--   ----- |  ------+-- "alpha" | Just 10+--  "beta" | Just 12+-- "delta" | Nothing+-- "gamma" | Nothing+--+-- To only combine elements where keys are in both series, see 'zipWithMatched'+zipWith :: (Vector v a, Vector v b, Vector v c, Vector v (Maybe c), Ord k) +        => (a -> b -> c) -> Series v k a -> Series v k b -> Series v k (Maybe c)+zipWith f left right+    = let matched = zipWithMatched f left right+          matchedKeys   = index matched+          allKeys       = index left `Index.union` index right+          unmatchedKeys = allKeys `Index.difference` matchedKeys+          unmatched     = MkSeries unmatchedKeys (Vector.replicate (Index.size unmatchedKeys) Nothing)+       in G.map Just matched <> unmatched+{-# INLINABLE zipWith #-}+++-- | Apply a function elementwise to three series, matching elements+-- based on their keys. For keys present only in the left or right series, +-- the value 'Nothing' is returned.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int),  ("beta", 1),   ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11),  ("delta", 13) ]+-- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ]+-- >>> zipWith3 (\x y z -> x + y + z) xs ys zs+--     index |  values+--     ----- |  ------+--   "alpha" | Just 30+--    "beta" | Nothing+--   "delta" | Nothing+-- "epsilon" | Nothing+--   "gamma" | Nothing+--+-- To only combine elements where keys are in all series, see 'zipWithMatched3'+zipWith3 :: (Vector v a, Vector v b, Vector v c, Vector v d, Vector v (Maybe d), Ord k) +         => (a -> b -> c -> d) +         -> Series v k a +         -> Series v k b +         -> Series v k c +         -> Series v k (Maybe d)+zipWith3 f left center right+    = let matched       = zipWithMatched3 f left center right+          matchedKeys   = index matched+          allKeys       = index left `Index.union` index center `Index.union` index right+          unmatchedKeys = allKeys `Index.difference` matchedKeys+          unmatched     = MkSeries unmatchedKeys (Vector.replicate (Index.size unmatchedKeys) Nothing)+       in G.map Just matched <> unmatched+{-# INLINABLE zipWith3 #-}++++-- | Apply a function elementwise to two series, matching elements+-- based on their keys. Keys present only in the left or right series are dropped.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]+-- >>> zipWithMatched (+) xs ys+--   index | values+--   ----- | ------+-- "alpha" |     10+--  "beta" |     12+--+-- To combine elements where keys are in either series, see 'zipWith'. To combine+-- three series, see 'zipWithMatched3'.+zipWithMatched :: (Vector v a, Vector v b, Vector v c, Ord k) +               => (a -> b -> c) -> Series v k a -> Series v k b -> Series v k c+zipWithMatched f left right+    = let matchedKeys   = index left `Index.intersection` index right+          -- Recall that `selectSubset` is a performance optimization+          -- and is generally unsafe to use; however, in this case, we know+          -- that `matchedKeys` are subsets of the index of both series+          (MkSeries _ !xs) = left  `selectSubset` matchedKeys+          (MkSeries _ !ys) = right `selectSubset` matchedKeys+          -- The following construction relies on the fact that keys are always sorted+       in MkSeries matchedKeys $ Vector.zipWith f xs ys+{-# INLINABLE zipWithMatched #-}+++-- | Apply a function elementwise to three series, matching elements+-- based on their keys. Keys not present in all three series are dropped.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int),  ("beta", 1),   ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11),  ("delta", 13) ]+-- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ]+-- >>> zipWithMatched3 (\x y z -> x + y + z) xs ys zs+--   index | values+--   ----- | ------+-- "alpha" |     30+zipWithMatched3 :: (Vector v a, Vector v b, Vector v c, Vector v d, Ord k) +                => (a -> b -> c -> d) +                -> Series v k a +                -> Series v k b +                -> Series v k c+                -> Series v k d+zipWithMatched3 f left center right+    = let matchedKeys   = index left `Index.intersection` index center `Index.intersection` index right+          -- Recall that `selectSubset` is a performance optimization+          -- and is generally unsafe to use; however, in this case, we know+          -- that `matchedKeys` are subsets of the index of all series+          (MkSeries _ !xs) = left   `selectSubset` matchedKeys+          (MkSeries _ !ys) = center `selectSubset` matchedKeys+          (MkSeries _ !zs) = right  `selectSubset` matchedKeys+          -- The following construction relies on the fact that keys are always sorted+       in MkSeries matchedKeys $ Vector.zipWith3 f xs ys zs+{-# INLINABLE zipWithMatched3 #-}+++-- | Apply a function elementwise to two series, matching elements+-- based on their keys. Keys present only in the left or right series are dropped.+-- +-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]+-- >>> zipWithKey (\k x y -> length k + x + y) xs ys+--   index | values+--   ----- | ------+-- "alpha" |     15+--  "beta" |     16+--+-- To combine elements where keys are in either series, see 'zipWith'+zipWithKey :: (Vector v a, Vector v b, Vector v c, Vector v k, Ord k) +           => (k -> a -> b -> c) -> Series v k a -> Series v k b -> Series v k c+zipWithKey f left right+    = let matchedKeys   = index left `Index.intersection` index right+          -- Recall that `selectSubset` is a performance optimization+          -- and is generally unsafe to use; however, in this case, we know+          -- that `matchedKeys` are subsets of the index of both series+          (MkSeries _ xs) = left  `selectSubset` matchedKeys+          (MkSeries _ ys) = right `selectSubset` matchedKeys+          ks              = Index.toAscVector matchedKeys+          -- The following construction relies on the fact that keys are always sorted+       in  MkSeries matchedKeys $ Vector.zipWith3 f ks xs ys+{-# INLINABLE zipWithKey #-}+++-- | Apply a function elementwise to three series, matching elements+-- based on their keys. Keys not present in all series are dropped.+-- +-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]+-- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("beta", 7), ("delta", 5) ]+-- >>> zipWithKey3 (\k x y z -> length k + x + y + z) xs ys zs+--   index | values+--   ----- | ------+-- "alpha" |     35+--  "beta" |     23++zipWithKey3 :: (Vector v a, Vector v b, Vector v c, Vector v d, Vector v k, Ord k) +            => (k -> a -> b -> c -> d) +            -> Series v k a +            -> Series v k b +            -> Series v k c+            -> Series v k d+zipWithKey3 f left center right+    = let matchedKeys   = index left `Index.intersection` index right+          -- Recall that `selectSubset` is a performance optimization+          -- and is generally unsafe to use; however, in this case, we know+          -- that `matchedKeys` are subsets of the index of all series+          (MkSeries _ xs) = left   `selectSubset` matchedKeys+          (MkSeries _ ys) = center `selectSubset` matchedKeys+          (MkSeries _ zs) = right  `selectSubset` matchedKeys+          ks              = Index.toAscVector matchedKeys+          -- The following construction relies on the fact that keys are always sorted+       in  MkSeries matchedKeys $ Vector.zipWith4 f ks xs ys zs+{-# INLINABLE zipWithKey3 #-}+++-- | Replace values from the right series with values from the left series at matching keys.+-- Keys in the right series but not in the right series are unaffected.+replace :: (Vector v a, Vector v Int, Ord k) +        => Series v k a -> Series v k a -> Series v k a+{-# INLINABLE replace #-}+xs `replace` ys +    = let keysToReplace = index xs `Index.intersection` index ys+          iixs          = Index.toAscVector $ Index.Internal.mapMonotonic (\k -> Index.Internal.findIndex k (index ys)) keysToReplace+       in MkSeries (index ys) $ Vector.update_ (values ys) iixs (values (xs `selectSubset` keysToReplace))+++-- | Infix version of 'replace'+(|->) :: (Vector v a, Vector v Int, Ord k)+      => Series v k a -> Series v k a -> Series v k a+{-# INLINABLE (|->) #-}+(|->) = replace+++-- | Flipped version of '|->',+(<-|) :: (Vector v a, Vector v Int, Ord k) +      => Series v k a -> Series v k a -> Series v k a+{-# INLINABLE (<-|)  #-}+(<-|) = flip replace+++-- | A 'ZipStrategy' is a function which is used to decide what to do when a key is missing from one+-- of two 'Series' being zipped together with 'zipWithStrategy'.+--+-- If a 'ZipStrategy' returns 'Nothing', the key is dropped.+-- If a 'ZipStrategy' returns @'Just' v@ for key @k@, then the value @v@ is inserted at key @k@.+--+-- For example, the most basic 'ZipStrategy' is to skip over any key which is missing from the other series.+-- Such a strategy can be written as @skip key value = 'Nothing'@ (see 'skipStrategy').+type ZipStrategy k a b = (k -> a -> Maybe b)+++-- | This 'ZipStrategy' drops keys which are not present in both 'Series'.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]+-- >>> zipWithStrategy (+) skipStrategy skipStrategy xs ys+--   index | values+--   ----- | ------+-- "alpha" |     10+--  "beta" |     12+skipStrategy :: ZipStrategy k a b+skipStrategy _ _ = Nothing+{-# INLINABLE skipStrategy #-}+++-- | This 'ZipStrategy' sets the value at keys which are not present in both 'Series' +-- to the some mapping from the value present in one of the series. See the example below.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 5::Int), ("beta", 6), ("delta", 7) ]+-- >>> zipWithStrategy (+) (mapStrategy id) (mapStrategy (*10)) xs ys+--   index | values+--   ----- | ------+-- "alpha" |      5+--  "beta" |      7+-- "delta" |     70+-- "gamma" |      2+mapStrategy :: (a -> b) -> ZipStrategy k a b+mapStrategy f _ x = Just (f x)+{-# INLINABLE mapStrategy #-}+++-- | This 'ZipStrategy' sets a constant value at keys which are not present in both 'Series'.+--+-- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ]+-- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ]+-- >>> zipWith (+) xs ys+--   index |  values+--   ----- |  ------+-- "alpha" | Just 10+--  "beta" | Just 12+-- "delta" | Nothing+-- "gamma" | Nothing+-- >>> zipWithStrategy (+) (constStrategy (-100)) (constStrategy 200)  xs ys+--   index | values+--   ----- | ------+-- "alpha" |     10+--  "beta" |     12+-- "delta" |    200+-- "gamma" |   -100+constStrategy :: b -> ZipStrategy k a b+constStrategy v = mapStrategy (const v)+{-# INLINABLE constStrategy #-}+++-- | Zip two 'Series' with a combining function, applying a 'ZipStrategy' when one key is present in one of the 'Series' but not both.+--+-- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched' f@ +-- than using @'zipWithStrategy' f skipStrategy skipStrategy@.+zipWithStrategy :: (Vector v a, Vector v b, Vector v c, Ord k) +                => (a -> b -> c)     -- ^ Function to combine values when present in both series+                -> ZipStrategy k a c -- ^ Strategy for when the key is in the left series but not the right+                -> ZipStrategy k b c -- ^ Strategy for when the key is in the right series but not the left+                -> Series v k a+                -> Series v k b +                -> Series v k c+zipWithStrategy f whenLeft whenRight left right +    = let onlyLeftKeys  = index left  `Index.difference` index right+          onlyRightKeys = index right `Index.difference` index left+          -- Recall that `selectSubset` is a performance optimization+          -- and is generally unsafe to use; however, in this case, we know+          -- that `matchedKeys` are subsets of the index of both series+          leftZip =  applyStrategy whenLeft  $ left  `selectSubset` onlyLeftKeys+          rightZip = applyStrategy whenRight $ right `selectSubset` onlyRightKeys+          +        in zipWithMatched f left right <> leftZip <> rightZip+    where+        -- Application of the 'ZipStrategy' is done on a `Map` rather than+        -- the 'Series' directly to keep the type contraints of `zipWithStrategy` to+        -- a minimum. Recall that unboxed 'Series' cannot contain `Maybe a`.  +        applyStrategy strat = G.toSeries +                            . Map.mapMaybeWithKey strat+                            . G.fromSeries+{-# INLINABLE zipWithStrategy #-}+++-- | Zip three 'Series' with a combining function, applying a 'ZipStrategy' when one key is +-- present in one of the 'Series' but not all of the others.+--+-- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched3' f@ +-- than using @'zipWithStrategy3' f skipStrategy skipStrategy skipStrategy@.+zipWithStrategy3 :: (Vector v a, Vector v b, Vector v c, Vector v d, Ord k) +                => (a -> b -> c -> d) -- ^ Function to combine values when present in all series+                -> ZipStrategy k a d  -- ^ Strategy for when the key is in the left series but not in all the others+                -> ZipStrategy k b d  -- ^ Strategy for when the key is in the center series but not in all the others+                -> ZipStrategy k c d  -- ^ Strategy for when the key is in the right series but not in all the others+                -> Series v k a+                -> Series v k b +                -> Series v k c+                -> Series v k d+zipWithStrategy3 f whenLeft whenCenter whenRight left center right +    = let onlyLeftKeys  = index left    `Index.difference` (index center `Index.union` index right)+          onlyCenterKeys = index center `Index.difference` (index left   `Index.union` index right)+          onlyRightKeys = index right   `Index.difference` (index center `Index.union` index left)+          -- Recall that `selectSubset` is a performance optimization+          -- and is generally unsafe to use; however, in this case, we know+          -- that `matchedKeys` are subsets of the index of all series+          leftZip =  applyStrategy whenLeft  $ left     `selectSubset` onlyLeftKeys+          centerZip = applyStrategy whenCenter $ center `selectSubset` onlyCenterKeys+          rightZip = applyStrategy whenRight $ right    `selectSubset` onlyRightKeys+          +        in zipWithMatched3 f left center right <> leftZip <> centerZip <> rightZip+    where+        -- Application of the 'ZipStrategy' is done on a `Map` rather than+        -- the 'Series' directly to keep the type contraints of `zipWithStrategy` to+        -- a minimum. Recall that unboxed 'Series' cannot contain `Maybe a`.  +        applyStrategy strat = G.toSeries +                            . Map.mapMaybeWithKey strat+                            . G.fromSeries+{-# INLINABLE zipWithStrategy3 #-}+++-- | Zip two 'Series' with a combining function. The value for keys which are missing from+-- either 'Series' is replaced with the appropriate 'mempty' value.+--+-- >>> import Data.Monoid ( Sum(..) )+-- >>> let xs = Series.fromList [ ("2023-01-01", Sum (1::Int)), ("2023-01-02", Sum 2) ]+-- >>> let ys = Series.fromList [ ("2023-01-01", Sum (5::Int)), ("2023-01-03", Sum 7) ]+-- >>> zipWith (<>) xs ys+--        index |                  values+--        ----- |                  ------+-- "2023-01-01" | Just (Sum {getSum = 6})+-- "2023-01-02" |                 Nothing+-- "2023-01-03" |                 Nothing+-- >>> zipWithMonoid (<>) xs ys+--        index |           values+--        ----- |           ------+-- "2023-01-01" | Sum {getSum = 6}+-- "2023-01-02" | Sum {getSum = 2}+-- "2023-01-03" | Sum {getSum = 7}+zipWithMonoid :: ( Monoid a, Monoid b+                 , Vector v a, Vector v b, Vector v c+                 , Ord k+                 ) +              => (a -> b -> c)+              -> Series v k a+              -> Series v k b +              -> Series v k c+zipWithMonoid f left right +    = let fullindex = index left `Index.union` index right+          (MkSeries ix ls) = requireWith (const mempty) id left  fullindex+          (MkSeries _ rs)  = requireWith (const mempty) id right fullindex          +        in MkSeries ix $ Vector.zipWith f ls rs+{-# INLINABLE zipWithMonoid #-}+++-- | Elementwise sum of two 'Series'. Elements missing in one or the other 'Series' is considered 0. +--+-- >>> let xs = Series.fromList [ ("2023-01-01", (1::Int)), ("2023-01-02", 2) ]+-- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]+-- >>> xs `esum` ys+--        index | values+--        ----- | ------+-- "2023-01-01" |      6+-- "2023-01-02" |      2+-- "2023-01-03" |      7+esum :: (Ord k, Num a, Vector v a, Vector v (Sum a)) +     => Series v k a +     -> Series v k a+     -> Series v k a+esum ls rs = G.map getSum $ zipWithMonoid (<>) (G.map Sum ls) (G.map Sum rs)+{-# INLINABLE esum #-}+++-- | Elementwise product of two 'Series'. Elements missing in one or the other 'Series' is considered 1. +--+-- >>> let xs = Series.fromList [ ("2023-01-01", (2::Int)), ("2023-01-02", 3) ]+-- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]+-- >>> xs `eproduct` ys+--        index | values+--        ----- | ------+-- "2023-01-01" |     10+-- "2023-01-02" |      3+-- "2023-01-03" |      7+eproduct :: (Ord k, Num a, Vector v a, Vector v (Product a)) +         => Series v k a +         -> Series v k a+         -> Series v k a+eproduct ls rs = G.map getProduct $ zipWithMonoid (<>) (G.map Product ls) (G.map Product rs)+{-# INLINABLE eproduct #-}+++-- | \(O(n)\) Unzip a 'Series' of 2-tuples.+unzip :: (Vector v a, Vector v b, Vector v (a, b)) +      => Series v k (a, b)+      -> ( Series v k a+         , Series v k b+         )+unzip (MkSeries ix vs) +    = let (left, right) = Vector.unzip vs+       in (MkSeries ix left, MkSeries ix right)+{-# INLINABLE unzip #-}+++-- | \(O(n)\) Unzip a 'Series' of 3-tuples.+unzip3 :: (Vector v a, Vector v b, Vector v c, Vector v (a, b, c)) +       => Series v k (a, b, c)+       -> ( Series v k a+          , Series v k b+          , Series v k c+          )+unzip3 (MkSeries ix vs) +    = let (left, center, right) = Vector.unzip3 vs+       in (MkSeries ix left, MkSeries ix center, MkSeries ix right)+{-# INLINABLE unzip3 #-}
src/Data/Series/Index.hs view
@@ -1,108 +1,108 @@------------------------------------------------------------------------------
--- |
--- Module      :  $header
--- Copyright   :  (c) Laurent P. René de Cotret
--- License     :  MIT-style
--- Maintainer  :  Laurent P. René de Cotret
--- Portability :  portable
---
--- This module contains the definition of 'Index', a sequence of /unique/ and /sorted/
--- keys which can be used to efficient index a 'Data.Series.Series'.
---
--- = Construction
---
--- Constructing an 'Index' can be done from the usual list using `fromList`. Note that 
--- the 'Index' length could be smaller than the input list, due to the requirement that
--- an 'Index' be a sequence of unique keys.  A better way to construct an 'Index' is 
--- to use a 'Data.Set' (`fromSet`)
---
--- For quick INLINABLE definitions of an 'Index', you can also make use of the @OverloadedLists@ extension:
--- 
--- >>> :set -XOverloadedLists
--- >>> let (ix :: Index Int) = [1,2,3,4,5,5,5]
--- >>> ix
--- Index [1,2,3,4,5] 
---
--- Another useful function to construct an 'Index' is `range`. This allows to build an 'Index'
--- from a starting value up to an ending value, with a custom step function. For example,
--- here's an 'Index' with values from 1 to 10, in steps of 3:
---
--- >>> range (+3) (1 :: Int) 10
--- Index [1,4,7,10]
---
--- Note that `range` is a special case of the `unfoldr` function, which is also provided in this module.
---
--- = Set operations
--- 
--- Just like a 'Data.Set', 'Index' supports efficient `member`, `notMember`, `union`, `intersection`, and `difference` operations.
--- Like 'Data.Set', the `Semigroup` and `Monoid` instance of 'Index' are defined using the `union` operation:
---
--- >>> fromList ['a', 'b', 'c'] <> fromList ['b', 'c', 'd']
--- Index "abcd"
---
--- = Mapping
---
--- Because of the restriction that all keys be unique, an 'Index' is not a true `Functor`; you can't use
--- `fmap` to map elements of an index. Instead, you can use the general-purpose function 'map'. If you want
--- to map elements of an 'Index' with a monotonic function (i.e. a function which will not re-order elements and won't
--- create duplicate elements), you can use the 'Data.Series.mapMonotonic' function which operates faster.
---
--- = Indexing
---
--- One of the key operations for 'Data.Series.Series' is to find the integer index of an element in an 'Index'. For this purpose, you
--- can use `lookupIndex`:
---
--- >>> lookupIndex 'b' $ fromList ['a', 'b', 'c']
--- Just 1
--- >>> lookupIndex 'd' $ fromList ['a', 'b', 'c']
--- Nothing
-
-module Data.Series.Index (
-    Index,
-
-    -- * Creation and Conversion
-    singleton,
-    unfoldr,
-    range,
-    IsIndex(..),
-    fromSet,
-    fromList,
-    fromVector,
-    toSet,
-    toAscList,
-    toAscVector,
-
-    -- * Set-like operations
-    null,
-    member,
-    notMember,
-    union,
-    intersection,
-    difference,
-    symmetricDifference,
-    contains,
-    size,
-    take,
-    drop,
-
-    -- * Mapping and filtering
-    map,
-    indexed,
-    filter,
-    traverse,
-    
-    -- * Indexing
-    lookupIndex,
-
-    -- * Insertion and deletion
-    insert,
-    delete,
-) where
-
-import Data.Series.Index.Definition ( Index, IsIndex(..), singleton, unfoldr, range, fromSet, fromList, fromVector, toSet
-                                    , toAscList, toAscVector, null, member, notMember, union, intersection
-                                    , difference, symmetricDifference, contains, size, take, drop, map, indexed
-                                    , filter, traverse, lookupIndex, insert, delete 
-                                    )
-import Prelude hiding ( null, take, drop, map, filter, traverse )
-
+-----------------------------------------------------------------------------+-- |+-- Module      :  $header+-- Copyright   :  (c) Laurent P. René de Cotret+-- License     :  MIT-style+-- Maintainer  :  Laurent P. René de Cotret+-- Portability :  portable+--+-- This module contains the definition of 'Index', a sequence of /unique/ and /sorted/+-- keys which can be used to efficient index a 'Data.Series.Series'.+--+-- = Construction+--+-- Constructing an 'Index' can be done from the usual list using `fromList`. Note that +-- the 'Index' length could be smaller than the input list, due to the requirement that+-- an 'Index' be a sequence of unique keys.  A better way to construct an 'Index' is +-- to use a 'Data.Set' (`fromSet`)+--+-- For quick INLINABLE definitions of an 'Index', you can also make use of the @OverloadedLists@ extension:+-- +-- >>> :set -XOverloadedLists+-- >>> let (ix :: Index Int) = [1,2,3,4,5,5,5]+-- >>> ix+-- Index [1,2,3,4,5] +--+-- Another useful function to construct an 'Index' is `range`. This allows to build an 'Index'+-- from a starting value up to an ending value, with a custom step function. For example,+-- here's an 'Index' with values from 1 to 10, in steps of 3:+--+-- >>> range (+3) (1 :: Int) 10+-- Index [1,4,7,10]+--+-- Note that `range` is a special case of the `unfoldr` function, which is also provided in this module.+--+-- = Set operations+-- +-- Just like a 'Data.Set', 'Index' supports efficient `member`, `notMember`, `union`, `intersection`, and `difference` operations.+-- Like 'Data.Set', the `Semigroup` and `Monoid` instance of 'Index' are defined using the `union` operation:+--+-- >>> fromList ['a', 'b', 'c'] <> fromList ['b', 'c', 'd']+-- Index "abcd"+--+-- = Mapping+--+-- Because of the restriction that all keys be unique, an 'Index' is not a true `Functor`; you can't use+-- `fmap` to map elements of an index. Instead, you can use the general-purpose function 'map'. If you want+-- to map elements of an 'Index' with a monotonic function (i.e. a function which will not re-order elements and won't+-- create duplicate elements), you can use the 'Data.Series.mapMonotonic' function which operates faster.+--+-- = Indexing+--+-- One of the key operations for 'Data.Series.Series' is to find the integer index of an element in an 'Index'. For this purpose, you+-- can use `lookupIndex`:+--+-- >>> lookupIndex 'b' $ fromList ['a', 'b', 'c']+-- Just 1+-- >>> lookupIndex 'd' $ fromList ['a', 'b', 'c']+-- Nothing++module Data.Series.Index (+    Index,++    -- * Creation and Conversion+    singleton,+    unfoldr,+    range,+    IsIndex(..),+    fromSet,+    fromList,+    fromVector,+    toSet,+    toAscList,+    toAscVector,++    -- * Set-like operations+    null,+    member,+    notMember,+    union,+    intersection,+    difference,+    symmetricDifference,+    contains,+    size,+    take,+    drop,++    -- * Mapping and filtering+    map,+    indexed,+    filter,+    traverse,+    +    -- * Indexing+    lookupIndex,++    -- * Insertion and deletion+    insert,+    delete,+) where++import Data.Series.Index.Definition ( Index, IsIndex(..), singleton, unfoldr, range, fromSet, fromList, fromVector, toSet+                                    , toAscList, toAscVector, null, member, notMember, union, intersection+                                    , difference, symmetricDifference, contains, size, take, drop, map, indexed+                                    , filter, traverse, lookupIndex, insert, delete +                                    )+import Prelude hiding ( null, take, drop, map, filter, traverse )+
src/Data/Series/Index/Definition.hs view
@@ -1,517 +1,519 @@-{-# LANGUAGE TypeFamilies #-}
-{-# OPTIONS_GHC -Wno-redundant-constraints #-}
-
------------------------------------------------------------------------------
--- |
--- Module      :  $header
--- Copyright   :  (c) Laurent P. René de Cotret
--- License     :  MIT-style
--- Maintainer  :  Laurent P. René de Cotret
--- Portability :  portable
---
--- This module contains the definition of 'Index', a sequence of /unique/ and /sorted/
--- keys which can be used to efficient index a 'Series'.
-
-
-module Data.Series.Index.Definition (
-    Index(..),
-
-    -- * Creation and Conversion
-    singleton,
-    unfoldr,
-    range,
-    fromSet, toSet,
-    fromList, toAscList,
-    fromAscList, fromDistinctAscList,
-    fromVector, toAscVector,
-    fromAscVector, fromDistinctAscVector,
-    -- ** Ad-hoc conversion with other data structures
-    IsIndex(..),
-    
-    -- * Set-like operations
-    null,
-    member,
-    notMember,
-    union,
-    intersection,
-    difference,
-    symmetricDifference,
-    cartesianProduct,
-    contains,
-    size,
-    take,
-    drop,
-
-    -- * Mapping and filtering
-    map,
-    mapMonotonic,
-    indexed,
-    filter,
-    traverse,
-    
-    -- * Indexing
-    findIndex,
-    lookupIndex,
-    elemAt,
-
-    -- * Insertion and deletion
-    insert,
-    delete,
-) where
-
-import           Control.DeepSeq        ( NFData )
-import           Control.Monad          ( guard )
-import           Control.Monad.ST       ( runST )
-import           Data.Coerce            ( coerce )
-import qualified Data.Foldable          as Foldable
-import           Data.Functor           ( ($>) )
-import           Data.IntSet            ( IntSet )
-import qualified Data.IntSet            as IntSet
-import qualified Data.List              as List
-import           Data.Sequence          ( Seq )
-import qualified Data.Sequence          as Seq
-import           Data.Set               ( Set )
-import qualified Data.Set               as Set
-import qualified Data.Traversable       as Traversable
-import qualified Data.Vector            as Boxed
-import           Data.Vector.Algorithms.Intro ( sortUniq )
-import           Data.Vector.Generic    ( Vector )
-import qualified Data.Vector.Generic    as Vector
-import qualified Data.Vector.Generic.Mutable as M
-import qualified Data.Vector.Unboxed    as Unboxed
-import           GHC.Exts               ( IsList )
-import qualified GHC.Exts               as Exts
-import           GHC.Stack              ( HasCallStack )
-import           Prelude                as P hiding ( null, take, drop, map, filter, traverse, product )
-
--- $setup
--- >>> import Data.Series.Index
--- >>> import qualified Data.Vector as Vector
-
-
--- | Representation of the index of a series.
--- An index is a sequence of sorted elements. All elements are unique, much like a 'Set'.
---
--- You can construct an 'Index' from a set ('fromSet'), from a list ('fromList'), or from a vector ('fromVector'). You can 
--- also make use of the @OverloadedLists@ extension:
---
--- >>> :set -XOverloadedLists
--- >>> let (ix :: Index Int) = [1, 2, 3]
--- >>> ix
--- Index [1,2,3]
---
--- Since keys in an 'Index' are always sorted and unique, 'Index' is not a 'Functor'. To map a function
--- over an 'Index', use 'map'.
-newtype Index k = MkIndex (Set k)
-    deriving (Eq, Ord, Semigroup, Monoid, Foldable, NFData)
-
-
-instance Ord k => IsList (Index k) where
-    type Item (Index k) = k
-    fromList :: [k] -> Index k
-    fromList = fromList
-    toList :: Index k -> [Exts.Item (Index k)]
-    toList = toAscList
-
-
-instance Show k => Show (Index k) where
-    show :: Index k -> String
-    show (MkIndex s) = "Index " ++ show (Set.toList s)
-
-
--- | \(O(1)\)  Create a singleton 'Index'.
-singleton :: k -> Index k
-singleton = MkIndex . Set.singleton
-{-# INLINABLE singleton #-}
-
-
--- | \(O(n \log n)\) Create an 'Index' from a seed value. 
--- Note that the order in which elements are generated does not matter; elements are stored
--- in order. See the example below.
---
--- >>> unfoldr (\x -> if x < 1 then Nothing else Just (x, x-1)) (7 :: Int)
--- Index [1,2,3,4,5,6,7]
-unfoldr :: Ord a => (b -> Maybe (a, b)) -> b -> Index a
-unfoldr f = fromList . List.unfoldr f
-{-# INLINABLE unfoldr #-}
-
-
--- | \(O(n \log n)\) Create an 'Index' as a range of values. @range f start end@ will generate 
--- an 'Index' with values @[start, f start, f (f start), ... ]@ such that the largest element
--- less or equal to @end@ is included. See examples below.
---
--- >>> range (+3) (1 :: Int) 10
--- Index [1,4,7,10]
--- >>> range (+3) (1 :: Int) 11
--- Index [1,4,7,10]
-range :: Ord a 
-      => (a -> a) -- ^ Function to generate the next element in the index
-      -> a        -- ^ Starting value of the 'Index'
-      -> a        -- ^ Ending value of the 'Index', which may or may not be contained
-      -> Index a
-range next start end 
-    = unfoldr (\x -> guard (x <= end) $> (x, next x)) start
-{-# INLINABLE range #-}
-
-
--- | The 'IsIndex' typeclass allow for ad-hoc definition
--- of conversion functions, converting to / from 'Index'.
-class IsIndex t k where
-    -- | Construct an 'Index' from some container of keys. There is no
-    -- condition on the order of keys. Duplicate keys are silently dropped.
-    toIndex    :: t -> Index k
-
-    -- | Construct a container from keys of an 'Index'. 
-    -- The elements are returned in ascending order of keys.
-    fromIndex  :: Index k -> t
-
-
-instance IsIndex (Set k) k where
-    -- | \(O(1)\) Build an 'Index' from a 'Set'.
-    toIndex :: Set k -> Index k
-    toIndex = coerce
-    {-# INLINABLE toIndex #-}
-
-    -- | \(O(1)\) Build an 'Index' from a 'Set'.
-    fromIndex :: Index k -> Set k
-    fromIndex = coerce
-    {-# INLINABLE fromIndex #-}
-
-
-instance Ord k => IsIndex [k] k where
-    -- | \(O(n \log n)\) Build an 'Index' from a list.
-    toIndex :: [k] -> Index k
-    toIndex = fromList
-    {-# INLINABLE toIndex #-}
-
-    -- | \(O(n)\) Convert an 'Index' to a list.
-    fromIndex :: Index k -> [k]
-    fromIndex = toAscList
-    {-# INLINABLE fromIndex #-}
-
-
-instance Ord k => IsIndex (Seq k) k where
-    -- | \(O(n \log n)\) Build an 'Index' from a 'Seq'.
-    toIndex :: Seq k -> Index k
-    toIndex = fromList . Foldable.toList
-    {-# INLINABLE toIndex #-}
-
-    -- | \(O(n)\) Convert an 'Index' to a 'Seq'.
-    fromIndex :: Index k -> Seq k
-    fromIndex = Seq.fromList . toAscList
-    {-# INLINABLE fromIndex #-}
-
-
-instance IsIndex IntSet Int where
-    -- | \(O(n \min(n,W))\), where \W\ is the number of bits in an 'Int' on your platform (32 or 64).
-    toIndex :: IntSet -> Index Int
-    toIndex = fromDistinctAscList . IntSet.toList
-    {-# INLINABLE toIndex #-}
-    
-    -- | \(O(n)\) Convert an 'Index' to an 'IntSet.
-    fromIndex :: Index Int -> IntSet
-    fromIndex = IntSet.fromDistinctAscList . toAscList
-    {-# INLINABLE fromIndex #-}
-
-
-instance (Ord k) => IsIndex (Boxed.Vector k) k where
-    toIndex :: Boxed.Vector k -> Index k
-    toIndex = fromVector
-    {-# INLINABLE toIndex #-} 
-
-    fromIndex :: Index k -> Boxed.Vector k
-    fromIndex = toAscVector
-    {-# INLINABLE fromIndex #-}
-
-
-instance (Ord k, Unboxed.Unbox k) => IsIndex (Unboxed.Vector k) k where
-    toIndex :: Unboxed.Vector k -> Index k
-    toIndex = fromVector
-    {-# INLINABLE toIndex #-} 
-
-    fromIndex :: Index k -> Unboxed.Vector k
-    fromIndex ix = runST $ M.generate (size ix) (`elemAt` ix) >>= Vector.freeze
-    {-# INLINABLE fromIndex #-}
-
-
--- | \(O(1)\) Build an 'Index' from a 'Set'.
-fromSet :: Set k -> Index k
-fromSet = toIndex
-{-# INLINABLE fromSet #-}
-
-
--- | \(O(n \log n)\) Build an 'Index' from a list. Note that since an 'Index' is
--- composed of unique elements, the length of the index may not be
--- the same as the length of the input list:
---
--- >>> fromList ['c', 'a', 'b', 'b']
--- Index "abc"
---
--- If the list is already sorted, `fromAscList` is generally faster.
-fromList :: Ord k => [k] -> Index k
-fromList = fromSet . Set.fromList
-{-# INLINABLE fromList #-}
-
-
--- | \(O(n)\) Build an 'Index' from a list of elements in ascending order. The precondition
--- that elements already be sorted is not checked.
--- 
--- Note that since an 'Index' is composed of unique elements, the length of 
--- the index may not be the same as the length of the input list.
-fromAscList :: Eq k => [k] -> Index k
-fromAscList = toIndex . Set.fromAscList
-{-# INLINABLE fromAscList #-}
-
-
--- | \(O(n)\) Build an 'Index' from a list of distinct elements in ascending order. The precondition
--- that elements be unique and sorted is not checked.
-fromDistinctAscList :: [k] -> Index k
-fromDistinctAscList = MkIndex . Set.fromDistinctAscList
-{-# INLINABLE fromDistinctAscList #-}
-
-
--- | \(O(n \log n)\) Build an 'Index' from a 'Vector'. Note that since an 'Index' is
--- composed of unique elements, the length of the index may not be
--- the same as the length of the input vector:
---
--- >>> import Data.Vector as V
--- >>> fromVector $ V.fromList ['c', 'a', 'b', 'b']
--- Index "abc"
---
--- If the 'Vector' is already sorted, 'fromAscVector' is generally faster.
-fromVector :: (Vector v k, Ord k) => v k -> Index k
-fromVector vs = fromDistinctAscVector $ runST $ Vector.thaw vs >>= sortUniq >>= Vector.freeze
-{-# INLINABLE fromVector #-}
-
-
--- | \(O(n \log n)\) Build an 'Index' from a 'Vector' of elements in ascending order. The precondition
--- that elements already be sorted is not checked. 
---
--- Note that since an 'Index' is composed of unique elements, 
--- the length of the index may not be the same as the length of the input vector:
---
--- >>> import Data.Vector as V
--- >>> fromAscVector $ V.fromList ['a', 'b', 'b', 'c']
--- Index "abc"
-fromAscVector :: (Vector v k, Ord k) => v k -> Index k
-fromAscVector = fromAscList . Vector.toList
-{-# INLINABLE fromAscVector #-}
-
-
--- | \(O(n)\) Build an 'Index' from a 'Vector' of unique elements in ascending order. The precondition
--- that elements already be unique and sorted is not checked.
-fromDistinctAscVector :: Vector v k => v k -> Index k
-fromDistinctAscVector = fromDistinctAscList . Vector.toList
-{-# INLINABLE fromDistinctAscVector #-}
-
-
--- | \(O(1)\) Convert an 'Index' to a 'Set'.
-toSet :: Index k -> Set k
-toSet = fromIndex
-{-# INLINABLE toSet #-}
-
-
--- | \(O(n)\) Convert an 'Index' to a list. Elements will be produced in ascending order.
-toAscList :: Index k -> [k]
-toAscList (MkIndex s) = Set.toAscList s
-{-# INLINABLE toAscList #-}
-
-
--- | \(O(n)\) Convert an 'Index' to a list. Elements will be produced in ascending order.
-toAscVector :: Vector v k => Index k -> v k
-toAscVector ix = runST $ M.generate (size ix) (`elemAt` ix) >>= Vector.freeze
-{-# INLINABLE toAscVector #-}
-
-
--- | \(O(1)\) Returns 'True' for an empty 'Index', and @False@ otherwise.
-null :: Index k -> Bool
-null (MkIndex ix) = Set.null ix
-{-# INLINABLE null #-}
-
-
--- | \(O(n \log n)\) Check whether the element is in the index.
-member :: Ord k => k -> Index k -> Bool
-member k (MkIndex ix) = k `Set.member` ix
-{-# INLINABLE member #-}
-
-
--- | \(O(n \log n)\) Check whether the element is NOT in the index.
-notMember :: Ord k => k -> Index k -> Bool
-notMember k = not . member k
-{-# INLINABLE notMember #-}
-
-
--- | \(O\bigl(m \log\bigl(\frac{n+1}{m+1}\bigr)\bigr), \; m \leq n\) Union of two 'Index', containing
--- elements either in the left index, right right index, or both.
-union :: Ord k => Index k -> Index k -> Index k
-union = (<>)
-{-# INLINABLE union #-}
-
-
--- | \(O\bigl(m \log\bigl(\frac{n+1}{m+1}\bigr)\bigr), \; m \leq n\) Intersection of two 'Index', containing
--- elements which are in both the left index and the right index.
-intersection :: Ord k => Index k -> Index k -> Index k
-intersection (MkIndex ix) (MkIndex jx) = MkIndex $ ix `Set.intersection` jx
-{-# INLINABLE intersection #-}
-
-
--- | \(O\bigl(m \log\bigl(\frac{n+1}{m+1}\bigr)\bigr), \; m \leq n\) Returns the elements of the first index 
--- which are not found in the second index.
---
--- >>> difference (fromList ['a', 'b', 'c']) (fromList ['b', 'c', 'd'])
--- Index "a"
-difference :: Ord k => Index k -> Index k -> Index k
-difference (MkIndex ix) (MkIndex jx) = MkIndex $ Set.difference ix jx
-{-# INLINABLE difference #-}
-
-
--- | \(O(n+m)\). The symmetric difference of two 'Index'.
--- The first element of the tuple is an 'Index' containing all elements which
--- are only found in the left 'Index', while the second element of the tuple is an 'Index' containing
--- all elements which are only found in the right 'Index':
---
--- >>> left = fromList ['a', 'b', 'c']
--- >>> right = fromList ['c', 'd', 'e']
--- >>> left `symmetricDifference` right
--- (Index "ab",Index "de")
-symmetricDifference :: Ord k => Index k -> Index k -> (Index k, Index k)
-symmetricDifference left right = (left `difference` right, right `difference` left)
-{-# INLINABLE symmetricDifference #-}
-
-
--- | \(O(n m)\) Take the cartesian product of two 'Index':
---
--- >>> (range (+1) (1 :: Int) 2) `cartesianProduct` (range (+1) (3 :: Int) 4)
--- Index [(1,3),(1,4),(2,3),(2,4)]
-cartesianProduct :: Index k -> Index g -> Index (k, g)
-cartesianProduct (MkIndex xs) (MkIndex ys) 
-    = MkIndex $ Set.cartesianProduct xs ys
-{-# INLINABLE cartesianProduct #-}
-
-
--- | \(O\bigl(m \log\bigl(\frac{n+1}{m+1}\bigr)\bigr), \; m \leq n\).
--- @(ix1 \'contains\' ix2)@ indicates whether all keys in @ix2@ are also in @ix1@.
-contains :: Ord k => Index k -> Index k -> Bool
-contains (MkIndex ix1) (MkIndex ix2)= ix2 `Set.isSubsetOf` ix1
-{-# INLINABLE contains #-}
-
-
--- | \(O(1)\) Returns the number of keys in the index.
-size :: Index k -> Int
-size (MkIndex ix) = Set.size ix
-{-# INLINABLE size #-}
-
-
--- | \(O(\log n)\). Take @n@ elements from the index, in ascending order.
--- Taking more than the number of elements in the index is a no-op:
---
--- >>> take 10 $ fromList [1::Int,2,3]
--- Index [1,2,3]
-take :: Int -> Index k -> Index k
-take n (MkIndex ix) = MkIndex (Set.take n ix)
-{-# INLINABLE take #-}
-
-
--- | \(O(\log n)\). Drop @n@ elements from the index, in ascending order.
-drop :: Int -> Index k -> Index k
-drop n (MkIndex ix) = MkIndex (Set.drop n ix)
-{-# INLINABLE drop #-}
-
-
--- | \(O(n \log n)\) Map a function over keys in the index.
--- Note that since keys in an 'Index' are unique, the length of the resulting
--- index may not be the same as the input:
---
--- >>> map (\x -> if even x then 0::Int else 1) $ fromList [0::Int,1,2,3,4]
--- Index [0,1]
---
--- If the mapping is monotonic, see 'mapMonotonic', which has better performance
--- characteristics.
-map :: Ord g => (k -> g) -> Index k -> Index g
-map f (MkIndex ix) = MkIndex $ Set.map f ix
-{-# INLINABLE map #-}
-
-
--- | \(O(n)\) Map a monotonic function over keys in the index. /Monotonic/ means that if @a < b@, then @f a < f b@.
--- Using 'mapMonononic' can be much faster than using 'map' for a large 'Index'.
--- Note that the precondiction that the function be monotonic is not checked.
---
--- >>> mapMonotonic (+1) $ fromList [0::Int,1,2,3,4,5]
--- Index [1,2,3,4,5,6]
-mapMonotonic :: (k -> g) -> Index k -> Index g
-mapMonotonic f (MkIndex ix) = MkIndex $ Set.mapMonotonic f ix
-{-# INLINABLE mapMonotonic #-}
-
-
--- | \(O(n)\) Pair each key in the index with its position in the index, starting with 0:
---
--- @since 0.1.1.0
---
--- >>> indexed (fromList ['a', 'b', 'c', 'd'])
--- Index [(0,'a'),(1,'b'),(2,'c'),(3,'d')]
-indexed :: Index k -> Index (Int, k)
-indexed = fromDistinctAscList 
-        . zip [0..] 
-        . toAscList
-{-# INLINABLE indexed #-}
-
-
--- | \(O(n)\) Filter elements satisfying a predicate.
---
--- >>> filter even $ fromList [1::Int,2,3,4,5]
--- Index [2,4]
-filter :: (k -> Bool) -> Index k -> Index k
-filter p (MkIndex ix) = MkIndex $ Set.filter p ix
-{-# INLINABLE filter #-}
-
-
--- | \(O(\log n)\). Returns the integer /index/ of a key. This function raises an exception
--- if the key is not in the 'Index'; see 'lookupIndex' for a safe version.
---
--- >>> findIndex 'b' $ fromList ['a', 'b', 'c']
--- 1
-findIndex :: HasCallStack => Ord k => k -> Index k -> Int
-findIndex e (MkIndex ix) = Set.findIndex e ix 
-{-# INLINABLE findIndex #-}
-
-
--- | \(O(\log n)\). Returns the integer /index/ of a key, if the key is in the index.
---
--- >>> lookupIndex 'b' $ fromList ['a', 'b', 'c']
--- Just 1
--- >>> lookupIndex 'd' $ fromList ['a', 'b', 'c']
--- Nothing
-lookupIndex :: Ord k => k -> Index k -> Maybe Int
-lookupIndex e (MkIndex ix) = Set.lookupIndex e ix
-{-# INLINABLE lookupIndex #-}
-
-
--- | \(O(\log n)\) Returns the element at some integer index. This function raises
--- an exception if the integer index is out-of-bounds.
-elemAt :: HasCallStack => Int -> Index k -> k
-elemAt n (MkIndex ix) = Set.elemAt n ix
-{-# INLINABLE elemAt #-}
-
-
--- | \(O(\log n)\). Insert a key in an 'Index'. If the key is already 
--- present, the 'Index' will not change.
-insert :: Ord k => k -> Index k -> Index k
-insert k (MkIndex ix) = MkIndex $ k `Set.insert` ix
-{-# INLINABLE insert #-}
-
-
--- | \(O(\log n)\). Delete a key from an 'Index', if this key is present
--- in the index.
-delete :: Ord k => k -> Index k -> Index k
-delete k (MkIndex ix) = MkIndex $ k `Set.delete` ix
-{-# INLINABLE delete #-}
-
-
--- | \(O(n \log n)\). Map each element of an 'Index' to an applicative action, 
--- evaluate these actions from left to right, and collect the results.
---
--- Note that the data type 'Index' is not a member of 'Traversable'
--- because it is not a 'Functor'.
-traverse :: (Applicative f, Ord b) => (k -> f b) -> Index k -> f (Index b)
-traverse f = fmap fromList . Traversable.traverse f . toAscList
-{-# INLINABLE traverse #-}
+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  $header+-- Copyright   :  (c) Laurent P. René de Cotret+-- License     :  MIT-style+-- Maintainer  :  Laurent P. René de Cotret+-- Portability :  portable+--+-- This module contains the definition of 'Index', a sequence of /unique/ and /sorted/+-- keys which can be used to efficient index a 'Series'.+++module Data.Series.Index.Definition (+    Index(..),++    -- * Creation and Conversion+    singleton,+    unfoldr,+    range,+    fromSet, toSet,+    fromList, toAscList,+    fromAscList, fromDistinctAscList,+    fromVector, toAscVector,+    fromAscVector, fromDistinctAscVector,+    -- ** Ad-hoc conversion with other data structures+    IsIndex(..),+    +    -- * Set-like operations+    null,+    member,+    notMember,+    union,+    intersection,+    difference,+    symmetricDifference,+    cartesianProduct,+    contains,+    size,+    take,+    drop,++    -- * Mapping and filtering+    map,+    mapMonotonic,+    indexed,+    filter,+    traverse,+    +    -- * Indexing+    findIndex,+    lookupIndex,+    elemAt,++    -- * Insertion and deletion+    insert,+    delete,+) where++import           Control.DeepSeq        ( NFData )+import           Control.Monad          ( guard )+import           Control.Monad.ST       ( runST )+import           Data.Coerce            ( coerce )+import qualified Data.Foldable          as Foldable+import           Data.Functor           ( ($>) )+import           Data.IntSet            ( IntSet )+import qualified Data.IntSet            as IntSet+import qualified Data.List              as List+import           Data.Sequence          ( Seq )+import qualified Data.Sequence          as Seq+import           Data.Set               ( Set )+import qualified Data.Set               as Set+import qualified Data.Traversable       as Traversable+import qualified Data.Vector            as Boxed+import           Data.Vector.Algorithms.Intro ( sortUniq )+import           Data.Vector.Generic    ( Vector )+import qualified Data.Vector.Generic    as Vector+import qualified Data.Vector.Generic.Mutable as M+import qualified Data.Vector.Unboxed    as Unboxed+import           GHC.Exts               ( IsList )+import qualified GHC.Exts               as Exts+import           GHC.Stack              ( HasCallStack )+import           Prelude                as P hiding ( null, take, drop, map, filter, traverse, product )++-- $setup+-- >>> import Data.Series.Index+-- >>> import qualified Data.Vector as Vector+++-- | Representation of the index of a series.+-- An index is a sequence of sorted elements. All elements are unique, much like a 'Set'.+--+-- You can construct an 'Index' from a set ('fromSet'), from a list ('fromList'), or from a vector ('fromVector'). You can +-- also make use of the @OverloadedLists@ extension:+--+-- >>> :set -XOverloadedLists+-- >>> let (ix :: Index Int) = [1, 2, 3]+-- >>> ix+-- Index [1,2,3]+--+-- Since keys in an 'Index' are always sorted and unique, 'Index' is not a 'Functor'. To map a function+-- over an 'Index', use 'map'.+newtype Index k = MkIndex (Set k)+    deriving (Eq, Ord, Semigroup, Monoid, Foldable, NFData)+++instance Ord k => IsList (Index k) where+    type Item (Index k) = k+    fromList :: [k] -> Index k+    fromList = fromList+    toList :: Index k -> [Exts.Item (Index k)]+    toList = toAscList+++instance Show k => Show (Index k) where+    show :: Index k -> String+    show (MkIndex s) = "Index " ++ show (Set.toList s)+++-- | \(O(1)\)  Create a singleton 'Index'.+singleton :: k -> Index k+singleton = MkIndex . Set.singleton+{-# INLINABLE singleton #-}+++-- | \(O(n \log n)\) Create an 'Index' from a seed value. +-- Note that the order in which elements are generated does not matter; elements are stored+-- in order. See the example below.+--+-- >>> unfoldr (\x -> if x < 1 then Nothing else Just (x, x-1)) (7 :: Int)+-- Index [1,2,3,4,5,6,7]+unfoldr :: Ord a => (b -> Maybe (a, b)) -> b -> Index a+unfoldr f = fromList . List.unfoldr f+{-# INLINABLE unfoldr #-}+++-- | \(O(n \log n)\) Create an 'Index' as a range of values. @range f start end@ will generate +-- an 'Index' with values @[start, f start, f (f start), ... ]@ such that the largest element+-- less or equal to @end@ is included. See examples below.+--+-- >>> range (+3) (1 :: Int) 10+-- Index [1,4,7,10]+-- >>> range (+3) (1 :: Int) 11+-- Index [1,4,7,10]+range :: Ord a +      => (a -> a) -- ^ Function to generate the next element in the index+      -> a        -- ^ Starting value of the 'Index'+      -> a        -- ^ Ending value of the 'Index', which may or may not be contained+      -> Index a+range next start end +    = unfoldr (\x -> guard (x <= end) $> (x, next x)) start+{-# INLINABLE range #-}+++-- | The 'IsIndex' typeclass allow for ad-hoc definition+-- of conversion functions, converting to / from 'Index'.+class IsIndex t k where+    -- | Construct an 'Index' from some container of keys. There is no+    -- condition on the order of keys. Duplicate keys are silently dropped.+    toIndex    :: t -> Index k++    -- | Construct a container from keys of an 'Index'. +    -- The elements are returned in ascending order of keys.+    fromIndex  :: Index k -> t+++instance IsIndex (Set k) k where+    -- | \(O(1)\) Build an 'Index' from a 'Set'.+    toIndex :: Set k -> Index k+    toIndex = coerce+    {-# INLINABLE toIndex #-}++    -- | \(O(1)\) Build an 'Index' from a 'Set'.+    fromIndex :: Index k -> Set k+    fromIndex = coerce+    {-# INLINABLE fromIndex #-}+++instance Ord k => IsIndex [k] k where+    -- | \(O(n \log n)\) Build an 'Index' from a list.+    toIndex :: [k] -> Index k+    toIndex = fromList+    {-# INLINABLE toIndex #-}++    -- | \(O(n)\) Convert an 'Index' to a list.+    fromIndex :: Index k -> [k]+    fromIndex = toAscList+    {-# INLINABLE fromIndex #-}+++instance Ord k => IsIndex (Seq k) k where+    -- | \(O(n \log n)\) Build an 'Index' from a 'Seq'.+    toIndex :: Seq k -> Index k+    toIndex = fromList . Foldable.toList+    {-# INLINABLE toIndex #-}++    -- | \(O(n)\) Convert an 'Index' to a 'Seq'.+    fromIndex :: Index k -> Seq k+    fromIndex = Seq.fromList . toAscList+    {-# INLINABLE fromIndex #-}+++instance IsIndex IntSet Int where+    -- | \(O(n \min(n,W))\), where \W\ is the number of bits in an 'Int' on your platform (32 or 64).+    toIndex :: IntSet -> Index Int+    toIndex = fromDistinctAscList . IntSet.toList+    {-# INLINABLE toIndex #-}+    +    -- | \(O(n)\) Convert an 'Index' to an 'IntSet.+    fromIndex :: Index Int -> IntSet+    fromIndex = IntSet.fromDistinctAscList . toAscList+    {-# INLINABLE fromIndex #-}+++instance (Ord k) => IsIndex (Boxed.Vector k) k where+    toIndex :: Boxed.Vector k -> Index k+    toIndex = fromVector+    {-# INLINABLE toIndex #-} ++    fromIndex :: Index k -> Boxed.Vector k+    fromIndex = toAscVector+    {-# INLINABLE fromIndex #-}+++instance (Ord k, Unboxed.Unbox k) => IsIndex (Unboxed.Vector k) k where+    toIndex :: Unboxed.Vector k -> Index k+    toIndex = fromVector+    {-# INLINABLE toIndex #-} ++    fromIndex :: Index k -> Unboxed.Vector k+    fromIndex ix = runST $ M.generate (size ix) (`elemAt` ix) >>= Vector.freeze+    {-# INLINABLE fromIndex #-}+++-- | \(O(1)\) Build an 'Index' from a 'Set'.+fromSet :: Set k -> Index k+fromSet = toIndex+{-# INLINABLE fromSet #-}+++-- | \(O(n \log n)\) Build an 'Index' from a list. Note that since an 'Index' is+-- composed of unique elements, the length of the index may not be+-- the same as the length of the input list:+--+-- >>> fromList ['c', 'a', 'b', 'b']+-- Index "abc"+--+-- If the list is already sorted, `fromAscList` is generally faster.+fromList :: Ord k => [k] -> Index k+fromList = fromSet . Set.fromList+{-# INLINABLE fromList #-}+++-- | \(O(n)\) Build an 'Index' from a list of elements in ascending order. The precondition+-- that elements already be sorted is not checked.+-- +-- Note that since an 'Index' is composed of unique elements, the length of +-- the index may not be the same as the length of the input list.+fromAscList :: Eq k => [k] -> Index k+fromAscList = toIndex . Set.fromAscList+{-# INLINABLE fromAscList #-}+++-- | \(O(n)\) Build an 'Index' from a list of distinct elements in ascending order. The precondition+-- that elements be unique and sorted is not checked.+fromDistinctAscList :: [k] -> Index k+fromDistinctAscList = MkIndex . Set.fromDistinctAscList+{-# INLINABLE fromDistinctAscList #-}+++-- | \(O(n \log n)\) Build an 'Index' from a 'Vector'. Note that since an 'Index' is+-- composed of unique elements, the length of the index may not be+-- the same as the length of the input vector:+--+-- >>> import Data.Vector as V+-- >>> fromVector $ V.fromList ['c', 'a', 'b', 'b']+-- Index "abc"+--+-- If the 'Vector' is already sorted, 'fromAscVector' is generally faster.+fromVector :: (Vector v k, Ord k) => v k -> Index k+fromVector vs = fromDistinctAscVector $ runST $ Vector.thaw vs >>= sortUniq >>= Vector.freeze+{-# INLINABLE fromVector #-}+++-- | \(O(n \log n)\) Build an 'Index' from a 'Vector' of elements in ascending order. The precondition+-- that elements already be sorted is not checked. +--+-- Note that since an 'Index' is composed of unique elements, +-- the length of the index may not be the same as the length of the input vector:+--+-- >>> import Data.Vector as V+-- >>> fromAscVector $ V.fromList ['a', 'b', 'b', 'c']+-- Index "abc"+fromAscVector :: (Vector v k, Ord k) => v k -> Index k+fromAscVector = fromAscList . Vector.toList+{-# INLINABLE fromAscVector #-}+++-- | \(O(n)\) Build an 'Index' from a 'Vector' of unique elements in ascending order. The precondition+-- that elements already be unique and sorted is not checked.+fromDistinctAscVector :: Vector v k => v k -> Index k+fromDistinctAscVector = fromDistinctAscList . Vector.toList+{-# INLINABLE fromDistinctAscVector #-}+++-- | \(O(1)\) Convert an 'Index' to a 'Set'.+toSet :: Index k -> Set k+toSet = fromIndex+{-# INLINABLE toSet #-}+++-- | \(O(n)\) Convert an 'Index' to a list. Elements will be produced in ascending order.+toAscList :: Index k -> [k]+toAscList (MkIndex s) = Set.toAscList s+{-# INLINABLE toAscList #-}+++-- | \(O(n)\) Convert an 'Index' to a list. Elements will be produced in ascending order.+toAscVector :: Vector v k => Index k -> v k+toAscVector ix = runST $ M.generate (size ix) (`elemAt` ix) >>= Vector.freeze+{-# INLINABLE toAscVector #-}+++-- | \(O(1)\) Returns 'True' for an empty 'Index', and @False@ otherwise.+null :: Index k -> Bool+null (MkIndex ix) = Set.null ix+{-# INLINABLE null #-}+++-- | \(O(n \log n)\) Check whether the element is in the index.+member :: Ord k => k -> Index k -> Bool+member k (MkIndex ix) = k `Set.member` ix+{-# INLINABLE member #-}+++-- | \(O(n \log n)\) Check whether the element is NOT in the index.+notMember :: Ord k => k -> Index k -> Bool+notMember k = not . member k+{-# INLINABLE notMember #-}+++-- | \(O\bigl(m \log\bigl(\frac{n+1}{m+1}\bigr)\bigr), \; m \leq n\) Union of two 'Index', containing+-- elements either in the left index, right right index, or both.+union :: Ord k => Index k -> Index k -> Index k+union = (<>)+{-# INLINABLE union #-}+++-- | \(O\bigl(m \log\bigl(\frac{n+1}{m+1}\bigr)\bigr), \; m \leq n\) Intersection of two 'Index', containing+-- elements which are in both the left index and the right index.+intersection :: Ord k => Index k -> Index k -> Index k+intersection (MkIndex ix) (MkIndex jx) = MkIndex $ ix `Set.intersection` jx+{-# INLINABLE intersection #-}+++-- | \(O\bigl(m \log\bigl(\frac{n+1}{m+1}\bigr)\bigr), \; m \leq n\) Returns the elements of the first index +-- which are not found in the second index.+--+-- >>> difference (fromList ['a', 'b', 'c']) (fromList ['b', 'c', 'd'])+-- Index "a"+difference :: Ord k => Index k -> Index k -> Index k+difference (MkIndex ix) (MkIndex jx) = MkIndex $ Set.difference ix jx+{-# INLINABLE difference #-}+++-- | \(O(n+m)\). The symmetric difference of two 'Index'.+-- The first element of the tuple is an 'Index' containing all elements which+-- are only found in the left 'Index', while the second element of the tuple is an 'Index' containing+-- all elements which are only found in the right 'Index':+--+-- >>> left = fromList ['a', 'b', 'c']+-- >>> right = fromList ['c', 'd', 'e']+-- >>> left `symmetricDifference` right+-- (Index "ab",Index "de")+symmetricDifference :: Ord k => Index k -> Index k -> (Index k, Index k)+symmetricDifference left right = (left `difference` right, right `difference` left)+{-# INLINABLE symmetricDifference #-}+++-- | \(O(n m)\) Take the cartesian product of two 'Index':+--+-- >>> (range (+1) (1 :: Int) 2) `cartesianProduct` (range (+1) (3 :: Int) 4)+-- Index [(1,3),(1,4),(2,3),(2,4)]+cartesianProduct :: Index k -> Index g -> Index (k, g)+cartesianProduct (MkIndex xs) (MkIndex ys) +    = MkIndex $ Set.cartesianProduct xs ys+{-# INLINABLE cartesianProduct #-}+++-- | \(O\bigl(m \log\bigl(\frac{n+1}{m+1}\bigr)\bigr), \; m \leq n\).+-- @(ix1 \'contains\' ix2)@ indicates whether all keys in @ix2@ are also in @ix1@.+contains :: Ord k => Index k -> Index k -> Bool+contains (MkIndex ix1) (MkIndex ix2)= ix2 `Set.isSubsetOf` ix1+{-# INLINABLE contains #-}+++-- | \(O(1)\) Returns the number of keys in the index.+size :: Index k -> Int+size (MkIndex ix) = Set.size ix+{-# INLINABLE size #-}+++-- | \(O(\log n)\). Take @n@ elements from the index, in ascending order.+-- Taking more than the number of elements in the index is a no-op:+--+-- >>> take 10 $ fromList [1::Int,2,3]+-- Index [1,2,3]+take :: Int -> Index k -> Index k+take n (MkIndex ix) = MkIndex (Set.take n ix)+{-# INLINABLE take #-}+++-- | \(O(\log n)\). Drop @n@ elements from the index, in ascending order.+drop :: Int -> Index k -> Index k+drop n (MkIndex ix) = MkIndex (Set.drop n ix)+{-# INLINABLE drop #-}+++-- | \(O(n \log n)\) Map a function over keys in the index.+-- Note that since keys in an 'Index' are unique, the length of the resulting+-- index may not be the same as the input:+--+-- >>> map (\x -> if even x then 0::Int else 1) $ fromList [0::Int,1,2,3,4]+-- Index [0,1]+--+-- If the mapping is monotonic, see 'mapMonotonic', which has better performance+-- characteristics.+map :: Ord g => (k -> g) -> Index k -> Index g+map f (MkIndex ix) = MkIndex $ Set.map f ix+{-# INLINABLE map #-}+++-- | \(O(n)\) Map a monotonic function over keys in the index. /Monotonic/ means that if @a < b@, then @f a < f b@.+-- Using 'mapMonononic' can be much faster than using 'map' for a large 'Index'.+-- Note that the precondiction that the function be monotonic is not checked.+--+-- >>> mapMonotonic (+1) $ fromList [0::Int,1,2,3,4,5]+-- Index [1,2,3,4,5,6]+mapMonotonic :: (k -> g) -> Index k -> Index g+mapMonotonic f (MkIndex ix) = MkIndex $ Set.mapMonotonic f ix+{-# INLINABLE mapMonotonic #-}+++-- | \(O(n)\) Pair each key in the index with its position in the index, starting with 0:+--+-- @since 0.1.1.0+--+-- >>> indexed (fromList ['a', 'b', 'c', 'd'])+-- Index [(0,'a'),(1,'b'),(2,'c'),(3,'d')]+indexed :: Index k -> Index (Int, k)+indexed = fromDistinctAscList +        . zip [0..] +        . toAscList+{-# INLINABLE indexed #-}+++-- | \(O(n)\) Filter elements satisfying a predicate.+--+-- >>> filter even $ fromList [1::Int,2,3,4,5]+-- Index [2,4]+filter :: (k -> Bool) -> Index k -> Index k+filter p (MkIndex ix) = MkIndex $ Set.filter p ix+{-# INLINABLE filter #-}+++-- | \(O(\log n)\). Returns the integer /index/ of a key. This function raises an exception+-- if the key is not in the 'Index'; see 'lookupIndex' for a safe version.+--+-- >>> findIndex 'b' $ fromList ['a', 'b', 'c']+-- 1+findIndex :: HasCallStack => Ord k => k -> Index k -> Int+findIndex e (MkIndex ix) = Set.findIndex e ix +{-# INLINABLE findIndex #-}+++-- | \(O(\log n)\). Returns the integer /index/ of a key, if the key is in the index.+--+-- >>> lookupIndex 'b' $ fromList ['a', 'b', 'c']+-- Just 1+-- >>> lookupIndex 'd' $ fromList ['a', 'b', 'c']+-- Nothing+lookupIndex :: Ord k => k -> Index k -> Maybe Int+lookupIndex e (MkIndex ix) = Set.lookupIndex e ix+{-# INLINABLE lookupIndex #-}+++-- | \(O(\log n)\) Returns the element at some integer index. +-- +-- This function raises an exception if the integer index is out-of-bounds. +-- Consider using 'lookupIndex' instead.+elemAt :: HasCallStack => Int -> Index k -> k+elemAt n (MkIndex ix) = Set.elemAt n ix+{-# INLINABLE elemAt #-}+++-- | \(O(\log n)\). Insert a key in an 'Index'. If the key is already +-- present, the 'Index' will not change.+insert :: Ord k => k -> Index k -> Index k+insert k (MkIndex ix) = MkIndex $ k `Set.insert` ix+{-# INLINABLE insert #-}+++-- | \(O(\log n)\). Delete a key from an 'Index', if this key is present+-- in the index.+delete :: Ord k => k -> Index k -> Index k+delete k (MkIndex ix) = MkIndex $ k `Set.delete` ix+{-# INLINABLE delete #-}+++-- | \(O(n \log n)\). Map each element of an 'Index' to an applicative action, +-- evaluate these actions from left to right, and collect the results.+--+-- Note that the data type 'Index' is not a member of 'Traversable'+-- because it is not a 'Functor'.+traverse :: (Applicative f, Ord b) => (k -> f b) -> Index k -> f (Index b)+traverse f = fmap fromList . Traversable.traverse f . toAscList+{-# INLINABLE traverse #-}
src/Data/Series/Index/Internal.hs view
@@ -1,39 +1,39 @@-{-# LANGUAGE TypeFamilies #-}
-
------------------------------------------------------------------------------
--- |
--- Module      :  Data.Series.Generic.Internal
--- Copyright   :  (c) Laurent P. René de Cotret
--- License     :  MIT
--- Maintainer  :  laurent.decotret@outlook.com
--- Portability :  portable
---
--- = WARNING
---
--- This module is considered __internal__. It contains functions
--- which may be unsafe to use in general, for example requiring 
--- the data to be pre-sorted like 'fromDistinctAscList'.
---
--- The Package Versioning Policy still applies.
-
-module Data.Series.Index.Internal(
-    Index(..),
-
-    -- * Unsafe construction
-    fromAscList,
-    fromDistinctAscList,
-    fromAscVector,
-    fromDistinctAscVector,
-
-    -- * Functions with unchecked pre-conditions
-    mapMonotonic,
-
-    -- * Unsafe indexing
-    elemAt,
-    findIndex,
-
-) where
-
-import Data.Series.Index.Definition ( Index(..), fromAscList, fromDistinctAscList, fromAscVector
-                                    , fromDistinctAscVector, mapMonotonic, elemAt, findIndex
-                                    )
+{-# LANGUAGE TypeFamilies #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Series.Generic.Internal+-- Copyright   :  (c) Laurent P. René de Cotret+-- License     :  MIT+-- Maintainer  :  laurent.decotret@outlook.com+-- Portability :  portable+--+-- = WARNING+--+-- This module is considered __internal__. It contains functions+-- which may be unsafe to use in general, for example requiring +-- the data to be pre-sorted like 'fromDistinctAscList'.+--+-- The Package Versioning Policy still applies.++module Data.Series.Index.Internal(+    Index(..),++    -- * Unsafe construction+    fromAscList,+    fromDistinctAscList,+    fromAscVector,+    fromDistinctAscVector,++    -- * Functions with unchecked pre-conditions+    mapMonotonic,++    -- * Unsafe indexing+    elemAt,+    findIndex,++) where++import Data.Series.Index.Definition ( Index(..), fromAscList, fromDistinctAscList, fromAscVector+                                    , fromDistinctAscVector, mapMonotonic, elemAt, findIndex+                                    )
src/Data/Series/Tutorial.hs view
@@ -1,770 +1,770 @@-{-# OPTIONS_GHC -fno-warn-unused-imports #-}
-
-module Data.Series.Tutorial (
-    -- * Introduction
-    -- $introduction
-
-    -- * Construction
-    -- $construction
-
-    -- * Index
-    -- $index
-
-    -- * Selections
-    -- ** Single-key selection
-    -- $singlekey
-
-    -- ** Bulk selections
-    -- $multikey
-
-    -- * Filtering and mapping
-    -- $filteringandmapping
-
-    -- * Folding
-    -- $folding
-
-    -- * Grouping
-    -- $grouping
-
-    -- * Window aggregation
-    -- $windowing
-
-    -- * Combining 'Series' together
-    -- $zipping
-
-    -- * Conclusion
-    -- $conclusion and further reading
-    
-    -- * Advanced topics
-    -- ** Handling duplicate keys
-    -- $duplicates
-
-    -- ** Unboxed and generic series
-    -- $unboxed
-
-    -- ** Replacing values
-    -- $replacement
-
-    -- ** Comparison with other data structures
-    -- $comparison
-
-) where
-
-import           Control.Foldl   ( Fold )
-import           Data.Series     ( IsSeries(..), Series, Occurrence, at, iat, select, to, from, upto, require
-                                 , groupBy, aggregateWith, (<-|), (|->), Range, windowing
-                                 )
-import qualified Data.Series     as Series
-import qualified Data.Series.Generic
-import           Data.Series.Index ( Index )
-import qualified Data.Series.Index as Index
-import qualified Data.Series.Unboxed
-import           Data.Set        ( Set )
-import qualified Data.Set
-import           Data.Map.Strict ( Map )
-import qualified Data.Map.Strict
-import qualified Data.Map.Merge.Strict
-import           Numeric.Natural ( Natural)
-import qualified Data.List
-import qualified Data.Vector
-import qualified Data.Vector.Unboxed
-
-{- $introduction
-
-This is a short user guide on how to get started using @javelin@ and its various modules.
-
-The central data structure at the heart of this package is the 'Series'. A @'Series' k a@ 
-is a labeled array of type @v@ filled with values of type @a@, indexed by keys of type @k@.
-
-Like 'Data.Map.Strict.Map', 'Series' support efficient:
-
-* random access by key ( \(O(\log n)\) );
-* slice by key ( \(O(\log n)\) ).
-
-Like 'Data.Vector.Vector', 'Series' support efficient:
-
-* numerical operations.
-* random access by index ( \(O(1)\) );
-* slice by index ( \(O(1)\) );     
-
-To follow along this tutorial, the following imports are expected:
-
->>> import Data.Series as Series
--}
-
-{- $construction 
-
-The easiest way to create a 'Series' is to do it from a list using 'Data.Series.fromList':
-
->>> Series.fromList [ ('a', 1::Int), ('b', 2), ('c', 3), ('d', 4) ]
-index | values
------ | ------
-  'a' |      1
-  'b' |      2
-  'c' |      3
-  'd' |      4
-
-Note what happens when we have the same key (@\'a\'@) attached to multiple values:
-
->>> Series.fromList [ ('a', 1::Int), ('a', 0), ('b', 2), ('c', 3), ('d', 4) ]
-index | values
------ | ------
-  'a' |      0
-  'b' |      2
-  'c' |      3
-  'd' |      4
-
-'Series', like 'Map's, have unique keys; therefore, the output series may 
-not be the same length as the input series. See further below for an 
-explanation of how to handle duplicate keys. 
-
-Since 'Series' are like 'Map', it's easy to convert between the two:
-
->>> let mp = Data.Map.Strict.fromList [ ('a', 0::Int), ('a', 1), ('b', 2), ('c', 3), ('d', 4) ]
->>> mp
-fromList [('a',1),('b',2),('c',3),('d',4)]
->>> Series.fromStrictMap mp
-index | values
------ | ------
-  'a' |      1
-  'b' |      2
-  'c' |      3
-  'd' |      4
-
-Of course, 'Series.fromLazyMap' is also available. In fact, conversion to/from 'Series' is supported for
-many types; see the 'IsSeries' typeclass and its methods, 'toSeries' and 'fromSeries'.
-
--}
-
-{- $index
-
-'Series' have two components: values and an index.
-
-The index (of type @'Index' k@) is an ordered set of unique elements which allows to determine 
-where are each values in the series. Since all keys in an 'Index' are unique and sorted, it
-is fast to find the value associated to any random key.
-
-As we'll see soon, 'Index' is an important data structure which can be used to slice through a 'Series', 
-so let's get comfortable with them.
-
->>> import qualified Data.Series.Index as Index
-
-An 'Index' can be constructed from a list:
-
->>> Index.fromList [5::Int,5,4,3,2,1,5,5,5]
-Index [1,2,3,4,5]
-
-As you see above, repeated elements (in this case, @5@) won't be repeated in the 'Index'. Therefore, it often makes 
-more sense to construct an 'Index' using 'Index.fromSet' from a 'Set' from "Data.Set".
-
-One common way to construct an 'Index' is to programmatically __unfold__ a seed value using 
-'Index.unfoldr'. Below, we want to generate numbers from 7 down to 1:
-
->>> Index.unfoldr (\x -> if x < 1 then Nothing else Just (x, x-1)) (7 :: Int)
-Index [1,2,3,4,5,6,7]
-
-This task is so common that there is a convenience function to create ranges, 'Index.range'. 
-For example, if you want to create an 'Index' of values starting at 1 and ending at 10, in 
-steps of 3:
-
->>> Index.range (+3) (1 :: Int) 10
-Index [1,4,7,10]
-
-An 'Index' is very much like a 'Set', so you can 
-
-* check for membership using 'Index.member';
-* combine two 'Index' using 'Index.union', 'Index.intersection', and 'Index.difference';
-* find the integer index of a key using 'Index.lookupIndex';
-
-and more.
-
--}
-
-{- $singlekey 
-
-Single-element selections are performed using 'at', which selects a single element by key. 'at' is safe;
-if the key is missing, 'Nothing' is returned:
-
->>> let xs = Series.fromList [ ('a', 1::Int), ('b', 2), ('c', 3), ('d', 4) ]
->>> xs
-index | values
------ | ------
-  'a' |      1
-  'b' |      2
-  'c' |      3
-  'd' |      4
->>> xs `at` 'a'
-Just 1
->>> xs `at` 'z'
-Nothing
-
--}
-
-{- $multikey 
-
-Bulk selection, also known as *slicing*, is the method by which we extract a sub-series from a series.
-In the examples below, we'll assume that we have the series @aapl_close@ is available in-scope, which represents
-the closing price of Apple stock:
-
->>> :{
-let aapl_close = Series.fromList [ ("2010-01-04", 6.5522 :: Double)
-                                 , ("2010-01-05", 6.5636)
-                                 , ("2010-01-06", 6.4592)
-                                 , ("2010-01-07", 6.4472)
-                                 , ("2010-01-08", 6.4901)
-                                 -- No prices during the weekend
-                                 , ("2010-01-11", 6.5152)
-                                 , ("2010-01-12", 6.4047)
-                                 , ("2010-01-13", 6.3642)
-                                 , ("2010-01-14", 6.4328)
-                                 , ("2010-01-15", 6.4579)
-                                 ]
-    :}
-
-Bulk selection is done via the 'select' function. 'select' works with many types of inputs. 
-For example, we can query for a contiguous range of keys by using 'to':
-
->>> aapl_close `select` "2010-01-04" `to` "2010-01-08"
-       index | values
-       ----- | ------
-"2010-01-04" | 6.5522
-"2010-01-05" | 6.5636
-"2010-01-06" | 6.4592
-"2010-01-07" | 6.4472
-"2010-01-08" | 6.4901
-
-You can also request unbounded ranges. For example all dates up to @"2010-01-08"@ using 'upto':
-
->>> aapl_close `select` upto "2010-01-08"
-       index | values
-       ----- | ------
-"2010-01-04" | 6.5522
-"2010-01-05" | 6.5636
-"2010-01-06" | 6.4592
-"2010-01-07" | 6.4472
-"2010-01-08" | 6.4901
-
-There's also the other unbound range, 'from':
-
->>> aapl_close `select` from "2010-01-11"
-       index | values
-       ----- | ------
-"2010-01-11" | 6.5152
-"2010-01-12" | 6.4047
-"2010-01-13" | 6.3642
-"2010-01-14" | 6.4328
-"2010-01-15" | 6.4579
-
-Note that the bounds may contain less data than you think! For example, 
-let's look at a 5-day range:
-
->>> aapl_close `select` "2010-01-08" `to` "2010-01-12"
-       index | values
-       ----- | ------
-"2010-01-08" | 6.4901
-"2010-01-11" | 6.5152
-"2010-01-12" | 6.4047
-
-We've requested a range of 5 days (@"2010-01-08"@, @"2010-01-09"@, @"2010-01-10"@, @"2010-01-11"@, @"2010-01-12"@), 
-but there's no data in our series with the keys @"2010-01-09"@ and @"2010-01-10"@, because it was the week-end 
-(stock markets are usually closed on week-ends). 
-
-Sometimes you want to be more specific than a contiguous range of data; 'select' 
-also supports bulk *random* access like so:
-
->>> aapl_close `select` ["2010-01-08", "2010-01-10", "2010-01-12"]
-       index | values
-       ----- | ------
-"2010-01-08" | 6.4901
-"2010-01-12" | 6.4047
-
-Note above that we've requested data for the date @"2010-01-10"@, but it's missing. Therefore, 
-the data isn't returned. If you want to get a sub-series which has the exact index that 
-you've asked for, you can use 'require' in combination with an 'Index':
-
->>> import qualified Data.Series.Index as Index
->>> aapl_close `require` Index.fromList ["2010-01-08", "2010-01-10", "2010-01-12"]
-       index |      values
-       ----- |      ------
-"2010-01-08" | Just 6.4901
-"2010-01-10" |     Nothing
-"2010-01-12" | Just 6.4047
-
-Using 'require' or 'select' in conjunction with 'Index.range' is very powerful.
-
--}
-
-{- $filteringandmapping 
-
-'Series' support operations on both their index and their values. To illustrate 
-this, let's load some latitude and longitude data for some cities.
-
-We'll assume that the following types are in scope:
-
->>> import Data.Fixed (Centi)
->>> data Position = Pos { latitude :: Centi, longitude :: Centi } deriving (Show)
->>> :{
-    let cities = Series.fromList [ ("Paris"::String , Pos  48.86    2.35)
-                                 , ("New York City" , Pos  40.71   (-74.01))
-                                 , ("Taipei"        , Pos  25.04    121.56)
-                                 , ("Buenos Aires"  , Pos (-34.60) (-58.38)) 
-                                 ]
-    :}
-
-We can easily filter for data just like you would filter a list. 
-In this example, let's find cities in the western hemisphere (i.e. cities 
-which have negative longitudes), using 'Series.filter':
-
->>> Series.filter (\pos -> longitude pos < 0) cities
-          index |                                      values
-          ----- |                                      ------
- "Buenos Aires" | Pos {latitude = -34.60, longitude = -58.38}
-"New York City" |  Pos {latitude = 40.71, longitude = -74.01}
-
-We can transform the values of a 'Series' using 'Series.map'. In this example, 
-let's isolate the latitude of cities in the western hemisphere:
-
->>> let western_cities = Series.filter (\pos -> longitude pos < 0) cities
->>> Series.map latitude western_cities
-          index | values
-          ----- | ------
- "Buenos Aires" | -34.60
-"New York City" |  40.71
-
-Finally, we can summarize the 'Series' by reducing all its values. 
-Let's average the latitude of cities in the western hemisphere:
-
->>> import Data.Series ( mean )
->>> let latitudes = Series.map latitude western_cities
->>> Series.fold mean latitudes
-3.05
-
-The next section introduces 'Series.fold' more generally.
--}
-
-{- $folding
-
-Folding refers to the action of aggregating values in a 'Series' to a single value.
-Folding 'Series' is done through the 'Series.fold' function. Its type signature is:
-
->>> :t Series.fold
-Series.fold :: Fold a b -> Series k a -> b
-
-Here, @'Fold' a b@ represents a calculation which takes in values of type @a@, and will ultimately produce a
-final value of type b. Such calculations are provided by the @foldl@ package (see 'Control.Foldl'), although
-some of its functions are re-exported by "Data.Series" (and "Data.Series.Unboxed"), such as 'Data.Series.mean'.
-
-Let's look at an example. First, we'll need some data. We'll use end-of-day stock prices for Apple Inc:
-
->>> import Data.Fixed ( Centi )
->>> (aapl_closing :: Series String Double) <- (Series.fromList . read) <$> readFile "files/aapl.txt"
->>> aapl_closing 
-       index |  values
-       ----- |  ------
-"1980-12-12" |  0.1007
-"1980-12-15" | 9.54e-2
-"1980-12-16" | 8.84e-2
-         ... |     ...
-"2022-01-05" |  174.92
-"2022-01-06" |   172.0
-"2022-01-07" |  172.17
-
-Normally we would use an appropriate datetime type for the index of @aapl_closing@, 
-for example from the @time@ package, but we're keeping it simple for this tutorial. 
-
-Prices have changed a lot over the years, so we'll restrict ourselves to 2021:
-
->>> let aapl_closing_2021 = aapl_closing `select` "2021-01-01" `to` "2021-12-31"
->>> aapl_closing_2021
-       index |   values
-       ----- |   ------
-"2021-01-04" | 128.6174
-"2021-01-05" | 130.2076
-"2021-01-06" | 125.8246
-         ... |      ...
-"2021-12-29" |   179.38
-"2021-12-30" |    178.2
-"2021-12-31" |   177.57
-
-To calculate the average closing price over the year 2021, we use 'Data.Series.fold' in conjunction with
-'Data.Series.mean':
-
->>> Series.fold Series.mean aapl_closing_2021
-140.61256349206354
-
-One of the magic things about 'Fold' is that it's possible to combine them in such a way that you can 
-traverse a 'Series' only once, which is important for good performance. As an example, we'll calculate
-both the mean closing price AND the standard deviation of closing prices.
-
->>> let meanAndStdDev = (,) <$> Data.Series.mean <*> Data.Series.std
->>> Series.fold meanAndStdDev aapl_closing_2021
-(140.61256349206354,14.811663837435361)
-
-See 'Control.Foldl' from the @foldl@ package for more information on 'Fold'.
--}
-
-{- $grouping
-
-One important feature of 'Series' is the ability to efficiently group values 
-together based on their keys.
-
-Let's load some stock price data again for this part:
-
->>> import Data.Fixed ( Centi )
->>> (aapl_closing :: Series String Double) <- (Series.fromList . read) <$> readFile "files/aapl.txt"
->>> aapl_closing 
-       index |  values
-       ----- |  ------
-"1980-12-12" |  0.1007
-"1980-12-15" | 9.54e-2
-"1980-12-16" | 8.84e-2
-         ... |     ...
-"2022-01-05" |  174.92
-"2022-01-06" |   172.0
-"2022-01-07" |  172.17
-
-Grouping involves two steps:
-
-  (1) Grouping keys in some way using 'groupBy';
-  (2) Aggregating the values in each group using 'aggregateWith' or other variants.
-
-Let's find the highest closing price of each month. First, we need to define
-our grouping function:
-
->>> :{ 
-       -- | Extract the year and month from a date like XXXX-YY-ZZ. For example:
-       -- 
-       -- >>> month "2023-01-01"
-       -- "2023-01"
-       month :: String -> String
-       month = take 7
-    :}
-
-Then, we can group keys by month and take the 'maximum' of each group:
-
->>> aapl_closing `groupBy` month `aggregateWith` maximum
-    index | values
-    ----- | ------
-"1980-12" | 0.1261
-"1981-01" | 0.1208
-"1981-02" | 0.1007
-      ... |    ...
-"2021-11" |  165.3
-"2021-12" | 180.33
-"2022-01" | 182.01
-
-This means, for example, that the maximum closing price for Apple stock in the 
-month of November 2021 was $165.30 per share. This library also contains 
-numerical aggregation functions such as 'Data.Series.mean' and 'Data.Series.std'. Therefore, in order 
-to find the monthly average Apple closing price, rounded to the nearest cent:
-
->>> import Data.Series (mean)
->>> let (roundToCent :: Double -> Double) = \x -> fromIntegral ((round $ x * 100) :: Int) / 100
->>> aapl_closing `groupBy` month `aggregateWith` (roundToCent . Series.fold mean)
-    index | values
-    ----- | ------
-"1980-12" |   0.11
-"1981-01" |   0.11
-"1981-02" | 9.0e-2
-      ... |    ...
-"2021-11" | 154.21
-"2021-12" | 173.55
-"2022-01" | 176.16
-
--}
-
-{- $windowing
-
-Windowing aggregation refers to the practice of aggregating values in a window around every key.
-
-General-purpose windowing is done using the 'windowing' function. Let's look at its
-type signature:
-
->>> :t windowing
-windowing
-  :: Ord k =>
-     (k -> Range k) -> (Series k a -> b) -> Series k a -> Series k b
-
-Here, @`windowing` window aggfunc xs@ is a new series @'Series' k b@ where
-for every key @k@, the values in the range @window k@ are aggregated by @aggfunc@
-and placed in the resulting series at key @k@. Here's an example where
-for every key @k@, we add the values at @k@ and @k+1@:
-
->>> :{ 
-let (xs :: Series Int Int) 
-      = Series.fromList [ (1, 0)
-                        , (2, 1)
-                        , (3, 2)
-                        , (4, 3)
-                        , (5, 4)
-                        , (6, 5)
-                        ]
-in windowing (\k -> k `to` (k + 1)) sum xs
-:}
-index | values
------ | ------
-    1 |      1
-    2 |      3
-    3 |      5
-    4 |      7
-    5 |      9
-    6 |      5
-
-'windowing' can be used to compute so-called rolling aggregations. An example of
-this is to compute the rolling mean of the last 3 keys:
-
->>> import Data.Series ( mean )
->>> :{ 
-let rollingMean = windowing (\k -> (k-3) `to` k) (Series.fold mean)
-    (xs :: Series Int Double) 
-      = Series.fromList [ (1, 0)
-                        , (2, 1)
-                        , (3, 2)
-                        , (4, 3)
-                        , (5, 4)
-                        , (6, 5)
-                        ]
- in (rollingMean xs) :: Series Int Double
-:}
-index | values
------ | ------
-    1 |    0.0
-    2 |    0.5
-    3 |    1.0
-    4 |    1.5
-    5 |    2.5
-    6 |    3.5
-
--}
-
-{- $zipping 
-
-An important class of operations are combining two 'Series' together, also known as *zipping*. 
-For lists, Haskell has 'Data.List.zipWith'. 'Series' also have 'Series.zipWith' and variants:
-
-* 'Series.zipWith', which combines two series with some elementwise function;
-* 'Series.zipWithMatched', which combines two series with some elementwise function 
-  on keys which are in *both* maps;
-* 'Series.zipWithStrategy', which combines two series with some elementwise 
-  function and supports custom operations to deal with missing keys;
-
-To illustrate the differences between the various zipping functions, 
-consider the following two series. There's population:
-
->>> :set -XNumericUnderscores
->>> import Data.Fixed (Centi)
->>> :{ 
-    -- Most recent population estimate rounded to the nearest million
-    let population = Series.fromList [ ("Canada"::String, 40_000_000::Centi)
-                                     , ("Kenya"         , 56_000_000)
-                                     , ("Poland"        , 38_000_000)
-                                     , ("Singapore"     ,  6_000_000)
-                                     ]
-    :}
-
-and there's total land mass:
-
->>> :{ 
-    -- Land mass in square kilometer
-    let landmass = Series.fromList [ ("Brazil"::String, 8_520_000::Centi)
-                                   , ("Canada",         9_990_000)
-                                   , ("Kenya",            580_000)
-                                   , ("Poland",           313_000)
-                                   ] 
-    :}
-
-@'Series.zipWith' f left right@ combines the series @left@ and @right@ using the 
-function @f@ which admits two arguments, for all keys one-by-one. If a key 
-is missing from either @left@ or @right@, 'Series.zipWith' returns 'Nothing'. For example, 
-the population density per country would be:
-
->>> Series.zipWith (/) population landmass
-      index |      values
-      ----- |      ------
-   "Brazil" |     Nothing
-   "Canada" |   Just 4.00
-    "Kenya" |  Just 96.55
-   "Poland" | Just 121.40
-"Singapore" |     Nothing
-
-Since we don't have population estimates for Brazil and no land mass 
-information for Singapore, we can't calculate their population densities.
-
-Sometimes, we only care about the results of @'Series.zipWith' f@ where keys are 
-in both series. In this case, we can use 'Series.zipWithMatched':
-
->>> Series.zipWithMatched (/) population landmass
-   index | values
-   ----- | ------
-"Canada" |   4.00
- "Kenya" |  96.55
-"Poland" | 121.40
-
-Finally, in case we want full control over what to do when a key is missing, 
-we can use @Series.zipWithStrategy'. For example, consider the case where:
-
-* If population numbers are missing, I want to set the density to 0;
-* If land mass information is missing, I wait to skip calculating the density of this country. 
-
->>> import Data.Series (skipStrategy, constStrategy)
->>> let noPopulationStrategy = Series.constStrategy 0
->>> let noLandmassStrategy   = Series.skipStrategy
->>> Series.zipWithStrategy (/) noPopulationStrategy noLandmassStrategy population landmass
-      index | values
-      ----- | ------
-   "Canada" |   4.00
-    "Kenya" |  96.55
-   "Poland" | 121.40
-"Singapore" |   0.00
-
-As you can imagine, 'Series.zipWithStrategy' is the most general and gives the most control, but is less easy 
-to use than 'Series.zipWith' and 'Series.zipWithMatched'.
-
--}
-
-{- $conclusion
-
-This section concludes the introductory tutorial to the @javelin@ package and its "Data.Series" module.
-
-For a more in-depth look at this package, you can read the full documentation for each module:
-
-* "Data.Series"
-* "Data.Series.Index"
-* "Data.Series.Unboxed"
-* "Data.Series.Generic"
-
--}
-
-{- $duplicates
-
-If you must build a 'Series' with duplicate keys, you can use the 'Data.Series.fromListDuplicates' or 
-'Data.Series.fromVectorDuplicates' functions. 
-In the example below, the key @\'d\'@ is repeated three times:
-
->>> Series.fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]
-  index | values
-  ----- | ------
-('a',0) |      5
-('b',0) |      0
-('d',0) |      1
-('d',1) |     -4
-('d',2) |      7
-
-Note that the 'Series' produced by 'Data.Series.fromListDuplicates' still has unique keys, but each key is a 
-composite of a character and an occurrence. This is reflected in the type:
-
->>> :t Series.fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]
-Series.fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]
-  :: Series (Char, Occurrence) Int
-
-Here, 'Data.Series.Occurrence' is a non-negative number, and can be converted to 
-other integer-like numbers using 'fromIntegral'. In practice, you should aim to aggregate your 'Series' to remove duplicate keys, for example
-using 'Data.Series.groupBy' and grouping on the first element of the key ('fst'):
-
->>> let xs = Series.fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]
->>> xs `groupBy` fst `aggregateWith` sum
-index | values
------ | ------
-  'a' |      5
-  'b' |      0
-  'd' |      4
-
--}
-
-{- $unboxed 
-
-The 'Data.Series.Series' defined in "Data.Series" are based on 'Data.Vector.Vector' from "Data.Vector". 
-This implementation is nice because such 'Series' can hold _any_ Haskell type. However, because
-Haskell types can be arbitrarily complex, numerical operations on 'Series' may not be as fast
-as could be.
-
-For simpler types such as 'Double' and 'Int', a different kind of series can be used to
-speed up numerical calculations: 'Data.Series.Unboxed.Series' from the "Data.Series.Unboxed" module.
-Such 'Data.Series.Unboxed.Series' are much more limited: they can only contain datatypes which are
-instances of 'Data.Vector.Unboxed.Unbox'. 
-
-This then brings the question: how can you write software which supports both ordinary 'Data.Series.Series'
-__and__ unboxed 'Data.Series.Unboxed.Series'? The answer is to use functions from the "Data.Series.Generic".
-
-For example, we could implement the dot product of two series as:
-
->>> import qualified Data.Series.Generic as G
->>> import Data.Vector.Generic ( Vector )
->>> :{
-      dot :: (Ord k, Num a, Vector v a) => G.Series v k a -> G.Series v k a -> a
-      dot v1 v2 = G.sum $ G.zipWithMatched (*) v1 v2
-    :}
-
-You can convert between the two types of series using the 'Data.Series.Generic.convert' function.
-
--}
-
-{- $replacement 
-
-'Series.map' allows to map every value of a series. How about replacing *some* 
-values in a series? The function 'Data.Series.replace' (and its infix variant, '|->') replaces values in the right operand 
-which have an analogue in the left operand:
-
->>> import Data.Series ( (|->) )
->>> let nan = (0/0) :: Double
->>> let right = Series.fromList [('a', 1), ('b', nan), ('c', 3), ('d', nan)]
->>> right
-index | values
------ | ------
-  'a' |    1.0
-  'b' |    NaN
-  'c' |    3.0
-  'd' |    NaN
->>> let left = Series.fromList [('b', 0::Double), ('d', 0), ('e', 0)]
->>> left
-index | values
------ | ------
-  'b' |    0.0
-  'd' |    0.0
-  'e' |    0.0
->>> left |-> right
-index | values
------ | ------
-  'a' |    1.0
-  'b' |    0.0
-  'c' |    3.0
-  'd' |    0.0
-
-In the example above, the key @\'e\'@ is ignored since it was not in the @right@ 
-series to begin with.
-
-The flipped version, '<-|', is also available.
-
--}
-
-{- $comparison 
-
-Below is a table showing which operations on "Data.Series" have analogues for 
-other data structures.
-
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Action                          | "Data.Series"                  | "Data.Map.Strict"               | "Data.List"       | "Data.Vector"        |
-+=================================+================================+=================================+===================+======================+
-| Mapping values                  | 'Data.Series.map'              | 'Data.Map.Strict.map'           | 'map'             | 'Data.Vector.map'    |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Mapping index                   | 'Data.Series.mapIndex'         | 'Data.Map.Strict.mapKeys'       |                   |                      |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Mapping values with key         | 'Data.Series.mapWithKey'       | 'Data.Map.Strict.mapWithKey'    |                   |                      |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Filtering values                | 'Data.Series.filter'           | 'Data.Map.Strict.filter'        | 'filter'          | 'Data.Vector.filter' |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Filtering index                 | 'Data.Series.select',          | 'Data.Map.Strict.filterWithKey' |                   |                      |
-|                                 | 'Data.Series.filterWithKey'    |                                 |                   |                      |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Indexing by key                 | 'Data.Series.at'               | 'Data.Map.Strict.lookup'        |                   |                      |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Indexing by position            | 'Data.Series.iat'              |                                 | 'Data.List.!'     | 'Data.Vector.!'      |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Combine two structures key-wise | 'Data.Series.zipWith'          | 'Data.Map.Merge.Strict.merge'   |                   |                      |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Union                           | 'Data.Series.<>'               | 'Data.Map.Strict.union'         | 'Data.List.union' |                      |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-| Group keys                      | 'Data.Series.groupBy'          |                                 |                   |                      |
-+---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+
-
--}
+{-# OPTIONS_GHC -fno-warn-unused-imports #-}++module Data.Series.Tutorial (+    -- * Introduction+    -- $introduction++    -- * Construction+    -- $construction++    -- * Index+    -- $index++    -- * Selections+    -- ** Single-key selection+    -- $singlekey++    -- ** Bulk selections+    -- $multikey++    -- * Filtering and mapping+    -- $filteringandmapping++    -- * Folding+    -- $folding++    -- * Grouping+    -- $grouping++    -- * Window aggregation+    -- $windowing++    -- * Combining 'Series' together+    -- $zipping++    -- * Conclusion+    -- $conclusion and further reading+    +    -- * Advanced topics+    -- ** Handling duplicate keys+    -- $duplicates++    -- ** Unboxed and generic series+    -- $unboxed++    -- ** Replacing values+    -- $replacement++    -- ** Comparison with other data structures+    -- $comparison++) where++import           Control.Foldl   ( Fold )+import           Data.Series     ( IsSeries(..), Series, Occurrence, at, iat, select, to, from, upto, require+                                 , groupBy, aggregateWith, (<-|), (|->), Range, windowing+                                 )+import qualified Data.Series     as Series+import qualified Data.Series.Generic+import           Data.Series.Index ( Index )+import qualified Data.Series.Index as Index+import qualified Data.Series.Unboxed+import           Data.Set        ( Set )+import qualified Data.Set+import           Data.Map.Strict ( Map )+import qualified Data.Map.Strict+import qualified Data.Map.Merge.Strict+import           Numeric.Natural ( Natural)+import qualified Data.List+import qualified Data.Vector+import qualified Data.Vector.Unboxed++{- $introduction++This is a short user guide on how to get started using @javelin@ and its various modules.++The central data structure at the heart of this package is the 'Series'. A @'Series' k a@ +is a labeled array of type @v@ filled with values of type @a@, indexed by keys of type @k@.++Like 'Data.Map.Strict.Map', 'Series' support efficient:++* random access by key ( \(O(\log n)\) );+* slice by key ( \(O(\log n)\) ).++Like 'Data.Vector.Vector', 'Series' support efficient:++* numerical operations.+* random access by index ( \(O(1)\) );+* slice by index ( \(O(1)\) );     ++To follow along this tutorial, the following imports are expected:++>>> import Data.Series as Series+-}++{- $construction ++The easiest way to create a 'Series' is to do it from a list using 'Data.Series.fromList':++>>> Series.fromList [ ('a', 1::Int), ('b', 2), ('c', 3), ('d', 4) ]+index | values+----- | ------+  'a' |      1+  'b' |      2+  'c' |      3+  'd' |      4++Note what happens when we have the same key (@\'a\'@) attached to multiple values:++>>> Series.fromList [ ('a', 1::Int), ('a', 0), ('b', 2), ('c', 3), ('d', 4) ]+index | values+----- | ------+  'a' |      0+  'b' |      2+  'c' |      3+  'd' |      4++'Series', like 'Map's, have unique keys; therefore, the output series may +not be the same length as the input series. See further below for an +explanation of how to handle duplicate keys. ++Since 'Series' are like 'Map', it's easy to convert between the two:++>>> let mp = Data.Map.Strict.fromList [ ('a', 0::Int), ('a', 1), ('b', 2), ('c', 3), ('d', 4) ]+>>> mp+fromList [('a',1),('b',2),('c',3),('d',4)]+>>> Series.fromStrictMap mp+index | values+----- | ------+  'a' |      1+  'b' |      2+  'c' |      3+  'd' |      4++Of course, 'Series.fromLazyMap' is also available. In fact, conversion to/from 'Series' is supported for+many types; see the 'IsSeries' typeclass and its methods, 'toSeries' and 'fromSeries'.++-}++{- $index++'Series' have two components: values and an index.++The index (of type @'Index' k@) is an ordered set of unique elements which allows to determine +where are each values in the series. Since all keys in an 'Index' are unique and sorted, it+is fast to find the value associated to any random key.++As we'll see soon, 'Index' is an important data structure which can be used to slice through a 'Series', +so let's get comfortable with them.++>>> import qualified Data.Series.Index as Index++An 'Index' can be constructed from a list:++>>> Index.fromList [5::Int,5,4,3,2,1,5,5,5]+Index [1,2,3,4,5]++As you see above, repeated elements (in this case, @5@) won't be repeated in the 'Index'. Therefore, it often makes +more sense to construct an 'Index' using 'Index.fromSet' from a 'Set' from "Data.Set".++One common way to construct an 'Index' is to programmatically __unfold__ a seed value using +'Index.unfoldr'. Below, we want to generate numbers from 7 down to 1:++>>> Index.unfoldr (\x -> if x < 1 then Nothing else Just (x, x-1)) (7 :: Int)+Index [1,2,3,4,5,6,7]++This task is so common that there is a convenience function to create ranges, 'Index.range'. +For example, if you want to create an 'Index' of values starting at 1 and ending at 10, in +steps of 3:++>>> Index.range (+3) (1 :: Int) 10+Index [1,4,7,10]++An 'Index' is very much like a 'Set', so you can ++* check for membership using 'Index.member';+* combine two 'Index' using 'Index.union', 'Index.intersection', and 'Index.difference';+* find the integer index of a key using 'Index.lookupIndex';++and more.++-}++{- $singlekey ++Single-element selections are performed using 'at', which selects a single element by key. 'at' is safe;+if the key is missing, 'Nothing' is returned:++>>> let xs = Series.fromList [ ('a', 1::Int), ('b', 2), ('c', 3), ('d', 4) ]+>>> xs+index | values+----- | ------+  'a' |      1+  'b' |      2+  'c' |      3+  'd' |      4+>>> xs `at` 'a'+Just 1+>>> xs `at` 'z'+Nothing++-}++{- $multikey ++Bulk selection, also known as *slicing*, is the method by which we extract a sub-series from a series.+In the examples below, we'll assume that we have the series @aapl_close@ is available in-scope, which represents+the closing price of Apple stock:++>>> :{+let aapl_close = Series.fromList [ ("2010-01-04", 6.5522 :: Double)+                                 , ("2010-01-05", 6.5636)+                                 , ("2010-01-06", 6.4592)+                                 , ("2010-01-07", 6.4472)+                                 , ("2010-01-08", 6.4901)+                                 -- No prices during the weekend+                                 , ("2010-01-11", 6.5152)+                                 , ("2010-01-12", 6.4047)+                                 , ("2010-01-13", 6.3642)+                                 , ("2010-01-14", 6.4328)+                                 , ("2010-01-15", 6.4579)+                                 ]+    :}++Bulk selection is done via the 'select' function. 'select' works with many types of inputs. +For example, we can query for a contiguous range of keys by using 'to':++>>> aapl_close `select` "2010-01-04" `to` "2010-01-08"+       index | values+       ----- | ------+"2010-01-04" | 6.5522+"2010-01-05" | 6.5636+"2010-01-06" | 6.4592+"2010-01-07" | 6.4472+"2010-01-08" | 6.4901++You can also request unbounded ranges. For example all dates up to @"2010-01-08"@ using 'upto':++>>> aapl_close `select` upto "2010-01-08"+       index | values+       ----- | ------+"2010-01-04" | 6.5522+"2010-01-05" | 6.5636+"2010-01-06" | 6.4592+"2010-01-07" | 6.4472+"2010-01-08" | 6.4901++There's also the other unbound range, 'from':++>>> aapl_close `select` from "2010-01-11"+       index | values+       ----- | ------+"2010-01-11" | 6.5152+"2010-01-12" | 6.4047+"2010-01-13" | 6.3642+"2010-01-14" | 6.4328+"2010-01-15" | 6.4579++Note that the bounds may contain less data than you think! For example, +let's look at a 5-day range:++>>> aapl_close `select` "2010-01-08" `to` "2010-01-12"+       index | values+       ----- | ------+"2010-01-08" | 6.4901+"2010-01-11" | 6.5152+"2010-01-12" | 6.4047++We've requested a range of 5 days (@"2010-01-08"@, @"2010-01-09"@, @"2010-01-10"@, @"2010-01-11"@, @"2010-01-12"@), +but there's no data in our series with the keys @"2010-01-09"@ and @"2010-01-10"@, because it was the week-end +(stock markets are usually closed on week-ends). ++Sometimes you want to be more specific than a contiguous range of data; 'select' +also supports bulk *random* access like so:++>>> aapl_close `select` ["2010-01-08", "2010-01-10", "2010-01-12"]+       index | values+       ----- | ------+"2010-01-08" | 6.4901+"2010-01-12" | 6.4047++Note above that we've requested data for the date @"2010-01-10"@, but it's missing. Therefore, +the data isn't returned. If you want to get a sub-series which has the exact index that +you've asked for, you can use 'require' in combination with an 'Index':++>>> import qualified Data.Series.Index as Index+>>> aapl_close `require` Index.fromList ["2010-01-08", "2010-01-10", "2010-01-12"]+       index |      values+       ----- |      ------+"2010-01-08" | Just 6.4901+"2010-01-10" |     Nothing+"2010-01-12" | Just 6.4047++Using 'require' or 'select' in conjunction with 'Index.range' is very powerful.++-}++{- $filteringandmapping ++'Series' support operations on both their index and their values. To illustrate +this, let's load some latitude and longitude data for some cities.++We'll assume that the following types are in scope:++>>> import Data.Fixed (Centi)+>>> data Position = Pos { latitude :: Centi, longitude :: Centi } deriving (Show)+>>> :{+    let cities = Series.fromList [ ("Paris"::String , Pos  48.86    2.35)+                                 , ("New York City" , Pos  40.71   (-74.01))+                                 , ("Taipei"        , Pos  25.04    121.56)+                                 , ("Buenos Aires"  , Pos (-34.60) (-58.38)) +                                 ]+    :}++We can easily filter for data just like you would filter a list. +In this example, let's find cities in the western hemisphere (i.e. cities +which have negative longitudes), using 'Series.filter':++>>> Series.filter (\pos -> longitude pos < 0) cities+          index |                                      values+          ----- |                                      ------+ "Buenos Aires" | Pos {latitude = -34.60, longitude = -58.38}+"New York City" |  Pos {latitude = 40.71, longitude = -74.01}++We can transform the values of a 'Series' using 'Series.map'. In this example, +let's isolate the latitude of cities in the western hemisphere:++>>> let western_cities = Series.filter (\pos -> longitude pos < 0) cities+>>> Series.map latitude western_cities+          index | values+          ----- | ------+ "Buenos Aires" | -34.60+"New York City" |  40.71++Finally, we can summarize the 'Series' by reducing all its values. +Let's average the latitude of cities in the western hemisphere:++>>> import Data.Series ( mean )+>>> let latitudes = Series.map latitude western_cities+>>> Series.fold mean latitudes+3.05++The next section introduces 'Series.fold' more generally.+-}++{- $folding++Folding refers to the action of aggregating values in a 'Series' to a single value.+Folding 'Series' is done through the 'Series.fold' function. Its type signature is:++>>> :t Series.fold+Series.fold :: Fold a b -> Series k a -> b++Here, @'Fold' a b@ represents a calculation which takes in values of type @a@, and will ultimately produce a+final value of type b. Such calculations are provided by the @foldl@ package (see 'Control.Foldl'), although+some of its functions are re-exported by "Data.Series" (and "Data.Series.Unboxed"), such as 'Data.Series.mean'.++Let's look at an example. First, we'll need some data. We'll use end-of-day stock prices for Apple Inc:++>>> import Data.Fixed ( Centi )+>>> (aapl_closing :: Series String Double) <- (Series.fromList . read) <$> readFile "files/aapl.txt"+>>> aapl_closing +       index |  values+       ----- |  ------+"1980-12-12" |  0.1007+"1980-12-15" | 9.54e-2+"1980-12-16" | 8.84e-2+         ... |     ...+"2022-01-05" |  174.92+"2022-01-06" |   172.0+"2022-01-07" |  172.17++Normally we would use an appropriate datetime type for the index of @aapl_closing@, +for example from the @time@ package, but we're keeping it simple for this tutorial. ++Prices have changed a lot over the years, so we'll restrict ourselves to 2021:++>>> let aapl_closing_2021 = aapl_closing `select` "2021-01-01" `to` "2021-12-31"+>>> aapl_closing_2021+       index |   values+       ----- |   ------+"2021-01-04" | 128.6174+"2021-01-05" | 130.2076+"2021-01-06" | 125.8246+         ... |      ...+"2021-12-29" |   179.38+"2021-12-30" |    178.2+"2021-12-31" |   177.57++To calculate the average closing price over the year 2021, we use 'Data.Series.fold' in conjunction with+'Data.Series.mean':++>>> Series.fold Series.mean aapl_closing_2021+140.61256349206354++One of the magic things about 'Fold' is that it's possible to combine them in such a way that you can +traverse a 'Series' only once, which is important for good performance. As an example, we'll calculate+both the mean closing price AND the standard deviation of closing prices.++>>> let meanAndStdDev = (,) <$> Data.Series.mean <*> Data.Series.std+>>> Series.fold meanAndStdDev aapl_closing_2021+(140.61256349206354,14.811663837435361)++See 'Control.Foldl' from the @foldl@ package for more information on 'Fold'.+-}++{- $grouping++One important feature of 'Series' is the ability to efficiently group values +together based on their keys.++Let's load some stock price data again for this part:++>>> import Data.Fixed ( Centi )+>>> (aapl_closing :: Series String Double) <- (Series.fromList . read) <$> readFile "files/aapl.txt"+>>> aapl_closing +       index |  values+       ----- |  ------+"1980-12-12" |  0.1007+"1980-12-15" | 9.54e-2+"1980-12-16" | 8.84e-2+         ... |     ...+"2022-01-05" |  174.92+"2022-01-06" |   172.0+"2022-01-07" |  172.17++Grouping involves two steps:++  (1) Grouping keys in some way using 'groupBy';+  (2) Aggregating the values in each group using 'aggregateWith' or other variants.++Let's find the highest closing price of each month. First, we need to define+our grouping function:++>>> :{ +       -- | Extract the year and month from a date like XXXX-YY-ZZ. For example:+       -- +       -- >>> month "2023-01-01"+       -- "2023-01"+       month :: String -> String+       month = take 7+    :}++Then, we can group keys by month and take the 'maximum' of each group:++>>> aapl_closing `groupBy` month `aggregateWith` maximum+    index | values+    ----- | ------+"1980-12" | 0.1261+"1981-01" | 0.1208+"1981-02" | 0.1007+      ... |    ...+"2021-11" |  165.3+"2021-12" | 180.33+"2022-01" | 182.01++This means, for example, that the maximum closing price for Apple stock in the +month of November 2021 was $165.30 per share. This library also contains +numerical aggregation functions such as 'Data.Series.mean' and 'Data.Series.std'. Therefore, in order +to find the monthly average Apple closing price, rounded to the nearest cent:++>>> import Data.Series (mean)+>>> let (roundToCent :: Double -> Double) = \x -> fromIntegral ((round $ x * 100) :: Int) / 100+>>> aapl_closing `groupBy` month `aggregateWith` (roundToCent . Series.fold mean)+    index | values+    ----- | ------+"1980-12" |   0.11+"1981-01" |   0.11+"1981-02" | 9.0e-2+      ... |    ...+"2021-11" | 154.21+"2021-12" | 173.55+"2022-01" | 176.16++-}++{- $windowing++Windowing aggregation refers to the practice of aggregating values in a window around every key.++General-purpose windowing is done using the 'windowing' function. Let's look at its+type signature:++>>> :t windowing+windowing+  :: Ord k =>+     (k -> Range k) -> (Series k a -> b) -> Series k a -> Series k b++Here, @`windowing` window aggfunc xs@ is a new series @'Series' k b@ where+for every key @k@, the values in the range @window k@ are aggregated by @aggfunc@+and placed in the resulting series at key @k@. Here's an example where+for every key @k@, we add the values at @k@ and @k+1@:++>>> :{ +let (xs :: Series Int Int) +      = Series.fromList [ (1, 0)+                        , (2, 1)+                        , (3, 2)+                        , (4, 3)+                        , (5, 4)+                        , (6, 5)+                        ]+in windowing (\k -> k `to` (k + 1)) sum xs+:}+index | values+----- | ------+    1 |      1+    2 |      3+    3 |      5+    4 |      7+    5 |      9+    6 |      5++'windowing' can be used to compute so-called rolling aggregations. An example of+this is to compute the rolling mean of the last 3 keys:++>>> import Data.Series ( mean )+>>> :{ +let rollingMean = windowing (\k -> (k-3) `to` k) (Series.fold mean)+    (xs :: Series Int Double) +      = Series.fromList [ (1, 0)+                        , (2, 1)+                        , (3, 2)+                        , (4, 3)+                        , (5, 4)+                        , (6, 5)+                        ]+ in (rollingMean xs) :: Series Int Double+:}+index | values+----- | ------+    1 |    0.0+    2 |    0.5+    3 |    1.0+    4 |    1.5+    5 |    2.5+    6 |    3.5++-}++{- $zipping ++An important class of operations are combining two 'Series' together, also known as *zipping*. +For lists, Haskell has 'Data.List.zipWith'. 'Series' also have 'Series.zipWith' and variants:++* 'Series.zipWith', which combines two series with some elementwise function;+* 'Series.zipWithMatched', which combines two series with some elementwise function +  on keys which are in *both* maps;+* 'Series.zipWithStrategy', which combines two series with some elementwise +  function and supports custom operations to deal with missing keys;++To illustrate the differences between the various zipping functions, +consider the following two series. There's population:++>>> :set -XNumericUnderscores+>>> import Data.Fixed (Centi)+>>> :{ +    -- Most recent population estimate rounded to the nearest million+    let population = Series.fromList [ ("Canada"::String, 40_000_000::Centi)+                                     , ("Kenya"         , 56_000_000)+                                     , ("Poland"        , 38_000_000)+                                     , ("Singapore"     ,  6_000_000)+                                     ]+    :}++and there's total land mass:++>>> :{ +    -- Land mass in square kilometer+    let landmass = Series.fromList [ ("Brazil"::String, 8_520_000::Centi)+                                   , ("Canada",         9_990_000)+                                   , ("Kenya",            580_000)+                                   , ("Poland",           313_000)+                                   ] +    :}++@'Series.zipWith' f left right@ combines the series @left@ and @right@ using the +function @f@ which admits two arguments, for all keys one-by-one. If a key +is missing from either @left@ or @right@, 'Series.zipWith' returns 'Nothing'. For example, +the population density per country would be:++>>> Series.zipWith (/) population landmass+      index |      values+      ----- |      ------+   "Brazil" |     Nothing+   "Canada" |   Just 4.00+    "Kenya" |  Just 96.55+   "Poland" | Just 121.40+"Singapore" |     Nothing++Since we don't have population estimates for Brazil and no land mass +information for Singapore, we can't calculate their population densities.++Sometimes, we only care about the results of @'Series.zipWith' f@ where keys are +in both series. In this case, we can use 'Series.zipWithMatched':++>>> Series.zipWithMatched (/) population landmass+   index | values+   ----- | ------+"Canada" |   4.00+ "Kenya" |  96.55+"Poland" | 121.40++Finally, in case we want full control over what to do when a key is missing, +we can use @Series.zipWithStrategy'. For example, consider the case where:++* If population numbers are missing, I want to set the density to 0;+* If land mass information is missing, I wait to skip calculating the density of this country. ++>>> import Data.Series (skipStrategy, constStrategy)+>>> let noPopulationStrategy = Series.constStrategy 0+>>> let noLandmassStrategy   = Series.skipStrategy+>>> Series.zipWithStrategy (/) noPopulationStrategy noLandmassStrategy population landmass+      index | values+      ----- | ------+   "Canada" |   4.00+    "Kenya" |  96.55+   "Poland" | 121.40+"Singapore" |   0.00++As you can imagine, 'Series.zipWithStrategy' is the most general and gives the most control, but is less easy +to use than 'Series.zipWith' and 'Series.zipWithMatched'.++-}++{- $conclusion++This section concludes the introductory tutorial to the @javelin@ package and its "Data.Series" module.++For a more in-depth look at this package, you can read the full documentation for each module:++* "Data.Series"+* "Data.Series.Index"+* "Data.Series.Unboxed"+* "Data.Series.Generic"++-}++{- $duplicates++If you must build a 'Series' with duplicate keys, you can use the 'Data.Series.fromListDuplicates' or +'Data.Series.fromVectorDuplicates' functions. +In the example below, the key @\'d\'@ is repeated three times:++>>> Series.fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]+  index | values+  ----- | ------+('a',0) |      5+('b',0) |      0+('d',0) |      1+('d',1) |     -4+('d',2) |      7++Note that the 'Series' produced by 'Data.Series.fromListDuplicates' still has unique keys, but each key is a +composite of a character and an occurrence. This is reflected in the type:++>>> :t Series.fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]+Series.fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]+  :: Series (Char, Occurrence) Int++Here, 'Data.Series.Occurrence' is a non-negative number, and can be converted to +other integer-like numbers using 'fromIntegral'. In practice, you should aim to aggregate your 'Series' to remove duplicate keys, for example+using 'Data.Series.groupBy' and grouping on the first element of the key ('fst'):++>>> let xs = Series.fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]+>>> xs `groupBy` fst `aggregateWith` sum+index | values+----- | ------+  'a' |      5+  'b' |      0+  'd' |      4++-}++{- $unboxed ++The 'Data.Series.Series' defined in "Data.Series" are based on 'Data.Vector.Vector' from "Data.Vector". +This implementation is nice because such 'Series' can hold _any_ Haskell type. However, because+Haskell types can be arbitrarily complex, numerical operations on 'Series' may not be as fast+as could be.++For simpler types such as 'Double' and 'Int', a different kind of series can be used to+speed up numerical calculations: 'Data.Series.Unboxed.Series' from the "Data.Series.Unboxed" module.+Such 'Data.Series.Unboxed.Series' are much more limited: they can only contain datatypes which are+instances of 'Data.Vector.Unboxed.Unbox'. ++This then brings the question: how can you write software which supports both ordinary 'Data.Series.Series'+__and__ unboxed 'Data.Series.Unboxed.Series'? The answer is to use functions from the "Data.Series.Generic".++For example, we could implement the dot product of two series as:++>>> import qualified Data.Series.Generic as G+>>> import Data.Vector.Generic ( Vector )+>>> :{+      dot :: (Ord k, Num a, Vector v a) => G.Series v k a -> G.Series v k a -> a+      dot v1 v2 = G.sum $ G.zipWithMatched (*) v1 v2+    :}++You can convert between the two types of series using the 'Data.Series.Generic.convert' function.++-}++{- $replacement ++'Series.map' allows to map every value of a series. How about replacing *some* +values in a series? The function 'Data.Series.replace' (and its infix variant, '|->') replaces values in the right operand +which have an analogue in the left operand:++>>> import Data.Series ( (|->) )+>>> let nan = (0/0) :: Double+>>> let right = Series.fromList [('a', 1), ('b', nan), ('c', 3), ('d', nan)]+>>> right+index | values+----- | ------+  'a' |    1.0+  'b' |    NaN+  'c' |    3.0+  'd' |    NaN+>>> let left = Series.fromList [('b', 0::Double), ('d', 0), ('e', 0)]+>>> left+index | values+----- | ------+  'b' |    0.0+  'd' |    0.0+  'e' |    0.0+>>> left |-> right+index | values+----- | ------+  'a' |    1.0+  'b' |    0.0+  'c' |    3.0+  'd' |    0.0++In the example above, the key @\'e\'@ is ignored since it was not in the @right@ +series to begin with.++The flipped version, '<-|', is also available.++-}++{- $comparison ++Below is a table showing which operations on "Data.Series" have analogues for +other data structures.+++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Action                          | "Data.Series"                  | "Data.Map.Strict"               | "Data.List"       | "Data.Vector"        |++=================================+================================+=================================+===================+======================++| Mapping values                  | 'Data.Series.map'              | 'Data.Map.Strict.map'           | 'map'             | 'Data.Vector.map'    |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Mapping index                   | 'Data.Series.mapIndex'         | 'Data.Map.Strict.mapKeys'       |                   |                      |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Mapping values with key         | 'Data.Series.mapWithKey'       | 'Data.Map.Strict.mapWithKey'    |                   |                      |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Filtering values                | 'Data.Series.filter'           | 'Data.Map.Strict.filter'        | 'filter'          | 'Data.Vector.filter' |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Filtering index                 | 'Data.Series.select',          | 'Data.Map.Strict.filterWithKey' |                   |                      |+|                                 | 'Data.Series.filterWithKey'    |                                 |                   |                      |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Indexing by key                 | 'Data.Series.at'               | 'Data.Map.Strict.lookup'        |                   |                      |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Indexing by position            | 'Data.Series.iat'              |                                 | 'Data.List.!'     | 'Data.Vector.!'      |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Combine two structures key-wise | 'Data.Series.zipWith'          | 'Data.Map.Merge.Strict.merge'   |                   |                      |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Union                           | 'Data.Series.<>'               | 'Data.Map.Strict.union'         | 'Data.List.union' |                      |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------++| Group keys                      | 'Data.Series.groupBy'          |                                 |                   |                      |++---------------------------------+--------------------------------+---------------------------------+-------------------+----------------------+++-}
src/Data/Series/Unboxed.hs view
@@ -1,1291 +1,1291 @@------------------------------------------------------------------------------
--- |
--- Module      :  Data.Series.Unboxed
--- Copyright   :  (c) Laurent P. René de Cotret
--- License     :  MIT
--- Maintainer  :  laurent.decotret@outlook.com
--- Portability :  portable
---
--- This module contains data structures and functions to work with 'Series' capable of holding unboxed values,
--- i.e. values of types which are instances of `Unbox`.
---
--- = Why use unboxed series?
---
--- Unboxed series can have much better performance, at the cost of less flexibility. For example,
--- an unboxed series cannot contain values of type @`Maybe` a@. Moreover, unboxed series aren't instances of 
--- `Functor` or `Foldable`.
---
--- If you are hesitating, you should prefer the series implementation in the "Data.Series" module.
---
--- = Introduction to series
---
--- A 'Series' of type @Series k a@ is a labeled array of values of type @a@,
--- indexed by keys of type @k@.
---
--- Like `Data.Map.Strict.Map` from the @containers@ package, 'Series' support efficient:
---
---      * random access by key ( \(O(\log n)\) );
---      * slice by key ( \(O(\log n)\) ).
---
--- Like `Data.Vector.Vector`, they support efficient:
---
---      * random access by index ( \(O(1)\) );
---      * slice by index ( \(O(1)\) );
---      * numerical operations.
---
--- This module re-exports most of the content of "Data.Series.Generic", with type signatures 
--- specialized to the unboxed vector type `Data.Vector.Unboxed.Vector`.
- 
-module Data.Series.Unboxed (
-    Series, index, values,
-
-    -- * Building/converting 'Series'
-    singleton, fromIndex,
-    -- ** Lists
-    fromList, toList,
-    -- ** Vectors
-    fromVector, toVector,
-    -- ** Handling duplicates
-    Occurrence, fromListDuplicates, fromVectorDuplicates,
-    -- ** Strict Maps
-    fromStrictMap, toStrictMap,
-    -- ** Lazy Maps
-    fromLazyMap, toLazyMap,
-    -- ** Ad-hoc conversion with other data structures
-    IsSeries(..),
-    -- ** Conversion between 'Series' types
-    G.convert,
-
-    -- * Mapping and filtering
-    map, mapWithKey, mapIndex, concatMap,
-    take, takeWhile, drop, dropWhile, filter, filterWithKey,
-    -- ** Mapping with effects
-    mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_,
-
-    -- * Combining series
-    zipWithMatched, zipWithKey,
-    zipWithMatched3, zipWithKey3,
-    ZipStrategy, skipStrategy, mapStrategy, constStrategy, zipWithStrategy, zipWithStrategy3,
-    zipWithMonoid, esum, eproduct, unzip, unzip3,
-
-    -- * Index manipulation
-    require, dropIndex,
-
-    -- * Accessors
-    -- ** Bulk access
-    select, selectWhere, Range, to, from, upto, Selection, 
-    -- ** Single-element access
-    at, iat,
-
-    -- * Replacement
-    replace, (|->), (<-|),
-
-    -- * Grouping and windowing operations
-    groupBy, Grouping, aggregateWith, foldWith, 
-    windowing, expanding,
-
-    -- * Folds
-    -- ** General folds
-    fold, foldM, foldWithKey, foldMWithKey, foldMap, foldMap', foldMapWithKey,
-    -- ** Specialized folds
-    G.mean, G.variance, G.std,
-    null, length, all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn,
-    argmin, argmax,
-
-    -- * Scans
-    postscanl, prescanl,
-
-    -- * Displaying 'Series'
-    display, displayWith,
-    noLongerThan,
-    DisplayOptions(..), G.defaultDisplayOptions
-) where
-
-import           Control.Foldl       ( Fold, FoldM )
-import qualified Data.Map.Lazy       as ML
-import qualified Data.Map.Strict     as MS
-import           Data.Series.Index   ( Index )
-import           Data.Series.Generic.View 
-                                     ( Range, Selection, to, from, upto )
-import           Data.Series.Generic ( IsSeries(..), ZipStrategy, Occurrence, DisplayOptions(..), skipStrategy, mapStrategy, constStrategy
-                                     , noLongerThan 
-                                     )
-import qualified Data.Series.Generic as G
-import           Data.Vector.Unboxed ( Vector, Unbox )
-import qualified Data.Vector.Unboxed as Vector
-
-import           Prelude             hiding ( map, concatMap, zipWith, filter, foldMap, null, length, all, any, and, or
-                                            , sum, product, maximum, minimum, take, takeWhile, drop, dropWhile
-                                            , last, unzip, unzip3
-                                            )
-
--- $setup
--- >>> import qualified Data.Series.Unboxed as Series
--- >>> import qualified Data.Series.Index as Index
-
-infixl 1 `select` 
-infix 6 |->, <-|
-
--- | A series is a labeled array of values of type @a@,
--- indexed by keys of type @k@.
---
--- Like @Data.Map@ and @Data.HashMap@, they support efficient:
---
---      * random access by key ( \(O(\log n)\) );
---      * slice by key ( \(O(\log n)\) ).
---
--- Like @Data.Vector.Vector@, they support efficient:
---
---      * random access by index ( \(O(1)\) );
---      * slice by index ( \(O(1)\) );
---      * numerical operations.
-type Series = G.Series Vector
-
-
-index :: Series k a -> Index k
-{-# INLINABLE index #-}
-index = G.index
-
-
-values :: Series k a -> Vector a
-{-# INLINABLE values #-}
-values = G.values
-
-
--- | Create a 'Series' with a single element.
-singleton :: Unbox a => k -> a -> Series k a
-{-# INLINABLE singleton #-}
-singleton = G.singleton
-
-
--- | \(O(n)\) Generate a 'Series' by mapping every element of its index.
---
--- >>> fromIndex (const (0::Int)) $ Index.fromList ['a','b','c','d']
--- index | values
--- ----- | ------
---   'a' |      0
---   'b' |      0
---   'c' |      0
---   'd' |      0
-fromIndex :: Unbox a
-          => (k -> a) -> Index k -> Series k a
-{-# INLINABLE fromIndex #-}
-fromIndex = G.fromIndex
-
-
--- | Construct a series from a list of key-value pairs. There is no
--- condition on the order of pairs.
---
--- >>> let xs = fromList [('b', 0::Int), ('a', 5), ('d', 1) ]
--- >>> xs
--- index | values
--- ----- | ------
---   'a' |      5
---   'b' |      0
---   'd' |      1
---
--- If you need to handle duplicate keys, take a look at `fromListDuplicates`.
-fromList :: (Ord k, Unbox a) => [(k, a)] -> Series k a
-{-# INLINABLE fromList #-}
-fromList = G.fromList
-
-
--- | Construct a series from a list of key-value pairs.
--- Contrary to `fromList`, values at duplicate keys are preserved. To keep each
--- key unique, an `Occurrence` number counts up.
---
--- >>> let xs = fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]
--- >>> xs
---   index | values
---   ----- | ------
--- ('a',0) |      5
--- ('b',0) |      0
--- ('d',0) |      1
--- ('d',1) |     -4
--- ('d',2) |      7
-fromListDuplicates :: (Ord k, Unbox a) => [(k, a)] -> Series (k, Occurrence) a
-{-# INLINABLE fromListDuplicates #-}
-fromListDuplicates = G.fromListDuplicates
-
-
--- | Construct a list from key-value pairs. The elements are in order sorted by key:
---
--- >>> let xs = Series.fromList [ ('b', 0::Int), ('a', 5), ('d', 1) ]
--- >>> xs
--- index | values
--- ----- | ------
---   'a' |      5
---   'b' |      0
---   'd' |      1
--- >>> toList xs
--- [('a',5),('b',0),('d',1)]
-toList :: Unbox a => Series k a -> [(k, a)]
-{-# INLINABLE toList #-}
-toList = G.toList
-
-
--- | Construct a 'Vector' of key-value pairs. The elements are in order sorted by key. 
-toVector :: (Unbox a, Unbox k) => Series k a -> Vector (k, a)
-{-# INLINABLE toVector #-}
-toVector = G.toVector
-
-
--- | Construct a 'Series' from a 'Vector' of key-value pairs. There is no
--- condition on the order of pairs. Duplicate keys are silently dropped. If you
--- need to handle duplicate keys, see 'fromVectorDuplicates'.
---
--- Note that due to differences in sorting,
--- @Series.fromList@ and @Series.fromVector . Vector.fromList@ 
--- may not be equivalent if the input list contains duplicate keys.
-fromVector :: (Ord k, Unbox k, Unbox a)
-           => Vector (k, a) -> Series k a
-{-# INLINABLE fromVector #-}
-fromVector = G.fromVector
-
-
--- | Construct a series from a 'Vector' of key-value pairs.
--- Contrary to 'fromVector', values at duplicate keys are preserved. To keep each
--- key unique, an 'Occurrence' number counts up.
---
--- >>> import qualified Data.Vector.Unboxed as Unboxed
--- >>> let xs = fromVectorDuplicates $ Unboxed.fromList [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]
--- >>> xs
---   index | values
---   ----- | ------
--- ('a',0) |      5
--- ('b',0) |      0
--- ('d',0) |      1
--- ('d',1) |     -4
--- ('d',2) |      7
-fromVectorDuplicates :: (Unbox k, Unbox a, Ord k) => Vector (k, a) -> Series (k, Occurrence) a
-{-# INLINABLE fromVectorDuplicates #-}
-fromVectorDuplicates = G.fromVectorDuplicates
-
-
--- | Convert a series into a lazy @Map@.
-toLazyMap :: (Unbox a) => Series k a -> ML.Map k a
-{-# INLINABLE toLazyMap #-}
-toLazyMap = G.toLazyMap
-
-
--- | Construct a series from a lazy @Map@.
-fromLazyMap :: (Unbox a) => ML.Map k a -> Series k a
-{-# INLINABLE fromLazyMap #-}
-fromLazyMap = G.fromLazyMap
-
-
--- | Convert a series into a strict @Map@.
-toStrictMap :: (Unbox a) => Series k a -> MS.Map k a
-{-# INLINABLE toStrictMap #-}
-toStrictMap = G.toStrictMap
-
--- | Construct a series from a strict @Map@.
-fromStrictMap :: (Unbox a) => MS.Map k a -> Series k a
-{-# INLINABLE fromStrictMap #-}
-fromStrictMap = G.fromStrictMap
-
-
--- | \(O(n)\) Map every element of a 'Series'.
-map :: (Unbox a, Unbox b) => (a -> b) -> Series k a -> Series k b
-{-# INLINABLE map #-}
-map = G.map
-
-
--- | \(O(n)\) Map every element of a 'Series', possibly using the key as well.
-mapWithKey :: (Unbox a, Unbox b) => (k -> a -> b) -> Series k a -> Series k b
-{-# INLINABLE mapWithKey #-}
-mapWithKey = G.mapWithKey
-
-
--- | \(O(n \log n)\).
--- Map each key in the index to another value. Note that the resulting series
--- may have less elements, because each key must be unique.
---
--- In case new keys are conflicting, the first element is kept.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> import qualified Data.List
--- >>> xs `mapIndex` (Data.List.take 1)
--- index | values
--- ----- | ------
---   "L" |      4
---   "P" |      1
-mapIndex :: (Unbox a, Ord k, Ord g) => Series k a -> (k -> g) -> Series g a
-{-# INLINABLE mapIndex #-}
-mapIndex = G.mapIndex
-
-
--- | Map a function over all the elements of a 'Series' and concatenate the result into a single 'Series'.
-concatMap :: (Unbox a, Unbox k, Unbox b, Ord k) 
-          => (a -> Series k b) 
-          -> Series k a 
-          -> Series k b
-{-# INLINABLE concatMap #-}
-concatMap = G.concatMap
-
-
--- | \(O(n)\) Apply the monadic action to every element of a series and its
--- index, yielding a series of results.
-mapWithKeyM :: (Unbox a, Unbox b, Monad m, Ord k) => (k -> a -> m b) -> Series k a -> m (Series k b)
-{-# INLINABLE mapWithKeyM #-}
-mapWithKeyM = G.mapWithKeyM
-
-
--- | \(O(n)\) Apply the monadic action to every element of a series and its
--- index, discarding the results.
-mapWithKeyM_ :: (Unbox a, Monad m) => (k -> a -> m b) -> Series k a -> m ()
-{-# INLINABLE mapWithKeyM_ #-}
-mapWithKeyM_ = G.mapWithKeyM_
-
-
--- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, 
--- yielding a series of results.
-forWithKeyM :: (Unbox a, Unbox b, Monad m, Ord k) => Series k a -> (k -> a -> m b) -> m (Series k b)
-{-# INLINABLE forWithKeyM #-}
-forWithKeyM = G.forWithKeyM
-
-
--- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, 
--- discarding the results.
-forWithKeyM_ :: (Unbox a, Monad m) => Series k a -> (k -> a -> m b) -> m ()
-{-# INLINABLE forWithKeyM_ #-}
-forWithKeyM_ = G.forWithKeyM_
-
-
--- | \(O(\log n)\) @'take' n xs@ returns at most @n@ elements of the 'Series' @xs@.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- "Vienna" |      5
--- >>> take 2 xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
-take :: Unbox a => Int -> Series k a -> Series k a
-{-# INLINABLE take #-}
-take = G.take
-
-
--- | \(O(n)\) Returns the longest prefix (possibly empty) of the input 'Series' that satisfy a predicate.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- "Vienna" |      5
-
--- >>> takeWhile (>1) xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
-takeWhile :: Unbox a => (a -> Bool) -> Series k a -> Series k a
-takeWhile = G.takeWhile
-
-
--- | \(O(\log n)\) @'drop' n xs@ drops at most @n@ elements from the 'Series' @xs@.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- "Vienna" |      5
--- >>> drop 2 xs
---    index | values
---    ----- | ------
---  "Paris" |      1
--- "Vienna" |      5
-drop :: Unbox a => Int -> Series k a -> Series k a
-{-# INLINABLE drop #-}
-drop = G.drop
-
-
--- | \(O(n)\) Returns the complement of `takeWhile`.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- "Vienna" |      5
-
--- >>> dropWhile (>1) xs
---    index | values
---    ----- | ------
---  "Paris" |      1
--- "Vienna" |      5
-dropWhile :: Unbox a => (a -> Bool) -> Series k a -> Series k a
-dropWhile = G.dropWhile
-
-
--- | Apply a function elementwise to two series, matching elements
--- based on their keys. Keys present only in the left or right series are dropped.
---
--- >>> let xs = Series.fromList [ ('a', 0::Int),  ('b', 1),  ('g', 2) ]
--- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11), ('d', 13) ]
--- >>> zipWithMatched (+) xs ys
--- index | values
--- ----- | ------
---   'a' |     10
---   'b' |     12
-zipWithMatched :: (Unbox a, Unbox b, Unbox c, Ord k) 
-               => (a -> b -> c) -> Series k a -> Series k b -> Series k c
-{-# INLINABLE zipWithMatched #-}
-zipWithMatched = G.zipWithMatched
-
-
--- | Apply a function elementwise to three series, matching elements
--- based on their keys. Keys not present in all three series are dropped.
---
--- >>> let xs = Series.fromList [ ('a', 0::Int),  ('b', 1),   ('g', 2) ]
--- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11),  ('d', 13) ]
--- >>> let zs = Series.fromList [ ('a', 20::Int), ('d', 13), ('e', 6) ]
--- >>> zipWithMatched3 (\x y z -> x + y + z) xs ys zs
--- index | values
--- ----- | ------
---   'a' |     30
-zipWithMatched3 :: (Unbox a, Unbox b, Unbox c, Unbox d, Ord k) 
-                => (a -> b -> c -> d) 
-                -> Series k a 
-                -> Series k b 
-                -> Series k c
-                -> Series k d
-{-# INLINABLE zipWithMatched3 #-}
-zipWithMatched3 = G.zipWithMatched3
-
-
--- | Apply a function elementwise to two series, matching elements
--- based on their keys. Keys present only in the left or right series are dropped.
--- 
---
--- >>> import Data.Char ( ord )
--- >>> let xs = Series.fromList [ ('a', 0::Int), ('b', 1), ('c', 2) ]
--- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11), ('d', 13) ]
--- >>> zipWithKey (\k x y -> ord k + x + y) xs ys
--- index | values
--- ----- | ------
---   'a' |    107
---   'b' |    110
-zipWithKey :: (Unbox a, Unbox b, Unbox c, Unbox k, Ord k)  
-           => (k -> a -> b -> c) -> Series k a -> Series k b -> Series k c
-{-# INLINABLE zipWithKey #-}
-zipWithKey = G.zipWithKey
-
-
--- | Apply a function elementwise to three series, matching elements
--- based on their keys. Keys present only in the left or right series are dropped.
--- 
--- >>> import Data.Char ( ord )
--- >>> let xs = Series.fromList [ ('a', 0::Int), ('b', 1), ('g', 2) ]
--- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11), ('d', 13) ]
--- >>> let zs = Series.fromList [ ('a', 20::Int), ('b', 7), ('d', 5) ]
--- >>> zipWithKey3 (\k x y z -> ord k + x + y + z) xs ys zs
--- index | values
--- ----- | ------
---   'a' |    127
---   'b' |    117
-zipWithKey3 :: (Unbox a, Unbox b, Unbox c, Unbox d, Unbox k, Ord k) 
-            => (k -> a -> b -> c -> d) 
-            -> Series k a 
-            -> Series k b 
-            -> Series k c
-            -> Series k d
-{-# INLINABLE zipWithKey3 #-}
-zipWithKey3 = G.zipWithKey3
-
-
--- | Zip two 'Series' with a combining function, applying a 'ZipStrategy' when one key is present in one of the 'Series' but not both.
---
--- In the example below, we want to set the value to @-100@ (via @'constStrategy' (-100)@) for keys which are only present 
--- in the left 'Series', and drop keys (via 'skipStrategy') which are only present in the `right 'Series'  
---
--- >>> let xs = Series.fromList [ ('a', 0::Int),  ('b', 1),  ('g', 2) ]
--- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11), ('d', 13) ]
--- >>> zipWithStrategy (+) (constStrategy (-100)) skipStrategy  xs ys
--- index | values
--- ----- | ------
---   'a' |     10
---   'b' |     12
---   'g' |   -100
---
--- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched' f@ 
--- than using @'zipWithStrategy' f 'skipStrategy' 'skipStrategy'@.
-zipWithStrategy :: (Ord k, Unbox a, Unbox b, Unbox c) 
-                => (a -> b -> c)     -- ^ Function to combine values when present in both series
-                -> ZipStrategy k a c -- ^ Strategy for when the key is in the left series but not the right
-                -> ZipStrategy k b c -- ^ Strategy for when the key is in the right series but not the left
-                -> Series k a
-                -> Series k b 
-                -> Series k c
-{-# INLINABLE zipWithStrategy #-}
-zipWithStrategy = G.zipWithStrategy
-
-
--- | Zip three 'Series' with a combining function, applying a 'ZipStrategy' when one key is 
--- present in one of the 'Series' but not all of the others.
---
--- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched3' f@ 
--- than using @'zipWithStrategy3' f skipStrategy skipStrategy skipStrategy@.
-zipWithStrategy3 :: (Ord k, Unbox a, Unbox b, Unbox c, Unbox d) 
-                => (a -> b -> c -> d) -- ^ Function to combine values when present in all series
-                -> ZipStrategy k a d  -- ^ Strategy for when the key is in the left series but not in all the others
-                -> ZipStrategy k b d  -- ^ Strategy for when the key is in the center series but not in all the others
-                -> ZipStrategy k c d  -- ^ Strategy for when the key is in the right series but not in all the others
-                -> Series k a
-                -> Series k b 
-                -> Series k c
-                -> Series k d
-zipWithStrategy3 = G.zipWithStrategy3
-{-# INLINABLE zipWithStrategy3 #-}
-
-
--- | Zip two 'Series' with a combining function. The value for keys which are missing from
--- either 'Series' is replaced with the appropriate `mempty` value.
---
--- >>> import Data.Monoid ( Sum(..) )
--- >>> let xs = Series.fromList [ ("2023-01-01", Sum (1::Int)), ("2023-01-02", Sum 2) ]
--- >>> let ys = Series.fromList [ ("2023-01-01", Sum (5::Int)), ("2023-01-03", Sum 7) ]
--- >>> zipWithMonoid (<>) xs ys
---        index |           values
---        ----- |           ------
--- "2023-01-01" | Sum {getSum = 6}
--- "2023-01-02" | Sum {getSum = 2}
--- "2023-01-03" | Sum {getSum = 7}
-zipWithMonoid :: ( Monoid a, Monoid b
-                 , Unbox a, Unbox b, Unbox c
-                 , Ord k
-                 ) 
-              => (a -> b -> c)
-              -> Series k a
-              -> Series k b 
-              -> Series k c
-zipWithMonoid = G.zipWithMonoid
-{-# INLINABLE zipWithMonoid #-}
-
-
--- | Elementwise sum of two 'Series'. Elements missing in one or the other 'Series' is considered 0. 
---
--- >>> let xs = Series.fromList [ ("2023-01-01", (1::Int)), ("2023-01-02", 2) ]
--- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]
--- >>> xs `esum` ys
---        index | values
---        ----- | ------
--- "2023-01-01" |      6
--- "2023-01-02" |      2
--- "2023-01-03" |      7
-esum :: (Ord k, Num a, Unbox a) 
-     => Series k a 
-     -> Series k a
-     -> Series k a
-esum = G.esum
-{-# INLINABLE esum #-}
-
-
--- | Elementwise product of two 'Series'. Elements missing in one or the other 'Series' is considered 1. 
---
--- >>> let xs = Series.fromList [ ("2023-01-01", (2::Int)), ("2023-01-02", 3) ]
--- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]
--- >>> xs `eproduct` ys
---        index | values
---        ----- | ------
--- "2023-01-01" |     10
--- "2023-01-02" |      3
--- "2023-01-03" |      7
-eproduct :: (Ord k, Num a, Unbox a) 
-         => Series k a 
-         -> Series k a
-         -> Series k a
-eproduct = G.eproduct
-{-# INLINABLE eproduct #-}
-
-
--- | \(O(n)\) Unzip a 'Series' of 2-tuples.
-unzip :: (Unbox a, Unbox b) 
-      => Series k (a, b)
-      -> ( Series k a
-         , Series k b
-         )
-unzip = G.unzip
-{-# INLINABLE unzip #-}
-
-
--- | \(O(n)\) Unzip a 'Series' of 3-tuples.
-unzip3 :: (Unbox a, Unbox b, Unbox c) 
-       => Series k (a, b, c)
-       -> ( Series k a
-          , Series k b
-          , Series k c
-          )
-unzip3 = G.unzip3
-{-# INLINABLE unzip3 #-}
-
-
--- | Require a series to have a specific `Index`. 
--- Contrary to @select@, all keys in the `Index` will be present in the resulting series.
---
--- Note that unlike the implementation for boxed series (`Data.Series.require`), missing keys need to be mapped to some values because unboxed
--- series cannot contain values of type @`Maybe` a@. 
---
--- In the example below, the missing value for key @\"Taipei\"@ is mapped to 0:
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> require (const 0) xs (Index.fromList ["Paris", "Lisbon", "Taipei"])
---    index | values
---    ----- | ------
--- "Lisbon" |      4
---  "Paris" |      1
--- "Taipei" |      0
-require :: (Unbox a, Ord k) 
-        => (k -> a) -> Series k a -> Index k -> Series k a
-{-# INLINABLE require #-}
-require f = G.requireWith f id
-
-
--- | \(O(n)\) Drop the index of a series by replacing it with an `Int`-based index. Values will
--- be indexed from 0.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> dropIndex xs
--- index | values
--- ----- | ------
---     0 |      4
---     1 |      2
---     2 |      1
-dropIndex :: Series k a -> Series Int a
-{-# INLINABLE dropIndex #-}
-dropIndex = G.dropIndex
-
-
--- | Filter elements. Only elements for which the predicate is @True@ are kept. 
--- Notice that the filtering is done on the values, not on the keys.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> filter (>2) xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
---
--- See also 'filterWithKey'.
-filter :: (Unbox a, Ord k) => (a -> Bool) -> Series k a -> Series k a
-{-# INLINABLE filter #-}
-filter = G.filter
-
-
--- | Filter elements, taking into account the corresponding key. Only elements for which 
--- the predicate is @True@ are kept. 
-filterWithKey :: (Unbox a, Ord k) 
-              => (k -> a -> Bool) 
-              -> Series k a 
-              -> Series k a
-{-# INLINABLE filterWithKey #-}
-filterWithKey = G.filterWithKey
-
-
--- | Select a subseries. There are a few ways to do this.
---
--- The first way to do this is to select a sub-series based on random keys. For example,
--- selecting a subseries from an `Index`:
---
--- >>> let xs = Series.fromList [('a', 10::Int), ('b', 20), ('c', 30), ('d', 40)]
--- >>> xs `select` Index.fromList ['a', 'd']
--- index | values
--- ----- | ------
---   'a' |     10
---   'd' |     40
---
--- The second way to select a sub-series is to select all keys in a range:
---
--- >>> xs `select` 'b' `to` 'c'
--- index | values
--- ----- | ------
---   'b' |     20
---   'c' |     30
---
--- Note that with `select`, you'll always get a sub-series; if you ask for a key which is not
--- in the series, it'll be ignored:
---
--- >>> xs `select` Index.fromList ['a', 'd', 'e']
--- index | values
--- ----- | ------
---   'a' |     10
---   'd' |     40
---
--- See `require` if you want to ensure that all keys are present.
-select :: (Unbox a, Selection s, Ord k) => Series k a -> s k -> Series k a
-select = G.select
-
-
--- | Select a sub-series from a series matching a condition.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> xs `selectWhere` (Series.map (>1) xs)
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
-selectWhere :: (Unbox a, Ord k) => Series k a -> Series k Bool -> Series k a
-{-# INLINABLE selectWhere #-}
-selectWhere = G.selectWhere
-
-
--- | \(O(\log n)\). Extract a single value from a series, by key.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs `at` "Paris"
--- Just 1
--- >>> xs `at` "Sydney"
--- Nothing
-at :: (Unbox a, Ord k) => Series k a -> k -> Maybe a
-{-# INLINABLE at #-}
-at = G.at
-
-
--- | \(O(1)\). Extract a single value from a series, by index.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> xs `iat` 0
--- Just 4
--- >>> xs `iat` 3
--- Nothing
-iat :: Unbox a => Series k a -> Int -> Maybe a
-{-# INLINABLE iat #-}
-iat = G.iat
-
-
--- | Replace values in the right series from values in the left series at matching keys.
--- Keys not in the right series are unaffected.
--- 
--- See `(|->)` and `(<-|)`, which might be more readable.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> let ys = Series.singleton "Paris" (99::Int)
--- >>> ys `replace` xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |     99
-replace :: (Unbox a, Ord k) => Series k a -> Series k a -> Series k a
-{-# INLINABLE replace #-}
-replace = G.replace
-
-
--- | Replace values in the right series from values in the left series at matching keys.
--- Keys not in the right series are unaffected.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> let ys = Series.singleton "Paris" (99::Int)
--- >>> ys |-> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |     99
-(|->) :: (Unbox a, Ord k) => Series k a -> Series k a -> Series k a
-{-# INLINABLE (|->) #-}
-(|->) = (G.|->)
-
-
--- | Replace values in the left series from values in the right series at matching keys.
--- Keys not in the left series are unaffected.
---
--- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]
--- >>> xs
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |      1
--- >>> let ys = Series.singleton "Paris" (99::Int)
--- >>> xs <-| ys
---    index | values
---    ----- | ------
--- "Lisbon" |      4
--- "London" |      2
---  "Paris" |     99
-(<-|) :: (Unbox a, Ord k) => Series k a -> Series k a -> Series k a
-{-# INLINABLE (<-|) #-}
-(<-|) = (G.<-|)
-
-
--- | \(O(n)\) Execute a 'Fold' over a 'Series'.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Double
--- >>> xs
--- index | values
--- ----- | ------
---     0 |    1.0
---     1 |    2.0
---     2 |    3.0
---     3 |    4.0
--- >>> import Control.Foldl (variance)
--- >>> fold variance xs
--- 1.25
---
--- See also 'foldM' for monadic folds, and 'foldWithKey' to take keys into
--- account while folding.
-fold :: Unbox a 
-     => Fold a b -> Series k a -> b
-fold = G.fold
-{-# INLINABLE fold #-}
-
-
--- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series'.
---
--- See also 'fold' for pure folds, and 'foldMWithKey' to take keys into
--- account while folding.
-foldM :: (Monad m, Unbox a) 
-      => FoldM m a b  
-      -> Series k a 
-      -> m b
-foldM = G.foldM
-{-# INLINABLE foldM #-}
-
-
--- | \(O(n)\) Execute a 'Fold' over a 'Series', taking keys into account.
-foldWithKey :: (Unbox k, Unbox a) 
-            => Fold (k, a) b -> Series k a -> b
-foldWithKey = G.foldWithKey
-{-# INLINABLE foldWithKey #-}
-
-
--- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series', where the 'FoldM' takes keys into account.
-foldMWithKey :: (Monad m, Unbox a, Unbox k) 
-             => FoldM m (k, a) b  
-             -> Series k a 
-             -> m b
-foldMWithKey = G.foldMWithKey
-{-# INLINABLE foldMWithKey #-}
-
-
--- | \(O(n)\) Map each element of the structure to a monoid and combine
--- the results.
-foldMap :: (Monoid m, Unbox a) => (a -> m) -> Series k a -> m
-{-# INLINABLE foldMap #-}
-foldMap = G.foldMap
-
-
--- | \(O(n)\) Like 'foldMap', but strict in the accumulator. It uses the same
--- implementation as the corresponding method of the 'Foldable' type class.
-foldMap' :: (Monoid m, Unbox a) => (a -> m) -> Series k a -> m
-{-# INLINABLE foldMap' #-}
-foldMap' f = Vector.foldMap' f . values
-
-
--- | \(O(n)\) Map each element and associated key of the structure to a monoid and combine
--- the results.
-foldMapWithKey :: (Monoid m, Unbox a, Unbox k) => (k -> a -> m) -> Series k a -> m
-{-# INLINABLE foldMapWithKey #-}
-foldMapWithKey = G.foldMapWithKey
-
-
--- | Group values in a 'Series' by some grouping function (@k -> g@).
--- The provided grouping function is guaranteed to operate on a non-empty 'Series'.
---
--- This function is expected to be used in conjunction with @aggregate@:
--- 
--- >>> import Data.Maybe ( fromMaybe )
--- >>> type Date = (Int, String)
--- >>> month :: (Date -> String) = snd
--- >>> :{ 
---     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)
---                              , ((2021, "January"), -5)
---                              , ((2020, "June")   , 20)
---                              , ((2021, "June")   , 25) 
---                              ]
---      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)
--- :}
---     index | values
---     ----- | ------
--- "January" |     -5
---    "June" |     20
-groupBy :: Series k a      -- ^ Grouping function
-        -> (k -> g)        -- ^ Input series
-        -> Grouping k g a  -- ^ Grouped series
-{-# INLINABLE groupBy #-}
-groupBy = G.groupBy
-
-
--- | Representation of a 'Series' being grouped.
-type Grouping k g a = G.Grouping k g Vector a
-
-
--- | Aggregate groups resulting from a call to 'groupBy':
--- 
--- >>> import Data.Maybe ( fromMaybe )
--- >>> type Date = (Int, String)
--- >>> month :: (Date -> String) = snd
--- >>> :{ 
---     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)
---                              , ((2021, "January"), -5)
---                              , ((2020, "June")   , 20)
---                              , ((2021, "June")   , 25) 
---                              ]
---      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)
--- :}
---     index | values
---     ----- | ------
--- "January" |     -5
---    "June" |     20
---
--- If you want to aggregate groups using a binary function, see 'foldWith' which
--- may be much faster.
-aggregateWith :: (Ord g, Unbox a, Unbox b) 
-              => Grouping k g a 
-              -> (Series k a -> b) 
-              -> Series g b
-{-# INLINABLE aggregateWith #-}
-aggregateWith = G.aggregateWith
-
-
--- | Aggregate each group in a 'Grouping' using a binary function.
--- While this is not as expressive as 'aggregateWith', users looking for maximum
--- performance should use 'foldWith' as much as possible.
-foldWith :: (Ord g, Unbox a) 
-         => Grouping k g a
-         -> (a -> a -> a)
-         -> Series g a
-{-# INLINABLE foldWith #-}
-foldWith = G.foldWith
-
-
--- | Expanding window aggregation.
---
--- >>> :{ 
---     let (xs :: Series Int Int) 
---          = fromList [ (1, 0)
---                     , (2, 1)
---                     , (3, 2)
---                     , (4, 3)
---                     , (5, 4)
---                     , (6, 5)
---                     ]
---     in (xs `expanding` sum) :: Series Int Int 
--- :}
--- index | values
--- ----- | ------
---     1 |      0
---     2 |      1
---     3 |      3
---     4 |      6
---     5 |     10
---     6 |     15
-expanding :: (Unbox a, Unbox b) 
-          => Series k a        -- ^ Series vector
-          -> (Series k a -> b) -- ^ Aggregation function
-          -> Series k b        -- ^ Resulting vector
-{-# INLINABLE expanding #-}
-expanding = G.expanding
-
-
--- | General-purpose window aggregation.
---
--- >>> :{ 
---     let (xs :: Series.Series Int Int) 
---          = Series.fromList [ (1, 0)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 3)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in windowing (\k -> k `to` (k+2)) sum xs
--- :}
--- index | values
--- ----- | ------
---     1 |      3
---     2 |      6
---     3 |      9
---     4 |     12
---     5 |      9
---     6 |      5
-windowing :: (Ord k, Unbox a, Unbox b)
-          => (k -> Range k)
-          -> (Series k a -> b)
-          -> Series k a
-          -> Series k b
-{-# INLINABLE windowing #-}
-windowing = G.windowing 
-
-
--- | \(O(1)\) Test whether a 'Series' is empty.
-null :: Unbox a => Series k a -> Bool
-{-# INLINABLE null #-}
-null = G.null
-
-
--- |\(O(1)\) Extract the length of a 'Series'.
-length :: Unbox a => Series k a -> Int
-{-# INLINABLE length #-}
-length = G.length
-
-
--- | \(O(n)\) Check if all elements satisfy the predicate.
-all :: Unbox a => (a -> Bool) -> Series k a -> Bool
-{-# INLINABLE all #-}
-all = G.all
-
-
--- | \(O(n)\) Check if any element satisfies the predicate.
-any :: Unbox a => (a -> Bool) -> Series k a -> Bool
-{-# INLINABLE any #-}
-any = G.any
-
-
--- | \(O(n)\) Check if all elements are 'True'.
-and :: Series k Bool -> Bool
-{-# INLINABLE and #-}
-and = G.and
-
-
--- | \(O(n)\) Check if any element is 'True'.
-or :: Series k Bool -> Bool
-{-# INLINABLE or #-}
-or = G.or
-
-
--- | \(O(n)\) Compute the sum of the elements.
-sum :: (Unbox a, Num a) => Series k a -> a
-{-# INLINABLE sum #-}
-sum = G.sum
-
-
--- | \(O(n)\) Compute the product of the elements.
-product :: (Unbox a, Num a) => Series k a -> a
-{-# INLINABLE product #-}
-product = G.product
-
-
--- | \(O(n)\) Yield the maximum element of the series. In case of a tie, the first occurrence wins.
--- If the 'Series' is empty, @Nothing@ is returned.
---
--- See also 'argmax'.
-maximum :: (Ord a, Unbox a) => Series k a -> Maybe a
-{-# INLINABLE maximum #-}
-maximum = G.maximum
-
-
--- | \(O(n)\) @'maximumOn' f xs@ teturns the maximum element of the series @xs@, as determined by the function @f@.
--- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned.
-maximumOn :: (Ord b, Unbox a) => (a -> b) -> Series k a -> Maybe a
-{-# INLINABLE maximumOn #-}
-maximumOn = G.maximumOn
-
-
--- | \(O(n)\) Yield the minimum element of the series. In case of a tie, the first occurrence wins.
--- If the 'Series' is empty, @Nothing@ is returned.
---
--- See also 'argmin'.
-minimum :: (Ord a, Unbox a) => Series k a -> Maybe a
-{-# INLINABLE minimum #-}
-minimum = G.minimum
-
-
--- | \(O(n)\) @'minimumOn' f xs@ teturns the minimum element of the series @xs@, as determined by the function @f@.
--- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned.
-minimumOn :: (Ord b, Unbox a) => (a -> b) -> Series k a -> Maybe a
-{-# INLINABLE minimumOn #-}
-minimumOn = G.minimumOn
-
-
--- | \(O(n)\) Find the index of the maximum element in the input series.
--- If the input series is empty, 'Nothing' is returned.
---
--- The index of the first occurrence of the maximum element is returned.
---
--- >>> import qualified Data.Series.Unboxed as Series 
--- >>> :{ 
---     let (xs :: Series.Series Int Int) 
---          = Series.fromList [ (1, 0)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 7)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in argmax xs 
--- :}
--- Just 4
-argmax :: (Ord a, Unbox a)
-       => Series k a
-       -> Maybe k
-argmax = G.argmax
-{-# INLINABLE argmax #-}
-
-
--- | \(O(n)\) Find the index of the minimum element in the input series.
--- If the input series is empty, 'Nothing' is returned.
---
--- The index of the first occurrence of the minimum element is returned.
--- >>> import qualified Data.Series.Unboxed as Series 
--- >>> :{ 
---     let (xs :: Series.Series Int Int) 
---          = Series.fromList [ (1, 1)
---                            , (2, 1)
---                            , (3, 2)
---                            , (4, 0)
---                            , (5, 4)
---                            , (6, 5)
---                            ]
---     in argmin xs 
--- :}
--- Just 4
-argmin :: (Ord a, Unbox a)
-       => Series k a
-       -> Maybe k
-argmin = G.argmin
-{-# INLINABLE argmin #-}
-
-
--- | \(O(n)\) Left-to-right postscan.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int
--- >>> xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      3
---     3 |      4
--- >>> postscanl (+) 0 xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      3
---     2 |      6
---     3 |     10
-postscanl :: (Unbox a, Unbox b) 
-          => (a -> b -> a) -> a -> Series k b -> Series k a
-{-# INLINABLE postscanl #-}
-postscanl = G.postscanl
-
-
--- | \(O(n)\) Left-to-right prescan.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int
--- >>> xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      3
---     3 |      4
--- >>> prescanl (+) 0 xs
--- index | values
--- ----- | ------
---     0 |      0
---     1 |      1
---     2 |      3
---     3 |      6
-prescanl :: (Unbox a, Unbox b) 
-         => (a -> b -> a) -> a -> Series k b -> Series k a
-{-# INLINABLE prescanl #-}
-prescanl = G.prescanl
-
-
--- | Display a 'Series' using default 'DisplayOptions'.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int
--- >>> putStrLn $ display xs
--- index | values
--- ----- | ------
---     0 |      1
---     1 |      2
---     2 |      3
---   ... |    ...
---     4 |      5
---     5 |      6
---     6 |      7
-display :: (Unbox a, Show k, Show a) 
-        => Series k a 
-        -> String
-display = G.display
-
-
--- | Display a 'Series' using customizable 'DisplayOptions'.
---
--- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int
--- >>> import Data.List (replicate)
--- >>> :{
---     let opts = DisplayOptions { maximumNumberOfRows  = 4
---                               , indexHeader = "keys"
---                               , valuesHeader = "vals"
---                               , keyDisplayFunction   = (\i -> replicate i 'x') `noLongerThan` 5
---                               , valueDisplayFunction = (\i -> replicate i 'o') 
---                               }
---      in putStrLn $ displayWith opts xs
--- :}
---   keys |    vals
---  ----- |  ------
---        |       o
---      x |      oo
---    ... |     ...
---  xxxxx |  oooooo
--- xxx... | ooooooo
-displayWith :: (Unbox a) 
-            => DisplayOptions k a
-            -> Series k a 
-            -> String
-displayWith = G.displayWith
+-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Series.Unboxed+-- Copyright   :  (c) Laurent P. René de Cotret+-- License     :  MIT+-- Maintainer  :  laurent.decotret@outlook.com+-- Portability :  portable+--+-- This module contains data structures and functions to work with 'Series' capable of holding unboxed values,+-- i.e. values of types which are instances of `Unbox`.+--+-- = Why use unboxed series?+--+-- Unboxed series can have much better performance, at the cost of less flexibility. For example,+-- an unboxed series cannot contain values of type @`Maybe` a@. Moreover, unboxed series aren't instances of +-- `Functor` or `Foldable`.+--+-- If you are hesitating, you should prefer the series implementation in the "Data.Series" module.+--+-- = Introduction to series+--+-- A 'Series' of type @Series k a@ is a labeled array of values of type @a@,+-- indexed by keys of type @k@.+--+-- Like `Data.Map.Strict.Map` from the @containers@ package, 'Series' support efficient:+--+--      * random access by key ( \(O(\log n)\) );+--      * slice by key ( \(O(\log n)\) ).+--+-- Like `Data.Vector.Vector`, they support efficient:+--+--      * random access by index ( \(O(1)\) );+--      * slice by index ( \(O(1)\) );+--      * numerical operations.+--+-- This module re-exports most of the content of "Data.Series.Generic", with type signatures +-- specialized to the unboxed vector type `Data.Vector.Unboxed.Vector`.+ +module Data.Series.Unboxed (+    Series, index, values,++    -- * Building/converting 'Series'+    singleton, fromIndex,+    -- ** Lists+    fromList, toList,+    -- ** Vectors+    fromVector, toVector,+    -- ** Handling duplicates+    Occurrence, fromListDuplicates, fromVectorDuplicates,+    -- ** Strict Maps+    fromStrictMap, toStrictMap,+    -- ** Lazy Maps+    fromLazyMap, toLazyMap,+    -- ** Ad-hoc conversion with other data structures+    IsSeries(..),+    -- ** Conversion between 'Series' types+    G.convert,++    -- * Mapping and filtering+    map, mapWithKey, mapIndex, concatMap,+    take, takeWhile, drop, dropWhile, filter, filterWithKey,+    -- ** Mapping with effects+    mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_,++    -- * Combining series+    zipWithMatched, zipWithKey,+    zipWithMatched3, zipWithKey3,+    ZipStrategy, skipStrategy, mapStrategy, constStrategy, zipWithStrategy, zipWithStrategy3,+    zipWithMonoid, esum, eproduct, unzip, unzip3,++    -- * Index manipulation+    require, dropIndex,++    -- * Accessors+    -- ** Bulk access+    select, selectWhere, Range, to, from, upto, Selection, +    -- ** Single-element access+    at, iat,++    -- * Replacement+    replace, (|->), (<-|),++    -- * Grouping and windowing operations+    groupBy, Grouping, aggregateWith, foldWith, +    windowing, expanding,++    -- * Folds+    -- ** General folds+    fold, foldM, foldWithKey, foldMWithKey, foldMap, foldMap', foldMapWithKey,+    -- ** Specialized folds+    G.mean, G.variance, G.std,+    null, length, all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn,+    argmin, argmax,++    -- * Scans+    postscanl, prescanl,++    -- * Displaying 'Series'+    display, displayWith,+    noLongerThan,+    DisplayOptions(..), G.defaultDisplayOptions+) where++import           Control.Foldl       ( Fold, FoldM )+import qualified Data.Map.Lazy       as ML+import qualified Data.Map.Strict     as MS+import           Data.Series.Index   ( Index )+import           Data.Series.Generic.View +                                     ( Range, Selection, to, from, upto )+import           Data.Series.Generic ( IsSeries(..), ZipStrategy, Occurrence, DisplayOptions(..), skipStrategy, mapStrategy, constStrategy+                                     , noLongerThan +                                     )+import qualified Data.Series.Generic as G+import           Data.Vector.Unboxed ( Vector, Unbox )+import qualified Data.Vector.Unboxed as Vector++import           Prelude             hiding ( map, concatMap, zipWith, filter, foldMap, null, length, all, any, and, or+                                            , sum, product, maximum, minimum, take, takeWhile, drop, dropWhile+                                            , last, unzip, unzip3+                                            )++-- $setup+-- >>> import qualified Data.Series.Unboxed as Series+-- >>> import qualified Data.Series.Index as Index++infixl 1 `select` +infix 6 |->, <-|++-- | A series is a labeled array of values of type @a@,+-- indexed by keys of type @k@.+--+-- Like @Data.Map@ and @Data.HashMap@, they support efficient:+--+--      * random access by key ( \(O(\log n)\) );+--      * slice by key ( \(O(\log n)\) ).+--+-- Like @Data.Vector.Vector@, they support efficient:+--+--      * random access by index ( \(O(1)\) );+--      * slice by index ( \(O(1)\) );+--      * numerical operations.+type Series = G.Series Vector+++index :: Series k a -> Index k+{-# INLINABLE index #-}+index = G.index+++values :: Series k a -> Vector a+{-# INLINABLE values #-}+values = G.values+++-- | Create a 'Series' with a single element.+singleton :: Unbox a => k -> a -> Series k a+{-# INLINABLE singleton #-}+singleton = G.singleton+++-- | \(O(n)\) Generate a 'Series' by mapping every element of its index.+--+-- >>> fromIndex (const (0::Int)) $ Index.fromList ['a','b','c','d']+-- index | values+-- ----- | ------+--   'a' |      0+--   'b' |      0+--   'c' |      0+--   'd' |      0+fromIndex :: Unbox a+          => (k -> a) -> Index k -> Series k a+{-# INLINABLE fromIndex #-}+fromIndex = G.fromIndex+++-- | Construct a series from a list of key-value pairs. There is no+-- condition on the order of pairs.+--+-- >>> let xs = fromList [('b', 0::Int), ('a', 5), ('d', 1) ]+-- >>> xs+-- index | values+-- ----- | ------+--   'a' |      5+--   'b' |      0+--   'd' |      1+--+-- If you need to handle duplicate keys, take a look at `fromListDuplicates`.+fromList :: (Ord k, Unbox a) => [(k, a)] -> Series k a+{-# INLINABLE fromList #-}+fromList = G.fromList+++-- | Construct a series from a list of key-value pairs.+-- Contrary to `fromList`, values at duplicate keys are preserved. To keep each+-- key unique, an `Occurrence` number counts up.+--+-- >>> let xs = fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]+-- >>> xs+--   index | values+--   ----- | ------+-- ('a',0) |      5+-- ('b',0) |      0+-- ('d',0) |      1+-- ('d',1) |     -4+-- ('d',2) |      7+fromListDuplicates :: (Ord k, Unbox a) => [(k, a)] -> Series (k, Occurrence) a+{-# INLINABLE fromListDuplicates #-}+fromListDuplicates = G.fromListDuplicates+++-- | Construct a list from key-value pairs. The elements are in order sorted by key:+--+-- >>> let xs = Series.fromList [ ('b', 0::Int), ('a', 5), ('d', 1) ]+-- >>> xs+-- index | values+-- ----- | ------+--   'a' |      5+--   'b' |      0+--   'd' |      1+-- >>> toList xs+-- [('a',5),('b',0),('d',1)]+toList :: Unbox a => Series k a -> [(k, a)]+{-# INLINABLE toList #-}+toList = G.toList+++-- | Construct a 'Vector' of key-value pairs. The elements are in order sorted by key. +toVector :: (Unbox a, Unbox k) => Series k a -> Vector (k, a)+{-# INLINABLE toVector #-}+toVector = G.toVector+++-- | Construct a 'Series' from a 'Vector' of key-value pairs. There is no+-- condition on the order of pairs. Duplicate keys are silently dropped. If you+-- need to handle duplicate keys, see 'fromVectorDuplicates'.+--+-- Note that due to differences in sorting,+-- @Series.fromList@ and @Series.fromVector . Vector.fromList@ +-- may not be equivalent if the input list contains duplicate keys.+fromVector :: (Ord k, Unbox k, Unbox a)+           => Vector (k, a) -> Series k a+{-# INLINABLE fromVector #-}+fromVector = G.fromVector+++-- | Construct a series from a 'Vector' of key-value pairs.+-- Contrary to 'fromVector', values at duplicate keys are preserved. To keep each+-- key unique, an 'Occurrence' number counts up.+--+-- >>> import qualified Data.Vector.Unboxed as Unboxed+-- >>> let xs = fromVectorDuplicates $ Unboxed.fromList [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ]+-- >>> xs+--   index | values+--   ----- | ------+-- ('a',0) |      5+-- ('b',0) |      0+-- ('d',0) |      1+-- ('d',1) |     -4+-- ('d',2) |      7+fromVectorDuplicates :: (Unbox k, Unbox a, Ord k) => Vector (k, a) -> Series (k, Occurrence) a+{-# INLINABLE fromVectorDuplicates #-}+fromVectorDuplicates = G.fromVectorDuplicates+++-- | Convert a series into a lazy @Map@.+toLazyMap :: (Unbox a) => Series k a -> ML.Map k a+{-# INLINABLE toLazyMap #-}+toLazyMap = G.toLazyMap+++-- | Construct a series from a lazy @Map@.+fromLazyMap :: (Unbox a) => ML.Map k a -> Series k a+{-# INLINABLE fromLazyMap #-}+fromLazyMap = G.fromLazyMap+++-- | Convert a series into a strict @Map@.+toStrictMap :: (Unbox a) => Series k a -> MS.Map k a+{-# INLINABLE toStrictMap #-}+toStrictMap = G.toStrictMap++-- | Construct a series from a strict @Map@.+fromStrictMap :: (Unbox a) => MS.Map k a -> Series k a+{-# INLINABLE fromStrictMap #-}+fromStrictMap = G.fromStrictMap+++-- | \(O(n)\) Map every element of a 'Series'.+map :: (Unbox a, Unbox b) => (a -> b) -> Series k a -> Series k b+{-# INLINABLE map #-}+map = G.map+++-- | \(O(n)\) Map every element of a 'Series', possibly using the key as well.+mapWithKey :: (Unbox a, Unbox b) => (k -> a -> b) -> Series k a -> Series k b+{-# INLINABLE mapWithKey #-}+mapWithKey = G.mapWithKey+++-- | \(O(n \log n)\).+-- Map each key in the index to another value. Note that the resulting series+-- may have less elements, because each key must be unique.+--+-- In case new keys are conflicting, the first element is kept.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> import qualified Data.List+-- >>> xs `mapIndex` (Data.List.take 1)+-- index | values+-- ----- | ------+--   "L" |      4+--   "P" |      1+mapIndex :: (Unbox a, Ord k, Ord g) => Series k a -> (k -> g) -> Series g a+{-# INLINABLE mapIndex #-}+mapIndex = G.mapIndex+++-- | Map a function over all the elements of a 'Series' and concatenate the result into a single 'Series'.+concatMap :: (Unbox a, Unbox k, Unbox b, Ord k) +          => (a -> Series k b) +          -> Series k a +          -> Series k b+{-# INLINABLE concatMap #-}+concatMap = G.concatMap+++-- | \(O(n)\) Apply the monadic action to every element of a series and its+-- index, yielding a series of results.+mapWithKeyM :: (Unbox a, Unbox b, Monad m, Ord k) => (k -> a -> m b) -> Series k a -> m (Series k b)+{-# INLINABLE mapWithKeyM #-}+mapWithKeyM = G.mapWithKeyM+++-- | \(O(n)\) Apply the monadic action to every element of a series and its+-- index, discarding the results.+mapWithKeyM_ :: (Unbox a, Monad m) => (k -> a -> m b) -> Series k a -> m ()+{-# INLINABLE mapWithKeyM_ #-}+mapWithKeyM_ = G.mapWithKeyM_+++-- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, +-- yielding a series of results.+forWithKeyM :: (Unbox a, Unbox b, Monad m, Ord k) => Series k a -> (k -> a -> m b) -> m (Series k b)+{-# INLINABLE forWithKeyM #-}+forWithKeyM = G.forWithKeyM+++-- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, +-- discarding the results.+forWithKeyM_ :: (Unbox a, Monad m) => Series k a -> (k -> a -> m b) -> m ()+{-# INLINABLE forWithKeyM_ #-}+forWithKeyM_ = G.forWithKeyM_+++-- | \(O(\log n)\) @'take' n xs@ returns at most @n@ elements of the 'Series' @xs@.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- "Vienna" |      5+-- >>> take 2 xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+take :: Unbox a => Int -> Series k a -> Series k a+{-# INLINABLE take #-}+take = G.take+++-- | \(O(n)\) Returns the longest prefix (possibly empty) of the input 'Series' that satisfy a predicate.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- "Vienna" |      5++-- >>> takeWhile (>1) xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+takeWhile :: Unbox a => (a -> Bool) -> Series k a -> Series k a+takeWhile = G.takeWhile+++-- | \(O(\log n)\) @'drop' n xs@ drops at most @n@ elements from the 'Series' @xs@.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- "Vienna" |      5+-- >>> drop 2 xs+--    index | values+--    ----- | ------+--  "Paris" |      1+-- "Vienna" |      5+drop :: Unbox a => Int -> Series k a -> Series k a+{-# INLINABLE drop #-}+drop = G.drop+++-- | \(O(n)\) Returns the complement of `takeWhile`.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- "Vienna" |      5++-- >>> dropWhile (>1) xs+--    index | values+--    ----- | ------+--  "Paris" |      1+-- "Vienna" |      5+dropWhile :: Unbox a => (a -> Bool) -> Series k a -> Series k a+dropWhile = G.dropWhile+++-- | Apply a function elementwise to two series, matching elements+-- based on their keys. Keys present only in the left or right series are dropped.+--+-- >>> let xs = Series.fromList [ ('a', 0::Int),  ('b', 1),  ('g', 2) ]+-- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11), ('d', 13) ]+-- >>> zipWithMatched (+) xs ys+-- index | values+-- ----- | ------+--   'a' |     10+--   'b' |     12+zipWithMatched :: (Unbox a, Unbox b, Unbox c, Ord k) +               => (a -> b -> c) -> Series k a -> Series k b -> Series k c+{-# INLINABLE zipWithMatched #-}+zipWithMatched = G.zipWithMatched+++-- | Apply a function elementwise to three series, matching elements+-- based on their keys. Keys not present in all three series are dropped.+--+-- >>> let xs = Series.fromList [ ('a', 0::Int),  ('b', 1),   ('g', 2) ]+-- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11),  ('d', 13) ]+-- >>> let zs = Series.fromList [ ('a', 20::Int), ('d', 13), ('e', 6) ]+-- >>> zipWithMatched3 (\x y z -> x + y + z) xs ys zs+-- index | values+-- ----- | ------+--   'a' |     30+zipWithMatched3 :: (Unbox a, Unbox b, Unbox c, Unbox d, Ord k) +                => (a -> b -> c -> d) +                -> Series k a +                -> Series k b +                -> Series k c+                -> Series k d+{-# INLINABLE zipWithMatched3 #-}+zipWithMatched3 = G.zipWithMatched3+++-- | Apply a function elementwise to two series, matching elements+-- based on their keys. Keys present only in the left or right series are dropped.+-- +--+-- >>> import Data.Char ( ord )+-- >>> let xs = Series.fromList [ ('a', 0::Int), ('b', 1), ('c', 2) ]+-- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11), ('d', 13) ]+-- >>> zipWithKey (\k x y -> ord k + x + y) xs ys+-- index | values+-- ----- | ------+--   'a' |    107+--   'b' |    110+zipWithKey :: (Unbox a, Unbox b, Unbox c, Unbox k, Ord k)  +           => (k -> a -> b -> c) -> Series k a -> Series k b -> Series k c+{-# INLINABLE zipWithKey #-}+zipWithKey = G.zipWithKey+++-- | Apply a function elementwise to three series, matching elements+-- based on their keys. Keys present only in the left or right series are dropped.+-- +-- >>> import Data.Char ( ord )+-- >>> let xs = Series.fromList [ ('a', 0::Int), ('b', 1), ('g', 2) ]+-- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11), ('d', 13) ]+-- >>> let zs = Series.fromList [ ('a', 20::Int), ('b', 7), ('d', 5) ]+-- >>> zipWithKey3 (\k x y z -> ord k + x + y + z) xs ys zs+-- index | values+-- ----- | ------+--   'a' |    127+--   'b' |    117+zipWithKey3 :: (Unbox a, Unbox b, Unbox c, Unbox d, Unbox k, Ord k) +            => (k -> a -> b -> c -> d) +            -> Series k a +            -> Series k b +            -> Series k c+            -> Series k d+{-# INLINABLE zipWithKey3 #-}+zipWithKey3 = G.zipWithKey3+++-- | Zip two 'Series' with a combining function, applying a 'ZipStrategy' when one key is present in one of the 'Series' but not both.+--+-- In the example below, we want to set the value to @-100@ (via @'constStrategy' (-100)@) for keys which are only present +-- in the left 'Series', and drop keys (via 'skipStrategy') which are only present in the `right 'Series'  +--+-- >>> let xs = Series.fromList [ ('a', 0::Int),  ('b', 1),  ('g', 2) ]+-- >>> let ys = Series.fromList [ ('a', 10::Int), ('b', 11), ('d', 13) ]+-- >>> zipWithStrategy (+) (constStrategy (-100)) skipStrategy  xs ys+-- index | values+-- ----- | ------+--   'a' |     10+--   'b' |     12+--   'g' |   -100+--+-- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched' f@ +-- than using @'zipWithStrategy' f 'skipStrategy' 'skipStrategy'@.+zipWithStrategy :: (Ord k, Unbox a, Unbox b, Unbox c) +                => (a -> b -> c)     -- ^ Function to combine values when present in both series+                -> ZipStrategy k a c -- ^ Strategy for when the key is in the left series but not the right+                -> ZipStrategy k b c -- ^ Strategy for when the key is in the right series but not the left+                -> Series k a+                -> Series k b +                -> Series k c+{-# INLINABLE zipWithStrategy #-}+zipWithStrategy = G.zipWithStrategy+++-- | Zip three 'Series' with a combining function, applying a 'ZipStrategy' when one key is +-- present in one of the 'Series' but not all of the others.+--+-- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched3' f@ +-- than using @'zipWithStrategy3' f skipStrategy skipStrategy skipStrategy@.+zipWithStrategy3 :: (Ord k, Unbox a, Unbox b, Unbox c, Unbox d) +                => (a -> b -> c -> d) -- ^ Function to combine values when present in all series+                -> ZipStrategy k a d  -- ^ Strategy for when the key is in the left series but not in all the others+                -> ZipStrategy k b d  -- ^ Strategy for when the key is in the center series but not in all the others+                -> ZipStrategy k c d  -- ^ Strategy for when the key is in the right series but not in all the others+                -> Series k a+                -> Series k b +                -> Series k c+                -> Series k d+zipWithStrategy3 = G.zipWithStrategy3+{-# INLINABLE zipWithStrategy3 #-}+++-- | Zip two 'Series' with a combining function. The value for keys which are missing from+-- either 'Series' is replaced with the appropriate `mempty` value.+--+-- >>> import Data.Monoid ( Sum(..) )+-- >>> let xs = Series.fromList [ ("2023-01-01", Sum (1::Int)), ("2023-01-02", Sum 2) ]+-- >>> let ys = Series.fromList [ ("2023-01-01", Sum (5::Int)), ("2023-01-03", Sum 7) ]+-- >>> zipWithMonoid (<>) xs ys+--        index |           values+--        ----- |           ------+-- "2023-01-01" | Sum {getSum = 6}+-- "2023-01-02" | Sum {getSum = 2}+-- "2023-01-03" | Sum {getSum = 7}+zipWithMonoid :: ( Monoid a, Monoid b+                 , Unbox a, Unbox b, Unbox c+                 , Ord k+                 ) +              => (a -> b -> c)+              -> Series k a+              -> Series k b +              -> Series k c+zipWithMonoid = G.zipWithMonoid+{-# INLINABLE zipWithMonoid #-}+++-- | Elementwise sum of two 'Series'. Elements missing in one or the other 'Series' is considered 0. +--+-- >>> let xs = Series.fromList [ ("2023-01-01", (1::Int)), ("2023-01-02", 2) ]+-- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]+-- >>> xs `esum` ys+--        index | values+--        ----- | ------+-- "2023-01-01" |      6+-- "2023-01-02" |      2+-- "2023-01-03" |      7+esum :: (Ord k, Num a, Unbox a) +     => Series k a +     -> Series k a+     -> Series k a+esum = G.esum+{-# INLINABLE esum #-}+++-- | Elementwise product of two 'Series'. Elements missing in one or the other 'Series' is considered 1. +--+-- >>> let xs = Series.fromList [ ("2023-01-01", (2::Int)), ("2023-01-02", 3) ]+-- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ]+-- >>> xs `eproduct` ys+--        index | values+--        ----- | ------+-- "2023-01-01" |     10+-- "2023-01-02" |      3+-- "2023-01-03" |      7+eproduct :: (Ord k, Num a, Unbox a) +         => Series k a +         -> Series k a+         -> Series k a+eproduct = G.eproduct+{-# INLINABLE eproduct #-}+++-- | \(O(n)\) Unzip a 'Series' of 2-tuples.+unzip :: (Unbox a, Unbox b) +      => Series k (a, b)+      -> ( Series k a+         , Series k b+         )+unzip = G.unzip+{-# INLINABLE unzip #-}+++-- | \(O(n)\) Unzip a 'Series' of 3-tuples.+unzip3 :: (Unbox a, Unbox b, Unbox c) +       => Series k (a, b, c)+       -> ( Series k a+          , Series k b+          , Series k c+          )+unzip3 = G.unzip3+{-# INLINABLE unzip3 #-}+++-- | Require a series to have a specific `Index`. +-- Contrary to @select@, all keys in the `Index` will be present in the resulting series.+--+-- Note that unlike the implementation for boxed series (`Data.Series.require`), missing keys need to be mapped to some values because unboxed+-- series cannot contain values of type @`Maybe` a@. +--+-- In the example below, the missing value for key @\"Taipei\"@ is mapped to 0:+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> require (const 0) xs (Index.fromList ["Paris", "Lisbon", "Taipei"])+--    index | values+--    ----- | ------+-- "Lisbon" |      4+--  "Paris" |      1+-- "Taipei" |      0+require :: (Unbox a, Ord k) +        => (k -> a) -> Series k a -> Index k -> Series k a+{-# INLINABLE require #-}+require f = G.requireWith f id+++-- | \(O(n)\) Drop the index of a series by replacing it with an `Int`-based index. Values will+-- be indexed from 0.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> dropIndex xs+-- index | values+-- ----- | ------+--     0 |      4+--     1 |      2+--     2 |      1+dropIndex :: Series k a -> Series Int a+{-# INLINABLE dropIndex #-}+dropIndex = G.dropIndex+++-- | Filter elements. Only elements for which the predicate is @True@ are kept. +-- Notice that the filtering is done on the values, not on the keys.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> filter (>2) xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+--+-- See also 'filterWithKey'.+filter :: (Unbox a, Ord k) => (a -> Bool) -> Series k a -> Series k a+{-# INLINABLE filter #-}+filter = G.filter+++-- | Filter elements, taking into account the corresponding key. Only elements for which +-- the predicate is @True@ are kept. +filterWithKey :: (Unbox a, Ord k) +              => (k -> a -> Bool) +              -> Series k a +              -> Series k a+{-# INLINABLE filterWithKey #-}+filterWithKey = G.filterWithKey+++-- | Select a subseries. There are a few ways to do this.+--+-- The first way to do this is to select a sub-series based on random keys. For example,+-- selecting a subseries from an `Index`:+--+-- >>> let xs = Series.fromList [('a', 10::Int), ('b', 20), ('c', 30), ('d', 40)]+-- >>> xs `select` Index.fromList ['a', 'd']+-- index | values+-- ----- | ------+--   'a' |     10+--   'd' |     40+--+-- The second way to select a sub-series is to select all keys in a range:+--+-- >>> xs `select` 'b' `to` 'c'+-- index | values+-- ----- | ------+--   'b' |     20+--   'c' |     30+--+-- Note that with `select`, you'll always get a sub-series; if you ask for a key which is not+-- in the series, it'll be ignored:+--+-- >>> xs `select` Index.fromList ['a', 'd', 'e']+-- index | values+-- ----- | ------+--   'a' |     10+--   'd' |     40+--+-- See `require` if you want to ensure that all keys are present.+select :: (Unbox a, Selection s, Ord k) => Series k a -> s k -> Series k a+select = G.select+++-- | Select a sub-series from a series matching a condition.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> xs `selectWhere` (Series.map (>1) xs)+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+selectWhere :: (Unbox a, Ord k) => Series k a -> Series k Bool -> Series k a+{-# INLINABLE selectWhere #-}+selectWhere = G.selectWhere+++-- | \(O(\log n)\). Extract a single value from a series, by key.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs `at` "Paris"+-- Just 1+-- >>> xs `at` "Sydney"+-- Nothing+at :: (Unbox a, Ord k) => Series k a -> k -> Maybe a+{-# INLINABLE at #-}+at = G.at+++-- | \(O(1)\). Extract a single value from a series, by index.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> xs `iat` 0+-- Just 4+-- >>> xs `iat` 3+-- Nothing+iat :: Unbox a => Series k a -> Int -> Maybe a+{-# INLINABLE iat #-}+iat = G.iat+++-- | Replace values in the right series from values in the left series at matching keys.+-- Keys not in the right series are unaffected.+-- +-- See `(|->)` and `(<-|)`, which might be more readable.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> let ys = Series.singleton "Paris" (99::Int)+-- >>> ys `replace` xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |     99+replace :: (Unbox a, Ord k) => Series k a -> Series k a -> Series k a+{-# INLINABLE replace #-}+replace = G.replace+++-- | Replace values in the right series from values in the left series at matching keys.+-- Keys not in the right series are unaffected.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> let ys = Series.singleton "Paris" (99::Int)+-- >>> ys |-> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |     99+(|->) :: (Unbox a, Ord k) => Series k a -> Series k a -> Series k a+{-# INLINABLE (|->) #-}+(|->) = (G.|->)+++-- | Replace values in the left series from values in the right series at matching keys.+-- Keys not in the left series are unaffected.+--+-- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)]+-- >>> xs+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |      1+-- >>> let ys = Series.singleton "Paris" (99::Int)+-- >>> xs <-| ys+--    index | values+--    ----- | ------+-- "Lisbon" |      4+-- "London" |      2+--  "Paris" |     99+(<-|) :: (Unbox a, Ord k) => Series k a -> Series k a -> Series k a+{-# INLINABLE (<-|) #-}+(<-|) = (G.<-|)+++-- | \(O(n)\) Execute a 'Fold' over a 'Series'.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Double+-- >>> xs+-- index | values+-- ----- | ------+--     0 |    1.0+--     1 |    2.0+--     2 |    3.0+--     3 |    4.0+-- >>> import Control.Foldl (variance)+-- >>> fold variance xs+-- 1.25+--+-- See also 'foldM' for monadic folds, and 'foldWithKey' to take keys into+-- account while folding.+fold :: Unbox a +     => Fold a b -> Series k a -> b+fold = G.fold+{-# INLINABLE fold #-}+++-- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series'.+--+-- See also 'fold' for pure folds, and 'foldMWithKey' to take keys into+-- account while folding.+foldM :: (Monad m, Unbox a) +      => FoldM m a b  +      -> Series k a +      -> m b+foldM = G.foldM+{-# INLINABLE foldM #-}+++-- | \(O(n)\) Execute a 'Fold' over a 'Series', taking keys into account.+foldWithKey :: (Unbox k, Unbox a) +            => Fold (k, a) b -> Series k a -> b+foldWithKey = G.foldWithKey+{-# INLINABLE foldWithKey #-}+++-- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series', where the 'FoldM' takes keys into account.+foldMWithKey :: (Monad m, Unbox a, Unbox k) +             => FoldM m (k, a) b  +             -> Series k a +             -> m b+foldMWithKey = G.foldMWithKey+{-# INLINABLE foldMWithKey #-}+++-- | \(O(n)\) Map each element of the structure to a monoid and combine+-- the results.+foldMap :: (Monoid m, Unbox a) => (a -> m) -> Series k a -> m+{-# INLINABLE foldMap #-}+foldMap = G.foldMap+++-- | \(O(n)\) Like 'foldMap', but strict in the accumulator. It uses the same+-- implementation as the corresponding method of the 'Foldable' type class.+foldMap' :: (Monoid m, Unbox a) => (a -> m) -> Series k a -> m+{-# INLINABLE foldMap' #-}+foldMap' f = Vector.foldMap' f . values+++-- | \(O(n)\) Map each element and associated key of the structure to a monoid and combine+-- the results.+foldMapWithKey :: (Monoid m, Unbox a, Unbox k) => (k -> a -> m) -> Series k a -> m+{-# INLINABLE foldMapWithKey #-}+foldMapWithKey = G.foldMapWithKey+++-- | Group values in a 'Series' by some grouping function (@k -> g@).+-- The provided grouping function is guaranteed to operate on a non-empty 'Series'.+--+-- This function is expected to be used in conjunction with @aggregate@:+-- +-- >>> import Data.Maybe ( fromMaybe )+-- >>> type Date = (Int, String)+-- >>> month :: (Date -> String) = snd+-- >>> :{ +--     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)+--                              , ((2021, "January"), -5)+--                              , ((2020, "June")   , 20)+--                              , ((2021, "June")   , 25) +--                              ]+--      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)+-- :}+--     index | values+--     ----- | ------+-- "January" |     -5+--    "June" |     20+groupBy :: Series k a      -- ^ Grouping function+        -> (k -> g)        -- ^ Input series+        -> Grouping k g a  -- ^ Grouped series+{-# INLINABLE groupBy #-}+groupBy = G.groupBy+++-- | Representation of a 'Series' being grouped.+type Grouping k g a = G.Grouping k g Vector a+++-- | Aggregate groups resulting from a call to 'groupBy':+-- +-- >>> import Data.Maybe ( fromMaybe )+-- >>> type Date = (Int, String)+-- >>> month :: (Date -> String) = snd+-- >>> :{ +--     let xs = Series.fromList [ ((2020, "January") :: Date,  0 :: Int)+--                              , ((2021, "January"), -5)+--                              , ((2020, "June")   , 20)+--                              , ((2021, "June")   , 25) +--                              ]+--      in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum)+-- :}+--     index | values+--     ----- | ------+-- "January" |     -5+--    "June" |     20+--+-- If you want to aggregate groups using a binary function, see 'foldWith' which+-- may be much faster.+aggregateWith :: (Ord g, Unbox a, Unbox b) +              => Grouping k g a +              -> (Series k a -> b) +              -> Series g b+{-# INLINABLE aggregateWith #-}+aggregateWith = G.aggregateWith+++-- | Aggregate each group in a 'Grouping' using a binary function.+-- While this is not as expressive as 'aggregateWith', users looking for maximum+-- performance should use 'foldWith' as much as possible.+foldWith :: (Ord g, Unbox a) +         => Grouping k g a+         -> (a -> a -> a)+         -> Series g a+{-# INLINABLE foldWith #-}+foldWith = G.foldWith+++-- | Expanding window aggregation.+--+-- >>> :{ +--     let (xs :: Series Int Int) +--          = fromList [ (1, 0)+--                     , (2, 1)+--                     , (3, 2)+--                     , (4, 3)+--                     , (5, 4)+--                     , (6, 5)+--                     ]+--     in (xs `expanding` sum) :: Series Int Int +-- :}+-- index | values+-- ----- | ------+--     1 |      0+--     2 |      1+--     3 |      3+--     4 |      6+--     5 |     10+--     6 |     15+expanding :: (Unbox a, Unbox b) +          => Series k a        -- ^ Series vector+          -> (Series k a -> b) -- ^ Aggregation function+          -> Series k b        -- ^ Resulting vector+{-# INLINABLE expanding #-}+expanding = G.expanding+++-- | General-purpose window aggregation.+--+-- >>> :{ +--     let (xs :: Series.Series Int Int) +--          = Series.fromList [ (1, 0)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 3)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in windowing (\k -> k `to` (k+2)) sum xs+-- :}+-- index | values+-- ----- | ------+--     1 |      3+--     2 |      6+--     3 |      9+--     4 |     12+--     5 |      9+--     6 |      5+windowing :: (Ord k, Unbox a, Unbox b)+          => (k -> Range k)+          -> (Series k a -> b)+          -> Series k a+          -> Series k b+{-# INLINABLE windowing #-}+windowing = G.windowing +++-- | \(O(1)\) Test whether a 'Series' is empty.+null :: Unbox a => Series k a -> Bool+{-# INLINABLE null #-}+null = G.null+++-- |\(O(1)\) Extract the length of a 'Series'.+length :: Unbox a => Series k a -> Int+{-# INLINABLE length #-}+length = G.length+++-- | \(O(n)\) Check if all elements satisfy the predicate.+all :: Unbox a => (a -> Bool) -> Series k a -> Bool+{-# INLINABLE all #-}+all = G.all+++-- | \(O(n)\) Check if any element satisfies the predicate.+any :: Unbox a => (a -> Bool) -> Series k a -> Bool+{-# INLINABLE any #-}+any = G.any+++-- | \(O(n)\) Check if all elements are 'True'.+and :: Series k Bool -> Bool+{-# INLINABLE and #-}+and = G.and+++-- | \(O(n)\) Check if any element is 'True'.+or :: Series k Bool -> Bool+{-# INLINABLE or #-}+or = G.or+++-- | \(O(n)\) Compute the sum of the elements.+sum :: (Unbox a, Num a) => Series k a -> a+{-# INLINABLE sum #-}+sum = G.sum+++-- | \(O(n)\) Compute the product of the elements.+product :: (Unbox a, Num a) => Series k a -> a+{-# INLINABLE product #-}+product = G.product+++-- | \(O(n)\) Yield the maximum element of the series. In case of a tie, the first occurrence wins.+-- If the 'Series' is empty, @Nothing@ is returned.+--+-- See also 'argmax'.+maximum :: (Ord a, Unbox a) => Series k a -> Maybe a+{-# INLINABLE maximum #-}+maximum = G.maximum+++-- | \(O(n)\) @'maximumOn' f xs@ teturns the maximum element of the series @xs@, as determined by the function @f@.+-- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned.+maximumOn :: (Ord b, Unbox a) => (a -> b) -> Series k a -> Maybe a+{-# INLINABLE maximumOn #-}+maximumOn = G.maximumOn+++-- | \(O(n)\) Yield the minimum element of the series. In case of a tie, the first occurrence wins.+-- If the 'Series' is empty, @Nothing@ is returned.+--+-- See also 'argmin'.+minimum :: (Ord a, Unbox a) => Series k a -> Maybe a+{-# INLINABLE minimum #-}+minimum = G.minimum+++-- | \(O(n)\) @'minimumOn' f xs@ teturns the minimum element of the series @xs@, as determined by the function @f@.+-- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned.+minimumOn :: (Ord b, Unbox a) => (a -> b) -> Series k a -> Maybe a+{-# INLINABLE minimumOn #-}+minimumOn = G.minimumOn+++-- | \(O(n)\) Find the index of the maximum element in the input series.+-- If the input series is empty, 'Nothing' is returned.+--+-- The index of the first occurrence of the maximum element is returned.+--+-- >>> import qualified Data.Series.Unboxed as Series +-- >>> :{ +--     let (xs :: Series.Series Int Int) +--          = Series.fromList [ (1, 0)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 7)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in argmax xs +-- :}+-- Just 4+argmax :: (Ord a, Unbox a)+       => Series k a+       -> Maybe k+argmax = G.argmax+{-# INLINABLE argmax #-}+++-- | \(O(n)\) Find the index of the minimum element in the input series.+-- If the input series is empty, 'Nothing' is returned.+--+-- The index of the first occurrence of the minimum element is returned.+-- >>> import qualified Data.Series.Unboxed as Series +-- >>> :{ +--     let (xs :: Series.Series Int Int) +--          = Series.fromList [ (1, 1)+--                            , (2, 1)+--                            , (3, 2)+--                            , (4, 0)+--                            , (5, 4)+--                            , (6, 5)+--                            ]+--     in argmin xs +-- :}+-- Just 4+argmin :: (Ord a, Unbox a)+       => Series k a+       -> Maybe k+argmin = G.argmin+{-# INLINABLE argmin #-}+++-- | \(O(n)\) Left-to-right postscan.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int+-- >>> xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      3+--     3 |      4+-- >>> postscanl (+) 0 xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      3+--     2 |      6+--     3 |     10+postscanl :: (Unbox a, Unbox b) +          => (a -> b -> a) -> a -> Series k b -> Series k a+{-# INLINABLE postscanl #-}+postscanl = G.postscanl+++-- | \(O(n)\) Left-to-right prescan.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int+-- >>> xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      3+--     3 |      4+-- >>> prescanl (+) 0 xs+-- index | values+-- ----- | ------+--     0 |      0+--     1 |      1+--     2 |      3+--     3 |      6+prescanl :: (Unbox a, Unbox b) +         => (a -> b -> a) -> a -> Series k b -> Series k a+{-# INLINABLE prescanl #-}+prescanl = G.prescanl+++-- | Display a 'Series' using default 'DisplayOptions'.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int+-- >>> putStrLn $ display xs+-- index | values+-- ----- | ------+--     0 |      1+--     1 |      2+--     2 |      3+--   ... |    ...+--     4 |      5+--     5 |      6+--     6 |      7+display :: (Unbox a, Show k, Show a) +        => Series k a +        -> String+display = G.display+++-- | Display a 'Series' using customizable 'DisplayOptions'.+--+-- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int+-- >>> import Data.List (replicate)+-- >>> :{+--     let opts = DisplayOptions { maximumNumberOfRows  = 4+--                               , indexHeader = "keys"+--                               , valuesHeader = "vals"+--                               , keyDisplayFunction   = (\i -> replicate i 'x') `noLongerThan` 5+--                               , valueDisplayFunction = (\i -> replicate i 'o') +--                               }+--      in putStrLn $ displayWith opts xs+-- :}+--   keys |    vals+--  ----- |  ------+--        |       o+--      x |      oo+--    ... |     ...+--  xxxxx |  oooooo+-- xxx... | ooooooo+displayWith :: (Unbox a) +            => DisplayOptions k a+            -> Series k a +            -> String+displayWith = G.displayWith
test/Main.hs view
@@ -1,19 +1,19 @@-module Main (main) where
-
-import qualified Test.Data.Series
-import qualified Test.Data.Series.Generic.Aggregation
-import qualified Test.Data.Series.Generic.Definition
-import qualified Test.Data.Series.Index
-import qualified Test.Data.Series.Generic.View
-import qualified Test.Data.Series.Generic.Zip
-
-import           Test.Tasty ( defaultMain, testGroup )
-
-main :: IO ()
-main = defaultMain $ testGroup "Test suite" [ Test.Data.Series.tests
-                                            , Test.Data.Series.Index.tests
-                                            , Test.Data.Series.Generic.Aggregation.tests
-                                            , Test.Data.Series.Generic.Definition.tests
-                                            , Test.Data.Series.Generic.View.tests
-                                            , Test.Data.Series.Generic.Zip.tests
-                                            ]
+module Main (main) where++import qualified Test.Data.Series+import qualified Test.Data.Series.Generic.Aggregation+import qualified Test.Data.Series.Generic.Definition+import qualified Test.Data.Series.Index+import qualified Test.Data.Series.Generic.View+import qualified Test.Data.Series.Generic.Zip++import           Test.Tasty ( defaultMain, testGroup )++main :: IO ()+main = defaultMain $ testGroup "Test suite" [ Test.Data.Series.tests+                                            , Test.Data.Series.Index.tests+                                            , Test.Data.Series.Generic.Aggregation.tests+                                            , Test.Data.Series.Generic.Definition.tests+                                            , Test.Data.Series.Generic.View.tests+                                            , Test.Data.Series.Generic.Zip.tests+                                            ]
test/Test/Data/Series.hs view
@@ -1,7 +1,7 @@-
-module Test.Data.Series (tests) where
-
-import           Test.Tasty           ( testGroup, TestTree ) 
-
-tests :: TestTree
++module Test.Data.Series (tests) where++import           Test.Tasty           ( testGroup, TestTree ) ++tests :: TestTree tests = testGroup "Data.Series" []
test/Test/Data/Series/Generic/Aggregation.hs view
@@ -1,134 +1,153 @@-
-module Test.Data.Series.Generic.Aggregation (tests) where
-
-import qualified Data.Map.Strict      as MS
-import qualified Data.Series.Generic  as Series
-import           Data.Series.Generic  ( Series, fromStrictMap, groupBy, aggregateWith, foldWith, windowing, to, expanding)
-import           Data.Vector          ( Vector )
-
-import           Hedgehog             ( property, forAll, (===) )
-import qualified Hedgehog.Gen         as Gen
-import qualified Hedgehog.Range       as Range
-
-import           Prelude              hiding ( zipWith )
-
-import           Test.Tasty           ( testGroup, TestTree )
-import           Test.Tasty.Hedgehog  ( testProperty )
-import           Test.Tasty.HUnit     ( testCase, assertEqual )
-
-tests :: TestTree
-tests = testGroup "Data.Series.Generic.Aggregation" [ testGroupBy
-                                                    , testWindowing
-                                                    , testWindowingRollingForwards
-                                                    , testWindowingRollingBackwards
-                                                    , testPropAggregateVsfoldWith
-                                                    , testExpanding
-                                                    ]
-
-
-testGroupBy :: TestTree
-testGroupBy = testGroup "Data.Series.Generic.groupBy" [ testGroupBy1, testGroupBy2 ]
-    where
-        testGroupBy1 = testCase "groupBy" $ do
-            let (series :: Series Vector String Int) = fromStrictMap $ MS.fromList [("aa", 1), ("ab", 2), ("c", 3), ("dc", 4), ("ae", 5)]
-                expectation = fromStrictMap $ MS.fromList [(1, 3), (2, 1+2+4+5)]
-            
-            assertEqual mempty expectation $ series `groupBy` length `aggregateWith` (Series.sum :: Series Vector String Int -> Int)
-
-        testGroupBy2 = testCase "groupBy" $ do
-            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList $ zip [0,1,2,3] [0,1,2,3]
-                expectation = fromStrictMap $ MS.fromList [(True, 0+2), (False, 1+3)]
-            
-            assertEqual mempty expectation $ series `groupBy` even `aggregateWith` (Series.sum :: Series Vector Int Int -> Int)
-
-
-
-testWindowing :: TestTree
-testWindowing = testCase "Data.Series.Generic.windowing" $ do
-
-    let (xs :: Series Vector Int Int) 
-         = Series.fromList [ (1, 0)
-                           , (2, 1)
-                           , (3, 2)
-                           , (4, 3)
-                           , (5, 4)
-                           , (6, 5)
-                           ]
-        expectation = Series.fromList [ (1, 3)
-                                      , (2, 6)
-                                      , (3, 9)
-                                      , (4, 12)
-                                      , (5, 9)
-                                      , (6, 5)
-                                      ]
-    assertEqual mempty expectation $ windowing (\k -> k `to` (k+2)) sum xs
-
-
-testWindowingRollingForwards :: TestTree
-testWindowingRollingForwards = testGroup "Data.Series.Generic.windowing" [ test1, test2 ]
-    where
-        test1 = testCase "rollingForwards" $ do
-            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
-                expectation = fromStrictMap $ MS.fromList [ (1, 1+2)
-                                                          , (2, 2+3)
-                                                          , (3, 3+4)
-                                                          , (4, 4+5)
-                                                          , (5, 5)
-                                                          ]
-            
-            assertEqual mempty expectation $ windowing (\k -> k `to` (k + 1)) (Series.sum :: Series Vector Int Int -> Int) series
-
-        test2 = testCase "rollingForwards" $ do
-            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
-                expectation = fromStrictMap $ MS.fromList [ (1, 1+2+3)
-                                                          , (2, 2+3+4)
-                                                          , (3, 3+4+5)
-                                                          , (4, 4+5)
-                                                          , (5, 5)
-                                                          ]
-            
-            assertEqual mempty expectation $ windowing (\k -> k `to` (k + 2)) (Series.sum :: Series Vector Int Int -> Int) series
-
-
-testWindowingRollingBackwards :: TestTree
-testWindowingRollingBackwards = testGroup "Data.Series.Generic.windowing" [ test1, test2 ]
-    where
-        test1 = testCase "rollingForwards" $ do
-            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
-                expectation = fromStrictMap $ MS.fromList [ (1, 1)
-                                                          , (2, 1+2)
-                                                          , (3, 2+3)
-                                                          , (4, 3+4)
-                                                          , (5, 4+5)
-                                                          ]
-            
-            assertEqual mempty expectation $ windowing (\k -> (k-1) `to` k) (Series.sum :: Series Vector Int Int -> Int) series
-
-        test2 = testCase "rollingForwards" $ do
-            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
-                expectation = fromStrictMap $ MS.fromList [ (1, 1)
-                                                          , (2, 1+2)
-                                                          , (3, 1+2+3)
-                                                          , (4, 2+3+4)
-                                                          , (5, 3+4+5)
-                                                          ]
-            
-            assertEqual mempty expectation $ windowing (\k -> (k-2) `to` k)  (Series.sum :: Series Vector Int Int -> Int) series
-
-
-testPropAggregateVsfoldWith :: TestTree
-testPropAggregateVsfoldWith 
-    = testProperty "check that groupBy and testWindowingRollingForwards are equivalent" $ property $ do
-        ms <- forAll $ Gen.list (Range.linear 0 100) (Gen.int $ Range.linear (-500) 500) 
-        let (xs :: Series Vector Int Int) = Series.fromList (zip [0::Int ..] ms)
-
-        xs `groupBy` (`mod` 5) `aggregateWith` (Series.sum :: Series Vector Int Int -> Int) === xs `groupBy` (`mod` 5) `foldWith` (+)
-
-
-testExpanding :: TestTree
-testExpanding = testCase "expanding" $ do
-    let (xs :: Series Vector Char Int) = Series.fromList $ zip ['a', 'b', 'c', 'd'] [1::Int,2,3,4]
-        rs = xs `expanding` Series.sum
-        expectation = Series.fromList $ zip ['a', 'b', 'c', 'd'] [1,1+2,1+2+3,1+2+3+4]
-    
++module Test.Data.Series.Generic.Aggregation (tests) where++import qualified Data.IntMap.Strict   as IS+import qualified Data.Map.Strict      as MS+import qualified Data.Series.Generic  as Series+import           Data.Series.Generic  ( Series, fromStrictMap, groupBy, aggregateWith, foldWith, windowing, to, expanding)+import           Data.Vector          ( Vector )+import qualified Data.Vector          as Vector++import           Hedgehog             ( property, forAll, (===) )+import qualified Hedgehog.Gen         as Gen+import qualified Hedgehog.Range       as Range++import           Prelude              hiding ( zipWith )++import           Test.Tasty           ( testGroup, TestTree )+import           Test.Tasty.Hedgehog  ( testProperty )+import           Test.Tasty.HUnit     ( testCase, assertEqual )++tests :: TestTree+tests = testGroup "Data.Series.Generic.Aggregation" [ testGroupBy+                                                    , testWindowing+                                                    , testWindowingRollingForwards+                                                    , testWindowingRollingBackwards+                                                    , testPropAggregateVsfoldWith+                                                    , testExpanding+                                                    ]+++testGroupBy :: TestTree+testGroupBy = testGroup "Data.Series.Generic.groupBy" [ testGroupBy1, testGroupBy2, testGroupBy3, testGroupBy4 ]+    where+        testGroupBy1 = testCase "groupBy" $ do+            let (series :: Series Vector String Int) = fromStrictMap $ MS.fromList [("aa", 1), ("ab", 2), ("c", 3), ("dc", 4), ("ae", 5)]+                expectation = fromStrictMap $ MS.fromList [(1, 3), (2, 1+2+4+5)]+            +            assertEqual mempty expectation $ series `groupBy` length `aggregateWith` (Series.sum :: Series Vector String Int -> Int)++        testGroupBy2 = testCase "groupBy" $ do+            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList $ zip [0,1,2,3] [0,1,2,3]+                expectation = fromStrictMap $ MS.fromList [(True, 0+2), (False, 1+3)]+            +            assertEqual mempty expectation $ series `groupBy` even `aggregateWith` (Series.sum :: Series Vector Int Int -> Int)++        -- The following example resulted in an exception+        -- when the implementation of `aggregateWith` didn't aggregate keys in the+        -- right order+        testGroupBy3 = testCase "groupBy" $ do+            let (series :: Series Vector (Int, Int) Int) = fromStrictMap $ MS.fromList [ ((0, 0), 1), ((0, 1), 2) ]+                expectation = fromStrictMap $ MS.fromList [(0, IS.fromList [(0, 1), (1, 2)])]+            +            assertEqual mempty expectation $ series `groupBy` fst `aggregateWith` (IS.fromList . Series.toList . flip Series.mapIndex snd)++        testGroupBy4 = testCase "groupBy" $ do+            let (series :: Series Vector (Int, Int) Int) = fromStrictMap $ MS.fromList $ zip (zip [0,1,2,3,4,5] [1,1,2,2,3,3]) [2,1,2,1,2,1]+                expectation = fromStrictMap $ MS.fromList [ (1, Vector.fromList [2,1])+                                                          , (2, Vector.fromList [2,1])+                                                          , (3, Vector.fromList [2,1])+                                                          ]+            +            assertEqual mempty expectation $ series `groupBy` snd `aggregateWith` (Series.values)+++testWindowing :: TestTree+testWindowing = testCase "Data.Series.Generic.windowing" $ do++    let (xs :: Series Vector Int Int) +         = Series.fromList [ (1, 0)+                           , (2, 1)+                           , (3, 2)+                           , (4, 3)+                           , (5, 4)+                           , (6, 5)+                           ]+        expectation = Series.fromList [ (1, 3)+                                      , (2, 6)+                                      , (3, 9)+                                      , (4, 12)+                                      , (5, 9)+                                      , (6, 5)+                                      ]+    assertEqual mempty expectation $ windowing (\k -> k `to` (k+2)) sum xs+++testWindowingRollingForwards :: TestTree+testWindowingRollingForwards = testGroup "Data.Series.Generic.windowing" [ test1, test2 ]+    where+        test1 = testCase "rollingForwards" $ do+            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]+                expectation = fromStrictMap $ MS.fromList [ (1, 1+2)+                                                          , (2, 2+3)+                                                          , (3, 3+4)+                                                          , (4, 4+5)+                                                          , (5, 5)+                                                          ]+            +            assertEqual mempty expectation $ windowing (\k -> k `to` (k + 1)) (Series.sum :: Series Vector Int Int -> Int) series++        test2 = testCase "rollingForwards" $ do+            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]+                expectation = fromStrictMap $ MS.fromList [ (1, 1+2+3)+                                                          , (2, 2+3+4)+                                                          , (3, 3+4+5)+                                                          , (4, 4+5)+                                                          , (5, 5)+                                                          ]+            +            assertEqual mempty expectation $ windowing (\k -> k `to` (k + 2)) (Series.sum :: Series Vector Int Int -> Int) series+++testWindowingRollingBackwards :: TestTree+testWindowingRollingBackwards = testGroup "Data.Series.Generic.windowing" [ test1, test2 ]+    where+        test1 = testCase "rollingForwards" $ do+            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]+                expectation = fromStrictMap $ MS.fromList [ (1, 1)+                                                          , (2, 1+2)+                                                          , (3, 2+3)+                                                          , (4, 3+4)+                                                          , (5, 4+5)+                                                          ]+            +            assertEqual mempty expectation $ windowing (\k -> (k-1) `to` k) (Series.sum :: Series Vector Int Int -> Int) series++        test2 = testCase "rollingForwards" $ do+            let (series :: Series Vector Int Int) = fromStrictMap $ MS.fromList [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]+                expectation = fromStrictMap $ MS.fromList [ (1, 1)+                                                          , (2, 1+2)+                                                          , (3, 1+2+3)+                                                          , (4, 2+3+4)+                                                          , (5, 3+4+5)+                                                          ]+            +            assertEqual mempty expectation $ windowing (\k -> (k-2) `to` k)  (Series.sum :: Series Vector Int Int -> Int) series+++testPropAggregateVsfoldWith :: TestTree+testPropAggregateVsfoldWith +    = testProperty "check that groupBy and testWindowingRollingForwards are equivalent" $ property $ do+        ms <- forAll $ Gen.list (Range.linear 0 100) (Gen.int $ Range.linear (-500) 500) +        let (xs :: Series Vector Int Int) = Series.fromList (zip [0::Int ..] ms)++        xs `groupBy` (`mod` 5) `aggregateWith` (Series.sum :: Series Vector Int Int -> Int) === xs `groupBy` (`mod` 5) `foldWith` (+)+++testExpanding :: TestTree+testExpanding = testCase "expanding" $ do+    let (xs :: Series Vector Char Int) = Series.fromList $ zip ['a', 'b', 'c', 'd'] [1::Int,2,3,4]+        rs = xs `expanding` Series.sum+        expectation = Series.fromList $ zip ['a', 'b', 'c', 'd'] [1,1+2,1+2+3,1+2+3+4]+         assertEqual mempty expectation rs
test/Test/Data/Series/Generic/Definition.hs view
@@ -1,206 +1,206 @@-
-module Test.Data.Series.Generic.Definition (tests) where
-
-import qualified Control.Foldl        as Fold
-import           Data.Function        ( on )
-import           Data.Functor.Identity ( Identity(..))
-import           Data.List            ( nubBy, sortOn )
-import qualified Data.Map.Strict      as MS
-import qualified Data.Map.Lazy        as ML
-import           Data.Series.Generic  ( Series, Occurrence, fromStrictMap, toStrictMap, fromLazyMap, toLazyMap, fromList, toList, fromVector, toVector )
-import qualified Data.Series.Generic  as Series
-import           Data.Vector          ( Vector )
-import qualified Data.Vector          as Vector
-
-import           Hedgehog             ( property, forAll, (===), tripping )
-import qualified Hedgehog.Gen         as Gen
-import qualified Hedgehog.Range       as Range
-
-import           Test.Tasty           ( testGroup, TestTree ) 
-import           Test.Tasty.Hedgehog  ( testProperty )
-import           Test.Tasty.HUnit     ( testCase, assertEqual )
-
-tests :: TestTree
-tests = testGroup "Data.Series.Generic.Definition" 
-    [ testMappend
-    , testPropMappendLikeMap
-    , testPropShow
-    , testFromStrictMap
-    , testToStrictMap
-    , testPropRoundtripConversionWithStrictMap
-    , testPropRoundtripConversionWithLazyMap
-    , testPropRoundtripConversionWithList
-    , testPropFromListDuplicatesNeverDrops
-    , testPropFromVectorDuplicatesNeverDrops
-    , testPropFromVectorDuplicatesAndFromListDuplicatesHaveSameOrder
-    , testPropRoundtripConversionWithVector
-    , testPropVectorVsList
-    , testFromLazyMap
-    , testToLazyMap
-    , testTakeWhile
-    , testDropWhile
-    , testFold
-    ]
-
-
-testMappend :: TestTree
-testMappend = testCase "(<>)" $ do
-    let (s1 :: Series Vector Char Int) = fromList [('a', 1), ('b', 5)]
-        (s2 :: Series Vector Char Int) = fromList [('b', 10), ('x', 25)]
-        expectation = fromList [('a', 1), ('b', 5),  ('x', 25)]
-    
-    assertEqual mempty expectation (s1 <> s2)
-
-
-testPropMappendLikeMap :: TestTree
-testPropMappendLikeMap 
-    = testProperty "Mappend property similar to Data.Map.Strict" $ property $ do
-        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.int (Range.linear 0 1000)   <*> Gen.alpha)
-        m2 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.int (Range.linear 500 1500) <*> Gen.alpha)
-
-        (fromStrictMap :: MS.Map Int Char -> Series Vector Int Char) (m1 <> m2) === fromStrictMap m1 <> fromStrictMap m2
-
-
-testPropShow :: TestTree
-testPropShow
-    = testProperty "Show is never too long" $ property $ do
-        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.int (Range.linear 0 1000)   <*> Gen.alpha)
-
-        let (xs :: Series Vector Int Char) = fromStrictMap m1
-            ls = lines $ show xs
-        if Series.length xs > 6
-            then length ls === 2 + 6 + 1
-            else length ls === 2 + Series.length xs
-
-
-testFromStrictMap :: TestTree
-testFromStrictMap = testCase "fromStrictMap" $ do
-    -- Note the duplicate input at key 'a', which should disappear
-    let input = MS.fromList [('b', 2), ('a', 1), ('a', 1)]
-        (series :: Series Vector Char Int) = fromStrictMap input
-        expectation = fromList [('a', 1), ('b', 2)]
-    
-    assertEqual mempty series expectation
-
-
-testToStrictMap :: TestTree
-testToStrictMap = testCase "toStrictMap" $ do
-    let input = MS.fromList [('b', 2), ('a', 1)]
-        (series :: Series Vector Char Int) = fromStrictMap input
-    
-    assertEqual mempty (toStrictMap series) input
-
-
-testPropRoundtripConversionWithStrictMap :: TestTree
-testPropRoundtripConversionWithStrictMap 
-    = testProperty "Roundtrip property with Data.Map.Strict" $ property $ do
-        ms <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-        tripping ms (fromStrictMap :: MS.Map Char Char -> Series Vector Char Char) (Just . toStrictMap)
-
-
-testPropRoundtripConversionWithLazyMap :: TestTree
-testPropRoundtripConversionWithLazyMap 
-    = testProperty "Roundtrip property with Data.Map.Lazy" $ property $ do
-        ms <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-        tripping (ML.fromDistinctAscList $ MS.toAscList ms) (fromLazyMap :: MS.Map Char Char -> Series Vector Char Char) (Just . toLazyMap)
-
-
-testPropRoundtripConversionWithList :: TestTree
-testPropRoundtripConversionWithList 
-    = testProperty "Roundtrip property with List" $ property $ do
-        xs <- forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-50) 50) <*> Gen.alpha)
-
-        -- The property below needs some explanation.
-        -- In case of conflicting keys, a Series will be biased like a Map. Therefore,
-        -- the expected List won't have duplicated (hence the use of nubBy), but the elements which
-        -- are kept are in the order of `reverse xs`.
-        (toList :: Series Vector Int Char -> [(Int, Char)] ) (fromList xs) === sortOn fst (nubBy (\left right -> fst left == fst right) (reverse xs))
-
-
-testPropFromListDuplicatesNeverDrops :: TestTree
-testPropFromListDuplicatesNeverDrops
-    = testProperty "fromListDuplicates never drops elements" $ property $ do
-        xs <- forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-10) 10) <*> Gen.alpha)
-        Series.length (Series.fromListDuplicates xs :: Series Vector (Int, Occurrence) Char) === length xs
-
-
-testPropFromVectorDuplicatesNeverDrops :: TestTree
-testPropFromVectorDuplicatesNeverDrops
-    = testProperty "fromVectorDuplicates never drops elements" $ property $ do
-        xs <- fmap Vector.fromList $ forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-10) 10) <*> Gen.alpha)
-        Series.length (Series.fromVectorDuplicates xs :: Series Vector (Int, Occurrence) Char) === length xs
-
-
-testPropFromVectorDuplicatesAndFromListDuplicatesHaveSameOrder :: TestTree
-testPropFromVectorDuplicatesAndFromListDuplicatesHaveSameOrder
-    = testProperty "fromVectorDuplicates and fromListDuplicates are equivalent" $ property $ do
-        xs <- fmap Vector.fromList $ forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-10) 10) <*> Gen.alpha)
-        Series.fromVectorDuplicates xs === Series.fromListDuplicates (Vector.toList xs)
-
-
-testPropRoundtripConversionWithVector :: TestTree
-testPropRoundtripConversionWithVector 
-    = testProperty "Roundtrip property with Vector" $ property $ do
-        xs <- forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-50) 50) <*> Gen.alpha)
-
-        let (srs :: Series Vector Int Char) = fromList xs
-        tripping srs toVector (Just . fromVector)
-
-
-testPropVectorVsList :: TestTree
-testPropVectorVsList 
-    = testProperty "building from a list or vector yields the same results" $ property $ do
-        xs <- forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-50) 50) <*> Gen.alpha)
-        -- Note that due to differences in sorting,
-        -- Series.fromList   and Series.fromVector . Vector.fromList 
-        -- are not equivalent if the input list contains duplicate keys.
-        let unique = nubBy ((==) `on` fst) xs 
-        (fromList unique :: Series Vector Int Char) === fromVector (Vector.fromList unique)
-
-
-testFromLazyMap :: TestTree
-testFromLazyMap = testCase "fromLazyMap" $ do
-    let input = ML.fromList [('b', 2), ('a', 1)]
-        (series :: Series Vector Char Int) = fromLazyMap input
-        expectation = fromList [('a', 1), ('b', 2)]
-    
-    assertEqual mempty series expectation
-
-
-testToLazyMap :: TestTree
-testToLazyMap = testCase "toLazyMap" $ do
-    let input = ML.fromList [('b', 2), ('a', 1)]
-        (series :: Series Vector Char Int) = fromLazyMap input
-    
-    assertEqual mempty (toLazyMap series) input
-
-
-testTakeWhile :: TestTree
-testTakeWhile = testProperty "takeWhile behaves like lists" $ property $ do
-    xs <- forAll $ Gen.list (Range.linear 0 100) (Gen.int (Range.linear (-50) 50))
-    let (ys :: Series Vector Int Int) = Series.fromList $ zip [0..] xs
-
-    n  <- forAll $ Gen.int  (Range.linear 1 10)
-    Series.takeWhile (\v -> v `mod` n == 0) ys === Series.fromList (takeWhile (\(_, v) -> v `mod` n == 0) $ Series.toList ys)
-
-
-testDropWhile :: TestTree
-testDropWhile = testProperty "dropWhile behaves like lists" $ property $ do
-    xs <- forAll $ Gen.list (Range.linear 0 100) (Gen.int (Range.linear (-50) 50))
-    let (ys :: Series Vector Int Int) = Series.fromList $ zip [0..] xs
-
-    n  <- forAll $ Gen.int  (Range.linear 1 10)
-    Series.dropWhile (\v -> v `mod` n /= 0) ys === Series.fromList (dropWhile (\(_, v) -> v `mod` n /= 0) $ Series.toList ys)
-
-
-testFold :: TestTree
-testFold = testGroup "fold"
-         [ testProperty "Series.sum and Control.Foldl.sum should be equivalent" $ property $ do
-            xs <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-50) 50))
-            let (ys :: Series Vector Int Int) = Series.fromList $ zip [0..] xs
-            Series.fold Fold.sum ys === Series.sum ys
-         , testProperty "FoldM Identity should be equivalent to a pure fold" $ property $ do
-            xs <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-50) 50))
-            let (ys :: Series Vector Int Int) = Series.fromList $ zip [0..] xs
-            runIdentity (Series.foldM (Fold.generalize Fold.sum) ys) === Series.sum ys
++module Test.Data.Series.Generic.Definition (tests) where++import qualified Control.Foldl        as Fold+import           Data.Function        ( on )+import           Data.Functor.Identity ( Identity(..))+import           Data.List            ( nubBy, sortOn )+import qualified Data.Map.Strict      as MS+import qualified Data.Map.Lazy        as ML+import           Data.Series.Generic  ( Series, Occurrence, fromStrictMap, toStrictMap, fromLazyMap, toLazyMap, fromList, toList, fromVector, toVector )+import qualified Data.Series.Generic  as Series+import           Data.Vector          ( Vector )+import qualified Data.Vector          as Vector++import           Hedgehog             ( property, forAll, (===), tripping )+import qualified Hedgehog.Gen         as Gen+import qualified Hedgehog.Range       as Range++import           Test.Tasty           ( testGroup, TestTree ) +import           Test.Tasty.Hedgehog  ( testProperty )+import           Test.Tasty.HUnit     ( testCase, assertEqual )++tests :: TestTree+tests = testGroup "Data.Series.Generic.Definition" +    [ testMappend+    , testPropMappendLikeMap+    , testPropShow+    , testFromStrictMap+    , testToStrictMap+    , testPropRoundtripConversionWithStrictMap+    , testPropRoundtripConversionWithLazyMap+    , testPropRoundtripConversionWithList+    , testPropFromListDuplicatesNeverDrops+    , testPropFromVectorDuplicatesNeverDrops+    , testPropFromVectorDuplicatesAndFromListDuplicatesHaveSameOrder+    , testPropRoundtripConversionWithVector+    , testPropVectorVsList+    , testFromLazyMap+    , testToLazyMap+    , testTakeWhile+    , testDropWhile+    , testFold+    ]+++testMappend :: TestTree+testMappend = testCase "(<>)" $ do+    let (s1 :: Series Vector Char Int) = fromList [('a', 1), ('b', 5)]+        (s2 :: Series Vector Char Int) = fromList [('b', 10), ('x', 25)]+        expectation = fromList [('a', 1), ('b', 5),  ('x', 25)]+    +    assertEqual mempty expectation (s1 <> s2)+++testPropMappendLikeMap :: TestTree+testPropMappendLikeMap +    = testProperty "Mappend property similar to Data.Map.Strict" $ property $ do+        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.int (Range.linear 0 1000)   <*> Gen.alpha)+        m2 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.int (Range.linear 500 1500) <*> Gen.alpha)++        (fromStrictMap :: MS.Map Int Char -> Series Vector Int Char) (m1 <> m2) === fromStrictMap m1 <> fromStrictMap m2+++testPropShow :: TestTree+testPropShow+    = testProperty "Show is never too long" $ property $ do+        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.int (Range.linear 0 1000)   <*> Gen.alpha)++        let (xs :: Series Vector Int Char) = fromStrictMap m1+            ls = lines $ show xs+        if Series.length xs > 6+            then length ls === 2 + 6 + 1+            else length ls === 2 + Series.length xs+++testFromStrictMap :: TestTree+testFromStrictMap = testCase "fromStrictMap" $ do+    -- Note the duplicate input at key 'a', which should disappear+    let input = MS.fromList [('b', 2), ('a', 1), ('a', 1)]+        (series :: Series Vector Char Int) = fromStrictMap input+        expectation = fromList [('a', 1), ('b', 2)]+    +    assertEqual mempty series expectation+++testToStrictMap :: TestTree+testToStrictMap = testCase "toStrictMap" $ do+    let input = MS.fromList [('b', 2), ('a', 1)]+        (series :: Series Vector Char Int) = fromStrictMap input+    +    assertEqual mempty (toStrictMap series) input+++testPropRoundtripConversionWithStrictMap :: TestTree+testPropRoundtripConversionWithStrictMap +    = testProperty "Roundtrip property with Data.Map.Strict" $ property $ do+        ms <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+        tripping ms (fromStrictMap :: MS.Map Char Char -> Series Vector Char Char) (Just . toStrictMap)+++testPropRoundtripConversionWithLazyMap :: TestTree+testPropRoundtripConversionWithLazyMap +    = testProperty "Roundtrip property with Data.Map.Lazy" $ property $ do+        ms <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+        tripping (ML.fromDistinctAscList $ MS.toAscList ms) (fromLazyMap :: MS.Map Char Char -> Series Vector Char Char) (Just . toLazyMap)+++testPropRoundtripConversionWithList :: TestTree+testPropRoundtripConversionWithList +    = testProperty "Roundtrip property with List" $ property $ do+        xs <- forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-50) 50) <*> Gen.alpha)++        -- The property below needs some explanation.+        -- In case of conflicting keys, a Series will be biased like a Map. Therefore,+        -- the expected List won't have duplicated (hence the use of nubBy), but the elements which+        -- are kept are in the order of `reverse xs`.+        (toList :: Series Vector Int Char -> [(Int, Char)] ) (fromList xs) === sortOn fst (nubBy (\left right -> fst left == fst right) (reverse xs))+++testPropFromListDuplicatesNeverDrops :: TestTree+testPropFromListDuplicatesNeverDrops+    = testProperty "fromListDuplicates never drops elements" $ property $ do+        xs <- forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-10) 10) <*> Gen.alpha)+        Series.length (Series.fromListDuplicates xs :: Series Vector (Int, Occurrence) Char) === length xs+++testPropFromVectorDuplicatesNeverDrops :: TestTree+testPropFromVectorDuplicatesNeverDrops+    = testProperty "fromVectorDuplicates never drops elements" $ property $ do+        xs <- fmap Vector.fromList $ forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-10) 10) <*> Gen.alpha)+        Series.length (Series.fromVectorDuplicates xs :: Series Vector (Int, Occurrence) Char) === length xs+++testPropFromVectorDuplicatesAndFromListDuplicatesHaveSameOrder :: TestTree+testPropFromVectorDuplicatesAndFromListDuplicatesHaveSameOrder+    = testProperty "fromVectorDuplicates and fromListDuplicates are equivalent" $ property $ do+        xs <- fmap Vector.fromList $ forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-10) 10) <*> Gen.alpha)+        Series.fromVectorDuplicates xs === Series.fromListDuplicates (Vector.toList xs)+++testPropRoundtripConversionWithVector :: TestTree+testPropRoundtripConversionWithVector +    = testProperty "Roundtrip property with Vector" $ property $ do+        xs <- forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-50) 50) <*> Gen.alpha)++        let (srs :: Series Vector Int Char) = fromList xs+        tripping srs toVector (Just . fromVector)+++testPropVectorVsList :: TestTree+testPropVectorVsList +    = testProperty "building from a list or vector yields the same results" $ property $ do+        xs <- forAll $ Gen.list (Range.linear 0 100) ((,) <$> Gen.int (Range.linear (-50) 50) <*> Gen.alpha)+        -- Note that due to differences in sorting,+        -- Series.fromList   and Series.fromVector . Vector.fromList +        -- are not equivalent if the input list contains duplicate keys.+        let unique = nubBy ((==) `on` fst) xs +        (fromList unique :: Series Vector Int Char) === fromVector (Vector.fromList unique)+++testFromLazyMap :: TestTree+testFromLazyMap = testCase "fromLazyMap" $ do+    let input = ML.fromList [('b', 2), ('a', 1)]+        (series :: Series Vector Char Int) = fromLazyMap input+        expectation = fromList [('a', 1), ('b', 2)]+    +    assertEqual mempty series expectation+++testToLazyMap :: TestTree+testToLazyMap = testCase "toLazyMap" $ do+    let input = ML.fromList [('b', 2), ('a', 1)]+        (series :: Series Vector Char Int) = fromLazyMap input+    +    assertEqual mempty (toLazyMap series) input+++testTakeWhile :: TestTree+testTakeWhile = testProperty "takeWhile behaves like lists" $ property $ do+    xs <- forAll $ Gen.list (Range.linear 0 100) (Gen.int (Range.linear (-50) 50))+    let (ys :: Series Vector Int Int) = Series.fromList $ zip [0..] xs++    n  <- forAll $ Gen.int  (Range.linear 1 10)+    Series.takeWhile (\v -> v `mod` n == 0) ys === Series.fromList (takeWhile (\(_, v) -> v `mod` n == 0) $ Series.toList ys)+++testDropWhile :: TestTree+testDropWhile = testProperty "dropWhile behaves like lists" $ property $ do+    xs <- forAll $ Gen.list (Range.linear 0 100) (Gen.int (Range.linear (-50) 50))+    let (ys :: Series Vector Int Int) = Series.fromList $ zip [0..] xs++    n  <- forAll $ Gen.int  (Range.linear 1 10)+    Series.dropWhile (\v -> v `mod` n /= 0) ys === Series.fromList (dropWhile (\(_, v) -> v `mod` n /= 0) $ Series.toList ys)+++testFold :: TestTree+testFold = testGroup "fold"+         [ testProperty "Series.sum and Control.Foldl.sum should be equivalent" $ property $ do+            xs <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-50) 50))+            let (ys :: Series Vector Int Int) = Series.fromList $ zip [0..] xs+            Series.fold Fold.sum ys === Series.sum ys+         , testProperty "FoldM Identity should be equivalent to a pure fold" $ property $ do+            xs <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-50) 50))+            let (ys :: Series Vector Int Int) = Series.fromList $ zip [0..] xs+            runIdentity (Series.foldM (Fold.generalize Fold.sum) ys) === Series.sum ys          ]
test/Test/Data/Series/Generic/View.hs view
@@ -1,143 +1,143 @@-module Test.Data.Series.Generic.View (tests) where
-
-import qualified Data.Map.Strict      as MS
-import           Data.Series.Generic  ( Series, index, fromStrictMap, fromList, to, from, upto, select
-                                      , selectWhere, require, mapIndex, argmax, argmin, )
-import qualified Data.Series.Index    as Index
-import           Data.Vector          ( Vector )
-
-import           Hedgehog             ( property, forAll, (===), assert )
-import qualified Hedgehog.Gen         as Gen
-import qualified Hedgehog.Range       as Range
-
-import           Test.Tasty           ( testGroup, TestTree )
-import           Test.Tasty.Hedgehog  ( testProperty )
-import           Test.Tasty.HUnit     ( testCase, assertEqual )
-
-tests :: TestTree
-tests = testGroup "Data.Series.Generic.View" [ testSelectRange
-                                             , testSelectUnboundedRange
-                                             , testSelectUnboundedRangeEquivalence
-                                             , testSelectRangeEmptyRange
-                                             , testPropSelectRangeSubseries
-                                             , testSelectSet 
-                                             , testPropSelectSetSubseries
-                                             , testSelectWhere
-                                             , testPropRequire
-                                             , testMapIndex
-                                             , testArgmax
-                                             , testArgmin
-                                             ]
-
-
-testSelectRange :: TestTree
-testSelectRange = testCase "from ... to ..." $ do
-    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
-        subSeries = series `select` ('b' `to` 'd')
-        expectation = fromStrictMap $ MS.fromList [('b', 2), ('c', 3), ('d', 4)]
-    assertEqual mempty expectation subSeries
-
-
-testSelectUnboundedRange :: TestTree
-testSelectUnboundedRange = testCase "from and upto" $ do
-    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
-        openLeftsubSeries = series `select` from 'b'
-        openLeftExpectation = fromStrictMap $ MS.fromList [('b', 2), ('c', 3), ('d', 4), ('e', 5)]
-    assertEqual mempty openLeftExpectation openLeftsubSeries
-
-    let openRightsubSeries = series `select` upto 'b'
-        openRightExpectation = fromStrictMap $ MS.fromList [('a', 1), ('b', 2)]
-    assertEqual mempty openRightExpectation openRightsubSeries
-
-
-testSelectUnboundedRangeEquivalence :: TestTree
-testSelectUnboundedRangeEquivalence 
-    = testProperty "Combining unbounded ranges is equivalent to a bounded range" 
-    $ property $ do
-        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))
-        (b1, b2) <- (,) <$> forAll Gen.alpha <*> forAll Gen.alpha
-        let start = min b1 b2
-            end = max b1 b2
-            (xs :: Series Vector Char Int) = fromStrictMap m1
-
-        (xs `select` start `to` end) === ( (xs `select` from start) `select` upto end)
-
-
-testPropSelectRangeSubseries :: TestTree
-testPropSelectRangeSubseries = testProperty "xs `select` <x> `to` <y> always returns a proper subseries" $ property $ do
-        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))
-        start <- forAll Gen.alpha
-        end   <- forAll Gen.alpha
-        let (xs :: Series Vector Char Int) = fromStrictMap m1
-            ys = xs `select` start `to` end
-        
-        assert $ index xs `Index.contains` index ys
-
-
-testSelectRangeEmptyRange :: TestTree
-testSelectRangeEmptyRange = testCase "from ... to ... on an empty `Range``" $ do
-    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
-        subSeries = series `select` ('f' `to` 'z')
-    assertEqual mempty mempty subSeries
-
-
-testSelectSet :: TestTree
-testSelectSet = testCase "select" $ do
-    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
-        subSeries = series `select` Index.fromList ['a', 'd', 'x']
-        expectation = fromStrictMap $ MS.fromList [('a', 1), ('d', 4)]
-    
-    assertEqual mempty expectation subSeries
-
-
-testPropSelectSetSubseries :: TestTree
-testPropSelectSetSubseries = testProperty "xs `select` <some set> always returns a proper subseries" $ property $ do
-        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))
-        selection <- forAll $ Gen.set (Range.linear 0 10) Gen.alpha
-        let (xs :: Series Vector Char Int) = fromStrictMap m1
-            ys = xs `select` selection
-        
-        assert $ index xs `Index.contains` index ys
-
-
-testSelectWhere :: TestTree
-testSelectWhere = testCase "selectWhere" $ do
-    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
-        subSeries = series `selectWhere` fmap (>3) series 
-        expectation = fromStrictMap $ MS.fromList [('d', 4), ('e', 5)]
-    
-    assertEqual mempty expectation subSeries
-
-
-testPropRequire :: TestTree
-testPropRequire = testProperty "require always returns a series with the expected index" $ property $ do
-    m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.int (Range.linear 0 1000) <*> Gen.int (Range.linear 0 1000))
-    ss <- forAll $ Gen.set (Range.linear 0 100) (Gen.int (Range.linear (-100) 100))
-    
-    let (xs :: Series Vector Int Int) = fromStrictMap m1
-        ix = Index.fromSet ss
-    index (xs `require` ix) === ix 
-
-
-testMapIndex :: TestTree
-testMapIndex = testCase "mapIndex" $ do
-    let (series :: Series Vector String Int) = fromList [("aa", 1), ("ab", 2), ("bb", 3), ("bc", 4), ("c", 5)]
-        subSeries = series `mapIndex` take 1
-        expectation = fromList [("a", 1), ("b", 3), ("c", 5)]
-    
-    assertEqual mempty expectation subSeries
-
-
-testArgmax :: TestTree
-testArgmax = testCase "argmax" $ do
-    let (series :: Series Vector String Int) = fromList [("aa", 1), ("ab", 2), ("bb", 10), ("bc", 4), ("c", 5)]
-        expectation = Just "bb"
-    
-    assertEqual mempty expectation (argmax series)
-
-testArgmin :: TestTree
-testArgmin = testCase "argmin" $ do
-    let (series :: Series Vector String Int) = fromList [("aa", 1), ("ab", 2), ("bb", -10), ("bc", 4), ("c", 5)]
-        expectation = Just "bb"
-    
+module Test.Data.Series.Generic.View (tests) where++import qualified Data.Map.Strict      as MS+import           Data.Series.Generic  ( Series, index, fromStrictMap, fromList, to, from, upto, select+                                      , selectWhere, require, mapIndex, argmax, argmin, )+import qualified Data.Series.Index    as Index+import           Data.Vector          ( Vector )++import           Hedgehog             ( property, forAll, (===), assert )+import qualified Hedgehog.Gen         as Gen+import qualified Hedgehog.Range       as Range++import           Test.Tasty           ( testGroup, TestTree )+import           Test.Tasty.Hedgehog  ( testProperty )+import           Test.Tasty.HUnit     ( testCase, assertEqual )++tests :: TestTree+tests = testGroup "Data.Series.Generic.View" [ testSelectRange+                                             , testSelectUnboundedRange+                                             , testSelectUnboundedRangeEquivalence+                                             , testSelectRangeEmptyRange+                                             , testPropSelectRangeSubseries+                                             , testSelectSet +                                             , testPropSelectSetSubseries+                                             , testSelectWhere+                                             , testPropRequire+                                             , testMapIndex+                                             , testArgmax+                                             , testArgmin+                                             ]+++testSelectRange :: TestTree+testSelectRange = testCase "from ... to ..." $ do+    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]+        subSeries = series `select` ('b' `to` 'd')+        expectation = fromStrictMap $ MS.fromList [('b', 2), ('c', 3), ('d', 4)]+    assertEqual mempty expectation subSeries+++testSelectUnboundedRange :: TestTree+testSelectUnboundedRange = testCase "from and upto" $ do+    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]+        openLeftsubSeries = series `select` from 'b'+        openLeftExpectation = fromStrictMap $ MS.fromList [('b', 2), ('c', 3), ('d', 4), ('e', 5)]+    assertEqual mempty openLeftExpectation openLeftsubSeries++    let openRightsubSeries = series `select` upto 'b'+        openRightExpectation = fromStrictMap $ MS.fromList [('a', 1), ('b', 2)]+    assertEqual mempty openRightExpectation openRightsubSeries+++testSelectUnboundedRangeEquivalence :: TestTree+testSelectUnboundedRangeEquivalence +    = testProperty "Combining unbounded ranges is equivalent to a bounded range" +    $ property $ do+        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))+        (b1, b2) <- (,) <$> forAll Gen.alpha <*> forAll Gen.alpha+        let start = min b1 b2+            end = max b1 b2+            (xs :: Series Vector Char Int) = fromStrictMap m1++        (xs `select` start `to` end) === ( (xs `select` from start) `select` upto end)+++testPropSelectRangeSubseries :: TestTree+testPropSelectRangeSubseries = testProperty "xs `select` <x> `to` <y> always returns a proper subseries" $ property $ do+        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))+        start <- forAll Gen.alpha+        end   <- forAll Gen.alpha+        let (xs :: Series Vector Char Int) = fromStrictMap m1+            ys = xs `select` start `to` end+        +        assert $ index xs `Index.contains` index ys+++testSelectRangeEmptyRange :: TestTree+testSelectRangeEmptyRange = testCase "from ... to ... on an empty `Range``" $ do+    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]+        subSeries = series `select` ('f' `to` 'z')+    assertEqual mempty mempty subSeries+++testSelectSet :: TestTree+testSelectSet = testCase "select" $ do+    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]+        subSeries = series `select` Index.fromList ['a', 'd', 'x']+        expectation = fromStrictMap $ MS.fromList [('a', 1), ('d', 4)]+    +    assertEqual mempty expectation subSeries+++testPropSelectSetSubseries :: TestTree+testPropSelectSetSubseries = testProperty "xs `select` <some set> always returns a proper subseries" $ property $ do+        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))+        selection <- forAll $ Gen.set (Range.linear 0 10) Gen.alpha+        let (xs :: Series Vector Char Int) = fromStrictMap m1+            ys = xs `select` selection+        +        assert $ index xs `Index.contains` index ys+++testSelectWhere :: TestTree+testSelectWhere = testCase "selectWhere" $ do+    let (series :: Series Vector Char Int) = fromStrictMap $ MS.fromList [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]+        subSeries = series `selectWhere` fmap (>3) series +        expectation = fromStrictMap $ MS.fromList [('d', 4), ('e', 5)]+    +    assertEqual mempty expectation subSeries+++testPropRequire :: TestTree+testPropRequire = testProperty "require always returns a series with the expected index" $ property $ do+    m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.int (Range.linear 0 1000) <*> Gen.int (Range.linear 0 1000))+    ss <- forAll $ Gen.set (Range.linear 0 100) (Gen.int (Range.linear (-100) 100))+    +    let (xs :: Series Vector Int Int) = fromStrictMap m1+        ix = Index.fromSet ss+    index (xs `require` ix) === ix +++testMapIndex :: TestTree+testMapIndex = testCase "mapIndex" $ do+    let (series :: Series Vector String Int) = fromList [("aa", 1), ("ab", 2), ("bb", 3), ("bc", 4), ("c", 5)]+        subSeries = series `mapIndex` take 1+        expectation = fromList [("a", 1), ("b", 3), ("c", 5)]+    +    assertEqual mempty expectation subSeries+++testArgmax :: TestTree+testArgmax = testCase "argmax" $ do+    let (series :: Series Vector String Int) = fromList [("aa", 1), ("ab", 2), ("bb", 10), ("bc", 4), ("c", 5)]+        expectation = Just "bb"+    +    assertEqual mempty expectation (argmax series)++testArgmin :: TestTree+testArgmin = testCase "argmin" $ do+    let (series :: Series Vector String Int) = fromList [("aa", 1), ("ab", 2), ("bb", -10), ("bc", 4), ("c", 5)]+        expectation = Just "bb"+         assertEqual mempty expectation (argmin series)
test/Test/Data/Series/Generic/Zip.hs view
@@ -1,147 +1,147 @@-
-module Test.Data.Series.Generic.Zip ( tests ) where
-
-
-import           Control.Monad        ( forM_ )
-
-import           Data.Maybe           ( fromJust, isNothing )
-import           Data.Monoid          ( Sum(..) )
-import           Data.Series.Generic  ( Series(index), mapStrategy
-                                      , fromStrictMap, fromList, zipWith, select, at, replace, (|->), (<-|)
-                                      )
-import qualified Data.Series.Generic  as Series
-import qualified Data.Series.Index    as Index 
-import           Data.Vector          ( Vector )
-
-import           Hedgehog             ( property, forAll, (===), assert )
-import qualified Hedgehog.Gen         as Gen
-import qualified Hedgehog.Range       as Range
-
-import           Prelude              hiding ( zipWith )
-
-import           Test.Tasty           ( testGroup, TestTree ) 
-import           Test.Tasty.Hedgehog  ( testProperty )
-import           Test.Tasty.HUnit     ( testCase, assertEqual )
-
-tests :: TestTree
-tests = testGroup "Data.Series.Generic.Zip" [ testZipWith
-                                                  , testPropZipWithMatched
-                                                  , testPropZipWithMatchedAndZipWithMonoid
-                                                  , testPropZipWith
-                                                  , testPropReplace
-                                                  , testPropReplaceInfix
-                                                  , testPropZipWithStrategySkipStrategy
-                                                  , testMapStrategy
-                                                  ]
-
-
-testZipWith :: TestTree
-testZipWith = testCase "zipWith" $ do
-    let (s1 :: Series Vector Char Int) = fromList [('a', 1), ('b', 5)]
-        (s2 :: Series Vector Char Int) = fromList [('x', 25), ('b', 10)]
-        expectation = fromList [('a', Nothing), ('b', Just 15),  ('x', Nothing)]
-    
-    assertEqual mempty expectation (zipWith (+) s1 s2)
-
-
-testPropZipWithMatched :: TestTree
-testPropZipWithMatched 
-    = testProperty "zipWith when keys all match" $ property $ do
-        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))
-        let (xs :: Series Vector Char Int) = fromStrictMap m1
-        zipWith (+) xs xs === fmap (Just . (*2)) xs
-
-
-testPropZipWithMatchedAndZipWithMonoid :: TestTree
-testPropZipWithMatchedAndZipWithMonoid 
-    = testProperty "zipWithMonoid and zipWithStrategy give compatible results" $ property $ do
-        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))
-        m2 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))
-        let (xs :: Series Vector Char (Sum Int)) = Series.map Sum $ fromStrictMap m1
-            (ys :: Series Vector Char (Sum Int)) = Series.map Sum $ fromStrictMap m2
-
-            expectation = Series.zipWithStrategy (<>) (mapStrategy id) (mapStrategy id) xs ys
-        
-        expectation === Series.zipWithMonoid (<>) xs ys
-
-
-
-testPropZipWith :: TestTree
-testPropZipWith 
-    = testProperty "zipWith when keys all match" $ property $ do
-        m1 <- forAll $ Gen.map (Range.linear 0 100) ((,) <$> Gen.string (Range.singleton 2) Gen.alpha <*> Gen.int (Range.linear 0 1000))
-        m2 <- forAll $ Gen.map (Range.linear 0 100) ((,) <$> Gen.string (Range.singleton 2) Gen.alpha <*> Gen.int (Range.linear 0 1000))
-        let (x1 :: Series Vector String Int) = fromStrictMap m1
-            x2 = fromStrictMap m2
-            common  = index x1 `Index.intersection` index x2
-            symdiff = (index x1 `Index.union` index x2) `Index.difference` common
-            comb = zipWith (+) x1 x2
-
-        forM_ common $ \k -> do
-            let left  = fromJust $ x1 `at` k
-                right = fromJust $ x2 `at` k
-            fromJust (comb `at` k) === Just (left + right)
-        
-        assert $ all isNothing $ Series.values (comb `select` symdiff)
-
-
-testPropReplace :: TestTree
-testPropReplace 
-    = testProperty "replace" $ property $ do
-        ms <- forAll $ Gen.list (Range.linear 10 100) (Gen.int $ Range.linear (-500) 500) 
-        ns <- forAll $ Gen.list (Range.linear 0 10)   (Gen.int $ Range.linear (-500) 500) 
-        ixs <- forAll $ Gen.list (Range.singleton $ length ns) (Gen.int $ Range.linear 0 150)
-        let (xs :: Series Vector Int Int) = fromList (zip ixs ms)
-            ys = fromList (zip [0..] ns)
-            rs = ys `replace` xs
-
-        index rs === index xs
-
-        let commonKeys = index xs `Index.intersection` index ys
-
-        (rs `select` commonKeys) === (ys `select` commonKeys)
-
-
-testPropReplaceInfix :: TestTree
-testPropReplaceInfix 
-    = testProperty "(|->) and (<-|)" $ property $ do
-        ms <- forAll $ Gen.list (Range.linear 10 100) (Gen.int $ Range.linear (-500) 500) 
-        ns <- forAll $ Gen.list (Range.linear 0 10)   (Gen.int $ Range.linear (-500) 500) 
-        ixs <- forAll $ Gen.list (Range.singleton $ length ns) (Gen.int $ Range.linear 0 150)
-        let (xs :: Series Vector Int Int) = fromList (zip ixs ms)
-            ys = fromList (zip [0..] ns)
-            rs = ys `replace` xs
-        
-        ys |-> xs === rs 
-        ys |-> xs === xs <-| ys 
-
-
-testPropZipWithStrategySkipStrategy :: TestTree
-testPropZipWithStrategySkipStrategy 
-    = testProperty "zipWithStrategy f skipStrategy skipStrategy is equivalent to zipWithMatched" $ property $ do
-        m1 <- forAll $ Gen.map (Range.linear 0 100) ((,) <$> Gen.string (Range.singleton 2) Gen.alpha <*> Gen.int (Range.linear 0 1000))
-        m2 <- forAll $ Gen.map (Range.linear 0 100) ((,) <$> Gen.string (Range.singleton 2) Gen.alpha <*> Gen.int (Range.linear 0 1000))
-
-        let (xs :: Series Vector String Int) = fromStrictMap m1
-            ys = fromStrictMap m2
-
-            expectation = Series.zipWithMatched (+) xs ys
-        
-        expectation === Series.zipWithStrategy (+) Series.skipStrategy Series.skipStrategy xs ys
-
-
-testMapStrategy :: TestTree
-testMapStrategy 
-    = testCase "mapStrategy works as expected" $ do
-        let (xs :: Series Vector Int Int) = Series.fromList $ zip [0..] [1,2,3,4,5]
-            ys =                            Series.fromList $ zip [3..]       [3,4,5]
-        
-            expected = Series.fromList [ (0, 1+1)
-                                       , (1, 2+1)
-                                       , (2, 3+1)
-                                       , (3, 4+3)
-                                       , (4, 5+4)
-                                       , (5, 5*2)
-                                       ]
-
-        assertEqual mempty expected $ Series.zipWithStrategy (+) (mapStrategy (+1)) (mapStrategy (*2)) xs ys
++module Test.Data.Series.Generic.Zip ( tests ) where+++import           Control.Monad        ( forM_ )++import           Data.Maybe           ( fromJust, isNothing )+import           Data.Monoid          ( Sum(..) )+import           Data.Series.Generic  ( Series(index), mapStrategy+                                      , fromStrictMap, fromList, zipWith, select, at, replace, (|->), (<-|)+                                      )+import qualified Data.Series.Generic  as Series+import qualified Data.Series.Index    as Index +import           Data.Vector          ( Vector )++import           Hedgehog             ( property, forAll, (===), assert )+import qualified Hedgehog.Gen         as Gen+import qualified Hedgehog.Range       as Range++import           Prelude              hiding ( zipWith )++import           Test.Tasty           ( testGroup, TestTree ) +import           Test.Tasty.Hedgehog  ( testProperty )+import           Test.Tasty.HUnit     ( testCase, assertEqual )++tests :: TestTree+tests = testGroup "Data.Series.Generic.Zip" [ testZipWith+                                                  , testPropZipWithMatched+                                                  , testPropZipWithMatchedAndZipWithMonoid+                                                  , testPropZipWith+                                                  , testPropReplace+                                                  , testPropReplaceInfix+                                                  , testPropZipWithStrategySkipStrategy+                                                  , testMapStrategy+                                                  ]+++testZipWith :: TestTree+testZipWith = testCase "zipWith" $ do+    let (s1 :: Series Vector Char Int) = fromList [('a', 1), ('b', 5)]+        (s2 :: Series Vector Char Int) = fromList [('x', 25), ('b', 10)]+        expectation = fromList [('a', Nothing), ('b', Just 15),  ('x', Nothing)]+    +    assertEqual mempty expectation (zipWith (+) s1 s2)+++testPropZipWithMatched :: TestTree+testPropZipWithMatched +    = testProperty "zipWith when keys all match" $ property $ do+        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))+        let (xs :: Series Vector Char Int) = fromStrictMap m1+        zipWith (+) xs xs === fmap (Just . (*2)) xs+++testPropZipWithMatchedAndZipWithMonoid :: TestTree+testPropZipWithMatchedAndZipWithMonoid +    = testProperty "zipWithMonoid and zipWithStrategy give compatible results" $ property $ do+        m1 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))+        m2 <- forAll $ Gen.map (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.int (Range.linear 0 1000))+        let (xs :: Series Vector Char (Sum Int)) = Series.map Sum $ fromStrictMap m1+            (ys :: Series Vector Char (Sum Int)) = Series.map Sum $ fromStrictMap m2++            expectation = Series.zipWithStrategy (<>) (mapStrategy id) (mapStrategy id) xs ys+        +        expectation === Series.zipWithMonoid (<>) xs ys++++testPropZipWith :: TestTree+testPropZipWith +    = testProperty "zipWith when keys all match" $ property $ do+        m1 <- forAll $ Gen.map (Range.linear 0 100) ((,) <$> Gen.string (Range.singleton 2) Gen.alpha <*> Gen.int (Range.linear 0 1000))+        m2 <- forAll $ Gen.map (Range.linear 0 100) ((,) <$> Gen.string (Range.singleton 2) Gen.alpha <*> Gen.int (Range.linear 0 1000))+        let (x1 :: Series Vector String Int) = fromStrictMap m1+            x2 = fromStrictMap m2+            common  = index x1 `Index.intersection` index x2+            symdiff = (index x1 `Index.union` index x2) `Index.difference` common+            comb = zipWith (+) x1 x2++        forM_ common $ \k -> do+            let left  = fromJust $ x1 `at` k+                right = fromJust $ x2 `at` k+            fromJust (comb `at` k) === Just (left + right)+        +        assert $ all isNothing $ Series.values (comb `select` symdiff)+++testPropReplace :: TestTree+testPropReplace +    = testProperty "replace" $ property $ do+        ms <- forAll $ Gen.list (Range.linear 10 100) (Gen.int $ Range.linear (-500) 500) +        ns <- forAll $ Gen.list (Range.linear 0 10)   (Gen.int $ Range.linear (-500) 500) +        ixs <- forAll $ Gen.list (Range.singleton $ length ns) (Gen.int $ Range.linear 0 150)+        let (xs :: Series Vector Int Int) = fromList (zip ixs ms)+            ys = fromList (zip [0..] ns)+            rs = ys `replace` xs++        index rs === index xs++        let commonKeys = index xs `Index.intersection` index ys++        (rs `select` commonKeys) === (ys `select` commonKeys)+++testPropReplaceInfix :: TestTree+testPropReplaceInfix +    = testProperty "(|->) and (<-|)" $ property $ do+        ms <- forAll $ Gen.list (Range.linear 10 100) (Gen.int $ Range.linear (-500) 500) +        ns <- forAll $ Gen.list (Range.linear 0 10)   (Gen.int $ Range.linear (-500) 500) +        ixs <- forAll $ Gen.list (Range.singleton $ length ns) (Gen.int $ Range.linear 0 150)+        let (xs :: Series Vector Int Int) = fromList (zip ixs ms)+            ys = fromList (zip [0..] ns)+            rs = ys `replace` xs+        +        ys |-> xs === rs +        ys |-> xs === xs <-| ys +++testPropZipWithStrategySkipStrategy :: TestTree+testPropZipWithStrategySkipStrategy +    = testProperty "zipWithStrategy f skipStrategy skipStrategy is equivalent to zipWithMatched" $ property $ do+        m1 <- forAll $ Gen.map (Range.linear 0 100) ((,) <$> Gen.string (Range.singleton 2) Gen.alpha <*> Gen.int (Range.linear 0 1000))+        m2 <- forAll $ Gen.map (Range.linear 0 100) ((,) <$> Gen.string (Range.singleton 2) Gen.alpha <*> Gen.int (Range.linear 0 1000))++        let (xs :: Series Vector String Int) = fromStrictMap m1+            ys = fromStrictMap m2++            expectation = Series.zipWithMatched (+) xs ys+        +        expectation === Series.zipWithStrategy (+) Series.skipStrategy Series.skipStrategy xs ys+++testMapStrategy :: TestTree+testMapStrategy +    = testCase "mapStrategy works as expected" $ do+        let (xs :: Series Vector Int Int) = Series.fromList $ zip [0..] [1,2,3,4,5]+            ys =                            Series.fromList $ zip [3..]       [3,4,5]+        +            expected = Series.fromList [ (0, 1+1)+                                       , (1, 2+1)+                                       , (2, 3+1)+                                       , (3, 4+3)+                                       , (4, 5+4)+                                       , (5, 5*2)+                                       ]++        assertEqual mempty expected $ Series.zipWithStrategy (+) (mapStrategy (+1)) (mapStrategy (*2)) xs ys
test/Test/Data/Series/Index.hs view
@@ -1,123 +1,123 @@-
-module Test.Data.Series.Index (tests) where
-
-import qualified Data.Series.Index    as Index
-import qualified Data.Series.Index.Internal as Index.Internal
-import qualified Data.Set             as Set
-import qualified Data.Vector          as Vector
-
-import           Hedgehog             ( property, forAll, tripping, assert, (===) )
-import qualified Hedgehog.Gen         as Gen
-import qualified Hedgehog.Range       as Range
-
-
-import           Test.Tasty           ( testGroup, TestTree ) 
-import           Test.Tasty.Hedgehog  ( testProperty )
-
-
-tests :: TestTree
-tests = testGroup "Data.Series.Index" [ testPropRange
-                                      , testPropFromToSet
-                                      , testPropFromToList
-                                      , testPropFromToAscList
-                                      , testPropFromToVector
-                                      , testPropFromToAscVector
-                                      , testPropMemberNotMember
-                                      , testPropIndexed
-                                      , testPropFilter
-                                      ]
-
-
-testPropRange :: TestTree
-testPropRange = testProperty "range always includes the start, and all elements less than/equal to end" $ property $ do
-    start <- forAll $ Gen.int (Range.linear 0 50)
-    end   <- forAll $ Gen.int (Range.linear 51 100)
-    step  <- forAll $ Gen.int (Range.linear 1 5)
-
-    let ix = Index.range (+step) start end 
-
-    assert $ start `Index.member` ix
-    assert $ maximum ix <= end
-
-    if (end - start) `mod` step == 0
-        then assert (end `Index.member` ix)
-        else assert (end `Index.notMember` ix)
-
-
-testPropFromToSet :: TestTree
-testPropFromToSet = testGroup "conversion to/from Set" 
-    [ testProperty "fromSet / toSet" $ property $ do
-        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-        tripping (Set.fromList ms) Index.fromSet (Just . Index.toSet)
-    , testProperty "toIndex / fromIndex" $ property $ do
-        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-        tripping (Set.fromList ms) (Index.toIndex :: Set.Set (Char, Char) -> Index.Index (Char, Char)) (Just . Index.fromIndex)
-    ]
-
-
-testPropFromToList :: TestTree
-testPropFromToList = testGroup "conversion to/from list" 
-    [ testProperty "fromList / toAscList" $ property $ do
-        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-        let index = Index.fromList ms
-        tripping index (reverse . Index.toAscList) (Just . Index.fromList)
-    , testProperty "toIndex / fromIndex" $ property $ do
-        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-        let index = Index.toIndex ms :: Index.Index (Char, Char)
-        tripping index (reverse . Index.fromIndex) (Just . (Index.toIndex :: [(Char, Char)] -> Index.Index (Char, Char)))
-    ]
-
-
-testPropFromToAscList :: TestTree
-testPropFromToAscList = testProperty "fromAscList / toAscList" $ property $ do
-    ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-    let index = Index.fromList ms
-    tripping index Index.toAscList (Just . Index.Internal.fromAscList)
-
-
-testPropFromToVector :: TestTree
-testPropFromToVector = testGroup "conversion to/from Vector"
-    [ testProperty "fromVector / toAscVector" $ property $ do
-        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-        let index = Index.fromList ms
-        tripping index (Vector.reverse . Index.toAscVector) (Just . Index.fromVector)
-    , testProperty "toIndex / fromIndex" $ property $ do
-        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-        let index = Index.toIndex ms :: Index.Index (Char, Char)
-        tripping index (Vector.reverse . Index.fromIndex) (Just . (Index.toIndex :: Vector.Vector (Char, Char) -> Index.Index (Char, Char)))
-    ]
-
-
-testPropFromToAscVector :: TestTree
-testPropFromToAscVector = testProperty "fromAscVector / toAscVector" $ property $ do
-    ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)
-    let index = Index.fromList ms
-    tripping index (Index.toAscVector :: Index.Index (Char, Char) -> Vector.Vector (Char, Char)) (Just . Index.Internal.fromAscVector)
-
-
-testPropMemberNotMember :: TestTree
-testPropMemberNotMember = testProperty "elements are either a member or not a member of the index" $ property $ do
-    ms <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-100) 100))
-    k  <- forAll $ Gen.int (Range.linear (-100) 100)
-
-    let ix = Index.fromList ms
-    assert $ (k `Index.member` ix) /= (k `Index.notMember` ix)
-
-
-testPropIndexed :: TestTree
-testPropIndexed = testProperty "indexed works just like for Vectors" $ property $ do
-    ms <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-100) 100))
-
-    let ix = Index.fromList ms
-    
-    Index.toAscVector (Index.indexed ix) === Vector.indexed (Index.toAscVector ix)
-
-
-testPropFilter :: TestTree
-testPropFilter = testProperty "filter works just like for Sets" $ property $ do
-    ms <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-100) 100))
-
-    let ss = Set.fromList ms
-        ix = Index.fromSet ss
-    
-    Index.fromSet (Set.filter even ss) === Index.filter even ix
++module Test.Data.Series.Index (tests) where++import qualified Data.Series.Index    as Index+import qualified Data.Series.Index.Internal as Index.Internal+import qualified Data.Set             as Set+import qualified Data.Vector          as Vector++import           Hedgehog             ( property, forAll, tripping, assert, (===) )+import qualified Hedgehog.Gen         as Gen+import qualified Hedgehog.Range       as Range+++import           Test.Tasty           ( testGroup, TestTree ) +import           Test.Tasty.Hedgehog  ( testProperty )+++tests :: TestTree+tests = testGroup "Data.Series.Index" [ testPropRange+                                      , testPropFromToSet+                                      , testPropFromToList+                                      , testPropFromToAscList+                                      , testPropFromToVector+                                      , testPropFromToAscVector+                                      , testPropMemberNotMember+                                      , testPropIndexed+                                      , testPropFilter+                                      ]+++testPropRange :: TestTree+testPropRange = testProperty "range always includes the start, and all elements less than/equal to end" $ property $ do+    start <- forAll $ Gen.int (Range.linear 0 50)+    end   <- forAll $ Gen.int (Range.linear 51 100)+    step  <- forAll $ Gen.int (Range.linear 1 5)++    let ix = Index.range (+step) start end ++    assert $ start `Index.member` ix+    assert $ maximum ix <= end++    if (end - start) `mod` step == 0+        then assert (end `Index.member` ix)+        else assert (end `Index.notMember` ix)+++testPropFromToSet :: TestTree+testPropFromToSet = testGroup "conversion to/from Set" +    [ testProperty "fromSet / toSet" $ property $ do+        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+        tripping (Set.fromList ms) Index.fromSet (Just . Index.toSet)+    , testProperty "toIndex / fromIndex" $ property $ do+        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+        tripping (Set.fromList ms) (Index.toIndex :: Set.Set (Char, Char) -> Index.Index (Char, Char)) (Just . Index.fromIndex)+    ]+++testPropFromToList :: TestTree+testPropFromToList = testGroup "conversion to/from list" +    [ testProperty "fromList / toAscList" $ property $ do+        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+        let index = Index.fromList ms+        tripping index (reverse . Index.toAscList) (Just . Index.fromList)+    , testProperty "toIndex / fromIndex" $ property $ do+        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+        let index = Index.toIndex ms :: Index.Index (Char, Char)+        tripping index (reverse . Index.fromIndex) (Just . (Index.toIndex :: [(Char, Char)] -> Index.Index (Char, Char)))+    ]+++testPropFromToAscList :: TestTree+testPropFromToAscList = testProperty "fromAscList / toAscList" $ property $ do+    ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+    let index = Index.fromList ms+    tripping index Index.toAscList (Just . Index.Internal.fromAscList)+++testPropFromToVector :: TestTree+testPropFromToVector = testGroup "conversion to/from Vector"+    [ testProperty "fromVector / toAscVector" $ property $ do+        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+        let index = Index.fromList ms+        tripping index (Vector.reverse . Index.toAscVector) (Just . Index.fromVector)+    , testProperty "toIndex / fromIndex" $ property $ do+        ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+        let index = Index.toIndex ms :: Index.Index (Char, Char)+        tripping index (Vector.reverse . Index.fromIndex) (Just . (Index.toIndex :: Vector.Vector (Char, Char) -> Index.Index (Char, Char)))+    ]+++testPropFromToAscVector :: TestTree+testPropFromToAscVector = testProperty "fromAscVector / toAscVector" $ property $ do+    ms <- forAll $ Gen.list (Range.linear 0 50) ((,) <$> Gen.alpha <*> Gen.alpha)+    let index = Index.fromList ms+    tripping index (Index.toAscVector :: Index.Index (Char, Char) -> Vector.Vector (Char, Char)) (Just . Index.Internal.fromAscVector)+++testPropMemberNotMember :: TestTree+testPropMemberNotMember = testProperty "elements are either a member or not a member of the index" $ property $ do+    ms <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-100) 100))+    k  <- forAll $ Gen.int (Range.linear (-100) 100)++    let ix = Index.fromList ms+    assert $ (k `Index.member` ix) /= (k `Index.notMember` ix)+++testPropIndexed :: TestTree+testPropIndexed = testProperty "indexed works just like for Vectors" $ property $ do+    ms <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-100) 100))++    let ix = Index.fromList ms+    +    Index.toAscVector (Index.indexed ix) === Vector.indexed (Index.toAscVector ix)+++testPropFilter :: TestTree+testPropFilter = testProperty "filter works just like for Sets" $ property $ do+    ms <- forAll $ Gen.list (Range.linear 0 50) (Gen.int (Range.linear (-100) 100))++    let ss = Set.fromList ms+        ix = Index.fromSet ss+    +    Index.fromSet (Set.filter even ss) === Index.filter even ix