packages feed

javelin 0.1.0.0 → 0.1.1.0

raw patch · 29 files changed

+7586/−7630 lines, 29 filesdep −ieee754dep −statisticsPVP ok

version bump matches the API change (PVP)

Dependencies removed: ieee754, statistics

API changes (from Hackage documentation)

+ Data.Series.Index: indexed :: Index k -> Index (Int, k)

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@-# Revision history for javelin--## Release 0.1.0.0--* This is the first version of `javelin` and associated packages.+# 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.
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,141 +1,136 @@-cabal-version:      3.0-name:               javelin-version:            0.1.0.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.Numeric-                      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.Numeric-                      Test.Data.Series.Generic.View-                      Test.Data.Series.Generic.Zip-                      Test.Utils-    build-depends:    base,-                      containers,-                      foldl,-                      hedgehog,-                      HUnit,-                      ieee754,-                      javelin,-                      statistics,-                      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.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
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-{-# INLINE index #-}-index = G.index---values :: Series k a -> Vector a-{-# INLINE values #-}-values = G.values----- | Create a 'Series' with a single element.-singleton :: k -> a -> Series k a-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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)]-{-# INLINE 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)-{-# INLINE 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-{-# INLINE 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-{-# INLINE fromVectorDuplicates #-}-fromVectorDuplicates = G.fromVectorDuplicates----- | Convert a series into a lazy @Map@.-toLazyMap :: Series k a -> ML.Map k a-{-# INLINE toLazyMap #-}-toLazyMap = G.toLazyMap----- | Construct a series from a lazy @Map@.-fromLazyMap :: ML.Map k a -> Series k a-{-# INLINE fromLazyMap #-}-fromLazyMap = G.fromLazyMap----- | Convert a series into a strict @Map@.-toStrictMap :: Series k a -> MS.Map k a-{-# INLINE toStrictMap #-}-toStrictMap = G.toStrictMap----- | Construct a series from a strict @Map@.-fromStrictMap :: MS.Map k a -> Series k a-{-# INLINE fromStrictMap #-}-fromStrictMap = G.fromStrictMap----- | \(O(n)\) Map every element of a 'Series'.-map :: (a -> b) -> Series k a -> Series k b-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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)-{-# INLINE 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 ()-{-# INLINE 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)-{-# INLINE 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 ()-{-# INLINE 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)-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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 -{-# INLINE 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)-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE eproduct #-}----- | \(O(n)\) Unzip a 'Series' of 2-tuples.-unzip :: Series k (a, b)-      -> ( Series k a-         , Series k b-         )-unzip = G.unzip-{-# INLINE 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-{-# INLINE 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)-{-# INLINE require #-}-require = G.require ----- | 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE (|->) #-}-(|->) = (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-{-# INLINE (<-|) #-}-(<-|) = (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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE windowing #-}-windowing = G.windowing----- | \(O(1)\) Test whether a 'Series' is empty.-null :: Series k a -> Bool-{-# INLINE null #-}-null = G.null----- |\(O(1)\) Extract the length of a 'Series'.-length :: Series k a -> Int-{-# INLINE length #-}-length = G.length----- | \(O(n)\) Check if all elements satisfy the predicate.-all :: (a -> Bool) -> Series k a -> Bool-{-# INLINE all #-}-all = G.all----- | \(O(n)\) Check if any element satisfies the predicate.-any :: (a -> Bool) -> Series k a -> Bool-{-# INLINE any #-}-any = G.any----- | \(O(n)\) Check if all elements are 'True'.-and :: Series k Bool -> Bool-{-# INLINE and #-}-and = G.and----- | \(O(n)\) Check if any element is 'True'.-or :: Series k Bool -> Bool-{-# INLINE or #-}-or = G.or----- | \(O(n)\) Compute the sum of the elements.-sum :: (Num a) => Series k a -> a-{-# INLINE sum #-}-sum = G.sum----- | \(O(n)\) Compute the product of the elements.-product :: (Num a) => Series k a -> a-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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,86 +1,98 @@-{-# LANGUAGE NoImplicitPrelude #-}-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 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.Numeric      ( mean, variance, std )-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,326 @@-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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE any #-}-any f = Vector.any f . values----- | \(O(n)\) Check if all elements are 'True'.-and :: Vector v Bool => Series v k Bool -> Bool-{-# INLINE and #-}-and = Vector.and . values----- | \(O(n)\) Check if any element is 'True'.-or :: Vector v Bool => Series v k Bool -> Bool-{-# INLINE or #-}-or = Vector.or . values----- | \(O(n)\) Compute the sum of the elements.-sum :: (Num a, Vector v a) => Series v k a -> a-{-# INLINE sum #-}-sum = Vector.sum . values----- | \(O(n)\) Compute the product of the elements.-product :: (Num a, Vector v a) => Series v k a -> a-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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 
+    $ 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 #-}
 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-    {-# INLINE 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)-    {-# INLINE 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-{-# INLINE 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-{-# INLINE 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)]-{-# INLINE 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-    {-# INLINE toSeries #-}--    fromSeries = toVector-    {-# INLINE 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-    {-# INLINE toSeries #-}--    fromSeries :: Series U.Vector k a -> U.Vector (k, a)-    fromSeries = toVector-    {-# INLINE 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-{-# INLINE 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-{-# INLINE 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)-{-# INLINE 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-                }-    {-# INLINE toSeries #-}--    fromSeries :: Series v k a -> Map k a-    fromSeries (MkSeries ks vs)-        = MS.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)-    {-# INLINE fromSeries #-}---toLazyMap :: (Vector v a) => Series v k a -> Map k a-{-# INLINE 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-{-# INLINE fromLazyMap #-}-fromLazyMap = toSeries----- | Convert a series into a strict 'Data.Map.Strict.Map'.-toStrictMap :: (Vector v a) => Series v k a -> Map k a-{-# INLINE 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-{-# INLINE 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 -                }-    {-# INLINE toSeries #-}--    fromSeries :: Series v Int a -> IntMap a-    fromSeries (MkSeries ks vs) -        = IntMap.fromDistinctAscList $ zip (Index.toAscList ks) (Vector.toList vs)-    {-# INLINE 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-    {-# INLINE toSeries #-}--    fromSeries :: Series v k a -> Seq (k, a)-    fromSeries = Seq.fromList . fromSeries-    {-# INLINE 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-    {-# INLINE toSeries #-}--    fromSeries :: Series v k a -> Set (k, a)-    fromSeries = Set.fromDistinctAscList . toList-    {-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE concatMap #-}-concatMap f = fromVector -            . Vector.concatMap (toVector . f . snd) -            . toVector---instance (Vector v a, Ord k) => Semigroup (Series v k a) where-    {-# INLINE (<>) #-}-    (<>) :: 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--    {-# INLINE sconcat #-}-    sconcat = toSeries . sconcat . fmap toStrictMap---instance (Vector v a, Ord k) => Monoid (Series v k a) where-    {-# INLINE mempty #-}-    mempty :: Series v k a-    mempty = MkSeries mempty Vector.empty--    {-# INLINE mappend #-}-    mappend :: Series v k a -> Series v k a -> Series v k a-    mappend = (<>)--    {-# INLINE 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-    {-# INLINE (==) #-}-    (==) :: 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-    {-# INLINE 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-    {-# INLINE 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-    {-# INLINE 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-    {-# INLINE fold #-}-    fold :: Monoid m => Series v k m -> m-    fold = Foldable.fold . values--    {-# INLINE foldMap #-}-    foldMap :: (Monoid m) => (a -> m) -> Series v k a -> m-    foldMap f = Foldable.foldMap f . values--    {-# INLINE foldMap' #-}-    foldMap' :: (Monoid m) => (a -> m) -> Series v k a -> m-    foldMap' f = Foldable.foldMap f . values--    {-# INLINE foldr #-}-    foldr :: (a -> b -> b) -> b -> Series v k a -> b-    foldr f i = Foldable.foldr f i . values--    {-# INLINE foldr' #-}-    foldr' :: (a -> b -> b) -> b -> Series v k a -> b-    foldr' f i = Foldable.foldr' f i . values--    {-# INLINE foldl #-}-    foldl :: (b -> a -> b) -> b -> Series v k a -> b-    foldl f i = Foldable.foldl f i . values--    {-# INLINE foldl' #-}-    foldl' :: (b -> a -> b) -> b -> Series v k a -> b-    foldl' f i = Foldable.foldl' f i . values--    {-# INLINE foldr1 #-}-    foldr1 :: (a -> a -> a) -> Series v k a -> a-    foldr1 f = Foldable.foldr1 f . values--    {-# INLINE foldl1 #-}-    foldl1 :: (a -> a -> a) -> Series v k a -> a-    foldl1 f = Foldable.foldl1 f . values--    {-# INLINE toList #-}-    toList :: Series v k a -> [a]-    toList = Foldable.toList . values--    {-# INLINE null #-}-    null :: Series v k a -> Bool-    null = Foldable.null . values--    {-# INLINE length #-}-    length :: Series v k a -> Int-    length = Foldable.length . values--    {-# INLINE elem #-}-    elem :: Eq a => a -> Series v k a -> Bool-    elem e = Foldable.elem e . values--    {-# INLINE maximum #-}-    maximum :: Ord a => Series v k a -> a-    maximum = Foldable.maximum . values--    {-# INLINE minimum #-}-    minimum :: Ord a => Series v k a -> a-    minimum = Foldable.minimum . values--    {-# INLINE sum #-}-    sum :: Num a => Series v k a -> a-    sum = Foldable.sum . values--    {-# INLINE 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-    {-# INLINE ifoldMap #-}-    ifoldMap :: Monoid m => (k -> a -> m) -> Series v k a -> m-    ifoldMap = foldMapWithKey---instance (Foldable v) => Bifoldable (Series v) where-    {-# INLINE 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-    {-# INLINE 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-    {-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE foldMWithKey #-}----- | \(O(n)\) Fold over elements in a 'Series'.-foldMap :: (Monoid m, Vector v a) => (a -> m) -> Series v k a -> m-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE length #-}-length = Vector.length . values----- | \(O(1)\) Test whether a 'Series' is empty.-null :: Vector v a => Series v k a -> Bool-{-# INLINE 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)-{-# INLINE 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 ()-{-# INLINE 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)-{-# INLINE 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 ()-{-# INLINE 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)-{-# INLINE 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/Numeric.hs
@@ -1,7 +0,0 @@-module Data.Series.Generic.Numeric ( -    module Control.Foldl-) where--import  Control.Foldl ( sum, product, mean, variance, std )--
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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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,333 +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.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 = do-    ix <- Index.lookupIndex k ks-    pure $ Vector.unsafeIndex vs ix -{-# INLINE 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-{-# INLINE iat #-}----- | require a series with a new index.--- Contrary to 'select', all keys in @'Set' 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)-{-# INLINE 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-{-# INLINE 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))----- | 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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)-{-# INLINE 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-    {-# INLINE 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-    {-# INLINE 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-    {-# INLINE 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-    {-# INLINE 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 + indexOf series 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-{-# INLINE 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-{-# INLINE 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-    = MkSeries ss $ Boxed.convert-                  $ Boxed.map (Vector.unsafeIndex vs . (`Index.Internal.findIndex` ks))-                  $ Index.toAscVector ss----- | Yield a subseries based on 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-{-# INLINE slice #-}-slice start stop (MkSeries ks vs) -    = let stop' = min (Vector.length vs) stop-    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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE (|->) #-}-(|->) = replace----- | Flipped version of '|->',-(<-|) :: (Vector v a, Vector v Int, Ord k) -      => Series v k a -> Series v k a -> Series v k a-{-# INLINE (<-|)  #-}-(<-|) = 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-{-# INLINE 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)-{-# INLINE 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)-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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)-{-# INLINE 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)-{-# INLINE 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)-{-# INLINE 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)-{-# INLINE 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,107 +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 inline 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,-    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-                                    , 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,503 +1,517 @@-{-# 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,-    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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-    {-# INLINE toIndex #-}--    -- | \(O(1)\) Build an 'Index' from a 'Set'.-    fromIndex :: Index k -> Set k-    fromIndex = coerce-    {-# INLINE fromIndex #-}---instance Ord k => IsIndex [k] k where-    -- | \(O(n \log n)\) Build an 'Index' from a list.-    toIndex :: [k] -> Index k-    toIndex = fromList-    {-# INLINE toIndex #-}--    -- | \(O(n)\) Convert an 'Index' to a list.-    fromIndex :: Index k -> [k]-    fromIndex = toAscList-    {-# INLINE 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-    {-# INLINE toIndex #-}--    -- | \(O(n)\) Convert an 'Index' to a 'Seq'.-    fromIndex :: Index k -> Seq k-    fromIndex = Seq.fromList . toAscList-    {-# INLINE 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-    {-# INLINE toIndex #-}-    -    -- | \(O(n)\) Convert an 'Index' to an 'IntSet.-    fromIndex :: Index Int -> IntSet-    fromIndex = IntSet.fromDistinctAscList . toAscList-    {-# INLINE fromIndex #-}---instance (Ord k) => IsIndex (Boxed.Vector k) k where-    toIndex :: Boxed.Vector k -> Index k-    toIndex = fromVector-    {-# INLINE toIndex #-} --    fromIndex :: Index k -> Boxed.Vector k-    fromIndex = toAscVector-    {-# INLINE fromIndex #-}---instance (Ord k, Unboxed.Unbox k) => IsIndex (Unboxed.Vector k) k where-    toIndex :: Unboxed.Vector k -> Index k-    toIndex = fromVector-    {-# INLINE toIndex #-} --    fromIndex :: Index k -> Unboxed.Vector k-    fromIndex ix = runST $ M.generate (size ix) (`elemAt` ix) >>= Vector.freeze-    {-# INLINE fromIndex #-}----- | \(O(1)\) Build an 'Index' from a 'Set'.-fromSet :: Set k -> Index k-fromSet = toIndex-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE fromDistinctAscVector #-}----- | \(O(1)\) Convert an 'Index' to a 'Set'.-toSet :: Index k -> Set k-toSet = fromIndex-{-# INLINE 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-{-# INLINE 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-{-# INLINE toAscVector #-}----- | \(O(1)\) Returns 'True' for an empty 'Index', and @False@ otherwise.-null :: Index k -> Bool-null (MkIndex ix) = Set.null ix-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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 = (<>)-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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)-{-# INLINE 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-{-# INLINE 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-{-# INLINE contains #-}----- | \(O(1)\) Returns the number of keys in the index.-size :: Index k -> Int-size (MkIndex ix) = Set.size ix-{-# INLINE 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)-{-# INLINE 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)-{-# INLINE 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-{-# INLINE 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-{-# INLINE mapMonotonic #-}----- | \(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-{-# INLINE 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 -{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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.
+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-{-# INLINE index #-}-index = G.index---values :: Series k a -> Vector a-{-# INLINE values #-}-values = G.values----- | Create a 'Series' with a single element.-singleton :: Unbox a => k -> a -> Series k a-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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)]-{-# INLINE 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)-{-# INLINE 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-{-# INLINE 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-{-# INLINE fromVectorDuplicates #-}-fromVectorDuplicates = G.fromVectorDuplicates----- | Convert a series into a lazy @Map@.-toLazyMap :: (Unbox a) => Series k a -> ML.Map k a-{-# INLINE toLazyMap #-}-toLazyMap = G.toLazyMap----- | Construct a series from a lazy @Map@.-fromLazyMap :: (Unbox a) => ML.Map k a -> Series k a-{-# INLINE fromLazyMap #-}-fromLazyMap = G.fromLazyMap----- | Convert a series into a strict @Map@.-toStrictMap :: (Unbox a) => Series k a -> MS.Map k a-{-# INLINE toStrictMap #-}-toStrictMap = G.toStrictMap---- | Construct a series from a strict @Map@.-fromStrictMap :: (Unbox a) => MS.Map k a -> Series k a-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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)-{-# INLINE 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 ()-{-# INLINE 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)-{-# INLINE 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 ()-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE require #-}-require f = G.requireWith f id----- | 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE (|->) #-}-(|->) = (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-{-# INLINE (<-|) #-}-(<-|) = (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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE windowing #-}-windowing = G.windowing ----- | \(O(1)\) Test whether a 'Series' is empty.-null :: Unbox a => Series k a -> Bool-{-# INLINE null #-}-null = G.null----- |\(O(1)\) Extract the length of a 'Series'.-length :: Unbox a => Series k a -> Int-{-# INLINE length #-}-length = G.length----- | \(O(n)\) Check if all elements satisfy the predicate.-all :: Unbox a => (a -> Bool) -> Series k a -> Bool-{-# INLINE all #-}-all = G.all----- | \(O(n)\) Check if any element satisfies the predicate.-any :: Unbox a => (a -> Bool) -> Series k a -> Bool-{-# INLINE any #-}-any = G.any----- | \(O(n)\) Check if all elements are 'True'.-and :: Series k Bool -> Bool-{-# INLINE and #-}-and = G.and----- | \(O(n)\) Check if any element is 'True'.-or :: Series k Bool -> Bool-{-# INLINE or #-}-or = G.or----- | \(O(n)\) Compute the sum of the elements.-sum :: (Unbox a, Num a) => Series k a -> a-{-# INLINE sum #-}-sum = G.sum----- | \(O(n)\) Compute the product of the elements.-product :: (Unbox a, Num a) => Series k a -> a-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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-{-# INLINE 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,21 +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.Numeric-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.Numeric.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,134 @@--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.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]
+    
     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/Numeric.hs
@@ -1,62 +0,0 @@-module Test.Data.Series.Generic.Numeric (tests) where--import           Data.Series.Generic  ( Series, fromList, mean, variance, std)-import qualified Data.Series.Generic  as Series-import           Data.Vector          ( Vector )-import qualified Data.Vector          as Vector          --import           Hedgehog             ( property, forAll, (===), assert )-import qualified Hedgehog.Gen         as Gen-import qualified Hedgehog.Range       as Range--import qualified Statistics.Sample    as Stats--import           Test.Tasty           ( testGroup, TestTree ) -import           Test.Tasty.Hedgehog  ( testProperty )-import           Test.Utils           ( approx )---tests :: TestTree-tests = testGroup "Data.Series.Generic.Numeric" [ testPropMean-                                                , testPropVariance-                                                , testPropStdDev-                                                ]---testPropMean :: TestTree-testPropMean -    = testProperty "mean" $ property $ do-        ms <- forAll $ Gen.list (Range.linear 0 100) (Gen.double $ Range.linearFrac (-500) 500) -        let (xs :: Series Vector Int Double) = fromList (zip [0::Int ..] ms)-        Series.length xs === length ms -        let m :: Double = Series.fold mean xs-        -- Stats.mean of an empty vector is NaN, but is 0 for Control.Foldl.mean-        case Series.length xs of-            0 -> m === 0 -            _ -> m `approx` Stats.mean (Vector.fromList ms)---testPropVariance :: TestTree-testPropVariance-    = testProperty "population variance" $ property $ do-        ms <- forAll $ Gen.list (Range.linear 0 100) (Gen.double $ Range.linearFrac (-500) 500) -        let (xs :: Series Vector Int Double) = fromList (zip [0::Int ..] ms)-        Series.length xs === length ms -        let v :: Double = Series.fold variance xs-        -- IEEE 754 specifies that NaN != NaN...-        case Series.length xs of-            0 -> assert $ isNaN v-            _ -> v `approx` Stats.fastVariance (Vector.fromList ms)---testPropStdDev :: TestTree-testPropStdDev-    = testProperty "population standard deviation" $ property $ do-        ms <- forAll $ Gen.list (Range.linear 0 100) (Gen.double $ Range.linearFrac (-500) 500) -        let (xs :: Series Vector Int Double) = fromList (zip [0::Int ..] ms)-        Series.length xs === length ms -        let d :: Double = Series.fold std xs-        -- IEEE 754 specifies that NaN != NaN...-        case Series.length xs of-            0 -> assert $ isNaN d-            _ -> d `approx` Stats.fastStdDev (Vector.fromList ms)
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,113 +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-                                      , 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)---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
− test/Test/Utils.hs
@@ -1,13 +0,0 @@--module Test.Utils ( approx ) where--import           Data.AEq             ( AEq((~==)))-import           Hedgehog             ( MonadTest, diff )-import           Hedgehog.Internal.Source ( HasCallStack, withFrozenCallStack ) ----- | Fails the test if the two arguments provided are not equal to within `epsilon`.-approx :: (MonadTest m, AEq a, Show a, HasCallStack) => a -> a -> m ()-approx x y =-  withFrozenCallStack $-    diff x (~==) y