Changed chooseAmong implementation from recursive to linear.

This commit is contained in:
Ate Eskola 2022-01-22 21:48:23 +02:00
parent bedcad87f8
commit ab547c78c3

View file

@ -235,7 +235,7 @@ public import std.range.primitives;
public import std.typecons : Flag, Yes, No; public import std.typecons : Flag, Yes, No;
import std.internal.attributes : betterC; import std.internal.attributes : betterC;
import std.meta : allSatisfy, staticMap; import std.meta : allSatisfy, anySatisfy, staticMap;
import std.traits : CommonType, isCallable, isFloatingPoint, isIntegral, import std.traits : CommonType, isCallable, isFloatingPoint, isIntegral,
isPointer, isSomeFunction, isStaticArray, Unqual, isInstanceOf; isPointer, isSomeFunction, isStaticArray, Unqual, isInstanceOf;
@ -1412,7 +1412,8 @@ auto choose(R1, R2)(bool condition, return scope R1 r1, return scope R2 r2)
if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) && if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) &&
!is(CommonType!(ElementType!(Unqual!R1), ElementType!(Unqual!R2)) == void)) !is(CommonType!(ElementType!(Unqual!R1), ElementType!(Unqual!R2)) == void))
{ {
return ChooseResult!(R1, R2)(condition, r1, r2); size_t choice = condition? 0: 1;
return ChooseResult!(R1, R2)(choice, r1, r2);
} }
/// ///
@ -1447,76 +1448,102 @@ if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) &&
} }
private struct ChooseResult(R1, R2) private struct ChooseResult(Ranges...)
{ {
import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor; import std.meta : aliasSeqOf, ApplyLeft;
import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor,
lvalueOf;
private union private union
{ {
R1 r1; Ranges rs;
R2 r2;
} }
private bool r1Chosen; private size_t chosenI;
private static auto ref actOnChosen(alias foo, ExtraArgs ...)(ref ChooseResult r, private static auto ref actOnChosen(alias foo, ExtraArgs ...)
auto ref ExtraArgs extraArgs) (ref ChooseResult r, auto ref ExtraArgs extraArgs)
{ {
if (r.r1Chosen) ref getI(size_t i)(return ref ChooseResult r) @trusted { return r.rs[i]; }
switch(r.chosenI)
{ {
ref get1(return ref ChooseResult r) @trusted { return r.r1; } static foreach(candI; 0 .. rs.length)
return foo(get1(r), extraArgs); {
} case candI: return foo(getI!candI(r), extraArgs);
else }
{
ref get2(return ref ChooseResult r) @trusted { return r.r2; } default: assert(false);
return foo(get2(r), extraArgs);
} }
} }
this(bool r1Chosen, return scope R1 r1, return scope R2 r2) @trusted // @trusted because of assignment of r which overlap each other
this(size_t chosen, return scope Ranges rs) @trusted
{ {
// @trusted because of assignment of r1 and r2 which overlap each other
import core.lifetime : emplace; import core.lifetime : emplace;
// This should be the only place r1Chosen is ever assigned // This should be the only place chosenI is ever assigned
// independently // independently
this.r1Chosen = r1Chosen; this.chosenI = chosen;
if (r1Chosen)
// Otherwise the compiler will complain about skipping these fields
static foreach(i; 0 .. rs.length)
{ {
this.r2 = R2.init; this.rs[i] = Ranges[i].init;
emplace(&this.r1, r1);
} }
else
// The relevant field needs to be initialized last so it will overwrite
// the other initializations and not the other way around.
sw: switch(chosenI)
{ {
this.r1 = R1.init; static foreach(i; 0 .. rs.length)
emplace(&this.r2, r2); {
case i:
this.rs[i] = rs[i];
break sw;
}
default: assert(false);
} }
} }
// Some legacy code may still call this with typeof(choose(/*...*/))(/*...*/)
// without this overload the regular constructor would invert the meaning of
// the boolean
static if(rs.length == 2)
pragma(inline, true)
deprecated("Call with size_t (0 = first), or use the choose function")
this(bool firstChosen, Ranges rs)
{
import core.lifetime : move;
this(cast(size_t)(firstChosen? 0: 1), rs[0].move, rs[1].move);
}
void opAssign(ChooseResult r) void opAssign(ChooseResult r)
{ {
static if (hasElaborateDestructor!R1 || hasElaborateDestructor!R2) ref getI(size_t i)(return ref ChooseResult r) @trusted { return r.rs[i]; }
if (r1Chosen != r.r1Chosen)
{ static if (anySatisfy!(hasElaborateDestructor, Ranges))
// destroy the current item if (chosenI != r.chosenI)
actOnChosen!((ref r) => destroy(r))(this);
}
r1Chosen = r.r1Chosen;
if (r1Chosen)
{ {
ref get1(return ref ChooseResult r) @trusted { return r.r1; } // destroy the current item
get1(this) = get1(r); actOnChosen!((ref r) => destroy(r))(this);
} }
else chosenI = r.chosenI;
sw: switch(chosenI)
{ {
ref get2(return ref ChooseResult r) @trusted { return r.r2; } static foreach(candI; 0 .. rs.length)
get2(this) = get2(r); {
case candI: getI!candI(this) = getI!candI(r);
break sw;
}
default: assert(false);
} }
} }
// Carefully defined postblit to postblit the appropriate range // Carefully defined postblit to postblit the appropriate range
static if (hasElaborateCopyConstructor!R1 static if (anySatisfy!(hasElaborateCopyConstructor, Ranges))
|| hasElaborateCopyConstructor!R2)
this(this) this(this)
{ {
actOnChosen!((ref r) { actOnChosen!((ref r) {
@ -1524,20 +1551,18 @@ private struct ChooseResult(R1, R2)
})(this); })(this);
} }
static if (hasElaborateDestructor!R1 || hasElaborateDestructor!R2) static if (anySatisfy!(hasElaborateDestructor, Ranges))
~this() ~this()
{ {
actOnChosen!((ref r) => destroy(r))(this); actOnChosen!((ref r) => destroy(r))(this);
} }
static if (isInfinite!R1 && isInfinite!R2) // Propagate infiniteness.
// Propagate infiniteness. static if (allSatisfy!(isInfinite, Ranges)) enum bool empty = false;
enum bool empty = false; else @property bool empty()
else {
@property bool empty() return actOnChosen!(r => r.empty)(this);
{ }
return actOnChosen!(r => r.empty)(this);
}
@property auto ref front() @property auto ref front()
{ {
@ -1550,34 +1575,38 @@ private struct ChooseResult(R1, R2)
return actOnChosen!((ref r) { r.popFront; })(this); return actOnChosen!((ref r) { r.popFront; })(this);
} }
static if (isForwardRange!R1 && isForwardRange!R2) static if (allSatisfy!(isForwardRange, Ranges))
@property auto save() return scope @property auto save() // return scope inferred
{ {
if (r1Chosen) auto saveOrInit(size_t i)()
{ {
ref R1 getR1() @trusted { return r1; } ref getI() @trusted { return rs[i]; }
return ChooseResult(r1Chosen, getR1.save, R2.init); if (i == chosenI) return getI().save;
else return Ranges[i].init;
} }
else
return typeof(this)(chosenI, staticMap!(saveOrInit,
aliasSeqOf!(rs.length.iota)));
}
template front(T)
{
private enum overloadValidFor(alias r) = is(typeof(r.front = T.init));
static if (allSatisfy!(overloadValidFor, rs))
void front(T v)
{ {
ref R2 getR2() @trusted { return r2; } actOnChosen!((ref r, T v) { r.front = v; })(this, v);
return ChooseResult(r1Chosen, R1.init, getR2.save);
} }
} }
@property void front(T)(T v) static if (allSatisfy!(hasMobileElements, Ranges))
if (is(typeof({ r1.front = v; r2.front = v; }))) auto moveFront()
{ {
actOnChosen!((ref r, T v) { r.front = v; })(this, v); return actOnChosen!((ref r) => r.moveFront)(this);
} }
static if (hasMobileElements!R1 && hasMobileElements!R2) static if (allSatisfy!(isBidirectionalRange, Ranges))
auto moveFront()
{
return actOnChosen!((ref r) => r.moveFront)(this);
}
static if (isBidirectionalRange!R1 && isBidirectionalRange!R2)
{ {
@property auto ref back() @property auto ref back()
{ {
@ -1590,20 +1619,25 @@ private struct ChooseResult(R1, R2)
actOnChosen!((ref r) { r.popBack; })(this); actOnChosen!((ref r) { r.popBack; })(this);
} }
static if (hasMobileElements!R1 && hasMobileElements!R2) static if (allSatisfy!(hasMobileElements, Ranges))
auto moveBack() auto moveBack()
{
return actOnChosen!((ref r) => r.moveBack)(this);
}
@property void back(T)(T v)
if (is(typeof({ r1.back = v; r2.back = v; })))
{ {
actOnChosen!((ref r, T v) { r.back = v; })(this, v); return actOnChosen!((ref r) => r.moveBack)(this);
}
template back(T)
{
private enum overloadValidFor(alias r) = is(typeof(r.back = T.init));
static if (allSatisfy!(overloadValidFor, rs))
void back(T v)
{
actOnChosen!((ref r, T v) { r.back = v; })(this, v);
}
} }
} }
static if (hasLength!R1 && hasLength!R2) static if (allSatisfy!(hasLength, Ranges))
{ {
@property size_t length() @property size_t length()
{ {
@ -1612,7 +1646,7 @@ private struct ChooseResult(R1, R2)
alias opDollar = length; alias opDollar = length;
} }
static if (isRandomAccessRange!R1 && isRandomAccessRange!R2) static if (allSatisfy!(isRandomAccessRange, Ranges))
{ {
auto ref opIndex(size_t index) auto ref opIndex(size_t index)
{ {
@ -1620,33 +1654,41 @@ private struct ChooseResult(R1, R2)
return actOnChosen!get(this, index); return actOnChosen!get(this, index);
} }
static if (hasMobileElements!R1 && hasMobileElements!R2) static if (allSatisfy!(hasMobileElements, Ranges))
auto moveAt(size_t index) auto moveAt(size_t index)
{ {
return actOnChosen!((ref r, size_t index) => r.moveAt(index)) return actOnChosen!((ref r, size_t index) => r.moveAt(index))
(this, index); (this, index);
} }
void opIndexAssign(T)(T v, size_t index) private enum indexAssignable(T, R) = is(typeof(lvalueOf!R[1] = T.init));
if (is(typeof({ r1[1] = v; r2[1] = v; })))
template opIndexAssign(T)
if (allSatisfy!(ApplyLeft!(indexAssignable, T), Ranges))
{ {
return actOnChosen!((ref r, size_t index, T v) { r[index] = v; }) void opIndexAssign(T v, size_t index)
(this, index, v); {
return actOnChosen!((ref r, size_t index, T v) { r[index] = v; })
(this, index, v);
}
} }
} }
static if (hasSlicing!R1 && hasSlicing!R2) static if (allSatisfy!(hasSlicing, Ranges))
auto opSlice(size_t begin, size_t end) auto opSlice(size_t begin, size_t end)
{
alias Slice(R) = typeof(R.init[0 .. 1]);
alias Slices = staticMap!(Slice, Ranges);
auto sliceOrInit(size_t i)()
{ {
alias Slice1 = typeof(R1.init[0 .. 1]); ref getI() @trusted { return rs[i]; }
alias Slice2 = typeof(R2.init[0 .. 1]); return i == chosenI? getI()[begin .. end]: Slices[i].init;
return actOnChosen!((r, size_t begin, size_t end) {
static if (is(typeof(r) == Slice1))
return choose(true, r[begin .. end], Slice2.init);
else
return choose(false, Slice1.init, r[begin .. end]);
})(this, begin, end);
} }
return chooseAmong(chosenI, staticMap!(sliceOrInit,
aliasSeqOf!(rs.length.iota)));
}
} }
// https://issues.dlang.org/show_bug.cgi?id=18657 // https://issues.dlang.org/show_bug.cgi?id=18657
@ -1668,8 +1710,9 @@ pure @safe unittest
int front; int front;
bool empty; bool empty;
void popFront() {} void popFront() {}
@property R save() { p = q; return this; } // `p = q;` is only there to prevent inference of `scope return`.
// `p = q;` is only there to prevent inference of `scope return`. @property @safe R save() { p = q; return this; }
} }
R r; R r;
choose(true, r, r).save; choose(true, r, r).save;
@ -1801,10 +1844,7 @@ if (Ranges.length >= 2
&& allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) && allSatisfy!(isInputRange, staticMap!(Unqual, Ranges))
&& !is(CommonType!(staticMap!(ElementType, Ranges)) == void)) && !is(CommonType!(staticMap!(ElementType, Ranges)) == void))
{ {
static if (Ranges.length == 2) return ChooseResult!Ranges(index, rs);
return choose(index == 0, rs[0], rs[1]);
else
return choose(index == 0, rs[0], chooseAmong(index - 1, rs[1 .. $]));
} }
/// ///