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

chore: update quicksort from iterative noir_sort version #7348

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
112 changes: 99 additions & 13 deletions noir_stdlib/src/array/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -183,24 +183,22 @@ where
/// assert(sorted_descending == [32, 42]); // does not verify
/// }
/// ```
pub fn sort_via<Env>(self, ordering: fn[Env](T, T) -> bool) -> Self {
pub fn sort_via(self, ordering: fn(T, T) -> bool) -> Self {
// Safety: `sorted` array is checked to be:
// a. a permutation of `input`'s elements
// b. satisfying the predicate `ordering`
unsafe {
let sorted = quicksort::quicksort(self, ordering);

if !is_unconstrained() {
for i in 0..N - 1 {
assert(
ordering(sorted[i], sorted[i + 1]),
"Array has not been sorted correctly according to `ordering`.",
);
}
check_shuffle::check_shuffle(self, sorted);
let sorted = unsafe { quicksort::quicksort(self, ordering) };

if !is_unconstrained() {
for i in 0..N - 1 {
assert(
ordering(sorted[i], sorted[i + 1]),
"Array has not been sorted correctly according to `ordering`.",
);
}
sorted
check_shuffle::check_shuffle(self, sorted);
}
sorted
}
}

Expand Down Expand Up @@ -232,4 +230,92 @@ mod test {
fn map_empty() {
assert_eq([].map(|x| x + 1), []);
}

global arr_with_100_values: [u32; 100] = [
42, 123, 87, 93, 48, 80, 50, 5, 104, 84, 70, 47, 119, 66, 71, 121, 3, 29, 42, 118, 2, 54,
89, 44, 81, 0, 26, 106, 68, 96, 84, 48, 95, 54, 45, 32, 89, 100, 109, 19, 37, 41, 19, 98,
53, 114, 107, 66, 6, 74, 13, 19, 105, 64, 123, 28, 44, 50, 89, 58, 123, 126, 21, 43, 86, 35,
21, 62, 82, 0, 108, 120, 72, 72, 62, 80, 12, 71, 70, 86, 116, 73, 38, 15, 127, 81, 30, 8,
125, 28, 26, 69, 114, 63, 27, 28, 61, 42, 13, 32,
];
global expected_with_100_values: [u32; 100] = [
0, 0, 2, 3, 5, 6, 8, 12, 13, 13, 15, 19, 19, 19, 21, 21, 26, 26, 27, 28, 28, 28, 29, 30, 32,
32, 35, 37, 38, 41, 42, 42, 42, 43, 44, 44, 45, 47, 48, 48, 50, 50, 53, 54, 54, 58, 61, 62,
62, 63, 64, 66, 66, 68, 69, 70, 70, 71, 71, 72, 72, 73, 74, 80, 80, 81, 81, 82, 84, 84, 86,
86, 87, 89, 89, 89, 93, 95, 96, 98, 100, 104, 105, 106, 107, 108, 109, 114, 114, 116, 118,
119, 120, 121, 123, 123, 123, 125, 126, 127,
];
fn sort_u32(a: u32, b: u32) -> bool {
a <= b
}

#[test]
fn test_sort() {
let mut arr: [u32; 7] = [3, 6, 8, 10, 1, 2, 1];

let sorted = arr.sort();

let expected: [u32; 7] = [1, 1, 2, 3, 6, 8, 10];
assert(sorted == expected);
}

#[test]
fn test_sort_100_values() {
let mut arr: [u32; 100] = [
42, 123, 87, 93, 48, 80, 50, 5, 104, 84, 70, 47, 119, 66, 71, 121, 3, 29, 42, 118, 2,
54, 89, 44, 81, 0, 26, 106, 68, 96, 84, 48, 95, 54, 45, 32, 89, 100, 109, 19, 37, 41,
19, 98, 53, 114, 107, 66, 6, 74, 13, 19, 105, 64, 123, 28, 44, 50, 89, 58, 123, 126, 21,
43, 86, 35, 21, 62, 82, 0, 108, 120, 72, 72, 62, 80, 12, 71, 70, 86, 116, 73, 38, 15,
127, 81, 30, 8, 125, 28, 26, 69, 114, 63, 27, 28, 61, 42, 13, 32,
];

let sorted = arr.sort();

let expected: [u32; 100] = [
0, 0, 2, 3, 5, 6, 8, 12, 13, 13, 15, 19, 19, 19, 21, 21, 26, 26, 27, 28, 28, 28, 29, 30,
32, 32, 35, 37, 38, 41, 42, 42, 42, 43, 44, 44, 45, 47, 48, 48, 50, 50, 53, 54, 54, 58,
61, 62, 62, 63, 64, 66, 66, 68, 69, 70, 70, 71, 71, 72, 72, 73, 74, 80, 80, 81, 81, 82,
84, 84, 86, 86, 87, 89, 89, 89, 93, 95, 96, 98, 100, 104, 105, 106, 107, 108, 109, 114,
114, 116, 118, 119, 120, 121, 123, 123, 123, 125, 126, 127,
];
assert(sorted == expected);
}

#[test]
fn test_sort_100_values_comptime() {
let sorted = arr_with_100_values.sort();
assert(sorted == expected_with_100_values);
}

#[test]
fn test_sort_via() {
let mut arr: [u32; 7] = [3, 6, 8, 10, 1, 2, 1];

let sorted = arr.sort_via(sort_u32);

let expected: [u32; 7] = [1, 1, 2, 3, 6, 8, 10];
assert(sorted == expected);
}

#[test]
fn test_sort_via_100_values() {
let mut arr: [u32; 100] = [
42, 123, 87, 93, 48, 80, 50, 5, 104, 84, 70, 47, 119, 66, 71, 121, 3, 29, 42, 118, 2,
54, 89, 44, 81, 0, 26, 106, 68, 96, 84, 48, 95, 54, 45, 32, 89, 100, 109, 19, 37, 41,
19, 98, 53, 114, 107, 66, 6, 74, 13, 19, 105, 64, 123, 28, 44, 50, 89, 58, 123, 126, 21,
43, 86, 35, 21, 62, 82, 0, 108, 120, 72, 72, 62, 80, 12, 71, 70, 86, 116, 73, 38, 15,
127, 81, 30, 8, 125, 28, 26, 69, 114, 63, 27, 28, 61, 42, 13, 32,
];

let sorted = arr.sort_via(sort_u32);

let expected: [u32; 100] = [
0, 0, 2, 3, 5, 6, 8, 12, 13, 13, 15, 19, 19, 19, 21, 21, 26, 26, 27, 28, 28, 28, 29, 30,
32, 32, 35, 37, 38, 41, 42, 42, 42, 43, 44, 44, 45, 47, 48, 48, 50, 50, 53, 54, 54, 58,
61, 62, 62, 63, 64, 66, 66, 68, 69, 70, 70, 71, 71, 72, 72, 73, 74, 80, 80, 81, 81, 82,
84, 84, 86, 86, 87, 89, 89, 89, 93, 95, 96, 98, 100, 104, 105, 106, 107, 108, 109, 114,
114, 116, 118, 119, 120, 121, 123, 123, 123, 125, 126, 127,
];
assert(sorted == expected);
}
}
44 changes: 29 additions & 15 deletions noir_stdlib/src/array/quicksort.nr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
unconstrained fn partition<T, Env, let N: u32>(
unconstrained fn partition<T, let N: u32>(
arr: &mut [T; N],
low: u32,
high: u32,
sortfn: fn[Env](T, T) -> bool,
sortfn: unconstrained fn(T, T) -> bool,
) -> u32 {
let pivot = high;
let mut i = low;
Expand All @@ -14,34 +14,48 @@ unconstrained fn partition<T, Env, let N: u32>(
i += 1;
}
}

let temp = arr[i];
arr[i] = arr[pivot];
arr[pivot] = temp;
i
}

unconstrained fn quicksort_recursive<T, Env, let N: u32>(
unconstrained fn quicksort_loop<T, let N: u32>(
arr: &mut [T; N],
low: u32,
high: u32,
sortfn: fn[Env](T, T) -> bool,
sortfn: unconstrained fn(T, T) -> bool,
) {
if low < high {
let pivot_index = partition(arr, low, high, sortfn);
if pivot_index > 0 {
quicksort_recursive(arr, low, pivot_index - 1, sortfn);
let mut stack: [(u32, u32)] = &[(low, high)];
// TODO(https://github.com/noir-lang/noir_sort/issues/22): use 'loop' once it's stabilized
for _ in 0..2 * N {
if stack.len() == 0 {
break;
}

let (new_stack, (new_low, new_high)) = stack.pop_back();
stack = new_stack;

if new_high < new_low + 1 {
continue;
}

let pivot_index = partition(arr, new_low, new_high, sortfn);
stack = stack.push_back((pivot_index + 1, new_high));
if 0 < pivot_index {
stack = stack.push_back((new_low, pivot_index - 1));
}
quicksort_recursive(arr, pivot_index + 1, high, sortfn);
}
}

pub(crate) unconstrained fn quicksort<T, Env, let N: u32>(
_arr: [T; N],
sortfn: fn[Env](T, T) -> bool,
pub unconstrained fn quicksort<T, let N: u32>(
arr: [T; N],
sortfn: unconstrained fn(T, T) -> bool,
) -> [T; N] {
let mut arr: [T; N] = _arr;
if arr.len() <= 1 {} else {
quicksort_recursive(&mut arr, 0, arr.len() - 1, sortfn);
let mut arr: [T; N] = arr;
if arr.len() > 1 {
quicksort_loop(&mut arr, 0, arr.len() - 1, sortfn);
}
arr
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "regression_noir_sort"
type = "bin"
authors = [""]

[dependencies]
46 changes: 46 additions & 0 deletions test_programs/noir_test_success/regression_noir_sort/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
mod quicksort;

pub fn sort<T, let N: u32>(input: [T; N]) -> [T; N]
where
T: Ord,
{
sort_via(input, |a, b| a <= b)
}

pub fn sort_via<T, let N: u32>(input: [T; N], sortfn: fn(T, T) -> bool) -> [T; N] {
// Safety: test
let sorted = unsafe { quicksort::quicksort(input, sortfn) };

for i in 0..N - 1 {
assert(
sortfn(sorted[i], sorted[i + 1]),
"Array has not been sorted correctly according to `ordering`.",
);
}

sorted
}

mod test {
use crate::sort;

global arr_comptime: [u32; 7] = [3, 6, 8, 10, 1, 2, 1];
global expected_comptime: [u32; 7] = [1, 1, 2, 3, 6, 8, 10];

#[test]
fn test_sort() {
let mut arr: [u32; 7] = [3, 6, 8, 10, 1, 2, 1];

let sorted = sort(arr);

let expected: [u32; 7] = [1, 1, 2, 3, 6, 8, 10];
assert(sorted == expected);
}

#[test]
fn test_sort_comptime() {
let sorted = sort(arr_comptime);

assert(sorted == expected_comptime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
unconstrained fn partition<T, let N: u32>(
arr: &mut [T; N],
low: u32,
high: u32,
sortfn: unconstrained fn(T, T) -> bool,
) -> u32 {
let pivot = high;
let mut i = low;
for j in low..high {
if (sortfn(arr[j], arr[pivot])) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i += 1;
}
}

let temp = arr[i];
arr[i] = arr[pivot];
arr[pivot] = temp;
i
}

unconstrained fn quicksort_loop<T, let N: u32>(
arr: &mut [T; N],
low: u32,
high: u32,
sortfn: unconstrained fn(T, T) -> bool,
) {
let mut stack: [(u32, u32)] = &[(low, high)];
// TODO(https://github.com/noir-lang/noir_sort/issues/22): use 'loop' once it's stabilized
for _ in 0..2 * N {
if stack.len() == 0 {
break;
}

let (new_stack, (new_low, new_high)) = stack.pop_back();
stack = new_stack;

if new_high < new_low + 1 {
continue;
}

let pivot_index = partition(arr, new_low, new_high, sortfn);
stack = stack.push_back((pivot_index + 1, new_high));
if 0 < pivot_index {
stack = stack.push_back((new_low, pivot_index - 1));
}
}
}

pub unconstrained fn quicksort<T, let N: u32>(
arr: [T; N],
sortfn: unconstrained fn(T, T) -> bool,
) -> [T; N] {
let mut arr: [T; N] = arr;
if arr.len() <= 1 {} else {
quicksort_loop(&mut arr, 0, arr.len() - 1, sortfn);
}
arr
}
Loading