Skip to content

Commit 6fc5e6b

Browse files
committed
Add sorting functions that track number of swaps
1 parent aafd677 commit 6fc5e6b

File tree

6 files changed

+215
-2
lines changed

6 files changed

+215
-2
lines changed

c++/itertools/itertools.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "./range.hpp"
2828
#include "./slice.hpp"
2929
#include "./stride.hpp"
30+
#include "./sort.hpp"
3031
#include "./transform.hpp"
3132
#include "./zip.hpp"
3233

c++/itertools/sort.hpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright (c) 2024 Simons Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0.txt
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// Authors: Thomas Hahn, Olivier Parcollet, Nils Wentzell, chuffa
16+
17+
/**
18+
* @file
19+
* @brief Provides functions for sorting ranges.
20+
*/
21+
22+
#ifndef _ITERTOOLS_SORT_HPP
23+
#define _ITERTOOLS_SORT_HPP
24+
25+
#include <algorithm> // for iter_swap
26+
#include <cstddef> // for size_t
27+
#include <functional> // for less
28+
#include <iterator> // for forward_iterator, prev, random_access_iterator
29+
30+
namespace itertools {
31+
32+
/**
33+
* @ingroup sorting
34+
* @brief Bubble sort
35+
*
36+
* @details Sort the elements in the range [@p first, @p last) in the order
37+
* prescribed by the comparison function @p comp. The underlying sorting
38+
* algorithm is bubble sort. The sorting is stable, i.e. already sorted
39+
* elements will not be swapped. The number of swaps necessary to get the
40+
* elements into sorted order is recorded and returned.
41+
*
42+
* Computational complexity: \f$\mathcal{O}(n^2)\f$
43+
*
44+
* This function is eager and puts the range in sorted order.
45+
*
46+
* @param first Forward iterator for the first element of the range.
47+
* @param last Forward iterator for the element \a after the last of the range.
48+
* @param comp Comparison function callable with two dereferenced iterators.
49+
* @return Number of swaps necessary to sort the range.
50+
*/
51+
template <class ForwardIt, class Compare>
52+
requires std::forward_iterator<ForwardIt>
53+
std::size_t bubble_sort(ForwardIt first, ForwardIt last, Compare comp) {
54+
if (first == last) { return 0; }
55+
std::size_t n_swaps = 0;
56+
for (ForwardIt sorted = first; first != last; last = sorted) {
57+
sorted = first;
58+
for (ForwardIt curr = first, prev = first; ++curr != last; ++prev) {
59+
if (comp(*curr, *prev)) {
60+
std::iter_swap(curr, prev);
61+
sorted = curr;
62+
++n_swaps;
63+
}
64+
}
65+
}
66+
return n_swaps;
67+
}
68+
69+
/**
70+
* @ingroup sorting
71+
* @overload
72+
*
73+
* Sets the default comparison function to `std::less{}`.
74+
*/
75+
template <class ForwardIt>
76+
requires std::forward_iterator<ForwardIt>
77+
std::size_t bubble_sort(ForwardIt first, ForwardIt last) {
78+
return bubble_sort(first, last, std::less{});
79+
}
80+
81+
/**
82+
* @ingroup sorting
83+
* @brief Insertion sort
84+
*
85+
* @details Sort the elements in the range [@p first, @p last) in the order
86+
* prescribed by the comparison function @p comp. The underlying sorting
87+
* algorithm is insertion sort. The sorting is stable, i.e. already sorted
88+
* elements will not be swapped. The number of swaps necessary to get the
89+
* elements into sorted order is recorded and returned.
90+
*
91+
* Computational complexity: \f$\mathcal{O}(n^2)\f$
92+
*
93+
* This function is eager and puts the range in sorted order.
94+
*
95+
* @param first Random-access iterator for the first element of the range.
96+
* @param last Random-access iterator for the element \a after the last of the range.
97+
* @param comp Comparison function callable with two dereferenced iterators.
98+
* @return Number of swaps necessary to sort the range.
99+
*/
100+
template <class RandomIt, class Compare>
101+
requires std::random_access_iterator<RandomIt>
102+
std::size_t insertion_sort(RandomIt first, RandomIt last, Compare comp) {
103+
if (first == last) { return 0; }
104+
std::size_t swaps = 0;
105+
for (RandomIt i = std::next(first); i != last; ++i) {
106+
for (RandomIt j = i; j != first && comp(*j, *std::prev(j)); --j) {
107+
std::iter_swap(std::prev(j), j);
108+
++swaps;
109+
}
110+
}
111+
return swaps;
112+
}
113+
114+
/**
115+
* @ingroup sorting
116+
* @overload
117+
*
118+
* Sets the default comparison function to `std::less{}`.
119+
*/
120+
template <class RandomIt>
121+
requires std::random_access_iterator<RandomIt>
122+
std::size_t insertion_sort(RandomIt first, RandomIt last) {
123+
return insertion_sort(first, last, std::less{});
124+
}
125+
126+
} // namespace itertools
127+
128+
#endif // _ITERTOOLS_SORT_HPP

doc/DoxygenLayout.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@
5454
</tab>
5555
<tab type="user" url="@ref itertools::product_range" title="product_range"/>
5656
</tab>
57+
<tab type="usergroup" url="@ref sorting" title="Sorting">
58+
<tab type="user" url="@ref itertools::bubble_sort" title="bubble_sort"/>
59+
<tab type="user" url="@ref itertools::insertion_sort" title="insertion_sort"/>
60+
</tab>
5761
<tab type="usergroup" url="@ref utilities" title="Utilities">
5862
<tab type="user" url="@ref itertools::chunk_range" title="chunk_range"/>
5963
<tab type="user" url="@ref itertools::distance" title="distance"/>
@@ -287,4 +291,4 @@
287291
</memberdecl>
288292
<detaileddescription title=""/>
289293
</directory>
290-
</doxygenlayout>
294+
</doxygenlayout>

