Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions c++/itertools/itertools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "./range.hpp"
#include "./slice.hpp"
#include "./stride.hpp"
#include "./sort.hpp"
#include "./transform.hpp"
#include "./zip.hpp"

Expand Down
140 changes: 140 additions & 0 deletions c++/itertools/sort.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) 2024 Simons Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Authors: Thomas Hahn, Olivier Parcollet, Nils Wentzell, chuffa

/**
* @file
* @brief Provides functions for sorting ranges.
*/

#ifndef _ITERTOOLS_SORT_HPP
#define _ITERTOOLS_SORT_HPP

#include <algorithm>
#include <cstddef>
#include <functional>
#include <iterator>
#include <ranges>

namespace itertools {

/**
* @addtogroup sorting
* @{
*/

/**
* @brief Bubble sort elements in the given range.
*
* @details Sort the elements in the range `[first, last)` in the order prescribed by the comparison function `comp`.
* The underlying sorting algorithm is a stable bubble sort, i.e. already sorted elements will not be swapped. The
* number of swaps necessary to get the elements into sorted order is recorded and returned.
*
* Computational complexity: \f$ \mathcal{O}(n^2) \f$.
*
* This function is eager and puts the range in sorted order.
*
* @tparam ForwardIt Forward iterator type.
* @tparam Compare Comparison function type.
* @param first Forward iterator to the first element of the range.
* @param last Forward iterator to the element after the last of the range.
* @param comp Comparison function callable with two dereferenced iterators.
* @return Number of swaps necessary to sort the range.
*/
template <std::forward_iterator ForwardIt, class Compare = std::less<>>
std::size_t bubble_sort(ForwardIt first, ForwardIt last, Compare comp = {}) {
if (first == last) { return 0; }
std::size_t n_swaps = 0;
for (ForwardIt sorted = first; first != last; last = sorted) {
sorted = first;
for (ForwardIt curr = first, prev = first; ++curr != last; ++prev) {
if (comp(*curr, *prev)) {
std::iter_swap(curr, prev);
sorted = curr;
++n_swaps;
}
}
}
return n_swaps;
}

/**
* @brief Insertion sort elements in the given range.
*
* @details Sort the elements in the range `[first, last)` in the order prescribed by the comparison function `comp`.
* The underlying sorting algorithm is a stable insertion sort, i.e. already sorted elements will not be swapped. The
* number of swaps necessary to get the elements into sorted order is recorded and returned.
*
* Computational complexity: \f$ \mathcal{O}(n^2) \f$.
*
* This function is eager and puts the range in sorted order.
*
* @tparam BidirIt Bidirectional iterator type.
* @tparam Compare Comparison function type.
* @param first Bidirectional iterator to the first element of the range.
* @param last Bidirectional iterator to the element after the last of the range.
* @param comp Comparison function callable with two dereferenced iterators.
* @return Number of swaps necessary to sort the range.
*/
template <std::bidirectional_iterator BidirIt, class Compare = std::less<>>
std::size_t insertion_sort(BidirIt first, BidirIt last, Compare comp = {}) {
if (first == last) { return 0; }
std::size_t swaps = 0;
for (BidirIt i = std::next(first); i != last; ++i) {
for (BidirIt j = i; j != first && comp(*j, *std::prev(j)); --j) {
std::iter_swap(std::prev(j), j);
++swaps;
}
}
return swaps;
}

/**
* @brief Bubble sort elements in the given range.
*
* @details See itertools::bubble_sort for more details.
*
* @tparam Range Forward range type.
* @tparam Compare Comparison function type.
* @param rng A forward range to sort.
* @param comp Comparison function callable with two dereferenced iterators.
* @return Number of swaps necessary to sort the range.
*/
template <std::ranges::forward_range Range, class Compare = std::less<>>
std::size_t bubble_sort(Range &&rng, Compare comp = {}) { // NOLINT (ranges need not be forwarded)
return bubble_sort(std::ranges::begin(rng), std::ranges::end(rng), comp);
}

/**
* @brief Insertion sort elements in the given range.
*
* @details See itertools::insertion_sort for more details.
*
* @tparam Range Bidirectional range type.
* @tparam Compare Comparison function type.
* @param rng A bidirectional range to sort.
* @param comp Comparison function callable with two dereferenced iterators.
* @return Number of swaps necessary to sort the range.
*/
template <std::ranges::bidirectional_range Range, class Compare = std::less<>>
std::size_t insertion_sort(Range &&rng, Compare comp = {}) { // NOLINT (ranges need not be forwarded)
return insertion_sort(std::ranges::begin(rng), std::ranges::end(rng), comp);
}

/** @} */

} // namespace itertools

#endif // _ITERTOOLS_SORT_HPP
6 changes: 5 additions & 1 deletion doc/DoxygenLayout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
</tab>
<tab type="user" url="@ref itertools::product_range" title="product_range"/>
</tab>
<tab type="usergroup" url="@ref sorting" title="Sorting">
<tab type="user" url="@ref itertools::bubble_sort" title="bubble_sort"/>
<tab type="user" url="@ref itertools::insertion_sort" title="insertion_sort"/>
</tab>
<tab type="usergroup" url="@ref utilities" title="Utilities">
<tab type="user" url="@ref itertools::chunk_range" title="chunk_range"/>
<tab type="user" url="@ref itertools::distance" title="distance"/>
Expand Down Expand Up @@ -287,4 +291,4 @@
</memberdecl>
<detaileddescription title=""/>
</directory>
</doxygenlayout>
</doxygenlayout>
12 changes: 11 additions & 1 deletion doc/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ The following classes and functions related to integer ranges are defined in **i
* @ref itertools::foreach "foreach"
* @ref itertools::product_range "product_range"

