1 /** 2 * ae.utils.math.combinatorics 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.utils.math.combinatorics; 15 16 /// https://en.wikipedia.org/wiki/Binomial_coefficient 17 /// This implementation is non-recursive but overflows easily 18 R binomialCoefficient(T, R=T)(T n, T k) 19 { 20 if (n < k) 21 return R(0); 22 R result = 1; 23 foreach (i; n - k + 1 .. n + 1) 24 result *= i; 25 foreach (i; 1 .. k + 1) 26 result /= i; 27 return result; 28 } 29 30 unittest 31 { 32 assert(binomialCoefficient(3067L, 3) == 4803581405); 33 } 34 35 unittest 36 { 37 import std.bigint : BigInt; 38 assert(binomialCoefficient!(int, BigInt)(3067, 3) == 4803581405); 39 } 40 41 /// https://en.wikipedia.org/wiki/Multiset#Counting_multisets 42 R multisetCoefficient(T, R=T)(T n, T k) 43 { 44 return binomialCoefficient!(T, R)(n + k - 1, k); 45 } 46 47 unittest 48 { 49 assert(multisetCoefficient(3067L, 3) == 4812987894); 50 } 51 52 /// Precalculated binomial coefficient table 53 struct BinomialCoefficientTable(T, T maxN, T maxK) 54 { 55 static assert(maxN >= 1); 56 static assert(maxK >= 1); 57 58 T[maxK + 1][maxN + 1] table; 59 60 static typeof(this) generate() 61 { 62 typeof(this) result; 63 result.table[0][0] = 1; 64 foreach (size_t n; 1 .. maxN + 1) 65 { 66 result.table[n][0] = 1; 67 foreach (size_t k; 1 .. maxK + 1) 68 result.table[n][k] = cast(T)(result.table[n-1][k-1] + result.table[n-1][k]); 69 } 70 return result; 71 } 72 73 T binomialCoefficient(size_t n, size_t k) const 74 { 75 return table[n][k]; 76 } 77 78 T multisetCoefficient(size_t n, size_t k) const 79 { 80 return binomialCoefficient(n + k - 1, k); 81 } 82 } 83 84 unittest 85 { 86 assert(BinomialCoefficientTable!(ulong, 5000, 3).generate().binomialCoefficient(3067, 3) == 4803581405); 87 } 88 89 /// Combinatorial number system encoder/decoder 90 /// https://en.wikipedia.org/wiki/Combinatorial_number_system 91 struct CNS( 92 /// Type for packed representation 93 P, 94 /// Type for one position in unpacked representation 95 U, 96 /// Number of positions in unpacked representation 97 size_t N, 98 /// Cardinality (maximum value plus one) of one position in unpacked representation 99 U unpackedCard, 100 /// Produce lexicographic ordering? 101 bool lexicographic, 102 /// Are repetitions representable? (multiset support) 103 bool multiset, 104 /// Binomial coefficient calculator implementation 105 /// (You can supply a precalculated BinomialCoefficientTable here) 106 alias binomialCalculator = ae.utils.math.combinatorics, 107 ) 108 { 109 static: 110 /// Cardinality (maximum value plus one) of the packed representation 111 static if (multiset) 112 enum P packedCard = multisetCoefficient(unpackedCard, N); 113 else 114 enum P packedCard = binomialCoefficient(unpackedCard, N); 115 alias Index = P; 116 117 private P summand(U value, Index i) 118 { 119 static if (lexicographic) 120 { 121 value = cast(U)(unpackedCard-1 - value); 122 i = cast(Index)(N-1 - i); 123 } 124 static if (multiset) 125 value += i; 126 return binomialCalculator.binomialCoefficient(value, i + 1); 127 } 128 129 P pack(U[N] values) 130 { 131 P packed = 0; 132 foreach (Index i, value; values) 133 { 134 static if (!multiset) 135 assert(i == 0 || value > values[i-1]); 136 else 137 assert(i == 0 || value >= values[i-1]); 138 139 packed += summand(value, i); 140 } 141 142 static if (lexicographic) 143 packed = packedCard-1 - packed; 144 return packed; 145 } 146 147 U[N] unpack(P packed) @nogc 148 { 149 import std.algorithm.iteration : map; 150 import std.range : iota, retro, assumeSorted, SearchPolicy; 151 152 import ae.utils.meta : I; 153 154 static if (lexicographic) 155 packed = packedCard-1 - packed; 156 157 // We make i a compile-time parameter here to avoid 158 // allocating a closure for std.algorithm.map 159 void unpackOne(Index i)(ref U r) @nogc 160 { 161 static if (lexicographic) 162 enum lastValue = 0; 163 else 164 enum lastValue = unpackedCard - 1; 165 166 bool checkValue(U value, U nextValue) 167 { 168 if (value == lastValue || summand(nextValue, i) > packed) 169 { 170 r = value; 171 packed -= summand(value, i); 172 return true; 173 } 174 return false; 175 } 176 177 static if (lexicographic) 178 { 179 r = unpackedCard 180 .iota 181 .map!(value => summand(value, i)) 182 .retro 183 .assumeSorted 184 .lowerBound!(SearchPolicy.binarySearch)(packed + 1) 185 .length 186 .I!(l => cast(U)(unpackedCard - l)); 187 packed -= summand(r, i); 188 } 189 else 190 { 191 r = unpackedCard 192 .iota 193 .map!(value => summand(value, i)) 194 .assumeSorted 195 .lowerBound!(SearchPolicy.binarySearch)(packed + 1) 196 .length 197 .I!(l => cast(U)(l - 1)); 198 packed -= summand(r, i); 199 } 200 } 201 202 U[N] values; 203 static if (lexicographic) 204 static foreach (Index i; 0 .. N) 205 unpackOne!i(values[i]); 206 else 207 static foreach_reverse (Index i; 0 .. N) 208 unpackOne!i(values[i]); 209 210 return values; 211 } 212 } 213 214 static if (__VERSION__ >= 2_096) 215 unittest 216 { 217 enum N = 3; 218 enum cardinality = 10; 219 220 static foreach (lexicographic; [false, true]) 221 static foreach (multiset; [false, true]) 222 static foreach (precalculate; [false, true]) 223 {{ 224 static if (precalculate) 225 static immutable calculator = BinomialCoefficientTable!(uint, cardinality + 1, N).generate(); 226 else 227 alias calculator = ae.utils.math.combinatorics; 228 alias testCNS = CNS!(uint, ubyte, N, cardinality, lexicographic, multiset, calculator); 229 230 uint counter; 231 foreach (ubyte a; 0 .. cardinality) 232 foreach (ubyte b; cast(ubyte)(a + (multiset ? 0 : 1)) .. cardinality) 233 foreach (ubyte c; cast(ubyte)(b + (multiset ? 0 : 1)) .. cardinality) 234 { 235 ubyte[N] input = [a, b, c]; 236 auto packed = testCNS.pack(input); 237 assert(packed < testCNS.packedCard); 238 static if (lexicographic) 239 assert(counter == packed, "Packed is wrong"); 240 auto unpacked = testCNS.unpack(packed); 241 assert(input == unpacked, "Unpacked is wrong"); 242 counter++; 243 } 244 }} 245 }