| // Written in the D programming language. | |
| /** | |
| This is a submodule of $(MREF std, algorithm). | |
| It contains generic algorithms that implement set operations. | |
| The functions $(LREF multiwayMerge), $(LREF multiwayUnion), $(LREF setDifference), | |
| $(LREF setIntersection), $(LREF setSymmetricDifference) expect a range of sorted | |
| ranges as input. | |
| All algorithms are generalized to accept as input not only sets but also | |
| $(LINK2 https://en.wikipedia.org/wiki/Multiset, multisets). Each algorithm | |
| documents behaviour in the presence of duplicated inputs. | |
| $(SCRIPT inhibitQuickIndex = 1;) | |
| $(BOOKTABLE Cheat Sheet, | |
| $(TR $(TH Function Name) $(TH Description)) | |
| $(T2 cartesianProduct, | |
| Computes Cartesian product of two ranges.) | |
| $(T2 largestPartialIntersection, | |
| Copies out the values that occur most frequently in a range of ranges.) | |
| $(T2 largestPartialIntersectionWeighted, | |
| Copies out the values that occur most frequently (multiplied by | |
| per-value weights) in a range of ranges.) | |
| $(T2 multiwayMerge, | |
| Merges a range of sorted ranges.) | |
| $(T2 multiwayUnion, | |
| Computes the union of a range of sorted ranges.) | |
| $(T2 setDifference, | |
| Lazily computes the set difference of two sorted ranges.) | |
| $(T2 setIntersection, | |
| Lazily computes the intersection of two or more sorted ranges.) | |
| $(T2 setSymmetricDifference, | |
| Lazily computes the symmetric set difference of two sorted ranges.) | |
| ) | |
| Copyright: Andrei Alexandrescu 2008-. | |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
| Authors: $(HTTP erdani.com, Andrei Alexandrescu) | |
| Source: $(PHOBOSSRC std/algorithm/setops.d) | |
| Macros: | |
| T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) | |
| */ | |
| module std.algorithm.setops; | |
| import std.range.primitives; | |
| import std.functional : unaryFun, binaryFun; | |
| import std.traits; | |
| import std.meta : AliasSeq, staticMap, allSatisfy, anySatisfy; | |
| import std.algorithm.sorting : Merge; | |
| import std.typecons : No; | |
| // cartesianProduct | |
| /** | |
| Lazily computes the Cartesian product of two or more ranges. The product is a | |
| range of tuples of elements from each respective range. | |
| The conditions for the two-range case are as follows: | |
| If both ranges are finite, then one must be (at least) a | |
| $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) and the | |
| other an $(REF_ALTTEXT input range, isInputRange, std,range,primitives). | |
| If one range is infinite and the other finite, then the finite range must | |
| be a forward range, and the infinite range can be an input range. | |
| If both ranges are infinite, then both must be forward ranges. | |
| When there are more than two ranges, the above conditions apply to each | |
| adjacent pair of ranges. | |
| Params: | |
| range1 = The first range | |
| range2 = The second range | |
| ranges = Two or more non-infinite forward ranges | |
| otherRanges = Zero or more non-infinite forward ranges | |
| Returns: | |
| A forward range of $(REF Tuple, std,typecons) representing elements of the | |
| cartesian product of the given ranges. | |
| */ | |
| auto cartesianProduct(R1, R2)(R1 range1, R2 range2) | |
| if (!allSatisfy!(isForwardRange, R1, R2) || | |
| anySatisfy!(isInfinite, R1, R2)) | |
| { | |
| import std.algorithm.iteration : map, joiner; | |
| static if (isInfinite!R1 && isInfinite!R2) | |
| { | |
| static if (isForwardRange!R1 && isForwardRange!R2) | |
| { | |
| import std.range : zip, repeat, take, chain, sequence; | |
| // This algorithm traverses the cartesian product by alternately | |
| // covering the right and bottom edges of an increasing square area | |
| // over the infinite table of combinations. This schedule allows us | |
| // to require only forward ranges. | |
| return zip(sequence!"n"(cast(size_t) 0), range1.save, range2.save, | |
| repeat(range1), repeat(range2)) | |
| .map!(function(a) => chain( | |
| zip(repeat(a[1]), take(a[4].save, a[0])), | |
| zip(take(a[3].save, a[0]+1), repeat(a[2])) | |
| ))() | |
| .joiner(); | |
| } | |
| else static assert(0, "cartesianProduct of infinite ranges requires "~ | |
| "forward ranges"); | |
| } | |
| else static if (isInputRange!R1 && isForwardRange!R2 && !isInfinite!R2) | |
| { | |
| import std.range : zip, repeat; | |
| return joiner(map!((ElementType!R1 a) => zip(repeat(a), range2.save)) | |
| (range1)); | |
| } | |
| else static if (isInputRange!R2 && isForwardRange!R1 && !isInfinite!R1) | |
| { | |
| import std.range : zip, repeat; | |
| return joiner(map!((ElementType!R2 a) => zip(range1.save, repeat(a))) | |
| (range2)); | |
| } | |
| else static assert(0, "cartesianProduct involving finite ranges must "~ | |
| "have at least one finite forward range"); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.algorithm.searching : canFind; | |
| import std.range; | |
| import std.typecons : tuple; | |
| auto N = sequence!"n"(0); // the range of natural numbers | |
| auto N2 = cartesianProduct(N, N); // the range of all pairs of natural numbers | |
| // Various arbitrary number pairs can be found in the range in finite time. | |
| assert(canFind(N2, tuple(0, 0))); | |
| assert(canFind(N2, tuple(123, 321))); | |
| assert(canFind(N2, tuple(11, 35))); | |
| assert(canFind(N2, tuple(279, 172))); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.algorithm.searching : canFind; | |
| import std.typecons : tuple; | |
| auto B = [ 1, 2, 3 ]; | |
| auto C = [ 4, 5, 6 ]; | |
| auto BC = cartesianProduct(B, C); | |
| foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6], | |
| [2, 6], [3, 6]]) | |
| { | |
| assert(canFind(BC, tuple(n[0], n[1]))); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| // Test cartesian product of two infinite ranges | |
| import std.algorithm.searching : canFind; | |
| import std.range; | |
| import std.typecons : tuple; | |
| auto Even = sequence!"2*n"(0); | |
| auto Odd = sequence!"2*n+1"(0); | |
| auto EvenOdd = cartesianProduct(Even, Odd); | |
| foreach (pair; [[0, 1], [2, 1], [0, 3], [2, 3], [4, 1], [4, 3], [0, 5], | |
| [2, 5], [4, 5], [6, 1], [6, 3], [6, 5]]) | |
| { | |
| assert(canFind(EvenOdd, tuple(pair[0], pair[1]))); | |
| } | |
| // This should terminate in finite time | |
| assert(canFind(EvenOdd, tuple(124, 73))); | |
| assert(canFind(EvenOdd, tuple(0, 97))); | |
| assert(canFind(EvenOdd, tuple(42, 1))); | |
| } | |
| @safe unittest | |
| { | |
| // Test cartesian product of an infinite input range and a finite forward | |
| // range. | |
| import std.algorithm.searching : canFind; | |
| import std.range; | |
| import std.typecons : tuple; | |
| auto N = sequence!"n"(0); | |
| auto M = [100, 200, 300]; | |
| auto NM = cartesianProduct(N,M); | |
| foreach (pair; [[0, 100], [0, 200], [0, 300], [1, 100], [1, 200], [1, 300], | |
| [2, 100], [2, 200], [2, 300], [3, 100], [3, 200], | |
| [3, 300]]) | |
| { | |
| assert(canFind(NM, tuple(pair[0], pair[1]))); | |
| } | |
| // We can't solve the halting problem, so we can only check a finite | |
| // initial segment here. | |
| assert(!canFind(NM.take(100), tuple(100, 0))); | |
| assert(!canFind(NM.take(100), tuple(1, 1))); | |
| assert(!canFind(NM.take(100), tuple(100, 200))); | |
| auto MN = cartesianProduct(M,N); | |
| foreach (pair; [[100, 0], [200, 0], [300, 0], [100, 1], [200, 1], [300, 1], | |
| [100, 2], [200, 2], [300, 2], [100, 3], [200, 3], | |
| [300, 3]]) | |
| { | |
| assert(canFind(MN, tuple(pair[0], pair[1]))); | |
| } | |
| // We can't solve the halting problem, so we can only check a finite | |
| // initial segment here. | |
| assert(!canFind(MN.take(100), tuple(0, 100))); | |
| assert(!canFind(MN.take(100), tuple(0, 1))); | |
| assert(!canFind(MN.take(100), tuple(100, 200))); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.searching : canFind; | |
| import std.typecons : tuple; | |
| // Test cartesian product of two finite ranges. | |
| auto X = [1, 2, 3]; | |
| auto Y = [4, 5, 6]; | |
| auto XY = cartesianProduct(X, Y); | |
| auto Expected = [[1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4], | |
| [3, 5], [3, 6]]; | |
| // Verify Expected ⊆ XY | |
| foreach (pair; Expected) | |
| { | |
| assert(canFind(XY, tuple(pair[0], pair[1]))); | |
| } | |
| // Verify XY ⊆ Expected | |
| foreach (pair; XY) | |
| { | |
| assert(canFind(Expected, [pair[0], pair[1]])); | |
| } | |
| // And therefore, by set comprehension, XY == Expected | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.algorithm.iteration : map; | |
| import std.algorithm.searching : canFind; | |
| import std.typecons : tuple; | |
| import std.range; | |
| auto N = sequence!"n"(0); | |
| // To force the template to fall to the second case, we wrap N in a struct | |
| // that doesn't allow bidirectional access. | |
| struct FwdRangeWrapper(R) | |
| { | |
| R impl; | |
| // Input range API | |
| @property auto front() { return impl.front; } | |
| void popFront() { impl.popFront(); } | |
| static if (isInfinite!R) | |
| enum empty = false; | |
| else | |
| @property bool empty() { return impl.empty; } | |
| // Forward range API | |
| @property auto save() { return typeof(this)(impl.save); } | |
| } | |
| auto fwdWrap(R)(R range) { return FwdRangeWrapper!R(range); } | |
| // General test: two infinite bidirectional ranges | |
| auto N2 = cartesianProduct(N, N); | |
| assert(canFind(N2, tuple(0, 0))); | |
| assert(canFind(N2, tuple(123, 321))); | |
| assert(canFind(N2, tuple(11, 35))); | |
| assert(canFind(N2, tuple(279, 172))); | |
| // Test first case: forward range with bidirectional range | |
| auto fwdN = fwdWrap(N); | |
| auto N2_a = cartesianProduct(fwdN, N); | |
| assert(canFind(N2_a, tuple(0, 0))); | |
| assert(canFind(N2_a, tuple(123, 321))); | |
| assert(canFind(N2_a, tuple(11, 35))); | |
| assert(canFind(N2_a, tuple(279, 172))); | |
| // Test second case: bidirectional range with forward range | |
| auto N2_b = cartesianProduct(N, fwdN); | |
| assert(canFind(N2_b, tuple(0, 0))); | |
| assert(canFind(N2_b, tuple(123, 321))); | |
| assert(canFind(N2_b, tuple(11, 35))); | |
| assert(canFind(N2_b, tuple(279, 172))); | |
| // Test third case: finite forward range with (infinite) input range | |
| static struct InpRangeWrapper(R) | |
| { | |
| R impl; | |
| // Input range API | |
| @property auto front() { return impl.front; } | |
| void popFront() { impl.popFront(); } | |
| static if (isInfinite!R) | |
| enum empty = false; | |
| else | |
| @property bool empty() { return impl.empty; } | |
| } | |
| auto inpWrap(R)(R r) { return InpRangeWrapper!R(r); } | |
| auto inpN = inpWrap(N); | |
| auto B = [ 1, 2, 3 ]; | |
| auto fwdB = fwdWrap(B); | |
| auto BN = cartesianProduct(fwdB, inpN); | |
| assert(equal(map!"[a[0],a[1]]"(BN.take(10)), [[1, 0], [2, 0], [3, 0], | |
| [1, 1], [2, 1], [3, 1], [1, 2], [2, 2], [3, 2], [1, 3]])); | |
| // Test fourth case: (infinite) input range with finite forward range | |
| auto NB = cartesianProduct(inpN, fwdB); | |
| assert(equal(map!"[a[0],a[1]]"(NB.take(10)), [[0, 1], [0, 2], [0, 3], | |
| [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1]])); | |
| // General finite range case | |
| auto C = [ 4, 5, 6 ]; | |
| auto BC = cartesianProduct(B, C); | |
| foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6], | |
| [2, 6], [3, 6]]) | |
| { | |
| assert(canFind(BC, tuple(n[0], n[1]))); | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=13091 | |
| pure nothrow @safe @nogc unittest | |
| { | |
| int[1] a = [1]; | |
| foreach (t; cartesianProduct(a[], a[])) {} | |
| } | |
| /// ditto | |
| auto cartesianProduct(RR...)(RR ranges) | |
| if (ranges.length >= 2 && | |
| allSatisfy!(isForwardRange, RR) && | |
| !anySatisfy!(isInfinite, RR)) | |
| { | |
| // This overload uses a much less template-heavy implementation when | |
| // all ranges are finite forward ranges, which is the most common use | |
| // case, so that we don't run out of resources too quickly. | |
| // | |
| // For infinite ranges or non-forward ranges, we fall back to the old | |
| // implementation which expands an exponential number of templates. | |
| import std.typecons : tuple; | |
| import std.meta : allSatisfy; | |
| import std.range.primitives : hasLength; | |
| static struct Result | |
| { | |
| RR ranges; | |
| RR current; | |
| bool empty = true; | |
| this(RR _ranges) | |
| { | |
| ranges = _ranges; | |
| empty = false; | |
| foreach (i, r; ranges) | |
| { | |
| current[i] = r.save; | |
| if (current[i].empty) | |
| empty = true; | |
| } | |
| } | |
| @property auto front() | |
| { | |
| import std.algorithm.internal : algoFormat; | |
| import std.range : iota; | |
| return mixin(algoFormat("tuple(%(current[%d].front%|,%))", | |
| iota(0, current.length))); | |
| } | |
| void popFront() scope | |
| { | |
| foreach_reverse (i, ref r; current) | |
| { | |
| r.popFront(); | |
| if (!r.empty) break; | |
| static if (i == 0) | |
| empty = true; | |
| else | |
| r = ranges[i].save; // rollover | |
| } | |
| } | |
| @property Result save() return scope | |
| { | |
| Result copy = this; | |
| foreach (i, r; ranges) | |
| { | |
| copy.ranges[i] = ranges[i].save; | |
| copy.current[i] = current[i].save; | |
| } | |
| return copy; | |
| } | |
| static if (allSatisfy!(hasLength, RR)) | |
| { | |
| @property size_t length() | |
| { | |
| size_t result = 1; | |
| foreach (r; ranges) | |
| { | |
| result *= r.length; | |
| } | |
| return result; | |
| } | |
| } | |
| } | |
| static assert(isForwardRange!Result, Result.stringof ~ " must be a forward" | |
| ~ " range"); | |
| return Result(ranges); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.setops : cartesianProduct; | |
| // 2 ranges | |
| assert(cartesianProduct([1, 2], [3, 4]).length == 4); | |
| // different sizes | |
| assert(cartesianProduct([1, 2, 3], [4, 5]).length == 6); | |
| // 3 ranges | |
| assert(cartesianProduct([1, 2], [3, 4], [5]).length == 4); | |
| } | |
| // cartesian product of empty ranges should be empty | |
| // https://issues.dlang.org/show_bug.cgi?id=10693 | |
| @safe unittest | |
| { | |
| int[] a, b, c, d, e; | |
| auto cprod = cartesianProduct(a,b,c,d,e); | |
| assert(cprod.empty); | |
| foreach (_; cprod) {} // should not crash | |
| // Test case where only one of the ranges is empty: the result should still | |
| // be empty. | |
| int[] p=[1], q=[]; | |
| auto cprod2 = cartesianProduct(p,p,p,q,p); | |
| assert(cprod2.empty); | |
| foreach (_; cprod2) {} // should not crash | |
| } | |
| @safe unittest | |
| { | |
| // .init value of cartesianProduct should be empty | |
| auto cprod = cartesianProduct([0,0], [1,1], [2,2]); | |
| assert(!cprod.empty); | |
| assert(cprod.init.empty); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=13393 | |
| @safe unittest | |
| { | |
| assert(!cartesianProduct([0],[0],[0]).save.empty); | |
| } | |
| /// ditto | |
| auto cartesianProduct(R1, R2, RR...)(R1 range1, R2 range2, RR otherRanges) | |
| if (!allSatisfy!(isForwardRange, R1, R2, RR) || | |
| anySatisfy!(isInfinite, R1, R2, RR)) | |
| { | |
| /* We implement the n-ary cartesian product by recursively invoking the | |
| * binary cartesian product. To make the resulting range nicer, we denest | |
| * one level of tuples so that a ternary cartesian product, for example, | |
| * returns 3-element tuples instead of nested 2-element tuples. | |
| */ | |
| import std.algorithm.internal : algoFormat; | |
| import std.algorithm.iteration : map; | |
| import std.range : iota; | |
| enum string denest = algoFormat("tuple(a[0], %(a[1][%d]%|,%))", | |
| iota(0, otherRanges.length+1)); | |
| return map!denest( | |
| cartesianProduct(range1, cartesianProduct(range2, otherRanges)) | |
| ); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.searching : canFind; | |
| import std.range; | |
| import std.typecons : tuple, Tuple; | |
| auto N = sequence!"n"(0); | |
| auto N3 = cartesianProduct(N, N, N); | |
| // Check that tuples are properly denested | |
| assert(is(ElementType!(typeof(N3)) == Tuple!(size_t,size_t,size_t))); | |
| assert(canFind(N3, tuple(0, 27, 7))); | |
| assert(canFind(N3, tuple(50, 23, 11))); | |
| assert(canFind(N3, tuple(9, 3, 0))); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.searching : canFind; | |
| import std.range; | |
| import std.typecons : tuple, Tuple; | |
| auto N = sequence!"n"(0); | |
| auto N4 = cartesianProduct(N, N, N, N); | |
| // Check that tuples are properly denested | |
| assert(is(ElementType!(typeof(N4)) == Tuple!(size_t,size_t,size_t,size_t))); | |
| assert(canFind(N4, tuple(1, 2, 3, 4))); | |
| assert(canFind(N4, tuple(4, 3, 2, 1))); | |
| assert(canFind(N4, tuple(10, 3, 1, 2))); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=9878 | |
| /// | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.typecons : tuple; | |
| auto A = [ 1, 2, 3 ]; | |
| auto B = [ 'a', 'b', 'c' ]; | |
| auto C = [ "x", "y", "z" ]; | |
| auto ABC = cartesianProduct(A, B, C); | |
| assert(ABC.equal([ | |
| tuple(1, 'a', "x"), tuple(1, 'a', "y"), tuple(1, 'a', "z"), | |
| tuple(1, 'b', "x"), tuple(1, 'b', "y"), tuple(1, 'b', "z"), | |
| tuple(1, 'c', "x"), tuple(1, 'c', "y"), tuple(1, 'c', "z"), | |
| tuple(2, 'a', "x"), tuple(2, 'a', "y"), tuple(2, 'a', "z"), | |
| tuple(2, 'b', "x"), tuple(2, 'b', "y"), tuple(2, 'b', "z"), | |
| tuple(2, 'c', "x"), tuple(2, 'c', "y"), tuple(2, 'c', "z"), | |
| tuple(3, 'a', "x"), tuple(3, 'a', "y"), tuple(3, 'a', "z"), | |
| tuple(3, 'b', "x"), tuple(3, 'b', "y"), tuple(3, 'b', "z"), | |
| tuple(3, 'c', "x"), tuple(3, 'c', "y"), tuple(3, 'c', "z") | |
| ])); | |
| } | |
| pure @safe nothrow @nogc unittest | |
| { | |
| import std.range.primitives : isForwardRange; | |
| int[2] A = [1,2]; | |
| auto C = cartesianProduct(A[], A[], A[]); | |
| assert(isForwardRange!(typeof(C))); | |
| C.popFront(); | |
| auto front1 = C.front; | |
| auto D = C.save; | |
| C.popFront(); | |
| assert(D.front == front1); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=13935 | |
| @safe unittest | |
| { | |
| import std.algorithm.iteration : map; | |
| auto seq = [1, 2].map!(x => x); | |
| foreach (pair; cartesianProduct(seq, seq)) {} | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.typecons : tuple; | |
| static struct SystemRange | |
| { | |
| int[] data; | |
| int front() @system @property inout | |
| { | |
| return data[0]; | |
| } | |
| bool empty() @system @property inout | |
| { | |
| return data.length == 0; | |
| } | |
| void popFront() @system | |
| { | |
| data = data[1 .. $]; | |
| } | |
| SystemRange save() @system | |
| { | |
| return this; | |
| } | |
| } | |
| assert(SystemRange([1, 2]).cartesianProduct(SystemRange([3, 4])) | |
| .equal([tuple(1, 3), tuple(1, 4), tuple(2, 3), tuple(2, 4)])); | |
| } | |
| // largestPartialIntersection | |
| /** | |
| Given a range of sorted $(REF_ALTTEXT forward ranges, isForwardRange, std,range,primitives) | |
| `ror`, copies to `tgt` the elements that are common to most ranges, along with their number | |
| of occurrences. All ranges in `ror` are assumed to be sorted by $(D | |
| less). Only the most frequent `tgt.length` elements are returned. | |
| Params: | |
| less = The predicate the ranges are sorted by. | |
| ror = A range of forward ranges sorted by `less`. | |
| tgt = The target range to copy common elements to. | |
| sorted = Whether the elements copied should be in sorted order. | |
| The function `largestPartialIntersection` is useful for | |
| e.g. searching an $(LINK2 https://en.wikipedia.org/wiki/Inverted_index, | |
| inverted index) for the documents most | |
| likely to contain some terms of interest. The complexity of the search | |
| is $(BIGOH n * log(tgt.length)), where `n` is the sum of lengths of | |
| all input ranges. This approach is faster than keeping an associative | |
| array of the occurrences and then selecting its top items, and also | |
| requires less memory (`largestPartialIntersection` builds its | |
| result directly in `tgt` and requires no extra memory). | |
| If at least one of the ranges is a multiset, then all occurences | |
| of a duplicate element are taken into account. The result is | |
| equivalent to merging all ranges and picking the most frequent | |
| `tgt.length` elements. | |
| Warning: Because `largestPartialIntersection` does not allocate | |
| extra memory, it will leave `ror` modified. Namely, $(D | |
| largestPartialIntersection) assumes ownership of `ror` and | |
| discretionarily swaps and advances elements of it. If you want $(D | |
| ror) to preserve its contents after the call, you may want to pass a | |
| duplicate to `largestPartialIntersection` (and perhaps cache the | |
| duplicate in between calls). | |
| */ | |
| void largestPartialIntersection | |
| (alias less = "a < b", RangeOfRanges, Range) | |
| (RangeOfRanges ror, Range tgt, SortOutput sorted = No.sortOutput) | |
| { | |
| struct UnitWeights | |
| { | |
| static int opIndex(ElementType!(ElementType!RangeOfRanges)) { return 1; } | |
| } | |
| return largestPartialIntersectionWeighted!less(ror, tgt, UnitWeights(), | |
| sorted); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.typecons : tuple, Tuple; | |
| // Figure which number can be found in most arrays of the set of | |
| // arrays below. | |
| double[][] a = | |
| [ | |
| [ 1, 4, 7, 8 ], | |
| [ 1, 7 ], | |
| [ 1, 7, 8], | |
| [ 4 ], | |
| [ 7 ], | |
| ]; | |
| auto b = new Tuple!(double, uint)[1]; | |
| // it will modify the input range, hence we need to create a duplicate | |
| largestPartialIntersection(a.dup, b); | |
| // First member is the item, second is the occurrence count | |
| assert(b[0] == tuple(7.0, 4u)); | |
| // 7.0 occurs in 4 out of 5 inputs, more than any other number | |
| // If more of the top-frequent numbers are needed, just create a larger | |
| // tgt range | |
| auto c = new Tuple!(double, uint)[2]; | |
| largestPartialIntersection(a, c); | |
| assert(c[0] == tuple(1.0, 3u)); | |
| // 1.0 occurs in 3 inputs | |
| // multiset | |
| double[][] x = | |
| [ | |
| [1, 1, 1, 1, 4, 7, 8], | |
| [1, 7], | |
| [1, 7, 8], | |
| [4, 7], | |
| [7] | |
| ]; | |
| auto y = new Tuple!(double, uint)[2]; | |
| largestPartialIntersection(x.dup, y); | |
| // 7.0 occurs 5 times | |
| assert(y[0] == tuple(7.0, 5u)); | |
| // 1.0 occurs 6 times | |
| assert(y[1] == tuple(1.0, 6u)); | |
| } | |
| import std.algorithm.sorting : SortOutput; // FIXME | |
| // largestPartialIntersectionWeighted | |
| /** | |
| Similar to `largestPartialIntersection`, but associates a weight | |
| with each distinct element in the intersection. | |
| If at least one of the ranges is a multiset, then all occurences | |
| of a duplicate element are taken into account. The result | |
| is equivalent to merging all input ranges and picking the highest | |
| `tgt.length`, weight-based ranking elements. | |
| Params: | |
| less = The predicate the ranges are sorted by. | |
| ror = A range of $(REF_ALTTEXT forward ranges, isForwardRange, std,range,primitives) | |
| sorted by `less`. | |
| tgt = The target range to copy common elements to. | |
| weights = An associative array mapping elements to weights. | |
| sorted = Whether the elements copied should be in sorted order. | |
| */ | |
| void largestPartialIntersectionWeighted | |
| (alias less = "a < b", RangeOfRanges, Range, WeightsAA) | |
| (RangeOfRanges ror, Range tgt, WeightsAA weights, SortOutput sorted = No.sortOutput) | |
| { | |
| import std.algorithm.iteration : group; | |
| import std.algorithm.sorting : topNCopy; | |
| if (tgt.empty) return; | |
| alias InfoType = ElementType!Range; | |
| bool heapComp(InfoType a, InfoType b) | |
| { | |
| return weights[a[0]] * a[1] > weights[b[0]] * b[1]; | |
| } | |
| topNCopy!heapComp(group(multiwayMerge!less(ror)), tgt, sorted); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.typecons : tuple, Tuple; | |
| // Figure which number can be found in most arrays of the set of | |
| // arrays below, with specific per-element weights | |
| double[][] a = | |
| [ | |
| [ 1, 4, 7, 8 ], | |
| [ 1, 7 ], | |
| [ 1, 7, 8], | |
| [ 4 ], | |
| [ 7 ], | |
| ]; | |
| auto b = new Tuple!(double, uint)[1]; | |
| double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ]; | |
| largestPartialIntersectionWeighted(a, b, weights); | |
| // First member is the item, second is the occurrence count | |
| assert(b[0] == tuple(4.0, 2u)); | |
| // 4.0 occurs 2 times -> 4.6 (2 * 2.3) | |
| // 7.0 occurs 3 times -> 4.4 (3 * 1.1) | |
| // multiset | |
| double[][] x = | |
| [ | |
| [ 1, 1, 1, 4, 7, 8 ], | |
| [ 1, 7 ], | |
| [ 1, 7, 8], | |
| [ 4 ], | |
| [ 7 ], | |
| ]; | |
| auto y = new Tuple!(double, uint)[1]; | |
| largestPartialIntersectionWeighted(x, y, weights); | |
| assert(y[0] == tuple(1.0, 5u)); | |
| // 1.0 occurs 5 times -> 1.2 * 5 = 6 | |
| } | |
| @system unittest | |
| { | |
| import std.conv : text; | |
| import std.typecons : tuple, Tuple, Yes; | |
| double[][] a = | |
| [ | |
| [ 1, 4, 7, 8 ], | |
| [ 1, 7 ], | |
| [ 1, 7, 8], | |
| [ 4 ], | |
| [ 7 ], | |
| ]; | |
| auto b = new Tuple!(double, uint)[2]; | |
| largestPartialIntersection(a, b, Yes.sortOutput); | |
| assert(b == [ tuple(7.0, 4u), tuple(1.0, 3u) ][], text(b)); | |
| assert(a[0].empty); | |
| } | |
| @system unittest | |
| { | |
| import std.conv : text; | |
| import std.typecons : tuple, Tuple, Yes; | |
| string[][] a = | |
| [ | |
| [ "1", "4", "7", "8" ], | |
| [ "1", "7" ], | |
| [ "1", "7", "8"], | |
| [ "4" ], | |
| [ "7" ], | |
| ]; | |
| auto b = new Tuple!(string, uint)[2]; | |
| largestPartialIntersection(a, b, Yes.sortOutput); | |
| assert(b == [ tuple("7", 4u), tuple("1", 3u) ][], text(b)); | |
| } | |
| @system unittest | |
| { | |
| import std.typecons : tuple, Tuple; | |
| // Figure which number can be found in most arrays of the set of | |
| // arrays below, with specific per-element weights | |
| double[][] a = | |
| [ | |
| [ 1, 4, 7, 8 ], | |
| [ 1, 7 ], | |
| [ 1, 7, 8], | |
| [ 4 ], | |
| [ 7 ], | |
| ]; | |
| auto b = new Tuple!(double, uint)[1]; | |
| double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ]; | |
| largestPartialIntersectionWeighted(a, b, weights); | |
| // First member is the item, second is the occurrence count | |
| assert(b[0] == tuple(4.0, 2u)); | |
| } | |
| @system unittest | |
| { | |
| import std.container : Array; | |
| import std.typecons : Tuple; | |
| alias T = Tuple!(uint, uint); | |
| const Array!T arrayOne = Array!T( [ T(1,2), T(3,4) ] ); | |
| const Array!T arrayTwo = Array!T([ T(1,2), T(3,4) ] ); | |
| assert(arrayOne == arrayTwo); | |
| } | |
| // MultiwayMerge | |
| /** | |
| Merges multiple sets. The input sets are passed as a | |
| range of ranges and each is assumed to be sorted by $(D | |
| less). Computation is done lazily, one union element at a time. The | |
| complexity of one `popFront` operation is $(BIGOH | |
| log(ror.length)). However, the length of `ror` decreases as ranges | |
| in it are exhausted, so the complexity of a full pass through $(D | |
| MultiwayMerge) is dependent on the distribution of the lengths of ranges | |
| contained within `ror`. If all ranges have the same length `n` | |
| (worst case scenario), the complexity of a full pass through $(D | |
| MultiwayMerge) is $(BIGOH n * ror.length * log(ror.length)), i.e., $(D | |
| log(ror.length)) times worse than just spanning all ranges in | |
| turn. The output comes sorted (unstably) by `less`. | |
| The length of the resulting range is the sum of all lengths of | |
| the ranges passed as input. This means that all elements (duplicates | |
| included) are transferred to the resulting range. | |
| For backward compatibility, `multiwayMerge` is available under | |
| the name `nWayUnion` and `MultiwayMerge` under the name of `NWayUnion` . | |
| Future code should use `multiwayMerge` and `MultiwayMerge` as `nWayUnion` | |
| and `NWayUnion` will be deprecated. | |
| Params: | |
| less = Predicate the given ranges are sorted by. | |
| ror = A range of ranges sorted by `less` to compute the union for. | |
| Returns: | |
| A range of the union of the ranges in `ror`. | |
| Warning: Because `MultiwayMerge` does not allocate extra memory, it | |
| will leave `ror` modified. Namely, `MultiwayMerge` assumes ownership | |
| of `ror` and discretionarily swaps and advances elements of it. If | |
| you want `ror` to preserve its contents after the call, you may | |
| want to pass a duplicate to `MultiwayMerge` (and perhaps cache the | |
| duplicate in between calls). | |
| See_Also: $(REF merge, std,algorithm,sorting) for an analogous function that | |
| takes a static number of ranges of possibly disparate types. | |
| */ | |
| struct MultiwayMerge(alias less, RangeOfRanges) | |
| { | |
| import std.container : BinaryHeap; | |
| private alias ElementType = .ElementType!(.ElementType!RangeOfRanges); | |
| private alias comp = binaryFun!less; | |
| private RangeOfRanges _ror; | |
| /// | |
| static bool compFront(.ElementType!RangeOfRanges a, | |
| .ElementType!RangeOfRanges b) | |
| { | |
| // revert comparison order so we get the smallest elements first | |
| return comp(b.front, a.front); | |
| } | |
| private BinaryHeap!(RangeOfRanges, compFront) _heap; | |
| /// | |
| this(RangeOfRanges ror) | |
| { | |
| import std.algorithm.mutation : remove, SwapStrategy; | |
| // Preemptively get rid of all empty ranges in the input | |
| // No need for stability either | |
| _ror = remove!("a.empty", SwapStrategy.unstable)(ror); | |
| //Build the heap across the range | |
| _heap.acquire(_ror); | |
| } | |
| /// | |
| @property bool empty() { return _ror.empty; } | |
| /// | |
| @property auto ref front() | |
| { | |
| return _heap.front.front; | |
| } | |
| /// | |
| void popFront() | |
| { | |
| _heap.removeFront(); | |
| // let's look at the guy just popped | |
| _ror.back.popFront(); | |
| if (_ror.back.empty) | |
| { | |
| _ror.popBack(); | |
| // nothing else to do: the empty range is not in the | |
| // heap and not in _ror | |
| return; | |
| } | |
| // Put the popped range back in the heap | |
| const bool worked = _heap.conditionalInsert(_ror.back); | |
| assert(worked, "Failed to insert item into heap"); | |
| } | |
| } | |
| /// Ditto | |
| MultiwayMerge!(less, RangeOfRanges) multiwayMerge | |
| (alias less = "a < b", RangeOfRanges) | |
| (RangeOfRanges ror) | |
| { | |
| return typeof(return)(ror); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| double[][] a = | |
| [ | |
| [ 1, 4, 7, 8 ], | |
| [ 1, 7 ], | |
| [ 1, 7, 8], | |
| [ 4 ], | |
| [ 7 ], | |
| ]; | |
| auto witness = [ | |
| 1, 1, 1, 4, 4, 7, 7, 7, 7, 8, 8 | |
| ]; | |
| assert(equal(multiwayMerge(a), witness)); | |
| double[][] b = | |
| [ | |
| // range with duplicates | |
| [ 1, 1, 4, 7, 8 ], | |
| [ 7 ], | |
| [ 1, 7, 8], | |
| [ 4 ], | |
| [ 7 ], | |
| ]; | |
| // duplicates are propagated to the resulting range | |
| assert(equal(multiwayMerge(b), witness)); | |
| } | |
| alias nWayUnion = multiwayMerge; | |
| alias NWayUnion = MultiwayMerge; | |
| /** | |
| Computes the union of multiple ranges. The | |
| $(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) are passed | |
| as a range of ranges and each is assumed to be sorted by $(D | |
| less). Computation is done lazily, one union element at a time. | |
| `multiwayUnion(ror)` is functionally equivalent to `multiwayMerge(ror).uniq`. | |
| "The output of multiwayUnion has no duplicates even when its inputs contain duplicates." | |
| Params: | |
| less = Predicate the given ranges are sorted by. | |
| ror = A range of ranges sorted by `less` to compute the intersection for. | |
| Returns: | |
| A range of the union of the ranges in `ror`. | |
| See also: $(LREF multiwayMerge) | |
| */ | |
| auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) | |
| { | |
| import std.algorithm.iteration : uniq; | |
| import std.functional : not; | |
| return ror.multiwayMerge!(less).uniq!(not!less); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| // sets | |
| double[][] a = | |
| [ | |
| [ 1, 4, 7, 8 ], | |
| [ 1, 7 ], | |
| [ 1, 7, 8], | |
| [ 4 ], | |
| [ 7 ], | |
| ]; | |
| auto witness = [1, 4, 7, 8]; | |
| assert(equal(multiwayUnion(a), witness)); | |
| // multisets | |
| double[][] b = | |
| [ | |
| [ 1, 1, 1, 4, 7, 8 ], | |
| [ 1, 7 ], | |
| [ 1, 7, 7, 8], | |
| [ 4 ], | |
| [ 7 ], | |
| ]; | |
| assert(equal(multiwayUnion(b), witness)); | |
| double[][] c = | |
| [ | |
| [9, 8, 8, 8, 7, 6], | |
| [9, 8, 6], | |
| [9, 8, 5] | |
| ]; | |
| auto witness2 = [9, 8, 7, 6, 5]; | |
| assert(equal(multiwayUnion!"a > b"(c), witness2)); | |
| } | |
| /** | |
| Lazily computes the difference of `r1` and `r2`. The two ranges | |
| are assumed to be sorted by `less`. The element types of the two | |
| ranges must have a common type. | |
| In the case of multisets, considering that element `a` appears `x` | |
| times in `r1` and `y` times and `r2`, the number of occurences | |
| of `a` in the resulting range is going to be `x-y` if x > y or 0 otherwise. | |
| Params: | |
| less = Predicate the given ranges are sorted by. | |
| r1 = The first range. | |
| r2 = The range to subtract from `r1`. | |
| Returns: | |
| A range of the difference of `r1` and `r2`. | |
| See_also: $(LREF setSymmetricDifference) | |
| */ | |
| struct SetDifference(alias less = "a < b", R1, R2) | |
| if (isInputRange!(R1) && isInputRange!(R2)) | |
| { | |
| private: | |
| R1 r1; | |
| R2 r2; | |
| alias comp = binaryFun!(less); | |
| void adjustPosition() | |
| { | |
| while (!r1.empty) | |
| { | |
| if (r2.empty || comp(r1.front, r2.front)) break; | |
| if (comp(r2.front, r1.front)) | |
| { | |
| r2.popFront(); | |
| } | |
| else | |
| { | |
| // both are equal | |
| r1.popFront(); | |
| r2.popFront(); | |
| } | |
| } | |
| } | |
| public: | |
| /// | |
| this(R1 r1, R2 r2) | |
| { | |
| this.r1 = r1; | |
| this.r2 = r2; | |
| // position to the first element | |
| adjustPosition(); | |
| } | |
| /// | |
| void popFront() | |
| { | |
| r1.popFront(); | |
| adjustPosition(); | |
| } | |
| /// | |
| @property auto ref front() | |
| { | |
| assert(!empty, "Can not get front of empty SetDifference"); | |
| return r1.front; | |
| } | |
| static if (isForwardRange!R1 && isForwardRange!R2) | |
| { | |
| /// | |
| @property typeof(this) save() | |
| { | |
| auto ret = this; | |
| ret.r1 = r1.save; | |
| ret.r2 = r2.save; | |
| return ret; | |
| } | |
| } | |
| /// | |
| @property bool empty() { return r1.empty; } | |
| } | |
| /// Ditto | |
| SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2) | |
| (R1 r1, R2 r2) | |
| { | |
| return typeof(return)(r1, r2); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.range.primitives : isForwardRange; | |
| //sets | |
| int[] a = [ 1, 2, 4, 5, 7, 9 ]; | |
| int[] b = [ 0, 1, 2, 4, 7, 8 ]; | |
| assert(equal(setDifference(a, b), [5, 9])); | |
| static assert(isForwardRange!(typeof(setDifference(a, b)))); | |
| // multisets | |
| int[] x = [1, 1, 1, 2, 3]; | |
| int[] y = [1, 1, 2, 4, 5]; | |
| auto r = setDifference(x, y); | |
| assert(equal(r, [1, 3])); | |
| assert(setDifference(r, x).empty); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=10460 | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| int[] a = [1, 2, 3, 4, 5]; | |
| int[] b = [2, 4]; | |
| foreach (ref e; setDifference(a, b)) | |
| e = 0; | |
| assert(equal(a, [0, 2, 0, 4, 0])); | |
| } | |
| /** | |
| Lazily computes the intersection of two or more | |
| $(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) | |
| `ranges`. The ranges are assumed to be sorted by `less`. The element | |
| types of the ranges must have a common type. | |
| In the case of multisets, the range with the minimum number of | |
| occurences of a given element, propagates the number of | |
| occurences of this element to the resulting range. | |
| Params: | |
| less = Predicate the given ranges are sorted by. | |
| ranges = The ranges to compute the intersection for. | |
| Returns: | |
| A range containing the intersection of the given ranges. | |
| */ | |
| struct SetIntersection(alias less = "a < b", Rs...) | |
| if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && | |
| !is(CommonType!(staticMap!(ElementType, Rs)) == void)) | |
| { | |
| private: | |
| Rs _input; | |
| alias comp = binaryFun!less; | |
| alias ElementType = CommonType!(staticMap!(.ElementType, Rs)); | |
| // Positions to the first elements that are all equal | |
| void adjustPosition() | |
| { | |
| if (empty) return; | |
| size_t done = Rs.length; | |
| static if (Rs.length > 1) while (true) | |
| { | |
| foreach (i, ref r; _input) | |
| { | |
| alias next = _input[(i + 1) % Rs.length]; | |
| if (comp(next.front, r.front)) | |
| { | |
| do | |
| { | |
| next.popFront(); | |
| if (next.empty) return; | |
| } while (comp(next.front, r.front)); | |
| done = Rs.length; | |
| } | |
| if (--done == 0) return; | |
| } | |
| } | |
| } | |
| public: | |
| /// | |
| this(Rs input) | |
| { | |
| this._input = input; | |
| // position to the first element | |
| adjustPosition(); | |
| } | |
| /// | |
| @property bool empty() | |
| { | |
| foreach (ref r; _input) | |
| { | |
| if (r.empty) return true; | |
| } | |
| return false; | |
| } | |
| /// | |
| void popFront() | |
| { | |
| assert(!empty, "Can not popFront of empty SetIntersection"); | |
| static if (Rs.length > 1) foreach (i, ref r; _input) | |
| { | |
| alias next = _input[(i + 1) % Rs.length]; | |
| assert(!comp(r.front, next.front), "Set elements must not" | |
| ~ " contradict the less predicate"); | |
| } | |
| foreach (ref r; _input) | |
| { | |
| r.popFront(); | |
| } | |
| adjustPosition(); | |
| } | |
| /// | |
| @property ElementType front() | |
| { | |
| assert(!empty, "Can not get front of empty SetIntersection"); | |
| return _input[0].front; | |
| } | |
| static if (allSatisfy!(isForwardRange, Rs)) | |
| { | |
| /// | |
| @property SetIntersection save() | |
| { | |
| auto ret = this; | |
| foreach (i, ref r; _input) | |
| { | |
| ret._input[i] = r.save; | |
| } | |
| return ret; | |
| } | |
| } | |
| } | |
| /// Ditto | |
| SetIntersection!(less, Rs) setIntersection(alias less = "a < b", Rs...)(Rs ranges) | |
| if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && | |
| !is(CommonType!(staticMap!(ElementType, Rs)) == void)) | |
| { | |
| return typeof(return)(ranges); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| // sets | |
| int[] a = [ 1, 2, 4, 5, 7, 9 ]; | |
| int[] b = [ 0, 1, 2, 4, 7, 8 ]; | |
| int[] c = [ 0, 1, 4, 5, 7, 8 ]; | |
| assert(equal(setIntersection(a, a), a)); | |
| assert(equal(setIntersection(a, b), [1, 2, 4, 7])); | |
| assert(equal(setIntersection(a, b, c), [1, 4, 7])); | |
| // multisets | |
| int[] d = [ 1, 1, 2, 2, 7, 7 ]; | |
| int[] e = [ 1, 1, 1, 7]; | |
| assert(equal(setIntersection(a, d), [1, 2, 7])); | |
| assert(equal(setIntersection(d, e), [1, 1, 7])); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.algorithm.iteration : filter; | |
| int[] a = [ 1, 2, 4, 5, 7, 9 ]; | |
| int[] b = [ 0, 1, 2, 4, 7, 8 ]; | |
| int[] c = [ 0, 1, 4, 5, 7, 8 ]; | |
| int[] d = [ 1, 3, 4 ]; | |
| int[] e = [ 4, 5 ]; | |
| assert(equal(setIntersection(a, a), a)); | |
| assert(equal(setIntersection(a, a, a), a)); | |
| assert(equal(setIntersection(a, b), [1, 2, 4, 7])); | |
| assert(equal(setIntersection(a, b, c), [1, 4, 7])); | |
| assert(equal(setIntersection(a, b, c, d), [1, 4])); | |
| assert(equal(setIntersection(a, b, c, d, e), [4])); | |
| auto inpA = a.filter!(_ => true), inpB = b.filter!(_ => true); | |
| auto inpC = c.filter!(_ => true), inpD = d.filter!(_ => true); | |
| assert(equal(setIntersection(inpA, inpB, inpC, inpD), [1, 4])); | |
| assert(equal(setIntersection(a, b, b, a), [1, 2, 4, 7])); | |
| assert(equal(setIntersection(a, c, b), [1, 4, 7])); | |
| assert(equal(setIntersection(b, a, c), [1, 4, 7])); | |
| assert(equal(setIntersection(b, c, a), [1, 4, 7])); | |
| assert(equal(setIntersection(c, a, b), [1, 4, 7])); | |
| assert(equal(setIntersection(c, b, a), [1, 4, 7])); | |
| } | |
| /** | |
| Lazily computes the symmetric difference of `r1` and `r2`, | |
| i.e. the elements that are present in exactly one of `r1` and $(D | |
| r2). The two ranges are assumed to be sorted by `less`, and the | |
| output is also sorted by `less`. The element types of the two | |
| ranges must have a common type. | |
| If both ranges are sets (without duplicated elements), the resulting | |
| range is going to be a set. If at least one of the ranges is a multiset, | |
| the number of occurences of an element `x` in the resulting range is `abs(a-b)` | |
| where `a` is the number of occurences of `x` in `r1`, `b` is the number of | |
| occurences of `x` in `r2`, and `abs` is the absolute value. | |
| If both arguments are ranges of L-values of the same type then | |
| `SetSymmetricDifference` will also be a range of L-values of | |
| that type. | |
| Params: | |
| less = Predicate the given ranges are sorted by. | |
| r1 = The first range. | |
| r2 = The second range. | |
| Returns: | |
| A range of the symmetric difference between `r1` and `r2`. | |
| See_also: $(LREF setDifference) | |
| */ | |
| struct SetSymmetricDifference(alias less = "a < b", R1, R2) | |
| if (isInputRange!(R1) && isInputRange!(R2)) | |
| { | |
| private: | |
| R1 r1; | |
| R2 r2; | |
| //bool usingR2; | |
| alias comp = binaryFun!(less); | |
| void adjustPosition() | |
| { | |
| while (!r1.empty && !r2.empty) | |
| { | |
| if (comp(r1.front, r2.front) || comp(r2.front, r1.front)) | |
| { | |
| break; | |
| } | |
| // equal, pop both | |
| r1.popFront(); | |
| r2.popFront(); | |
| } | |
| } | |
| public: | |
| /// | |
| this(R1 r1, R2 r2) | |
| { | |
| this.r1 = r1; | |
| this.r2 = r2; | |
| // position to the first element | |
| adjustPosition(); | |
| } | |
| /// | |
| void popFront() | |
| { | |
| assert(!empty, "Can not popFront of empty SetSymmetricDifference"); | |
| if (r1.empty) r2.popFront(); | |
| else if (r2.empty) r1.popFront(); | |
| else | |
| { | |
| // neither is empty | |
| if (comp(r1.front, r2.front)) | |
| { | |
| r1.popFront(); | |
| } | |
| else | |
| { | |
| assert(comp(r2.front, r1.front), "Elements of R1 and R2" | |
| ~ " must be different"); | |
| r2.popFront(); | |
| } | |
| } | |
| adjustPosition(); | |
| } | |
| /// | |
| @property auto ref front() | |
| { | |
| assert(!empty, "Can not get the front of an empty" | |
| ~ " SetSymmetricDifference"); | |
| immutable chooseR1 = r2.empty || !r1.empty && comp(r1.front, r2.front); | |
| assert(chooseR1 || r1.empty || comp(r2.front, r1.front), "Failed to" | |
| ~ " get appropriate front"); | |
| return chooseR1 ? r1.front : r2.front; | |
| } | |
| static if (isForwardRange!R1 && isForwardRange!R2) | |
| { | |
| /// | |
| @property typeof(this) save() | |
| { | |
| auto ret = this; | |
| ret.r1 = r1.save; | |
| ret.r2 = r2.save; | |
| return ret; | |
| } | |
| } | |
| /// | |
| ref auto opSlice() { return this; } | |
| /// | |
| @property bool empty() { return r1.empty && r2.empty; } | |
| } | |
| /// Ditto | |
| SetSymmetricDifference!(less, R1, R2) | |
| setSymmetricDifference(alias less = "a < b", R1, R2) | |
| (R1 r1, R2 r2) | |
| { | |
| return typeof(return)(r1, r2); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.range.primitives : isForwardRange; | |
| // sets | |
| int[] a = [ 1, 2, 4, 5, 7, 9 ]; | |
| int[] b = [ 0, 1, 2, 4, 7, 8 ]; | |
| assert(equal(setSymmetricDifference(a, b), [0, 5, 8, 9][])); | |
| static assert(isForwardRange!(typeof(setSymmetricDifference(a, b)))); | |
| //mutisets | |
| int[] c = [1, 1, 1, 1, 2, 2, 2, 4, 5, 6]; | |
| int[] d = [1, 1, 2, 2, 2, 2, 4, 7, 9]; | |
| assert(equal(setSymmetricDifference(c, d), setSymmetricDifference(d, c))); | |
| assert(equal(setSymmetricDifference(c, d), [1, 1, 2, 5, 6, 7, 9])); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=10460 | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| int[] a = [1, 2]; | |
| double[] b = [2.0, 3.0]; | |
| int[] c = [2, 3]; | |
| alias R1 = typeof(setSymmetricDifference(a, b)); | |
| static assert(is(ElementType!R1 == double)); | |
| static assert(!hasLvalueElements!R1); | |
| alias R2 = typeof(setSymmetricDifference(a, c)); | |
| static assert(is(ElementType!R2 == int)); | |
| static assert(hasLvalueElements!R2); | |
| assert(equal(setSymmetricDifference(a, b), [1.0, 3.0])); | |
| assert(equal(setSymmetricDifference(a, c), [1, 3])); | |
| } | |
| /++ | |
| TODO: once SetUnion got deprecated we can provide the usual definition | |
| (= merge + filter after uniqs) | |
| See: https://github.com/dlang/phobos/pull/4249 | |
| /** | |
| Lazily computes the union of two or more ranges `rs`. The ranges | |
| are assumed to be sorted by `less`. Elements in the output are | |
| unique. The element types of all ranges must have a common type. | |
| Params: | |
| less = Predicate the given ranges are sorted by. | |
| rs = The ranges to compute the union for. | |
| Returns: | |
| A range containing the unique union of the given ranges. | |
| See_Also: | |
| $(REF merge, std,algorithm,sorting) | |
| */ | |
| auto setUnion(alias less = "a < b", Rs...) | |
| (Rs rs) | |
| { | |
| import std.algorithm.iteration : uniq; | |
| import std.algorithm.sorting : merge; | |
| return merge!(less, Rs)(rs).uniq; | |
| } | |
| /// | |
| @safe pure nothrow unittest | |
| /// | |
| { | |
| import std.algorithm.comparison : equal; | |
| int[] a = [1, 3, 5]; | |
| int[] b = [2, 3, 4]; | |
| assert(a.setUnion(b).equal([1, 2, 3, 4, 5])); | |
| } | |
| @safe pure nothrow unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| int[] a = [ 1, 2, 4, 5, 7, 9 ]; | |
| int[] b = [ 0, 1, 2, 4, 7, 8 ]; | |
| double[] c = [ 10.5 ]; | |
| assert(equal(setUnion(a, b), [0, 1, 2, 4, 5, 7, 8, 9][])); | |
| assert(equal(setUnion(a, c, b), | |
| [0, 1, 2, 4, 5, 7, 8, 9, 10.5][])); | |
| } | |
| @safe unittest | |
| { | |
| // save | |
| import std.range : dropOne; | |
| int[] a = [0, 1, 2]; | |
| int[] b = [0, 3]; | |
| auto arr = a.setUnion(b); | |
| assert(arr.front == 0); | |
| assert(arr.save.dropOne.front == 1); | |
| assert(arr.front == 0); | |
| } | |
| @nogc @safe pure nothrow unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| static immutable a = [1, 3, 5]; | |
| static immutable b = [2, 4]; | |
| static immutable r = [1, 2, 3, 4, 5]; | |
| assert(a.setUnion(b).equal(r)); | |
| } | |
| @safe pure nothrow unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.internal.test.dummyrange; | |
| import std.range : iota; | |
| auto dummyResult1 = [1, 1.5, 2, 3, 4, 5, 5.5, 6, 7, 8, 9, 10]; | |
| auto dummyResult2 = iota(1, 11); | |
| foreach (DummyType; AllDummyRanges) | |
| { | |
| DummyType d; | |
| assert(d.setUnion([1, 1.5, 5.5]).equal(dummyResult1)); | |
| assert(d.setUnion(d).equal(dummyResult2)); | |
| } | |
| } | |
| ++/ |