Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Within Vector, use Array.Copy wherever possible #3236

Merged
merged 22 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
- [Implemented initial `Table.group_by` function on Standard.Table][3305]
- [Implemented `Text.pad` and `Text.trim`][3309]
- [Updated `Text.repeat` and added `*` operator shorthand][3310]
- [General improved Vector performance and new `Vector.each_with_index`,
`Vector.fold_with_index` and `Vector.take` methods.][3236]

[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
Expand Down Expand Up @@ -94,6 +96,7 @@
[3305]: https://github.com/enso-org/enso/pull/3305
[3309]: https://github.com/enso-org/enso/pull/3309
[3310]: https://github.com/enso-org/enso/pull/3310
[3236]: https://github.com/enso-org/enso/pull/3236

#### Enso Compiler

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from Standard.Base import all
from Standard.Builtins import Builtins

## UNSTABLE
ADVANCED
Expand All @@ -17,3 +18,12 @@ Array.to_default_visualization_data : Text
Array.to_default_visualization_data =
Vector.Vector this . to_default_visualization_data

## PRIVATE
Copy the backing Array to an Enso Array
TODO This is a workaround for the issue with polyglot Array.copy
https://www.pivotaltracker.com/story/show/181122542
Array.copy_to : Array->Integer->Array->Integer->Integer
Array.copy_to src_array src_start dest_array dest_start length =
case Builtins.Meta.is_polyglot src_array of
True -> 0.up_to length . each i->(dest_array.set_at (i+dest_start) (src_array.at i+src_start))
False -> Array.copy src_array src_start dest_array dest_start length
146 changes: 89 additions & 57 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector.enso
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,24 @@ type Vector
f = acc -> ix -> function acc (arr.at ix)
0.up_to this.length . fold init f

## Combines all the elements of the vector, by iteratively applying the
passed function with next elements of the vector.

Arguments:
- init: The initial value for the fold.
- function: A function taking the current value, an index and an item
and combining them.

> Example
Compute the sum of all of the elements and indexes in a vector.

[0, 1, 2] . fold 0 (s->i->e->s+i+e)
fold_with_index : Any -> (Any -> Integer -> Any -> Any) -> Any
fold_with_index init function =
arr = this.to_array
f = acc -> ix -> function acc ix (arr.at ix)
0.up_to this.length . fold init f

## Combines all the elements of a non-empty vector using a binary operation.

Arguments:
Expand All @@ -198,7 +216,10 @@ type Vector
reduce : (Any -> Any -> Any) -> Any ! Empty_Error
reduce function =
case this.not_empty of
True -> this.tail.fold this.head function
True -> if this.length == 1 then this.unsafe_at 0 else
arr = this.to_array
f = acc -> ix -> function acc (arr.at ix)
1.up_to this.length . fold (this.unsafe_at 0) f
False -> Error.throw Empty_Error

## Computes the sum of the values in the vector.
Expand Down Expand Up @@ -231,10 +252,7 @@ type Vector
[1, 2, 3, 4, 5].exists (> 3)
exists : (Any -> Boolean) -> Boolean
exists predicate =
len = this.length
go idx found = if found || (idx >= len) then found else
@Tail_Call go idx+1 (predicate (this.unsafe_at idx))
go 0 False
0.up_to this.length . exists (idx -> (predicate (this.unsafe_at idx)))

## Returns the first element of the vector that satisfies the predicate or
if no elements of the vector satisfy the predicate, it throws nothing.
Expand Down Expand Up @@ -284,7 +302,7 @@ type Vector

[-1, 1, 5, 8].all (< 0)
all : (Any -> Boolean) -> Boolean
all predicate = this.fold True (l -> r -> l && predicate r)
all predicate = (this.exists (r -> (predicate r).not)).not
jdunkerley marked this conversation as resolved.
Show resolved Hide resolved

## Checks whether this vector contains a given value as an element.

Expand Down Expand Up @@ -346,12 +364,8 @@ type Vector
[0, 10, 2, 2].filter (==) == [0, 2]
filter_with_index : (Integer -> Any -> Boolean) -> Vector Any
filter_with_index predicate =
acc = this.fold (Pair here.new_builder 0) acc-> elem->
builder = acc.first
ix = acc.second
new_builder = if predicate ix elem then builder.append elem else builder
Pair new_builder ix+1
builder = acc.first
builder = this.fold_with_index here.new_builder builder-> ix-> elem->
if predicate ix elem then builder.append elem else builder
builder.to_vector

## Partitions the vector into vectors of elements which satisfy a given
Expand All @@ -372,15 +386,15 @@ type Vector
Splitting a vector into even and odd elements.

[1, 2, 3, 4, 5].partition (x -> x % 2 == 0) == (Pair [2, 4] [1, 3, 5])
partition : (Any -> Boolean) -> Vector Any
partition : (Any -> Boolean) -> Pair (Vector Any) (Vector Any)
partition predicate =
acc = this.fold (Pair here.new_builder here.new_builder) acc-> elem->
pair = this.fold (Pair here.new_builder here.new_builder) acc-> elem->
case predicate elem of
True ->
Pair (acc.first.append elem) acc.second
False ->
Pair acc.first (acc.second.append elem)
acc.map .to_vector
pair.map .to_vector

## Partitions the vector into vectors of elements which satisfy a given
predicate and ones that do not.
Expand All @@ -400,17 +414,13 @@ type Vector
Splitting a vector into elements at even and odd positions.

["a", "b", "c", "d"].partition_with_index (ix -> _ -> ix % 2 == 0) == (Pair ["a", "c"] ["b", "d"])
partition_with_index : (Integer -> Any -> Boolean) -> Vector Any
partition_with_index : (Integer -> Any -> Boolean) -> Pair (Vector Any) (Vector Any)
partition_with_index predicate =
acc = this.fold (Partition_Accumulator here.new_builder here.new_builder 0) acc-> elem->
case predicate acc.ix elem of
True ->
Partition_Accumulator (acc.true_builder.append elem) acc.false_builder acc.ix+1
False ->
Partition_Accumulator acc.true_builder (acc.false_builder.append elem) acc.ix+1
case acc of
Partition_Accumulator true_builder false_builder _ ->
Pair true_builder.to_vector false_builder.to_vector
pair = this.fold_with_index (Pair here.new_builder here.new_builder) acc-> ix-> elem->
case predicate ix elem of
True -> Pair (acc.first.append elem) acc.second
False -> Pair acc.first (acc.second.append elem)
pair.map .to_vector

## Applies a function to each element of the vector, returning the vector of
results.
Expand Down Expand Up @@ -440,13 +450,7 @@ type Vector
[0, 1, 2] . flat_map (n -> Vector.fill n n)
flat_map : (Any -> Vector Any) -> Vector Any
flat_map function =
mapped = this.map function
length = mapped.fold 0 acc-> elem-> acc + elem.length
arr = Array.new length
mapped.fold 0 i-> vec->
vec.map_with_index j-> elem-> arr.set_at i+j elem
i + vec.length
Vector arr
this.map function . flatten

## Transforms a vector of vectors into a vector of inner elements - removes
one layer of nesting from a stack of nested vectors.
Expand All @@ -460,8 +464,7 @@ type Vector
length = this.fold 0 acc-> elem-> acc + elem.length
arr = Array.new length
this.fold 0 i-> vec->
# TODO could use Array.copy here, if it was safe...
vec.map_with_index j-> elem-> arr.set_at i+j elem
Array.copy_to vec.to_array 0 arr i vec.length
i + vec.length
Vector arr

Expand Down Expand Up @@ -499,6 +502,26 @@ type Vector
0.up_to this.length . each ix->
f (this.unsafe_at ix)

## Applies a function to each element of the vector.

Arguments:
- function: A function to apply that takes an index and an item.

The function is called with both the element index as well as the
element itself.

Unlike `map`, this method does not return the individual results,
therefore it is only useful for side-effecting computations.

> Example
Print each element in the vector to standard output.

[1, 2, 3, 4, 5] . each_with_index (ix->elem-> IO.println Pair ix elem)
each_with_index : (Integer -> Any -> Any) -> Nothing
each_with_index f =
0.up_to this.length . each ix->
f ix (this.unsafe_at ix)

## Reverses the vector, returning a vector with the same elements, but in
the opposite order.

Expand Down Expand Up @@ -572,10 +595,8 @@ type Vector
+ that =
this_len = this.length
arr = Array.new (this_len + that.length)
0.up_to this_len . each i->
arr.set_at i (this.unsafe_at i)
this.length.up_to arr.length . each i->
arr.set_at i (that.unsafe_at i-this_len)
Array.copy_to this.to_array 0 arr 0 this_len
Array.copy_to that.to_array 0 arr this_len that.length
Vector arr

## Add `element` to the beginning of `this` vector.
Expand Down Expand Up @@ -620,6 +641,28 @@ type Vector
if this.length == 1 then prefix + this.unsafe_at 0 + suffix else
prefix + this.unsafe_at 0 + (1.up_to this.length . fold "" acc-> i-> acc + separator + this.unsafe_at i) + suffix

## Creates a new vector with the skipping elements until `start` and then
continuing until `end` index.

Arguments:
- start: The index of the first element to include.
- end: The index to stop slicing at.

> Example
Remove the first 2 elements then continue until index 5 from the vector.

[1, 2, 3, 4, 5, 6, 7, 8].slice 2 5 == [3, 4, 5]
take : Integer -> Integer -> Vector Any
take start end =
slice_start = Math.max 0 start
slice_end = Math.min this.length end
if slice_start >= slice_end then Vector (Array.new 0) else
if (slice_start == 0) && (slice_end == this.length) then this else
len = slice_end - slice_start
arr = Array.new len
Array.copy_to this.to_array slice_start arr 0 len
Vector arr

## Creates a new vector with the first `count` elements in `this` removed.

Arguments:
Expand All @@ -630,8 +673,7 @@ type Vector

[1, 2, 3, 4, 5].drop_start 1
drop_start : Integer -> Vector Any
drop_start count = if count >= this.length then here.new 0 (x -> x) else
here.new (this.length - count) (i -> this.unsafe_at i+count)
drop_start count = this.take count this.length

## Creates a new vector with the last `count` elements in `this` removed.

Expand All @@ -643,8 +685,7 @@ type Vector

[1, 2, 3, 4, 5].drop_end 2
drop_end : Integer -> Vector Any
drop_end count = if count >= this.length then here.new 0 (x -> x) else
this.take_start (this.length - count)
drop_end count = this.take 0 (this.length - count)

## Creates a new vector, consisting of the first `count` elements on the
left of `this`.
Expand All @@ -657,8 +698,7 @@ type Vector

[1, 2, 3, 4, 5].take_start 2
take_start : Integer -> Vector Any
take_start count = if count >= this.length then this else
here.new count this.at
take_start count = this.take 0 count

## Creates a new vector, consisting of the last `count` elements on the
right of `this`.
Expand All @@ -671,8 +711,7 @@ type Vector

[1, 2, 3, 4, 5].take_end 3
take_end : Integer -> Vector Any
take_end count = if count >= this.length then this else
this.drop_start (this.length - count)
take_end count = this.take (this.length - count) this.length

## Performs a pair-wise operation passed in `function` on consecutive
elements of `this` and `that`.
Expand Down Expand Up @@ -851,7 +890,7 @@ type Vector
not want to sort in place on the original vector, as `sort` is not
intended to be mutable.
new_vec_arr = Array.new this.length
Array.copy this.to_array 0 new_vec_arr 0 this.length
Array.copy_to this.to_array 0 new_vec_arr 0 this.length

## As we want to account for both custom projections and custom
comparisons we need to construct a comparator for internal use that
Expand Down Expand Up @@ -1027,9 +1066,7 @@ type Builder
False ->
old_array = this.to_array
new_array = Array.new old_array.length*2
0.up_to this.length . each i->
new_array.set_at i (old_array.at i)
Nothing
Array.copy old_array 0 new_array 0 old_array.length
Unsafe.set_atom_field this 0 new_array
this.append item
Nothing
Expand All @@ -1042,10 +1079,7 @@ type Builder

exists : (Any -> Boolean) -> Boolean
exists predicate =
len = this.length
go idx found = if found || (idx >= len) then found else
@Tail_Call go idx+1 (predicate (this.to_array.at idx))
go 0 False
0.up_to this.length . exists (idx -> (predicate (this.to_array.at idx)))

## Converts this builder to a vector containing all the appended elements.

Expand All @@ -1062,9 +1096,7 @@ type Builder
to_vector =
old_array = this.to_array
new_array = Array.new this.length
0.up_to this.length . each i->
new_array.set_at i (old_array.at i)
Nothing
Array.copy old_array 0 new_array 0 this.length
Vector new_array

## UNSTABLE
Expand Down
12 changes: 11 additions & 1 deletion test/Benchmarks/src/Vector.enso
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,18 @@ make_random_vec n =

main =
random_vec = here.make_random_vec here.vector_size

random_vec_2 = here.make_random_vec 100000

Bench.measure (Base.Vector.new here.vector_size i->i) "New Vector" here.iter_size here.num_iterations
Bench.measure (random_vec + [1]) "Append Single" here.iter_size here.num_iterations
Bench.measure (random_vec + random_vec_2) "Append Large" here.iter_size here.num_iterations
Bench.measure (random_vec.sum) "Sum" here.iter_size here.num_iterations
Bench.measure ((random_vec.drop_start 20).sum) "Drop First 20 and Sum" here.iter_size here.num_iterations
Bench.measure ((random_vec.drop_end 20).sum) "Drop Last 20 and Sum" here.iter_size here.num_iterations
Bench.measure (random_vec.filter (x -> x % 3 == 1)) "Filter" here.iter_size here.num_iterations
Bench.measure (random_vec.filter_with_index (i->x -> (i+x) % 3 == 1)) "Filter With Index" here.iter_size here.num_iterations
Bench.measure (random_vec.partition (x -> x % 3 == 1)) "Partition" here.iter_size here.num_iterations
Bench.measure (random_vec.partition_with_index (i->x -> (i+x) % 3 == 1)) "Partition With Index" here.iter_size here.num_iterations

stateful_fun x =
s = State.get Number
Expand Down
12 changes: 12 additions & 0 deletions test/Tests/src/Data/Vector_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ foreign js generate_js_array = """

spec = Test.group "Vectors" <|

Test.specify "text bytes" <|
"Lore".utf_8 . should_equal [76, 111, 114, 101]

Test.specify "should allow vector creation with a programmatic constructor" <|
Vector.new 100 (ix -> ix + 1) . fold 0 (+) . should_equal 5050

Expand Down Expand Up @@ -117,6 +120,8 @@ spec = Test.group "Vectors" <|
Test.specify "should partition elements" <|
[1, 2, 3, 4, 5].partition (x -> x % 2 == 0) . should_equal <| Pair [2, 4] [1, 3, 5]
([1, 2, 3, 4].partition x-> if x == 1 then Error.throw <| My_Error "foo" else True) . should_fail_with My_Error

Test.specify "should partition elements with indices" <|
["a", "b", "c", "d"].partition_with_index (ix -> _ -> ix % 2 == 0) == (Pair ["a", "c"] ["b", "d"])
["a", "b", "c", "d"].partition_with_index (ix -> _ -> if ix % 2 == 0 then Error.throw <| My_Error "foo" else True) . should_fail_with My_Error

Expand All @@ -143,6 +148,7 @@ spec = Test.group "Vectors" <|
[[1]].flatten . should_equal [1]
[[[1], [2, 3]], [[4]]].flatten . should_equal [[1], [2, 3], [4]]
[["a", 2], [], [[[3]]], [T 1 2, 44]].flatten . should_equal ["a", 2, [[3]], T 1 2, 44]
(["polyglot", " ", "array"].map .utf_8).flatten . should_equal "polyglot array".utf_8

Test.specify "should allow applying a function to each element" <|
vec = [1, 2, 3, 4]
Expand Down Expand Up @@ -183,6 +189,7 @@ spec = Test.group "Vectors" <|
vec = [1, 2, 3, 4, 5, 6]
first_four = [1, 2, 3, 4]
last_four = [3, 4, 5, 6]
vec.take 2 4 . should_equal [3, 4]
vec.drop_start 2 . should_equal last_four
vec.drop_end 2 . should_equal first_four
vec.take_start 4 . should_equal first_four
Expand Down Expand Up @@ -339,4 +346,9 @@ spec = Test.group "Vectors" <|
Test.specify "should return a vector containing only unique elements up to some criteria" <|
[Pair 1 "a", Pair 2 "b", Pair 1 "c"] . distinct (on = _.first) . should_equal [Pair 1 "a", Pair 2 "b"]

Test.specify "should be able to sort a polyglot vector" <|
input = "beta".utf_8
expected = "abet".utf_8
input.sort . should_equal expected

main = Test.Suite.run_main here.spec
1 change: 0 additions & 1 deletion test/Tests/src/System/File_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,3 @@ spec =

filtered2 = Enso_Project.data.list name_filter="*/*/*" recursive=True . map .to_text
filtered2.should_equal (resolve ["subdirectory/nested/b.txt"])