1 module nxt.combinations;
2 
3 /** Given non-negative integers $(D m) and $(D n), generate all size $(D m)
4    combinations of the integers from 0 to $(D n)-1 in sorted order (each
5    combination is sorted and the entire table is sorted).
6 
7    For example, 3 comb 5 is
8    0 1 2
9    0 1 3
10    0 1 4
11    0 2 3
12    0 2 4
13    0 3 4
14    1 2 3
15    1 2 4
16    1 3 4
17    2 3 4
18 
19    See_Also: http://rosettacode.org/wiki/Combinations
20 */
21 struct Combinations(T, bool copy = true, bool useArray = true) {
22 	import std.container: Array;
23 	import std.traits: Unqual;
24 
25 	static if (useArray)
26 		alias Indices = Array!size_t;
27 	else
28 		alias Indices = size_t[];
29 
30 	Unqual!T[] pool, front;
31 	size_t r, n;
32 	bool empty = false;
33 
34 	Indices indices;
35 
36 	size_t len;
37 	bool lenComputed = false;
38 
39 	this(T[] pool_, in size_t r_) // pure nothrow @safe
40 	{
41 		this.pool = pool_.dup;
42 		this.r = r_;
43 		this.n = pool.length;
44 		if (r > n)
45 			empty = true;
46 
47 		indices.length = r;
48 
49 		size_t i;
50 
51 		i = 0;
52 		foreach (ref ini; indices[])
53 			ini = i++;
54 
55 		front.length = r;
56 
57 		i = 0;
58 		foreach (immutable idx; indices[])
59 			front[i++] = pool[idx];
60 	}
61 
62 	@property size_t length() /*logic_const*/ // pure nothrow @nogc
63 	{
64 		static size_t binomial(size_t n, size_t k) // pure nothrow @safe @nogc
65 		in
66 		{
67 			assert(n > 0, "binomial: n must be > 0.");
68 		}
69 		do
70 		{
71 			if (k < 0 || k > n)
72 				return 0;
73 			if (k > (n / 2))
74 				k = n - k;
75 			size_t result = 1;
76 			foreach (size_t d; 1 .. k + 1) {
77 				result *= n;
78 				n--;
79 				result /= d;
80 			}
81 			return result;
82 		}
83 
84 		if (!lenComputed) {
85 			// Set cache.
86 			len = binomial(n, r);
87 			lenComputed = true;
88 		}
89 		return len;
90 	}
91 
92 	void popFront() // pure nothrow @safe
93 	{
94 		if (!empty) {
95 			bool broken = false;
96 			size_t pos = 0;
97 			foreach_reverse (immutable i; 0 .. r) {
98 				pos = i;
99 				if (indices[i] != i + n - r) {
100 					broken = true;
101 					break;
102 				}
103 			}
104 			if (!broken) {
105 				empty = true;
106 				return;
107 			}
108 			indices[pos]++;
109 			foreach (immutable j; pos + 1 .. r)
110 				indices[j] = indices[j - 1] + 1;
111 			static if (copy)
112 				front = new Unqual!T[front.length];
113 
114 			size_t i = 0;
115 			foreach (immutable idx; indices[]) {
116 				front[i] = pool[idx];
117 				i++;
118 			}
119 		}
120 	}
121 }
122 
123 auto combinations(bool copy = true, T, bool useArray = false)(T[] items, in size_t k)
124 in(items.length)
125 	=> Combinations!(T, copy, useArray)(items, k);
126 
127 unittest {
128 	import std.algorithm: equal, map;
129 	// assert(equal([1, 2, 3, 4].combinations!false(2), [[3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4]]));
130 	enum solution = [[1, 2],
131 					 [1, 3],
132 					 [1, 4],
133 					 [2, 3],
134 					 [2, 4],
135 					 [3, 4]];
136 	assert(equal([1, 2, 3, 4].combinations!true(2), solution));
137 	assert(equal([1, 2, 3, 4].combinations(2).map!(x => x), solution));
138 }
139 
140 import std.range.primitives : isInputRange;
141 
142 /** All Unordered Element Pairs (2-Element Subsets) of a $(D Range).
143 
144 	TODO: Add template parameter to decide if .array should be used internally.
145 
146 	See_Also: http://forum.dlang.org/thread/iqkybajwdzcvdytakgvw@forum.dlang.org#post-vhufbwsqbssyqwfxxbuu:40forum.dlang.org
147 	See_Also: https://issues.dlang.org/show_bug.cgi?id=6788
148 	See_Also: https://issues.dlang.org/show_bug.cgi?id=7128
149 */
150 auto pairwise(R)(R r)
151 if (isInputRange!R) {
152 	struct Pairwise(R) {
153 		import core.internal.traits : Unqual;
154 		import std.traits : ForeachType;
155 		import std.typecons: Tuple;
156 
157 		alias UR = Unqual!R;
158 		alias E = ForeachType!UR;
159 		alias Pair = Tuple!(E, E);
160 
161 		import std.range.primitives : isRandomAccessRange, hasLength;
162 		import std.traits : isNarrowString;
163 
164 		static if (isRandomAccessRange!R &&
165 				   hasLength!R &&
166 				   !isNarrowString!R) {
167 
168 			this(R r_) {
169 				this._input = r_;
170 				j = 1;
171 			}
172 			@property bool empty() => j >= _input.length;
173 			@property Pair front() => typeof(return)(_input[i], _input[j]);
174 			void popFront() {
175 				if (j >= _input.length - 1) {
176 					i++;
177 					j = i + 1;
178 				}
179 				else
180 					j++;
181 			}
182 			private size_t i, j;
183 		}
184 		else // isInputRange!UR
185 		{
186 			import std.range : dropOne;
187 			this(R r_) {
188 				this._input = r_;
189 				i = r_;
190 				if (!i.empty)
191 					j = i.dropOne;
192 				else
193 					j = UR.init;
194 			}
195 			@property bool empty() => j.empty;
196 			@property Pair front() => typeof(return)(i.front, j.front);
197 			void popFront() {
198 				j.popFront();
199 				if (j.empty) {
200 					i.popFront();
201 					j = i.dropOne;
202 				}
203 			}
204 			private UR i, j; // temporary copies of $(D _input)
205 		}
206 
207 	private:
208 		UR _input;
209 	}
210 
211 	return Pairwise!R(r);
212 }
213 
214 /// test RandomAccessRange input
215 unittest {
216 	import std.algorithm: equal, filter;
217 	import std.typecons: Tuple;
218 
219 	assert((new int[0]).pairwise.empty);
220 	assert([1].pairwise.empty);
221 
222 	alias T = Tuple!(int, int);
223 	assert(equal([1, 2].pairwise,
224 				 [T(1, 2)]));
225 	assert(equal([1, 2, 3].pairwise,
226 				 [T(1, 2), T(1, 3), T(2, 3)]));
227 	assert(equal([1, 2, 3, 4].pairwise,
228 				 [T(1, 2), T(1, 3), T(1, 4),
229 				  T(2, 3), T(2, 4), T(3, 4)]));
230 }
231 
232 /// test ForwardRange input
233 unittest {
234 	import std.algorithm: equal, filter;
235 	import std.array : array;
236 
237 	auto p = [1].filter!"a < 4".pairwise;
238 	assert(p.empty);
239 
240 	assert(equal(p.array,
241 				 [1].pairwise.array));
242 
243 	assert(equal([1, 2, 3, 4].filter!"a < 4".pairwise,
244 				 [1, 2, 3].pairwise));
245 }