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 }