diff --git a/src/Data/Array.purs b/src/Data/Array.purs index 0390e7e3..abd9c7b5 100644 --- a/src/Data/Array.purs +++ b/src/Data/Array.purs @@ -128,7 +128,12 @@ import Partial.Unsafe (unsafePartial) -- | Convert an `Array` into an `Unfoldable` structure. toUnfoldable :: forall f a. Unfoldable f => Array a -> f a -toUnfoldable = unfoldr $ uncons' (const Nothing) (\h t -> Just (Tuple h t)) +toUnfoldable xs = unfoldr f 0 + where + len = length xs + f i + | i < len = Just (Tuple (unsafePartial (unsafeIndex xs i)) (i+1)) + | otherwise = Nothing -- | Convert a `Foldable` structure into an `Array`. fromFoldable :: forall f a. Foldable f => f a -> Array a @@ -219,7 +224,7 @@ insertBy cmp x ys = -- | -- | Running time: `O(1)`. head :: forall a. Array a -> Maybe a -head = uncons' (const Nothing) (\x _ -> Just x) +head xs = xs !! 0 -- | Get the last element in an array, or `Nothing` if the array is empty -- | @@ -481,25 +486,37 @@ dropWhile p xs = (span p xs).rest -- | Split an array into two parts: -- | --- | 1. the longest initial subarray for which all element satisfy the specified --- | predicate +-- | 1. the longest initial subarray for which all elements satisfy the +-- | specified predicate -- | 2. the remaining elements -- | -- | ```purescript -- | span (\n -> n % 2 == 1) [1,3,2,4,5] == { init: [1,3], rest: [2,4,5] } -- | ``` +-- | +-- | Running time: `O(n)`. span :: forall a . (a -> Boolean) -> Array a -> { init :: Array a, rest :: Array a } -span p = go [] +span p arr = + case breakIndex of + Just 0 -> + { init: [], rest: arr } + Just i -> + { init: slice 0 i arr, rest: slice i (length arr) arr } + Nothing -> + { init: arr, rest: [] } where - go :: Array a -> Array a -> { init :: Array a, rest :: Array a } - go acc xs = - case uncons xs of - Just { head: x, tail: xs' } | p x -> go (x : acc) xs' - _ -> { init: reverse acc, rest: xs } + breakIndex = go 0 + go i = + -- This looks like a good opportunity to use the Monad Maybe instance, + -- but it's important to write out an explicit case expression here in + -- order to ensure that TCO is triggered. + case index arr i of + Just x -> if p x then go (i+1) else Just i + Nothing -> Nothing -- | Group equal, consecutive elements of an array into arrays. -- | diff --git a/test/Test/Data/Array.purs b/test/Test/Data/Array.purs index 04ba492f..c36f42a4 100644 --- a/test/Test/Data/Array.purs +++ b/test/Test/Data/Array.purs @@ -5,8 +5,8 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (log, CONSOLE) -import Data.Array (range, replicate, foldM, unzip, zip, zipWithA, zipWith, intersectBy, intersect, (\\), deleteBy, delete, unionBy, union, nubBy, nub, groupBy, group', group, span, dropWhile, drop, takeWhile, take, sortBy, sort, catMaybes, mapMaybe, mapWithIndex, filterM, filter, concat, concatMap, reverse, alterAt, modifyAt, updateAt, deleteAt, insertAt, findLastIndex, findIndex, elemLastIndex, elemIndex, (!!), uncons, init, tail, last, head, insertBy, insert, snoc, (:), length, null, singleton, fromFoldable) -import Data.Foldable (for_, foldMapDefaultR, class Foldable, all) +import Data.Array (range, replicate, foldM, unzip, zip, zipWithA, zipWith, intersectBy, intersect, (\\), deleteBy, delete, unionBy, union, nubBy, nub, groupBy, group', group, span, dropWhile, drop, takeWhile, take, sortBy, sort, catMaybes, mapMaybe, mapWithIndex, filterM, filter, concat, concatMap, reverse, alterAt, modifyAt, updateAt, deleteAt, insertAt, findLastIndex, findIndex, elemLastIndex, elemIndex, (!!), uncons, init, tail, last, head, insertBy, insert, snoc, (:), length, null, singleton, fromFoldable, toUnfoldable) +import Data.Foldable (for_, foldMapDefaultR, class Foldable, all, traverse_) import Data.Maybe (Maybe(..), isNothing, fromJust) import Data.NonEmpty ((:|)) import Data.NonEmpty as NE @@ -237,9 +237,30 @@ testArray = do assert $ (drop (-2) [1, 2, 3]) == [1, 2, 3] log "span should split an array in two based on a predicate" - let spanResult = span (_ < 4) [1, 2, 3, 4, 5, 6, 7] - assert $ spanResult.init == [1, 2, 3] - assert $ spanResult.rest == [4, 5, 6, 7] + let testSpan { p, input, init_, rest_ } = do + let result = span p input + assert $ result.init == init_ + assert $ result.rest == rest_ + + let oneToSeven = [1, 2, 3, 4, 5, 6, 7] + testSpan { p: (_ < 4), input: oneToSeven, init_: [1, 2, 3], rest_: [4, 5, 6, 7] } + + log "span with all elements satisfying the predicate" + testSpan { p: const true, input: oneToSeven, init_: oneToSeven, rest_: [] } + + log "span with no elements satisfying the predicate" + testSpan { p: const false, input: oneToSeven, init_: [], rest_: oneToSeven } + + log "span with large inputs: 10000" + let testBigSpan n = + testSpan { p: (_ < n), input: range 1 n, init_: range 1 (n-1), rest_: [n] } + testBigSpan 10000 + + log "span with large inputs: 40000" + testBigSpan 40000 + + log "span with large inputs: 100000" + testBigSpan 100000 log "group should group consecutive equal elements into arrays" assert $ group [1, 2, 2, 3, 3, 3, 1] == [NE.singleton 1, 2 :| [2], 3:| [3, 3], NE.singleton 1] @@ -308,6 +329,17 @@ testArray = do assert $ length arr == n assert $ all (_ == elem) arr + log "toUnfoldable" + let toUnfoldableId xs = toUnfoldable xs == xs + traverse_ (assert <<< toUnfoldableId) + [ [] + , [1] + , [1,2,3] + , [2,3,1] + , [4,0,0,1,25,36,458,5842,23757] + ] + + nil :: Array Int nil = []