Skip to content

Commit b15b79b

Browse files
committed
add merge for series
1 parent e7d53e1 commit b15b79b

File tree

14 files changed

+1477
-56
lines changed

14 files changed

+1477
-56
lines changed

source/mir/algorithm/iteration.d

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Written in the D programming language.
2+
/**
3+
This is a submodule of $(MREF std, algorithm).
4+
It contains generic _iteration algorithms.
5+
$(SCRIPT inhibitQuickIndex = 1;)
6+
$(BOOKTABLE Cheat Sheet,
7+
$(TR $(TH Function Name) $(TH Description))
8+
$(T2 uniq,
9+
Iterates over the unique elements in a range, which is assumed sorted.)
10+
)
11+
Copyright: Andrei Alexandrescu 2008-.
12+
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
13+
Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilya Yaroshenko (Mir & BetterC rework).
14+
Source: $(PHOBOSSRC std/algorithm/_iteration.d)
15+
Macros:
16+
T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
17+
*/
18+
module mir.algorithm.iteration;
19+
20+
import std.range.primitives: isInputRange, isBidirectionalRange, isInfinite, isForwardRange, ElementType;
21+
import mir.functional: naryFun;
22+
import mir.array.primitives;
23+
24+
// uniq
25+
/**
26+
Lazily iterates unique consecutive elements of the given range (functionality
27+
akin to the $(HTTP wikipedia.org/wiki/_Uniq, _uniq) system
28+
utility). Equivalence of elements is assessed by using the predicate
29+
$(D pred), by default $(D "a == b"). The predicate is passed to
30+
$(REF nary, mir,functional), and can either accept a string, or any callable
31+
that can be executed via $(D pred(element, element)). If the given range is
32+
bidirectional, $(D uniq) also yields a
33+
`std,range,primitives`.
34+
Params:
35+
pred = Predicate for determining equivalence between range elements.
36+
r = An input range of elements to filter.
37+
Returns:
38+
An input range of
39+
consecutively unique elements in the original range. If `r` is also a
40+
forward range or bidirectional range, the returned range will be likewise.
41+
*/
42+
Uniq!(naryFun!pred, Range) uniq(alias pred = "a == b", Range)(auto ref Range r)
43+
if (isInputRange!Range && is(typeof(naryFun!pred(r.front, r.front)) == bool))
44+
{
45+
return typeof(return)(r);
46+
}
47+
48+
///
49+
@safe unittest
50+
{
51+
import std.algorithm.comparison : equal;
52+
import std.algorithm.mutation : copy;
53+
54+
int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
55+
assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ][]));
56+
57+
// Filter duplicates in-place using copy
58+
arr.length -= arr.uniq().copy(arr).length;
59+
assert(arr == [ 1, 2, 3, 4, 5 ]);
60+
61+
// Note that uniqueness is only determined consecutively; duplicated
62+
// elements separated by an intervening different element will not be
63+
// eliminated:
64+
assert(equal(uniq([ 1, 1, 2, 1, 1, 3, 1]), [1, 2, 1, 3, 1]));
65+
}
66+
67+
///
68+
struct Uniq(alias pred, Range)
69+
{
70+
Range _input;
71+
72+
// this()(auto ref Range input)
73+
// {
74+
// import std.meta: AliasSeq;
75+
// import mir.functional: forward;
76+
// AliasSeq!_input = forward!input;
77+
// }
78+
79+
auto opSlice()()
80+
{
81+
return this;
82+
}
83+
84+
void popFront()()
85+
{
86+
assert(!empty, "Attempting to popFront an empty uniq.");
87+
auto last = _input.front;
88+
do
89+
{
90+
_input.popFront();
91+
}
92+
while (!_input.empty && pred(last, _input.front));
93+
}
94+
95+
@property ElementType!Range front()()
96+
{
97+
assert(!empty, "Attempting to fetch the front of an empty uniq.");
98+
return _input.front;
99+
}
100+
101+
static if (isBidirectionalRange!Range)
102+
{
103+
void popBack()()
104+
{
105+
assert(!empty, "Attempting to popBack an empty uniq.");
106+
auto last = _input.back;
107+
do
108+
{
109+
_input.popBack();
110+
}
111+
while (!_input.empty && pred(last, _input.back));
112+
}
113+
114+
@property ElementType!Range back()()
115+
{
116+
assert(!empty, "Attempting to fetch the back of an empty uniq.");
117+
return _input.back;
118+
}
119+
}
120+
121+
static if (isInfinite!Range)
122+
{
123+
enum bool empty = false; // Propagate infiniteness.
124+
}
125+
else
126+
{
127+
@property bool empty()() { return _input.empty; }
128+
}
129+
130+
static if (isForwardRange!Range)
131+
{
132+
@property typeof(this) save()() {
133+
return typeof(this)(_input.save);
134+
}
135+
}
136+
}
137+
138+
version(none)
139+
@safe unittest
140+
{
141+
import std.algorithm.comparison : equal;
142+
import std.internal.test.dummyrange;
143+
import std.range;
144+
145+
int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
146+
auto r = uniq(arr);
147+
static assert(isForwardRange!(typeof(r)));
148+
149+
assert(equal(r, [ 1, 2, 3, 4, 5 ][]));
150+
assert(equal(retro(r), retro([ 1, 2, 3, 4, 5 ][])));
151+
152+
foreach (DummyType; AllDummyRanges)
153+
{
154+
DummyType d;
155+
auto u = uniq(d);
156+
assert(equal(u, [1,2,3,4,5,6,7,8,9,10]));
157+
158+
static assert(d.rt == RangeType.Input || isForwardRange!(typeof(u)));
159+
160+
static if (d.rt >= RangeType.Bidirectional)
161+
{
162+
assert(equal(retro(u), [10,9,8,7,6,5,4,3,2,1]));
163+
}
164+
}
165+
}
166+
167+
@safe unittest // https://issues.dlang.org/show_bug.cgi?id=17264
168+
{
169+
import std.algorithm.comparison : equal;
170+
171+
const(int)[] var = [0, 1, 1, 2];
172+
assert(var.uniq.equal([0, 1, 2]));
173+
}

