Skip to content

Commit 0f9351e

Browse files
hdgarroodpaf31
authored andcommitted
Iterator groupby (#84)
* Add Data.Array.ST.Iterator * Add runSTArray' For instances where you want to return more than just one STArray from an STArray computation. For example, a pair of STArrays (with potentially different types), or an STArray of STArrays, or even an STArray of nonempty STArrays. The possibilities are endless! Also use unsafe-coerce instead of FFI. * Faster `groupBy` Implement `groupBy` using `Data.Array.ST.Iterator`; this should make it faster. * Fix groupBy tests \x y -> odd x && odd y is not an equivalence relation! It doesn't satisfy reflexivity. * Restrict to a closed row in runSTArray
1 parent 83f7c55 commit 0f9351e

File tree

7 files changed

+171
-40
lines changed

7 files changed

+171
-40
lines changed

bower.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"purescript-st": "^2.0.0",
2323
"purescript-tailrec": "^2.0.0",
2424
"purescript-tuples": "^3.0.0",
25-
"purescript-unfoldable": "^2.0.0"
25+
"purescript-unfoldable": "^2.0.0",
26+
"purescript-unsafe-coerce": "^2.0.0"
2627
},
2728
"devDependencies": {
2829
"purescript-assert": "^2.0.0",

src/Data/Array.purs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,16 @@ import Prelude
115115
import Control.Alt ((<|>))
116116
import Control.Alternative (class Alternative)
117117
import Control.Lazy (class Lazy, defer)
118+
import Control.Monad.Eff (Eff)
118119
import Control.Monad.Rec.Class (class MonadRec, Step(..), tailRecM2)
120+
import Control.Monad.ST (ST)
119121

122+
import Data.Array.ST (STArray, emptySTArray, pushSTArray, runSTArray')
123+
import Data.Array.ST.Iterator (iterate, iterator, pushWhile)
120124
import Data.Foldable (class Foldable, foldl, foldr)
121125
import Data.Foldable (foldl, foldr, foldMap, fold, intercalate, elem, notElem, find, findMap, any, all) as Exports
122126
import Data.Maybe (Maybe(..), maybe, isJust, fromJust)
127+
import Data.Newtype (class Newtype, unwrap)
123128
import Data.NonEmpty (NonEmpty, (:|))
124129
import Data.Traversable (scanl, scanr) as Exports
125130
import Data.Traversable (sequence, traverse)
@@ -548,14 +553,24 @@ group' = group <<< sort
548553
-- | Group equal, consecutive elements of an array into arrays, using the
549554
-- | specified equivalence relation to detemine equality.
550555
groupBy :: forall a. (a -> a -> Boolean) -> Array a -> Array (NonEmpty Array a)
551-
groupBy op = go []
556+
groupBy op xs =
557+
runGroupedSTArray do
558+
result <- emptySTArray
559+
iter <- iterator (xs !! _)
560+
iterate iter \x -> do
561+
sub <- emptySTArray
562+
pushSTArray result (x :| sub)
563+
pushWhile (op x) iter sub
564+
pure result
552565
where
553-
go :: Array (NonEmpty Array a) -> Array a -> Array (NonEmpty Array a)
554-
go acc xs = case uncons xs of
555-
Just o ->
556-
let sp = span (op o.head) o.tail
557-
in go ((o.head :| sp.init) : acc) sp.rest
558-
Nothing -> reverse acc
566+
runGroupedSTArray
567+
:: forall b
568+
. (forall h. Eff (st :: ST h) (STArray h (NonEmpty (STArray h) b)))
569+
-> Array (NonEmpty Array b)
570+
runGroupedSTArray a = unwrap (runSTArray' (map Grouped a))
571+
572+
newtype Grouped a arr = Grouped (arr (NonEmpty arr a))
573+
derive instance newtypeGrouped :: Newtype (Grouped a arr) _
559574

560575
-- | Remove the duplicates from an array, creating a new array.
561576
nub :: forall a. Eq a => Array a -> Array a

src/Data/Array/ST.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
"use strict";
22

3-
exports.runSTArray = function (f) {
4-
return f;
5-
};
6-
73
exports.emptySTArray = function () {
84
return [];
95
};

src/Data/Array/ST.purs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module Data.Array.ST
66
( STArray(..)
77
, Assoc()
88
, runSTArray
9+
, runSTArray'
910
, emptySTArray
1011
, peekSTArray
1112
, pokeSTArray
@@ -16,10 +17,12 @@ module Data.Array.ST
1617
, toAssocArray
1718
) where
1819

19-
import Control.Monad.Eff (Eff)
20+
import Prelude
21+
import Control.Monad.Eff (Eff, Pure, runPure)
2022
import Control.Monad.ST (ST)
21-
2223
import Data.Maybe (Maybe(..))
24+
import Data.Newtype (class Newtype, unwrap)
25+
import Unsafe.Coerce (unsafeCoerce)
2326

2427
-- | A reference to a mutable array.
2528
-- |
@@ -33,14 +36,48 @@ foreign import data STArray :: * -> * -> *
3336
-- | An element and its index.
3437
type Assoc a = { value :: a, index :: Int }
3538

39+
newtype Id a f = Id (f a)
40+
41+
derive instance newtypeId :: Newtype (Id a f) _
42+
3643
-- | Freeze a mutable array, creating an immutable array. Use this function as you would use
3744
-- | `runST` to freeze a mutable reference.
3845
-- |
39-
-- | The rank-2 type prevents the reference from escaping the scope of `runSTArray`.
40-
foreign import runSTArray
41-
:: forall a r
42-
. (forall h. Eff (st :: ST h | r) (STArray h a))
43-
-> Eff r (Array a)
46+
-- | The rank-2 type prevents the reference from escaping the scope of `runSTArray'`,
47+
-- | and the closed row on the `Eff` computation prevents the reference from
48+
-- | escaping into other parts of your program via native effects such as `setTimeout`.
49+
-- |
50+
-- | You can also return an immutable copy of an `STArray` from an `ST` computation
51+
-- | by using `freeze` combined with `runST`. However, when possible, you should
52+
-- | prefer this function, because it is `O(1)`. By contrast, `freeze` must copy the
53+
-- | underlying array and is therefore `O(n)`.
54+
runSTArray
55+
:: forall a
56+
. (forall h. Eff (st :: ST h) (STArray h a))
57+
-> Array a
58+
runSTArray a = unwrap (runSTArray' (map Id a))
59+
60+
-- | Freeze all mutable arrays in some structure, creating a version of the
61+
-- | same structure where all mutable arrays are replaced with immutable
62+
-- | arrays. Use this function as you would use `runST` to freeze a mutable
63+
-- | reference.
64+
-- |
65+
-- | The rank-2 type prevents the reference from escaping the scope of `runSTArray'`,
66+
-- | and the closed row on the `Eff` computation prevents the reference from
67+
-- | escaping into other parts of your program via native effects such as `setTimeout`.
68+
-- |
69+
-- | You can also return an immutable copy of an `STArray` from an `ST` computation
70+
-- | by using `freeze` combined with `runST`. However, when possible, you should
71+
-- | prefer this function, because it is `O(1)`. By contrast, `freeze` must copy the
72+
-- | underlying array and is therefore `O(n)`.
73+
runSTArray'
74+
:: forall f
75+
. (forall h. Eff (st :: ST h) (f (STArray h)))
76+
-> f Array
77+
runSTArray' x = runPure (go x)
78+
where
79+
go :: (forall h. Eff (st :: ST h) (f (STArray h))) -> Pure (f Array)
80+
go = unsafeCoerce
4481

4582
-- | Create an empty mutable array.
4683
foreign import emptySTArray :: forall a h r. Eff (st :: ST h | r) (STArray h a)

src/Data/Array/ST/Iterator.purs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
module Data.Array.ST.Iterator
2+
( Iterator
3+
, iterator
4+
, iterate
5+
, next
6+
, peek
7+
, exhausted
8+
, pushWhile
9+
, pushAll
10+
) where
11+
12+
import Prelude
13+
import Control.Monad.Eff (Eff, whileE)
14+
import Control.Monad.ST (ST, STRef, newSTRef, readSTRef, writeSTRef, modifySTRef)
15+
import Data.Array.ST (STArray, pushSTArray)
16+
17+
import Data.Maybe (Maybe(..), isNothing)
18+
19+
-- | This type provides a slightly easier way of iterating over an array's
20+
-- | elements in an STArray computation, without having to keep track of
21+
-- | indices.
22+
data Iterator h a = Iterator (Int -> Maybe a) (STRef h Int)
23+
24+
-- | Make an Iterator given an indexing function into an array (or anything
25+
-- | else). If `xs :: Array a`, the standard way to create an iterator over
26+
-- | `xs` is to use `iterator (xs !! _)`, where `(!!)` comes from `Data.Array`.
27+
iterator :: forall a h r. (Int -> Maybe a) -> Eff (st :: ST h | r) (Iterator h a)
28+
iterator f =
29+
Iterator f <$> newSTRef 0
30+
31+
-- | Perform an action once for each item left in an iterator. If the action
32+
-- | itself also advances the same iterator, `iterate` will miss those items
33+
-- | out.
34+
iterate :: forall a h r. Iterator h a -> (a -> Eff (st :: ST h | r) Unit) -> Eff (st :: ST h | r) Unit
35+
iterate iter f = do
36+
break <- newSTRef false
37+
whileE (not <$> readSTRef break) do
38+
mx <- next iter
39+
case mx of
40+
Just x -> f x
41+
Nothing -> void $ writeSTRef break true
42+
43+
-- | Get the next item out of an iterator, advancing it. Returns Nothing if the
44+
-- | Iterator is exhausted.
45+
next :: forall a h r. Iterator h a -> Eff (st :: ST h | r) (Maybe a)
46+
next (Iterator f currentIndex) = do
47+
i <- readSTRef currentIndex
48+
modifySTRef currentIndex (_ + 1)
49+
pure (f i)
50+
51+
-- | Get the next item out of an iterator without advancing it.
52+
peek :: forall a h r. Iterator h a -> Eff (st :: ST h | r) (Maybe a)
53+
peek (Iterator f currentIndex) = do
54+
i <- readSTRef currentIndex
55+
pure (f i)
56+
57+
-- | Check whether an iterator has been exhausted.
58+
exhausted :: forall a h r. Iterator h a -> Eff (st :: ST h | r) Boolean
59+
exhausted = map isNothing <<< peek
60+
61+
-- | Extract elements from an iterator and push them on to an STArray for as
62+
-- | long as those elements satisfy a given predicate.
63+
pushWhile :: forall a h r. (a -> Boolean) -> Iterator h a -> STArray h a -> Eff (st :: ST h | r) Unit
64+
pushWhile p iter array = do
65+
break <- newSTRef false
66+
whileE (not <$> readSTRef break) do
67+
mx <- peek iter
68+
case mx of
69+
Just x | p x -> do
70+
pushSTArray array x
71+
void $ next iter
72+
_ ->
73+
void $ writeSTRef break true
74+
75+
-- | Push the entire remaining contents of an iterator onto an STArray.
76+
pushAll :: forall a h r. Iterator h a -> STArray h a -> Eff (st :: ST h | r) Unit
77+
pushAll = pushWhile (const true)

test/Test/Data/Array.purs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ testArray = do
285285
assert $ A.group' [1, 2, 2, 3, 3, 3, 1] == [1 :| [1], 2 :| [2], 3 :| [3, 3]]
286286

287287
log "groupBy should group consecutive equal elements into arrays based on an equivalence relation"
288-
assert $ A.groupBy (\x y -> odd x && odd y) [1, 1, 2, 2, 3, 3] == [1 :| [1], NE.singleton 2, NE.singleton 2, 3 :| [3]]
288+
assert $ A.groupBy eqParity [1, 1, 2, 2, 3, 5, 4] == [1 :| [1], 2 :| [2], 3 :| [5], NE.singleton 4]
289289

290290
log "nub should remove duplicate elements from the list, keeping the first occurence"
291291
assert $ A.nub [1, 2, 2, 3, 4, 1] == [1, 2, 3, 4]
@@ -365,6 +365,11 @@ odd n = n `mod` 2 /= zero
365365
doubleAndOrig :: Int -> Array Int
366366
doubleAndOrig x = [x * 2, x]
367367

368+
-- | An equivalence relation on integers. This relation splits the integers
369+
-- | into two equivalence classes: odds and evens.
370+
eqParity :: Int -> Int -> Boolean
371+
eqParity x y = x `mod` 2 == y `mod` 2
372+
368373
data Replicated a = Replicated Int a
369374

370375
instance foldableReplicated :: Foldable Replicated where

test/Test/Data/Array/ST.purs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ testArrayST = do
1717

1818
log "emptySTArray should produce an empty array"
1919

20-
assert $ runPure (runSTArray emptySTArray) == nil
20+
assert $ runSTArray emptySTArray == nil
2121

2222
log "thaw should produce an STArray from a standard array"
2323

24-
assert $ runPure (runSTArray (thaw [1, 2, 3])) == [1, 2, 3]
24+
assert $ runSTArray (thaw [1, 2, 3]) == [1, 2, 3]
2525

2626
log "freeze should produce a standard array from an STArray"
2727

@@ -31,28 +31,28 @@ testArrayST = do
3131

3232
log "pushSTArray should append a value to the end of the array"
3333

34-
assert $ runPure (runSTArray (do
34+
assert $ runSTArray (do
3535
arr <- emptySTArray
3636
pushSTArray arr 1
3737
pushSTArray arr 2
38-
pure arr)) == [1, 2]
38+
pure arr) == [1, 2]
3939

40-
assert $ runPure (runSTArray (do
40+
assert $ runSTArray (do
4141
arr <- thaw [1, 2, 3]
4242
pushSTArray arr 4
43-
pure arr)) == [1, 2, 3, 4]
43+
pure arr) == [1, 2, 3, 4]
4444

4545
log "pushAllSTArray should append multiple values to the end of the array"
4646

47-
assert $ runPure (runSTArray (do
47+
assert $ runSTArray (do
4848
arr <- emptySTArray
4949
pushAllSTArray arr [1, 2]
50-
pure arr)) == [1, 2]
50+
pure arr) == [1, 2]
5151

52-
assert $ runPure (runSTArray (do
52+
assert $ runSTArray (do
5353
arr <- thaw [1, 2, 3]
5454
pushAllSTArray arr [4, 5, 6]
55-
pure arr)) == [1, 2, 3, 4, 5, 6]
55+
pure arr) == [1, 2, 3, 4, 5, 6]
5656

5757
log "peekSTArray should return Nothing when peeking a value outside the array bounds"
5858

@@ -104,24 +104,24 @@ testArrayST = do
104104

105105
log "pokeSTArray should replace the value at the specified index"
106106

107-
assert $ runPure (runSTArray (do
107+
assert $ runSTArray (do
108108
arr <- thaw [1]
109109
pokeSTArray arr 0 10
110-
pure arr)) == [10]
110+
pure arr) == [10]
111111

112112
log "pokeSTArray should do nothing when attempting to modify a value outside the array bounds"
113113

114-
assert $ runPure (runSTArray (do
114+
assert $ runSTArray (do
115115
arr <- thaw [1]
116116
pokeSTArray arr 1 2
117-
pure arr)) == [1]
117+
pure arr) == [1]
118118

119119
log "spliceSTArray should be able to delete multiple items at a specified index"
120120

121-
assert $ runPure (runSTArray (do
121+
assert $ runSTArray (do
122122
arr <- thaw [1, 2, 3, 4, 5]
123123
spliceSTArray arr 1 3 []
124-
pure arr)) == [1, 5]
124+
pure arr) == [1, 5]
125125

126126
log "spliceSTArray should return the items removed"
127127

@@ -131,17 +131,17 @@ testArrayST = do
131131

132132
log "spliceSTArray should be able to insert multiple items at a specified index"
133133

134-
assert $ runPure (runSTArray (do
134+
assert $ runSTArray (do
135135
arr <- thaw [1, 2, 3, 4, 5]
136136
spliceSTArray arr 1 0 [0, 100]
137-
pure arr)) == [1, 0, 100, 2, 3, 4, 5]
137+
pure arr) == [1, 0, 100, 2, 3, 4, 5]
138138

139139
log "spliceSTArray should be able to delete and insert at the same time"
140140

141-
assert $ runPure (runSTArray (do
141+
assert $ runSTArray (do
142142
arr <- thaw [1, 2, 3, 4, 5]
143143
spliceSTArray arr 1 2 [0, 100]
144-
pure arr)) == [1, 0, 100, 4, 5]
144+
pure arr) == [1, 0, 100, 4, 5]
145145

146146
log "toAssocArray should return all items in the array with the correct indices and values"
147147

0 commit comments

Comments
 (0)