inf-backprop-0.2.0.0: src/Numeric/InfBackprop/Utils/Vector.hs
-- | Module : Numeric.InfBackprop.Instances.NumHask
-- Copyright : (C) 2025 Alexey Tochin
-- License : BSD3 (see the file LICENSE)
-- Maintainer : Alexey Tochin <Alexey.Tochin@gmail.com>
--
-- Utility functions for working with vectors.
module Numeric.InfBackprop.Utils.Vector
( fromTuple,
safeHead,
safeLast,
trimArrayHead,
trimArrayTail,
zipWith,
)
where
import Control.Monad (MonadPlus, mzero)
import Data.Bool (otherwise)
import Data.Eq (Eq, (==))
import Data.Function (($))
import qualified Data.IndexedListLiterals as DILL
import Data.Maybe (Maybe (Just, Nothing))
import Data.Ord (Ordering (EQ, GT, LT), compare)
import qualified Data.Vector.Generic as DVG
import GHC.Base (pure, (.))
-- | Converts a tuple into a Vector (`Data.Vector.Vector`).
--
-- === __Examples__
--
-- >>> import GHC.Int (Int)
-- >>> import qualified Data.Vector as DV
--
-- >>> fromTuple (1 :: Int, 2 :: Int, 3 :: Int) :: DV.Vector Int
-- [1,2,3]
fromTuple ::
(DVG.Vector v a) =>
(DILL.IndexedListLiterals input length a) =>
input ->
v a
fromTuple = DVG.fromList . DILL.toList
-- | Returns the first element of a vector safely.
-- If the vector is empty, it returns 'Nothing'.
--
-- ==== __Examples__
--
-- >>> import GHC.Int (Int)
-- >>> import Data.Vector (fromList)
--
-- >>> safeHead (fromList [1, 2, 3]) :: Maybe Int
-- Just 1
--
-- >>> safeHead (fromList []) :: Maybe Int
-- Nothing
safeHead :: (DVG.Vector v a, MonadPlus m) => v a -> m a
safeHead vec
| DVG.null vec = mzero
| otherwise = pure $ DVG.unsafeHead vec
-- | Returns the last element of a vector safely.
-- If the vector is empty, it returns 'Nothing'.
--
-- ==== __Examples__
--
-- >>> import GHC.Int (Int)
-- >>> import Data.Vector (fromList, empty)
--
-- >>> safeLast (fromList [1, 2, 3]) :: Maybe Int
-- Just 3
--
-- >>> safeLast empty :: Maybe Int
-- Nothing
safeLast :: (DVG.Vector v a, MonadPlus m) => v a -> m a
safeLast vec
| DVG.null vec = mzero
| otherwise = pure $ DVG.unsafeLast vec
-- | Removes elements from the beginning of the vector until the first element
-- is not equal to the given value.
--
-- ==== __Examples__
--
-- >>> import Data.Vector (fromList, empty)
--
-- >>> trimArrayHead 1 (fromList [1, 1, 1, 2, 3])
-- [2,3]
--
-- >>> trimArrayHead 1 empty
-- []
trimArrayHead :: (DVG.Vector v a, Eq a) => a -> v a -> v a
trimArrayHead x vec = case safeHead vec of
Nothing -> DVG.empty
Just firstVal ->
if firstVal == x
then trimArrayHead x (DVG.tail vec)
else vec
-- | Removes elements from the end of the vector until the last element
-- is not equal to the given value.
--
-- ==== __Examples__
--
-- >>> import Data.Vector (fromList, empty)
--
-- >>> trimArrayTail 3 (fromList [1, 2, 3, 3, 3])
-- [1,2]
--
-- >>> trimArrayTail 3 empty
-- []
trimArrayTail :: (DVG.Vector v a, Eq a) => a -> v a -> v a
trimArrayTail x array = case safeLast array of
Nothing -> DVG.empty
Just lastVal ->
if lastVal == x
then trimArrayTail x (DVG.init array)
else array
-- | Combines two arrays of different lengths using a custom function.
-- The resulting array has a length equal to the maximum of the two input vectors.
-- The shorter array is padded with values generated by the provided functions.
--
-- ==== __Examples__
--
-- >>> import Prelude (id, negate, (-), Int)
-- >>> import qualified Data.Vector as DV
--
-- The following example demonstrates subtracting two arrays of different lengths.
-- The shorter array is padded with zeros, and the remaining elements are processed
-- using the provided functions.
--
-- >>>:{
-- zipWith
-- (-) -- Subtract corresponding elements from the two arrays
-- id -- Keep the remaining elements of the first array unchanged
-- negate -- Negate the remaining elements of the second array
-- (DV.fromList [10, 20, 30]) -- First array
-- (DV.fromList [1, 2]) -- Second array
-- :}
-- [9,18,30]
--
-- >>> import Prelude (id, negate, (-), Int)
-- >>> import Data.Vector (fromList)
--
-- >>> let v0 :: DV.Vector Int = DV.fromList [10, 20, 30]
-- >>> let v1 :: DV.Vector Int = DV.fromList [1, 2]
-- >>> zipWith (-) id negate v0 v1
-- [9,18,30]
zipWith ::
(DVG.Vector v a, DVG.Vector v b, DVG.Vector v c) =>
(a -> b -> c) ->
(a -> c) ->
(b -> c) ->
v a ->
v b ->
v c
zipWith f g h a0 a1 = case compare l0 l1 of
EQ -> base
GT -> base DVG.++ DVG.map g (DVG.drop l1 a0)
LT -> base DVG.++ DVG.map h (DVG.drop l0 a1)
where
l0 = DVG.length a0
l1 = DVG.length a1
base = DVG.zipWith f a0 a1