Skip to content

Commit 8fe8c00

Browse files
authored
Merge pull request #15 from hmenke/sorting
Add sorting functions that track number of swaps
2 parents aafd677 + 240c3bf commit 8fe8c00

File tree

6 files changed

+235
-2
lines changed

6 files changed

+235
-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: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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>
26+
#include <cstddef>
27+
#include <functional>
28+
#include <iterator>
29+
#include <ranges>
30+
31+
namespace itertools {
32+
33+
/**
34+
* @addtogroup sorting
35+
* @{
36+
*/
37+
38+
/**
39+
* @brief Bubble sort elements in the given range.
40+
*
41+
* @details Sort the elements in the range `[first, last)` in the order prescribed by the comparison function `comp`.
42+
* The underlying sorting algorithm is a stable bubble sort, i.e. already sorted elements will not be swapped. The
43+
* number of swaps necessary to get the elements into sorted order is recorded and returned.
44+
*
45+
* Computational complexity: \f$ \mathcal{O}(n^2) \f$.
46+
*
47+
* This function is eager and puts the range in sorted order.
48+
*
49+
* @tparam ForwardIt Forward iterator type.
50+
* @tparam Compare Comparison function type.
51+
* @param first Forward iterator to the first element of the range.
52+
* @param last Forward iterator to the element after the last of the range.
53+
* @param comp Comparison function callable with two dereferenced iterators.
54+
* @return Number of swaps necessary to sort the range.
55+
*/
56+
template <std::forward_iterator ForwardIt, class Compare = std::less<>>
57+
std::size_t bubble_sort(ForwardIt first, ForwardIt last, Compare comp = {}) {
58+
if (first == last) { return 0; }
59+
std::size_t n_swaps = 0;
60+
for (ForwardIt sorted = first; first != last; last = sorted) {
61+
sorted = first;
62+
for (ForwardIt curr = first, prev = first; ++curr != last; ++prev) {
63+
if (comp(*curr, *prev)) {
64+
std::iter_swap(curr, prev);
65+
sorted = curr;
66+
++n_swaps;
67+
}
68+
}
69+
}
70+
return n_swaps;
71+
}
72+
73+
/**
74+
* @brief Insertion sort elements in the given range.
75+
*
76+
* @details Sort the elements in the range `[first, last)` in the order prescribed by the comparison function `comp`.
77+
* The underlying sorting algorithm is a stable insertion sort, i.e. already sorted elements will not be swapped. The
78+
* number of swaps necessary to get the elements into sorted order is recorded and returned.
79+
*
80+
* Computational complexity: \f$ \mathcal{O}(n^2) \f$.
81+
*
82+
* This function is eager and puts the range in sorted order.
83+
*
84+
* @tparam BidirIt Bidirectional iterator type.
85+
* @tparam Compare Comparison function type.
86+
* @param first Bidirectional iterator to the first element of the range.
87+
* @param last Bidirectional iterator to the element after the last of the range.
88+
* @param comp Comparison function callable with two dereferenced iterators.
89+
* @return Number of swaps necessary to sort the range.
90+
*/
91+
template <std::bidirectional_iterator BidirIt, class Compare = std::less<>>
92+
std::size_t insertion_sort(BidirIt first, BidirIt last, Compare comp = {}) {
93+
if (first == last) { return 0; }
94+
std::size_t swaps = 0;
95+
for (BidirIt i = std::next(first); i != last; ++i) {
96+
for (BidirIt j = i; j != first && comp(*j, *std::prev(j)); --j) {
97+
std::iter_swap(std::prev(j), j);
98+
++swaps;
99+
}
100+
}
101+
return swaps;
102+
}
103+
104+
/**
105+
* @brief Bubble sort elements in the given range.
106+
*
107+
* @details See itertools::bubble_sort for more details.
108+
*
109+
* @tparam Range Forward range type.
110+
* @tparam Compare Comparison function type.
111+
* @param rng A forward range to sort.
112+
* @param comp Comparison function callable with two dereferenced iterators.
113+
* @return Number of swaps necessary to sort the range.
114+
*/
115+
template <std::ranges::forward_range Range, class Compare = std::less<>>
116+
std::size_t bubble_sort(Range &&rng, Compare comp = {}) { // NOLINT (ranges need not be forwarded)
117+
return bubble_sort(std::ranges::begin(rng), std::ranges::end(rng), comp);
118+
}
119+
120+
/**
121+
* @brief Insertion sort elements in the given range.
122+
*
123+
* @details See itertools::insertion_sort for more details.
124+
*
125+
* @tparam Range Bidirectional range type.
126+
* @tparam Compare Comparison function type.
127+
* @param rng A bidirectional range to sort.
128+
* @param comp Comparison function callable with two dereferenced iterators.
129+
* @return Number of swaps necessary to sort the range.
130+
*/
131+
template <std::ranges::bidirectional_range Range, class Compare = std::less<>>
132+
std::size_t insertion_sort(Range &&rng, Compare comp = {}) { // NOLINT (ranges need not be forwarded)
133+
return insertion_sort(std::ranges::begin(rng), std::ranges::end(rng), comp);
134+
}
135+
136+
/** @} */
137+
138+
} // namespace itertools
139+
140+
#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 @ref 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: 70 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,74 @@ TEST(Itertools, RangeRandomAccessOperations) {
369371
}
370372
}
371373

