Skip to content

Commit

Permalink
feat: Add map, fold, reduce, any, and all for slices (#5331)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

## Summary\*

Looks like these were removed back when we split arrays from slices
since they no longer shared the array implementation. I've added them
back.

## Additional Context

Currently requires #5332 to work
due to a panic in the type system found while adding to the slices test.

## Documentation\*

Check one:
- [ ] No documentation needed.
- [x] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
jfecher authored Jun 25, 2024
1 parent 52d48ff commit 03e25b4
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/docs/noir/concepts/data_types/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ fn main() {

### reduce

Same as fold, but uses the first element as starting element.
Same as fold, but uses the first element as the starting element.

```rust
fn reduce(self, f: fn(T, T) -> T) -> T
Expand Down
105 changes: 105 additions & 0 deletions docs/docs/noir/concepts/data_types/slices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,108 @@ fn main() {
assert(array[1] == slice[1]);
}
```

### map

Applies a function to each element of the slice, returning a new slice containing the mapped elements.

```rust
fn map<U>(self, f: fn(T) -> U) -> [U]
```

example

```rust
let a = &[1, 2, 3];
let b = a.map(|a| a * 2); // b is now &[2, 4, 6]
```

### fold

Applies a function to each element of the slice, returning the final accumulated value. The first
parameter is the initial value.

```rust
fn fold<U>(self, mut accumulator: U, f: fn(U, T) -> U) -> U
```

This is a left fold, so the given function will be applied to the accumulator and first element of
the slice, then the second, and so on. For a given call the expected result would be equivalent to:

```rust
let a1 = &[1];
let a2 = &[1, 2];
let a3 = &[1, 2, 3];

let f = |a, b| a - b;
a1.fold(10, f) //=> f(10, 1)
a2.fold(10, f) //=> f(f(10, 1), 2)
a3.fold(10, f) //=> f(f(f(10, 1), 2), 3)
```

example:

```rust

fn main() {
let slice = &[2, 2, 2, 2, 2];
let folded = slice.fold(0, |a, b| a + b);
assert(folded == 10);
}

```

### reduce

Same as fold, but uses the first element as the starting element.

```rust
fn reduce(self, f: fn(T, T) -> T) -> T
```

example:

```rust
fn main() {
let slice = &[2, 2, 2, 2, 2];
let reduced = slice.reduce(|a, b| a + b);
assert(reduced == 10);
}
```

### all

Returns true if all the elements satisfy the given predicate

```rust
fn all(self, predicate: fn(T) -> bool) -> bool
```

example:

```rust
fn main() {
let slice = &[2, 2, 2, 2, 2];
let all = slice.all(|a| a == 2);
assert(all);
}
```

### any

Returns true if any of the elements satisfy the given predicate

```rust
fn any(self, predicate: fn(T) -> bool) -> bool
```

example:

```rust
fn main() {
let slice = &[2, 2, 2, 2, 5];
let any = slice.any(|a| a == 5);
assert(any);
}

```
49 changes: 49 additions & 0 deletions noir_stdlib/src/slice.nr
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,53 @@ impl<T> [T] {
}
array
}

// Apply a function to each element of the slice, returning a new slice
// containing the mapped elements.
pub fn map<U, Env>(self, f: fn[Env](T) -> U) -> [U] {
let mut ret = &[];
for elem in self {
ret = ret.push_back(f(elem));
}
ret
}

// Apply a function to each element of the slice and an accumulator value,
// returning the final accumulated value. This function is also sometimes
// called `foldl`, `fold_left`, `reduce`, or `inject`.
pub fn fold<U, Env>(self, mut accumulator: U, f: fn[Env](U, T) -> U) -> U {
for elem in self {
accumulator = f(accumulator, elem);
}
accumulator
}

// Apply a function to each element of the slice and an accumulator value,
// returning the final accumulated value. Unlike fold, reduce uses the first
// element of the given slice as its starting accumulator value.
pub fn reduce<Env>(self, f: fn[Env](T, T) -> T) -> T {
let mut accumulator = self[0];
for i in 1..self.len() {
accumulator = f(accumulator, self[i]);
}
accumulator
}

// Returns true if all elements in the slice satisfy the predicate
pub fn all<Env>(self, predicate: fn[Env](T) -> bool) -> bool {
let mut ret = true;
for elem in self {
ret &= predicate(elem);
}
ret
}

// Returns true if any element in the slice satisfies the predicate
pub fn any<Env>(self, predicate: fn[Env](T) -> bool) -> bool {
let mut ret = false;
for elem in self {
ret |= predicate(elem);
}
ret
}
}
8 changes: 8 additions & 0 deletions test_programs/execution_success/slices/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ fn main(x: Field, y: pub Field) {
assert(append[0] == 1);
assert(append[4] == 5);

let mapped = &[1, 2].map(|x| x + 1);
assert_eq(mapped, &[2, 3]);

assert_eq(&[1, 2, 3].fold(0, |acc, x| acc + x), 6);
assert_eq(&[1, 2, 3].reduce(|acc, x| acc + x), 6);
assert(&[2, 4, 6].all(|x| x > 0));
assert(&[2, 4, 6].any(|x| x > 5));

regression_2083();
// The parameters to this function must come from witness values (inputs to main)
regression_merge_slices(x, y);
Expand Down

0 comments on commit 03e25b4

Please sign in to comment.