The OpenD Programming Language

1 /**
2 This module contains various combinatorics algorithms.
3 
4 Authors: Sebastian Wilzbach, Ilia Ki
5 
6 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
7 */
8 module mir.combinatorics;
9 
10 import mir.primitives: hasLength;
11 import mir.qualifier;
12 import std.traits;
13 
14 ///
15 version(mir_test) unittest
16 {
17     import mir.ndslice.fuse;
18     import std.string: representation;
19 
20     assert(['a', 'b'].representation.permutations.fuse == [['a', 'b'], ['b', 'a']]);
21     assert(['a', 'b'].representation.cartesianPower(2).fuse == [['a', 'a'], ['a', 'b'], ['b', 'a'], ['b', 'b']]);
22     assert(['a', 'b'].representation.combinations(2).fuse == [['a', 'b']]);
23     assert(['a', 'b'].representation.combinationsRepeat(2).fuse == [['a', 'a'], ['a', 'b'], ['b', 'b']]);
24 
25     assert(permutations!ushort(2).fuse == [[0, 1], [1, 0]]);
26     assert(cartesianPower!ushort(2, 2).fuse == [[0, 0], [0, 1], [1, 0], [1, 1]]);
27     assert(combinations!ushort(2, 2).fuse == [[0, 1]]);
28     assert(combinationsRepeat!ushort(2, 2).fuse == [[0, 0], [0, 1], [1, 1]]);
29 
30     assert([3, 1].permutations!ubyte.fuse == [[3, 1], [1, 3]]);
31     assert([3, 1].cartesianPower!ubyte(2).fuse == [[3, 3], [3, 1], [1, 3], [1, 1]]);
32     assert([3, 1].combinations!ubyte(2).fuse == [[3, 1]]);
33     assert([3, 1].combinationsRepeat!ubyte(2).fuse == [[3, 3], [3, 1], [1, 1]]);
34 }
35 
36 /**
37 Checks whether we can do basic arithmetic operations, comparisons, modulo and
38 assign values to the type.
39 */
40 private template isArithmetic(R)
41 {
42     enum bool isArithmetic = is(typeof(
43     (inout int = 0)
44     {
45         R r = 1;
46         R test = (r * r / r + r - r) % r;
47         if (r < r && r > r) {}
48     }));
49 }
50 
51 /**
52 Checks whether we can do basic arithmetic operations, comparison and modulo
53 between two types. R needs to support item assignment of S (it includes S).
54 Both R and S need to be arithmetic types themselves.
55 */
56 private template isArithmetic(R, S)
57 {
58     enum bool isArithmetic = is(typeof(
59     (inout int = 0)
60     {
61         if (isArithmetic!R && isArithmetic!S) {}
62         S s = 1;
63         R r = 1;
64         R test = r * s + r * s;
65         R test2 = r / s + r / s;
66         R test3 = r - s + r - s;
67         R test4 = r % s + r % s;
68         if (r < s && s > r) {}
69         if (s < r && r > s) {}
70     }));
71 }
72 
73 /**
74 Computes the $(WEB en.wikipedia.org/wiki/Binomial_coefficient, binomial coefficient)
75 of n and k.
76 It is also known as "n choose k" or more formally as `_n!/_k!(_n-_k)`.
77 If a fixed-length integer type is used and an overflow happens, `0` is returned.
78 
79 Uses the generalized binomial coefficient for negative integers and floating
80 point number
81 
82 Params:
83     n = arbitrary arithmetic type
84     k = arbitrary arithmetic type
85 
86 Returns:
87     Binomial coefficient
88 */
89 R binomial(R = ulong, T)(const T n, const T k)
90     if (isArithmetic!(R, T) &&
91         ((is(typeof(T.min < 0)) && is(typeof(T.init & 1))) || !is(typeof(T.min < 0))) )
92 {
93     R result = 1;
94 
95     enum hasMinProperty = is(typeof(T.min < 0));
96     
97     T n2 = n;
98     T k2 = k;
99 
100     // only add negative support if possible
101     static if ((hasMinProperty && T.min < 0) || !hasMinProperty)
102     {
103         if (n2 < 0)
104         {
105             if (k2 >= 0)
106             {
107                 return (k2 & 1 ? -1 : 1) * binomial!(R, T)(-n2 + k2 - 1, k2);
108             }
109             else if (k2 <= n2)
110             {
111                 return ((n2 - k2) & 1 ? -1 : 1) * binomial!(R, T)(-k2 - 1, n2 - k2);
112             }
113         }
114         if (k2 < 0)
115         {
116             result = 0;
117             return result;
118         }
119     }
120 
121     if (k2 > n2)
122     {
123         result = 0;
124         return result;
125     }
126     if (k2 > n2 - k2)
127     {
128         k2 = n2 - k2;
129     }
130     // make a copy of n (could be a custom type)
131     for (T i = 1, m = n2; i <= k2; i++, m--)
132     {
133         // check whether an overflow can happen
134         // hasMember!(Result, "max") doesn't work with dmd2.068 and ldc 0.17
135         static if (is(typeof(0 > R.max)))
136         {
137             if (result / i > R.max / m) return 0;
138             result = result / i * m + result % i * m / i;
139         }
140         else
141         {
142             result = result * m / i;
143         }
144     }
145     return result;
146 }
147 
148 ///
149 pure version(mir_test) unittest
150 {
151     assert(binomial(5, 2) == 10);
152     assert(binomial(6, 4) == 15);
153     assert(binomial(3, 1) == 3);
154 
155     import std.bigint: BigInt;
156     assert(binomial!BigInt(1000, 10) == BigInt("263409560461970212832400"));
157 }
158 
159 pure nothrow @safe @nogc version(mir_test) unittest
160 {
161     assert(binomial(5, 1) == 5);
162     assert(binomial(5, 0) == 1);
163     assert(binomial(1, 2) == 0);
164     assert(binomial(1, 0) == 1);
165     assert(binomial(1, 1) == 1);
166     assert(binomial(2, 1) == 2);
167     assert(binomial(2, 1) == 2);
168 
169     // negative
170     assert(binomial!long(-5, 3) == -35);
171     assert(binomial!long(5, -3) == 0);
172 }
173 
174 version(mir_test) unittest
175 {
176     import std.bigint;
177 
178     // test larger numbers
179     assert(binomial(100, 10) == 17_310_309_456_440);
180     assert(binomial(999, 5) == 82_09_039_793_949);
181     assert(binomial(300, 10) == 1_398_320_233_241_701_770LU);
182     assert(binomial(300LU, 10LU) == 1_398_320_233_241_701_770LU);
183 
184     // test overflow
185     assert(binomial(500, 10) == 0);
186 
187     // all parameters as custom types
188     BigInt n = 1010, k = 9;
189     assert(binomial!BigInt(n, k) == BigInt("2908077120956865974260"));
190 
191     // negative
192     assert(binomial!BigInt(-5, 3) == -35);
193     assert(binomial!BigInt(5, -3) == 0);
194     assert(binomial!BigInt(-5, -7) == 15);
195 }
196 
197 version(mir_test)
198 @safe pure nothrow @nogc 
199 unittest
200 {
201     const size_t n = 5;
202     const size_t k = 2;
203     assert(binomial(n, k) == 10);
204 }
205 
206 /**
207 Creates a projection of a generalized `Collection` range for the numeric case
208 case starting from `0` onto a custom `range` of any type.
209 
210 Params:
211     collection = range to be projected from
212     range = random access range to be projected to
213 
214 Returns:
215     Range with a projection to range for every element of collection
216 
217 See_Also:
218     $(LREF permutations), $(LREF cartesianPower), $(LREF combinations),
219     $(LREF combinationsRepeat)
220 */
221 IndexedRoR!(Collection, Range) indexedRoR(Collection, Range)(Collection collection, Range range)
222     if (__traits(compiles, Range.init[size_t.init]))
223 {
224     return IndexedRoR!(Collection, Range)(collection, range);
225 }
226 
227 /// ditto
228 struct IndexedRoR(Collection, Range)
229     if (__traits(compiles, Range.init[size_t.init]))
230 {
231     private Collection c;
232     private Range r;
233 
234     ///
235     alias DeepElement = ForeachType!Range;
236 
237     ///
238     this()(Collection collection, Range range)
239     {
240         this.c = collection;
241         this.r = range;
242     }
243 
244     ///
245     auto lightScope()() return scope
246     {
247         return IndexedRoR!(LightScopeOf!Collection, LightScopeOf!Range)(.lightScope(c), .lightScope(r));
248     }
249 
250     ///
251     auto lightScope()() return scope const
252     {
253         return IndexedRoR!(LightConstOf!(LightScopeOf!Collection), LightConstOf!(LightScopeOf!Range))(.lightScope(c), .lightScope(r));
254     }
255 
256     ///
257     auto lightConst()() return scope const
258     {
259         return IndexedRoR!(LightConstOf!Collection, LightConstOf!Range)(.lightConst(c), .lightConst(r));
260     }
261 
262     /// Input range primitives
263     auto front()() @property
264     {
265         import mir.ndslice.slice: isSlice, sliced;
266         import mir.ndslice.topology: indexed;
267         import std.traits: ForeachType;
268         static if (isSlice!(ForeachType!Collection))
269             return r.indexed(c.front);
270         else
271             return r.indexed(c.front.sliced);
272     }
273 
274     /// ditto
275     void popFront() scope
276     {
277         c.popFront;
278     }
279 
280     /// ditto
281     bool empty()() @property scope const
282     {
283         return c.empty;
284     }
285 
286     static if (hasLength!Collection)
287     {
288         /// ditto
289         @property size_t length()() scope const
290         {
291             return c.length;
292         }
293 
294         /// 
295         @property size_t[2] shape()() scope const
296         {
297             return c.shape;
298         }
299     }
300 
301     static if (__traits(hasMember, Collection, "save"))
302     {
303         /// Forward range primitive. Calls `collection.save`.
304         typeof(this) save()() @property
305         {
306             return IndexedRoR!(Collection, Range)(c.save, r);
307         }
308     }
309 }
310 
311 ///
312 @safe pure nothrow version(mir_test) unittest
313 {
314     import mir.ndslice.fuse;
315 
316     auto perms = 2.permutations;
317     assert(perms.save.fuse == [[0, 1], [1, 0]]);
318 
319     auto projection = perms.indexedRoR([1, 2]);
320     assert(projection.fuse == [[1, 2], [2, 1]]);
321 }
322 
323 ///
324 version(mir_test) unittest
325 {
326     import mir.ndslice.fuse;
327     import std.string: representation;
328     // import mir.ndslice.topology: only;
329 
330     auto projectionD = 2.permutations.indexedRoR("ab"d.representation);
331     assert(projectionD.fuse == [['a', 'b'], ['b', 'a']]);
332 
333     // auto projectionC = 2.permutations.indexedRoR(only('a', 'b'));
334     // assert(projectionC.fuse == [['a', 'b'], ['b', 'a']]);
335 }
336 
337 @safe pure nothrow version(mir_test) unittest
338 {
339     import mir.ndslice.fuse;
340     import std.range: dropOne;
341 
342     auto perms = 2.permutations;
343     auto projection = perms.indexedRoR([1, 2]);
344     assert(projection.length == 2);
345 
346     // can save
347     assert(projection.save.dropOne.front == [2, 1]);
348     assert(projection.front == [1, 2]);
349 }
350 
351 @safe nothrow @nogc version(mir_test) unittest
352 {
353     import mir.algorithm.iteration: all;
354     import mir.ndslice.slice: sliced;
355     import mir.ndslice.fuse;
356     static perms = 2.permutations;
357     static immutable projectionArray = [1, 2];
358     auto projection = perms.indexedRoR(projectionArray);
359 
360     static immutable result = [1, 2,
361                                2, 1];
362     assert(result.sliced(2, 2).all!"a == b"(projection));
363 }
364 
365 /**
366 Lazily computes all _permutations of `r` using $(WEB
367 en.wikipedia.org/wiki/Heap%27s_algorithm, Heap's algorithm).
368 
369 While generating a new item is in `O(k)` (amortized `O(1)`),
370 the number of permutations is `|n|!`.
371 
372 Params:
373     n = number of elements (`|r|`)
374     r = random access field. A field may not have iteration primitivies.
375     alloc = custom Allocator
376 
377 Returns:
378     Forward range, which yields the permutations
379 
380 See_Also:
381     $(LREF Permutations)
382 */
383 Permutations!T permutations(T = uint)(size_t n) @safe pure nothrow
384     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
385 {
386     assert(n, "must have at least one item");
387     return Permutations!T(new T[n-1], new T[n]);
388 }
389 
390 /// ditto
391 IndexedRoR!(Permutations!T, Range) permutations(T = uint, Range)(Range r) @safe pure nothrow
392     if (__traits(compiles, Range.init[size_t.init]))
393 {
394     return permutations!T(r.length).indexedRoR(r);
395 }
396 
397 /// ditto
398 Permutations!T makePermutations(T = uint, Allocator)(auto ref Allocator alloc, size_t n)
399     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
400 {
401     assert(n, "must have at least one item");
402     import std.experimental.allocator: makeArray;
403     auto state = alloc.makeArray!T(n - 1);
404     auto indices = alloc.makeArray!T(n);
405     return Permutations!T(state, indices);
406 }
407 
408 /**
409 Lazy Forward range of permutations using $(WEB
410 en.wikipedia.org/wiki/Heap%27s_algorithm, Heap's algorithm).
411 
412 It always generates the permutations from 0 to `n - 1`,
413 use $(LREF indexedRoR) to map it to your range.
414 
415 Generating a new item is in `O(k)` (amortized `O(1)`),
416 the total number of elements is `n^k`.
417 
418 See_Also:
419     $(LREF permutations), $(LREF makePermutations)
420 */
421 struct Permutations(T)
422     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
423 {
424     import mir.ndslice.slice: sliced, Slice;
425 
426     private T[] indices, state;
427     private bool _empty;
428     private size_t _max_states = 1, _pos;
429 
430     ///
431     alias DeepElement = const T;
432 
433     /**
434     state should have the length of `n - 1`,
435     whereas the length of indices should be `n`
436     */
437     this()(T[] state, T[] indices) @safe pure nothrow @nogc
438     {
439         assert(state.length + 1 == indices.length);
440         // iota
441         foreach (i, ref index; indices)
442             index = cast(T)i;
443         state[] = 0;
444 
445         this.indices = indices;
446         this.state = state;
447 
448         _empty = indices.length == 0;
449 
450         // factorial
451         foreach (i; 1..indices.length + 1)
452             _max_states *= i;
453     }
454 
455     /// Input range primitives
456     @property Slice!(const(T)*) front()() @safe pure nothrow @nogc
457     {
458         import mir.ndslice.slice: sliced;
459         return indices.sliced;
460     }
461 
462     /// ditto
463     void popFront()() scope @safe pure nothrow @nogc
464     {
465         import std.algorithm.mutation : swapAt;
466 
467         assert(!empty);
468         _pos++;
469 
470         for (T h = 0;;h++)
471         {
472             if (h + 2 > indices.length)
473             {
474                 _empty = true;
475                 break;
476             }
477 
478             indices.swapAt((h & 1) ? 0 : state[h], h + 1);
479 
480             if (state[h] == h + 1)
481             {
482                 state[h] = 0;
483                 continue;
484             }
485             state[h]++;
486             break;
487         }
488     }
489 
490     /// ditto
491     @property bool empty()() @safe pure nothrow @nogc scope const
492     {
493         return _empty;
494     }
495 
496     /// ditto
497     @property size_t length()() @safe pure nothrow @nogc scope const
498     {
499         return _max_states - _pos;
500     }
501 
502     /// 
503     @property size_t[2] shape()() scope const
504     {
505         return [length, indices.length];
506     }
507 
508     /// Forward range primitive. Allocates using GC.
509     @property Permutations save()() @safe pure nothrow
510     {
511         typeof(this) c = this;
512         c.indices = indices.dup;
513         c.state = state.dup;
514         return c;
515     }
516 }
517 
518 ///
519 pure @safe nothrow version(mir_test) unittest
520 {
521     import mir.ndslice.fuse;
522     import mir.ndslice.topology : iota;
523 
524     auto expectedRes = [[0, 1, 2],
525          [1, 0, 2],
526          [2, 0, 1],
527          [0, 2, 1],
528          [1, 2, 0],
529          [2, 1, 0]];
530 
531     auto r = iota(3);
532     auto rp = permutations(r.length).indexedRoR(r);
533     assert(rp.fuse == expectedRes);
534 
535     // direct style
536     auto rp2 = iota(3).permutations;
537     assert(rp2.fuse == expectedRes);
538 }
539 
540 ///
541 @nogc version(mir_test) unittest
542 {
543     import mir.algorithm.iteration: equal;
544     import mir.ndslice.slice: sliced;
545     import mir.ndslice.topology : iota;
546 
547     import std.experimental.allocator.mallocator;
548 
549     static immutable expected2 = [0, 1, 1, 0];
550     auto r = iota(2);
551     auto rp = makePermutations(Mallocator.instance, r.length);
552     assert(expected2.sliced(2, 2).equal(rp.indexedRoR(r)));
553     dispose(Mallocator.instance, rp);
554 }
555 
556 pure @safe nothrow version(mir_test) unittest
557 {
558     // is copyable?
559     import mir.ndslice.fuse;
560     import mir.ndslice.topology: iota;
561     import std.range: dropOne;
562     auto a = iota(2).permutations;
563     assert(a.front == [0, 1]);
564     assert(a.save.dropOne.front == [1, 0]);
565     assert(a.front == [0, 1]);
566 
567     // length
568     assert(1.permutations.length == 1);
569     assert(2.permutations.length == 2);
570     assert(3.permutations.length == 6);
571     assert(4.permutations.length == 24);
572     assert(10.permutations.length == 3_628_800);
573 }
574 
575 version (assert)
576 version(mir_test) unittest
577 {
578     // check invalid
579     import std.exception: assertThrown;
580     import core.exception: AssertError;
581     import std.experimental.allocator.mallocator: Mallocator;
582 
583     assertThrown!AssertError(0.permutations);
584     assertThrown!AssertError(Mallocator.instance.makePermutations(0));
585 }
586 
587 /**
588 Disposes a Permutations object. It destroys and then deallocates the
589 Permutations object pointed to by a pointer.
590 It is assumed the respective entities had been allocated with the same allocator.
591 
592 Params:
593     alloc = Custom allocator
594     perm = Permutations object
595 
596 See_Also:
597     $(LREF makePermutations)
598 */
599 void dispose(T, Allocator)(auto ref Allocator alloc, auto ref Permutations!T perm)
600 {
601     import std.experimental.allocator: dispose;
602     dispose(alloc, perm.state);
603     dispose(alloc, perm.indices);
604 }
605 
606 /**
607 Lazily computes the Cartesian power of `r` with itself
608 for a number of repetitions `D repeat`.
609 If the input is sorted, the product is in lexicographic order.
610 
611 While generating a new item is in `O(k)` (amortized `O(1)`),
612 the total number of elements is `n^k`.
613 
614 Params:
615     n = number of elements (`|r|`)
616     r = random access field. A field may not have iteration primitivies.
617     repeat = number of repetitions
618     alloc = custom Allocator
619 
620 Returns:
621     Forward range, which yields the product items
622 
623 See_Also:
624     $(LREF CartesianPower)
625 */
626 CartesianPower!T cartesianPower(T = uint)(size_t n, size_t repeat = 1) @safe pure nothrow
627     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
628 {
629     assert(repeat >= 1, "Invalid number of repetitions");
630     return CartesianPower!T(n, new T[repeat]);
631 }
632 
633 /// ditto
634 IndexedRoR!(CartesianPower!T, Range) cartesianPower(T = uint, Range)(Range r, size_t repeat = 1)
635 if (isUnsigned!T && __traits(compiles, Range.init[size_t.init]))
636 {
637     assert(repeat >= 1, "Invalid number of repetitions");
638     return cartesianPower!T(r.length, repeat).indexedRoR(r);
639 }
640 
641 /// ditto
642 CartesianPower!T makeCartesianPower(T = uint, Allocator)(auto ref Allocator alloc, size_t n, size_t repeat)
643     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
644 {
645     assert(repeat >= 1, "Invalid number of repetitions");
646     import std.experimental.allocator: makeArray;
647     return CartesianPower!T(n, alloc.makeArray!T(repeat));
648 }
649 
650 /**
651 Lazy Forward range of Cartesian Power.
652 It always generates Cartesian Power from 0 to `n - 1`,
653 use $(LREF indexedRoR) to map it to your range.
654 
655 Generating a new item is in `O(k)` (amortized `O(1)`),
656 the total number of elements is `n^k`.
657 
658 See_Also:
659     $(LREF cartesianPower), $(LREF makeCartesianPower)
660 */
661 struct CartesianPower(T)
662     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
663 {
664     import mir.ndslice.slice: Slice;
665 
666     private T[] _state;
667     private size_t n;
668     private size_t _max_states, _pos;
669 
670     ///
671     alias DeepElement = const T;
672 
673     /// state should have the length of `repeat`
674     this()(size_t n, T[] state) @safe pure nothrow @nogc
675     {
676         assert(state.length >= 1, "Invalid number of repetitions");
677 
678         import std.math: pow;
679         this.n = n;
680         assert(n <= T.max);
681         this._state = state;
682 
683         _max_states = pow(n, state.length);
684     }
685 
686     /// Input range primitives
687     @property Slice!(const(T)*) front()() @safe pure nothrow @nogc
688     {
689         import mir.ndslice.slice: sliced;
690         return _state.sliced;
691     }
692 
693     /// ditto
694     void popFront()() scope @safe pure nothrow @nogc
695     {
696         assert(!empty);
697         _pos++;
698 
699         /*
700         * Bitwise increment - starting from back
701         * It works like adding 1 in primary school arithmetic.
702         * If a block has reached the number of elements, we reset it to
703         * 0, and continue to increment, e.g. for n = 2:
704         *
705         * [0, 0, 0] -> [0, 0, 1]
706         * [0, 1, 1] -> [1, 0, 0]
707         */
708         foreach_reverse (i, ref el; _state)
709         {
710             ++el;
711             if (el < n)
712                 break;
713 
714             el = 0;
715         }
716     }
717 
718     /// ditto
719     @property size_t length()() @safe pure nothrow @nogc scope const
720     {
721         return _max_states - _pos;
722     }
723 
724     /// ditto
725     @property bool empty()() @safe pure nothrow @nogc scope const
726     {
727         return _pos == _max_states;
728     }
729 
730     /// 
731     @property size_t[2] shape()() scope const
732     {
733         return [length, _state.length];
734     }
735 
736     /// Forward range primitive. Allocates using GC.
737     @property CartesianPower save()() @safe pure nothrow
738     {
739         typeof(this) c = this;
740         c._state = _state.dup;
741         return c;
742     }
743 }
744 
745 ///
746 pure nothrow @safe version(mir_test) unittest
747 {
748     import mir.ndslice.fuse;
749     import mir.ndslice.topology: iota;
750 
751     assert(iota(2).cartesianPower.fuse == [[0], [1]]);
752     assert(iota(2).cartesianPower(2).fuse == [[0, 0], [0, 1], [1, 0], [1, 1]]);
753 
754     auto three_nums_two_bins = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]];
755     assert(iota(3).cartesianPower(2).fuse == three_nums_two_bins);
756 
757     import std.string: representation;
758     assert("AB"d.representation.cartesianPower(2).fuse == ["AA"d, "AB"d, "BA"d, "BB"d]);
759 }
760 
761 ///
762 @nogc version(mir_test) unittest
763 {
764     import mir.ndslice.topology: iota;
765     import mir.algorithm.iteration: equal;
766     import mir.ndslice.slice: sliced;
767 
768     import std.experimental.allocator.mallocator: Mallocator;
769     auto alloc = Mallocator.instance;
770 
771     static immutable expected2r2 = [
772         0, 0,
773         0, 1,
774         1, 0,
775         1, 1];
776     auto r = iota(2);
777     auto rc = alloc.makeCartesianPower(r.length, 2);
778     assert(expected2r2.sliced(4, 2).equal(rc.indexedRoR(r)));
779     alloc.dispose(rc);
780 }
781 
782 pure nothrow @safe version(mir_test) unittest
783 {
784     import mir.ndslice.fuse;
785     import mir.array.allocation: array;
786     import mir.ndslice.topology: iota;
787     import std.range: dropOne;
788     import std.string: representation;
789 
790     assert(iota(0).cartesianPower.length == 0);
791     assert("AB"d.representation.cartesianPower(3).fuse == ["AAA"d, "AAB"d, "ABA"d, "ABB"d, "BAA"d, "BAB"d, "BBA"d, "BBB"d]);
792     auto expected = ["AA"d, "AB"d, "AC"d, "AD"d,
793                      "BA"d, "BB"d, "BC"d, "BD"d,
794                      "CA"d, "CB"d, "CC"d, "CD"d,
795                      "DA"d, "DB"d, "DC"d, "DD"d];
796     assert("ABCD"d.representation.cartesianPower(2).fuse == expected);
797     // verify with array too
798     assert("ABCD"d.representation.cartesianPower(2).fuse == expected);
799 
800     assert(iota(2).cartesianPower.front == [0]);
801 
802     // is copyable?
803     auto a = iota(2).cartesianPower;
804     assert(a.front == [0]);
805     assert(a.save.dropOne.front == [1]);
806     assert(a.front == [0]);
807 
808     // test length shrinking
809     auto d = iota(2).cartesianPower;
810     assert(d.length == 2);
811     d.popFront;
812     assert(d.length == 1);
813 }
814 
815 version(assert)
816 version(mir_test) unittest
817 {
818     // check invalid
819     import std.exception: assertThrown;
820     import core.exception: AssertError;
821     import std.experimental.allocator.mallocator : Mallocator;
822 
823     assertThrown!AssertError(0.cartesianPower(0));
824     assertThrown!AssertError(Mallocator.instance.makeCartesianPower(0, 0));
825 }
826 
827 // length
828 pure nothrow @safe version(mir_test) unittest
829 {
830     assert(1.cartesianPower(1).length == 1);
831     assert(1.cartesianPower(2).length == 1);
832     assert(2.cartesianPower(1).length == 2);
833     assert(2.cartesianPower(2).length == 4);
834     assert(2.cartesianPower(3).length == 8);
835     assert(3.cartesianPower(1).length == 3);
836     assert(3.cartesianPower(2).length == 9);
837     assert(3.cartesianPower(3).length == 27);
838     assert(3.cartesianPower(4).length == 81);
839     assert(4.cartesianPower(10).length == 1_048_576);
840     assert(14.cartesianPower(7).length == 105_413_504);
841 }
842 
843 /**
844 Disposes a CartesianPower object. It destroys and then deallocates the
845 CartesianPower object pointed to by a pointer.
846 It is assumed the respective entities had been allocated with the same allocator.
847 
848 Params:
849     alloc = Custom allocator
850     cartesianPower = CartesianPower object
851 
852 See_Also:
853     $(LREF makeCartesianPower)
854 */
855 void dispose(T = uint, Allocator)(auto ref Allocator alloc, auto ref CartesianPower!T cartesianPower)
856 {
857     import std.experimental.allocator: dispose;
858     dispose(alloc, cartesianPower._state);
859 }
860 
861 /**
862 Lazily computes all k-combinations of `r`.
863 Imagine this as the $(LREF cartesianPower) filtered for only strictly ordered items.
864 
865 While generating a new combination is in `O(k)`,
866 the number of combinations is `binomial(n, k)`.
867 
868 Params:
869     n = number of elements (`|r|`)
870     r = random access field. A field may not have iteration primitivies.
871     k = number of combinations
872     alloc = custom Allocator
873 
874 Returns:
875     Forward range, which yields the k-combinations items
876 
877 See_Also:
878     $(LREF Combinations)
879 */
880 Combinations!T combinations(T = uint)(size_t n, size_t k = 1) @safe pure nothrow
881     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
882 {
883     assert(k >= 1, "Invalid number of combinations");
884     return Combinations!T(n, new T[k]);
885 }
886 
887 /// ditto
888 IndexedRoR!(Combinations!T, Range) combinations(T = uint, Range)(Range r, size_t k = 1)
889 if (isUnsigned!T && __traits(compiles, Range.init[size_t.init]))
890 {
891     assert(k >= 1, "Invalid number of combinations");
892     return combinations!T(r.length, k).indexedRoR(r);
893 }
894 
895 /// ditto
896 Combinations!T makeCombinations(T = uint, Allocator)(auto ref Allocator alloc, size_t n, size_t repeat)
897     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
898 {
899     assert(repeat >= 1, "Invalid number of repetitions");
900     import std.experimental.allocator: makeArray;
901     return Combinations!T(cast(T) n, alloc.makeArray!T(cast(T) repeat));
902 }
903 
904 /**
905 Lazy Forward range of Combinations.
906 It always generates combinations from 0 to `n - 1`,
907 use $(LREF indexedRoR) to map it to your range.
908 
909 Generating a new combination is in `O(k)`,
910 the number of combinations is `binomial(n, k)`.
911 
912 See_Also:
913     $(LREF combinations), $(LREF makeCombinations)
914 */
915 struct Combinations(T)
916     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
917 {
918     import mir.ndslice.slice: Slice;
919 
920     private T[] state;
921     private size_t n;
922     private size_t max_states, pos;
923 
924     ///
925     alias DeepElement = const T;
926 
927     /// state should have the length of `repeat`
928     this()(size_t n, T[] state) @safe pure nothrow @nogc
929     {
930         import mir.ndslice.topology: iota;
931 
932         assert(state.length <= T.max);
933         this.n = n;
934         assert(n <= T.max);
935         this.max_states = cast(size_t) binomial(n, state.length);
936         this.state = state;
937 
938         // set initial state and calculate max possibilities
939         if (n > 0)
940         {
941             // skip first duplicate
942             if (n > 1 && state.length > 1)
943             {
944                 auto iotaResult = iota(state.length);
945                 foreach (i, ref el; state)
946                 {
947                     el = cast(T) iotaResult[i];
948                 }
949             }
950         }
951     }
952 
953     /// Input range primitives
954     @property Slice!(const(T)*) front()() @safe pure nothrow @nogc
955     {
956         import mir.ndslice.slice: sliced;
957         return state.sliced;
958     }
959 
960     /// ditto
961     void popFront()() scope @safe pure nothrow @nogc
962     {
963         assert(!empty);
964         pos++;
965         // we might have bumped into the end state now
966         if (empty) return;
967 
968         // Behaves like: do _getNextState();  while (!_state.isStrictlySorted);
969         size_t i = state.length - 1;
970         /* Go from the back to next settable block
971         * - A must block must be lower than it's previous
972         * - A state i is not settable if it's maximum height is reached
973         *
974         * Think of it as a backwords search on state with
975         * iota(_repeat + d, _repeat + d) as search mask.
976         * (d = _nrElements -_repeat)
977         *
978         * As an example n = 3, r = 2, iota is [1, 2] and hence:
979         * [0, 1] -> i = 2
980         * [0, 2] -> i = 1
981         */
982         while (state[i] == n - state.length + i)
983         {
984             i--;
985         }
986         state[i] = cast(T)(state[i] + 1);
987 
988         /* Starting from our changed block, we need to take the change back
989         * to the end of the state array and update them by their new diff.
990         * [0, 1, 4] -> [0, 2, 3]
991         * [0, 3, 4] -> [1, 2, 3]
992         */
993         foreach (j; i + 1 .. state.length)
994         {
995             state[j] = cast(T)(state[i] + j - i);
996         }
997     }
998 
999     /// ditto
1000     @property size_t length()() @safe pure nothrow @nogc scope const
1001     {
1002         return max_states - pos;
1003     }
1004 
1005     /// ditto
1006     @property bool empty()() @safe pure nothrow @nogc scope const
1007     {
1008         return pos == max_states;
1009     }
1010 
1011     /// 
1012     @property size_t[2] shape()() scope const
1013     {
1014         return [length, state.length];
1015     }
1016 
1017     /// Forward range primitive. Allocates using GC.
1018     @property Combinations save()() @safe pure nothrow
1019     {
1020         typeof(this) c = this;
1021         c.state = state.dup;
1022         return c;
1023     }
1024 }
1025 
1026 ///
1027 pure nothrow @safe version(mir_test) unittest
1028 {
1029     import mir.ndslice.fuse;
1030     import mir.ndslice.topology: iota;
1031     import std.string: representation;
1032     assert(iota(3).combinations(2).fuse == [[0, 1], [0, 2], [1, 2]]);
1033     assert("AB"d.representation.combinations(2).fuse == ["AB"d]);
1034     assert("ABC"d.representation.combinations(2).fuse == ["AB"d, "AC"d, "BC"d]);
1035 }
1036 
1037 ///
1038 @nogc version(mir_test) unittest
1039 {
1040     import mir.algorithm.iteration: equal;
1041     import mir.ndslice.slice: sliced;
1042     import mir.ndslice.topology: iota;
1043 
1044     import std.experimental.allocator.mallocator;
1045     auto alloc = Mallocator.instance;
1046 
1047     static immutable expected3r2 = [
1048         0, 1,
1049         0, 2,
1050         1, 2];
1051     auto r = iota(3);
1052     auto rc = alloc.makeCombinations(r.length, 2);
1053     assert(expected3r2.sliced(3, 2).equal(rc.indexedRoR(r)));
1054     alloc.dispose(rc);
1055 }
1056 
1057 pure nothrow @safe version(mir_test) unittest
1058 {
1059     import mir.ndslice.fuse;
1060     import mir.array.allocation: array;
1061     import mir.ndslice.topology: iota;
1062     import std.range: dropOne;
1063     import std.string: representation;
1064 
1065     assert(iota(0).combinations.length == 0);
1066     assert(iota(2).combinations.fuse == [[0], [1]]);
1067 
1068     auto expected = ["AB"d, "AC"d, "AD"d, "BC"d, "BD"d, "CD"d];
1069     assert("ABCD"d.representation.combinations(2).fuse == expected);
1070     // verify with array too
1071     assert("ABCD"d.representation.combinations(2).fuse == expected);
1072     assert(iota(2).combinations.front == [0]);
1073 
1074     // is copyable?
1075     auto a = iota(2).combinations;
1076     assert(a.front == [0]);
1077     assert(a.save.dropOne.front == [1]);
1078     assert(a.front == [0]);
1079 
1080     // test length shrinking
1081     auto d = iota(2).combinations;
1082     assert(d.length == 2);
1083     d.popFront;
1084     assert(d.length == 1);
1085 
1086     // test larger combinations
1087     auto expected5 = [[0, 1, 2], [0, 1, 3], [0, 1, 4],
1088                       [0, 2, 3], [0, 2, 4], [0, 3, 4],
1089                       [1, 2, 3], [1, 2, 4], [1, 3, 4],
1090                       [2, 3, 4]];
1091     assert(iota(5).combinations(3).fuse == expected5);
1092     assert(iota(4).combinations(3).fuse == [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]);
1093     assert(iota(3).combinations(3).fuse == [[0, 1, 2]]);
1094     assert(iota(2).combinations(3).length == 0);
1095     assert(iota(1).combinations(3).length == 0);
1096 
1097     assert(iota(3).combinations(2).fuse == [[0, 1], [0, 2], [1, 2]]);
1098     assert(iota(2).combinations(2).fuse == [[0, 1]]);
1099     assert(iota(1).combinations(2).length == 0);
1100 
1101     assert(iota(1).combinations(1).fuse == [[0]]);
1102 }
1103 
1104 pure nothrow @safe version(mir_test) unittest
1105 {
1106     // test larger combinations
1107     import mir.ndslice.fuse;
1108     import mir.ndslice.topology: iota;
1109 
1110     auto expected6r4 = [[0, 1, 2, 3], [0, 1, 2, 4], [0, 1, 2, 5],
1111                         [0, 1, 3, 4], [0, 1, 3, 5], [0, 1, 4, 5],
1112                         [0, 2, 3, 4], [0, 2, 3, 5], [0, 2, 4, 5],
1113                         [0, 3, 4, 5], [1, 2, 3, 4], [1, 2, 3, 5],
1114                         [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]];
1115     assert(iota(6).combinations(4).fuse == expected6r4);
1116 
1117     auto expected6r3 = [[0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 1, 5],
1118                         [0, 2, 3], [0, 2, 4], [0, 2, 5], [0, 3, 4],
1119                         [0, 3, 5], [0, 4, 5], [1, 2, 3], [1, 2, 4],
1120                         [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5],
1121                         [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]];
1122     assert(iota(6).combinations(3).fuse == expected6r3);
1123 
1124     auto expected6r2 = [[0, 1], [0, 2], [0, 3], [0, 4], [0, 5],
1125                         [1, 2], [1, 3], [1, 4], [1, 5], [2, 3],
1126                         [2, 4], [2, 5], [3, 4], [3, 5], [4, 5]];
1127     assert(iota(6).combinations(2).fuse == expected6r2);
1128 
1129     auto expected7r5 = [[0, 1, 2, 3, 4], [0, 1, 2, 3, 5], [0, 1, 2, 3, 6],
1130                         [0, 1, 2, 4, 5], [0, 1, 2, 4, 6], [0, 1, 2, 5, 6],
1131                         [0, 1, 3, 4, 5], [0, 1, 3, 4, 6], [0, 1, 3, 5, 6],
1132                         [0, 1, 4, 5, 6], [0, 2, 3, 4, 5], [0, 2, 3, 4, 6],
1133                         [0, 2, 3, 5, 6], [0, 2, 4, 5, 6], [0, 3, 4, 5, 6],
1134                         [1, 2, 3, 4, 5], [1, 2, 3, 4, 6], [1, 2, 3, 5, 6],
1135                         [1, 2, 4, 5, 6], [1, 3, 4, 5, 6], [2, 3, 4, 5, 6]];
1136     assert(iota(7).combinations(5).fuse == expected7r5);
1137 }
1138 
1139 // length
1140 pure nothrow @safe version(mir_test) unittest
1141 {
1142     assert(1.combinations(1).length == 1);
1143     assert(1.combinations(2).length == 0);
1144     assert(2.combinations(1).length == 2);
1145     assert(2.combinations(2).length == 1);
1146     assert(2.combinations(3).length == 0);
1147     assert(3.combinations(1).length == 3);
1148     assert(3.combinations(2).length == 3);
1149     assert(3.combinations(3).length == 1);
1150     assert(3.combinations(4).length == 0);
1151     assert(4.combinations(10).length == 0);
1152     assert(14.combinations(11).length == 364);
1153     assert(20.combinations(7).length == 77_520);
1154     assert(30.combinations(10).length == 30_045_015);
1155     assert(30.combinations(15).length == 155_117_520);
1156 }
1157 
1158 version(assert)
1159 version(mir_test) unittest
1160 {
1161     // check invalid
1162     import std.exception: assertThrown;
1163     import core.exception: AssertError;
1164     import std.experimental.allocator.mallocator: Mallocator;
1165 
1166     assertThrown!AssertError(0.combinations(0));
1167     assertThrown!AssertError(Mallocator.instance.makeCombinations(0, 0));
1168 }
1169 
1170 /**
1171 Disposes a Combinations object. It destroys and then deallocates the
1172 Combinations object pointed to by a pointer.
1173 It is assumed the respective entities had been allocated with the same allocator.
1174 
1175 Params:
1176     alloc = Custom allocator
1177     perm = Combinations object
1178 
1179 See_Also:
1180     $(LREF makeCombinations)
1181 */
1182 void dispose(T, Allocator)(auto ref Allocator alloc, auto ref Combinations!T perm)
1183 {
1184     import std.experimental.allocator: dispose;
1185     dispose(alloc, perm.state);
1186 }
1187 
1188 /**
1189 Lazily computes all k-combinations of `r` with repetitions.
1190 A k-combination with repetitions, or k-multicombination,
1191 or multisubset of size k from a set S is given by a sequence of k
1192 not necessarily distinct elements of S, where order is not taken into account.
1193 Imagine this as the cartesianPower filtered for only ordered items.
1194 
1195 While generating a new combination with repeats is in `O(k)`,
1196 the number of combinations with repeats is `binomial(n + k - 1, k)`.
1197 
1198 Params:
1199     n = number of elements (`|r|`)
1200     r = random access field. A field may not have iteration primitivies.
1201     k = number of combinations
1202     alloc = custom Allocator
1203 
1204 Returns:
1205     Forward range, which yields the k-multicombinations items
1206 
1207 See_Also:
1208     $(LREF CombinationsRepeat)
1209 */
1210 CombinationsRepeat!T combinationsRepeat(T = uint)(size_t n, size_t k = 1) @safe pure nothrow
1211     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
1212 {
1213     assert(k >= 1, "Invalid number of combinations");
1214     return CombinationsRepeat!T(n, new T[k]);
1215 }
1216 
1217 /// ditto
1218 IndexedRoR!(CombinationsRepeat!T, Range) combinationsRepeat(T = uint, Range)(Range r, size_t k = 1)
1219     if (isUnsigned!T && __traits(compiles, Range.init[size_t.init]))
1220 {
1221     assert(k >= 1, "Invalid number of combinations");
1222     return combinationsRepeat!T(r.length, k).indexedRoR(r);
1223 }
1224 
1225 /// ditto
1226 CombinationsRepeat!T makeCombinationsRepeat(T = uint, Allocator)(auto ref Allocator alloc, size_t n, size_t repeat)
1227     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
1228 {
1229     assert(repeat >= 1, "Invalid number of repetitions");
1230     import std.experimental.allocator: makeArray;
1231     return CombinationsRepeat!T(n, alloc.makeArray!T(repeat));
1232 }
1233 
1234 /**
1235 Lazy Forward range of combinations with repeats.
1236 It always generates combinations with repeats from 0 to `n - 1`,
1237 use $(LREF indexedRoR) to map it to your range.
1238 
1239 Generating a new combination with repeats is in `O(k)`,
1240 the number of combinations with repeats is `binomial(n, k)`.
1241 
1242 See_Also:
1243     $(LREF combinationsRepeat), $(LREF makeCombinationsRepeat)
1244 */
1245 struct CombinationsRepeat(T)
1246     if (isUnsigned!T && T.sizeof <= size_t.sizeof)
1247 {
1248     import mir.ndslice.slice: Slice;
1249 
1250     private T[] state;
1251     private size_t n;
1252     private size_t max_states, pos;
1253 
1254     ///
1255     alias DeepElement = const T;
1256 
1257     /// state should have the length of `repeat`
1258     this()(size_t n, T[] state) @safe pure nothrow @nogc
1259     {
1260         this.n = n;
1261         assert(n <= T.max);
1262         this.state = state;
1263         size_t repeatLen = state.length;
1264 
1265         // set initial state and calculate max possibilities
1266         if (n > 0)
1267         {
1268             max_states = cast(size_t) binomial(n + repeatLen - 1, repeatLen);
1269         }
1270     }
1271 
1272     /// Input range primitives
1273     @property Slice!(const(T)*) front()() @safe pure nothrow @nogc
1274     {
1275         import mir.ndslice.slice: sliced;
1276         return state.sliced;
1277     }
1278 
1279     /// ditto
1280     void popFront()() scope @safe pure nothrow @nogc
1281     {
1282         assert(!empty);
1283         pos++;
1284 
1285         immutable repeat = state.length;
1286 
1287         // behaves like: do _getNextState();  while (!_state.isSorted);
1288         size_t i = repeat - 1;
1289         // go to next settable block
1290         // a block is settable if its not in the end state (=nrElements - 1)
1291         while (state[i] == n - 1 && i != 0)
1292         {
1293             i--;
1294         }
1295         state[i] = cast(T)(state[i] + 1);
1296 
1297         // if we aren't at the last block, we need to set all blocks
1298         // to equal the current one
1299         // e.g. [0, 2] -> (upper block: [1, 2]) -> [1, 1]
1300         if (i != repeat - 1)
1301         {
1302             for (size_t j = i + 1; j < repeat; j++)
1303                 state[j] = state[i];
1304         }
1305     }
1306 
1307     /// ditto
1308     @property size_t length()() @safe pure nothrow @nogc scope const
1309     {
1310         return max_states - pos;
1311     }
1312 
1313     /// ditto
1314     @property bool empty()() @safe pure nothrow @nogc scope const
1315     {
1316         return pos == max_states;
1317     }
1318 
1319     /// 
1320     @property size_t[2] shape()() scope const
1321     {
1322         return [length, state.length];
1323     }
1324 
1325     /// Forward range primitive. Allocates using GC.
1326     @property CombinationsRepeat save()() @safe pure nothrow
1327     {
1328         typeof(this) c = this;
1329         c.state = state.dup;
1330         return c;
1331     }
1332 }
1333 
1334 ///
1335 pure nothrow @safe version(mir_test) unittest
1336 {
1337     import mir.ndslice.fuse;
1338     import mir.ndslice.topology: iota;
1339     import std.string: representation;
1340 
1341     assert(iota(2).combinationsRepeat.fuse == [[0], [1]]);
1342     assert(iota(2).combinationsRepeat(2).fuse == [[0, 0], [0, 1], [1, 1]]);
1343     assert(iota(3).combinationsRepeat(2).fuse == [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]]);
1344     assert("AB"d.representation.combinationsRepeat(2).fuse == ["AA"d, "AB"d,  "BB"d]);
1345 }
1346 
1347 ///
1348 @nogc version(mir_test) unittest
1349 {
1350     import mir.algorithm.iteration: equal;
1351     import mir.ndslice.slice: sliced;
1352     import mir.ndslice.topology: iota;
1353 
1354     import std.experimental.allocator.mallocator;
1355     auto alloc = Mallocator.instance;
1356 
1357     static immutable expected3r1 = [
1358         0, 
1359         1, 
1360         2];
1361     auto r = iota(3);
1362     auto rc = alloc.makeCombinationsRepeat(r.length, 1);
1363     assert(expected3r1.sliced(3, 1).equal(rc.indexedRoR(r)));
1364     alloc.dispose(rc);
1365 }
1366 
1367 version(mir_test) unittest
1368 {
1369     import mir.ndslice.fuse;
1370     import mir.array.allocation: array;
1371     import mir.ndslice.topology: iota;
1372     import std.range: dropOne;
1373     import std.string: representation;
1374 
1375     assert(iota(0).combinationsRepeat.length == 0);
1376     assert("AB"d.representation.combinationsRepeat(3).fuse == ["AAA"d, "AAB"d, "ABB"d,"BBB"d]);
1377 
1378     auto expected = ["AA"d, "AB"d, "AC"d, "AD"d, "BB"d, "BC"d, "BD"d, "CC"d, "CD"d, "DD"d];
1379     assert("ABCD"d.representation.combinationsRepeat(2).fuse == expected);
1380     // verify with array too
1381     assert("ABCD"d.representation.combinationsRepeat(2).fuse == expected);
1382 
1383     assert(iota(2).combinationsRepeat.front == [0]);
1384 
1385     // is copyable?
1386     auto a = iota(2).combinationsRepeat;
1387     assert(a.front == [0]);
1388     assert(a.save.dropOne.front == [1]);
1389     assert(a.front == [0]);
1390 
1391     // test length shrinking
1392     auto d = iota(2).combinationsRepeat;
1393     assert(d.length == 2);
1394     d.popFront;
1395     assert(d.length == 1);
1396 }
1397 
1398 // length
1399 pure nothrow @safe version(mir_test) unittest
1400 {
1401     assert(1.combinationsRepeat(1).length == 1);
1402     assert(1.combinationsRepeat(2).length == 1);
1403     assert(2.combinationsRepeat(1).length == 2);
1404     assert(2.combinationsRepeat(2).length == 3);
1405     assert(2.combinationsRepeat(3).length == 4);
1406     assert(3.combinationsRepeat(1).length == 3);
1407     assert(3.combinationsRepeat(2).length == 6);
1408     assert(3.combinationsRepeat(3).length == 10);
1409     assert(3.combinationsRepeat(4).length == 15);
1410     assert(4.combinationsRepeat(10).length == 286);
1411     assert(11.combinationsRepeat(14).length == 1_961_256);
1412     assert(20.combinationsRepeat(7).length == 657_800);
1413     assert(20.combinationsRepeat(10).length == 20_030_010);
1414     assert(30.combinationsRepeat(10).length == 635_745_396);
1415 }
1416 
1417 pure nothrow @safe version(mir_test) unittest
1418 {
1419     // test larger combinations
1420     import mir.ndslice.fuse;
1421     import mir.ndslice.topology: iota;
1422 
1423     auto expected3r1 = [[0], [1], [2]];
1424     assert(iota(3).combinationsRepeat(1).fuse == expected3r1);
1425 
1426     auto expected3r2 = [[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]];
1427     assert(iota(3).combinationsRepeat(2).fuse == expected3r2);
1428 
1429     auto expected3r3 = [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 1],
1430                         [0, 1, 2], [0, 2, 2], [1, 1, 1], [1, 1, 2],
1431                         [1, 2, 2], [2, 2, 2]];
1432     assert(iota(3).combinationsRepeat(3).fuse == expected3r3);
1433 
1434     auto expected3r4 = [[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 0, 2],
1435                         [0, 0, 1, 1], [0, 0, 1, 2], [0, 0, 2, 2],
1436                         [0, 1, 1, 1], [0, 1, 1, 2], [0, 1, 2, 2],
1437                         [0, 2, 2, 2], [1, 1, 1, 1], [1, 1, 1, 2],
1438                         [1, 1, 2, 2], [1, 2, 2, 2], [2, 2, 2, 2]];
1439     assert(iota(3).combinationsRepeat(4).fuse == expected3r4);
1440 
1441     auto expected4r3 = [[0, 0, 0], [0, 0, 1], [0, 0, 2],
1442                         [0, 0, 3], [0, 1, 1], [0, 1, 2],
1443                         [0, 1, 3], [0, 2, 2], [0, 2, 3],
1444                         [0, 3, 3], [1, 1, 1], [1, 1, 2],
1445                         [1, 1, 3], [1, 2, 2], [1, 2, 3],
1446                         [1, 3, 3], [2, 2, 2], [2, 2, 3],
1447                         [2, 3, 3], [3, 3, 3]];
1448     assert(iota(4).combinationsRepeat(3).fuse == expected4r3);
1449 
1450     auto expected4r2 = [[0, 0], [0, 1], [0, 2], [0, 3],
1451                          [1, 1], [1, 2], [1, 3], [2, 2],
1452                          [2, 3], [3, 3]];
1453     assert(iota(4).combinationsRepeat(2).fuse == expected4r2);
1454 
1455     auto expected5r3 = [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 0, 4],
1456                         [0, 1, 1], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 2],
1457                         [0, 2, 3], [0, 2, 4], [0, 3, 3], [0, 3, 4], [0, 4, 4],
1458                         [1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4], [1, 2, 2],
1459                         [1, 2, 3], [1, 2, 4], [1, 3, 3], [1, 3, 4], [1, 4, 4],
1460                         [2, 2, 2], [2, 2, 3], [2, 2, 4], [2, 3, 3], [2, 3, 4],
1461                         [2, 4, 4], [3, 3, 3], [3, 3, 4], [3, 4, 4], [4, 4, 4]];
1462     assert(iota(5).combinationsRepeat(3).fuse == expected5r3);
1463 
1464     auto expected5r2 = [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4],
1465                         [1, 1], [1, 2], [1, 3], [1, 4], [2, 2],
1466                         [2, 3], [2, 4], [3, 3], [3, 4], [4, 4]];
1467     assert(iota(5).combinationsRepeat(2).fuse == expected5r2);
1468 }
1469 
1470 version(assert)
1471 version(mir_test) unittest
1472 {
1473     // check invalid
1474     import std.exception: assertThrown;
1475     import core.exception: AssertError;
1476     import std.experimental.allocator.mallocator: Mallocator;
1477 
1478     assertThrown!AssertError(0.combinationsRepeat(0));
1479     assertThrown!AssertError(Mallocator.instance.makeCombinationsRepeat(0, 0));
1480 }
1481 
1482 /**
1483 Disposes a CombinationsRepeat object. It destroys and then deallocates the
1484 CombinationsRepeat object pointed to by a pointer.
1485 It is assumed the respective entities had been allocated with the same allocator.
1486 
1487 Params:
1488     alloc = Custom allocator
1489     perm = CombinationsRepeat object
1490 
1491 See_Also:
1492     $(LREF makeCombinationsRepeat)
1493 */
1494 void dispose(T, Allocator)(auto ref Allocator alloc, auto ref CombinationsRepeat!T perm)
1495 {
1496     import std.experimental.allocator: dispose;
1497     dispose(alloc, perm.state);
1498 }