diff --git a/README.md b/README.md
index b293c690..a5b405a6 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@ evaluation wherever possible.
[accumulate](#accumulate)
[compress](#compress)
[sorted](#sorted)
+[shuffled](#shuffled)
[chain](#chain)
[chain.from\_iterable](#chainfrom_iterable)
[reversed](#reversed)
@@ -528,6 +529,25 @@ The below outputs `0 1 2 3 4`.
```c++
unordered_set nums{4, 0, 2, 1, 3};
for (auto&& i : sorted(nums)) {
+ cout << i << ' ';
+}
+```
+
+shuffled
+--------
+*Additional Requirements*: Input must have a ForwardIterator.
+
+Allows iteration over a sequence in shuffled order. `shuffled` does
+**not** produce a new sequence, copy elements, or modify the original
+sequence. It only provides a way to iterate over existing elements.
+`shuffled` also takes an optional second argument - randomization seed.
+If not provided, defaults to 1.
+
+The below outputs `0 2 4 3 1`.
+
+```c++
+unordered_set nums{4, 0, 2, 1, 3};
+for (auto&& i : shuffled(nums)) {
cout << i << '\n';
}
```
diff --git a/shuffled.hpp b/shuffled.hpp
new file mode 100644
index 00000000..73919fc9
--- /dev/null
+++ b/shuffled.hpp
@@ -0,0 +1,197 @@
+#ifndef ITER_SHUFFLED_HPP_
+#define ITER_SHUFFLED_HPP_
+
+#include "internal/iterbase.hpp"
+
+/* Suppose the size of the container is N. We can find the power of two,
+ * that is greater or equal to N - K. Numbers from 1 to K can be easy
+ * shuffled with help of Linear Feedback Shift Register (LFSR) using
+ * special prime poly. Access to every element of the shuffled is
+ * implemented through advance of the first (begin) iterator of the
+ * container, so random access iterator is desirable. In the constructor
+ * we have to calculate the total size of the container, so
+ * std::distance will be used. User can define more effective
+ * std::distance for the container.*/
+
+namespace iter {
+ namespace impl {
+ template
+ class ShuffledView;
+ // linear feedback shift register
+ namespace lfsr {
+ const uint16_t PRIME_POLY[64] = {
+ 1, 3, 3, 3, 5, 3, 3, 29, 17, 9, 5, 83, 27, 43, 3, 45, 9, 129, 39,
+ 9, 5, 3, 33, 27, 9, 71, 39, 9, 5, 83, 9, 197, 8193, 231, 5, 119,
+ 83, 99, 17, 57, 9, 153, 89, 101, 27, 449, 33, 183, 113, 29, 75,
+ 9, 71, 125, 71, 149, 129, 99, 123, 3, 39, 105, 3, 27
+ }; // prime poly for full-cycle shift registers
+ uint16_t get_approx(uint64_t val);
+ uint64_t shift(uint64_t reg, uint8_t reg_size);
+ }
+ }
+
+ template
+ impl::ShuffledView shuffled(Container&& container, int seed = 1) {
+ return {std::forward(container), seed};
+ }
+}
+
+// power of 2 approximation (val < pow(2, get_approx(val)+1))
+inline uint16_t
+iter::impl::lfsr::get_approx(uint64_t val) {
+ if (val == 0)
+ return 0;
+ uint16_t pow2_approx = 0;
+ if (val > 9223372036854775808ULL) {
+ pow2_approx = 63;
+ }
+ else {
+ while (pow2_approx < 64) {
+ if (val >= (static_cast(1) << (pow2_approx + 1)))
+ ++pow2_approx;
+ else
+ break;
+ }
+ }
+ return pow2_approx;
+}
+
+inline uint64_t
+iter::impl::lfsr::shift(uint64_t reg, uint8_t reg_size) {
+ if (reg & 1) {
+ reg = ((reg ^ PRIME_POLY[reg_size]) >> 1) |
+ (static_cast(1) << reg_size);
+ }
+ else {
+ reg >>= 1;
+ }
+ return reg;
+}
+
+template
+class iter::impl::ShuffledView {
+ private:
+
+ uint64_t size;
+ uint8_t size_approx;
+ iterator_type in_begin;
+ uint64_t seed;
+ Container container;
+
+ template
+ friend ShuffledView iter::shuffled(C&&, int);
+
+
+ public:
+ using IterDeref = typename std::remove_reference>;
+ ShuffledView(ShuffledView&& copy)
+ : size(0), container(std::forward(copy.container)) {};
+ ShuffledView(Container&& container, int seed)
+ : size(std::distance(std::begin(container), std::end(container))),
+ size_approx(lfsr::get_approx(size)),
+ in_begin(std::begin(container)), seed(seed),
+ container(std::forward(container)) {
+ if (size == 1) {
+ this->seed = 1;
+ }
+ else if (size > 1) {
+ uint64_t mask = 0xFFFFFFFFFFFFFFFFULL;
+ mask = (mask >> (64-(size_approx+1)));
+ this->seed = seed & mask;
+ this->seed = lfsr::shift(this->seed, size_approx);
+ while(this->seed > size)
+ this->seed = lfsr::shift(this->seed, size_approx);
+ }
+ }
+
+ class Iterator {
+ private:
+ friend class ShuffledView;
+ ShuffledView* owner;
+ uint64_t state;
+ iterator_type copy; // referenced by operator* value
+
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using value_type = iterator_traits_deref;
+ using difference_type = std::ptrdiff_t;
+ using pointer = value_type*;
+ using reference = value_type&;
+
+ Iterator() : owner(nullptr), state(0) {}
+ Iterator(const Iterator& other) { operator=(other); }
+ Iterator& operator=(const Iterator& other) {
+ owner = other.owner;
+ state = other.state;
+ return *this;
+ };
+
+ Iterator& operator++() {
+ state = lfsr::shift(state, owner->size_approx);
+ while(state > owner->size)
+ state = lfsr::shift(state, owner->size_approx);
+ if (state == owner->seed)
+ operator=(owner->end());
+ return *this;
+ }
+
+ Iterator operator++(int) {
+ auto ret = *this;
+ ++*this;
+ return ret;
+ }
+
+ bool operator==(const Iterator& other) const {
+ return owner == other.owner && state == other.state;
+ }
+
+ bool operator!=(const Iterator& other) const {
+ return !operator==(other);
+ }
+
+ auto operator*() -> decltype(*copy) {
+ copy = owner->in_begin;
+ dumb_advance(copy, std::end(owner->container), static_cast(state-1));
+ return *copy;
+ }
+
+ auto operator->() -> ArrowProxy{
+ return {**this};
+ }
+ };
+
+ Iterator begin() {
+ Iterator begin;
+ begin.owner = this;
+ begin.state = (size == 0 ? 0 : seed);
+ return begin;
+ }
+
+ Iterator end() {
+ Iterator end;
+ end.owner = this;
+ end.state = 0;
+ return end;
+ }
+
+ /* @brief restore iteration through a vector in shuffled order from the
+ * index of the last seen element in non shuffled container
+ *
+ * @example:
+ * v = {"apple", "banana", "orange", "peach"}; // original vector
+ * s = shuffled(v); // {"orange", "peach", "banana", "apple"} - shuffled vector
+ *
+ * We iterated through s. Last seen element was "banana". In the
+ * original vector "banana" had index 1. So to restore shuffling wee
+ * need to do s.restore(1); Operation is zero cost - no iterators
+ * advance is needed.*/
+ Iterator restore(uint64_t state) {
+ Iterator rs;
+ rs.owner = this;
+ rs.state = (state >= size ? seed : state + 1);
+ return rs;
+ }
+};
+
+#endif
+
diff --git a/test/SConstruct b/test/SConstruct
index d9513f5f..2c7fae77 100644
--- a/test/SConstruct
+++ b/test/SConstruct
@@ -36,6 +36,7 @@ progs = Split(
slice
sliding_window
sorted
+ shuffled
takewhile
unique_everseen
unique_justseen
diff --git a/test/test_shuffled.cpp b/test/test_shuffled.cpp
new file mode 100644
index 00000000..c865d559
--- /dev/null
+++ b/test/test_shuffled.cpp
@@ -0,0 +1,251 @@
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "helpers.hpp"
+#include "catch.hpp"
+
+using iter::shuffled;
+
+using Vec = const std::vector;
+
+TEST_CASE("shuffled: iterates through a vector in shuffled order", "[shuffled]") {
+ Vec ns = {4, 0, 5, 1, 6, 7, 9, 3, 2, 8};
+ auto s = shuffled(ns);
+ Vec v(std::begin(s), std::end(s));
+ Vec vc = {2, 9, 8, 6, 7, 5, 3, 1, 0, 4};
+ REQUIRE(v == vc);
+}
+
+TEST_CASE("shuffled: shuffling one-element container", "[shuffled]") {
+ Vec ns = {4};
+ auto s = shuffled(ns);
+ Vec v(std::begin(s), std::end(s));
+ Vec vc = {4};
+ REQUIRE(v == vc);
+}
+
+TEST_CASE("shuffled: shuffling two-element container", "[shuffled]") {
+ Vec ns = {4, 3};
+ auto s = shuffled(ns);
+ Vec v(std::begin(s), std::end(s));
+ Vec vc = {3, 4};
+ REQUIRE(v == vc);
+}
+
+TEST_CASE("shuffled: iterates through a list of strings in shuffled order",
+ "[shuffled]") {
+ std::list ns = {"apple", "banana", "orange", "peach"};
+ auto s = shuffled(ns);
+ std::list v(std::begin(s), std::end(s));
+ std::list vc = {"orange", "peach", "banana", "apple"};
+ REQUIRE(v == vc);
+}
+
+TEST_CASE("shuffled: restore iteration through a vector in shuffled order "
+ "from the index of the last seen element in non shuffled container",
+ "[shuffled]") {
+ //index: 0 1 2 3 4 5 6 7 8 9
+ Vec ns = {4, 0, 5, 1, 6, 7, 9, 3, 2, 8};
+ auto s = shuffled(ns);
+
+ // overflow - same as default
+ Vec v10(s.restore(10), std::end(s));
+ Vec vc10 = {2, 9, 8, 6, 7, 5, 3, 1, 0, 4};
+ REQUIRE(v10 == vc10);
+
+ Vec v1(s.restore(6), std::end(s));
+ Vec vc1 = {9, 8, 6, 7, 5, 3, 1, 0, 4};
+ REQUIRE(v1 == vc1);
+
+ Vec v2(s.restore(9), std::end(s));
+ Vec vc2 = {8, 6, 7, 5, 3, 1, 0, 4};
+ REQUIRE(v2 == vc2);
+
+ Vec v3(s.restore(4), std::end(s));
+ Vec vc3 = {6, 7, 5, 3, 1, 0, 4};
+ REQUIRE(v3 == vc3);
+
+ Vec v4(s.restore(5), std::end(s));
+ Vec vc4 = {7, 5, 3, 1, 0, 4};
+ REQUIRE(v4 == vc4);
+
+ Vec v5(s.restore(2), std::end(s));
+ Vec vc5 = {5, 3, 1, 0, 4};
+ REQUIRE(v5 == vc5);
+
+ Vec v6(s.restore(7), std::end(s));
+ Vec vc6 = {3, 1, 0, 4};
+ REQUIRE(v6 == vc6);
+
+ Vec v7(s.restore(3), std::end(s));
+ Vec vc7 = {1, 0, 4};
+ REQUIRE(v7 == vc7);
+
+ Vec v8(s.restore(1), std::end(s));
+ Vec vc8 = {0, 4};
+ REQUIRE(v8 == vc8);
+
+ Vec v9(s.restore(0), std::end(s));
+ Vec vc9 = {4};
+ REQUIRE(v9 == vc9);
+}
+
+TEST_CASE("shuffled: restore iteration through a vector in shuffled order "
+ "from the index of the last seen element in non shuffled "
+ "container of strings", "[shuffled]") {
+ std::list ns = {"apple", "banana", "orange", "peach"};
+ auto s = shuffled(ns);
+ std::list v(s.restore(1), std::end(s));
+ std::list vc = {"banana", "apple"};
+ REQUIRE(v == vc);
+}
+
+TEST_CASE("shuffled: iterates through a vector in shuffled order with non"
+ " standard seed", "[shuffled]") {
+ Vec ns = {4, 0, 5, 1, 6, 7, 9, 3, 2, 8};
+ auto s = shuffled(ns, 1234);
+ Vec v(std::begin(s), std::end(s));
+ Vec vc = {4, 2, 9, 8, 6, 7, 5, 3, 1, 0};
+ REQUIRE(v == vc);
+}
+
+TEST_CASE("shuffled: can modify elements through shuffled", "[shuffled]") {
+ std::vector ns(3, 9);
+ for (auto&& n : shuffled(ns)) {
+ n = -1;
+ }
+ Vec vc(3, -1);
+ REQUIRE(ns == vc);
+}
+
+TEST_CASE("shuffled: can iterate over unordered container", "[shuffled]") {
+ std::unordered_set ns = {1, 3, 2, 0, 4};
+ auto s = shuffled(ns);
+
+ Vec v(std::begin(s), std::end(s));
+ Vec vc = {1, 2, 3, 0, 4};
+ REQUIRE(v == vc);
+}
+
+TEST_CASE("shuffled: empty when iterable is empty", "[shuffled]") {
+ Vec ns{};
+ auto s = shuffled(ns);
+ REQUIRE(std::begin(s) == std::end(s));
+}
+
+namespace {
+ template
+ class BasicIterableWithConstDeref {
+ private:
+ T* data;
+ std::size_t size;
+ bool was_moved_from_ = false;
+
+ public:
+ BasicIterableWithConstDeref(std::initializer_list il)
+ : data{new T[il.size()]}, size{il.size()} {
+ // would like to use enumerate, can't because it's for unit
+ // testing enumerate
+ std::size_t i = 0;
+ for (auto&& e : il) {
+ data[i] = e;
+ ++i;
+ }
+ }
+
+ BasicIterableWithConstDeref& operator=(
+ BasicIterableWithConstDeref&&) = delete;
+ BasicIterableWithConstDeref& operator=(
+ const BasicIterableWithConstDeref&) = delete;
+ BasicIterableWithConstDeref(const BasicIterableWithConstDeref&) = delete;
+
+ BasicIterableWithConstDeref(BasicIterableWithConstDeref&& other)
+ : data{other.data}, size{other.size} {
+ other.data = nullptr;
+ other.was_moved_from_ = true;
+ }
+
+ bool was_moved_from() const {
+ return this->was_moved_from_;
+ }
+
+ ~BasicIterableWithConstDeref() {
+ delete[] this->data;
+ }
+
+ class Iterator
+ : public std::iterator {
+ private:
+ T* p;
+
+ public:
+ Iterator(T* b) : p{b} {}
+ bool operator!=(const Iterator& other) const {
+ return this->p != other.p;
+ }
+
+ Iterator& operator++() {
+ ++this->p;
+ return *this;
+ }
+
+ T& operator*() {
+ return *this->p;
+ }
+
+ const T& operator*() const {
+ return *this->p;
+ }
+ };
+
+ Iterator begin() {
+ return {this->data};
+ }
+
+ Iterator end() {
+ return {this->data + this->size};
+ }
+ };
+}
+
+TEST_CASE("shuffled: moves rvalues and binds to lvalues", "[shuffled]") {
+ BasicIterableWithConstDeref bi{1, 2};
+ shuffled(bi);
+ REQUIRE_FALSE(bi.was_moved_from());
+
+ shuffled(std::move(bi));
+ REQUIRE(bi.was_moved_from());
+}
+
+TEST_CASE("shuffled: doesn't move or copy elements of iterable", "[shuffled]") {
+ using itertest::SolidInt;
+ constexpr SolidInt arr[] = {{6}, {7}, {8}};
+ for (auto &&i : shuffled(arr))
+ (void)i;
+}
+
+template
+using ImpT = decltype(shuffled(std::declval()));
+TEST_CASE("shuffled: has correct ctor and assign ops", "[shuffled]") {
+ REQUIRE(itertest::IsMoveConstructibleOnly>::value);
+ REQUIRE(itertest::IsMoveConstructibleOnly>::value);
+}
+
+TEST_CASE("shuffled: correct work with 1 and 0 sized container.", "[shuffled]") {
+ Vec onesz = {7};
+ for (auto &&i : shuffled(onesz))
+ REQUIRE(i == 7);
+ Vec zerosz;
+ for (auto &&i : shuffled(zerosz))
+ {
+ (void)i; // prevent warning
+ REQUIRE(false);
+ }
+}