mirror of
https://github.com/dlang/phobos.git
synced 2025-04-28 14:10:30 +03:00

* Fix #10538 - Cannot swap a std.typecons.Tuple Co-authored-by: Elias Batek <0xEAB@users.noreply.github.com> * De-optimize `opAssign` of `std.typecons.Tuple` --------- Co-authored-by: Elias Batek <0xEAB@users.noreply.github.com> Co-authored-by: Elias Batek <desisma@heidel.beer>
3367 lines
91 KiB
D
3367 lines
91 KiB
D
// 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 ref return 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);
|
|
}
|