From 22f472249d25a7d3885f281932dfb89619089577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Luqu=C3=A9?= Date: Fri, 25 Mar 2022 17:06:11 +0100 Subject: [PATCH] AK: Introduce cutoff to insertion sort for Quicksort Implement insertion sort in AK. The cutoff value 7 is a magic number here, values [5, 15] should work well. Main idea of the cutoff is to reduce recursion performed by quicksort to speed up sorting of small partitions. --- AK/InsertionSort.h | 45 +++++++++++++++++++++++++++ AK/QuickSort.h | 16 ++++++++++ Tests/AK/CMakeLists.txt | 1 + Tests/AK/TestInsertionSort.cpp | 57 ++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 AK/InsertionSort.h create mode 100644 Tests/AK/TestInsertionSort.cpp diff --git a/AK/InsertionSort.h b/AK/InsertionSort.h new file mode 100644 index 0000000000..2ed2e7cab4 --- /dev/null +++ b/AK/InsertionSort.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, Marc Luqué + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace AK { + +// Standard Insertion Sort, with `end` inclusive! +template()[declval()])> +void insertion_sort(Collection& col, ssize_t start, ssize_t end, Comparator comparator) +requires(Indexable) +{ + for (ssize_t i = start + 1; i <= end; ++i) { + for (ssize_t j = i; j > 0 && comparator(col[j], col[j - 1]); --j) + swap(col[j], col[j - 1]); + } +} + +template()[declval()])> +void insertion_sort(Collection& collection, Comparator comparator) +requires(Indexable) +{ + if (collection.size() == 0) + return; + insertion_sort(collection, 0, collection.size() - 1, move(comparator)); +} + +template()[declval()])> +void insertion_sort(Collection& collection) +requires(Indexable) +{ + if (collection.size() == 0) + return; + insertion_sort(collection, 0, collection.size() - 1, [](auto& a, auto& b) { return a < b; }); +} + +} + +using AK::insertion_sort; diff --git a/AK/QuickSort.h b/AK/QuickSort.h index f5f3af060d..5824cd0d35 100644 --- a/AK/QuickSort.h +++ b/AK/QuickSort.h @@ -1,11 +1,13 @@ /* * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2022, Marc Luqué * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include #include namespace AK { @@ -14,10 +16,24 @@ namespace AK { * pivot quick_sort below. The other quick_sort below should only be used when * you are stuck with simple iterators to a container and you don't have access * to the container itself. + * + * We use a cutoff to insertion sort for partitions of size 7 or smaller. + * The idea is to avoid recursion for small partitions. + * The value 7 here is a magic number. According to princeton's CS algorithm class + * a value between 5 and 15 should work well in most situations: + * https://algs4.cs.princeton.edu/23quicksort/ */ + +static constexpr int INSERTION_SORT_CUTOFF = 7; + template void dual_pivot_quick_sort(Collection& col, int start, int end, LessThan less_than) { + if ((end + 1) - start <= INSERTION_SORT_CUTOFF) { + AK::insertion_sort(col, start, end, less_than); + return; + } + while (start < end) { int size = end - start + 1; if (size > 3) { diff --git a/Tests/AK/CMakeLists.txt b/Tests/AK/CMakeLists.txt index c0d5d18763..d09532f91a 100644 --- a/Tests/AK/CMakeLists.txt +++ b/Tests/AK/CMakeLists.txt @@ -38,6 +38,7 @@ set(AK_TEST_SOURCES TestIPv4Address.cpp TestIPv6Address.cpp TestIndexSequence.cpp + TestInsertionSort.cpp TestIntegerMath.cpp TestIntrusiveList.cpp TestIntrusiveRedBlackTree.cpp diff --git a/Tests/AK/TestInsertionSort.cpp b/Tests/AK/TestInsertionSort.cpp new file mode 100644 index 0000000000..6939859e90 --- /dev/null +++ b/Tests/AK/TestInsertionSort.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022, Marc Luqué + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include + +static constexpr int const n = 10; +static constexpr int const iterations = 10; +static constexpr int const seed = 1337; + +TEST_CASE(sorts_ascending) +{ + srand(seed); + + for (int i = 0; i < iterations; ++i) { + Vector ints; + for (int j = 0; j < n; ++j) + ints.append(get_random()); + Vector ints_copy = ints; + + insertion_sort(ints); + insertion_sort(ints_copy); + + for (int j = 0; j < n - 1; ++j) { + EXPECT(ints[j] <= ints[j + 1]); + EXPECT_EQ(ints[j], ints_copy[j]); + EXPECT_EQ(ints[j + 1], ints_copy[j + 1]); + } + } +} + +TEST_CASE(sorts_decending) +{ + srand(seed); + + for (int i = 0; i < iterations; ++i) { + Vector ints; + for (int j = 0; j < n; ++j) + ints.append(get_random()); + Vector ints_copy = ints; + + insertion_sort(ints, [](auto& a, auto& b) { return a > b; }); + insertion_sort(ints_copy, [](auto& a, auto& b) { return a > b; }); + + for (int j = 0; j < n - 1; ++j) { + EXPECT(ints[j] >= ints[j + 1]); + EXPECT_EQ(ints[j], ints_copy[j]); + EXPECT_EQ(ints[j + 1], ints_copy[j + 1]); + } + } +}