source/mir/algorithm/setops.d

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Written in the D programming language.
2+
/**
3+
This is a submodule of $(MREF mir, algorithm). It contains `nothrow` `@nogc` BetterC alternative to `MultiwayMerge` from `std.algorithm.setops`.
4+
5+
Copyright: Andrei Alexandrescu 2008-.
6+
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
7+
Authors: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilya Yaroshenko (Mir & BetterC rework, optimization).
8+
*/
9+
module mir.algorithm.setops;
10+
11+
import mir.functional: naryFun;
12+
13+
/**
14+
Merges multiple sets. The input sets are passed as a
15+
range of ranges and each is assumed to be sorted by $(D
16+
less). Computation is done lazily, one union element at a time. The
17+
complexity of one $(D popFront) operation is $(BIGOH
18+
log(ror.length)). However, the length of $(D ror) decreases as ranges
19+
in it are exhausted, so the complexity of a full pass through $(D
20+
MultiwayMerge) is dependent on the distribution of the lengths of ranges
21+
contained within $(D ror). If all ranges have the same length $(D n)
22+
(worst case scenario), the complexity of a full pass through $(D
23+
MultiwayMerge) is $(BIGOH n * ror.length * log(ror.length)), i.e., $(D
24+
log(ror.length)) times worse than just spanning all ranges in
25+
turn. The output comes sorted (unstably) by $(D less).
26+
The length of the resulting range is the sum of all lengths of
27+
the ranges passed as input. This means that all elements (duplicates
28+
included) are transferred to the resulting range.
29+
For backward compatibility, `multiwayMerge` is available under
30+
the name `nWayUnion` and `MultiwayMerge` under the name of `NWayUnion` .
31+
Future code should use `multiwayMerge` and `MultiwayMerge` as `nWayUnion`
32+
and `NWayUnion` will be deprecated.
33+
Params:
34+
less = Predicate the given ranges are sorted by.
35+
ror = A range of ranges sorted by `less` to compute the union for.
36+
Returns:
37+
A range of the union of the ranges in `ror`.
38+
Warning: Because $(D MultiwayMerge) does not allocate extra memory, it
39+
will leave $(D ror) modified. Namely, $(D MultiwayMerge) assumes ownership
40+
of $(D ror) and discretionarily swaps and advances elements of it. If
41+
you want $(D ror) to preserve its contents after the call, you may
42+
want to pass a duplicate to $(D MultiwayMerge) (and perhaps cache the
43+
duplicate in between calls).
44+
*/
45+
struct MultiwayMerge(alias less, RangeOfRanges)
46+
{
47+
import mir.array.primitives;
48+
import mir.container.binaryheap;
49+
import std.range.primitives: ElementType;
50+
51+
///
52+
@disable this();
53+
///
54+
@disable this(this);
55+
56+
///
57+
static bool compFront()(auto ref ElementType!RangeOfRanges a, auto ref ElementType!RangeOfRanges b)
58+
{
59+
// revert comparison order so we get the smallest elements first
60+
return less(b.front, a.front);
61+
}
62+
63+
/// Heap
64+
BinaryHeap!(compFront, RangeOfRanges) _heap;
65+
66+
///
67+
this()(auto ref RangeOfRanges ror)
68+
{
69+
import std.algorithm.mutation : remove, SwapStrategy;
70+
71+
// Preemptively get rid of all empty ranges in the input
72+
// No need for stability either
73+
//Build the heap across the range
74+
_heap = typeof(_heap)(ror.remove!("a.empty", SwapStrategy.unstable));
75+
}
76+
77+
///
78+
@property bool empty()() { return _heap.empty; }
79+
80+
///
81+
@property auto ref front()()
82+
{
83+
assert(!empty);
84+
return _heap.front.front;
85+
}
86+
87+
///
88+
void popFront()()
89+
{
90+
_heap._store.front.popFront;
91+
if (!_heap._store.front.empty)
92+
_heap.siftDown(_heap._store[], 0, _heap._length);
93+
else
94+
_heap.removeFront;
95+
}
96+
}
97+
98+
/// Ditto
99+
MultiwayMerge!(naryFun!less, RangeOfRanges) multiwayMerge
100+
(alias less = "a < b", RangeOfRanges)
101+
(auto ref RangeOfRanges ror)
102+
{
103+
return typeof(return)(ror);
104+
}
105+
106+
///
107+
@safe nothrow @nogc unittest
108+
{
109+
import std.algorithm.comparison : equal;
110+
111+
static a =
112+
[
113+
[ 1, 4, 7, 8 ],
114+
[ 1, 7 ],
115+
[ 1, 7, 8],
116+
[ 4 ],
117+
[ 7 ],
118+
];
119+
static witness = [
120+
1, 1, 1, 4, 4, 7, 7, 7, 7, 8, 8
121+
];
122+
assert(a.multiwayMerge.equal(witness));
123+
124+
static b =
125+
[
126+
// range with duplicates
127+
[ 1, 1, 4, 7, 8 ],
128+
[ 7 ],
129+
[ 1, 7, 8],
130+
[ 4 ],
131+
[ 7 ],
132+
];
133+
// duplicates are propagated to the resulting range
134+
assert(b.multiwayMerge.equal(witness));
135+
}
136+
137+
/**
138+
Computes the union of multiple ranges. The input ranges are passed
139+
as a range of ranges and each is assumed to be sorted by $(D
140+
less). Computation is done lazily, one union element at a time.
141+
`multiwayUnion(ror)` is functionally equivalent to `multiwayMerge(ror).uniq`.
142+
"The output of multiwayUnion has no duplicates even when its inputs contain duplicates."
143+
Params:
144+
less = Predicate the given ranges are sorted by.
145+
ror = A range of ranges sorted by `less` to compute the intersection for.
146+
Returns:
147+
A range of the union of the ranges in `ror`.
148+
See also: $(LREF multiwayMerge)
149+
*/
150+
auto multiwayUnion(alias less = "a < b", RangeOfRanges)(auto ref RangeOfRanges ror)
151+
{
152+
import mir.functional: not;
153+
import mir.algorithm.iteration : Uniq;
154+
155+
return Uniq!(not!less, typeof(multiwayMerge!less(ror)))(multiwayMerge!less(ror));
156+
}
157+
158+
///
159+
@system unittest
160+
{
161+
import std.algorithm.comparison : equal;
162+
163+
// sets
164+
double[][] a =
165+
[
166+
[ 1, 4, 7, 8 ],
167+
[ 1, 7 ],
168+
[ 1, 7, 8],
169+
[ 4 ],
170+
[ 7 ],
171+
];
172+
173+
auto witness = [1, 4, 7, 8];
174+
assert(a.multiwayUnion.equal(witness));
175+
176+
// multisets
177+
double[][] b =
178+
[
179+
[ 1, 1, 1, 4, 7, 8 ],
180+
[ 1, 7 ],
181+
[ 1, 7, 7, 8],
182+
[ 4 ],
183+
[ 7 ],
184+
];
185+
assert(b.multiwayUnion.equal(witness));
186+
}
187+
188+
/++
189+
Computes the length of union of multiple ranges. The input ranges are passed
190+
as a range of ranges and each is assumed to be sorted by `less`.
191+
192+
Params:
193+
less = Predicate the given ranges are sorted by.
194+
ror = A range of ranges sorted by `less` to compute the intersection for.
195+
Returns:
196+
A length of the union of the ranges in `ror`.
197+
+/
198+
pragma(inline, false)
199+
size_t unionLength(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror)
200+
{
201+
size_t length;
202+
auto u = ror.multiwayUnion!less;
203+
if (!u.empty) do {
204+
length++;
205+
u.popFront;
206+
} while(!u.empty);
207+
return length;
208+
}

0 commit comments

Comments
 (0)