| // Written in the D programming language. | |
| /** | |
| This is a submodule of $(MREF std, algorithm). | |
| It contains generic mutation algorithms. | |
| $(SCRIPT inhibitQuickIndex = 1;) | |
| $(BOOKTABLE Cheat Sheet, | |
| $(TR $(TH Function Name) $(TH Description)) | |
| $(T2 bringToFront, | |
| If `a = [1, 2, 3]` and `b = [4, 5, 6, 7]`, | |
| `bringToFront(a, b)` leaves `a = [4, 5, 6]` and | |
| `b = [7, 1, 2, 3]`.) | |
| $(T2 copy, | |
| Copies a range to another. If | |
| `a = [1, 2, 3]` and `b = new int[5]`, then `copy(a, b)` | |
| leaves `b = [1, 2, 3, 0, 0]` and returns `b[3 .. $]`.) | |
| $(T2 fill, | |
| Fills a range with a pattern, | |
| e.g., if `a = new int[3]`, then `fill(a, 4)` | |
| leaves `a = [4, 4, 4]` and `fill(a, [3, 4])` leaves | |
| `a = [3, 4, 3]`.) | |
| $(T2 initializeAll, | |
| If `a = [1.2, 3.4]`, then `initializeAll(a)` leaves | |
| `a = [double.init, double.init]`.) | |
| $(T2 move, | |
| `move(a, b)` moves `a` into `b`. `move(a)` reads `a` | |
| destructively when necessary.) | |
| $(T2 moveEmplace, | |
| Similar to `move` but assumes `target` is uninitialized.) | |
| $(T2 moveAll, | |
| Moves all elements from one range to another.) | |
| $(T2 moveEmplaceAll, | |
| Similar to `moveAll` but assumes all elements in `target` are uninitialized.) | |
| $(T2 moveSome, | |
| Moves as many elements as possible from one range to another.) | |
| $(T2 moveEmplaceSome, | |
| Similar to `moveSome` but assumes all elements in `target` are uninitialized.) | |
| $(T2 remove, | |
| Removes elements from a range in-place, and returns the shortened | |
| range.) | |
| $(T2 reverse, | |
| If `a = [1, 2, 3]`, `reverse(a)` changes it to `[3, 2, 1]`.) | |
| $(T2 strip, | |
| Strips all leading and trailing elements equal to a value, or that | |
| satisfy a predicate. | |
| If `a = [1, 1, 0, 1, 1]`, then `strip(a, 1)` and | |
| `strip!(e => e == 1)(a)` returns `[0]`.) | |
| $(T2 stripLeft, | |
| Strips all leading elements equal to a value, or that satisfy a | |
| predicate. If `a = [1, 1, 0, 1, 1]`, then `stripLeft(a, 1)` and | |
| `stripLeft!(e => e == 1)(a)` returns `[0, 1, 1]`.) | |
| $(T2 stripRight, | |
| Strips all trailing elements equal to a value, or that satisfy a | |
| predicate. | |
| If `a = [1, 1, 0, 1, 1]`, then `stripRight(a, 1)` and | |
| `stripRight!(e => e == 1)(a)` returns `[1, 1, 0]`.) | |
| $(T2 swap, | |
| Swaps two values.) | |
| $(T2 swapAt, | |
| Swaps two values by indices.) | |
| $(T2 swapRanges, | |
| Swaps all elements of two ranges.) | |
| $(T2 uninitializedFill, | |
| Fills a range (assumed uninitialized) with a value.) | |
| ) | |
| 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/mutation.d) | |
| Macros: | |
| T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) | |
| */ | |
| module std.algorithm.mutation; | |
| import std.range.primitives; | |
| import std.traits : isArray, isAssignable, isBlitAssignable, isNarrowString, | |
| Unqual, isSomeChar, isMutable; | |
| import std.meta : allSatisfy; | |
| import std.typecons : tuple, Tuple; | |
| // bringToFront | |
| /** | |
| `bringToFront` takes two ranges `front` and `back`, which may | |
| be of different types. Considering the concatenation of `front` and | |
| `back` one unified range, `bringToFront` rotates that unified | |
| range such that all elements in `back` are brought to the beginning | |
| of the unified range. The relative ordering of elements in `front` | |
| and `back`, respectively, remains unchanged. | |
| The `bringToFront` function treats strings at the code unit | |
| level and it is not concerned with Unicode character integrity. | |
| `bringToFront` is designed as a function for moving elements | |
| in ranges, not as a string function. | |
| Performs $(BIGOH max(front.length, back.length)) evaluations of $(D | |
| swap). | |
| The `bringToFront` function can rotate elements in one buffer left or right, swap | |
| buffers of equal length, and even move elements across disjoint | |
| buffers of different types and different lengths. | |
| Preconditions: | |
| Either `front` and `back` are disjoint, or `back` is | |
| reachable from `front` and `front` is not reachable from $(D | |
| back). | |
| Params: | |
| front = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| back = a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) | |
| Returns: | |
| The number of elements brought to the front, i.e., the length of `back`. | |
| See_Also: | |
| $(LINK2 http://en.cppreference.com/w/cpp/algorithm/rotate, STL's `rotate`) | |
| */ | |
| size_t bringToFront(InputRange, ForwardRange)(InputRange front, ForwardRange back) | |
| if (isInputRange!InputRange && isForwardRange!ForwardRange) | |
| { | |
| import std.string : representation; | |
| static if (isNarrowString!InputRange) | |
| { | |
| auto frontW = representation(front); | |
| } | |
| else | |
| { | |
| alias frontW = front; | |
| } | |
| static if (isNarrowString!ForwardRange) | |
| { | |
| auto backW = representation(back); | |
| } | |
| else | |
| { | |
| alias backW = back; | |
| } | |
| return bringToFrontImpl(frontW, backW); | |
| } | |
| /** | |
| The simplest use of `bringToFront` is for rotating elements in a | |
| buffer. For example: | |
| */ | |
| @safe unittest | |
| { | |
| auto arr = [4, 5, 6, 7, 1, 2, 3]; | |
| auto p = bringToFront(arr[0 .. 4], arr[4 .. $]); | |
| assert(p == arr.length - 4); | |
| assert(arr == [ 1, 2, 3, 4, 5, 6, 7 ]); | |
| } | |
| /** | |
| The `front` range may actually "step over" the `back` | |
| range. This is very useful with forward ranges that cannot compute | |
| comfortably right-bounded subranges like `arr[0 .. 4]` above. In | |
| the example below, `r2` is a right subrange of `r1`. | |
| */ | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.container : SList; | |
| import std.range.primitives : popFrontN; | |
| auto list = SList!(int)(4, 5, 6, 7, 1, 2, 3); | |
| auto r1 = list[]; | |
| auto r2 = list[]; popFrontN(r2, 4); | |
| assert(equal(r2, [ 1, 2, 3 ])); | |
| bringToFront(r1, r2); | |
| assert(equal(list[], [ 1, 2, 3, 4, 5, 6, 7 ])); | |
| } | |
| /** | |
| Elements can be swapped across ranges of different types: | |
| */ | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.container : SList; | |
| auto list = SList!(int)(4, 5, 6, 7); | |
| auto vec = [ 1, 2, 3 ]; | |
| bringToFront(list[], vec); | |
| assert(equal(list[], [ 1, 2, 3, 4 ])); | |
| assert(equal(vec, [ 5, 6, 7 ])); | |
| } | |
| /** | |
| Unicode integrity is not preserved: | |
| */ | |
| @safe unittest | |
| { | |
| import std.string : representation; | |
| auto ar = representation("a".dup); | |
| auto br = representation("ç".dup); | |
| bringToFront(ar, br); | |
| auto a = cast(char[]) ar; | |
| auto b = cast(char[]) br; | |
| // Illegal UTF-8 | |
| assert(a == "\303"); | |
| // Illegal UTF-8 | |
| assert(b == "\247a"); | |
| } | |
| private size_t bringToFrontImpl(InputRange, ForwardRange)(InputRange front, ForwardRange back) | |
| if (isInputRange!InputRange && isForwardRange!ForwardRange) | |
| { | |
| import std.array : sameHead; | |
| import std.range : take, Take; | |
| enum bool sameHeadExists = is(typeof(front.sameHead(back))); | |
| size_t result; | |
| for (bool semidone; !front.empty && !back.empty; ) | |
| { | |
| static if (sameHeadExists) | |
| { | |
| if (front.sameHead(back)) break; // shortcut | |
| } | |
| // Swap elements until front and/or back ends. | |
| auto back0 = back.save; | |
| size_t nswaps; | |
| do | |
| { | |
| static if (sameHeadExists) | |
| { | |
| // Detect the stepping-over condition. | |
| if (front.sameHead(back0)) back0 = back.save; | |
| } | |
| swapFront(front, back); | |
| ++nswaps; | |
| front.popFront(); | |
| back.popFront(); | |
| } | |
| while (!front.empty && !back.empty); | |
| if (!semidone) result += nswaps; | |
| // Now deal with the remaining elements. | |
| if (back.empty) | |
| { | |
| if (front.empty) break; | |
| // Right side was shorter, which means that we've brought | |
| // all the back elements to the front. | |
| semidone = true; | |
| // Next pass: bringToFront(front, back0) to adjust the rest. | |
| back = back0; | |
| } | |
| else | |
| { | |
| assert(front.empty, "Expected front to be empty"); | |
| // Left side was shorter. Let's step into the back. | |
| static if (is(InputRange == Take!ForwardRange)) | |
| { | |
| front = take(back0, nswaps); | |
| } | |
| else | |
| { | |
| immutable subresult = bringToFront(take(back0, nswaps), | |
| back); | |
| if (!semidone) result += subresult; | |
| break; // done | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| import std.conv : text; | |
| import std.random : Random = Xorshift, uniform; | |
| // a more elaborate test | |
| { | |
| auto rnd = Random(123_456_789); | |
| int[] a = new int[uniform(100, 200, rnd)]; | |
| int[] b = new int[uniform(100, 200, rnd)]; | |
| foreach (ref e; a) e = uniform(-100, 100, rnd); | |
| foreach (ref e; b) e = uniform(-100, 100, rnd); | |
| int[] c = a ~ b; | |
| // writeln("a= ", a); | |
| // writeln("b= ", b); | |
| auto n = bringToFront(c[0 .. a.length], c[a.length .. $]); | |
| //writeln("c= ", c); | |
| assert(n == b.length); | |
| assert(c == b ~ a, text(c, "\n", a, "\n", b)); | |
| } | |
| // different types, moveFront, no sameHead | |
| { | |
| static struct R(T) | |
| { | |
| T[] data; | |
| size_t i; | |
| @property | |
| { | |
| R save() { return this; } | |
| bool empty() { return i >= data.length; } | |
| T front() { return data[i]; } | |
| T front(real e) { return data[i] = cast(T) e; } | |
| } | |
| void popFront() { ++i; } | |
| } | |
| auto a = R!int([1, 2, 3, 4, 5]); | |
| auto b = R!real([6, 7, 8, 9]); | |
| auto n = bringToFront(a, b); | |
| assert(n == 4); | |
| assert(a.data == [6, 7, 8, 9, 1]); | |
| assert(b.data == [2, 3, 4, 5]); | |
| } | |
| // front steps over back | |
| { | |
| int[] arr, r1, r2; | |
| // back is shorter | |
| arr = [4, 5, 6, 7, 1, 2, 3]; | |
| r1 = arr; | |
| r2 = arr[4 .. $]; | |
| bringToFront(r1, r2) == 3 || assert(0); | |
| assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); | |
| // front is shorter | |
| arr = [5, 6, 7, 1, 2, 3, 4]; | |
| r1 = arr; | |
| r2 = arr[3 .. $]; | |
| bringToFront(r1, r2) == 4 || assert(0); | |
| assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=16959 | |
| auto arr = ['4', '5', '6', '7', '1', '2', '3']; | |
| auto p = bringToFront(arr[0 .. 4], arr[4 .. $]); | |
| assert(p == arr.length - 4); | |
| assert(arr == ['1', '2', '3', '4', '5', '6', '7']); | |
| } | |
| // Tests if types are arrays and support slice assign. | |
| private enum bool areCopyCompatibleArrays(T1, T2) = | |
| isArray!T1 && isArray!T2 && is(typeof(T2.init[] = T1.init[])); | |
| // copy | |
| /** | |
| Copies the content of `source` into `target` and returns the | |
| remaining (unfilled) part of `target`. | |
| Preconditions: `target` shall have enough room to accommodate | |
| the entirety of `source`. | |
| Params: | |
| source = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| target = an output range | |
| Returns: | |
| The unfilled part of target | |
| */ | |
| TargetRange copy(SourceRange, TargetRange)(SourceRange source, TargetRange target) | |
| if (isInputRange!SourceRange && isOutputRange!(TargetRange, ElementType!SourceRange)) | |
| { | |
| static if (areCopyCompatibleArrays!(SourceRange, TargetRange)) | |
| { | |
| const tlen = target.length; | |
| const slen = source.length; | |
| assert(tlen >= slen, | |
| "Cannot copy a source range into a smaller target range."); | |
| immutable overlaps = () @trusted { | |
| return source.ptr < target.ptr + tlen && | |
| target.ptr < source.ptr + slen; }(); | |
| if (overlaps) | |
| { | |
| if (source.ptr < target.ptr) | |
| { | |
| foreach_reverse (idx; 0 .. slen) | |
| target[idx] = source[idx]; | |
| } | |
| else | |
| { | |
| foreach (idx; 0 .. slen) | |
| target[idx] = source[idx]; | |
| } | |
| return target[slen .. tlen]; | |
| } | |
| else | |
| { | |
| // Array specialization. This uses optimized memory copying | |
| // routines under the hood and is about 10-20x faster than the | |
| // generic implementation. | |
| target[0 .. slen] = source[]; | |
| return target[slen .. $]; | |
| } | |
| } | |
| else | |
| { | |
| // Specialize for 2 random access ranges. | |
| // Typically 2 random access ranges are faster iterated by common | |
| // index than by x.popFront(), y.popFront() pair | |
| static if (isRandomAccessRange!SourceRange && | |
| hasLength!SourceRange && | |
| hasSlicing!TargetRange && | |
| isRandomAccessRange!TargetRange && | |
| hasLength!TargetRange) | |
| { | |
| auto len = source.length; | |
| foreach (idx; 0 .. len) | |
| target[idx] = source[idx]; | |
| return target[len .. target.length]; | |
| } | |
| else | |
| { | |
| foreach (element; source) | |
| put(target, element); | |
| return target; | |
| } | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| int[] a = [ 1, 5 ]; | |
| int[] b = [ 9, 8 ]; | |
| int[] buf = new int[](a.length + b.length + 10); | |
| auto rem = a.copy(buf); // copy a into buf | |
| rem = b.copy(rem); // copy b into remainder of buf | |
| assert(buf[0 .. a.length + b.length] == [1, 5, 9, 8]); | |
| assert(rem.length == 10); // unused slots in buf | |
| } | |
| /** | |
| As long as the target range elements support assignment from source | |
| range elements, different types of ranges are accepted: | |
| */ | |
| @safe unittest | |
| { | |
| float[] src = [ 1.0f, 5 ]; | |
| double[] dest = new double[src.length]; | |
| src.copy(dest); | |
| } | |
| /** | |
| To _copy at most `n` elements from a range, you may want to use | |
| $(REF take, std,range): | |
| */ | |
| @safe unittest | |
| { | |
| import std.range; | |
| int[] src = [ 1, 5, 8, 9, 10 ]; | |
| auto dest = new int[](3); | |
| src.take(dest.length).copy(dest); | |
| assert(dest == [ 1, 5, 8 ]); | |
| } | |
| /** | |
| To _copy just those elements from a range that satisfy a predicate, | |
| use $(LREF filter): | |
| */ | |
| @safe unittest | |
| { | |
| import std.algorithm.iteration : filter; | |
| int[] src = [ 1, 5, 8, 9, 10, 1, 2, 0 ]; | |
| auto dest = new int[src.length]; | |
| auto rem = src | |
| .filter!(a => (a & 1) == 1) | |
| .copy(dest); | |
| assert(dest[0 .. $ - rem.length] == [ 1, 5, 9, 1 ]); | |
| } | |
| /** | |
| $(REF retro, std,range) can be used to achieve behavior similar to | |
| $(LINK2 http://en.cppreference.com/w/cpp/algorithm/copy_backward, STL's `copy_backward`'): | |
| */ | |
| @safe unittest | |
| { | |
| import std.algorithm, std.range; | |
| int[] src = [1, 2, 4]; | |
| int[] dest = [0, 0, 0, 0, 0]; | |
| src.retro.copy(dest.retro); | |
| assert(dest == [0, 0, 1, 2, 4]); | |
| } | |
| // Test CTFE copy. | |
| @safe unittest | |
| { | |
| enum c = copy([1,2,3], [4,5,6,7]); | |
| assert(c == [7]); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.iteration : filter; | |
| { | |
| int[] a = [ 1, 5 ]; | |
| int[] b = [ 9, 8 ]; | |
| auto e = copy(filter!("a > 1")(a), b); | |
| assert(b[0] == 5 && e.length == 1); | |
| } | |
| { | |
| int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | |
| copy(a[5 .. 10], a[4 .. 9]); | |
| assert(a[4 .. 9] == [6, 7, 8, 9, 10]); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=21724 | |
| { | |
| int[] a = [1, 2, 3, 4]; | |
| copy(a[0 .. 2], a[1 .. 3]); | |
| assert(a == [1, 1, 2, 4]); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=7898 | |
| { | |
| enum v = | |
| { | |
| import std.algorithm; | |
| int[] arr1 = [10, 20, 30, 40, 50]; | |
| int[] arr2 = arr1.dup; | |
| copy(arr1, arr2); | |
| return 35; | |
| }(); | |
| assert(v == 35); | |
| } | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=13650 | |
| @safe unittest | |
| { | |
| import std.meta : AliasSeq; | |
| static foreach (Char; AliasSeq!(char, wchar, dchar)) | |
| {{ | |
| Char[3] a1 = "123"; | |
| Char[6] a2 = "456789"; | |
| assert(copy(a1[], a2[]) is a2[3..$]); | |
| assert(a1[] == "123"); | |
| assert(a2[] == "123789"); | |
| }} | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=18804 | |
| @safe unittest | |
| { | |
| static struct NullSink | |
| { | |
| void put(E)(E) {} | |
| } | |
| int line = 0; | |
| struct R | |
| { | |
| int front; | |
| @property bool empty() { return line == 1; } | |
| void popFront() { line = 1; } | |
| } | |
| R r; | |
| copy(r, NullSink()); | |
| assert(line == 1); | |
| } | |
| /** | |
| Assigns `value` to each element of input range `range`. | |
| Alternatively, instead of using a single `value` to fill the `range`, | |
| a `filler` $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) | |
| can be provided. The length of `filler` and `range` do not need to match, but | |
| `filler` must not be empty. | |
| Params: | |
| range = An | |
| $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| that exposes references to its elements and has assignable | |
| elements | |
| value = Assigned to each element of range | |
| filler = A | |
| $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) | |
| representing the _fill pattern. | |
| Throws: If `filler` is empty. | |
| See_Also: | |
| $(LREF uninitializedFill) | |
| $(LREF initializeAll) | |
| */ | |
| void fill(Range, Value)(auto ref Range range, auto ref Value value) | |
| if ((isInputRange!Range && is(typeof(range.front = value)) || | |
| isSomeChar!Value && is(typeof(range[] = value)))) | |
| { | |
| alias T = ElementType!Range; | |
| static if (is(typeof(range[] = value))) | |
| { | |
| range[] = value; | |
| } | |
| else static if (is(typeof(range[] = T(value)))) | |
| { | |
| range[] = T(value); | |
| } | |
| else | |
| { | |
| for ( ; !range.empty; range.popFront() ) | |
| { | |
| range.front = value; | |
| } | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| int[] a = [ 1, 2, 3, 4 ]; | |
| fill(a, 5); | |
| assert(a == [ 5, 5, 5, 5 ]); | |
| } | |
| // test fallback on mutable narrow strings | |
| // https://issues.dlang.org/show_bug.cgi?id=16342 | |
| @safe unittest | |
| { | |
| char[] chars = ['a', 'b']; | |
| fill(chars, 'c'); | |
| assert(chars == "cc"); | |
| char[2] chars2 = ['a', 'b']; | |
| fill(chars2, 'c'); | |
| assert(chars2 == "cc"); | |
| wchar[] wchars = ['a', 'b']; | |
| fill(wchars, wchar('c')); | |
| assert(wchars == "cc"w); | |
| dchar[] dchars = ['a', 'b']; | |
| fill(dchars, dchar('c')); | |
| assert(dchars == "cc"d); | |
| } | |
| @nogc @safe unittest | |
| { | |
| const(char)[] chars; | |
| assert(chars.length == 0); | |
| static assert(!__traits(compiles, fill(chars, 'c'))); | |
| wstring wchars; | |
| assert(wchars.length == 0); | |
| static assert(!__traits(compiles, fill(wchars, wchar('c')))); | |
| } | |
| @nogc @safe unittest | |
| { | |
| char[] chars; | |
| fill(chars, 'c'); | |
| assert(chars == ""c); | |
| } | |
| @safe unittest | |
| { | |
| shared(char)[] chrs = ['r']; | |
| fill(chrs, 'c'); | |
| assert(chrs == [shared(char)('c')]); | |
| } | |
| @nogc @safe unittest | |
| { | |
| struct Str(size_t len) | |
| { | |
| private char[len] _data; | |
| void opIndexAssign(char value) @safe @nogc | |
| {_data[] = value;} | |
| } | |
| Str!2 str; | |
| str.fill(':'); | |
| assert(str._data == "::"); | |
| } | |
| @safe unittest | |
| { | |
| char[] chars = ['a','b','c','d']; | |
| chars[1 .. 3].fill(':'); | |
| assert(chars == "a::d"); | |
| } | |
| // end https://issues.dlang.org/show_bug.cgi?id=16342 | |
| @safe unittest | |
| { | |
| import std.conv : text; | |
| import std.internal.test.dummyrange; | |
| int[] a = [ 1, 2, 3 ]; | |
| fill(a, 6); | |
| assert(a == [ 6, 6, 6 ], text(a)); | |
| void fun0() | |
| { | |
| foreach (i; 0 .. 1000) | |
| { | |
| foreach (ref e; a) e = 6; | |
| } | |
| } | |
| void fun1() { foreach (i; 0 .. 1000) fill(a, 6); } | |
| // fill should accept InputRange | |
| alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input); | |
| enum filler = uint.max; | |
| InputRange range; | |
| fill(range, filler); | |
| foreach (value; range.arr) | |
| assert(value == filler); | |
| } | |
| @safe unittest | |
| { | |
| //ER8638_1 IS_NOT self assignable | |
| static struct ER8638_1 | |
| { | |
| void opAssign(int){} | |
| } | |
| //ER8638_1 IS self assignable | |
| static struct ER8638_2 | |
| { | |
| void opAssign(ER8638_2){} | |
| void opAssign(int){} | |
| } | |
| auto er8638_1 = new ER8638_1[](10); | |
| auto er8638_2 = new ER8638_2[](10); | |
| er8638_1.fill(5); //generic case | |
| er8638_2.fill(5); //opSlice(T.init) case | |
| } | |
| @safe unittest | |
| { | |
| { | |
| int[] a = [1, 2, 3]; | |
| immutable(int) b = 0; | |
| a.fill(b); | |
| assert(a == [0, 0, 0]); | |
| } | |
| { | |
| double[] a = [1, 2, 3]; | |
| immutable(int) b = 0; | |
| a.fill(b); | |
| assert(a == [0, 0, 0]); | |
| } | |
| } | |
| /// ditto | |
| void fill(InputRange, ForwardRange)(InputRange range, ForwardRange filler) | |
| if (isInputRange!InputRange | |
| && (isForwardRange!ForwardRange | |
| || (isInputRange!ForwardRange && isInfinite!ForwardRange)) | |
| && is(typeof(InputRange.init.front = ForwardRange.init.front))) | |
| { | |
| static if (isInfinite!ForwardRange) | |
| { | |
| //ForwardRange is infinite, no need for bounds checking or saving | |
| static if (hasSlicing!ForwardRange && hasLength!InputRange | |
| && is(typeof(filler[0 .. range.length]))) | |
| { | |
| copy(filler[0 .. range.length], range); | |
| } | |
| else | |
| { | |
| //manual feed | |
| for ( ; !range.empty; range.popFront(), filler.popFront()) | |
| { | |
| range.front = filler.front; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| import std.exception : enforce; | |
| enforce(!filler.empty, "Cannot fill range with an empty filler"); | |
| static if (hasLength!InputRange && hasLength!ForwardRange | |
| && is(typeof(range.length > filler.length))) | |
| { | |
| //Case we have access to length | |
| immutable len = filler.length; | |
| //Start by bulk copies | |
| while (range.length > len) | |
| { | |
| range = copy(filler.save, range); | |
| } | |
| //and finally fill the partial range. No need to save here. | |
| static if (hasSlicing!ForwardRange && is(typeof(filler[0 .. range.length]))) | |
| { | |
| //use a quick copy | |
| auto len2 = range.length; | |
| range = copy(filler[0 .. len2], range); | |
| } | |
| else | |
| { | |
| //iterate. No need to check filler, it's length is longer than range's | |
| for (; !range.empty; range.popFront(), filler.popFront()) | |
| { | |
| range.front = filler.front; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| //Most basic case. | |
| auto bck = filler.save; | |
| for (; !range.empty; range.popFront(), filler.popFront()) | |
| { | |
| if (filler.empty) filler = bck.save; | |
| range.front = filler.front; | |
| } | |
| } | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| int[] a = [ 1, 2, 3, 4, 5 ]; | |
| int[] b = [ 8, 9 ]; | |
| fill(a, b); | |
| assert(a == [ 8, 9, 8, 9, 8 ]); | |
| } | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| import std.internal.test.dummyrange; | |
| int[] a = [ 1, 2, 3, 4, 5 ]; | |
| int[] b = [1, 2]; | |
| fill(a, b); | |
| assert(a == [ 1, 2, 1, 2, 1 ]); | |
| // fill should accept InputRange | |
| alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input); | |
| InputRange range; | |
| fill(range,[1,2]); | |
| foreach (i,value;range.arr) | |
| assert(value == (i%2 == 0?1:2)); | |
| //test with a input being a "reference forward" range | |
| fill(a, new ReferenceForwardRange!int([8, 9])); | |
| assert(a == [8, 9, 8, 9, 8]); | |
| //test with a input being an "infinite input" range | |
| fill(a, new ReferenceInfiniteInputRange!int()); | |
| assert(a == [0, 1, 2, 3, 4]); | |
| //empty filler test | |
| assertThrown(fill(a, a[$..$])); | |
| } | |
| /** | |
| Initializes all elements of `range` with their `.init` value. | |
| Assumes that the elements of the range are uninitialized. | |
| This function is unavailable if `T` is a `struct` and `T.this()` is annotated | |
| with `@disable`. | |
| Params: | |
| range = An | |
| $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| that exposes references to its elements and has assignable | |
| elements | |
| See_Also: | |
| $(LREF fill) | |
| $(LREF uninitializedFill) | |
| */ | |
| void initializeAll(Range)(Range range) | |
| if (isInputRange!Range && hasLvalueElements!Range && hasAssignableElements!Range | |
| && __traits(compiles, { static ElementType!Range _; })) | |
| { | |
| import core.stdc.string : memset, memcpy; | |
| import std.traits : hasElaborateAssign, isDynamicArray; | |
| alias T = ElementType!Range; | |
| static if (hasElaborateAssign!T) | |
| { | |
| import std.algorithm.internal : addressOf; | |
| //Elaborate opAssign. Must go the memcpy/memset road. | |
| static if (!__traits(isZeroInit, T)) | |
| { | |
| for ( ; !range.empty ; range.popFront() ) | |
| { | |
| import core.internal.lifetime : emplaceInitializer; | |
| emplaceInitializer(range.front); | |
| } | |
| } | |
| else | |
| static if (isDynamicArray!Range) | |
| memset(range.ptr, 0, range.length * T.sizeof); | |
| else | |
| for ( ; !range.empty ; range.popFront() ) | |
| memset(addressOf(range.front), 0, T.sizeof); | |
| } | |
| else | |
| fill(range, T.init); | |
| } | |
| /// ditto | |
| void initializeAll(Range)(Range range) | |
| if (is(Range == char[]) || is(Range == wchar[])) | |
| { | |
| alias T = ElementEncodingType!Range; | |
| range[] = T.init; | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import core.stdc.stdlib : malloc, free; | |
| struct S | |
| { | |
| int a = 10; | |
| } | |
| auto s = (cast(S*) malloc(5 * S.sizeof))[0 .. 5]; | |
| initializeAll(s); | |
| assert(s == [S(10), S(10), S(10), S(10), S(10)]); | |
| scope(exit) free(s.ptr); | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.iteration : filter; | |
| import std.meta : AliasSeq; | |
| import std.traits : hasElaborateAssign; | |
| //Test strings: | |
| //Must work on narrow strings. | |
| //Must reject const | |
| char[3] a = void; | |
| a[].initializeAll(); | |
| assert(a[] == [char.init, char.init, char.init]); | |
| string s; | |
| assert(!__traits(compiles, s.initializeAll())); | |
| assert(!__traits(compiles, s.initializeAll())); | |
| assert(s.empty); | |
| //Note: Cannot call uninitializedFill on narrow strings | |
| enum e {e1, e2} | |
| e[3] b1 = void; | |
| b1[].initializeAll(); | |
| assert(b1[] == [e.e1, e.e1, e.e1]); | |
| e[3] b2 = void; | |
| b2[].uninitializedFill(e.e2); | |
| assert(b2[] == [e.e2, e.e2, e.e2]); | |
| static struct S1 | |
| { | |
| int i; | |
| } | |
| static struct S2 | |
| { | |
| int i = 1; | |
| } | |
| static struct S3 | |
| { | |
| int i; | |
| this(this){} | |
| } | |
| static struct S4 | |
| { | |
| int i = 1; | |
| this(this){} | |
| } | |
| static assert(!hasElaborateAssign!S1); | |
| static assert(!hasElaborateAssign!S2); | |
| static assert( hasElaborateAssign!S3); | |
| static assert( hasElaborateAssign!S4); | |
| assert(!typeid(S1).initializer().ptr); | |
| assert( typeid(S2).initializer().ptr); | |
| assert(!typeid(S3).initializer().ptr); | |
| assert( typeid(S4).initializer().ptr); | |
| static foreach (S; AliasSeq!(S1, S2, S3, S4)) | |
| { | |
| //initializeAll | |
| { | |
| //Array | |
| S[3] ss1 = void; | |
| ss1[].initializeAll(); | |
| assert(ss1[] == [S.init, S.init, S.init]); | |
| //Not array | |
| S[3] ss2 = void; | |
| auto sf = ss2[].filter!"true"(); | |
| sf.initializeAll(); | |
| assert(ss2[] == [S.init, S.init, S.init]); | |
| } | |
| //uninitializedFill | |
| { | |
| //Array | |
| S[3] ss1 = void; | |
| ss1[].uninitializedFill(S(2)); | |
| assert(ss1[] == [S(2), S(2), S(2)]); | |
| //Not array | |
| S[3] ss2 = void; | |
| auto sf = ss2[].filter!"true"(); | |
| sf.uninitializedFill(S(2)); | |
| assert(ss2[] == [S(2), S(2), S(2)]); | |
| } | |
| } | |
| } | |
| // test that initializeAll works for arrays of static arrays of structs with | |
| // elaborate assigns. | |
| @system unittest | |
| { | |
| struct Int { | |
| ~this() {} | |
| int x = 3; | |
| } | |
| Int[2] xs = [Int(1), Int(2)]; | |
| struct R { | |
| bool done; | |
| bool empty() { return done; } | |
| ref Int[2] front() { return xs; } | |
| void popFront() { done = true; } | |
| } | |
| initializeAll(R()); | |
| assert(xs[0].x == 3); | |
| assert(xs[1].x == 3); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=22105 | |
| @system unittest | |
| { | |
| struct NoDefaultCtor | |
| { | |
| @disable this(); | |
| } | |
| NoDefaultCtor[1] array = void; | |
| static assert(!__traits(compiles, array[].initializeAll)); | |
| } | |
| // move | |
| /** | |
| Moves `source` into `target`, via a destructive copy when necessary. | |
| If `T` is a struct with a destructor or postblit defined, source is reset | |
| to its `.init` value after it is moved into target, otherwise it is | |
| left unchanged. | |
| Preconditions: | |
| If source has internal pointers that point to itself and doesn't define | |
| opPostMove, it cannot be moved, and will trigger an assertion failure. | |
| Params: | |
| source = Data to copy. | |
| target = Where to copy into. The destructor, if any, is invoked before the | |
| copy is performed. | |
| */ | |
| void move(T)(ref T source, ref T target) | |
| if (__traits(compiles, target = T.init)) | |
| { | |
| moveImpl(target, source); | |
| } | |
| /// ditto | |
| template move(T) | |
| if (!__traits(compiles, imported!"std.traits".lvalueOf!T = T.init)) | |
| { | |
| /// | |
| deprecated("Can't move into `target` as `" ~ T.stringof ~ "` can't be assigned") | |
| void move(ref T source, ref T target) => moveImpl(target, source); | |
| } | |
| /// For non-struct types, `move` just performs `target = source`: | |
| @safe unittest | |
| { | |
| Object obj1 = new Object; | |
| Object obj2 = obj1; | |
| Object obj3; | |
| move(obj2, obj3); | |
| assert(obj3 is obj1); | |
| // obj2 unchanged | |
| assert(obj2 is obj1); | |
| } | |
| /// | |
| pure nothrow @safe @nogc unittest | |
| { | |
| // Structs without destructors are simply copied | |
| struct S1 | |
| { | |
| int a = 1; | |
| int b = 2; | |
| } | |
| S1 s11 = { 10, 11 }; | |
| S1 s12; | |
| move(s11, s12); | |
| assert(s12 == S1(10, 11)); | |
| assert(s11 == s12); | |
| // But structs with destructors or postblits are reset to their .init value | |
| // after copying to the target. | |
| struct S2 | |
| { | |
| int a = 1; | |
| int b = 2; | |
| ~this() pure nothrow @safe @nogc { } | |
| } | |
| S2 s21 = { 3, 4 }; | |
| S2 s22; | |
| move(s21, s22); | |
| assert(s21 == S2(1, 2)); | |
| assert(s22 == S2(3, 4)); | |
| } | |
| @safe unittest | |
| { | |
| import std.exception : assertCTFEable; | |
| import std.traits; | |
| assertCTFEable!((){ | |
| Object obj1 = new Object; | |
| Object obj2 = obj1; | |
| Object obj3; | |
| move(obj2, obj3); | |
| assert(obj3 is obj1); | |
| static struct S1 { int a = 1, b = 2; } | |
| S1 s11 = { 10, 11 }; | |
| S1 s12; | |
| move(s11, s12); | |
| assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); | |
| static struct S2 { int a = 1; int * b; } | |
| S2 s21 = { 10, null }; | |
| s21.b = new int; | |
| S2 s22; | |
| move(s21, s22); | |
| assert(s21 == s22); | |
| }); | |
| // https://issues.dlang.org/show_bug.cgi?id=5661 test(1) | |
| static struct S3 | |
| { | |
| static struct X { int n = 0; ~this(){n = 0;} } | |
| X x; | |
| } | |
| static assert(hasElaborateDestructor!S3); | |
| S3 s31, s32; | |
| s31.x.n = 1; | |
| move(s31, s32); | |
| assert(s31.x.n == 0); | |
| assert(s32.x.n == 1); | |
| // https://issues.dlang.org/show_bug.cgi?id=5661 test(2) | |
| static struct S4 | |
| { | |
| static struct X { int n = 0; this(this){n = 0;} } | |
| X x; | |
| } | |
| static assert(hasElaborateCopyConstructor!S4); | |
| S4 s41, s42; | |
| s41.x.n = 1; | |
| move(s41, s42); | |
| assert(s41.x.n == 0); | |
| assert(s42.x.n == 1); | |
| // https://issues.dlang.org/show_bug.cgi?id=13990 test | |
| class S5; | |
| S5 s51; | |
| S5 s52 = s51; | |
| S5 s53; | |
| move(s52, s53); | |
| assert(s53 is s51); | |
| } | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| immutable int i; | |
| ~this() @safe {} | |
| } | |
| alias ol = __traits(getOverloads, std.algorithm.mutation, "move", true)[1]; | |
| static assert(__traits(isDeprecated, ol!S)); | |
| // uncomment after deprecation | |
| //static assert(!__traits(compiles, { S a, b; move(a, b); })); | |
| } | |
| /// Ditto | |
| T move(T)(return scope ref T source) | |
| { | |
| return moveImpl(source); | |
| } | |
| /// Non-copyable structs can still be moved: | |
| pure nothrow @safe @nogc unittest | |
| { | |
| struct S | |
| { | |
| int a = 1; | |
| @disable this(this); | |
| ~this() pure nothrow @safe @nogc {} | |
| } | |
| S s1; | |
| s1.a = 2; | |
| S s2 = move(s1); | |
| assert(s1.a == 1); | |
| assert(s2.a == 2); | |
| } | |
| /// `opPostMove` will be called if defined: | |
| pure nothrow @safe @nogc unittest | |
| { | |
| struct S | |
| { | |
| int a; | |
| void opPostMove(const ref S old) | |
| { | |
| assert(a == old.a); | |
| a++; | |
| } | |
| } | |
| S s1; | |
| s1.a = 41; | |
| S s2 = move(s1); | |
| assert(s2.a == 42); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=20869 | |
| // `move` should propagate the attributes of `opPostMove` | |
| @system unittest | |
| { | |
| static struct S | |
| { | |
| void opPostMove(const ref S old) nothrow @system | |
| { | |
| __gshared int i; | |
| new int(i++); // Force @gc impure @system | |
| } | |
| } | |
| alias T = void function() @system nothrow; | |
| static assert(is(typeof({ S s; move(s); }) == T)); | |
| static assert(is(typeof({ S s; move(s, s); }) == T)); | |
| } | |
| private void moveImpl(T)(ref scope T target, ref return scope T source) | |
| { | |
| import std.traits : hasElaborateDestructor; | |
| static if (is(T == struct)) | |
| { | |
| // Unsafe when compiling without -dip1000 | |
| if ((() @trusted => &source == &target)()) return; | |
| // Destroy target before overwriting it | |
| static if (hasElaborateDestructor!T) target.__xdtor(); | |
| } | |
| // move and emplace source into target | |
| moveEmplaceImpl(target, source); | |
| } | |
| private T moveImpl(T)(ref return scope T source) | |
| { | |
| // Properly infer safety from moveEmplaceImpl as the implementation below | |
| // might void-initialize pointers in result and hence needs to be @trusted | |
| if (false) moveEmplaceImpl(source, source); | |
| return trustedMoveImpl(source); | |
| } | |
| private T trustedMoveImpl(T)(ref return scope T source) @trusted | |
| { | |
| T result = void; | |
| moveEmplaceImpl(result, source); | |
| return result; | |
| } | |
| @safe unittest | |
| { | |
| import std.exception : assertCTFEable; | |
| import std.traits; | |
| assertCTFEable!((){ | |
| Object obj1 = new Object; | |
| Object obj2 = obj1; | |
| Object obj3 = move(obj2); | |
| assert(obj3 is obj1); | |
| static struct S1 { int a = 1, b = 2; } | |
| S1 s11 = { 10, 11 }; | |
| S1 s12 = move(s11); | |
| assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11); | |
| static struct S2 { int a = 1; int * b; } | |
| S2 s21 = { 10, null }; | |
| s21.b = new int; | |
| S2 s22 = move(s21); | |
| assert(s21 == s22); | |
| }); | |
| // https://issues.dlang.org/show_bug.cgi?id=5661 test(1) | |
| static struct S3 | |
| { | |
| static struct X { int n = 0; ~this(){n = 0;} } | |
| X x; | |
| } | |
| static assert(hasElaborateDestructor!S3); | |
| S3 s31; | |
| s31.x.n = 1; | |
| S3 s32 = move(s31); | |
| assert(s31.x.n == 0); | |
| assert(s32.x.n == 1); | |
| // https://issues.dlang.org/show_bug.cgi?id=5661 test(2) | |
| static struct S4 | |
| { | |
| static struct X { int n = 0; this(this){n = 0;} } | |
| X x; | |
| } | |
| static assert(hasElaborateCopyConstructor!S4); | |
| S4 s41; | |
| s41.x.n = 1; | |
| S4 s42 = move(s41); | |
| assert(s41.x.n == 0); | |
| assert(s42.x.n == 1); | |
| // https://issues.dlang.org/show_bug.cgi?id=13990 test | |
| class S5; | |
| S5 s51; | |
| S5 s52 = s51; | |
| S5 s53; | |
| s53 = move(s52); | |
| assert(s53 is s51); | |
| } | |
| @system unittest | |
| { | |
| static struct S { int n = 0; ~this() @system { n = 0; } } | |
| S a, b; | |
| static assert(!__traits(compiles, () @safe { move(a, b); })); | |
| static assert(!__traits(compiles, () @safe { move(a); })); | |
| a.n = 1; | |
| () @trusted { move(a, b); }(); | |
| assert(a.n == 0); | |
| a.n = 1; | |
| () @trusted { move(a); }(); | |
| assert(a.n == 0); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=6217 | |
| @safe unittest | |
| { | |
| import std.algorithm.iteration : map; | |
| auto x = map!"a"([1,2,3]); | |
| x = move(x); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=8055 | |
| @safe unittest | |
| { | |
| static struct S | |
| { | |
| int x; | |
| ~this() | |
| { | |
| assert(x == 0); | |
| } | |
| } | |
| S foo(S s) | |
| { | |
| return move(s); | |
| } | |
| S a; | |
| a.x = 0; | |
| auto b = foo(a); | |
| assert(b.x == 0); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=8057 | |
| @system unittest | |
| { | |
| int n = 10; | |
| struct S | |
| { | |
| int x; | |
| ~this() | |
| { | |
| // Access to enclosing scope | |
| assert(n == 10); | |
| } | |
| } | |
| S foo(S s) | |
| { | |
| // Move nested struct | |
| return move(s); | |
| } | |
| S a; | |
| a.x = 1; | |
| auto b = foo(a); | |
| assert(b.x == 1); | |
| // Regression https://issues.dlang.org/show_bug.cgi?id=8171 | |
| static struct Array(T) | |
| { | |
| // nested struct has no member | |
| struct Payload | |
| { | |
| ~this() {} | |
| } | |
| } | |
| Array!int.Payload x = void; | |
| move(x); | |
| move(x, x); | |
| } | |
| private void moveEmplaceImpl(T)(ref scope T target, ref return scope T source) | |
| { | |
| import core.stdc.string : memcpy, memset; | |
| import std.traits : hasAliasing, hasElaborateAssign, | |
| hasElaborateCopyConstructor, hasElaborateDestructor, | |
| hasElaborateMove, | |
| isAssignable, isStaticArray; | |
| static if (!is(T == class) && hasAliasing!T) if (!__ctfe) | |
| { | |
| import std.exception : doesPointTo; | |
| assert(!(doesPointTo(source, source) && !hasElaborateMove!T), | |
| "Cannot move object of type " ~ T.stringof ~ " with internal pointer unless `opPostMove` is defined."); | |
| } | |
| static if (is(T == struct)) | |
| { | |
| // Unsafe when compiling without -dip1000 | |
| assert((() @trusted => &source !is &target)(), "source and target must not be identical"); | |
| static if (hasElaborateAssign!T || !isAssignable!T) | |
| () @trusted { memcpy(&target, &source, T.sizeof); }(); | |
| else | |
| target = source; | |
| static if (hasElaborateMove!T) | |
| __move_post_blt(target, source); | |
| // If the source defines a destructor or a postblit hook, we must obliterate the | |
| // object in order to avoid double freeing and undue aliasing | |
| static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) | |
| { | |
| // If T is nested struct, keep original context pointer | |
| static if (__traits(isNested, T)) | |
| enum sz = T.sizeof - (void*).sizeof; | |
| else | |
| enum sz = T.sizeof; | |
| static if (__traits(isZeroInit, T)) | |
| () @trusted { memset(&source, 0, sz); }(); | |
| else | |
| () @trusted { memcpy(&source, __traits(initSymbol, T).ptr, sz); }(); | |
| } | |
| } | |
| else static if (isStaticArray!T) | |
| { | |
| for (size_t i = 0; i < source.length; ++i) | |
| move(source[i], target[i]); | |
| } | |
| else | |
| { | |
| // Primitive data (including pointers and arrays) or class - | |
| // assignment works great | |
| target = source; | |
| } | |
| } | |
| /** | |
| * Similar to $(LREF move) but assumes `target` is uninitialized. This | |
| * is more efficient because `source` can be blitted over `target` | |
| * without destroying or initializing it first. | |
| * | |
| * Params: | |
| * source = value to be moved into target | |
| * target = uninitialized value to be filled by source | |
| */ | |
| void moveEmplace(T)(ref T source, ref T target) pure @system | |
| { | |
| moveEmplaceImpl(target, source); | |
| } | |
| /// | |
| pure nothrow @nogc @system unittest | |
| { | |
| static struct Foo | |
| { | |
| pure nothrow @nogc: | |
| this(int* ptr) { _ptr = ptr; } | |
| ~this() { if (_ptr) ++*_ptr; } | |
| int* _ptr; | |
| } | |
| int val; | |
| Foo foo1 = void; // uninitialized | |
| auto foo2 = Foo(&val); // initialized | |
| assert(foo2._ptr is &val); | |
| // Using `move(foo2, foo1)` would have an undefined effect because it would destroy | |
| // the uninitialized foo1. | |
| // moveEmplace directly overwrites foo1 without destroying or initializing it first. | |
| moveEmplace(foo2, foo1); | |
| assert(foo1._ptr is &val); | |
| assert(foo2._ptr is null); | |
| assert(val == 0); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=18913 | |
| @safe unittest | |
| { | |
| static struct NoCopy | |
| { | |
| int payload; | |
| ~this() { } | |
| @disable this(this); | |
| } | |
| static void f(NoCopy[2]) { } | |
| NoCopy[2] ncarray = [ NoCopy(1), NoCopy(2) ]; | |
| static assert(!__traits(compiles, f(ncarray))); | |
| f(move(ncarray)); | |
| } | |
| // moveAll | |
| /** | |
| Calls `move(a, b)` for each element `a` in `src` and the corresponding | |
| element `b` in `tgt`, in increasing order. | |
| Preconditions: | |
| `walkLength(src) <= walkLength(tgt)`. | |
| This precondition will be asserted. If you cannot ensure there is enough room in | |
| `tgt` to accommodate all of `src` use $(LREF moveSome) instead. | |
| Params: | |
| src = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with | |
| movable elements. | |
| tgt = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with | |
| elements that elements from `src` can be moved into. | |
| Returns: The leftover portion of `tgt` after all elements from `src` have | |
| been moved. | |
| */ | |
| InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) | |
| if (isInputRange!InputRange1 && isInputRange!InputRange2 | |
| && is(typeof(move(src.front, tgt.front)))) | |
| { | |
| return moveAllImpl!move(src, tgt); | |
| } | |
| /// | |
| pure nothrow @safe @nogc unittest | |
| { | |
| int[3] a = [ 1, 2, 3 ]; | |
| int[5] b; | |
| assert(moveAll(a[], b[]) is b[3 .. $]); | |
| assert(a[] == b[0 .. 3]); | |
| int[3] cmp = [ 1, 2, 3 ]; | |
| assert(a[] == cmp[]); | |
| } | |
| /** | |
| * Similar to $(LREF moveAll) but assumes all elements in `tgt` are | |
| * uninitialized. Uses $(LREF moveEmplace) to move elements from | |
| * `src` over elements from `tgt`. | |
| */ | |
| InputRange2 moveEmplaceAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) @system | |
| if (isInputRange!InputRange1 && isInputRange!InputRange2 | |
| && is(typeof(moveEmplace(src.front, tgt.front)))) | |
| { | |
| return moveAllImpl!moveEmplace(src, tgt); | |
| } | |
| /// | |
| pure nothrow @nogc @system unittest | |
| { | |
| static struct Foo | |
| { | |
| ~this() pure nothrow @nogc { if (_ptr) ++*_ptr; } | |
| int* _ptr; | |
| } | |
| int[3] refs = [0, 1, 2]; | |
| Foo[3] src = [Foo(&refs[0]), Foo(&refs[1]), Foo(&refs[2])]; | |
| Foo[5] dst = void; | |
| auto tail = moveEmplaceAll(src[], dst[]); // move 3 value from src over dst | |
| assert(tail.length == 2); // returns remaining uninitialized values | |
| initializeAll(tail); | |
| import std.algorithm.searching : all; | |
| assert(src[].all!(e => e._ptr is null)); | |
| assert(dst[0 .. 3].all!(e => e._ptr !is null)); | |
| } | |
| @system unittest | |
| { | |
| struct InputRange | |
| { | |
| ref int front() { return data[0]; } | |
| void popFront() { data.popFront; } | |
| bool empty() { return data.empty; } | |
| int[] data; | |
| } | |
| auto a = InputRange([ 1, 2, 3 ]); | |
| auto b = InputRange(new int[5]); | |
| moveAll(a, b); | |
| assert(a.data == b.data[0 .. 3]); | |
| assert(a.data == [ 1, 2, 3 ]); | |
| } | |
| private InputRange2 moveAllImpl(alias moveOp, InputRange1, InputRange2)( | |
| ref InputRange1 src, ref InputRange2 tgt) | |
| { | |
| import std.exception : enforce; | |
| static if (isRandomAccessRange!InputRange1 && hasLength!InputRange1 && hasLength!InputRange2 | |
| && hasSlicing!InputRange2 && isRandomAccessRange!InputRange2) | |
| { | |
| auto toMove = src.length; | |
| assert(toMove <= tgt.length, "Source buffer needs to be smaller or equal to the target buffer."); | |
| foreach (idx; 0 .. toMove) | |
| moveOp(src[idx], tgt[idx]); | |
| return tgt[toMove .. tgt.length]; | |
| } | |
| else | |
| { | |
| for (; !src.empty; src.popFront(), tgt.popFront()) | |
| { | |
| assert(!tgt.empty, "Source buffer needs to be smaller or equal to the target buffer."); | |
| moveOp(src.front, tgt.front); | |
| } | |
| return tgt; | |
| } | |
| } | |
| // moveSome | |
| /** | |
| Calls `move(a, b)` for each element `a` in `src` and the corresponding | |
| element `b` in `tgt`, in increasing order, stopping when either range has been | |
| exhausted. | |
| Params: | |
| src = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with | |
| movable elements. | |
| tgt = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with | |
| elements that elements from `src` can be moved into. | |
| Returns: The leftover portions of the two ranges after one or the other of the | |
| ranges have been exhausted. | |
| */ | |
| Tuple!(InputRange1, InputRange2) moveSome(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) | |
| if (isInputRange!InputRange1 && isInputRange!InputRange2 | |
| && is(typeof(move(src.front, tgt.front)))) | |
| { | |
| return moveSomeImpl!move(src, tgt); | |
| } | |
| /// | |
| pure nothrow @safe @nogc unittest | |
| { | |
| int[5] a = [ 1, 2, 3, 4, 5 ]; | |
| int[3] b; | |
| assert(moveSome(a[], b[])[0] is a[3 .. $]); | |
| assert(a[0 .. 3] == b); | |
| assert(a == [ 1, 2, 3, 4, 5 ]); | |
| } | |
| /** | |
| * Same as $(LREF moveSome) but assumes all elements in `tgt` are | |
| * uninitialized. Uses $(LREF moveEmplace) to move elements from | |
| * `src` over elements from `tgt`. | |
| */ | |
| Tuple!(InputRange1, InputRange2) moveEmplaceSome(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) @system | |
| if (isInputRange!InputRange1 && isInputRange!InputRange2 | |
| && is(typeof(move(src.front, tgt.front)))) | |
| { | |
| return moveSomeImpl!moveEmplace(src, tgt); | |
| } | |
| /// | |
| pure nothrow @nogc @system unittest | |
| { | |
| static struct Foo | |
| { | |
| ~this() pure nothrow @nogc { if (_ptr) ++*_ptr; } | |
| int* _ptr; | |
| } | |
| int[4] refs = [0, 1, 2, 3]; | |
| Foo[4] src = [Foo(&refs[0]), Foo(&refs[1]), Foo(&refs[2]), Foo(&refs[3])]; | |
| Foo[3] dst = void; | |
| auto res = moveEmplaceSome(src[], dst[]); | |
| assert(res.length == 2); | |
| import std.algorithm.searching : all; | |
| assert(src[0 .. 3].all!(e => e._ptr is null)); | |
| assert(src[3]._ptr !is null); | |
| assert(dst[].all!(e => e._ptr !is null)); | |
| } | |
| private Tuple!(InputRange1, InputRange2) moveSomeImpl(alias moveOp, InputRange1, InputRange2)( | |
| ref InputRange1 src, ref InputRange2 tgt) | |
| { | |
| for (; !src.empty && !tgt.empty; src.popFront(), tgt.popFront()) | |
| moveOp(src.front, tgt.front); | |
| return tuple(src, tgt); | |
| } | |
| // SwapStrategy | |
| /** | |
| Defines the swapping strategy for algorithms that need to swap | |
| elements in a range (such as partition and sort). The strategy | |
| concerns the swapping of elements that are not the core concern of the | |
| algorithm. For example, consider an algorithm that sorts $(D [ "abc", | |
| "b", "aBc" ]) according to `toUpper(a) < toUpper(b)`. That | |
| algorithm might choose to swap the two equivalent strings `"abc"` | |
| and `"aBc"`. That does not affect the sorting since both | |
| `["abc", "aBc", "b" ]` and `[ "aBc", "abc", "b" ]` are valid | |
| outcomes. | |
| Some situations require that the algorithm must NOT ever change the | |
| relative ordering of equivalent elements (in the example above, only | |
| `[ "abc", "aBc", "b" ]` would be the correct result). Such | |
| algorithms are called $(B stable). If the ordering algorithm may swap | |
| equivalent elements discretionarily, the ordering is called $(B | |
| unstable). | |
| Yet another class of algorithms may choose an intermediate tradeoff by | |
| being stable only on a well-defined subrange of the range. There is no | |
| established terminology for such behavior; this library calls it $(B | |
| semistable). | |
| Generally, the `stable` ordering strategy may be more costly in | |
| time and/or space than the other two because it imposes additional | |
| constraints. Similarly, `semistable` may be costlier than $(D | |
| unstable). As (semi-)stability is not needed very often, the ordering | |
| algorithms in this module parameterized by `SwapStrategy` all | |
| choose `SwapStrategy.unstable` as the default. | |
| */ | |
| enum SwapStrategy | |
| { | |
| /** | |
| Allows freely swapping of elements as long as the output | |
| satisfies the algorithm's requirements. | |
| */ | |
| unstable, | |
| /** | |
| In algorithms partitioning ranges in two, preserve relative | |
| ordering of elements only to the left of the partition point. | |
| */ | |
| semistable, | |
| /** | |
| Preserve the relative ordering of elements to the largest | |
| extent allowed by the algorithm's requirements. | |
| */ | |
| stable, | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| int[] a = [0, 1, 2, 3]; | |
| assert(remove!(SwapStrategy.stable)(a, 1) == [0, 2, 3]); | |
| a = [0, 1, 2, 3]; | |
| assert(remove!(SwapStrategy.unstable)(a, 1) == [0, 3, 2]); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.algorithm.sorting : partition; | |
| // Put stuff greater than 3 on the left | |
| auto arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | |
| assert(partition!(a => a > 3, SwapStrategy.stable)(arr) == [1, 2, 3]); | |
| assert(arr == [4, 5, 6, 7, 8, 9, 10, 1, 2, 3]); | |
| arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | |
| assert(partition!(a => a > 3, SwapStrategy.semistable)(arr) == [2, 3, 1]); | |
| assert(arr == [4, 5, 6, 7, 8, 9, 10, 2, 3, 1]); | |
| arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | |
| assert(partition!(a => a > 3, SwapStrategy.unstable)(arr) == [3, 2, 1]); | |
| assert(arr == [10, 9, 8, 4, 5, 6, 7, 3, 2, 1]); | |
| } | |
| private template isValidIntegralTuple(T) | |
| { | |
| import std.traits : isIntegral; | |
| import std.typecons : isTuple; | |
| static if (isTuple!T) | |
| { | |
| enum isValidIntegralTuple = T.length == 2 && | |
| isIntegral!(typeof(T.init[0])) && isIntegral!(typeof(T.init[0])); | |
| } | |
| else | |
| { | |
| enum isValidIntegralTuple = isIntegral!T; | |
| } | |
| } | |
| /** | |
| Eliminates elements at given offsets from `range` and returns the shortened | |
| range. | |
| For example, here is how to remove a single element from an array: | |
| $(RUNNABLE_EXAMPLE | |
| ---- | |
| import std.algorithm.mutation; | |
| string[] a = [ "a", "b", "c", "d" ]; | |
| a = a.remove(1); // remove element at offset 1 | |
| assert(a == [ "a", "c", "d"]); | |
| ---- | |
| ) | |
| Note that `remove` does not change the length of the original range directly; | |
| instead, it returns the shortened range. If its return value is not assigned to | |
| the original range, the original range will retain its original length, though | |
| its contents will have changed: | |
| $(RUNNABLE_EXAMPLE | |
| ---- | |
| import std.algorithm.mutation; | |
| int[] a = [ 3, 5, 7, 8 ]; | |
| assert(remove(a, 1) == [ 3, 7, 8 ]); | |
| assert(a == [ 3, 7, 8, 8 ]); | |
| ---- | |
| ) | |
| The element at offset `1` has been removed and the rest of the elements have | |
| shifted up to fill its place, however, the original array remains of the same | |
| length. This is because all functions in `std.algorithm` only change $(I | |
| content), not $(I topology). The value `8` is repeated because $(LREF move) was | |
| invoked to rearrange elements, and on integers `move` simply copies the source | |
| to the destination. To replace `a` with the effect of the removal, simply | |
| assign the slice returned by `remove` to it, as shown in the first example. | |
| $(H3 $(LNAME2 remove-multiple, Removing multiple elements)) | |
| Multiple indices can be passed into `remove`. In that case, | |
| elements at the respective indices are all removed. The indices must | |
| be passed in increasing order, otherwise an exception occurs. | |
| $(RUNNABLE_EXAMPLE | |
| ---- | |
| import std.algorithm.mutation; | |
| int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; | |
| assert(remove(a, 1, 3, 5) == | |
| [ 0, 2, 4, 6, 7, 8, 9, 10 ]); | |
| ---- | |
| ) | |
| Note that all indices refer to slots in the $(I original) array, not | |
| in the array as it is being progressively shortened. | |
| Tuples of two integral offsets can be supplied to remove a range of indices: | |
| $(RUNNABLE_EXAMPLE | |
| ---- | |
| import std.algorithm.mutation, std.typecons; | |
| int[] a = [ 3, 4, 5, 6, 7]; | |
| // remove elements at indices 1 and 2 | |
| assert(remove(a, tuple(1, 3)) == [ 3, 6, 7 ]); | |
| ---- | |
| ) | |
| The tuple passes in a range closed to the left and open to | |
| the right (consistent with built-in slices), e.g. `tuple(1, 3)` | |
| means indices `1` and `2` but not `3`. | |
| Finally, any combination of integral offsets and tuples composed of two integral | |
| offsets can be passed in: | |
| $(RUNNABLE_EXAMPLE | |
| ---- | |
| import std.algorithm.mutation, std.typecons; | |
| int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; | |
| a = remove(a, 1, tuple(3, 5), 9); | |
| assert(a == [ 0, 2, 5, 6, 7, 8, 10 ]); | |
| ---- | |
| ) | |
| In this case, the slots at positions 1, 3, 4, and 9 are removed from | |
| the array. | |
| $(H3 $(LNAME2 remove-moving, Moving strategy)) | |
| If the need is to remove some elements in the range but the order of | |
| the remaining elements does not have to be preserved, you may want to | |
| pass `SwapStrategy.unstable` to `remove`. | |
| $(RUNNABLE_EXAMPLE | |
| ---- | |
| import std.algorithm.mutation; | |
| int[] a = [ 0, 1, 2, 3 ]; | |
| assert(remove!(SwapStrategy.unstable)(a, 1) == [ 0, 3, 2 ]); | |
| ---- | |
| ) | |
| In the case above, the element at slot `1` is removed, but replaced | |
| with the last element of the range. Taking advantage of the relaxation | |
| of the stability requirement, `remove` moved elements from the end | |
| of the array over the slots to be removed. This way there is less data | |
| movement to be done which improves the execution time of the function. | |
| `remove` works on bidirectional ranges that have assignable | |
| lvalue elements. The moving strategy is (listed from fastest to slowest): | |
| $(UL | |
| $(LI If $(D s == SwapStrategy.unstable && isRandomAccessRange!Range && | |
| hasLength!Range && hasLvalueElements!Range), then elements are moved from the | |
| end of the range into the slots to be filled. In this case, the absolute | |
| minimum of moves is performed.) | |
| $(LI Otherwise, if $(D s == | |
| SwapStrategy.unstable && isBidirectionalRange!Range && hasLength!Range | |
| && hasLvalueElements!Range), then elements are still moved from the | |
| end of the range, but time is spent on advancing between slots by repeated | |
| calls to `range.popFront`.) | |
| $(LI Otherwise, elements are moved | |
| incrementally towards the front of `range`; a given element is never | |
| moved several times, but more elements are moved than in the previous | |
| cases.) | |
| ) | |
| Params: | |
| s = a SwapStrategy to determine if the original order needs to be preserved | |
| range = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) | |
| with a length member | |
| offset = which element(s) to remove | |
| Returns: | |
| A range containing elements of `range` with 1 or more elements removed. | |
| */ | |
| Range remove | |
| (SwapStrategy s = SwapStrategy.stable, Range, Offset ...) | |
| (Range range, Offset offset) | |
| if (Offset.length >= 1 && allSatisfy!(isValidIntegralTuple, Offset)) | |
| { | |
| // Activate this check when the deprecation of non-integral tuples is over | |
| //import std.traits : isIntegral; | |
| //import std.typecons : isTuple; | |
| //static foreach (T; Offset) | |
| //{ | |
| //static if (isTuple!T) | |
| //{ | |
| //static assert(T.length == 2 && | |
| //isIntegral!(typeof(T.init[0])) && isIntegral!(typeof(T.init[0])), | |
| //"Each offset must be an integral or a tuple of two integrals." ~ | |
| //"Use `arr.remove(pos1, pos2)` or `arr.remove(tuple(start, begin))`"); | |
| //} | |
| //else | |
| //{ | |
| //static assert(isIntegral!T, | |
| //"Each offset must be an integral or a tuple of two integrals." ~ | |
| //"Use `arr.remove(pos1, pos2)` or `arr.remove(tuple(start, begin))`"); | |
| //} | |
| //} | |
| return removeImpl!s(range, offset); | |
| } | |
| /// ditto | |
| deprecated("Use of non-integral tuples is deprecated. Use remove(tuple(start, end).") | |
| Range remove | |
| (SwapStrategy s = SwapStrategy.stable, Range, Offset ...) | |
| (Range range, Offset offset) | |
| if (Offset.length >= 1 && !allSatisfy!(isValidIntegralTuple, Offset)) | |
| { | |
| return removeImpl!s(range, offset); | |
| } | |
| /// | |
| @safe pure unittest | |
| { | |
| import std.typecons : tuple; | |
| auto a = [ 0, 1, 2, 3, 4, 5 ]; | |
| assert(remove!(SwapStrategy.stable)(a, 1) == [ 0, 2, 3, 4, 5 ]); | |
| a = [ 0, 1, 2, 3, 4, 5 ]; | |
| assert(remove!(SwapStrategy.stable)(a, 1, 3) == [ 0, 2, 4, 5] ); | |
| a = [ 0, 1, 2, 3, 4, 5 ]; | |
| assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 6)) == [ 0, 2 ]); | |
| a = [ 0, 1, 2, 3, 4, 5 ]; | |
| assert(remove!(SwapStrategy.unstable)(a, 1) == [0, 5, 2, 3, 4]); | |
| a = [ 0, 1, 2, 3, 4, 5 ]; | |
| assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4)) == [0, 5, 4]); | |
| } | |
| /// | |
| @safe pure unittest | |
| { | |
| import std.typecons : tuple; | |
| // Delete an index | |
| assert([4, 5, 6].remove(1) == [4, 6]); | |
| // Delete multiple indices | |
| assert([4, 5, 6, 7, 8].remove(1, 3) == [4, 6, 8]); | |
| // Use an indices range | |
| assert([4, 5, 6, 7, 8].remove(tuple(1, 3)) == [4, 7, 8]); | |
| // Use an indices range and individual indices | |
| assert([4, 5, 6, 7, 8].remove(0, tuple(1, 3), 4) == [7]); | |
| } | |
| /// `SwapStrategy.unstable` is faster, but doesn't guarantee the same order of the original array | |
| @safe pure unittest | |
| { | |
| assert([5, 6, 7, 8].remove!(SwapStrategy.stable)(1) == [5, 7, 8]); | |
| assert([5, 6, 7, 8].remove!(SwapStrategy.unstable)(1) == [5, 8, 7]); | |
| } | |
| private auto removeImpl(SwapStrategy s, Range, Offset...)(Range range, Offset offset) | |
| { | |
| static if (isNarrowString!Range) | |
| { | |
| static assert(isMutable!(typeof(range[0])), | |
| "Elements must be mutable to remove"); | |
| static assert(s == SwapStrategy.stable, | |
| "Only stable removing can be done for character arrays"); | |
| return removeStableString(range, offset); | |
| } | |
| else | |
| { | |
| static assert(isBidirectionalRange!Range, | |
| "Range must be bidirectional"); | |
| static assert(hasLvalueElements!Range, | |
| "Range must have Lvalue elements (see std.range.hasLvalueElements)"); | |
| static if (s == SwapStrategy.unstable) | |
| { | |
| static assert(hasLength!Range, | |
| "Range must have `length` for unstable remove"); | |
| return removeUnstable(range, offset); | |
| } | |
| else static if (s == SwapStrategy.stable) | |
| return removeStable(range, offset); | |
| else | |
| static assert(false, | |
| "Only SwapStrategy.stable and SwapStrategy.unstable are supported"); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| import std.exception : assertThrown; | |
| import std.range; | |
| // https://issues.dlang.org/show_bug.cgi?id=10173 | |
| int[] test = iota(0, 10).array(); | |
| assertThrown(remove!(SwapStrategy.stable)(test, tuple(2, 4), tuple(1, 3))); | |
| assertThrown(remove!(SwapStrategy.unstable)(test, tuple(2, 4), tuple(1, 3))); | |
| assertThrown(remove!(SwapStrategy.stable)(test, 2, 4, 1, 3)); | |
| assertThrown(remove!(SwapStrategy.unstable)(test, 2, 4, 1, 3)); | |
| } | |
| @safe unittest | |
| { | |
| import std.range; | |
| int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; | |
| a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; | |
| assert(remove!(SwapStrategy.stable)(a, 1) == | |
| [ 0, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]); | |
| a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; | |
| assert(remove!(SwapStrategy.unstable)(a, 0, 10) == | |
| [ 9, 1, 2, 3, 4, 5, 6, 7, 8 ]); | |
| a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; | |
| assert(remove!(SwapStrategy.unstable)(a, 0, tuple(9, 11)) == | |
| [ 8, 1, 2, 3, 4, 5, 6, 7 ]); | |
| // https://issues.dlang.org/show_bug.cgi?id=5224 | |
| a = [ 1, 2, 3, 4 ]; | |
| assert(remove!(SwapStrategy.unstable)(a, 2) == | |
| [ 1, 2, 4 ]); | |
| a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; | |
| assert(remove!(SwapStrategy.stable)(a, 1, 5) == | |
| [ 0, 2, 3, 4, 6, 7, 8, 9, 10 ]); | |
| a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; | |
| assert(remove!(SwapStrategy.stable)(a, 1, 3, 5) | |
| == [ 0, 2, 4, 6, 7, 8, 9, 10]); | |
| a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; | |
| assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5)) | |
| == [ 0, 2, 5, 6, 7, 8, 9, 10]); | |
| a = iota(0, 10).array(); | |
| assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4), tuple(6, 7)) | |
| == [0, 9, 8, 7, 4, 5]); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=11576 | |
| @safe unittest | |
| { | |
| auto arr = [1,2,3]; | |
| arr = arr.remove!(SwapStrategy.unstable)(2); | |
| assert(arr == [1,2]); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=12889 | |
| @safe unittest | |
| { | |
| import std.range; | |
| int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]]; | |
| auto orig = arr.dup; | |
| foreach (i; iota(arr.length)) | |
| { | |
| assert(orig == arr.remove!(SwapStrategy.unstable)(tuple(i,i))); | |
| assert(orig == arr.remove!(SwapStrategy.stable)(tuple(i,i))); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| char[] chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; | |
| remove(chars, 4); | |
| assert(chars == ['a', 'b', 'c', 'd', 'f', 'g', 'h', 'h']); | |
| char[] bigChars = "∑œ∆¬é˚˙ƒé∂ß¡¡".dup; | |
| assert(remove(bigChars, tuple(4, 6), 8) == ("∑œ∆¬˙ƒ∂ß¡¡")); | |
| import std.exception : assertThrown; | |
| assertThrown(remove(bigChars.dup, 1, 0)); | |
| assertThrown(remove(bigChars.dup, tuple(4, 3))); | |
| } | |
| private Range removeUnstable(Range, Offset...)(Range range, Offset offset) | |
| { | |
| Tuple!(size_t, "pos", size_t, "len")[offset.length] blackouts; | |
| foreach (i, v; offset) | |
| { | |
| static if (is(typeof(v[0]) : size_t) && is(typeof(v[1]) : size_t)) | |
| { | |
| blackouts[i].pos = v[0]; | |
| blackouts[i].len = v[1] - v[0]; | |
| } | |
| else | |
| { | |
| static assert(is(typeof(v) : size_t), typeof(v).stringof); | |
| blackouts[i].pos = v; | |
| blackouts[i].len = 1; | |
| } | |
| static if (i > 0) | |
| { | |
| import std.exception : enforce; | |
| enforce(blackouts[i - 1].pos + blackouts[i - 1].len | |
| <= blackouts[i].pos, | |
| "remove(): incorrect ordering of elements to remove"); | |
| } | |
| } | |
| size_t left = 0, right = offset.length - 1; | |
| auto tgt = range.save; | |
| size_t tgtPos = 0; | |
| while (left <= right) | |
| { | |
| // Look for a blackout on the right | |
| if (blackouts[right].pos + blackouts[right].len >= range.length) | |
| { | |
| range.popBackExactly(blackouts[right].len); | |
| // Since right is unsigned, we must check for this case, otherwise | |
| // we might turn it into size_t.max and the loop condition will not | |
| // fail when it should. | |
| if (right > 0) | |
| { | |
| --right; | |
| continue; | |
| } | |
| else | |
| break; | |
| } | |
| // Advance to next blackout on the left | |
| assert(blackouts[left].pos >= tgtPos, "Next blackout on the left shouldn't appear before the target."); | |
| tgt.popFrontExactly(blackouts[left].pos - tgtPos); | |
| tgtPos = blackouts[left].pos; | |
| // Number of elements to the right of blackouts[right] | |
| immutable tailLen = range.length - (blackouts[right].pos + blackouts[right].len); | |
| size_t toMove = void; | |
| if (tailLen < blackouts[left].len) | |
| { | |
| toMove = tailLen; | |
| blackouts[left].pos += toMove; | |
| blackouts[left].len -= toMove; | |
| } | |
| else | |
| { | |
| toMove = blackouts[left].len; | |
| ++left; | |
| } | |
| tgtPos += toMove; | |
| foreach (i; 0 .. toMove) | |
| { | |
| move(range.back, tgt.front); | |
| range.popBack(); | |
| tgt.popFront(); | |
| } | |
| } | |
| return range; | |
| } | |
| private Range removeStable(Range, Offset...)(Range range, Offset offset) | |
| { | |
| auto result = range; | |
| auto src = range, tgt = range; | |
| size_t pos; | |
| foreach (pass, i; offset) | |
| { | |
| static if (is(typeof(i[0])) && is(typeof(i[1]))) | |
| { | |
| auto from = i[0], delta = i[1] - i[0]; | |
| } | |
| else | |
| { | |
| auto from = i; | |
| enum delta = 1; | |
| } | |
| static if (pass > 0) | |
| { | |
| import std.exception : enforce; | |
| enforce(pos <= from, | |
| "remove(): incorrect ordering of elements to remove"); | |
| for (; pos < from; ++pos, src.popFront(), tgt.popFront()) | |
| { | |
| move(src.front, tgt.front); | |
| } | |
| } | |
| else | |
| { | |
| src.popFrontExactly(from); | |
| tgt.popFrontExactly(from); | |
| pos = from; | |
| } | |
| // now skip source to the "to" position | |
| src.popFrontExactly(delta); | |
| result.popBackExactly(delta); | |
| pos += delta; | |
| } | |
| // leftover move | |
| moveAll(src, tgt); | |
| return result; | |
| } | |
| private Range removeStableString(Range, Offset...)(Range range, Offset offsets) | |
| { | |
| import std.utf : stride; | |
| size_t charIdx = 0; | |
| size_t dcharIdx = 0; | |
| size_t charShift = 0; | |
| void skipOne() | |
| { | |
| charIdx += stride(range[charIdx .. $]); | |
| ++dcharIdx; | |
| } | |
| void copyBackOne() | |
| { | |
| auto encodedLen = stride(range[charIdx .. $]); | |
| foreach (j; charIdx .. charIdx + encodedLen) | |
| range[j - charShift] = range[j]; | |
| charIdx += encodedLen; | |
| ++dcharIdx; | |
| } | |
| foreach (pass, i; offsets) | |
| { | |
| static if (is(typeof(i[0])) && is(typeof(i[1]))) | |
| { | |
| auto from = i[0]; | |
| auto delta = i[1] - i[0]; | |
| } | |
| else | |
| { | |
| auto from = i; | |
| enum delta = 1; | |
| } | |
| import std.exception : enforce; | |
| enforce(dcharIdx <= from && delta >= 0, | |
| "remove(): incorrect ordering of elements to remove"); | |
| while (dcharIdx < from) | |
| static if (pass == 0) | |
| skipOne(); | |
| else | |
| copyBackOne(); | |
| auto mark = charIdx; | |
| while (dcharIdx < from + delta) | |
| skipOne(); | |
| charShift += charIdx - mark; | |
| } | |
| foreach (i; charIdx .. range.length) | |
| range[i - charShift] = range[i]; | |
| return range[0 .. $ - charShift]; | |
| } | |
| // Use of dynamic arrays as offsets is too error-prone | |
| // https://issues.dlang.org/show_bug.cgi?id=12086 | |
| // Activate these tests once the deprecation period of remove with non-integral tuples is over | |
| @safe unittest | |
| { | |
| //static assert(!__traits(compiles, [0, 1, 2, 3, 4].remove([1, 3]) == [0, 3, 4])); | |
| static assert(__traits(compiles, [0, 1, 2, 3, 4].remove(1, 3) == [0, 2, 4])); | |
| //static assert(!__traits(compiles, assert([0, 1, 2, 3, 4].remove([1, 3, 4]) == [0, 3, 4]))); | |
| //static assert(!__traits(compiles, assert([0, 1, 2, 3, 4].remove(tuple(1, 3, 4)) == [0, 3, 4]))); | |
| import std.range : only; | |
| //static assert(!__traits(compiles, assert([0, 1, 2, 3, 4].remove(only(1, 3)) == [0, 3, 4]))); | |
| static assert(__traits(compiles, assert([0, 1, 2, 3, 4].remove(1, 3) == [0, 2, 4]))); | |
| } | |
| /** | |
| Reduces the length of the | |
| $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) `range` by removing | |
| elements that satisfy `pred`. If `s = SwapStrategy.unstable`, | |
| elements are moved from the right end of the range over the elements | |
| to eliminate. If `s = SwapStrategy.stable` (the default), | |
| elements are moved progressively to front such that their relative | |
| order is preserved. Returns the filtered range. | |
| Params: | |
| range = a bidirectional ranges with lvalue elements | |
| or mutable character arrays | |
| Returns: | |
| the range with all of the elements where `pred` is `true` | |
| removed | |
| */ | |
| Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range)(Range range) | |
| { | |
| import std.functional : unaryFun; | |
| alias pred_ = unaryFun!pred; | |
| static if (isNarrowString!Range) | |
| { | |
| static assert(isMutable!(typeof(range[0])), | |
| "Elements must be mutable to remove"); | |
| static assert(s == SwapStrategy.stable, | |
| "Only stable removing can be done for character arrays"); | |
| return removePredString!pred_(range); | |
| } | |
| else | |
| { | |
| static assert(isBidirectionalRange!Range, | |
| "Range must be bidirectional"); | |
| static assert(hasLvalueElements!Range, | |
| "Range must have Lvalue elements (see std.range.hasLvalueElements)"); | |
| static if (s == SwapStrategy.unstable) | |
| return removePredUnstable!pred_(range); | |
| else static if (s == SwapStrategy.stable) | |
| return removePredStable!pred_(range); | |
| else | |
| static assert(false, | |
| "Only SwapStrategy.stable and SwapStrategy.unstable are supported"); | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| static immutable base = [1, 2, 3, 2, 4, 2, 5, 2]; | |
| int[] arr = base[].dup; | |
| // using a string-based predicate | |
| assert(remove!("a == 2")(arr) == [ 1, 3, 4, 5 ]); | |
| // The original array contents have been modified, | |
| // so we need to reset it to its original state. | |
| // The length is unmodified however. | |
| arr[] = base[]; | |
| // using a lambda predicate | |
| assert(remove!(a => a == 2)(arr) == [ 1, 3, 4, 5 ]); | |
| } | |
| @safe unittest | |
| { | |
| int[] a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ]; | |
| assert(remove!("a == 2", SwapStrategy.unstable)(a) == | |
| [ 1, 6, 3, 5, 3, 4, 5 ]); | |
| a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ]; | |
| assert(remove!("a == 2", SwapStrategy.stable)(a) == | |
| [ 1, 3, 3, 4, 5, 5, 6 ]); | |
| } | |
| @nogc @safe unittest | |
| { | |
| // @nogc test | |
| static int[] arr = [0,1,2,3,4,5,6,7,8,9]; | |
| alias pred = e => e < 5; | |
| auto r = arr[].remove!(SwapStrategy.unstable)(0); | |
| r = r.remove!(SwapStrategy.stable)(0); | |
| r = r.remove!(pred, SwapStrategy.unstable); | |
| r = r.remove!(pred, SwapStrategy.stable); | |
| } | |
| @safe unittest | |
| { | |
| import std.algorithm.comparison : min; | |
| import std.algorithm.searching : all, any; | |
| import std.algorithm.sorting : isStrictlyMonotonic; | |
| import std.array : array; | |
| import std.meta : AliasSeq; | |
| import std.range : iota, only; | |
| import std.typecons : Tuple; | |
| alias E = Tuple!(int, int); | |
| alias S = Tuple!(E); | |
| S[] soffsets; | |
| foreach (start; 0 .. 5) | |
| foreach (end; min(start+1,5) .. 5) | |
| soffsets ~= S(E(start,end)); | |
| alias D = Tuple!(E, E); | |
| D[] doffsets; | |
| foreach (start1; 0 .. 10) | |
| foreach (end1; min(start1+1,10) .. 10) | |
| foreach (start2; end1 .. 10) | |
| foreach (end2; min(start2+1,10) .. 10) | |
| doffsets ~= D(E(start1,end1),E(start2,end2)); | |
| alias T = Tuple!(E, E, E); | |
| T[] toffsets; | |
| foreach (start1; 0 .. 15) | |
| foreach (end1; min(start1+1,15) .. 15) | |
| foreach (start2; end1 .. 15) | |
| foreach (end2; min(start2+1,15) .. 15) | |
| foreach (start3; end2 .. 15) | |
| foreach (end3; min(start3+1,15) .. 15) | |
| toffsets ~= T(E(start1,end1),E(start2,end2),E(start3,end3)); | |
| static void verify(O...)(int[] r, int len, int removed, bool stable, O offsets) | |
| { | |
| assert(r.length == len - removed); | |
| assert(!stable || r.isStrictlyMonotonic); | |
| assert(r.all!(e => all!(o => e < o[0] || e >= o[1])(offsets.only))); | |
| } | |
| static foreach (offsets; AliasSeq!(soffsets,doffsets,toffsets)) | |
| foreach (os; offsets) | |
| { | |
| int len = 5*os.length; | |
| auto w = iota(0, len).array; | |
| auto x = w.dup; | |
| auto y = w.dup; | |
| auto z = w.dup; | |
| alias pred = e => any!(o => o[0] <= e && e < o[1])(only(os.expand)); | |
| w = w.remove!(SwapStrategy.unstable)(os.expand); | |
| x = x.remove!(SwapStrategy.stable)(os.expand); | |
| y = y.remove!(pred, SwapStrategy.unstable); | |
| z = z.remove!(pred, SwapStrategy.stable); | |
| int removed; | |
| foreach (o; os) | |
| removed += o[1] - o[0]; | |
| verify(w, len, removed, false, os[]); | |
| verify(x, len, removed, true, os[]); | |
| verify(y, len, removed, false, os[]); | |
| verify(z, len, removed, true, os[]); | |
| assert(w == y); | |
| assert(x == z); | |
| } | |
| } | |
| @safe unittest | |
| { | |
| char[] chars = "abcdefg".dup; | |
| assert(chars.remove!(dc => dc == 'c' || dc == 'f') == "abdeg"); | |
| assert(chars == "abdegfg"); | |
| assert(chars.remove!"a == 'd'" == "abegfg"); | |
| char[] bigChars = "¥^¨^©é√∆π".dup; | |
| assert(bigChars.remove!(dc => dc == "¨"d[0] || dc == "é"d[0]) == "¥^^©√∆π"); | |
| } | |
| private Range removePredUnstable(alias pred, Range)(Range range) | |
| { | |
| auto result = range; | |
| for (;!range.empty;) | |
| { | |
| if (!pred(range.front)) | |
| { | |
| range.popFront(); | |
| continue; | |
| } | |
| move(range.back, range.front); | |
| range.popBack(); | |
| result.popBack(); | |
| } | |
| return result; | |
| } | |
| private Range removePredStable(alias pred, Range)(Range range) | |
| { | |
| auto result = range; | |
| auto tgt = range; | |
| for (; !range.empty; range.popFront()) | |
| { | |
| if (pred(range.front)) | |
| { | |
| // yank this guy | |
| result.popBack(); | |
| continue; | |
| } | |
| // keep this guy | |
| move(range.front, tgt.front); | |
| tgt.popFront(); | |
| } | |
| return result; | |
| } | |
| private Range removePredString(alias pred, SwapStrategy s = SwapStrategy.stable, Range) | |
| (Range range) | |
| { | |
| import std.utf : decode; | |
| import std.functional : unaryFun; | |
| alias pred_ = unaryFun!pred; | |
| size_t charIdx = 0; | |
| size_t charShift = 0; | |
| while (charIdx < range.length) | |
| { | |
| size_t start = charIdx; | |
| if (pred_(decode(range, charIdx))) | |
| { | |
| charShift += charIdx - start; | |
| break; | |
| } | |
| } | |
| while (charIdx < range.length) | |
| { | |
| size_t start = charIdx; | |
| auto doRemove = pred_(decode(range, charIdx)); | |
| auto encodedLen = charIdx - start; | |
| if (doRemove) | |
| charShift += encodedLen; | |
| else | |
| foreach (i; start .. charIdx) | |
| range[i - charShift] = range[i]; | |
| } | |
| return range[0 .. $ - charShift]; | |
| } | |
| // reverse | |
| /** | |
| Reverses `r` in-place. Performs `r.length / 2` evaluations of `swap`. | |
| UTF sequences consisting of multiple code units are preserved properly. | |
| Params: | |
| r = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) | |
| with either swappable elements, a random access range with a length member, | |
| or a narrow string | |
| Returns: `r` | |
| Note: | |
| When passing a string with unicode modifiers on characters, such as `\u0301`, | |
| this function will not properly keep the position of the modifier. For example, | |
| reversing `ba\u0301d` ("bád") will result in d\u0301ab ("d́ab") instead of | |
| `da\u0301b` ("dáb"). | |
| See_Also: $(REF retro, std,range) for a lazy reverse without changing `r` | |
| */ | |
| Range reverse(Range)(Range r) | |
| if (isBidirectionalRange!Range && | |
| (hasSwappableElements!Range || | |
| (hasAssignableElements!Range && hasLength!Range && isRandomAccessRange!Range) || | |
| (isNarrowString!Range && isAssignable!(ElementType!Range)))) | |
| { | |
| static if (isRandomAccessRange!Range && hasLength!Range) | |
| { | |
| //swapAt is in fact the only way to swap non lvalue ranges | |
| immutable last = r.length - 1; | |
| immutable steps = r.length / 2; | |
| for (size_t i = 0; i < steps; i++) | |
| { | |
| r.swapAt(i, last - i); | |
| } | |
| return r; | |
| } | |
| else static if (isNarrowString!Range && isAssignable!(ElementType!Range)) | |
| { | |
| import std.string : representation; | |
| import std.utf : stride; | |
| auto raw = representation(r); | |
| for (size_t i = 0; i < r.length;) | |
| { | |
| immutable step = stride(r, i); | |
| if (step > 1) | |
| { | |
| .reverse(raw[i .. i + step]); | |
| i += step; | |
| } | |
| else | |
| { | |
| ++i; | |
| } | |
| } | |
| reverse(raw); | |
| return r; | |
| } | |
| else | |
| { | |
| while (!r.empty) | |
| { | |
| swap(r.front, r.back); | |
| r.popFront(); | |
| if (r.empty) break; | |
| r.popBack(); | |
| } | |
| return r; | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| int[] arr = [ 1, 2, 3 ]; | |
| assert(arr.reverse == [ 3, 2, 1 ]); | |
| } | |
| @safe unittest | |
| { | |
| int[] range = null; | |
| reverse(range); | |
| range = [ 1 ]; | |
| reverse(range); | |
| assert(range == [1]); | |
| range = [1, 2]; | |
| reverse(range); | |
| assert(range == [2, 1]); | |
| range = [1, 2, 3]; | |
| assert(range.reverse == [3, 2, 1]); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| char[] arr = "hello\U00010143\u0100\U00010143".dup; | |
| assert(arr.reverse == "\U00010143\u0100\U00010143olleh"); | |
| } | |
| @safe unittest | |
| { | |
| void test(string a, string b) | |
| { | |
| auto c = a.dup; | |
| reverse(c); | |
| assert(c == b, c ~ " != " ~ b); | |
| } | |
| test("a", "a"); | |
| test(" ", " "); | |
| test("\u2029", "\u2029"); | |
| test("\u0100", "\u0100"); | |
| test("\u0430", "\u0430"); | |
| test("\U00010143", "\U00010143"); | |
| test("abcdefcdef", "fedcfedcba"); | |
| test("hello\U00010143\u0100\U00010143", "\U00010143\u0100\U00010143olleh"); | |
| } | |
| /** | |
| The strip group of functions allow stripping of either leading, trailing, | |
| or both leading and trailing elements. | |
| The `stripLeft` function will strip the `front` of the range, | |
| the `stripRight` function will strip the `back` of the range, | |
| while the `strip` function will strip both the `front` and `back` | |
| of the range. | |
| Note that the `strip` and `stripRight` functions require the range to | |
| be a $(LREF BidirectionalRange) range. | |
| All of these functions come in two varieties: one takes a target element, | |
| where the range will be stripped as long as this element can be found. | |
| The other takes a lambda predicate, where the range will be stripped as | |
| long as the predicate returns true. | |
| Params: | |
| range = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) | |
| or $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| element = the elements to remove | |
| Returns: | |
| a Range with all of range except element at the start and end | |
| */ | |
| Range strip(Range, E)(Range range, E element) | |
| if (isBidirectionalRange!Range && is(typeof(range.front == element) : bool)) | |
| { | |
| return range.stripLeft(element).stripRight(element); | |
| } | |
| /// ditto | |
| Range strip(alias pred, Range)(Range range) | |
| if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) | |
| { | |
| return range.stripLeft!pred().stripRight!pred(); | |
| } | |
| /// ditto | |
| Range stripLeft(Range, E)(Range range, E element) | |
| if (isInputRange!Range && is(typeof(range.front == element) : bool)) | |
| { | |
| import std.algorithm.searching : find; | |
| return find!((auto ref a) => a != element)(range); | |
| } | |
| /// ditto | |
| Range stripLeft(alias pred, Range)(Range range) | |
| if (isInputRange!Range && is(typeof(pred(range.front)) : bool)) | |
| { | |
| import std.algorithm.searching : find; | |
| import std.functional : not; | |
| return find!(not!pred)(range); | |
| } | |
| /// ditto | |
| Range stripRight(Range, E)(Range range, E element) | |
| if (isBidirectionalRange!Range && is(typeof(range.back == element) : bool)) | |
| { | |
| for (; !range.empty; range.popBack()) | |
| { | |
| if (range.back != element) | |
| break; | |
| } | |
| return range; | |
| } | |
| /// ditto | |
| Range stripRight(alias pred, Range)(Range range) | |
| if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) | |
| { | |
| for (; !range.empty; range.popBack()) | |
| { | |
| if (!pred(range.back)) | |
| break; | |
| } | |
| return range; | |
| } | |
| /// Strip leading and trailing elements equal to the target element. | |
| @safe pure unittest | |
| { | |
| assert(" foobar ".strip(' ') == "foobar"); | |
| assert("00223.444500".strip('0') == "223.4445"); | |
| assert("ëëêéüŗōpéêëë".strip('ë') == "êéüŗōpéê"); | |
| assert([1, 1, 0, 1, 1].strip(1) == [0]); | |
| assert([0.0, 0.01, 0.01, 0.0].strip(0).length == 2); | |
| } | |
| /// Strip leading and trailing elements while the predicate returns true. | |
| @safe pure unittest | |
| { | |
| assert(" foobar ".strip!(a => a == ' ')() == "foobar"); | |
| assert("00223.444500".strip!(a => a == '0')() == "223.4445"); | |
| assert("ëëêéüŗōpéêëë".strip!(a => a == 'ë')() == "êéüŗōpéê"); | |
| assert([1, 1, 0, 1, 1].strip!(a => a == 1)() == [0]); | |
| assert([0.0, 0.01, 0.5, 0.6, 0.01, 0.0].strip!(a => a < 0.4)().length == 2); | |
| } | |
| /// Strip leading elements equal to the target element. | |
| @safe pure unittest | |
| { | |
| assert(" foobar ".stripLeft(' ') == "foobar "); | |
| assert("00223.444500".stripLeft('0') == "223.444500"); | |
| assert("ůůűniçodêéé".stripLeft('ů') == "űniçodêéé"); | |
| assert([1, 1, 0, 1, 1].stripLeft(1) == [0, 1, 1]); | |
| assert([0.0, 0.01, 0.01, 0.0].stripLeft(0).length == 3); | |
| } | |
| /// Strip leading elements while the predicate returns true. | |
| @safe pure unittest | |
| { | |
| assert(" foobar ".stripLeft!(a => a == ' ')() == "foobar "); | |
| assert("00223.444500".stripLeft!(a => a == '0')() == "223.444500"); | |
| assert("ůůűniçodêéé".stripLeft!(a => a == 'ů')() == "űniçodêéé"); | |
| assert([1, 1, 0, 1, 1].stripLeft!(a => a == 1)() == [0, 1, 1]); | |
| assert([0.0, 0.01, 0.10, 0.5, 0.6].stripLeft!(a => a < 0.4)().length == 2); | |
| } | |
| /// Strip trailing elements equal to the target element. | |
| @safe pure unittest | |
| { | |
| assert(" foobar ".stripRight(' ') == " foobar"); | |
| assert("00223.444500".stripRight('0') == "00223.4445"); | |
| assert("ùniçodêéé".stripRight('é') == "ùniçodê"); | |
| assert([1, 1, 0, 1, 1].stripRight(1) == [1, 1, 0]); | |
| assert([0.0, 0.01, 0.01, 0.0].stripRight(0).length == 3); | |
| } | |
| /// Strip trailing elements while the predicate returns true. | |
| @safe pure unittest | |
| { | |
| assert(" foobar ".stripRight!(a => a == ' ')() == " foobar"); | |
| assert("00223.444500".stripRight!(a => a == '0')() == "00223.4445"); | |
| assert("ùniçodêéé".stripRight!(a => a == 'é')() == "ùniçodê"); | |
| assert([1, 1, 0, 1, 1].stripRight!(a => a == 1)() == [1, 1, 0]); | |
| assert([0.0, 0.01, 0.10, 0.5, 0.6].stripRight!(a => a > 0.4)().length == 3); | |
| } | |
| // swap | |
| /** | |
| Swaps `lhs` and `rhs`. The instances `lhs` and `rhs` are moved in | |
| memory, without ever calling `opAssign`, nor any other function. `T` | |
| need not be assignable at all to be swapped. | |
| If `lhs` and `rhs` reference the same instance, then nothing is done. | |
| `lhs` and `rhs` must be mutable. If `T` is a struct or union, then | |
| its fields must also all be (recursively) mutable. | |
| Params: | |
| lhs = Data to be swapped with `rhs`. | |
| rhs = Data to be swapped with `lhs`. | |
| */ | |
| void swap(T)(ref T lhs, ref T rhs) | |
| if (is(typeof(lhs.proxySwap(rhs)))) | |
| { | |
| lhs.proxySwap(rhs); | |
| } | |
| /// ditto | |
| void swap(T)(ref T lhs, ref T rhs) @trusted pure nothrow @nogc | |
| if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) | |
| { | |
| import std.traits : hasAliasing, hasElaborateAssign, isAssignable, | |
| isStaticArray; | |
| static if (hasAliasing!T) if (!__ctfe) | |
| { | |
| import std.exception : doesPointTo; | |
| assert(!doesPointTo(lhs, lhs), "Swap: lhs internal pointer."); | |
| assert(!doesPointTo(rhs, rhs), "Swap: rhs internal pointer."); | |
| assert(!doesPointTo(lhs, rhs), "Swap: lhs points to rhs."); | |
| assert(!doesPointTo(rhs, lhs), "Swap: rhs points to lhs."); | |
| } | |
| static if (hasElaborateAssign!T || !isAssignable!T) | |
| { | |
| if (&lhs != &rhs) | |
| { | |
| static if (__traits(compiles, lhs.tupleof = rhs.tupleof)) | |
| { | |
| if (__ctfe) | |
| { | |
| // can't reinterpret cast | |
| foreach (i, ref e; lhs.tupleof) | |
| swap(e, rhs.tupleof[i]); | |
| return; | |
| } | |
| } | |
| // For structs with non-trivial assignment, move memory directly | |
| ubyte[T.sizeof] t = void; | |
| auto a = (cast(ubyte*) &lhs)[0 .. T.sizeof]; | |
| auto b = (cast(ubyte*) &rhs)[0 .. T.sizeof]; | |
| t[] = a[]; | |
| a[] = b[]; | |
| b[] = t[]; | |
| } | |
| } | |
| else | |
| { | |
| //Avoid assigning overlapping arrays. Dynamic arrays are fine, because | |
| //it's their ptr and length properties which get assigned rather | |
| //than their elements when assigning them, but static arrays are value | |
| //types and therefore all of their elements get copied as part of | |
| //assigning them, which would be assigning overlapping arrays if lhs | |
| //and rhs were the same array. | |
| static if (isStaticArray!T) | |
| { | |
| if (lhs.ptr == rhs.ptr) | |
| return; | |
| } | |
| // For non-elaborate-assign types, suffice to do the classic swap | |
| static if (__traits(hasCopyConstructor, T)) | |
| { | |
| // don't invoke any elaborate constructors either | |
| T tmp = void; | |
| tmp = lhs; | |
| } | |
| else | |
| auto tmp = lhs; | |
| lhs = rhs; | |
| rhs = tmp; | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| // Swapping POD (plain old data) types: | |
| int a = 42, b = 34; | |
| swap(a, b); | |
| assert(a == 34 && b == 42); | |
| // Swapping structs with indirection: | |
| static struct S { int x; char c; int[] y; } | |
| S s1 = { 0, 'z', [ 1, 2 ] }; | |
| S s2 = { 42, 'a', [ 4, 6 ] }; | |
| swap(s1, s2); | |
| assert(s1.x == 42); | |
| assert(s1.c == 'a'); | |
| assert(s1.y == [ 4, 6 ]); | |
| assert(s2.x == 0); | |
| assert(s2.c == 'z'); | |
| assert(s2.y == [ 1, 2 ]); | |
| // Immutables cannot be swapped: | |
| immutable int imm1 = 1, imm2 = 2; | |
| static assert(!__traits(compiles, swap(imm1, imm2))); | |
| int c = imm1 + 0; | |
| int d = imm2 + 0; | |
| swap(c, d); | |
| assert(c == 2); | |
| assert(d == 1); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| // Non-copyable types can still be swapped. | |
| static struct NoCopy | |
| { | |
| this(this) { assert(0); } | |
| int n; | |
| string s; | |
| } | |
| NoCopy nc1, nc2; | |
| nc1.n = 127; nc1.s = "abc"; | |
| nc2.n = 513; nc2.s = "uvwxyz"; | |
| swap(nc1, nc2); | |
| assert(nc1.n == 513 && nc1.s == "uvwxyz"); | |
| assert(nc2.n == 127 && nc2.s == "abc"); | |
| swap(nc1, nc1); | |
| swap(nc2, nc2); | |
| assert(nc1.n == 513 && nc1.s == "uvwxyz"); | |
| assert(nc2.n == 127 && nc2.s == "abc"); | |
| // Types containing non-copyable fields can also be swapped. | |
| static struct NoCopyHolder | |
| { | |
| NoCopy noCopy; | |
| } | |
| NoCopyHolder h1, h2; | |
| h1.noCopy.n = 31; h1.noCopy.s = "abc"; | |
| h2.noCopy.n = 65; h2.noCopy.s = null; | |
| swap(h1, h2); | |
| assert(h1.noCopy.n == 65 && h1.noCopy.s == null); | |
| assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc"); | |
| swap(h1, h1); | |
| swap(h2, h2); | |
| assert(h1.noCopy.n == 65 && h1.noCopy.s == null); | |
| assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc"); | |
| // Const types cannot be swapped. | |
| const NoCopy const1, const2; | |
| assert(const1.n == 0 && const2.n == 0); | |
| static assert(!__traits(compiles, swap(const1, const2))); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=4789 | |
| @safe unittest | |
| { | |
| int[1] s = [1]; | |
| swap(s, s); | |
| int[3] a = [1, 2, 3]; | |
| swap(a[1], a[2]); | |
| assert(a == [1, 3, 2]); | |
| } | |
| @safe unittest | |
| { | |
| static struct NoAssign | |
| { | |
| int i; | |
| void opAssign(NoAssign) @disable; | |
| } | |
| auto s1 = NoAssign(1); | |
| auto s2 = NoAssign(2); | |
| swap(s1, s2); | |
| assert(s1.i == 2); | |
| assert(s2.i == 1); | |
| } | |
| @safe unittest | |
| { | |
| struct S | |
| { | |
| const int i; | |
| int i2 = 2; | |
| int i3 = 3; | |
| } | |
| S s; | |
| static assert(!__traits(compiles, swap(s, s))); | |
| swap(s.i2, s.i3); | |
| assert(s.i2 == 3); | |
| assert(s.i3 == 2); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=11853 | |
| @safe unittest | |
| { | |
| import std.traits : isAssignable; | |
| alias T = Tuple!(int, double); | |
| static assert(isAssignable!T); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=12024 | |
| @safe unittest | |
| { | |
| import std.datetime; | |
| SysTime a, b; | |
| swap(a, b); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=9975 | |
| @system unittest | |
| { | |
| import std.exception : doesPointTo, mayPointTo; | |
| static struct S2 | |
| { | |
| union | |
| { | |
| size_t sz; | |
| string s; | |
| } | |
| } | |
| S2 a , b; | |
| a.sz = -1; | |
| assert(!doesPointTo(a, b)); | |
| assert( mayPointTo(a, b)); | |
| swap(a, b); | |
| //Note: we can catch an error here, because there is no RAII in this test | |
| import std.exception : assertThrown; | |
| void* p, pp; | |
| p = &p; | |
| assertThrown!Error(move(p)); | |
| assertThrown!Error(move(p, pp)); | |
| assertThrown!Error(swap(p, pp)); | |
| } | |
| @system unittest | |
| { | |
| static struct A | |
| { | |
| int* x; | |
| this(this) { x = new int; } | |
| } | |
| A a1, a2; | |
| swap(a1, a2); | |
| static struct B | |
| { | |
| int* x; | |
| void opAssign(B) { x = new int; } | |
| } | |
| B b1, b2; | |
| swap(b1, b2); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=20732 | |
| @safe unittest | |
| { | |
| static struct A | |
| { | |
| int x; | |
| this(scope return ref const A other) | |
| { | |
| import std.stdio; | |
| x = other.x; | |
| // note, struct functions inside @safe functions infer ALL | |
| // attributes, so the following 3 lines are meant to prevent this. | |
| new int; // prevent @nogc inference | |
| writeln("impure"); // prevent pure inference | |
| throw new Exception(""); // prevent nothrow inference | |
| } | |
| } | |
| A a1, a2; | |
| swap(a1, a2); | |
| A[1] a3, a4; | |
| swap(a3, a4); | |
| } | |
| // https://issues.dlang.org/show_bug.cgi?id=21429 | |
| @safe unittest | |
| { | |
| enum e = (){ | |
| Tuple!int a = 5, b = 6; | |
| swap(a, b); | |
| assert(a[0] == 6); | |
| assert(b[0] == 5); | |
| return 0; | |
| }(); | |
| } | |
| /** | |
| Swaps two elements in-place of a range `r`, | |
| specified by their indices `i1` and `i2`. | |
| Params: | |
| r = a range with swappable elements | |
| i1 = first index | |
| i2 = second index | |
| */ | |
| void swapAt(R)(auto ref R r, size_t i1, size_t i2) | |
| { | |
| static if (is(typeof(&r.swapAt))) | |
| { | |
| r.swapAt(i1, i2); | |
| } | |
| else static if (is(typeof(&r[i1]))) | |
| { | |
| swap(r[i1], r[i2]); | |
| } | |
| else | |
| { | |
| if (i1 == i2) return; | |
| auto t1 = r.moveAt(i1); | |
| auto t2 = r.moveAt(i2); | |
| r[i2] = t1; | |
| r[i1] = t2; | |
| } | |
| } | |
| /// | |
| pure @safe nothrow unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| auto a = [1, 2, 3]; | |
| a.swapAt(1, 2); | |
| assert(a.equal([1, 3, 2])); | |
| } | |
| pure @safe nothrow unittest | |
| { | |
| import std.algorithm.comparison : equal; | |
| auto a = [4, 5, 6]; | |
| a.swapAt(1, 1); | |
| assert(a.equal([4, 5, 6])); | |
| } | |
| pure @safe nothrow unittest | |
| { | |
| // test non random access ranges | |
| import std.algorithm.comparison : equal; | |
| import std.array : array; | |
| char[] b = ['a', 'b', 'c']; | |
| b.swapAt(1, 2); | |
| assert(b.equal(['a', 'c', 'b'])); | |
| int[3] c = [1, 2, 3]; | |
| c.swapAt(1, 2); | |
| assert(c.array.equal([1, 3, 2])); | |
| // opIndex returns lvalue | |
| struct RandomIndexType(T) | |
| { | |
| T payload; | |
| @property ref auto opIndex(size_t i) | |
| { | |
| return payload[i]; | |
| } | |
| } | |
| auto d = RandomIndexType!(int[])([4, 5, 6]); | |
| d.swapAt(1, 2); | |
| assert(d.payload.equal([4, 6, 5])); | |
| // custom moveAt and opIndexAssign | |
| struct RandomMoveAtType(T) | |
| { | |
| T payload; | |
| ElementType!T moveAt(size_t i) | |
| { | |
| return payload.moveAt(i); | |
| } | |
| void opIndexAssign(ElementType!T val, size_t idx) | |
| { | |
| payload[idx] = val; | |
| } | |
| } | |
| auto e = RandomMoveAtType!(int[])([7, 8, 9]); | |
| e.swapAt(1, 2); | |
| assert(e.payload.equal([7, 9, 8])); | |
| // custom swapAt | |
| struct RandomSwapAtType(T) | |
| { | |
| T payload; | |
| void swapAt(size_t i) | |
| { | |
| return payload.swapAt(i); | |
| } | |
| } | |
| auto f = RandomMoveAtType!(int[])([10, 11, 12]); | |
| swapAt(f, 1, 2); | |
| assert(f.payload.equal([10, 12, 11])); | |
| } | |
| private void swapFront(R1, R2)(R1 r1, R2 r2) | |
| if (isInputRange!R1 && isInputRange!R2) | |
| { | |
| static if (is(typeof(swap(r1.front, r2.front)))) | |
| { | |
| swap(r1.front, r2.front); | |
| } | |
| else | |
| { | |
| auto t1 = moveFront(r1), t2 = moveFront(r2); | |
| r1.front = move(t2); | |
| r2.front = move(t1); | |
| } | |
| } | |
| // swapRanges | |
| /** | |
| Swaps all elements of `r1` with successive elements in `r2`. | |
| Returns a tuple containing the remainder portions of `r1` and $(D | |
| r2) that were not swapped (one of them will be empty). The ranges may | |
| be of different types but must have the same element type and support | |
| swapping. | |
| Params: | |
| r1 = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| with swappable elements | |
| r2 = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| with swappable elements | |
| Returns: | |
| Tuple containing the remainder portions of r1 and r2 that were not swapped | |
| */ | |
| Tuple!(InputRange1, InputRange2) | |
| swapRanges(InputRange1, InputRange2)(InputRange1 r1, InputRange2 r2) | |
| if (hasSwappableElements!InputRange1 && hasSwappableElements!InputRange2 | |
| && is(ElementType!InputRange1 == ElementType!InputRange2)) | |
| { | |
| for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront()) | |
| { | |
| swap(r1.front, r2.front); | |
| } | |
| return tuple(r1, r2); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| import std.range : empty; | |
| int[] a = [ 100, 101, 102, 103 ]; | |
| int[] b = [ 0, 1, 2, 3 ]; | |
| auto c = swapRanges(a[1 .. 3], b[2 .. 4]); | |
| assert(c[0].empty && c[1].empty); | |
| assert(a == [ 100, 2, 3, 103 ]); | |
| assert(b == [ 0, 1, 101, 102 ]); | |
| } | |
| /** | |
| Initializes each element of `range` with `value`. | |
| Assumes that the elements of the range are uninitialized. | |
| This is of interest for structs that | |
| define copy constructors (for all other types, $(LREF fill) and | |
| uninitializedFill are equivalent). | |
| Params: | |
| range = An | |
| $(REF_ALTTEXT input range, isInputRange, std,range,primitives) | |
| that exposes references to its elements and has assignable | |
| elements | |
| value = Assigned to each element of range | |
| See_Also: | |
| $(LREF fill) | |
| $(LREF initializeAll) | |
| */ | |
| void uninitializedFill(Range, Value)(Range range, Value value) | |
| if (isInputRange!Range && hasLvalueElements!Range && is(typeof(range.front = value))) | |
| { | |
| import std.traits : hasElaborateAssign; | |
| alias T = ElementType!Range; | |
| static if (hasElaborateAssign!T) | |
| { | |
| import core.internal.lifetime : emplaceRef; | |
| // Must construct stuff by the book | |
| for (; !range.empty; range.popFront()) | |
| emplaceRef!T(range.front, value); | |
| } | |
| else | |
| // Doesn't matter whether fill is initialized or not | |
| return fill(range, value); | |
| } | |
| /// | |
| nothrow @system unittest | |
| { | |
| import core.stdc.stdlib : malloc, free; | |
| auto s = (cast(int*) malloc(5 * int.sizeof))[0 .. 5]; | |
| uninitializedFill(s, 42); | |
| assert(s == [ 42, 42, 42, 42, 42 ]); | |
| scope(exit) free(s.ptr); | |
| } |