Skip to content

Commit

Permalink
Merge 6a81368 into 826b18a
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljklein authored Feb 27, 2025
2 parents 826b18a + 6a81368 commit 9c4cb32
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 28 deletions.
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
}

0 comments on commit 9c4cb32

Please sign in to comment.