Skip to content

Commit c5ae83f

Browse files
Add short circuiting functions of elem, notElem, find, findMap, scanl, and scanr (#189)
* Implement monomorphic version of find that short circuits * Implement monomorphic short-circuiting elem and notElem * Implement monomorphic short-circuiting findMap * Implement monomorphic short-circuiting scanl/scanr * Add docs to elem and notElem * Remove polymorphic scanl/scanr from exports * Create a sized array in scanl/scanr * Add test for cases where more than one elem satisifes predicate * Replace scanl/scanr aliases with FFI implementations * Verify findMap: first elem is returned even if later elem satisfies pred * Verify scanl/r returns same results as Foldable counterpart * Import scanl/scanr from correct module
1 parent 131a232 commit c5ae83f

File tree

3 files changed

+136
-3
lines changed

3 files changed

+136
-3
lines changed

src/Data/Array.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ exports.indexImpl = function (just) {
107107
};
108108
};
109109

110+
exports.findMapImpl = function (nothing) {
111+
return function (isJust) {
112+
return function (f) {
113+
return function (xs) {
114+
for (var i = 0; i < xs.length; i++) {
115+
var result = f(xs[i]);
116+
if (isJust(result)) return result;
117+
}
118+
return nothing;
119+
};
120+
};
121+
};
122+
};
123+
110124
exports.findIndexImpl = function (just) {
111125
return function (nothing) {
112126
return function (f) {
@@ -222,6 +236,36 @@ exports.partition = function (f) {
222236
};
223237
};
224238

239+
exports.scanl = function (f) {
240+
return function (b) {
241+
return function (xs) {
242+
var len = xs.length;
243+
var acc = b;
244+
var out = new Array(len);
245+
for (var i = 0; i < len; i++) {
246+
acc = f(acc)(xs[i]);
247+
out[i] = acc;
248+
}
249+
return out;
250+
};
251+
};
252+
};
253+
254+
exports.scanr = function (f) {
255+
return function (b) {
256+
return function (xs) {
257+
var len = xs.length;
258+
var acc = b;
259+
var out = new Array(len);
260+
for (var i = len - 1; i >= 0; i--) {
261+
acc = f(xs[i])(acc);
262+
out[i] = acc;
263+
}
264+
return out;
265+
};
266+
};
267+
};
268+
225269
//------------------------------------------------------------------------------
226270
// Sorting ---------------------------------------------------------------------
227271
//------------------------------------------------------------------------------

src/Data/Array.purs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ module Data.Array
5252
, unsnoc
5353

5454
, (!!), index
55+
, elem
56+
, notElem
5557
, elemIndex
5658
, elemLastIndex
59+
, find
60+
, findMap
5761
, findIndex
5862
, findLastIndex
5963
, insertAt
@@ -75,6 +79,8 @@ module Data.Array
7579
, mapMaybe
7680
, catMaybes
7781
, mapWithIndex
82+
, scanl
83+
, scanr
7884

7985
, sort
8086
, sortBy
@@ -128,9 +134,8 @@ import Data.Array.NonEmpty.Internal (NonEmptyArray(..))
128134
import Data.Array.ST as STA
129135
import Data.Array.ST.Iterator as STAI
130136
import Data.Foldable (class Foldable, foldl, foldr, traverse_)
131-
import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports
132-
import Data.Maybe (Maybe(..), maybe, isJust, fromJust)
133-
import Data.Traversable (scanl, scanr) as Exports
137+
import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, any, all) as Exports
138+
import Data.Maybe (Maybe(..), maybe, isJust, fromJust, isNothing)
134139
import Data.Traversable (sequence, traverse)
135140
import Data.Tuple (Tuple(..), fst, snd)
136141
import Data.Unfoldable (class Unfoldable, unfoldr)
@@ -399,6 +404,14 @@ foreign import indexImpl
399404
-- |
400405
infixl 8 index as !!
401406

407+
-- | Returns true if the array has the given element.
408+
elem :: forall a. Eq a => a -> Array a -> Boolean
409+
elem a arr = isJust $ elemIndex a arr
410+
411+
-- | Returns true if the array does not have the given element.
412+
notElem :: forall a. Eq a => a -> Array a -> Boolean
413+
notElem a arr = isNothing $ elemIndex a arr
414+
402415
-- | Find the index of the first element equal to the specified element.
403416
-- |
404417
-- | ```purescript
@@ -419,6 +432,28 @@ elemIndex x = findIndex (_ == x)
419432
elemLastIndex :: forall a. Eq a => a -> Array a -> Maybe Int
420433
elemLastIndex x = findLastIndex (_ == x)
421434

435+
-- | Find the first element for which a predicate holds.
436+
-- |
437+
-- | ```purescript
438+
-- | findIndex (contains $ Pattern "b") ["a", "bb", "b", "d"] = Just "bb"
439+
-- | findIndex (contains $ Pattern "x") ["a", "bb", "b", "d"] = Nothing
440+
-- | ```
441+
find :: forall a. (a -> Boolean) -> Array a -> Maybe a
442+
find f xs = unsafePartial (unsafeIndex xs) <$> findIndex f xs
443+
444+
-- | Find the first element in a data structure which satisfies
445+
-- | a predicate mapping.
446+
findMap :: forall a b. (a -> Maybe b) -> Array a -> Maybe b
447+
findMap = findMapImpl Nothing isJust
448+
449+
foreign import findMapImpl
450+
:: forall a b
451+
. (forall c. Maybe c)
452+
-> (forall c. Maybe c -> Boolean)
453+
-> (a -> Maybe b)
454+
-> Array a
455+
-> Maybe b
456+
422457
-- | Find the first index for which a predicate holds.
423458
-- |
424459
-- | ```purescript
@@ -724,6 +759,26 @@ modifyAtIndices :: forall t a. Foldable t => t Int -> (a -> a) -> Array a -> Arr
724759
modifyAtIndices is f xs =
725760
ST.run (STA.withArray (\res -> traverse_ (\i -> STA.modify i f res) is) xs)
726761

762+
-- | Fold a data structure from the left, keeping all intermediate results
763+
-- | instead of only the final result. Note that the initial value does not
764+
-- | appear in the result (unlike Haskell's `Prelude.scanl`).
765+
-- |
766+
-- | ```
767+
-- | scanl (+) 0 [1,2,3] = [1,3,6]
768+
-- | scanl (-) 10 [1,2,3] = [9,7,4]
769+
-- | ```
770+
foreign import scanl :: forall a b. (b -> a -> b) -> b -> Array a -> Array b
771+
772+
-- | Fold a data structure from the right, keeping all intermediate results
773+
-- | instead of only the final result. Note that the initial value does not
774+
-- | appear in the result (unlike Haskell's `Prelude.scanr`).
775+
-- |
776+
-- | ```
777+
-- | scanr (+) 0 [1,2,3] = [6,5,3]
778+
-- | scanr (flip (-)) 10 [1,2,3] = [4,5,7]
779+
-- | ```
780+
foreign import scanr :: forall a b. (a -> b -> b) -> b -> Array a -> Array b
781+
727782
--------------------------------------------------------------------------------
728783
-- Sorting ---------------------------------------------------------------------
729784
--------------------------------------------------------------------------------

test/Test/Data/Array.purs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Data.Array as A
77
import Data.Array.NonEmpty as NEA
88
import Data.Const (Const(..))
99
import Data.Foldable (for_, foldMapDefaultR, class Foldable, all, traverse_)
10+
import Data.Traversable (scanl, scanr)
1011
import Data.Maybe (Maybe(..), isNothing, fromJust)
1112
import Data.Tuple (Tuple(..))
1213
import Data.Unfoldable (replicateA)
@@ -131,6 +132,14 @@ testArray = do
131132
assert $ [1, 2, 3] !! 6 == Nothing
132133
assert $ [1, 2, 3] !! (-1) == Nothing
133134

135+
log "elem should return true if the array contains the given element at least once"
136+
assert $ (A.elem 1 [1, 2, 1]) == true
137+
assert $ (A.elem 4 [1, 2, 1]) == false
138+
139+
log "notElem should return true if the array does not contain the given element"
140+
assert $ (A.notElem 1 [1, 2, 1]) == false
141+
assert $ (A.notElem 4 [1, 2, 1]) == true
142+
134143
log "elemIndex should return the index of an item that a predicate returns true for in an array"
135144
assert $ (A.elemIndex 1 [1, 2, 1]) == Just 0
136145
assert $ (A.elemIndex 4 [1, 2, 1]) == Nothing
@@ -139,6 +148,15 @@ testArray = do
139148
assert $ (A.elemLastIndex 1 [1, 2, 1]) == Just 2
140149
assert $ (A.elemLastIndex 4 [1, 2, 1]) == Nothing
141150

151+
log "find should return the first element for which a predicate returns true in an array"
152+
assert $ (A.find (_ /= 1) [1, 2, 1]) == Just 2
153+
assert $ (A.find (_ == 3) [1, 2, 1]) == Nothing
154+
155+
log "findMap should return the mapping of the first element that satisfies the given predicate"
156+
assert $ (A.findMap (\x -> if x > 3 then Just x else Nothing) [1, 2, 4]) == Just 4
157+
assert $ (A.findMap (\x -> if x > 3 then Just x else Nothing) [1, 2, 1]) == Nothing
158+
assert $ (A.findMap (\x -> if x > 3 then Just x else Nothing) [4, 1, 5]) == Just 4
159+
142160
log "findIndex should return the index of an item that a predicate returns true for in an array"
143161
assert $ (A.findIndex (_ /= 1) [1, 2, 1]) == Just 1
144162
assert $ (A.findIndex (_ == 3) [1, 2, 1]) == Nothing
@@ -245,6 +263,22 @@ testArray = do
245263
assert $ A.modifyAtIndices [0, 2, 8] not [true, true, true, true] ==
246264
[false, true, false, true]
247265

266+
log "scanl should return an array that stores the accumulated value at each step"
267+
assert $ A.scanl (+) 0 [1,2,3] == [1, 3, 6]
268+
assert $ A.scanl (-) 10 [1,2,3] == [9, 7, 4]
269+
270+
log "scanl should return the same results as its Foldable counterpart"
271+
assert $ A.scanl (+) 0 [1,2,3] == scanl (+) 0 [1,2,3]
272+
assert $ A.scanl (-) 10 [1,2,3] == scanl (-) 10 [1,2,3]
273+
274+
log "scanr should return an array that stores the accumulated value at each step"
275+
assert $ A.scanr (+) 0 [1,2,3] == [6,5,3]
276+
assert $ A.scanr (flip (-)) 10 [1,2,3] == [4,5,7]
277+
278+
log "scanr should return the same results as its Foldable counterpart"
279+
assert $ A.scanr (+) 0 [1,2,3] == scanr (+) 0 [1,2,3]
280+
assert $ A.scanr (flip (-)) 10 [1,2,3] == scanr (flip (-)) 10 [1,2,3]
281+
248282
log "sort should reorder a list into ascending order based on the result of compare"
249283
assert $ A.sort [1, 3, 2, 5, 6, 4] == [1, 2, 3, 4, 5, 6]
250284

0 commit comments

Comments
 (0)