doc/documentation.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ The following classes and functions related to integer ranges are defined in **i
8080
* @ref itertools::foreach "foreach"
8181
* @ref itertools::product_range "product_range"
8282

83+
## Sorting
84+
85+
We provide alternatives to `std::sort` that keep track of the number of swaps
86+
that have to be performed to put a range into a sorted order.
87+
88+
The following sorting functions are defined in **itertools**:
89+
90+
* @ref itertools::bubble_sort "bubble_sort"
91+
* @ref itertools::insertion_sort "insertion_sort"
92+
8393
## Utilities
8494

8595
@ref utilities are mostly internal implementation details and should not concern everyday users.
@@ -94,4 +104,4 @@ The following utilities are defined in **itertools**:
94104
* @ref itertools::make_sentinel "make_sentinel"
95105
* @ref itertools::make_vector_from_range "make_vector_from_range"
96106
* @ref itertools::omp_chunk "omp_chunk"
97-
* @ref itertools::sentinel_t "sentinel_t"
107+
* @ref itertools::sentinel_t "sentinel_t"

doc/groups.dox

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@
4444
* `start + i * step`.
4545
*/
4646

47+
/**
48+
* @defgroup sorting Sorting
49+
* @brief Functions to sort ranges.
50+
*
51+
* @details We provide alternatives to @p std::sort that keep track of the
52+
* number of swaps that have to be performed to put a range into a sorted order.
53+
*/
54+
4755
/**
4856
* @defgroup utilities Utilities
4957
* @brief Utilities are mostly internal implementation details and should not concern everyday users.

test/c++/itertools.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919

2020
#include <algorithm>
2121
#include <array>
22+
#include <forward_list>
2223
#include <list>
2324
#include <iostream>
2425
#include <numeric>
26+
#include <random>
2527
#include <utility>
2628
#include <vector>
2729

@@ -369,6 +371,66 @@ TEST(Itertools, RangeRandomAccessOperations) {
369371
}
370372
}
371373

374+
inline static std::default_random_engine &randint_engine() {
375+
static thread_local std::default_random_engine eng{std::random_device{}()};
376+
return eng;
377+
}
378+
379+
template <typename T> inline static T randint(T a, T b) {
380+
static_assert(std::is_integral<T>::value && sizeof(T) > 1, "The type must be an integer!");
381+
return std::uniform_int_distribution<T>(a, b)(randint_engine());
382+
}
383+
384+
TEST(Itertools, BubbleSort) {
385+
// Create a vector of random length and fill it with random numbers.
386+
std::forward_list<int> test1;
387+
for (auto _ : range(randint(0, 100))) { test1.push_front(randint(-1000, 1000)); }
388+
std::forward_list<int> test2 = test1; // Make a copy, sorting is in-place
389+
390+
bubble_sort(test1.begin(), test1.end());
391+
test2.sort();
392+
for (auto [lhs, rhs] : zip(test1, test2)) { EXPECT_EQ(lhs, rhs); }
393+
394+
bubble_sort(test1.begin(), test1.end(), std::greater{});
395+
test2.sort(std::greater{});
396+
for (auto [lhs, rhs] : zip(test1, test2)) { EXPECT_EQ(lhs, rhs); }
397+
}
398+
399+
TEST(Itertools, BubbleSortSwaps) {
400+
std::vector<int> test1 = {100, 2, 3, 56, 200, 3, -52, 3, 3, 99, 33, 177, -199};
401+
std::vector<int> test2 = test1; // Make a copy, sorting is in-place
402+
403+
std::size_t swaps = bubble_sort(test1.begin(), test1.end());
404+
std::sort(test2.begin(), test2.end());
405+
for (auto [lhs, rhs] : zip(test1, test2)) { EXPECT_EQ(lhs, rhs); }
406+
EXPECT_EQ(swaps, 37);
407+
}
408+
409+
TEST(Itertools, InsertionSort) {
410+
// Create a vector of random length and fill it with random numbers.
411+
std::vector<int> test1(randint(0, 100));
412+
for (auto &e : test1) { e = randint(-1000, 1000); }
413+
std::vector<int> test2 = test1; // Make a copy, sorting is in-place
414+
415+
insertion_sort(test1.begin(), test1.end());
416+
std::sort(test2.begin(), test2.end());
417+
for (auto [lhs, rhs] : zip(test1, test2)) { EXPECT_EQ(lhs, rhs); }
418+
419+
insertion_sort(test1.begin(), test1.end(), std::greater{});
420+
std::sort(test2.begin(), test2.end(), std::greater{});
421+
for (auto [lhs, rhs] : zip(test1, test2)) { EXPECT_EQ(lhs, rhs); }
422+
}
423+
424+
TEST(Itertools, InsertionSortSwaps) {
425+
std::vector<int> test1 = {100, 2, 3, 56, 200, 3, -52, 3, 3, 99, 33, 177, -199};
426+
std::vector<int> test2 = test1; // Make a copy, sorting is in-place
427+
428+
std::size_t swaps = insertion_sort(test1.begin(), test1.end());
429+
std::sort(test2.begin(), test2.end());
430+
for (auto [lhs, rhs] : zip(test1, test2)) { EXPECT_EQ(lhs, rhs); }
431+
EXPECT_EQ(swaps, 37);
432+
}
433+
372434
int main(int argc, char **argv) {
373435
::testing::InitGoogleTest(&argc, argv);
374436
return RUN_ALL_TESTS();

0 commit comments

Comments
 (0)