Skip to content

Commit 7447950

Browse files
committed
ENH: shuffled released
1 parent a9c719a commit 7447950

File tree

4 files changed

+430
-0
lines changed

4 files changed

+430
-0
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ evaluation wherever possible.
2626
[accumulate](#accumulate)<br />
2727
[compress](#compress)<br />
2828
[sorted](#sorted)<br />
29+
[shuffled](#shuffled)<br />
2930
[chain](#chain)<br />
3031
[chain.from\_iterable](#chainfrom_iterable)<br />
3132
[reversed](#reversed)<br />
@@ -525,6 +526,25 @@ an `operator*() const` member.
525526

526527
The below outputs `0 1 2 3 4`.
527528

529+
```c++
530+
unordered_set<int> nums{4, 0, 2, 1, 3};
531+
for (auto&& i : sorted(nums)) {
532+
cout << i << ' ';
533+
}
534+
```
535+
536+
shuffled
537+
--------
538+
*Additional Requirements*: Input must have a ForwardIterator.
539+
540+
Allows iteration over a sequence in shuffled order. `shuffled` does
541+
**not** produce a new sequence, copy elements, or modify the original
542+
sequence. It only provides a way to iterate over existing elements.
543+
`shuffled` also takes an optional second argument - randomization seed.
544+
If not provided, defaults to 1.
545+
546+
The below outputs `0 2 4 3 1`.
547+
528548
```c++
529549
unordered_set<int> nums{4, 0, 2, 1, 3};
530550
for (auto&& i : sorted(nums)) {

shuffled.hpp

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#ifndef ITER_SHUFFLED_HPP_
2+
#define ITER_SHUFFLED_HPP_
3+
4+
#include "internal/iterbase.hpp"
5+
6+
/* Suppose the size of the container is N. We can find the power of two,
7+
* that is greater or equal to N - K. Numbers from 1 to K can be easy
8+
* shuffled with help of Linear Feedback Shift Register (LFSR) using
9+
* special prime poly. Access to every element of the shuffled is
10+
* implemented through advance of the first (begin) iterator of the
11+
* container, so random access iterator is desirable. In the constructor
12+
* we have to calculate the total size of the container, so
13+
* dumb_distance will be used.*/
14+
15+
namespace iter {
16+
namespace impl {
17+
template <typename Container, typename Distance = std::size_t>
18+
class ShuffledView;
19+
// linear feedback shift register
20+
namespace lfsr {
21+
const uint16_t PRIME_POLY[64] = {
22+
1, 3, 3, 3, 5, 3, 3, 29, 17, 9, 5, 83, 27, 43, 3, 45, 9, 129, 39,
23+
9, 5, 3, 33, 27, 9, 71, 39, 9, 5, 83, 9, 197, 8193, 231, 5, 119,
24+
83, 99, 17, 57, 9, 153, 89, 101, 27, 449, 33, 183, 113, 29, 75,
25+
9, 71, 125, 71, 149, 129, 99, 123, 3, 39, 105, 3, 27
26+
}; // prime poly for full-cycle shift registers
27+
uint16_t get_approx(uint64_t val);
28+
uint64_t shift(uint64_t reg, uint8_t reg_size);
29+
}
30+
}
31+
32+
template <typename Container, typename Distance = std::size_t>
33+
impl::ShuffledView<Container, Distance> shuffled(Container&&, int seed = 1);
34+
}
35+
36+
// power of 2 approximation
37+
uint16_t iter::impl::lfsr::get_approx(uint64_t val) {
38+
if (val == 0)
39+
return 0;
40+
uint16_t pow2_approx = 0;
41+
if (val > 9223372036854775808UL) {
42+
pow2_approx = 63;
43+
}
44+
else {
45+
while (pow2_approx < 64) {
46+
if (val >= (1UL << (pow2_approx + 1)))
47+
++pow2_approx;
48+
else
49+
break;
50+
}
51+
}
52+
return pow2_approx + 1;
53+
}
54+
55+
uint64_t iter::impl::lfsr::shift(uint64_t reg, uint8_t reg_size) {
56+
if (reg & 1)
57+
reg = ((reg ^ PRIME_POLY[reg_size]) >> 1) | (1 << reg_size);
58+
else
59+
reg >>= 1;
60+
return reg;
61+
}
62+
63+
template <typename Container, typename Distance>
64+
class iter::impl::ShuffledView {
65+
private:
66+
67+
Distance size;
68+
uint8_t size_approx;
69+
iterator_type<Container> in_begin;
70+
uint64_t seed;
71+
72+
template <typename C, typename D>
73+
friend ShuffledView<C, D> iter::shuffled(C&&, int);
74+
75+
76+
public:
77+
using IterDeref = typename std::remove_reference<iterator_deref<Container>>;
78+
ShuffledView(ShuffledView&&) : size(0) {};
79+
ShuffledView(Container&& container, int seed)
80+
: size(dumb_size(container)), size_approx(lfsr::get_approx(size)),
81+
in_begin(std::begin(container)), seed(seed) {
82+
if (size > 0)
83+
{
84+
uint64_t mask = 0;
85+
std::uninitialized_fill((char*)&mask, (char*)&mask + size_approx, 0xFF);
86+
this->seed = seed & mask;
87+
this->seed = lfsr::shift(this->seed, size_approx);
88+
while(this->seed >= size)
89+
this->seed = lfsr::shift(this->seed, size_approx);
90+
}
91+
}
92+
93+
class Iterator
94+
: public std::iterator<std::input_iterator_tag, IterDeref> {
95+
private:
96+
friend class ShuffledView<Container, Distance>;
97+
ShuffledView<Container, Distance>* owner;
98+
uint64_t state;
99+
iterator_type<Container> copy; // referenced by operator* value
100+
101+
public:
102+
Iterator() : owner(nullptr), state(0) {}
103+
Iterator& operator=(const Iterator& other) {
104+
owner = other.owner;
105+
state = other.state;
106+
return *this;
107+
};
108+
109+
Iterator& operator++() {
110+
if (operator==(owner->end()))
111+
return *this;
112+
state = lfsr::shift(state, owner->size_approx);
113+
while(state > owner->size)
114+
state = lfsr::shift(state, owner->size_approx);
115+
if (state == owner->seed)
116+
operator=(owner->end());
117+
return *this;
118+
}
119+
120+
Iterator operator++(int) {
121+
auto ret = *this;
122+
++*this;
123+
return ret;
124+
}
125+
126+
bool operator==(const Iterator& other) const {
127+
return owner == other.owner && state == other.state;
128+
}
129+
130+
bool operator!=(const Iterator& other) const {
131+
return !operator==(other);
132+
}
133+
134+
auto operator*() -> decltype(*copy) {
135+
copy = owner->in_begin;
136+
dumb_advance(copy, static_cast<Distance>(state-1));
137+
return *copy;
138+
}
139+
140+
ArrowProxy<IterDeref> operator->() {
141+
return {**this};
142+
}
143+
};
144+
145+
Iterator begin() {
146+
Iterator begin;
147+
begin.owner = this;
148+
begin.state = (size == 0 ? 0 : seed);
149+
return begin;
150+
}
151+
152+
Iterator end() {
153+
Iterator end;
154+
end.owner = this;
155+
end.state = 0;
156+
return end;
157+
}
158+
159+
/* @brief restore iteration through a vector in shuffled order from the
160+
* index of the last seen element in non shuffled container
161+
*
162+
* @example:
163+
* v = {"apple", "banana", "orange", "peach"}; // original vector
164+
* s = shuffled(v); // {"orange", "peach", "banana", "apple"} - shuffled vector
165+
*
166+
* We iterated through s. Last seen element was "banana". In the
167+
* original vector "banana" had index 1. So to restore shuffling wee
168+
* need to do s.restore(1); Operation is zero cost - no iterators
169+
* advance is needed.*/
170+
Iterator restore(uint64_t state) {
171+
Iterator rs;
172+
rs.owner = this;
173+
rs.state = (state >= size ? seed : state + 1);
174+
return rs;
175+
}
176+
};
177+
178+
template <typename Container, typename Distance = std::size_t>
179+
iter::impl::ShuffledView<Container, Distance> iter::shuffled(
180+
Container&& container, int seed = 1) {
181+
return {std::forward<Container>(container), seed};
182+
}
183+
184+
#endif
185+

test/SConstruct

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ progs = Split(
3636
slice
3737
sliding_window
3838
sorted
39+
shuffled
3940
takewhile
4041
unique_everseen
4142
unique_justseen

0 commit comments

Comments
 (0)