From e139d4e8c6cf1cb1e201a1eb141f70063063a9e0 Mon Sep 17 00:00:00 2001 From: Harry Garrood Date: Mon, 7 Nov 2016 20:06:15 +0000 Subject: [PATCH 1/4] Make head O(1) By using index rather than uncons. --- src/Data/Array.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Array.purs b/src/Data/Array.purs index 0390e7e3..1f2e2d75 100644 --- a/src/Data/Array.purs +++ b/src/Data/Array.purs @@ -219,7 +219,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 -- | From 4c8bcd3135c1e92c0f0de1fd367da336e2c41ab0 Mon Sep 17 00:00:00 2001 From: Harry Garrood Date: Mon, 7 Nov 2016 20:39:19 +0000 Subject: [PATCH 2/4] Improve performance of 'toUnfoldable' Just use an integer index for the unfolding, as opposed to the uncons' implementation, which was constructing all of the tails of the input array. --- src/Data/Array.purs | 7 ++++++- test/Test/Data/Array.purs | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Data/Array.purs b/src/Data/Array.purs index 1f2e2d75..7981aa8d 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 diff --git a/test/Test/Data/Array.purs b/test/Test/Data/Array.purs index 04ba492f..767f5167 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 @@ -308,6 +308,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 = [] From 1c06da75e673db09c7d5bb92687a821dc2af228b Mon Sep 17 00:00:00 2001 From: Harry Garrood Date: Mon, 7 Nov 2016 22:44:32 +0000 Subject: [PATCH 3/4] Remove use of uncons in implementation of span --- src/Data/Array.purs | 24 ++++++++++++++++++------ test/Test/Data/Array.purs | 27 ++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/Data/Array.purs b/src/Data/Array.purs index 7981aa8d..5fadb4b4 100644 --- a/src/Data/Array.purs +++ b/src/Data/Array.purs @@ -493,18 +493,30 @@ dropWhile p xs = (span p xs).rest -- | ```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 767f5167..c36f42a4 100644 --- a/test/Test/Data/Array.purs +++ b/test/Test/Data/Array.purs @@ -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] From 58fa234c09a7009a4fed090f60cbd58e4602ee23 Mon Sep 17 00:00:00 2001 From: Harry Garrood Date: Mon, 7 Nov 2016 23:13:01 +0000 Subject: [PATCH 4/4] Fix typo in docs for 'span' --- src/Data/Array.purs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Array.purs b/src/Data/Array.purs index 5fadb4b4..abd9c7b5 100644 --- a/src/Data/Array.purs +++ b/src/Data/Array.purs @@ -486,8 +486,8 @@ 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