From 82a50c0b11e67f40ffc6a603189bb222f2a9c97f Mon Sep 17 00:00:00 2001 From: Florian Witteler Date: Mon, 12 Apr 2021 14:12:15 +0200 Subject: [PATCH 1/3] added creator-function rangeWithStep and window-functions sliding and slidingWithSizeAndStep --- src/Data/Array.purs | 72 +++++++++++++++++++++++++++++++++++++++ test/Test/Data/Array.purs | 15 ++++++++ 2 files changed, 87 insertions(+) diff --git a/src/Data/Array.purs b/src/Data/Array.purs index b7d2cf82..648b4704 100644 --- a/src/Data/Array.purs +++ b/src/Data/Array.purs @@ -32,6 +32,7 @@ module Data.Array , toUnfoldable , singleton , (..), range + , rangeWithStep , replicate , some , many @@ -87,6 +88,8 @@ module Data.Array , scanl , scanr + , sliding + , slidingWithSizeAndStep , sort , sortBy , sortWith @@ -1289,3 +1292,72 @@ unsafeIndex :: forall a. Partial => Array a -> Int -> a unsafeIndex = unsafeIndexImpl foreign import unsafeIndexImpl :: forall a. Array a -> Int -> a + +-- | Takes an arrays of length n and returns an array of Tuples with length n-1. +-- | It's a "sliding window" view of this array with a window size of 2 and a step size of 1. +-- | +-- | ```purescript +-- | sliding [1,2,3,4,5] = [(Tuple 1 2),(Tuple 2 3),(Tuple 3 4),(Tuple 4 5)] +-- | ``` +-- | +sliding :: forall a. Array a -> Array (Tuple a a) +sliding l = zip l (drop 1 l) + +-- | Takes an arrays and returns an array of arrays as a "sliding window" view of this array. +-- | Illegal arguments result in an empty Array. +-- | +-- | ```purescript +-- | > import Data.Array (range) +-- | > slidingWithSizeAndStep 3 2 (range 0 10) = [[0,1,2],[2,3,4],[4,5,6],[6,7,8],[8,9,10],[10]] +-- | > slidingWithSizeAndStep 3 3 (range 0 10) = [[0,1,2],[3,4,5],[6,7,8],[9,10]] +-- | ``` +-- | +slidingWithSizeAndStep :: forall a. Int -> Int -> Array a -> Array (Array a) +slidingWithSizeAndStep size step array = + let + maxIndex = (length array) - 1 + + indices = rangeWithStep 0 maxIndex step + + isValid = size > 0 && step > 0 + in + if isValid then + indices <#> \i -> slice i (i + size) array + else + [] + +-- | Create an array containing a range of integers with a given step size, including both endpoints. +-- | Illegal arguments result in an empty Array. +-- | +-- | ```purescript +-- | > rangeWithStep 0 6 2 = [0,2,4,6] +-- | > rangeWithStep 0 (-6) (-2) = [0,-2,-4,-6] +-- | ``` +-- | +rangeWithStep :: Int -> Int -> Int -> Array Int +rangeWithStep start endIndex step = + let + isValid = + step /= 0 + && if endIndex >= start then + step > 0 + else + step < 0 + + hasReachedEnd curr = + if step > 0 then + curr > endIndex + else + curr < endIndex + + helper :: Int -> Array Int -> Array Int + helper currentIndex acc = + if hasReachedEnd currentIndex then + acc + else + helper (currentIndex + step) (snoc acc currentIndex) + in + if isValid then + helper start [] + else + [] diff --git a/test/Test/Data/Array.purs b/test/Test/Data/Array.purs index 9325c0f8..92167a75 100644 --- a/test/Test/Data/Array.purs +++ b/test/Test/Data/Array.purs @@ -467,6 +467,21 @@ testArray = do , [4,0,0,1,25,36,458,5842,23757] ] + log "sliding" + assert $ A.sliding [ 1, 2, 3, 4, 5 ] == [ (Tuple 1 2), (Tuple 2 3), (Tuple 3 4), (Tuple 4 5) ] + + log "slidingWithStepAndSize" + assert $ A.slidingWithSizeAndStep 3 2 (A.range 0 10) == [[0,1,2],[2,3,4],[4,5,6],[6,7,8],[8,9,10],[10]] + assert $ A.slidingWithSizeAndStep 3 3 (A.range 0 10) == [[0,1,2],[3,4,5],[6,7,8],[9,10]] + assert $ A.slidingWithSizeAndStep 3 (-2) (A.range 0 10) == [] + assert $ A.slidingWithSizeAndStep (-3) (2) (A.range 0 10) == [] + + log "rangeWithStep" + assert $ A.rangeWithStep 0 6 2 == [ 0, 2, 4, 6 ] + assert $ A.rangeWithStep 0 (-6) (-2) == [ 0, -2, -4, -6 ] + assert $ A.rangeWithStep 0 6 (-2) == [] + assert $ A.rangeWithStep 0 (-6) (2) == [] + nea :: Array ~> NEA.NonEmptyArray nea = unsafePartial fromJust <<< NEA.fromArray From 54782cdea8611830864baca3651f0144073f30c7 Mon Sep 17 00:00:00 2001 From: Florian Witteler Date: Sun, 18 Apr 2021 12:39:47 +0200 Subject: [PATCH 2/3] introduced `rangeWithStep'`, that works on a mutable array and takes a mapping function --- src/Data/Array.purs | 89 +++++++++++++++++++++------------------ test/Test/Data/Array.purs | 14 +++--- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/Data/Array.purs b/src/Data/Array.purs index 648b4704..b28dfa89 100644 --- a/src/Data/Array.purs +++ b/src/Data/Array.purs @@ -33,6 +33,7 @@ module Data.Array , singleton , (..), range , rangeWithStep + , rangeWithStep' , replicate , some , many @@ -89,7 +90,7 @@ module Data.Array , scanr , sliding - , slidingWithSizeAndStep + , slidingSizeStep , sort , sortBy , sortWith @@ -1308,23 +1309,19 @@ sliding l = zip l (drop 1 l) -- | -- | ```purescript -- | > import Data.Array (range) --- | > slidingWithSizeAndStep 3 2 (range 0 10) = [[0,1,2],[2,3,4],[4,5,6],[6,7,8],[8,9,10],[10]] --- | > slidingWithSizeAndStep 3 3 (range 0 10) = [[0,1,2],[3,4,5],[6,7,8],[9,10]] +-- | > slidingSizeStep 3 2 (range 0 10) = [[0,1,2],[2,3,4],[4,5,6],[6,7,8],[8,9,10],[10]] +-- | > slidingSizeStep 3 3 (range 0 10) = [[0,1,2],[3,4,5],[6,7,8],[9,10]] -- | ``` -- | -slidingWithSizeAndStep :: forall a. Int -> Int -> Array a -> Array (Array a) -slidingWithSizeAndStep size step array = - let - maxIndex = (length array) - 1 +slidingSizeStep :: forall a. Int -> Int -> Array a -> Array (NonEmptyArray a) +slidingSizeStep size step array + | size <= 0 || step <= 0 = [] {- args not valid -} + | otherwise = + let + maxIndex = (length array) - 1 + in + rangeWithStep' 0 maxIndex step (\i -> NonEmptyArray (slice i (i + size) array)) - indices = rangeWithStep 0 maxIndex step - - isValid = size > 0 && step > 0 - in - if isValid then - indices <#> \i -> slice i (i + size) array - else - [] -- | Create an array containing a range of integers with a given step size, including both endpoints. -- | Illegal arguments result in an empty Array. @@ -1335,29 +1332,39 @@ slidingWithSizeAndStep size step array = -- | ``` -- | rangeWithStep :: Int -> Int -> Int -> Array Int -rangeWithStep start endIndex step = - let - isValid = - step /= 0 - && if endIndex >= start then - step > 0 - else - step < 0 - - hasReachedEnd curr = - if step > 0 then - curr > endIndex - else - curr < endIndex - - helper :: Int -> Array Int -> Array Int - helper currentIndex acc = - if hasReachedEnd currentIndex then - acc - else - helper (currentIndex + step) (snoc acc currentIndex) - in - if isValid then - helper start [] - else - [] +rangeWithStep start end step = rangeWithStep' start end step identity + +-- | Helper function to produce an array of elements like `rangeWithStep` with start, end and step, but immediatelly mapping over the result to work on one single mutable array. +-- | +-- | ```purescript +-- | > rangeWithStep' 0 6 2 identity = [0,2,4,6] +-- | > rangeWithStep' 0 6 2 (add 3) = [3,5,7,9] +-- | ``` +rangeWithStep' :: forall t. Int -> Int -> Int -> (Int -> t) -> Array t +rangeWithStep' start end step fn = + STA.run + ( do + let + isValid = + step /= 0 + && if end >= start then + step > 0 + else + step < 0 + + hasReachedEnd current = + if step > 0 then + current > end + else + current < end + + helper current acc = + if hasReachedEnd current then + pure acc + else do + void $ STA.push (fn current) acc + helper (current + step) acc + arr <- STA.new + void $ helper start arr + pure arr + ) diff --git a/test/Test/Data/Array.purs b/test/Test/Data/Array.purs index 92167a75..005665ea 100644 --- a/test/Test/Data/Array.purs +++ b/test/Test/Data/Array.purs @@ -470,11 +470,11 @@ testArray = do log "sliding" assert $ A.sliding [ 1, 2, 3, 4, 5 ] == [ (Tuple 1 2), (Tuple 2 3), (Tuple 3 4), (Tuple 4 5) ] - log "slidingWithStepAndSize" - assert $ A.slidingWithSizeAndStep 3 2 (A.range 0 10) == [[0,1,2],[2,3,4],[4,5,6],[6,7,8],[8,9,10],[10]] - assert $ A.slidingWithSizeAndStep 3 3 (A.range 0 10) == [[0,1,2],[3,4,5],[6,7,8],[9,10]] - assert $ A.slidingWithSizeAndStep 3 (-2) (A.range 0 10) == [] - assert $ A.slidingWithSizeAndStep (-3) (2) (A.range 0 10) == [] + log "slidingSizeStep" + assert $ A.slidingSizeStep 3 2 (A.range 0 10) == ([[0,1,2],[2,3,4],[4,5,6],[6,7,8],[8,9,10],[10]] <#> nea) + assert $ A.slidingSizeStep 3 3 (A.range 0 10) == ([[0,1,2],[3,4,5],[6,7,8],[9,10]] <#> nea) + assert $ A.slidingSizeStep 3 (-2) (A.range 0 10) == [] + assert $ A.slidingSizeStep (-3) (2) (A.range 0 10) == [] log "rangeWithStep" assert $ A.rangeWithStep 0 6 2 == [ 0, 2, 4, 6 ] @@ -482,6 +482,10 @@ testArray = do assert $ A.rangeWithStep 0 6 (-2) == [] assert $ A.rangeWithStep 0 (-6) (2) == [] + log "rangeWithStep'" + assert $ A.rangeWithStep 0 6 2 == A.rangeWithStep' 0 6 2 identity + assert $ A.rangeWithStep' 0 6 2 (add 3) == [3, 5, 7, 9] + nea :: Array ~> NEA.NonEmptyArray nea = unsafePartial fromJust <<< NEA.fromArray From 0660bdeec853a201aadf83059e8bda2e100cb6ae Mon Sep 17 00:00:00 2001 From: Florian Witteler Date: Sun, 18 Apr 2021 18:10:52 +0200 Subject: [PATCH 3/3] improved docs and tests based on code review --- src/Data/Array.purs | 67 +++++++++++++++++++++------------------ test/Test/Data/Array.purs | 3 ++ 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/Data/Array.purs b/src/Data/Array.purs index b28dfa89..bcde7c78 100644 --- a/src/Data/Array.purs +++ b/src/Data/Array.purs @@ -187,6 +187,7 @@ singleton :: forall a. a -> Array a singleton a = [a] -- | Create an array containing a range of integers, including both endpoints. +-- | If you want to control the step-size, see `rangeWithStep` -- | ```purescript -- | range 2 5 = [2, 3, 4, 5] -- | ``` @@ -1294,8 +1295,9 @@ unsafeIndex = unsafeIndexImpl foreign import unsafeIndexImpl :: forall a. Array a -> Int -> a --- | Takes an arrays of length n and returns an array of Tuples with length n-1. --- | It's a "sliding window" view of this array with a window size of 2 and a step size of 1. +-- | Returns an array where each value represents a "sliding window" of the given array with a window of size 2 and a step size of 1. +-- | +-- | If you need more control over the size of the window and how far to step before making a new window, see `slidingSizeStep` -- | -- | ```purescript -- | sliding [1,2,3,4,5] = [(Tuple 1 2),(Tuple 2 3),(Tuple 3 4),(Tuple 4 5)] @@ -1307,10 +1309,13 @@ sliding l = zip l (drop 1 l) -- | Takes an arrays and returns an array of arrays as a "sliding window" view of this array. -- | Illegal arguments result in an empty Array. -- | +-- | As you can see in the example below, the last window might not get filled completely. +-- | Its size `s` will be `1 <= s <= size`. +-- | -- | ```purescript -- | > import Data.Array (range) --- | > slidingSizeStep 3 2 (range 0 10) = [[0,1,2],[2,3,4],[4,5,6],[6,7,8],[8,9,10],[10]] --- | > slidingSizeStep 3 3 (range 0 10) = [[0,1,2],[3,4,5],[6,7,8],[9,10]] +-- | > slidingSizeStep 3 2 [0,1,2,3,4,5,6,7,8,9,10] = [[0,1,2],[2,3,4],[4,5,6],[6,7,8],[8,9,10],[10]] +-- | > slidingSizeStep 3 3 [0,1,2,3,4,5,6,7,8,9,10] = [[0,1,2],[3,4,5],[6,7,8],[9,10]] -- | ``` -- | slidingSizeStep :: forall a. Int -> Int -> Array a -> Array (NonEmptyArray a) @@ -1334,37 +1339,37 @@ slidingSizeStep size step array rangeWithStep :: Int -> Int -> Int -> Array Int rangeWithStep start end step = rangeWithStep' start end step identity --- | Helper function to produce an array of elements like `rangeWithStep` with start, end and step, but immediatelly mapping over the result to work on one single mutable array. +-- | Works just like rangeWithStep, but also uses a function to map each integer. +-- | `rangeWithStep' start end step f` is the same as `map f $ rangeWithStep start end step`, +-- | but without the extra intermediate array allocation. -- | -- | ```purescript -- | > rangeWithStep' 0 6 2 identity = [0,2,4,6] -- | > rangeWithStep' 0 6 2 (add 3) = [3,5,7,9] +-- | > rangeWithStep' 0 (-6) (-2) (add 3) = [3,1,-1,-3] -- | ``` rangeWithStep' :: forall t. Int -> Int -> Int -> (Int -> t) -> Array t rangeWithStep' start end step fn = - STA.run - ( do - let - isValid = - step /= 0 - && if end >= start then - step > 0 - else - step < 0 - - hasReachedEnd current = - if step > 0 then - current > end - else - current < end - - helper current acc = - if hasReachedEnd current then - pure acc - else do - void $ STA.push (fn current) acc - helper (current + step) acc - arr <- STA.new - void $ helper start arr - pure arr - ) + if not isValid + then [] + else STA.run do + let + helper current acc = + if hasReachedEnd current then + pure acc + else do + void $ STA.push (fn current) acc + helper (current + step) acc + arr <- STA.new + _ <- helper start arr + pure arr + where + isValid = + step /= 0 && + if end >= start + then step > 0 + else step < 0 + hasReachedEnd current = + if step > 0 + then current > end + else current < end diff --git a/test/Test/Data/Array.purs b/test/Test/Data/Array.purs index 005665ea..3e8d6640 100644 --- a/test/Test/Data/Array.purs +++ b/test/Test/Data/Array.purs @@ -469,8 +469,10 @@ testArray = do log "sliding" assert $ A.sliding [ 1, 2, 3, 4, 5 ] == [ (Tuple 1 2), (Tuple 2 3), (Tuple 3 4), (Tuple 4 5) ] + assert $ A.sliding nil == [] log "slidingSizeStep" + assert $ A.slidingSizeStep 2 1 (A.range 0 4) == ([[0,1],[1,2],[2,3],[3,4],[4]] <#> nea) assert $ A.slidingSizeStep 3 2 (A.range 0 10) == ([[0,1,2],[2,3,4],[4,5,6],[6,7,8],[8,9,10],[10]] <#> nea) assert $ A.slidingSizeStep 3 3 (A.range 0 10) == ([[0,1,2],[3,4,5],[6,7,8],[9,10]] <#> nea) assert $ A.slidingSizeStep 3 (-2) (A.range 0 10) == [] @@ -485,6 +487,7 @@ testArray = do log "rangeWithStep'" assert $ A.rangeWithStep 0 6 2 == A.rangeWithStep' 0 6 2 identity assert $ A.rangeWithStep' 0 6 2 (add 3) == [3, 5, 7, 9] + assert $ A.rangeWithStep' 0 (-6) (-2) (add 3) == [ 3, 1, -1, -3 ] nea :: Array ~> NEA.NonEmptyArray nea = unsafePartial fromJust <<< NEA.fromArray