Skip to content

Commit 5ec871d

Browse files
authored
Add ability to shrink/slice vectors and arrays (#181)
1 parent 40bcff2 commit 5ec871d

File tree

6 files changed

+366
-53
lines changed

6 files changed

+366
-53
lines changed

src/Data/Array/Mutable/Linear.hs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ module Data.Array.Mutable.Linear
5353
read,
5454
unsafeRead,
5555
size,
56+
slice,
5657
toList,
5758
)
5859
where
@@ -167,7 +168,7 @@ resize newSize seed (Array arr :: Array a)
167168
doCopy (Unlifted.allocBeside newSize seed arr)
168169
where
169170
doCopy :: (# Array# a, Array# a #) #-> Array a
170-
doCopy (# src, dest #) = wrap (Unlifted.copyInto src dest)
171+
doCopy (# src, dest #) = wrap (Unlifted.copyInto 0 src dest)
171172

172173
wrap :: (# Array# a, Array# a #) #-> Array a
173174
wrap (# old, new #) = old `Unlifted.lseq` Array new
@@ -177,6 +178,42 @@ resize newSize seed (Array arr :: Array a)
177178
toList :: Array a #-> Ur [a]
178179
toList (Array arr) = Unlifted.toList arr
179180

181+
-- | Copy a slice of the array, starting from given offset and copying given
182+
-- number of elements. Returns the pair (oldArray, slice).
183+
--
184+
-- Start offset + target size should be within the input array, and both should
185+
-- be non-negative.
186+
--
187+
-- @
188+
-- let b = slice i n a,
189+
-- then size b = n,
190+
-- and b[j] = a[i+j] for 0 <= j < n
191+
-- @
192+
slice
193+
:: HasCallStack
194+
=> Int -- ^ Start offset
195+
-> Int -- ^ Target size
196+
-> Array a #-> (Array a, Array a)
197+
slice from targetSize arr =
198+
size arr & \case
199+
(Array old, Ur s)
200+
| s < from + targetSize ->
201+
Unlifted.lseq
202+
old
203+
(error "Slice index out of bounds.")
204+
| otherwise ->
205+
doCopy
206+
(Unlifted.allocBeside
207+
targetSize
208+
(error "invariant violation: uninitialized array index")
209+
old)
210+
where
211+
doCopy :: (# Array# a, Array# a #) #-> (Array a, Array a)
212+
doCopy (# old, new #) = wrap (Unlifted.copyInto from old new)
213+
214+
wrap :: (# Array# a, Array# a #) #-> (Array a, Array a)
215+
wrap (# old, new #) = (Array old, Array new)
216+
180217
-- # Instances
181218
-------------------------------------------------------------------------------
182219

src/Data/Array/Mutable/Unlifted/Linear.hs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,18 +101,25 @@ write (GHC.I# i) (a :: a) = Unsafe.toLinear go
101101
_ -> Array# arr
102102
{-# NOINLINE write #-} -- prevents the runRW# effect from being reordered
103103

104-
-- | Copy the first mutable array into the second mutable array.
105-
-- This function is safe, it copies fewer elements if the second
106-
-- array is smaller than the first.
107-
copyInto :: Array# a #-> Array# a #-> (# Array# a, Array# a #)
108-
copyInto = Unsafe.toLinear2 go
104+
-- | Copy the first mutable array into the second mutable array, starting
105+
-- from the given index of the source array.
106+
--
107+
-- It copies fewer elements if the second array is smaller than the
108+
-- first. 'n' should be within [0..size src).
109+
--
110+
-- @
111+
-- copyInto n src dest:
112+
-- dest[i] = src[n+i] for i < size dest, i < size src + n
113+
-- @
114+
copyInto :: Int -> Array# a #-> Array# a #-> (# Array# a, Array# a #)
115+
copyInto start@(GHC.I# start#) = Unsafe.toLinear2 go
109116
where
110117
go :: Array# a -> Array# a -> (# Array# a, Array# a #)
111118
go (Array# src) (Array# dst) =
112119
let !(GHC.I# len#) = Prelude.min
113-
(GHC.I# (GHC.sizeofMutableArray# src))
120+
(GHC.I# (GHC.sizeofMutableArray# src) Prelude.- start)
114121
(GHC.I# (GHC.sizeofMutableArray# dst))
115-
in case GHC.runRW# (GHC.copyMutableArray# src 0# dst 0# len#) of
122+
in case GHC.runRW# (GHC.copyMutableArray# src start# dst 0# len#) of
116123
_ -> (# Array# src, Array# dst #)
117124
{-# NOINLINE copyInto #-} -- prevents the runRW# effect from being reordered
118125

src/Data/Vector/Mutable/Linear.hs

Lines changed: 108 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{-# LANGUAGE GADTs #-}
33
{-# LANGUAGE LinearTypes #-}
44
{-# LANGUAGE NoImplicitPrelude #-}
5+
{-# LANGUAGE LambdaCase #-}
56
{-# LANGUAGE MagicHash #-}
67
{-# LANGUAGE ScopedTypeVariables #-}
78
{-# LANGUAGE StrictData #-}
@@ -11,7 +12,8 @@
1112
-- | Mutable vectors with a linear API.
1213
--
1314
-- Vectors are arrays that grow automatically, that you can append to with
14-
-- 'snoc'.
15+
-- 'push'. They never shrink automatically to reduce unnecessary copying,
16+
-- use 'shrinkToFit' to get rid of the wasted space.
1517
--
1618
-- To use mutable vectors, create a linear computation of type
1719
-- @Vector a #-> Ur b@ and feed it to 'constant' or 'fromList'.
@@ -50,11 +52,15 @@ module Data.Vector.Mutable.Linear
5052
-- * Mutators
5153
write,
5254
unsafeWrite,
53-
snoc,
55+
push,
56+
pop,
57+
slice,
58+
shrinkToFit,
5459
-- * Accessors
5560
read,
5661
unsafeRead,
5762
size,
63+
capacity,
5864
toList,
5965
)
6066
where
@@ -64,6 +70,15 @@ import Prelude.Linear hiding (read)
6470
import Data.Array.Mutable.Linear (Array)
6571
import qualified Data.Array.Mutable.Linear as Array
6672

73+
-- # Constants
74+
-------------------------------------------------------------------------------
75+
76+
-- | When growing the vector, capacity will be multiplied by this number.
77+
--
78+
-- This is usually chosen between 1.5 and 2; 2 being the most common.
79+
constGrowthFactor :: Int
80+
constGrowthFactor = 2
81+
6782
-- # Core data types
6883
-------------------------------------------------------------------------------
6984

@@ -81,6 +96,8 @@ data Vector a where
8196

8297
-- | Create a 'Vector' from an 'Array'. Result will have the size and capacity
8398
-- equal to the size of the given array.
99+
--
100+
-- This is a constant time operation.
84101
fromArray :: HasCallStack => Array a #-> Vector a
85102
fromArray arr =
86103
Array.size arr
@@ -104,24 +121,44 @@ constant size' x f
104121
fromList :: HasCallStack => [a] -> (Vector a #-> Ur b) #-> Ur b
105122
fromList xs f = Array.fromList xs (f . fromArray)
106123

107-
-- | Number of elements inside the vector
124+
-- | Number of elements inside the vector.
125+
--
126+
-- This might be different than how much actual memory the vector is using.
127+
-- For that, see: 'capacity'.
108128
size :: Vector a #-> (Vector a, Ur Int)
109129
size (Vec size' arr) = (Vec size' arr, Ur size')
110130

111-
-- | Insert at the end of the vector
112-
snoc :: HasCallStack => Vector a #-> a -> Vector a
113-
snoc (Vec size' arr) x =
114-
Array.size arr & \(arr', Ur cap) ->
115-
if size' < cap
116-
then write (Vec (size' + 1) arr') size' x
117-
else write (unsafeResize ((max size' 1) * 2) (Vec (size' + 1) arr')) size' x
131+
-- | Capacity of a vector. In other words, the number of elements
132+
-- the vector can contain before it is copied to a bigger array.
133+
capacity :: Vector a #-> (Vector a, Ur Int)
134+
capacity (Vec s arr) =
135+
Array.size arr & \(arr', cap) -> (Vec s arr', cap)
136+
137+
-- | Insert at the end of the vector. This will grow the vector if there
138+
-- is no empty space.
139+
push :: Vector a #-> a -> Vector a
140+
push vec x =
141+
growToFit 1 vec & \(Vec s arr) ->
142+
write (Vec (s + 1) arr) s x
143+
144+
-- | Pop from the end of the vector. This will never shrink the vector, use
145+
-- 'shrinkToFit' to remove the wasted space.
146+
pop :: Vector a #-> (Vector a, Ur (Maybe a))
147+
pop vec =
148+
size vec & \case
149+
(vec', Ur 0) ->
150+
(vec', Ur Nothing)
151+
(vec', Ur s) ->
152+
read vec' (s-1) & \(Vec _ arr, Ur a) ->
153+
( Vec (s-1) arr
154+
, Ur (Just a)
155+
)
118156

119157
-- | Write to an element . Note: this will not write to elements beyond the
120158
-- current size of the vector and will error instead.
121159
write :: HasCallStack => Vector a #-> Int -> a -> Vector a
122-
write (Vec size' arr) ix val
123-
| indexInRange size' ix = Vec size' (Array.unsafeWrite arr ix val)
124-
| otherwise = arr `lseq` error "Write index not in range."
160+
write vec ix val =
161+
unsafeWrite (assertIndexInRange ix vec) ix val
125162

126163
-- | Same as 'write', but does not do bounds-checking. The behaviour is undefined
127164
-- when passed an invalid index.
@@ -132,11 +169,8 @@ unsafeWrite (Vec size' arr) ix val =
132169
-- | Read from a vector, with an in-range index and error for an index that is
133170
-- out of range (with the usual range @0..size-1@).
134171
read :: HasCallStack => Vector a #-> Int -> (Vector a, Ur a)
135-
read (Vec size' arr) ix
136-
| indexInRange size' ix =
137-
Array.unsafeRead arr ix
138-
& \(arr', val) -> (Vec size' arr', val)
139-
| otherwise = arr `lseq` error "Read index not in range."
172+
read vec ix =
173+
unsafeRead (assertIndexInRange ix vec) ix
140174

141175
-- | Same as 'read', but does not do bounds-checking. The behaviour is undefined
142176
-- when passed an invalid index.
@@ -153,6 +187,32 @@ toList (Vec s arr) =
153187
Array.toList arr & \(Ur xs) ->
154188
Ur (take s xs)
155189

190+
-- | Resize the vector to not have any wasted memory (size == capacity). This
191+
-- returns a semantically identical vector.
192+
shrinkToFit :: Vector a #-> Vector a
193+
shrinkToFit vec =
194+
capacity vec & \(vec', Ur cap) ->
195+
size vec' & \(vec'', Ur s') ->
196+
if cap > s'
197+
then unsafeResize s' vec''
198+
else vec''
199+
200+
-- | Return a slice of the vector with given size, starting from an offset.
201+
--
202+
-- Start offset + target size should be within the input vector, and both should
203+
-- be non-negative.
204+
--
205+
-- This is a constant time operation if the start offset is 0. Use 'shrinkToFit'
206+
-- to remove the possible wasted space if necessary.
207+
slice :: Int -> Int -> Vector a #-> Vector a
208+
slice from newSize (Vec oldSize arr) =
209+
if oldSize < from + newSize
210+
then arr `lseq` error "Slice index out of bounds"
211+
else if from == 0
212+
then Vec newSize arr
213+
else Array.slice from newSize arr & \(oldArr, newArr) ->
214+
oldArr `lseq` fromArray newArr
215+
156216
-- # Instances
157217
-------------------------------------------------------------------------------
158218

@@ -162,6 +222,29 @@ instance Consumable (Vector a) where
162222
-- # Internal library
163223
-------------------------------------------------------------------------------
164224

225+
-- | Grows the vector to the closest power of growthFactor to
226+
-- fit at least n more elements.
227+
growToFit :: HasCallStack => Int -> Vector a #-> Vector a
228+
growToFit n vec =
229+
capacity vec & \(vec', Ur cap) ->
230+
size vec' & \(vec'', Ur s') ->
231+
if s' + n <= cap
232+
then vec''
233+
else
234+
let -- Calculate the closest power of growth factor
235+
-- larger than required size.
236+
newSize =
237+
constGrowthFactor -- This constant is defined above.
238+
^ (ceiling :: Double -> Int)
239+
(logBase
240+
(fromIntegral constGrowthFactor)
241+
(fromIntegral (s' + n))) -- this is always
242+
-- > 0 because of
243+
-- the if condition
244+
in unsafeResize
245+
newSize
246+
vec''
247+
165248
-- | Resize the vector to a non-negative size. In-range elements are preserved,
166249
-- the possible new elements are bottoms.
167250
unsafeResize :: HasCallStack => Int -> Vector a #-> Vector a
@@ -174,6 +257,10 @@ unsafeResize newSize (Vec size' ma) =
174257
ma
175258
)
176259

177-
-- | Argument order: indexInRange size ix
178-
indexInRange :: Int -> Int -> Bool
179-
indexInRange size' ix = 0 <= ix && ix < size'
260+
-- | Check if given index is within the Vector, otherwise panic.
261+
assertIndexInRange :: HasCallStack => Int -> Vector a #-> Vector a
262+
assertIndexInRange i vec =
263+
size vec & \(vec', Ur s) ->
264+
if 0 <= i && i < s
265+
then vec'
266+
else vec' `lseq` error "Vector: index out of bounds"

0 commit comments

Comments
 (0)