374+
// Create and fill a container of size n with random integers in [a,b].
375+
template <typename C> auto random_int_range(std::size_t n, int a, int b) {
376+
static std::default_random_engine eng{std::random_device{}()};
377+
auto cont = C(std::uniform_int_distribution<std::size_t>(0, n)(eng));
378+
std::ranges::generate(cont, [&]() { return std::uniform_int_distribution<typename C::value_type>(a, b)(eng); });
379+
return cont;
380+
}
381+
382+
TEST(Itertools, BubbleSort) {
383+
// std::less, int range
384+
auto l1 = random_int_range<std::forward_list<int>>(100, -1000, 1000);
385+
auto l1_std = l1;
386+
bubble_sort(l1.begin(), l1.end());
387+
l1_std.sort();
388+
EXPECT_EQ(l1, l1_std);
389+
390+
// std::greater, size_t range
391+
auto l2 = random_int_range<std::forward_list<std::size_t>>(100, 5, 77);
392+
auto l2_std = l2;
393+
bubble_sort(l2, std::greater{});
394+
l2_std.sort(std::greater{});
395+
EXPECT_EQ(l2, l2_std);
396+
}
397+
398+
TEST(Itertools, BubbleSortSwaps) {
399+
auto v1 = std::vector<int>{100, 2, 3, 56, 200, 3, -52, 3, 3, 99, 33, 177, -199};
400+
auto v1_std = v1;
401+
auto swaps = bubble_sort(v1);
402+
std::ranges::sort(v1_std);
403+
EXPECT_EQ(v1, v1_std);
404+
EXPECT_EQ(swaps, 37);
405+
406+
auto v2 = std::vector<long>{1, 2, 3};
407+
auto swaps2 = bubble_sort(v2.begin(), v2.end(), std::greater{});
408+
EXPECT_EQ(v2, std::vector<long>({3, 2, 1}));
409+
EXPECT_EQ(swaps2, 3);
410+
}
411+
412+
TEST(Itertools, InsertionSort) {
413+
// std::less, int range
414+
auto v1 = random_int_range<std::list<int>>(100, -1000, 1000);
415+
auto v1_std = v1;
416+
insertion_sort(v1.begin(), v1.end());
417+
v1_std.sort();
418+
EXPECT_EQ(v1, v1_std);
419+
420+
// std::greater, size_t range
421+
auto v2 = random_int_range<std::list<std::size_t>>(100, 5, 77);
422+
auto v2_std = v2;
423+
insertion_sort(v2, std::greater{});
424+
v2_std.sort(std::greater{});
425+
EXPECT_EQ(v2, v2_std);
426+
}
427+
428+
TEST(Itertools, InsertionSortSwaps) {
429+
auto v1 = std::vector<int>{100, 2, 3, 56, 200, 3, -52, 3, 3, 99, 33, 177, -199};
430+
auto v1_std = v1;
431+
auto swaps = insertion_sort(v1);
432+
std::ranges::sort(v1_std);
433+
EXPECT_EQ(v1, v1_std);
434+
EXPECT_EQ(swaps, 37);
435+
436+
auto v2 = std::vector<long>{1, 2, 3};
437+
auto swaps2 = insertion_sort(v2.begin(), v2.end(), std::greater{});
438+
EXPECT_EQ(v2, std::vector<long>({3, 2, 1}));
439+
EXPECT_EQ(swaps2, 3);
440+
}
441+
372442
int main(int argc, char **argv) {
373443
::testing::InitGoogleTest(&argc, argv);
374444
return RUN_ALL_TESTS();

0 commit comments

Comments
 (0)