packages feed

mutable-fenwick (empty) → 0.1.0.0

raw patch · 9 files changed

+638/−0 lines, 9 filesdep +arraydep +basedep +commutative-semigroups

Dependencies added: array, base, commutative-semigroups, hspec, monoid-subclasses, mutable-fenwick, vector

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for mutable-fenwick++## 0.1.0.0 -- 2025-04-07++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2025 Parsa Alizadeh++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.
+ README.md view
@@ -0,0 +1,22 @@+# mutable-fenwick++This package provides an implementation of mutable+[Fenwick trees](https://en.wikipedia.org/wiki/Fenwick_tree) in Haskell.++## Features++It is maximally generic. Each operation of Fenwick tree is implemented using a subset of constraints+from `Semigroup`, `Monoid`, or `Commutative`, chosen carefully based on the nature of each+operation. This is mostly possible due to how Haskell typeclasses work, and provides different+functionality based on the constraints provided by the underlying element type.++It is fast and efficient. Every operation is marked as inline, meaning that they will be optimized+for the given element type. With `ArrayC` and `VectorC` from this package, it is possible to use+unboxed arrays and vectors for newtypes that implement a custom algebra (e.g. `Sum`, `Product` or+`Xor`). An implementation using this library can be as fast as a C/C++ implementation.++It is the only Haskell library (I believe, as of this date) that provides Fenwick trees. +- The [FenwickTree](https://hackage.haskell.org/package/FenwickTree) package is more similar to a+  Segment tree, and it does not have a generic interface for the data structure.+- The [binary-indexed-tree](https://hackage.haskell.org/package/binary-indexed-tree) package+  has an interface for ST monad, but the implementation is only limited to `Sum` monoid.
+ mutable-fenwick.cabal view
@@ -0,0 +1,68 @@+cabal-version:      2.4+name:               mutable-fenwick+version:            0.1.0.0++synopsis: Mutable Fenwick trees+description:+    This package provides an implementation of mutable+    [Fenwick trees](https://en.wikipedia.org/wiki/Fenwick_tree).+    .+    It is maximally generic. Each operation of Fenwick tree is implemented using a subset of constraints+    from @Semigroup@, @Monoid@, or @Commutative@, chosen based on the nature of each+    operation.+    .+    It is fast and efficient. With @ArrayC@ and @VectorC@ from this package, it is possible to use+    unboxed arrays and vectors for newtypes that implement a custom algebra (e.g. @Sum@, @Product@ or+    @Xor@). An implementation using this library can be as fast as a C/C++ implementation.++homepage: https://github.com/ParsaAlizadeh/fenwick-tree+bug-reports: https://github.com/ParsaAlizadeh/fenwick-tree/issues++license:            MIT+license-file:       LICENSE+author:             Parsa Alizadeh+maintainer:         parsa.alizadeh1@gmail.com+copyright:          (c) Parsa Alizade, 2025+category:           Data+extra-source-files:+    README.md+    CHANGELOG.md++tested-with: GHC == 9.2.8 || == 9.4.8++source-repository head+    type:     git+    location: https://github.com/ParsaAlizadeh/fenwick-tree.git++library+    hs-source-dirs:   src+    default-language: Haskell2010+    exposed-modules:+        Data.Array.ArrayC,+        Data.Vector.VectorC,+        Data.Fenwick.Array,+        Data.Fenwick.Vector++    -- Modules included in this library but not exported.+    -- other-modules:++    build-depends:+        base                   >= 4.17.2 && < 4.18,+        array                  >= 0.5.4 && < 0.6,+        commutative-semigroups >= 0.2.0 && < 0.3,+        monoid-subclasses      >= 1.2.6 && < 1.3,+        vector                 >= 0.13.1 && < 0.14,+++test-suite test-fenwick+    type: exitcode-stdio-1.0+    hs-source-dirs: tests+    default-language: Haskell2010+    build-depends: +        base,+        hspec,+        mutable-fenwick,+        array,+        vector,+    main-is: FenwickTest.hs+
+ src/Data/Array/ArrayC.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- | Module       : Data.Array.ArrayC+-- +-- This modulo provides 'ArrayC', which given a existing 'MArray' implementation and a+-- representation type, it stores elements by coercing the element type into representation type.+-- Using this, we can have elements with algebraic structures (like 'Data.Monoid.Monoid' used in+-- this package) but stored in efficient unboxed arrays.+module Data.Array.ArrayC ( ArrayC ) where++import Data.Array.MArray+import Data.Array.Base hiding (array, elems)+import Data.Coerce++-- | Given an array type (e.g. 'Data.Array.IO.IOUArray') and a representation type (e.g. 'Int'), the+-- type constructor @'ArrayC' 'Data.Array.IO.IOUArray' 'Int'@ is a valid array type, that can store+-- elements that are coercible to 'Int' (the representation type). The same type can be used for+-- immutable and mutable arrays. Use functions from 'Data.Array.IArray' and 'Data.Array.MArray' to+-- create and modify the array.+newtype ArrayC array rep ix elem = ArrayC (array ix rep)++instance (IArray array rep, Coercible rep elem) => IArray (ArrayC array rep) elem where+  bounds (ArrayC array) = bounds array+  {-# INLINE bounds #-}++  numElements (ArrayC array) = numElements array+  {-# INLINE numElements #-}++  unsafeArray ix elems = ArrayC $ unsafeArray ix (coerce elems)+  {-# INLINE unsafeArray #-}++  unsafeAt (ArrayC array) ix = coerce $ unsafeAt array ix+  {-# INLINE unsafeAt #-}++  unsafeReplace (ArrayC array) elems = ArrayC $ unsafeReplace array (coerce elems)+  {-# INLINE unsafeReplace #-}++  unsafeAccum f (ArrayC array) elems = ArrayC $ unsafeAccum (coerce f) array elems+  {-# INLINE unsafeAccum #-}++  unsafeAccumArray f e ix elems = ArrayC $ unsafeAccumArray (coerce f) (coerce e) ix elems+  {-# INLINE unsafeAccumArray #-}++instance (Monad m, MArray arr r m, Coercible r e) => MArray (ArrayC arr r) e m where+  getBounds (ArrayC arr) = getBounds arr+  {-# INLINE getBounds #-}++  getNumElements (ArrayC arr) = getNumElements arr+  {-# INLINE getNumElements #-}++  newArray ix e = ArrayC <$> newArray ix (coerce e)+  {-# INLINE newArray #-}++  newArray_ ix = ArrayC <$> newArray_ ix+  {-# INLINE newArray_ #-}++  unsafeNewArray_ ix = ArrayC <$> unsafeNewArray_ ix+  {-# INLINE unsafeNewArray_ #-}++  unsafeRead (ArrayC arr) i = coerce <$> unsafeRead arr i+  {-# INLINE unsafeRead #-}++  unsafeWrite (ArrayC arr) i e = unsafeWrite arr i (coerce e)+  {-# INLINE unsafeWrite #-}
+ src/Data/Fenwick/Array.hs view
@@ -0,0 +1,145 @@+{-# LANGUAGE BangPatterns #-}++-- | Module       : Data.Fenwick.Array+-- +-- This modulo provides mutable [Fenwick Trees](https://en.wikipedia.org/wiki/Fenwick_tree), using+-- arrays as the underlying data structure. The algebraic structure is given by 'Semigroup' and+-- 'Monoid' instances. Some of the functions require the structure to be+-- 'Data.Semigroup.Commutative.Commutative'. See @monoid-subclasses@ and @commutative-semigroups@.+-- If you want mark @Sum@ or @Product@ as @Commutative@, see @SumCommutative@ and+-- @ProductCommutative@ in the mentioned packages.++module Data.Fenwick.Array +  ( FenMArray+  , newFen+  , newAccumFen+  , newListFen+  , getSizeFen+  , addFen+  , sumPrefixFen+  , lowerBoundFen+  ) where++import Data.Array.MArray+import Control.Monad+import Data.Bits+import Data.Semigroup.Cancellative+import Data.Monoid.Cancellative+import Data.Array.Base++-- | Fenwick tree datatype. @array@ must be a type such that @array 'Int' elem@ refers to a valid+-- mutable array. Fenwick tree is assumed to be data structure over a 1-based array of size @n@.+data FenMArray array elem = FenMArray !Int !(array Int elem)++-- | least significant bit+lsb :: Int -> Int+lsb node = node .&. (-node)+{-# INLINE lsb #-}++modifyArray' :: (MArray a e m, Ix i) => a i e -> i -> (e -> e) -> m ()+modifyArray' arr i f = do+  x <- readArray arr i+  let !x' = f x+  writeArray arr i x'+{-# INLINABLE modifyArray' #-}++unsafeModifyArray' :: (MArray a e m, Ix i) => a i e -> Int -> (e -> e) -> m ()+unsafeModifyArray' arr i f = do+  x <- unsafeRead arr i+  let !x' = f x+  unsafeWrite arr i x'+{-# INLINABLE unsafeModifyArray' #-}++-- | Creates a Fenwick tree of size @n@ over 1-based array. All elements of the array are initially+-- @'mempty'@. \( O(n) \)+--+-- Using type application extension, you can specify the array and element type as the first and+-- second type argument.+-- +-- @ +-- fen <- 'newFen' \@('Data.Array.ArrayC.ArrayC' 'Data.Array.IO.IOUArray' 'Int') \@('Data.Monoid.Sum' 'Int') +-- @+newFen :: (MArray array elem m, Monoid elem) => Int -> m (FenMArray array elem)+newFen n = do+  arr <- newArray (0, n) mempty+  pure (FenMArray n arr)+{-# INLINABLE newFen #-}++-- | @newAccumFen n xs@ creates a Fenwick tree of size @n@ over 1-based array. Every item of @xs@ is+-- pair of an index and an element. The elements of the array are initialized by @'<>'@ing elements+-- for each index. This functions is faster than creating empty array using 'newFen' and adding+-- elements individually using 'addFen'. \( O(n + m) \) where \( m \) is @'Data.Foldable.length' xs@+newAccumFen :: (MArray array elem m, CommutativeMonoid elem, Foldable t) => Int -> t (Int, elem) -> m (FenMArray array elem)+newAccumFen n xs = do+  arr <- newArray (0, n) mempty+  forM_ xs $ \(i, e) -> do+    modifyArray' arr i (<> e)+  forM_ [1..n] $ \i -> do+    let j = i + lsb i+    when (j <= n) $ do+      e <- readArray arr i+      unsafeModifyArray' arr j (e <>)+  pure (FenMArray n arr)+{-# INLINABLE newAccumFen #-}++-- | Creates a Fenwick tree and initialize the array based on the elements of the list. \( O(n) \)+newListFen :: (MArray array elem m, CommutativeMonoid elem) => Int -> [elem] -> m (FenMArray array elem)+newListFen n xs = newAccumFen n $ zip [1..n] xs+{-# INLINABLE newListFen #-}++-- | Get the size of underlying array. \( O(1) \)+getSizeFen :: FenMArray array elem -> Int+getSizeFen (FenMArray n _) = n+{-# INLINE getSizeFen #-}++-- | Add a value to a cell of the array. The index must be in the range @[1, n]@. \( O(\log n) \)+addFen :: (MArray array elem m, Commutative elem) => FenMArray array elem -> Int -> elem -> m ()+addFen (FenMArray n arr) r a = go (check r) where+  check i+    | i < 1 || i > n = error "index out of range"+    | otherwise = i+  go i = when (i <= n) $ do+    unsafeModifyArray' arr i (a <>)+    go (i + lsb i)+{-# INLINABLE addFen #-}++-- | Given index @r@, get the prefix sum of elements of the array in the range @[1, r]@. It accepts+-- values of @r@ out of the range of indices, assuming that every element out of the range of array+-- is 'mempty'. \( O(\log n) \)+sumPrefixFen :: (MArray array elem m, Monoid elem) => FenMArray array elem -> Int -> m elem+sumPrefixFen (FenMArray n arr) = go mempty . min n where+  -- prefix (0, r], for 1 <= r <= n+  go !s i+    | i <= 0 = pure s+    | otherwise = do+      x <- unsafeRead arr i+      go (x <> s) (i - lsb i)+{-# INLINABLE sumPrefixFen #-}++-- | Given a prefix sum @q@, find the least index @r@ such that the prefix sum of @[1, r]@ is at+-- least @q@. Only applicable when partial sums are ordered. If the sum of the whole array is+-- less than @query@, it returns @n+1@. This functions is faster than binary search over+-- 'sumPrefixFen'. \( O(\log n) \) +lowerBoundFen :: (MArray array elem m, Monoid elem, Ord elem) => FenMArray array elem -> elem -> m Int+lowerBoundFen (FenMArray n arr) query = go root (n + 1) mempty where+  root = bit (finiteBitSize n - countLeadingZeros n - 1)+  go node+    | odd node = leaf node+    | otherwise = nonleaf node+  leaf node fallback prepend +    | node > n = pure fallback+    | otherwise = do+    nodeval <- unsafeRead arr node+    pure $ if prepend <> nodeval < query+      then fallback+      else node +  nonleaf node fallback prepend +    | node > n = go (leftOf node) fallback prepend+    | otherwise = do+    nodeval <- unsafeRead arr node+    if prepend <> nodeval < query+      then go (rightOf node) fallback (prepend <> nodeval)+      else go (leftOf node) node prepend+  leftOf node = node - (lsb node `unsafeShiftR` 1)+  rightOf node = node + (lsb node `unsafeShiftR` 1)+{-# INLINABLE lowerBoundFen #-}
+ src/Data/Fenwick/Vector.hs view
@@ -0,0 +1,136 @@+{-# LANGUAGE BangPatterns #-}++-- | Module       : Data.Fenwick.Vector+-- +-- This modulo provides mutable [Fenwick Trees](https://en.wikipedia.org/wiki/Fenwick_tree), using+-- vectors as the underlying data structure. The algebraic structure is given by 'Semigroup' and+-- 'Monoid' instances. Some of the functions require the structure to be 'Commutative'. See+-- @monoid-subclasses@ and @commutative-semigroups@. If you want mark @Sum@ or @Product@ as+-- @Commutative@, see @SumCommutative@ and @ProductCommutative@ in the mentioned packages.+--+-- I was hoping to merge this module with "Data.Fenwick.Array" under a common typeclass, although I+-- couldn't find any way through this. I may change this later, if I find the right abstractions. ++module Data.Fenwick.Vector +  ( FenMVector+  , newFen+  , newAccumFen+  , newListFen+  , getSizeFen+  , addFen+  , sumPrefixFen+  , lowerBoundFen+  ) where++import Control.Monad+import Data.Bits+import Data.Semigroup.Commutative+import Data.Monoid.Cancellative+import qualified Data.Vector.Generic.Mutable as V++-- | Fenwick tree datatype. @vector@ must be a valid mutable vector type. Fenwick tree is assumed to+-- be data structure over a 1-based array of size @n@.+data FenMVector vector s elem = FenMVector !Int !(vector s elem)++-- | least significant bit+lsb :: Int -> Int+lsb node = node .&. (-node)+{-# INLINE lsb #-}++modify' :: (V.PrimMonad m, V.MVector v a) => v (V.PrimState m) a -> (a -> a) -> Int -> m ()+modify' vec f = V.modify vec (f $!)+{-# INLINABLE modify' #-}++unsafeModify' :: (V.PrimMonad m, V.MVector v a) => v (V.PrimState m) a -> (a -> a) -> Int -> m ()+unsafeModify' vec f = V.unsafeModify vec (f $!)+{-# INLINABLE unsafeModify' #-}++-- | Creates a Fenwick tree of size @n@ over 1-based array. All elements of the array are initially+-- @'mempty'@. \( O(n) \)+--+-- Using type application extension, you can specify the vector and element type as the first and+-- second type argument.+newFen :: (V.MVector vector elem, V.PrimMonad m, Monoid elem) => Int -> m (FenMVector vector (V.PrimState m) elem)+newFen n = do+  vec <- V.replicate (n+1) mempty+  pure (FenMVector n vec)+{-# INLINABLE newFen #-}++-- | @newAccumFen n xs@ creates a Fenwick tree of size @n@ over 1-based array. Every item of @xs@ is+-- pair of an index and an element. The elements of the array are initialized by @'<>'@ing elements+-- for each index. This functions is faster than creating empty array using 'newFen' and adding+-- elements individually using 'addFen'. \( O(n + m) \) where \( m \) is @'Data.Foldable.length' xs@+newAccumFen :: (V.MVector vector elem, V.PrimMonad m, CommutativeMonoid elem, Foldable t) => Int -> t (Int, elem) -> m (FenMVector vector (V.PrimState m) elem)+newAccumFen n xs = do+  vec <- V.replicate (n + 1) mempty+  forM_ xs $ \(i, e) -> do+    modify' vec (<> e) i+  forM_ [1..n] $ \i -> do+    let j = i + lsb i+    when (j <= n) $ do+      e <- V.unsafeRead vec i+      unsafeModify' vec (e <>) j+  pure (FenMVector n vec)+{-# INLINABLE newAccumFen #-}++-- | Creates a Fenwick tree and initialize the array based on the elements of the list. \( O(n) \)+newListFen :: (V.MVector vector elem, V.PrimMonad m, CommutativeMonoid elem) => Int -> [elem] -> m (FenMVector vector (V.PrimState m) elem)+newListFen n xs = newAccumFen n $ zip [1..n] xs+{-# INLINABLE newListFen #-}++-- | Get the size of underlying array. \( O(1) \)+getSizeFen :: FenMVector vector s elem -> Int+getSizeFen (FenMVector n _) = n+{-# INLINE getSizeFen #-}++-- | Add a value to a cell of the array. The index must be in the range @[1, n]@. \( O(\log n) \)+addFen :: (V.PrimMonad m, V.MVector vector elem, Commutative elem) => FenMVector vector (V.PrimState m) elem -> Int -> elem -> m ()+addFen (FenMVector n vec) r a = go (check r) where+  check i+    | i < 1 || i > n = error "index out of range"+    | otherwise = i+  go i = when (i <= n) $ do+    unsafeModify' vec (a <>) i+    go (i + lsb i)+{-# INLINABLE addFen #-}++-- | Given index @r@, get the prefix sum of elements of the array in the range @[1, r]@. It accepts+-- values of @r@ out of the range of indices, assuming that every element out of the range of array+-- is 'mempty'. \( O(\log n) \)+sumPrefixFen :: (Monoid elem, V.PrimMonad m, V.MVector vector elem) => FenMVector vector (V.PrimState m) elem -> Int -> m elem+sumPrefixFen (FenMVector n vec) = go mempty . min n where+  -- prefix (0, r], for 1 <= r <= n+  go !s i+    | i <= 0 = pure s+    | otherwise = do+      x <- V.unsafeRead vec i+      go (x <> s) (i - lsb i)+{-# INLINABLE sumPrefixFen #-}++-- | Given a prefix sum @q@, find the least index @r@ such that the prefix sum of @[1, r]@ is at+-- least @q@. Only applicable when partial sums are ordered. If the sum of the whole array is+-- less than @query@, it returns @n+1@. This functions is faster than binary search over+-- 'sumPrefixFen'. \( O(\log n) \) +lowerBoundFen :: (Monoid elem, V.PrimMonad m, V.MVector vector elem, Ord elem) => FenMVector vector (V.PrimState m) elem -> elem -> m Int+lowerBoundFen (FenMVector n vec) query = go root (n + 1) mempty where+  root = bit (finiteBitSize n - countLeadingZeros n - 1)+  go node+    | odd node = leaf node+    | otherwise = nonleaf node+  leaf node fallback prepend +    | node > n = pure fallback+    | otherwise = do+    nodeval <- V.unsafeRead vec node+    pure $ if prepend <> nodeval < query+      then fallback+      else node +  nonleaf node fallback prepend +    | node > n = go (leftOf node) fallback prepend+    | otherwise = do+    nodeval <- V.unsafeRead vec node+    if prepend <> nodeval < query+      then go (rightOf node) fallback (prepend <> nodeval)+      else go (leftOf node) node prepend+  leftOf node = node - (lsb node `unsafeShiftR` 1)+  rightOf node = node + (lsb node `unsafeShiftR` 1)+{-# INLINABLE lowerBoundFen #-}
+ src/Data/Vector/VectorC.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | Module       : Data.Array.VectorC+-- +-- This modulo provides 'VectorC' and 'MVectorC', which given a existing vector implementation and a+-- representation type, it stores elements by coercing the element type into representation type.+-- Using this, we can have elements with algebraic structures (like 'Data.Monoid.Monoid' used in+-- this package) but stored in efficient unboxed vectors.+module Data.Vector.VectorC (+  VectorC, MVectorC+) where++import qualified Data.Vector.Generic as VG+import qualified Data.Vector.Generic.Mutable as VGM+import Data.Coerce++-- | Given a mutable vector type @vector@ and representation type @rep@, the type contructor+-- @'MVectorC' vector rep@ is a valid vector type that stores elements that are coercible to @rep@.+-- Use functions in 'Data.Vector.Generic.Mutable.MVector' to create and modify these vectors.+newtype MVectorC vector rep s elem = MVectorC (vector s rep)++-- | Immutable vectors corresponding to 'MVectorC'. Use functions in 'Data.Vector.Generic.Vector' to create and+-- modify these vectors.+newtype VectorC vector rep elem = VectorC (vector rep)++type instance VG.Mutable (VectorC vector rep) = MVectorC (VG.Mutable vector) rep++instance (Coercible elem rep, VGM.MVector vector rep) => VGM.MVector (MVectorC vector rep) elem where+  -- basicLength = coerce $ VGM.basicLength @vector @rep+  basicLength (MVectorC vec) = VGM.basicLength vec+  {-# INLINE basicLength #-}+  -- basicUnsafeSlice = coerce $ VGM.basicUnsafeSlice @VU.MVector+  basicUnsafeSlice i n (MVectorC vec) = MVectorC (VGM.basicUnsafeSlice i n vec)+  {-# INLINE basicUnsafeSlice #-}+  -- basicOverlaps = coerce $ VGM.basicOverlaps @VU.MVector+  basicOverlaps (MVectorC vec1) (MVectorC vec2) = VGM.basicOverlaps vec1 vec2+  {-# INLINE basicOverlaps #-}+  -- basicUnsafeNew = coerce $ VGM.basicUnsafeNew @VU.MVector+  basicUnsafeNew n = MVectorC <$> VGM.basicUnsafeNew n+  {-# INLINE basicUnsafeNew #-}+  -- basicInitialize = coerce $ VGM.basicInitialize @VU.MVector+  basicInitialize (MVectorC vec) = VGM.basicInitialize vec+  {-# INLINE basicInitialize #-}+  -- basicUnsafeReplicate = coerce $ VGM.basicUnsafeReplicate @VU.MVector+  basicUnsafeReplicate n a = MVectorC <$> VGM.basicUnsafeReplicate n (coerce a)+  {-# INLINE basicUnsafeReplicate #-}+  -- basicUnsafeRead = coerce $ VGM.basicUnsafeRead @VU.MVector+  basicUnsafeRead (MVectorC vec) i = coerce <$> VGM.basicUnsafeRead vec i+  {-# INLINE basicUnsafeRead #-}+  -- basicUnsafeWrite = coerce $ VGM.basicUnsafeWrite @VU.MVector+  basicUnsafeWrite (MVectorC vec) i a = VGM.basicUnsafeWrite vec i (coerce a)+  {-# INLINE basicUnsafeWrite #-}+  -- basicClear = coerce $ VGM.basicClear @VU.MVector+  basicClear (MVectorC vec) = VGM.basicClear vec+  {-# INLINE basicClear #-}+  -- basicSet = coerce $ VGM.basicSet @VU.MVector+  basicSet (MVectorC vec) a = VGM.basicSet vec (coerce a)+  {-# INLINE basicSet #-}+  -- basicUnsafeCopy = coerce $ VGM.basicUnsafeCopy @VU.MVector+  basicUnsafeCopy (MVectorC vec1) (MVectorC vec2) = VGM.basicUnsafeCopy vec1 vec2+  {-# INLINE basicUnsafeCopy #-}+  -- basicUnsafeMove = coerce $ VGM.basicUnsafeMove @VU.MVector+  basicUnsafeMove (MVectorC vec1) (MVectorC vec2) = VGM.basicUnsafeMove vec1 vec2+  {-# INLINE basicUnsafeMove #-}+  -- basicUnsafeGrow = coerce $ VGM.basicUnsafeGrow @VU.MVector+  basicUnsafeGrow (MVectorC vec) n = MVectorC <$> VGM.basicUnsafeGrow vec n+  {-# INLINE basicUnsafeGrow #-}++instance (Coercible elem rep, VG.Vector vector rep) => VG.Vector (VectorC vector rep) elem where+  basicUnsafeFreeze = coerce $ VG.basicUnsafeFreeze @vector @rep+  {-# INLINE basicUnsafeFreeze #-}+  basicUnsafeThaw = coerce $ VG.basicUnsafeThaw @vector @rep+  {-# INLINE basicUnsafeThaw #-}+  basicLength = coerce $ VG.basicLength @vector @rep+  {-# INLINE basicLength #-}+  basicUnsafeSlice = coerce $ VG.basicUnsafeSlice @vector @rep+  {-# INLINE basicUnsafeSlice #-}+  basicUnsafeIndexM = coerce $ VG.basicUnsafeIndexM @vector @rep+  {-# INLINE basicUnsafeIndexM #-}+  basicUnsafeCopy = coerce $ VG.basicUnsafeCopy @vector @rep+  {-# INLINE basicUnsafeCopy #-}+  elemseq (VectorC vec) a = VG.elemseq vec (coerce a)+  {-# INLINE elemseq #-}
+ tests/FenwickTest.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, TypeApplications, TupleSections #-}++module Main where++import Control.Monad+import Control.Monad.ST (RealWorld)+import Data.Array.ArrayC+import Data.Array.IO+import Data.List+import Data.Monoid+import Data.Proxy+import Data.Vector.VectorC+import Test.Hspec+import qualified Data.Fenwick.Array as FA+import qualified Data.Fenwick.Vector as FV+import qualified Data.Vector.Generic.Mutable as VG+import qualified Data.Vector.Unboxed.Mutable as VUM++class IsFenwick a where+  new :: Int -> IO a+  newAccum :: Foldable t => Int -> t (Int, Sum Int) -> IO a+  fromList :: Int -> [Sum Int] -> IO a+  getSize :: a -> Int+  increment :: a -> Int -> Sum Int -> IO ()+  sumPrefix :: a -> Int -> IO (Sum Int)+  lowerBound :: a -> Sum Int -> IO Int++type FenArray = FA.FenMArray (ArrayC IOUArray Int) (Sum Int)+type FenVector = FV.FenMVector (MVectorC VUM.MVector Int) RealWorld (Sum Int)++instance IsFenwick FenArray where+  new = FA.newFen+  newAccum = FA.newAccumFen+  fromList = FA.newListFen+  getSize = FA.getSizeFen+  increment = FA.addFen+  sumPrefix = FA.sumPrefixFen+  lowerBound = FA.lowerBoundFen++instance IsFenwick FenVector where+  new = FV.newFen+  newAccum = FV.newAccumFen+  fromList = FV.newListFen+  getSize = FV.getSizeFen+  increment = FV.addFen+  sumPrefix = FV.sumPrefixFen+  lowerBound = FV.lowerBoundFen++testFenwick :: forall a. IsFenwick a => Proxy a -> SpecWith ()+testFenwick _ = do+  it "has a fixed size" $ do+    fen <- new @a 100+    getSize fen `shouldBe` 100+  +  it "can compute partial sums" $ do+    let list = map Sum [3, -1, 4, 0, 9, -10, 8]+        n = length list+    fen <- fromList @a n list+    traverse (sumPrefix fen) [0..n] +      `shouldReturn` scanl (<>) mempty list+  +  it "can be used as a set" $ do+    let n = 100+        list = [34, 10, 100, 63, 12, 6, 54, 29, 83]+        m = length list+        sorted = sort list+    fen <- newAccum @a n $ map (, Sum 1) list+    forM_ (zip [1..] sorted) $ \(i, x) -> do+      lowerBound fen (Sum i) `shouldReturn` x+  +  it "can be updated" $ do+    let n = 5+        allSumPrefix fen = traverse (sumPrefix fen) [1..n]+    fen <- new @a n+    increment fen 2 (Sum 2)+    allSumPrefix fen `shouldReturn` [0, 2, 2, 2, 2]+    increment fen 5 (Sum 3)+    allSumPrefix fen `shouldReturn` [0, 2, 2, 2, 5]+    increment fen 1 (Sum (-1))+    allSumPrefix fen `shouldReturn` [-1, 1, 1, 1, 4]+  +main :: IO ()+main = hspec $ do+  describe "Data.Fenwick.Array" $+    testFenwick (Proxy @FenArray)+  describe "Data.Fenwick.Vector" $ +    testFenwick (Proxy @FenVector)