## Sorting

We provide alternatives to `std::sort` that keep track of the number of swaps
that have to be performed to put a range into a sorted order.

The following @ref sorting functions are defined in **itertools**:

* @ref itertools::bubble_sort "bubble_sort"
* @ref itertools::insertion_sort "insertion_sort"

## Utilities

@ref utilities are mostly internal implementation details and should not concern everyday users.
Expand All @@ -94,4 +104,4 @@ The following utilities are defined in **itertools**:
* @ref itertools::make_sentinel "make_sentinel"
* @ref itertools::make_vector_from_range "make_vector_from_range"
* @ref itertools::omp_chunk "omp_chunk"
* @ref itertools::sentinel_t "sentinel_t"
* @ref itertools::sentinel_t "sentinel_t"
8 changes: 8 additions & 0 deletions doc/groups.dox
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
* `start + i * step`.
*/

/**
* @defgroup sorting Sorting
* @brief Functions to sort ranges.
*
* @details We provide alternatives to @p std::sort that keep track of the
* number of swaps that have to be performed to put a range into a sorted order.
*/

/**
* @defgroup utilities Utilities
* @brief Utilities are mostly internal implementation details and should not concern everyday users.
Expand Down
70 changes: 70 additions & 0 deletions test/c++/itertools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@

#include <algorithm>
#include <array>
#include <forward_list>
#include <list>
#include <iostream>
#include <numeric>
#include <random>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -369,6 +371,74 @@ TEST(Itertools, RangeRandomAccessOperations) {
}
}

// Create and fill a container of size n with random integers in [a,b].
template <typename C> auto random_int_range(std::size_t n, int a, int b) {
static std::default_random_engine eng{std::random_device{}()};
auto cont = C(std::uniform_int_distribution<std::size_t>(0, n)(eng));
std::ranges::generate(cont, [&]() { return std::uniform_int_distribution<typename C::value_type>(a, b)(eng); });
Comment on lines +376 to +378
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Thoemi09 I want to do completely non-deterministic randomized testing here, in particular on the length of the container. The first bug that I found when I wrote the test was that my functions didn't correctly sort the empty range. Even better would be fuzzing but I don't think TRIQS has any setup for that.

return cont;
}

TEST(Itertools, BubbleSort) {
// std::less, int range
auto l1 = random_int_range<std::forward_list<int>>(100, -1000, 1000);
auto l1_std = l1;
bubble_sort(l1.begin(), l1.end());
l1_std.sort();
EXPECT_EQ(l1, l1_std);

// std::greater, size_t range
auto l2 = random_int_range<std::forward_list<std::size_t>>(100, 5, 77);
auto l2_std = l2;
bubble_sort(l2, std::greater{});
l2_std.sort(std::greater{});
EXPECT_EQ(l2, l2_std);
}

TEST(Itertools, BubbleSortSwaps) {
auto v1 = std::vector<int>{100, 2, 3, 56, 200, 3, -52, 3, 3, 99, 33, 177, -199};
auto v1_std = v1;
auto swaps = bubble_sort(v1);
std::ranges::sort(v1_std);
EXPECT_EQ(v1, v1_std);
EXPECT_EQ(swaps, 37);

auto v2 = std::vector<long>{1, 2, 3};
auto swaps2 = bubble_sort(v2.begin(), v2.end(), std::greater{});
EXPECT_EQ(v2, std::vector<long>({3, 2, 1}));
EXPECT_EQ(swaps2, 3);
}

TEST(Itertools, InsertionSort) {
// std::less, int range
auto v1 = random_int_range<std::list<int>>(100, -1000, 1000);
auto v1_std = v1;
insertion_sort(v1.begin(), v1.end());
v1_std.sort();
EXPECT_EQ(v1, v1_std);

// std::greater, size_t range
auto v2 = random_int_range<std::list<std::size_t>>(100, 5, 77);
auto v2_std = v2;
insertion_sort(v2, std::greater{});
v2_std.sort(std::greater{});
EXPECT_EQ(v2, v2_std);
}

TEST(Itertools, InsertionSortSwaps) {
auto v1 = std::vector<int>{100, 2, 3, 56, 200, 3, -52, 3, 3, 99, 33, 177, -199};
auto v1_std = v1;
auto swaps = insertion_sort(v1);
std::ranges::sort(v1_std);
EXPECT_EQ(v1, v1_std);
EXPECT_EQ(swaps, 37);

auto v2 = std::vector<long>{1, 2, 3};
auto swaps2 = insertion_sort(v2.begin(), v2.end(), std::greater{});
EXPECT_EQ(v2, std::vector<long>({3, 2, 1}));
EXPECT_EQ(swaps2, 3);
}

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
Expand Down
Loading