1 // Written in the D programming language.
2 /**
3 This is a submodule of $(MREF std, algorithm).
4 It contains generic algorithms that implement set operations.
5 
6 The functions $(LREF multiwayMerge), $(LREF multiwayUnion), $(LREF setDifference),
7 $(LREF setIntersection), $(LREF setSymmetricDifference) expect a range of sorted
8 ranges as input.
9 
10 All algorithms are generalized to accept as input not only sets but also
11 $(LINK2 https://en.wikipedia.org/wiki/Multiset, multisets). Each algorithm
12 documents behaviour in the presence of duplicated inputs.
13 
14 $(SCRIPT inhibitQuickIndex = 1;)
15 $(BOOKTABLE Cheat Sheet,
16 $(TR $(TH Function Name) $(TH Description))
17 $(T2 cartesianProduct,
18         Computes Cartesian product of two ranges.)
19 $(T2 largestPartialIntersection,
20         Copies out the values that occur most frequently in a range of ranges.)
21 $(T2 largestPartialIntersectionWeighted,
22         Copies out the values that occur most frequently (multiplied by
23         per-value weights) in a range of ranges.)
24 $(T2 multiwayMerge,
25         Merges a range of sorted ranges.)
26 $(T2 multiwayUnion,
27         Computes the union of a range of sorted ranges.)
28 $(T2 setDifference,
29         Lazily computes the set difference of two sorted ranges.)
30 $(T2 setIntersection,
31         Lazily computes the intersection of two or more sorted ranges.)
32 $(T2 setSymmetricDifference,
33         Lazily computes the symmetric set difference of two sorted ranges.)
34 )
35 
36 Copyright: Andrei Alexandrescu 2008-.
37 
38 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
39 
40 Authors: $(HTTP erdani.com, Andrei Alexandrescu)
41 
42 Source: $(PHOBOSSRC std/algorithm/setops.d)
43 
44 Macros:
45 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
46  */
47 module std.algorithm.setops;
48 
49 import std.range.primitives;
50 
51 import std.functional : unaryFun, binaryFun;
52 import std.traits;
53 import std.meta : AliasSeq, staticMap, allSatisfy, anySatisfy;
54 
55 import std.algorithm.sorting : Merge;
56 import std.typecons : No;
57 
58 // cartesianProduct
59 /**
60 Lazily computes the Cartesian product of two or more ranges. The product is a
61 range of tuples of elements from each respective range.
62 
63 The conditions for the two-range case are as follows:
64 
65 If both ranges are finite, then one must be (at least) a
66 $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) and the
67 other an $(REF_ALTTEXT input range, isInputRange, std,range,primitives).
68 
69 If one range is infinite and the other finite, then the finite range must
70 be a forward range, and the infinite range can be an input range.
71 
72 If both ranges are infinite, then both must be forward ranges.
73 
74 When there are more than two ranges, the above conditions apply to each
75 adjacent pair of ranges.
76 
77 Params:
78     range1 = The first range
79     range2 = The second range
80     ranges = Two or more non-infinite forward ranges
81     otherRanges = Zero or more non-infinite forward ranges
82 
83 Returns:
84     A forward range of $(REF Tuple, std,typecons) representing elements of the
85     cartesian product of the given ranges.
86 */
87 auto cartesianProduct(R1, R2)(R1 range1, R2 range2)
88 if (!allSatisfy!(isForwardRange, R1, R2) ||
89     anySatisfy!(isInfinite, R1, R2))
90 {
91     import std.algorithm.iteration : map, joiner;
92 
93     static if (isInfinite!R1 && isInfinite!R2)
94     {
95         static if (isForwardRange!R1 && isForwardRange!R2)
96         {
97             import std.range : zip, repeat, take, chain, sequence;
98 
99             // This algorithm traverses the cartesian product by alternately
100             // covering the right and bottom edges of an increasing square area
101             // over the infinite table of combinations. This schedule allows us
102             // to require only forward ranges.
103             return zip(sequence!"n"(cast(size_t) 0), range1.save, range2.save,
104                        repeat(range1), repeat(range2))
105                 .map!(function(a) => chain(
106                     zip(repeat(a[1]), take(a[4].save, a[0])),
107                     zip(take(a[3].save, a[0]+1), repeat(a[2]))
108                 ))()
109                 .joiner();
110         }
111         else static assert(0, "cartesianProduct of infinite ranges requires "~
112                               "forward ranges");
113     }
114     else static if (isInputRange!R1 && isForwardRange!R2 && !isInfinite!R2)
115     {
116         import std.range : zip, repeat;
117         return joiner(map!((ElementType!R1 a) => zip(repeat(a), range2.save))
118                           (range1));
119     }
120     else static if (isInputRange!R2 && isForwardRange!R1 && !isInfinite!R1)
121     {
122         import std.range : zip, repeat;
123         return joiner(map!((ElementType!R2 a) => zip(range1.save, repeat(a)))
124                           (range2));
125     }
126     else static assert(0, "cartesianProduct involving finite ranges must "~
127                           "have at least one finite forward range");
128 }
129 
130 ///
131 @safe unittest
132 {
133     import std.algorithm.searching : canFind;
134     import std.range;
135     import std.typecons : tuple;
136 
137     auto N = sequence!"n"(0);         // the range of natural numbers
138     auto N2 = cartesianProduct(N, N); // the range of all pairs of natural numbers
139 
140     // Various arbitrary number pairs can be found in the range in finite time.
141     assert(canFind(N2, tuple(0, 0)));
142     assert(canFind(N2, tuple(123, 321)));
143     assert(canFind(N2, tuple(11, 35)));
144     assert(canFind(N2, tuple(279, 172)));
145 }
146 
147 ///
148 @safe unittest
149 {
150     import std.algorithm.searching : canFind;
151     import std.typecons : tuple;
152 
153     auto B = [ 1, 2, 3 ];
154     auto C = [ 4, 5, 6 ];
155     auto BC = cartesianProduct(B, C);
156 
157     foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6],
158                  [2, 6], [3, 6]])
159     {
160         assert(canFind(BC, tuple(n[0], n[1])));
161     }
162 }
163 
164 @safe unittest
165 {
166     // Test cartesian product of two infinite ranges
167     import std.algorithm.searching : canFind;
168     import std.range;
169     import std.typecons : tuple;
170 
171     auto Even = sequence!"2*n"(0);
172     auto Odd = sequence!"2*n+1"(0);
173     auto EvenOdd = cartesianProduct(Even, Odd);
174 
175     foreach (pair; [[0, 1], [2, 1], [0, 3], [2, 3], [4, 1], [4, 3], [0, 5],
176                     [2, 5], [4, 5], [6, 1], [6, 3], [6, 5]])
177     {
178         assert(canFind(EvenOdd, tuple(pair[0], pair[1])));
179     }
180 
181     // This should terminate in finite time
182     assert(canFind(EvenOdd, tuple(124, 73)));
183     assert(canFind(EvenOdd, tuple(0, 97)));
184     assert(canFind(EvenOdd, tuple(42, 1)));
185 }
186 
187 @safe unittest
188 {
189     // Test cartesian product of an infinite input range and a finite forward
190     // range.
191     import std.algorithm.searching : canFind;
192     import std.range;
193     import std.typecons : tuple;
194 
195     auto N = sequence!"n"(0);
196     auto M = [100, 200, 300];
197     auto NM = cartesianProduct(N,M);
198 
199     foreach (pair; [[0, 100], [0, 200], [0, 300], [1, 100], [1, 200], [1, 300],
200                     [2, 100], [2, 200], [2, 300], [3, 100], [3, 200],
201                     [3, 300]])
202     {
203         assert(canFind(NM, tuple(pair[0], pair[1])));
204     }
205 
206     // We can't solve the halting problem, so we can only check a finite
207     // initial segment here.
208     assert(!canFind(NM.take(100), tuple(100, 0)));
209     assert(!canFind(NM.take(100), tuple(1, 1)));
210     assert(!canFind(NM.take(100), tuple(100, 200)));
211 
212     auto MN = cartesianProduct(M,N);
213     foreach (pair; [[100, 0], [200, 0], [300, 0], [100, 1], [200, 1], [300, 1],
214                     [100, 2], [200, 2], [300, 2], [100, 3], [200, 3],
215                     [300, 3]])
216     {
217         assert(canFind(MN, tuple(pair[0], pair[1])));
218     }
219 
220     // We can't solve the halting problem, so we can only check a finite
221     // initial segment here.
222     assert(!canFind(MN.take(100), tuple(0, 100)));
223     assert(!canFind(MN.take(100), tuple(0, 1)));
224     assert(!canFind(MN.take(100), tuple(100, 200)));
225 }
226 
227 @safe unittest
228 {
229     import std.algorithm.searching : canFind;
230     import std.typecons : tuple;
231 
232     // Test cartesian product of two finite ranges.
233     auto X = [1, 2, 3];
234     auto Y = [4, 5, 6];
235     auto XY = cartesianProduct(X, Y);
236     auto Expected = [[1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4],
237                      [3, 5], [3, 6]];
238 
239     // Verify Expected ⊆ XY
240     foreach (pair; Expected)
241     {
242         assert(canFind(XY, tuple(pair[0], pair[1])));
243     }
244 
245     // Verify XY ⊆ Expected
246     foreach (pair; XY)
247     {
248         assert(canFind(Expected, [pair[0], pair[1]]));
249     }
250 
251     // And therefore, by set comprehension, XY == Expected
252 }
253 
254 @safe unittest
255 {
256     import std.algorithm.comparison : equal;
257     import std.algorithm.iteration : map;
258     import std.algorithm.searching : canFind;
259     import std.typecons : tuple;
260 
261     import std.range;
262     auto N = sequence!"n"(0);
263 
264     // To force the template to fall to the second case, we wrap N in a struct
265     // that doesn't allow bidirectional access.
266     struct FwdRangeWrapper(R)
267     {
268         R impl;
269 
270         // Input range API
271         @property auto front() { return impl.front; }
272         void popFront() { impl.popFront(); }
273         static if (isInfinite!R)
274             enum empty = false;
275         else
276             @property bool empty() { return impl.empty; }
277 
278         // Forward range API
279         @property auto save() { return typeof(this)(impl.save); }
280     }
281     auto fwdWrap(R)(R range) { return FwdRangeWrapper!R(range); }
282 
283     // General test: two infinite bidirectional ranges
284     auto N2 = cartesianProduct(N, N);
285 
286     assert(canFind(N2, tuple(0, 0)));
287     assert(canFind(N2, tuple(123, 321)));
288     assert(canFind(N2, tuple(11, 35)));
289     assert(canFind(N2, tuple(279, 172)));
290 
291     // Test first case: forward range with bidirectional range
292     auto fwdN = fwdWrap(N);
293     auto N2_a = cartesianProduct(fwdN, N);
294 
295     assert(canFind(N2_a, tuple(0, 0)));
296     assert(canFind(N2_a, tuple(123, 321)));
297     assert(canFind(N2_a, tuple(11, 35)));
298     assert(canFind(N2_a, tuple(279, 172)));
299 
300     // Test second case: bidirectional range with forward range
301     auto N2_b = cartesianProduct(N, fwdN);
302 
303     assert(canFind(N2_b, tuple(0, 0)));
304     assert(canFind(N2_b, tuple(123, 321)));
305     assert(canFind(N2_b, tuple(11, 35)));
306     assert(canFind(N2_b, tuple(279, 172)));
307 
308     // Test third case: finite forward range with (infinite) input range
309     static struct InpRangeWrapper(R)
310     {
311         R impl;
312 
313         // Input range API
314         @property auto front() { return impl.front; }
315         void popFront() { impl.popFront(); }
316         static if (isInfinite!R)
317             enum empty = false;
318         else
319             @property bool empty() { return impl.empty; }
320     }
321     auto inpWrap(R)(R r) { return InpRangeWrapper!R(r); }
322 
323     auto inpN = inpWrap(N);
324     auto B = [ 1, 2, 3 ];
325     auto fwdB = fwdWrap(B);
326     auto BN = cartesianProduct(fwdB, inpN);
327 
328     assert(equal(map!"[a[0],a[1]]"(BN.take(10)), [[1, 0], [2, 0], [3, 0],
329                  [1, 1], [2, 1], [3, 1], [1, 2], [2, 2], [3, 2], [1, 3]]));
330 
331     // Test fourth case: (infinite) input range with finite forward range
332     auto NB = cartesianProduct(inpN, fwdB);
333 
334     assert(equal(map!"[a[0],a[1]]"(NB.take(10)), [[0, 1], [0, 2], [0, 3],
335                  [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1]]));
336 
337     // General finite range case
338     auto C = [ 4, 5, 6 ];
339     auto BC = cartesianProduct(B, C);
340 
341     foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6],
342                  [2, 6], [3, 6]])
343     {
344         assert(canFind(BC, tuple(n[0], n[1])));
345     }
346 }
347 
348 // https://issues.dlang.org/show_bug.cgi?id=13091
349 pure nothrow @safe @nogc unittest
350 {
351     int[1] a = [1];
352     foreach (t; cartesianProduct(a[], a[])) {}
353 }
354 
355 /// ditto
356 auto cartesianProduct(RR...)(RR ranges)
357 if (ranges.length >= 2 &&
358     allSatisfy!(isForwardRange, RR) &&
359     !anySatisfy!(isInfinite, RR))
360 {
361     // This overload uses a much less template-heavy implementation when
362     // all ranges are finite forward ranges, which is the most common use
363     // case, so that we don't run out of resources too quickly.
364     //
365     // For infinite ranges or non-forward ranges, we fall back to the old
366     // implementation which expands an exponential number of templates.
367     import std.typecons : tuple;
368 
369     static struct Result
370     {
371         RR ranges;
372         RR current;
373         bool empty = true;
374 
375         this(RR _ranges)
376         {
377             ranges = _ranges;
378             empty = false;
379             foreach (i, r; ranges)
380             {
381                 current[i] = r.save;
382                 if (current[i].empty)
383                     empty = true;
384             }
385         }
386         @property auto front()
387         {
388             import std.algorithm.internal : algoFormat;
389             import std.range : iota;
390             return mixin(algoFormat("tuple(%(current[%d].front%|,%))",
391                                     iota(0, current.length)));
392         }
393         void popFront() scope
394         {
395             foreach_reverse (i, ref r; current)
396             {
397                 r.popFront();
398                 if (!r.empty) break;
399 
400                 static if (i == 0)
401                     empty = true;
402                 else
403                     r = ranges[i].save; // rollover
404             }
405         }
406         @property Result save() return scope
407         {
408             Result copy = this;
409             foreach (i, r; ranges)
410             {
411                 copy.ranges[i] = ranges[i].save;
412                 copy.current[i] = current[i].save;
413             }
414             return copy;
415         }
416     }
417     static assert(isForwardRange!Result, Result.stringof ~ " must be a forward"
418             ~ " range");
419 
420     return Result(ranges);
421 }
422 
423 // cartesian product of empty ranges should be empty
424 // https://issues.dlang.org/show_bug.cgi?id=10693
425 @safe unittest
426 {
427     int[] a, b, c, d, e;
428     auto cprod = cartesianProduct(a,b,c,d,e);
429     assert(cprod.empty);
430     foreach (_; cprod) {} // should not crash
431 
432     // Test case where only one of the ranges is empty: the result should still
433     // be empty.
434     int[] p=[1], q=[];
435     auto cprod2 = cartesianProduct(p,p,p,q,p);
436     assert(cprod2.empty);
437     foreach (_; cprod2) {} // should not crash
438 }
439 
440 @safe unittest
441 {
442     // .init value of cartesianProduct should be empty
443     auto cprod = cartesianProduct([0,0], [1,1], [2,2]);
444     assert(!cprod.empty);
445     assert(cprod.init.empty);
446 }
447 
448 // https://issues.dlang.org/show_bug.cgi?id=13393
449 @safe unittest
450 {
451     assert(!cartesianProduct([0],[0],[0]).save.empty);
452 }
453 
454 /// ditto
455 auto cartesianProduct(R1, R2, RR...)(R1 range1, R2 range2, RR otherRanges)
456 if (!allSatisfy!(isForwardRange, R1, R2, RR) ||
457     anySatisfy!(isInfinite, R1, R2, RR))
458 {
459     /* We implement the n-ary cartesian product by recursively invoking the
460      * binary cartesian product. To make the resulting range nicer, we denest
461      * one level of tuples so that a ternary cartesian product, for example,
462      * returns 3-element tuples instead of nested 2-element tuples.
463      */
464     import std.algorithm.internal : algoFormat;
465     import std.algorithm.iteration : map;
466     import std.range : iota;
467 
468     enum string denest = algoFormat("tuple(a[0], %(a[1][%d]%|,%))",
469                                 iota(0, otherRanges.length+1));
470     return map!denest(
471         cartesianProduct(range1, cartesianProduct(range2, otherRanges))
472     );
473 }
474 
475 @safe unittest
476 {
477     import std.algorithm.searching : canFind;
478     import std.range;
479     import std.typecons : tuple, Tuple;
480 
481     auto N = sequence!"n"(0);
482     auto N3 = cartesianProduct(N, N, N);
483 
484     // Check that tuples are properly denested
485     assert(is(ElementType!(typeof(N3)) == Tuple!(size_t,size_t,size_t)));
486 
487     assert(canFind(N3, tuple(0, 27, 7)));
488     assert(canFind(N3, tuple(50, 23, 11)));
489     assert(canFind(N3, tuple(9, 3, 0)));
490 }
491 
492 @safe unittest
493 {
494     import std.algorithm.searching : canFind;
495     import std.range;
496     import std.typecons : tuple, Tuple;
497 
498     auto N = sequence!"n"(0);
499     auto N4 = cartesianProduct(N, N, N, N);
500 
501     // Check that tuples are properly denested
502     assert(is(ElementType!(typeof(N4)) == Tuple!(size_t,size_t,size_t,size_t)));
503 
504     assert(canFind(N4, tuple(1, 2, 3, 4)));
505     assert(canFind(N4, tuple(4, 3, 2, 1)));
506     assert(canFind(N4, tuple(10, 3, 1, 2)));
507 }
508 
509 // https://issues.dlang.org/show_bug.cgi?id=9878
510 ///
511 @safe unittest
512 {
513     import std.algorithm.comparison : equal;
514     import std.typecons : tuple;
515 
516     auto A = [ 1, 2, 3 ];
517     auto B = [ 'a', 'b', 'c' ];
518     auto C = [ "x", "y", "z" ];
519     auto ABC = cartesianProduct(A, B, C);
520 
521     assert(ABC.equal([
522         tuple(1, 'a', "x"), tuple(1, 'a', "y"), tuple(1, 'a', "z"),
523         tuple(1, 'b', "x"), tuple(1, 'b', "y"), tuple(1, 'b', "z"),
524         tuple(1, 'c', "x"), tuple(1, 'c', "y"), tuple(1, 'c', "z"),
525         tuple(2, 'a', "x"), tuple(2, 'a', "y"), tuple(2, 'a', "z"),
526         tuple(2, 'b', "x"), tuple(2, 'b', "y"), tuple(2, 'b', "z"),
527         tuple(2, 'c', "x"), tuple(2, 'c', "y"), tuple(2, 'c', "z"),
528         tuple(3, 'a', "x"), tuple(3, 'a', "y"), tuple(3, 'a', "z"),
529         tuple(3, 'b', "x"), tuple(3, 'b', "y"), tuple(3, 'b', "z"),
530         tuple(3, 'c', "x"), tuple(3, 'c', "y"), tuple(3, 'c', "z")
531     ]));
532 }
533 
534 pure @safe nothrow @nogc unittest
535 {
536     import std.range.primitives : isForwardRange;
537     int[2] A = [1,2];
538     auto C = cartesianProduct(A[], A[], A[]);
539     assert(isForwardRange!(typeof(C)));
540 
541     C.popFront();
542     auto front1 = C.front;
543     auto D = C.save;
544     C.popFront();
545     assert(D.front == front1);
546 }
547 
548 // https://issues.dlang.org/show_bug.cgi?id=13935
549 @safe unittest
550 {
551     import std.algorithm.iteration : map;
552     auto seq = [1, 2].map!(x => x);
553     foreach (pair; cartesianProduct(seq, seq)) {}
554 }
555 
556 @system unittest
557 {
558     import std.algorithm.comparison : equal;
559     import std.typecons : tuple;
560 
561     static struct SystemRange
562     {
563         int[] data;
564 
565         int front() @system @property inout
566         {
567             return data[0];
568         }
569 
570         bool empty() @system @property inout
571         {
572             return data.length == 0;
573         }
574 
575         void popFront() @system
576         {
577             data = data[1 .. $];
578         }
579 
580         SystemRange save() @system
581         {
582             return this;
583         }
584     }
585 
586     assert(SystemRange([1, 2]).cartesianProduct(SystemRange([3, 4]))
587         .equal([tuple(1, 3), tuple(1, 4), tuple(2, 3), tuple(2, 4)]));
588 }
589 
590 // largestPartialIntersection
591 /**
592 Given a range of sorted $(REF_ALTTEXT forward ranges, isForwardRange, std,range,primitives)
593 `ror`, copies to `tgt` the elements that are common to most ranges, along with their number
594 of occurrences. All ranges in `ror` are assumed to be sorted by $(D
595 less). Only the most frequent `tgt.length` elements are returned.
596 
597 Params:
598     less = The predicate the ranges are sorted by.
599     ror = A range of forward ranges sorted by `less`.
600     tgt = The target range to copy common elements to.
601     sorted = Whether the elements copied should be in sorted order.
602 
603 The function `largestPartialIntersection` is useful for
604 e.g. searching an $(LINK2 https://en.wikipedia.org/wiki/Inverted_index,
605 inverted index) for the documents most
606 likely to contain some terms of interest. The complexity of the search
607 is $(BIGOH n * log(tgt.length)), where `n` is the sum of lengths of
608 all input ranges. This approach is faster than keeping an associative
609 array of the occurrences and then selecting its top items, and also
610 requires less memory (`largestPartialIntersection` builds its
611 result directly in `tgt` and requires no extra memory).
612 
613 If at least one of the ranges is a multiset, then all occurences
614 of a duplicate element are taken into account. The result is
615 equivalent to merging all ranges and picking the most frequent
616 `tgt.length` elements.
617 
618 Warning: Because `largestPartialIntersection` does not allocate
619 extra memory, it will leave `ror` modified. Namely, $(D
620 largestPartialIntersection) assumes ownership of `ror` and
621 discretionarily swaps and advances elements of it. If you want $(D
622 ror) to preserve its contents after the call, you may want to pass a
623 duplicate to `largestPartialIntersection` (and perhaps cache the
624 duplicate in between calls).
625  */
626 void largestPartialIntersection
627 (alias less = "a < b", RangeOfRanges, Range)
628 (RangeOfRanges ror, Range tgt, SortOutput sorted = No.sortOutput)
629 {
630     struct UnitWeights
631     {
632         static int opIndex(ElementType!(ElementType!RangeOfRanges)) { return 1; }
633     }
634     return largestPartialIntersectionWeighted!less(ror, tgt, UnitWeights(),
635             sorted);
636 }
637 
638 ///
639 @system unittest
640 {
641     import std.typecons : tuple, Tuple;
642 
643     // Figure which number can be found in most arrays of the set of
644     // arrays below.
645     double[][] a =
646     [
647         [ 1, 4, 7, 8 ],
648         [ 1, 7 ],
649         [ 1, 7, 8],
650         [ 4 ],
651         [ 7 ],
652     ];
653     auto b = new Tuple!(double, uint)[1];
654     // it will modify the input range, hence we need to create a duplicate
655     largestPartialIntersection(a.dup, b);
656     // First member is the item, second is the occurrence count
657     assert(b[0] == tuple(7.0, 4u));
658     // 7.0 occurs in 4 out of 5 inputs, more than any other number
659 
660     // If more of the top-frequent numbers are needed, just create a larger
661     // tgt range
662     auto c = new Tuple!(double, uint)[2];
663     largestPartialIntersection(a, c);
664     assert(c[0] == tuple(1.0, 3u));
665     // 1.0 occurs in 3 inputs
666 
667     // multiset
668     double[][] x =
669     [
670         [1, 1, 1, 1, 4, 7, 8],
671         [1, 7],
672         [1, 7, 8],
673         [4, 7],
674         [7]
675     ];
676     auto y = new Tuple!(double, uint)[2];
677     largestPartialIntersection(x.dup, y);
678     // 7.0 occurs 5 times
679     assert(y[0] == tuple(7.0, 5u));
680     // 1.0 occurs 6 times
681     assert(y[1] == tuple(1.0, 6u));
682 }
683 
684 import std.algorithm.sorting : SortOutput; // FIXME
685 
686 // largestPartialIntersectionWeighted
687 /**
688 Similar to `largestPartialIntersection`, but associates a weight
689 with each distinct element in the intersection.
690 
691 If at least one of the ranges is a multiset, then all occurences
692 of a duplicate element are taken into account. The result
693 is equivalent to merging all input ranges and picking the highest
694 `tgt.length`, weight-based ranking elements.
695 
696 Params:
697     less = The predicate the ranges are sorted by.
698     ror = A range of $(REF_ALTTEXT forward ranges, isForwardRange, std,range,primitives)
699     sorted by `less`.
700     tgt = The target range to copy common elements to.
701     weights = An associative array mapping elements to weights.
702     sorted = Whether the elements copied should be in sorted order.
703 
704 */
705 void largestPartialIntersectionWeighted
706 (alias less = "a < b", RangeOfRanges, Range, WeightsAA)
707 (RangeOfRanges ror, Range tgt, WeightsAA weights, SortOutput sorted = No.sortOutput)
708 {
709     import std.algorithm.iteration : group;
710     import std.algorithm.sorting : topNCopy;
711 
712     if (tgt.empty) return;
713     alias InfoType = ElementType!Range;
714     bool heapComp(InfoType a, InfoType b)
715     {
716         return weights[a[0]] * a[1] > weights[b[0]] * b[1];
717     }
718     topNCopy!heapComp(group(multiwayMerge!less(ror)), tgt, sorted);
719 }
720 
721 ///
722 @system unittest
723 {
724     import std.typecons : tuple, Tuple;
725 
726     // Figure which number can be found in most arrays of the set of
727     // arrays below, with specific per-element weights
728     double[][] a =
729     [
730         [ 1, 4, 7, 8 ],
731         [ 1, 7 ],
732         [ 1, 7, 8],
733         [ 4 ],
734         [ 7 ],
735     ];
736     auto b = new Tuple!(double, uint)[1];
737     double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ];
738     largestPartialIntersectionWeighted(a, b, weights);
739     // First member is the item, second is the occurrence count
740     assert(b[0] == tuple(4.0, 2u));
741     // 4.0 occurs 2 times -> 4.6 (2 * 2.3)
742     // 7.0 occurs 3 times -> 4.4 (3 * 1.1)
743 
744    // multiset
745     double[][] x =
746     [
747         [ 1, 1, 1, 4, 7, 8 ],
748         [ 1, 7 ],
749         [ 1, 7, 8],
750         [ 4 ],
751         [ 7 ],
752     ];
753     auto y = new Tuple!(double, uint)[1];
754     largestPartialIntersectionWeighted(x, y, weights);
755     assert(y[0] == tuple(1.0, 5u));
756     // 1.0 occurs 5 times -> 1.2 * 5 = 6
757 }
758 
759 @system unittest
760 {
761     import std.conv : text;
762     import std.typecons : tuple, Tuple, Yes;
763 
764     double[][] a =
765         [
766             [ 1, 4, 7, 8 ],
767             [ 1, 7 ],
768             [ 1, 7, 8],
769             [ 4 ],
770             [ 7 ],
771         ];
772     auto b = new Tuple!(double, uint)[2];
773     largestPartialIntersection(a, b, Yes.sortOutput);
774     assert(b == [ tuple(7.0, 4u), tuple(1.0, 3u) ][], text(b));
775     assert(a[0].empty);
776 }
777 
778 @system unittest
779 {
780     import std.conv : text;
781     import std.typecons : tuple, Tuple, Yes;
782 
783     string[][] a =
784         [
785             [ "1", "4", "7", "8" ],
786             [ "1", "7" ],
787             [ "1", "7", "8"],
788             [ "4" ],
789             [ "7" ],
790         ];
791     auto b = new Tuple!(string, uint)[2];
792     largestPartialIntersection(a, b, Yes.sortOutput);
793     assert(b == [ tuple("7", 4u), tuple("1", 3u) ][], text(b));
794 }
795 
796 @system unittest
797 {
798     import std.typecons : tuple, Tuple;
799 
800     // Figure which number can be found in most arrays of the set of
801     // arrays below, with specific per-element weights
802     double[][] a =
803         [
804             [ 1, 4, 7, 8 ],
805             [ 1, 7 ],
806             [ 1, 7, 8],
807             [ 4 ],
808             [ 7 ],
809             ];
810     auto b = new Tuple!(double, uint)[1];
811     double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ];
812     largestPartialIntersectionWeighted(a, b, weights);
813     // First member is the item, second is the occurrence count
814     assert(b[0] == tuple(4.0, 2u));
815 }
816 
817 @system unittest
818 {
819     import std.container : Array;
820     import std.typecons : Tuple;
821 
822     alias T = Tuple!(uint, uint);
823     const Array!T arrayOne = Array!T( [ T(1,2), T(3,4) ] );
824     const Array!T arrayTwo = Array!T([ T(1,2), T(3,4) ] );
825 
826     assert(arrayOne == arrayTwo);
827 }
828 
829 // MultiwayMerge
830 /**
831 Merges multiple sets. The input sets are passed as a
832 range of ranges and each is assumed to be sorted by $(D
833 less). Computation is done lazily, one union element at a time. The
834 complexity of one `popFront` operation is $(BIGOH
835 log(ror.length)). However, the length of `ror` decreases as ranges
836 in it are exhausted, so the complexity of a full pass through $(D
837 MultiwayMerge) is dependent on the distribution of the lengths of ranges
838 contained within `ror`. If all ranges have the same length `n`
839 (worst case scenario), the complexity of a full pass through $(D
840 MultiwayMerge) is $(BIGOH n * ror.length * log(ror.length)), i.e., $(D
841 log(ror.length)) times worse than just spanning all ranges in
842 turn. The output comes sorted (unstably) by `less`.
843 
844 The length of the resulting range is the sum of all lengths of
845 the ranges passed as input. This means that all elements (duplicates
846 included) are transferred to the resulting range.
847 
848 For backward compatibility, `multiwayMerge` is available under
849 the name `nWayUnion` and `MultiwayMerge` under the name of `NWayUnion` .
850 Future code should use `multiwayMerge` and `MultiwayMerge` as `nWayUnion`
851 and `NWayUnion` will be deprecated.
852 
853 Params:
854     less = Predicate the given ranges are sorted by.
855     ror = A range of ranges sorted by `less` to compute the union for.
856 
857 Returns:
858     A range of the union of the ranges in `ror`.
859 
860 Warning: Because `MultiwayMerge` does not allocate extra memory, it
861 will leave `ror` modified. Namely, `MultiwayMerge` assumes ownership
862 of `ror` and discretionarily swaps and advances elements of it. If
863 you want `ror` to preserve its contents after the call, you may
864 want to pass a duplicate to `MultiwayMerge` (and perhaps cache the
865 duplicate in between calls).
866 
867 See_Also: $(REF merge, std,algorithm,sorting) for an analogous function that
868     takes a static number of ranges of possibly disparate types.
869  */
870 struct MultiwayMerge(alias less, RangeOfRanges)
871 {
872     import std.container : BinaryHeap;
873 
874     private alias ElementType = .ElementType!(.ElementType!RangeOfRanges);
875     private alias comp = binaryFun!less;
876     private RangeOfRanges _ror;
877 
878     ///
879     static bool compFront(.ElementType!RangeOfRanges a,
880             .ElementType!RangeOfRanges b)
881     {
882         // revert comparison order so we get the smallest elements first
883         return comp(b.front, a.front);
884     }
885     private BinaryHeap!(RangeOfRanges, compFront) _heap;
886 
887     ///
888     this(RangeOfRanges ror)
889     {
890         import std.algorithm.mutation : remove, SwapStrategy;
891 
892         // Preemptively get rid of all empty ranges in the input
893         // No need for stability either
894         _ror = remove!("a.empty", SwapStrategy.unstable)(ror);
895         //Build the heap across the range
896         _heap.acquire(_ror);
897     }
898 
899     ///
900     @property bool empty() { return _ror.empty; }
901 
902     ///
903     @property auto ref front()
904     {
905         return _heap.front.front;
906     }
907 
908     ///
909     void popFront()
910     {
911         _heap.removeFront();
912         // let's look at the guy just popped
913         _ror.back.popFront();
914         if (_ror.back.empty)
915         {
916             _ror.popBack();
917             // nothing else to do: the empty range is not in the
918             // heap and not in _ror
919             return;
920         }
921         // Put the popped range back in the heap
922         const bool worked = _heap.conditionalInsert(_ror.back);
923         assert(worked, "Failed to insert item into heap");
924     }
925 }
926 
927 /// Ditto
928 MultiwayMerge!(less, RangeOfRanges) multiwayMerge
929 (alias less = "a < b", RangeOfRanges)
930 (RangeOfRanges ror)
931 {
932     return typeof(return)(ror);
933 }
934 
935 ///
936 @system unittest
937 {
938     import std.algorithm.comparison : equal;
939 
940     double[][] a =
941     [
942         [ 1, 4, 7, 8 ],
943         [ 1, 7 ],
944         [ 1, 7, 8],
945         [ 4 ],
946         [ 7 ],
947     ];
948     auto witness = [
949         1, 1, 1, 4, 4, 7, 7, 7, 7, 8, 8
950     ];
951     assert(equal(multiwayMerge(a), witness));
952 
953     double[][] b =
954     [
955         // range with duplicates
956         [ 1, 1, 4, 7, 8 ],
957         [ 7 ],
958         [ 1, 7, 8],
959         [ 4 ],
960         [ 7 ],
961     ];
962     // duplicates are propagated to the resulting range
963     assert(equal(multiwayMerge(b), witness));
964 }
965 
966 alias nWayUnion = multiwayMerge;
967 alias NWayUnion = MultiwayMerge;
968 
969 /**
970 Computes the union of multiple ranges. The
971 $(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) are passed
972 as a range of ranges and each is assumed to be sorted by $(D
973 less). Computation is done lazily, one union element at a time.
974 `multiwayUnion(ror)` is functionally equivalent to `multiwayMerge(ror).uniq`.
975 
976 "The output of multiwayUnion has no duplicates even when its inputs contain duplicates."
977 
978 Params:
979     less = Predicate the given ranges are sorted by.
980     ror = A range of ranges sorted by `less` to compute the intersection for.
981 
982 Returns:
983     A range of the union of the ranges in `ror`.
984 
985 See also: $(LREF multiwayMerge)
986  */
987 auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror)
988 {
989     import std.algorithm.iteration : uniq;
990     import std.functional : not;
991     return ror.multiwayMerge!(less).uniq!(not!less);
992 }
993 
994 ///
995 @system unittest
996 {
997     import std.algorithm.comparison : equal;
998 
999     // sets
1000     double[][] a =
1001     [
1002         [ 1, 4, 7, 8 ],
1003         [ 1, 7 ],
1004         [ 1, 7, 8],
1005         [ 4 ],
1006         [ 7 ],
1007     ];
1008 
1009     auto witness = [1, 4, 7, 8];
1010     assert(equal(multiwayUnion(a), witness));
1011 
1012     // multisets
1013     double[][] b =
1014     [
1015         [ 1, 1, 1, 4, 7, 8 ],
1016         [ 1, 7 ],
1017         [ 1, 7, 7, 8],
1018         [ 4 ],
1019         [ 7 ],
1020     ];
1021     assert(equal(multiwayUnion(b), witness));
1022 
1023     double[][] c =
1024     [
1025         [9, 8, 8, 8, 7, 6],
1026         [9, 8, 6],
1027         [9, 8, 5]
1028     ];
1029     auto witness2 = [9, 8, 7, 6, 5];
1030     assert(equal(multiwayUnion!"a > b"(c), witness2));
1031 }
1032 
1033 /**
1034 Lazily computes the difference of `r1` and `r2`. The two ranges
1035 are assumed to be sorted by `less`. The element types of the two
1036 ranges must have a common type.
1037 
1038 
1039 In the case of multisets, considering that element `a` appears `x`
1040 times in `r1` and `y` times and `r2`, the number of occurences
1041 of `a` in the resulting range is going to be `x-y` if x > y or 0 otherwise.
1042 
1043 Params:
1044     less = Predicate the given ranges are sorted by.
1045     r1 = The first range.
1046     r2 = The range to subtract from `r1`.
1047 
1048 Returns:
1049     A range of the difference of `r1` and `r2`.
1050 
1051 See_also: $(LREF setSymmetricDifference)
1052  */
1053 struct SetDifference(alias less = "a < b", R1, R2)
1054 if (isInputRange!(R1) && isInputRange!(R2))
1055 {
1056 private:
1057     R1 r1;
1058     R2 r2;
1059     alias comp = binaryFun!(less);
1060 
1061     void adjustPosition()
1062     {
1063         while (!r1.empty)
1064         {
1065             if (r2.empty || comp(r1.front, r2.front)) break;
1066             if (comp(r2.front, r1.front))
1067             {
1068                 r2.popFront();
1069             }
1070             else
1071             {
1072                 // both are equal
1073                 r1.popFront();
1074                 r2.popFront();
1075             }
1076         }
1077     }
1078 
1079 public:
1080     ///
1081     this(R1 r1, R2 r2)
1082     {
1083         this.r1 = r1;
1084         this.r2 = r2;
1085         // position to the first element
1086         adjustPosition();
1087     }
1088 
1089     ///
1090     void popFront()
1091     {
1092         r1.popFront();
1093         adjustPosition();
1094     }
1095 
1096     ///
1097     @property auto ref front()
1098     {
1099         assert(!empty, "Can not get front of empty SetDifference");
1100         return r1.front;
1101     }
1102 
1103     static if (isForwardRange!R1 && isForwardRange!R2)
1104     {
1105         ///
1106         @property typeof(this) save()
1107         {
1108             auto ret = this;
1109             ret.r1 = r1.save;
1110             ret.r2 = r2.save;
1111             return ret;
1112         }
1113     }
1114 
1115     ///
1116     @property bool empty() { return r1.empty; }
1117 }
1118 
1119 /// Ditto
1120 SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2)
1121 (R1 r1, R2 r2)
1122 {
1123     return typeof(return)(r1, r2);
1124 }
1125 
1126 ///
1127 @safe unittest
1128 {
1129     import std.algorithm.comparison : equal;
1130     import std.range.primitives : isForwardRange;
1131 
1132     //sets
1133     int[] a = [ 1, 2, 4, 5, 7, 9 ];
1134     int[] b = [ 0, 1, 2, 4, 7, 8 ];
1135     assert(equal(setDifference(a, b), [5, 9]));
1136     static assert(isForwardRange!(typeof(setDifference(a, b))));
1137 
1138     // multisets
1139     int[] x = [1, 1, 1, 2, 3];
1140     int[] y = [1, 1, 2, 4, 5];
1141     auto r = setDifference(x, y);
1142     assert(equal(r, [1, 3]));
1143     assert(setDifference(r, x).empty);
1144 }
1145 
1146 // https://issues.dlang.org/show_bug.cgi?id=10460
1147 @safe unittest
1148 {
1149     import std.algorithm.comparison : equal;
1150 
1151     int[] a = [1, 2, 3, 4, 5];
1152     int[] b = [2, 4];
1153     foreach (ref e; setDifference(a, b))
1154         e = 0;
1155     assert(equal(a, [0, 2, 0, 4, 0]));
1156 }
1157 
1158 /**
1159 Lazily computes the intersection of two or more
1160 $(REF_ALTTEXT input ranges, isInputRange, std,range,primitives)
1161 `ranges`. The ranges are assumed to be sorted by `less`. The element
1162 types of the ranges must have a common type.
1163 
1164 In the case of multisets, the range with the minimum number of
1165 occurences of a given element, propagates the number of
1166 occurences of this element to the resulting range.
1167 
1168 Params:
1169     less = Predicate the given ranges are sorted by.
1170     ranges = The ranges to compute the intersection for.
1171 
1172 Returns:
1173     A range containing the intersection of the given ranges.
1174  */
1175 struct SetIntersection(alias less = "a < b", Rs...)
1176 if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) &&
1177     !is(CommonType!(staticMap!(ElementType, Rs)) == void))
1178 {
1179 private:
1180     Rs _input;
1181     alias comp = binaryFun!less;
1182     alias ElementType = CommonType!(staticMap!(.ElementType, Rs));
1183 
1184     // Positions to the first elements that are all equal
1185     void adjustPosition()
1186     {
1187         if (empty) return;
1188 
1189         size_t done = Rs.length;
1190         static if (Rs.length > 1) while (true)
1191         {
1192             foreach (i, ref r; _input)
1193             {
1194                 alias next = _input[(i + 1) % Rs.length];
1195 
1196                 if (comp(next.front, r.front))
1197                 {
1198                     do
1199                     {
1200                         next.popFront();
1201                         if (next.empty) return;
1202                     } while (comp(next.front, r.front));
1203                     done = Rs.length;
1204                 }
1205                 if (--done == 0) return;
1206             }
1207         }
1208     }
1209 
1210 public:
1211     ///
1212     this(Rs input)
1213     {
1214         this._input = input;
1215         // position to the first element
1216         adjustPosition();
1217     }
1218 
1219     ///
1220     @property bool empty()
1221     {
1222         foreach (ref r; _input)
1223         {
1224             if (r.empty) return true;
1225         }
1226         return false;
1227     }
1228 
1229     ///
1230     void popFront()
1231     {
1232         assert(!empty, "Can not popFront of empty SetIntersection");
1233         static if (Rs.length > 1) foreach (i, ref r; _input)
1234         {
1235             alias next = _input[(i + 1) % Rs.length];
1236             assert(!comp(r.front, next.front), "Set elements must not"
1237                     ~ " contradict the less predicate");
1238         }
1239 
1240         foreach (ref r; _input)
1241         {
1242             r.popFront();
1243         }
1244         adjustPosition();
1245     }
1246 
1247     ///
1248     @property ElementType front()
1249     {
1250         assert(!empty, "Can not get front of empty SetIntersection");
1251         return _input[0].front;
1252     }
1253 
1254     static if (allSatisfy!(isForwardRange, Rs))
1255     {
1256         ///
1257         @property SetIntersection save()
1258         {
1259             auto ret = this;
1260             foreach (i, ref r; _input)
1261             {
1262                 ret._input[i] = r.save;
1263             }
1264             return ret;
1265         }
1266     }
1267 }
1268 
1269 /// Ditto
1270 SetIntersection!(less, Rs) setIntersection(alias less = "a < b", Rs...)(Rs ranges)
1271 if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) &&
1272     !is(CommonType!(staticMap!(ElementType, Rs)) == void))
1273 {
1274     return typeof(return)(ranges);
1275 }
1276 
1277 ///
1278 @safe unittest
1279 {
1280     import std.algorithm.comparison : equal;
1281 
1282     // sets
1283     int[] a = [ 1, 2, 4, 5, 7, 9 ];
1284     int[] b = [ 0, 1, 2, 4, 7, 8 ];
1285     int[] c = [ 0, 1, 4, 5, 7, 8 ];
1286     assert(equal(setIntersection(a, a), a));
1287     assert(equal(setIntersection(a, b), [1, 2, 4, 7]));
1288     assert(equal(setIntersection(a, b, c), [1, 4, 7]));
1289 
1290     // multisets
1291     int[] d = [ 1, 1, 2, 2, 7, 7 ];
1292     int[] e = [ 1, 1, 1, 7];
1293     assert(equal(setIntersection(a, d), [1, 2, 7]));
1294     assert(equal(setIntersection(d, e), [1, 1, 7]));
1295 }
1296 
1297 @safe unittest
1298 {
1299     import std.algorithm.comparison : equal;
1300     import std.algorithm.iteration : filter;
1301 
1302     int[] a = [ 1, 2, 4, 5, 7, 9 ];
1303     int[] b = [ 0, 1, 2, 4, 7, 8 ];
1304     int[] c = [ 0, 1, 4, 5, 7, 8 ];
1305     int[] d = [ 1, 3, 4 ];
1306     int[] e = [ 4, 5 ];
1307 
1308     assert(equal(setIntersection(a, a), a));
1309     assert(equal(setIntersection(a, a, a), a));
1310     assert(equal(setIntersection(a, b), [1, 2, 4, 7]));
1311     assert(equal(setIntersection(a, b, c), [1, 4, 7]));
1312     assert(equal(setIntersection(a, b, c, d), [1, 4]));
1313     assert(equal(setIntersection(a, b, c, d, e), [4]));
1314 
1315     auto inpA = a.filter!(_ => true), inpB = b.filter!(_ => true);
1316     auto inpC = c.filter!(_ => true), inpD = d.filter!(_ => true);
1317     assert(equal(setIntersection(inpA, inpB, inpC, inpD), [1, 4]));
1318 
1319     assert(equal(setIntersection(a, b, b, a), [1, 2, 4, 7]));
1320     assert(equal(setIntersection(a, c, b), [1, 4, 7]));
1321     assert(equal(setIntersection(b, a, c), [1, 4, 7]));
1322     assert(equal(setIntersection(b, c, a), [1, 4, 7]));
1323     assert(equal(setIntersection(c, a, b), [1, 4, 7]));
1324     assert(equal(setIntersection(c, b, a), [1, 4, 7]));
1325 }
1326 
1327 /**
1328 Lazily computes the symmetric difference of `r1` and `r2`,
1329 i.e. the elements that are present in exactly one of `r1` and $(D
1330 r2). The two ranges are assumed to be sorted by `less`, and the
1331 output is also sorted by `less`. The element types of the two
1332 ranges must have a common type.
1333 
1334 If both ranges are sets (without duplicated elements), the resulting
1335 range is going to be a set. If at least one of the ranges is a multiset,
1336 the number of occurences of an element `x` in the resulting range is `abs(a-b)`
1337 where `a` is the number of occurences of `x` in `r1`, `b` is the number of
1338 occurences of `x` in `r2`, and `abs` is the absolute value.
1339 
1340 If both arguments are ranges of L-values of the same type then
1341 `SetSymmetricDifference` will also be a range of L-values of
1342 that type.
1343 
1344 Params:
1345     less = Predicate the given ranges are sorted by.
1346     r1 = The first range.
1347     r2 = The second range.
1348 
1349 Returns:
1350     A range of the symmetric difference between `r1` and `r2`.
1351 
1352 See_also: $(LREF setDifference)
1353  */
1354 struct SetSymmetricDifference(alias less = "a < b", R1, R2)
1355 if (isInputRange!(R1) && isInputRange!(R2))
1356 {
1357 private:
1358     R1 r1;
1359     R2 r2;
1360     //bool usingR2;
1361     alias comp = binaryFun!(less);
1362 
1363     void adjustPosition()
1364     {
1365         while (!r1.empty && !r2.empty)
1366         {
1367             if (comp(r1.front, r2.front) || comp(r2.front, r1.front))
1368             {
1369                 break;
1370             }
1371             // equal, pop both
1372             r1.popFront();
1373             r2.popFront();
1374         }
1375     }
1376 
1377 public:
1378     ///
1379     this(R1 r1, R2 r2)
1380     {
1381         this.r1 = r1;
1382         this.r2 = r2;
1383         // position to the first element
1384         adjustPosition();
1385     }
1386 
1387     ///
1388     void popFront()
1389     {
1390         assert(!empty, "Can not popFront of empty SetSymmetricDifference");
1391         if (r1.empty) r2.popFront();
1392         else if (r2.empty) r1.popFront();
1393         else
1394         {
1395             // neither is empty
1396             if (comp(r1.front, r2.front))
1397             {
1398                 r1.popFront();
1399             }
1400             else
1401             {
1402                 assert(comp(r2.front, r1.front), "Elements of R1 and R2"
1403                         ~ " must be different");
1404                 r2.popFront();
1405             }
1406         }
1407         adjustPosition();
1408     }
1409 
1410     ///
1411     @property auto ref front()
1412     {
1413         assert(!empty, "Can not get the front of an empty"
1414                 ~ " SetSymmetricDifference");
1415         immutable chooseR1 = r2.empty || !r1.empty && comp(r1.front, r2.front);
1416         assert(chooseR1 || r1.empty || comp(r2.front, r1.front), "Failed to"
1417                 ~ " get appropriate front");
1418         return chooseR1 ? r1.front : r2.front;
1419     }
1420 
1421     static if (isForwardRange!R1 && isForwardRange!R2)
1422     {
1423         ///
1424         @property typeof(this) save()
1425         {
1426             auto ret = this;
1427             ret.r1 = r1.save;
1428             ret.r2 = r2.save;
1429             return ret;
1430         }
1431     }
1432 
1433     ///
1434     ref auto opSlice() { return this; }
1435 
1436     ///
1437     @property bool empty() { return r1.empty && r2.empty; }
1438 }
1439 
1440 /// Ditto
1441 SetSymmetricDifference!(less, R1, R2)
1442 setSymmetricDifference(alias less = "a < b", R1, R2)
1443 (R1 r1, R2 r2)
1444 {
1445     return typeof(return)(r1, r2);
1446 }
1447 
1448 ///
1449 @safe unittest
1450 {
1451     import std.algorithm.comparison : equal;
1452     import std.range.primitives : isForwardRange;
1453 
1454     // sets
1455     int[] a = [ 1, 2, 4, 5, 7, 9 ];
1456     int[] b = [ 0, 1, 2, 4, 7, 8 ];
1457     assert(equal(setSymmetricDifference(a, b), [0, 5, 8, 9][]));
1458     static assert(isForwardRange!(typeof(setSymmetricDifference(a, b))));
1459 
1460     //mutisets
1461     int[] c = [1, 1, 1, 1, 2, 2, 2, 4, 5, 6];
1462     int[] d = [1, 1, 2, 2, 2, 2, 4, 7, 9];
1463     assert(equal(setSymmetricDifference(c, d), setSymmetricDifference(d, c)));
1464     assert(equal(setSymmetricDifference(c, d), [1, 1, 2, 5, 6, 7, 9]));
1465 }
1466 
1467 // https://issues.dlang.org/show_bug.cgi?id=10460
1468 @safe unittest
1469 {
1470     import std.algorithm.comparison : equal;
1471 
1472     int[] a = [1, 2];
1473     double[] b = [2.0, 3.0];
1474     int[] c = [2, 3];
1475 
1476     alias R1 = typeof(setSymmetricDifference(a, b));
1477     static assert(is(ElementType!R1 == double));
1478     static assert(!hasLvalueElements!R1);
1479 
1480     alias R2 = typeof(setSymmetricDifference(a, c));
1481     static assert(is(ElementType!R2 == int));
1482     static assert(hasLvalueElements!R2);
1483 
1484     assert(equal(setSymmetricDifference(a, b), [1.0, 3.0]));
1485     assert(equal(setSymmetricDifference(a, c), [1, 3]));
1486 }
1487 
1488 /++
1489 TODO: once SetUnion got deprecated we can provide the usual definition
1490 (= merge + filter after uniqs)
1491 See: https://github.com/dlang/phobos/pull/4249
1492 /**
1493 Lazily computes the union of two or more ranges `rs`. The ranges
1494 are assumed to be sorted by `less`. Elements in the output are
1495 unique. The element types of all ranges must have a common type.
1496 
1497 Params:
1498     less = Predicate the given ranges are sorted by.
1499     rs = The ranges to compute the union for.
1500 
1501 Returns:
1502     A range containing the unique union of the given ranges.
1503 
1504 See_Also:
1505    $(REF merge, std,algorithm,sorting)
1506  */
1507 auto setUnion(alias less = "a < b", Rs...)
1508 (Rs rs)
1509 {
1510     import std.algorithm.iteration : uniq;
1511     import std.algorithm.sorting : merge;
1512     return merge!(less, Rs)(rs).uniq;
1513 }
1514 
1515 ///
1516 @safe pure nothrow unittest
1517     ///
1518 {
1519     import std.algorithm.comparison : equal;
1520 
1521     int[] a = [1, 3, 5];
1522     int[] b = [2, 3, 4];
1523     assert(a.setUnion(b).equal([1, 2, 3, 4, 5]));
1524 }
1525 
1526 @safe pure nothrow unittest
1527 {
1528     import std.algorithm.comparison : equal;
1529 
1530     int[] a = [ 1, 2, 4, 5, 7, 9 ];
1531     int[] b = [ 0, 1, 2, 4, 7, 8 ];
1532     double[] c = [ 10.5 ];
1533 
1534     assert(equal(setUnion(a, b), [0, 1, 2, 4, 5, 7, 8, 9][]));
1535     assert(equal(setUnion(a, c, b),
1536                     [0, 1, 2, 4, 5, 7, 8, 9, 10.5][]));
1537 }
1538 
1539 @safe unittest
1540 {
1541     // save
1542     import std.range : dropOne;
1543     int[] a = [0, 1, 2];
1544     int[] b = [0, 3];
1545     auto arr = a.setUnion(b);
1546     assert(arr.front == 0);
1547     assert(arr.save.dropOne.front == 1);
1548     assert(arr.front == 0);
1549 }
1550 
1551 @nogc @safe pure nothrow unittest
1552 {
1553     import std.algorithm.comparison : equal;
1554 
1555     static immutable a = [1, 3, 5];
1556     static immutable b = [2, 4];
1557     static immutable r = [1, 2, 3, 4, 5];
1558     assert(a.setUnion(b).equal(r));
1559 }
1560 
1561 @safe pure nothrow unittest
1562 {
1563     import std.algorithm.comparison : equal;
1564     import std.internal.test.dummyrange;
1565     import std.range : iota;
1566 
1567     auto dummyResult1 = [1, 1.5, 2, 3, 4, 5, 5.5, 6, 7, 8, 9, 10];
1568     auto dummyResult2 = iota(1, 11);
1569     foreach (DummyType; AllDummyRanges)
1570     {
1571         DummyType d;
1572         assert(d.setUnion([1, 1.5, 5.5]).equal(dummyResult1));
1573         assert(d.setUnion(d).equal(dummyResult2));
1574     }
1575 }
1576 ++/