From a5f9a89d07b8624f137f2554c5a72bdc197e8076 Mon Sep 17 00:00:00 2001 From: Erik Fuller Date: Mon, 1 Jun 2026 16:48:15 -0700 Subject: [PATCH] feat(array_list): Add aws_array_list_shuffle for Fisher-Yates shuffle Add a general-purpose in-place shuffle function for aws_array_list using the Fisher-Yates algorithm with aws_device_random for randomness. Returns AWS_OP_SUCCESS on success, or propagates an error if the random source is unavailable. No-op for lists with 0 or 1 elements. --- include/aws/common/array_list.h | 8 ++++++ source/array_list.c | 19 ++++++++++++ tests/CMakeLists.txt | 2 ++ tests/array_list_test.c | 51 +++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) diff --git a/include/aws/common/array_list.h b/include/aws/common/array_list.h index 4cd05bb7d..61df9236a 100644 --- a/include/aws/common/array_list.h +++ b/include/aws/common/array_list.h @@ -231,6 +231,14 @@ void aws_array_list_swap(struct aws_array_list *AWS_RESTRICT list, size_t a, siz AWS_COMMON_API void aws_array_list_sort(struct aws_array_list *AWS_RESTRICT list, aws_array_list_comparator_fn *compare_fn); +/** + * Randomly shuffles the elements of the array list in place using the Fisher-Yates algorithm. + * Uses aws_device_random for cryptographic-quality randomness. + * Returns AWS_OP_SUCCESS, or an error code if the random source is unavailable. + */ +AWS_COMMON_API +int aws_array_list_shuffle(struct aws_array_list *AWS_RESTRICT list); + AWS_EXTERN_C_END #ifndef AWS_NO_STATIC_IMPL # include diff --git a/source/array_list.c b/source/array_list.c index a890115e9..285ccf1e0 100644 --- a/source/array_list.c +++ b/source/array_list.c @@ -4,6 +4,7 @@ */ #include +#include #include #include /* qsort */ @@ -217,3 +218,21 @@ void aws_array_list_sort(struct aws_array_list *AWS_RESTRICT list, aws_array_lis } AWS_POSTCONDITION(aws_array_list_is_valid(list)); } + +int aws_array_list_shuffle(struct aws_array_list *AWS_RESTRICT list) { + AWS_PRECONDITION(aws_array_list_is_valid(list)); + size_t len = aws_array_list_length(list); + if (len <= 1) { + return AWS_OP_SUCCESS; + } + for (size_t i = len - 1; i > 0; --i) { + uint64_t rand_val = 0; + if (aws_device_random_u64(&rand_val)) { + return aws_raise_error(aws_last_error()); + } + size_t j = (size_t)(rand_val % (i + 1)); + aws_array_list_swap(list, i, j); + } + AWS_POSTCONDITION(aws_array_list_is_valid(list)); + return AWS_OP_SUCCESS; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a5e147ae0..3de7f34f4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -88,6 +88,8 @@ add_test_case(array_list_not_enough_space_test) add_test_case(array_list_not_enough_space_test_failure) add_test_case(array_list_of_strings_sort) add_test_case(array_list_empty_sort) +add_test_case(array_list_shuffle) +add_test_case(array_list_shuffle_empty) add_test_case(priority_queue_push_pop_order_test) add_test_case(priority_queue_random_values_test) add_test_case(priority_queue_size_and_capacity_test) diff --git a/tests/array_list_test.c b/tests/array_list_test.c index 02ec921ac..9be4e6255 100644 --- a/tests/array_list_test.c +++ b/tests/array_list_test.c @@ -1056,3 +1056,54 @@ static int s_array_list_empty_sort_fn(struct aws_allocator *allocator, void *ctx } AWS_TEST_CASE(array_list_empty_sort, s_array_list_empty_sort_fn) + +static int s_array_list_shuffle_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + struct aws_array_list list; + const size_t count = 20; + ASSERT_SUCCESS(aws_array_list_init_dynamic(&list, allocator, count, sizeof(uint32_t))); + + for (uint32_t i = 0; i < count; ++i) { + ASSERT_SUCCESS(aws_array_list_push_back(&list, &i)); + } + + ASSERT_SUCCESS(aws_array_list_shuffle(&list)); + + /* Verify it's a valid permutation: all values 0..count-1 present exactly once */ + bool seen[20] = {false}; + bool is_sequential = true; + for (size_t i = 0; i < count; ++i) { + uint32_t val = 0; + aws_array_list_get_at(&list, &val, i); + ASSERT_TRUE(val < count); + ASSERT_FALSE(seen[val]); + seen[val] = true; + if (val != (uint32_t)i) { + is_sequential = false; + } + } + /* With 20 elements, probability of identity permutation is 1/20! ~ 0 */ + ASSERT_FALSE(is_sequential); + + aws_array_list_clean_up(&list); + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(array_list_shuffle, s_array_list_shuffle_fn) + +static int s_array_list_shuffle_empty_fn(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + struct aws_array_list list; + ASSERT_SUCCESS(aws_array_list_init_dynamic(&list, allocator, 0, sizeof(uint32_t))); + ASSERT_SUCCESS(aws_array_list_shuffle(&list)); + + uint32_t val = 42; + ASSERT_SUCCESS(aws_array_list_push_back(&list, &val)); + ASSERT_SUCCESS(aws_array_list_shuffle(&list)); + uint32_t result = 0; + aws_array_list_get_at(&list, &result, 0); + ASSERT_UINT_EQUALS(42, result); + + aws_array_list_clean_up(&list); + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(array_list_shuffle_empty, s_array_list_shuffle_empty_fn)