diff --git a/cairo/scripts/sorting/.gitignore b/cairo/scripts/sorting/.gitignore new file mode 100644 index 0000000..73aa31e --- /dev/null +++ b/cairo/scripts/sorting/.gitignore @@ -0,0 +1,2 @@ +target +.snfoundry_cache/ diff --git a/cairo/scripts/sorting/.tool-versions b/cairo/scripts/sorting/.tool-versions new file mode 100644 index 0000000..e002b74 --- /dev/null +++ b/cairo/scripts/sorting/.tool-versions @@ -0,0 +1,2 @@ +scarb 2.9.2 +snforge 0.31.0 diff --git a/cairo/scripts/sorting/Scarb.lock b/cairo/scripts/sorting/Scarb.lock new file mode 100644 index 0000000..51c6ed2 --- /dev/null +++ b/cairo/scripts/sorting/Scarb.lock @@ -0,0 +1,73 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "alexandria_bytes" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#162bed1c636d31ccaaa90ed3eb32c9eb1d5e3bd3" +dependencies = [ + "alexandria_data_structures", + "alexandria_math", +] + +[[package]] +name = "alexandria_data_structures" +version = "0.2.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#162bed1c636d31ccaaa90ed3eb32c9eb1d5e3bd3" +dependencies = [ + "alexandria_encoding", +] + +[[package]] +name = "alexandria_encoding" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#162bed1c636d31ccaaa90ed3eb32c9eb1d5e3bd3" +dependencies = [ + "alexandria_bytes", + "alexandria_data_structures", + "alexandria_math", + "alexandria_numeric", +] + +[[package]] +name = "alexandria_math" +version = "0.2.1" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#162bed1c636d31ccaaa90ed3eb32c9eb1d5e3bd3" + +[[package]] +name = "alexandria_numeric" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#162bed1c636d31ccaaa90ed3eb32c9eb1d5e3bd3" +dependencies = [ + "alexandria_math", + "alexandria_searching", +] + +[[package]] +name = "alexandria_searching" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#162bed1c636d31ccaaa90ed3eb32c9eb1d5e3bd3" +dependencies = [ + "alexandria_data_structures", +] + +[[package]] +name = "snforge_scarb_plugin" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" + +[[package]] +name = "snforge_std" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" +dependencies = [ + "snforge_scarb_plugin", +] + +[[package]] +name = "sorting" +version = "0.1.0" +dependencies = [ + "alexandria_data_structures", + "snforge_std", +] diff --git a/cairo/scripts/sorting/Scarb.toml b/cairo/scripts/sorting/Scarb.toml new file mode 100644 index 0000000..945d494 --- /dev/null +++ b/cairo/scripts/sorting/Scarb.toml @@ -0,0 +1,20 @@ +[package] +name = "sorting" +version = "0.1.0" +edition = "2023_11" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet = "2.9.2" +alexandria_data_structures = { git = "https://github.com/keep-starknet-strange/alexandria.git" } + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.31.0" } +assert_macros = "2.9.2" + +[[target.starknet-contract]] +sierra = true + +[scripts] +test = "snforge test" diff --git a/cairo/scripts/sorting/snfoundry.toml b/cairo/scripts/sorting/snfoundry.toml new file mode 100644 index 0000000..7d45ecd --- /dev/null +++ b/cairo/scripts/sorting/snfoundry.toml @@ -0,0 +1,9 @@ +# Visit https://foundry-rs.github.io/starknet-foundry/appendix/snfoundry-toml.html for more information + +# [sncast.myprofile1] # Define a profile name +# url = "http://127.0.0.1:5050/" # Url of the RPC provider +# accounts_file = "../account-file" # Path to the file with the account data +# account = "mainuser" # Account from `accounts_file` or default account file that will be used for the transactions +# keystore = "~/keystore" # Path to the keystore file +# wait_params = { timeout = 500, retry_interval = 10 } # Wait for submitted transaction parameters +# block_explorer = "StarkScan" # Block explorer service used to display links to transaction details diff --git a/cairo/scripts/sorting/src/bubble_sort.cairo b/cairo/scripts/sorting/src/bubble_sort.cairo new file mode 100644 index 0000000..4f5a8a6 --- /dev/null +++ b/cairo/scripts/sorting/src/bubble_sort.cairo @@ -0,0 +1,48 @@ +//! Bubble sort algorithm + +// Bubble sort +/// # Arguments +/// * `array` - Array to sort +/// # Returns +/// * `Array` - Sorted array +use super::interface::Sortable; + +pub impl BubbleSort of Sortable { + fn sort, +Drop, +PartialOrd>(mut array: Span) -> Array { + if array.len() == 0 { + return array![]; + } + if array.len() == 1 { + return array![*array[0]]; + } + let mut idx1 = 0; + let mut idx2 = 1; + let mut sorted_iteration = true; + let mut sorted_array = array![]; + + loop { + if idx2 == array.len() { + sorted_array.append(*array[idx1]); + if sorted_iteration { + break; + } + array = sorted_array.span(); + sorted_array = array![]; + idx1 = 0; + idx2 = 1; + sorted_iteration = true; + } else { + if *array[idx1] <= *array[idx2] { + sorted_array.append(*array[idx1]); + idx1 = idx2; + idx2 += 1; + } else { + sorted_array.append(*array[idx2]); + idx2 += 1; + sorted_iteration = false; + } + }; + }; + sorted_array + } +} diff --git a/cairo/scripts/sorting/src/interface.cairo b/cairo/scripts/sorting/src/interface.cairo new file mode 100644 index 0000000..dccc58b --- /dev/null +++ b/cairo/scripts/sorting/src/interface.cairo @@ -0,0 +1,11 @@ +use alexandria_data_structures::vec::{Felt252Vec}; + +pub trait Sortable { + fn sort, +Drop, +PartialOrd>(array: Span) -> Array; +} + +pub trait SortableVec { + fn sort, +Drop, +PartialOrd, +Felt252DictValue>( + array: Felt252Vec, + ) -> Felt252Vec; +} diff --git a/cairo/scripts/sorting/src/lib.cairo b/cairo/scripts/sorting/src/lib.cairo new file mode 100644 index 0000000..dcd8a2f --- /dev/null +++ b/cairo/scripts/sorting/src/lib.cairo @@ -0,0 +1,5 @@ +pub mod bubble_sort; +pub mod merge_sort; +pub mod quick_sort; +pub mod interface; +pub use interface::{Sortable, SortableVec}; diff --git a/cairo/scripts/sorting/src/merge_sort.cairo b/cairo/scripts/sorting/src/merge_sort.cairo new file mode 100644 index 0000000..5599fc8 --- /dev/null +++ b/cairo/scripts/sorting/src/merge_sort.cairo @@ -0,0 +1,73 @@ +//! Merge Sort + +// Merge Sort +/// # Arguments +/// * `arr` - Array to sort +/// # Returns +/// * `Array` - Sorted array + +use super::interface::Sortable; + +pub impl MergeSort of Sortable { + fn sort, +Drop, +PartialOrd>(mut array: Span) -> Array { + let len = array.len(); + if len == 0 { + return array![]; + } + if len == 1 { + return array![*array[0]]; + } + + // Create left and right arrays + let middle = len / 2; + let left_arr = array.slice(0, middle); + let right_arr = array.slice(middle, len - middle); + + // Recursively sort the left and right arrays + let sorted_left = Self::sort(left_arr); + let sorted_right = Self::sort(right_arr); + + let mut result_arr = array![]; + merge_recursive(sorted_left, sorted_right, ref result_arr, 0, 0); + result_arr + } +} + +// Merge two sorted arrays +/// # Arguments +/// * `left_arr` - Left array +/// * `right_arr` - Right array +/// * `result_arr` - Result array +/// * `left_arr_ix` - Left array index +/// * `right_arr_ix` - Right array index +/// # Returns +/// * `Array` - Sorted array +fn merge_recursive, +Drop, +PartialOrd>( + mut left_arr: Array, + mut right_arr: Array, + ref result_arr: Array, + left_arr_ix: usize, + right_arr_ix: usize, +) { + if result_arr.len() == left_arr.len() + right_arr.len() { + return; + } + + if left_arr_ix == left_arr.len() { + result_arr.append(*right_arr[right_arr_ix]); + return merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix, right_arr_ix + 1); + } + + if right_arr_ix == right_arr.len() { + result_arr.append(*left_arr[left_arr_ix]); + return merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix + 1, right_arr_ix); + } + + if *left_arr[left_arr_ix] < *right_arr[right_arr_ix] { + result_arr.append(*left_arr[left_arr_ix]); + merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix + 1, right_arr_ix) + } else { + result_arr.append(*right_arr[right_arr_ix]); + merge_recursive(left_arr, right_arr, ref result_arr, left_arr_ix, right_arr_ix + 1) + } +} diff --git a/cairo/scripts/sorting/src/quick_sort.cairo b/cairo/scripts/sorting/src/quick_sort.cairo new file mode 100644 index 0000000..6880aa4 --- /dev/null +++ b/cairo/scripts/sorting/src/quick_sort.cairo @@ -0,0 +1,61 @@ +//! Quick sort algorithm +use alexandria_data_structures::vec::{Felt252Vec, VecTrait}; + +// Quick sort +/// # Arguments +/// * `Felt252Vec` - Array to sort +/// # Returns +/// * `Felt252Vec` - Sorted array + +use super::SortableVec; + +pub impl QuickSort of SortableVec { + fn sort, +Drop, +PartialOrd, +Felt252DictValue>( + mut array: Felt252Vec, + ) -> Felt252Vec { + let array_size = array.len(); + if array_size <= 1 { + return array; + } + quick_sort_range(ref array, 0, array_size - 1); + + return array; + } +} + +fn quick_sort_range, +Drop, +PartialOrd, +Felt252DictValue>( + ref array: Felt252Vec, left: usize, right: usize, +) { + if left >= right { + return; + } + + let mut l = left; + let mut r = right; + + while l < r { + while (l < r) && (array[r] >= array[left]) { + r -= 1; + }; + + while (l < r) && (array[l] <= array[left]) { + l += 1; + }; + + if left != right { + let tmp = array[l]; + array.set(l, array[r]); + array.set(r, tmp); + } + }; + + let tmp = array[left]; + array.set(left, array[l]); + array.set(l, tmp); + + if l > 1 { + quick_sort_range(ref array, left, l - 1); + } + + quick_sort_range(ref array, r + 1, right); +} diff --git a/cairo/scripts/sorting/tests/test_bubble_sort.cairo b/cairo/scripts/sorting/tests/test_bubble_sort.cairo new file mode 100644 index 0000000..3103c33 --- /dev/null +++ b/cairo/scripts/sorting/tests/test_bubble_sort.cairo @@ -0,0 +1,30 @@ +use core::array::ArrayTrait; +use sorting::bubble_sort::BubbleSort; + +#[test] +fn test_empty_array() { + let empty: Array = array![]; + let result = BubbleSort::sort(empty.span()); + assert!(result.len() == 0, "Empty array test failed"); +} + +#[test] +fn test_single_element() { + let single = array![1_u32]; + let result = BubbleSort::sort(single.span()); + assert!(result.len() == 1 && *result[0] == 1_u32, "Single element test failed"); +} + +#[test] +fn test_unsorted_array() { + let unsorted = array![3_u32, 1_u32, 4_u32, 1_u32, 5_u32]; + let result = BubbleSort::sort(unsorted.span()); + assert!( + *result[0] == 1_u32 + && *result[1] == 1_u32 + && *result[2] == 3_u32 + && *result[3] == 4_u32 + && *result[4] == 5_u32, + "Sorting test failed", + ); +} diff --git a/cairo/scripts/sorting/tests/test_merge_sort.cairo b/cairo/scripts/sorting/tests/test_merge_sort.cairo new file mode 100644 index 0000000..d67a69d --- /dev/null +++ b/cairo/scripts/sorting/tests/test_merge_sort.cairo @@ -0,0 +1,64 @@ +#[cfg(test)] +mod tests { + use core::array::ArrayTrait; + use sorting::merge_sort::MergeSort; + use sorting::interface::Sortable; + + #[test] + fn test_empty_array() { + let empty: Array = array![]; + let result = MergeSort::sort(empty.span()); + assert!(result.len() == 0, "Empty array test failed"); + } + + #[test] + fn test_single_element() { + let single = array![42_u32]; + let result = MergeSort::sort(single.span()); + assert!(result.len() == 1 && *result[0] == 42_u32, "Single element test failed"); + } + + #[test] + fn test_even_length_array() { + let arr = array![4_u32, 2_u32, 3_u32, 1_u32]; + let result = MergeSort::sort(arr.span()); + assert!( + result.len() == 4 + && *result[0] == 1_u32 + && *result[1] == 2_u32 + && *result[2] == 3_u32 + && *result[3] == 4_u32, + "Even length array test failed", + ); + } + + #[test] + fn test_odd_length_array() { + let arr = array![5_u32, 3_u32, 1_u32, 4_u32, 2_u32]; + let result = MergeSort::sort(arr.span()); + assert!( + result.len() == 5 + && *result[0] == 1_u32 + && *result[1] == 2_u32 + && *result[2] == 3_u32 + && *result[3] == 4_u32 + && *result[4] == 5_u32, + "Odd length array test failed", + ); + } + + #[test] + fn test_array_with_duplicates() { + let arr = array![3_u32, 1_u32, 4_u32, 1_u32, 5_u32]; + let result = MergeSort::sort(arr.span()); + assert!( + result.len() == 5 + && *result[0] == 1_u32 + && *result[1] == 1_u32 + && *result[2] == 3_u32 + && *result[3] == 4_u32 + && *result[4] == 5_u32, + "Array with duplicates test failed", + ); + } +} diff --git a/cairo/scripts/sorting/tests/test_quick_sort.cairo b/cairo/scripts/sorting/tests/test_quick_sort.cairo new file mode 100644 index 0000000..65ff848 --- /dev/null +++ b/cairo/scripts/sorting/tests/test_quick_sort.cairo @@ -0,0 +1,112 @@ +use alexandria_data_structures::vec::{Felt252Vec, VecTrait}; +use sorting::quick_sort::QuickSort; + +/// * `a` - The first Felt252Vec. +/// * `b` - The second array. +/// # Returns +/// * `bool` - True if the arrays are equal, false otherwise. +fn is_equal_vec(mut a: Felt252Vec, mut b: Span) -> bool { + if a.len() != b.len() { + return false; + } + + let mut compare_id = 0; + + loop { + if compare_id == b.len() { + break true; + } + + if a[compare_id] != *b[compare_id] { + break false; + } + + compare_id += 1; + } +} + +#[test] +#[available_gas(20000000000000)] +fn quicksort_test() { + let mut data = VecTrait::::new(); + data.push(4); + data.push(2); + data.push(1); + data.push(3); + data.push(5); + data.push(0); + let correct = array![0_u32, 1, 2, 3, 4, 5]; + + let sorted = QuickSort::sort(data); + + assert!(is_equal_vec(sorted, correct.span())); +} + + +#[test] +#[available_gas(2000000)] +fn quicksort_test_empty() { + let data = VecTrait::::new(); + let correct = array![]; + + let sorted = QuickSort::sort(data); + + assert!(is_equal_vec(sorted, correct.span())); +} + +#[test] +#[available_gas(2000000)] +fn quicksort_test_one_element() { + let mut data = VecTrait::::new(); + data.push(2); + let correct = array![2_u32]; + + let sorted = QuickSort::sort(data); + + assert!(is_equal_vec(sorted, correct.span())); +} + +#[test] +#[available_gas(2000000)] +fn quicksort_test_pre_sorted() { + let mut data = VecTrait::::new(); + data.push(1); + data.push(2); + data.push(3); + data.push(4); + let correct = array![1_u32, 2, 3, 4]; + + let sorted = QuickSort::sort(data); + + assert!(is_equal_vec(sorted, correct.span())); +} + +#[test] +#[available_gas(2000000)] +fn quicksort_test_pre_sorted_decreasing() { + let mut data = VecTrait::::new(); + data.push(4); + data.push(3); + data.push(2); + data.push(1); + let correct = array![1_u32, 2, 3, 4]; + + let sorted = QuickSort::sort(data); + + assert!(is_equal_vec(sorted, correct.span())); +} + +#[test] +#[available_gas(2000000)] +fn quicksort_test_2_same_values() { + let mut data = VecTrait::::new(); + data.push(1); + data.push(2); + data.push(4); + data.push(2); + let correct = array![1_u32, 2, 2, 4]; + + let sorted = QuickSort::sort(data); + + assert!(is_equal_vec(sorted, correct.span())); +}