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

feat: use Zac's quicksort algorithm in stdlib sorting #5940

Merged
merged 15 commits into from
Sep 11, 2024
8 changes: 5 additions & 3 deletions docs/docs/noir/concepts/data_types/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ fn main() {

### sort_via

Sorts the array with a custom comparison function
Sorts the array with a custom comparison function. The ordering function must return true if the first argument should be sorted to be before the second argument or is equal to the second argument.

Using this method with an operator like `<` that does not return `true` for equal values will result in an assertion failure for arrays with equal elements.

```rust
fn sort_via(self, ordering: fn(T, T) -> bool) -> [T; N]
Expand All @@ -139,10 +141,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ example
```rust
fn main() {
let arr = [42, 32]
let sorted_ascending = arr.sort_via(|a, b| a < b);
let sorted_ascending = arr.sort_via(|a, b| a <= b);
assert(sorted_ascending == [32, 42]); // verifies

let sorted_descending = arr.sort_via(|a, b| a > b);
let sorted_descending = arr.sort_via(|a, b| a >= b);
assert(sorted_descending == [32, 42]); // does not verify
}
```
Expand Down
116 changes: 116 additions & 0 deletions noir_stdlib/src/array/check_shuffle.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use crate::cmp::Eq;

unconstrained fn __get_shuffle_indices<T, let N: u32>(lhs: [T; N], rhs: [T; N]) -> [Field; N] where T: Eq {
let mut shuffle_indices: [Field;N ] = [0; N];

let mut shuffle_mask: [bool; N] = [false; N];
for i in 0..N {
let mut found = false;
for j in 0..N {
if ((shuffle_mask[j] == false) & (!found)) {
if (lhs[i] == rhs[j]) {
found = true;
shuffle_indices[i] = j as Field;
shuffle_mask[j] = true;
}
}
if (found) {
continue;
}
}
assert(found == true, "check_shuffle, lhs and rhs arrays do not contain equivalent values");
}

shuffle_indices
}

unconstrained fn __get_index<let N: u32>(indices: [Field; N], idx: Field) -> Field {
let mut result = 0;
for i in 0..N {
if (indices[i] == idx) {
result = i as Field;
break;
}
}
result
}

pub(crate) fn check_shuffle<T, let N: u32>(lhs: [T; N], rhs: [T; N]) where T: Eq {
unsafe {
let shuffle_indices = __get_shuffle_indices(lhs, rhs);

for i in 0..N {
let idx = __get_index(shuffle_indices, i as Field);
assert_eq(shuffle_indices[idx], i as Field);
}
for i in 0..N {
let idx = shuffle_indices[i];
let expected = rhs[idx];
let result = lhs[i];
assert_eq(expected, result);
}
}
}

mod test {
use super::check_shuffle;
use crate::cmp::Eq;

struct CompoundStruct {
a: bool,
b: Field,
c: u64
}
impl Eq for CompoundStruct {
fn eq(self, other: Self) -> bool {
(self.a == other.a) & (self.b == other.b) & (self.c == other.c)
}
}

#[test]
fn test_shuffle() {
let lhs: [Field; 5] = [0, 1, 2, 3, 4];
let rhs: [Field; 5] = [2, 0, 3, 1, 4];
check_shuffle(lhs, rhs);
}

#[test]
fn test_shuffle_identity() {
let lhs: [Field; 5] = [0, 1, 2, 3, 4];
let rhs: [Field; 5] = [0, 1, 2, 3, 4];
check_shuffle(lhs, rhs);
}

#[test(should_fail_with = "check_shuffle, lhs and rhs arrays do not contain equivalent values")]
fn test_shuffle_fail() {
let lhs: [Field; 5] = [0, 1, 2, 3, 4];
let rhs: [Field; 5] = [0, 1, 2, 3, 5];
check_shuffle(lhs, rhs);
}

#[test(should_fail_with = "check_shuffle, lhs and rhs arrays do not contain equivalent values")]
fn test_shuffle_duplicates() {
let lhs: [Field; 5] = [0, 1, 2, 3, 4];
let rhs: [Field; 5] = [0, 1, 2, 3, 3];
check_shuffle(lhs, rhs);
}

#[test]
fn test_shuffle_compound_struct() {
let lhs: [CompoundStruct; 5] = [
CompoundStruct { a: false, b: 0, c: 12345 },
CompoundStruct { a: false, b: -100, c: 54321 },
CompoundStruct { a: true, b: 5, c: 0xffffffffffffffff },
CompoundStruct { a: true, b: 9814, c: 0xeeffee0011001133 },
CompoundStruct { a: false, b: 0x155, c: 0 }
];
let rhs: [CompoundStruct; 5] = [
CompoundStruct { a: false, b: 0x155, c: 0 },
CompoundStruct { a: false, b: 0, c: 12345 },
CompoundStruct { a: false, b: -100, c: 54321 },
CompoundStruct { a: true, b: 9814, c: 0xeeffee0011001133 },
CompoundStruct { a: true, b: 5, c: 0xffffffffffffffff }
];
check_shuffle(lhs, rhs);
}
}
Loading
Loading