Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixes in std algorithm

*Tuning of map for "length", "opIndex", "opSlice"
*Fill is more efficient, accepts infinite inpute length
*adjacentFind: Correctly saves
*minCount: Using value types (and not reference types). Throws on empty range
*minPos: Correcttly saves
*equal: More efficient implementation
  • Loading branch information...
commit 58fef77d02919f844b83b7de157420d9f8f8edda 1 parent 09ea7f5
@monarchdodra monarchdodra authored
Showing with 271 additions and 32 deletions.
  1. +271 −32 std/algorithm.d
View
303 std/algorithm.d
@@ -430,13 +430,18 @@ private struct MapResult(alias fun, Range)
static if (isRandomAccessRange!R)
{
- auto ref opIndex(size_t index)
+ static if (is(typeof(_input[ulong.max])))
+ private alias ulong opIndex_t;
+ else
+ private alias uint opIndex_t;
+
+ auto ref opIndex(opIndex_t index)
{
return fun(_input[index]);
}
}
- static if (hasLength!R || isSomeString!R)
+ static if (hasLength!R)
{
@property auto length()
{
@@ -448,19 +453,26 @@ private struct MapResult(alias fun, Range)
static if (hasSlicing!R)
{
- auto opSlice(size_t lowerBound, size_t upperBound)
+ static if (is(typeof(_input[ulong.max .. ulong.max])))
+ private alias ulong opSlice_t;
+ else
+ private alias uint opSlice_t;
+
+ auto opSlice(opSlice_t lowerBound, opSlice_t upperBound)
{
return typeof(this)(_input[lowerBound..upperBound]);
}
}
static if (isForwardRange!R)
+ {
@property auto save()
{
auto result = this;
result._input = result._input.save;
return result;
}
+ }
}
unittest
@@ -551,6 +563,20 @@ unittest
static assert(propagatesRangeType!(typeof(m), DummyType));
assert(equal(m, [1,4,9,16,25,36,49,64,81,100]));
}
+
+ //Test string access
+ string s1 = "hello world!";
+ dstring s2 = "日本語";
+ dstring s3 = "hello world!"d;
+ auto ms1 = map!(std.ascii.toUpper)(s1);
+ auto ms2 = map!(std.ascii.toUpper)(s2);
+ auto ms3 = map!(std.ascii.toUpper)(s3);
+ static assert(!is(ms1[0])); //narrow strings can't be indexed
+ assert(ms2[0] == '');
+ assert(ms3[0] == 'H');
+ static assert(!is(ms1[0..1])); //narrow strings can't be sliced
+ assert(equal(ms2[0..2], "日本"w));
+ assert(equal(ms3[0..2], "HE"));
}
unittest
{
@@ -918,15 +944,69 @@ assert(a == [ 8, 9, 8, 9, 8 ]);
----
*/
void fill(Range1, Range2)(Range1 range, Range2 filler)
-if (isInputRange!Range1 && isForwardRange!Range2
+ if (isInputRange!Range1
+ && (isForwardRange!Range2
+ || (isInputRange!Range2 && isInfinite!Range2))
&& is(typeof(Range1.init.front = Range2.init.front)))
{
- enforce(!filler.empty);
- auto t = filler.save;
- for (; !range.empty; range.popFront(), t.popFront())
+ static if(isInfinite!Range2)
{
- if (t.empty) t = filler;
- range.front = t.front;
+ //Range2 is infinite, no need for bounds checking or saving
+ static if(hasSlicing!Range2 && hasLength!Range1
+ && 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
+ {
+ enforce(!filler.empty, "Cannot fill range with an empty filler");
+
+ static if(hasLength!Range1 && hasLength!Range2
+ && is(typeof(range.length > filler.length)))
+ {
+ //Case we have access to length
+ auto len = filler.length;
+ //Start by bulk copies
+ for( ; range.length > len ; )
+ {
+ range = copy(filler.save, range);
+ }
+
+ //and finally fill the partial range. No need to save here.
+ static if (hasSlicing!Range2 && 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;
+ }
+ }
}
}
@@ -943,7 +1023,19 @@ unittest
InputRange range;
fill(range,[1,2]);
foreach(i,value;range.arr)
- assert(value == (i%2==0?1:2));
+ 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[$..$]));
+
}
/**
@@ -4651,7 +4743,7 @@ assert(p == [ 7, 8, 9 ]);
Range findAdjacent(alias pred = "a == b", Range)(Range r)
if (isForwardRange!(Range))
{
- auto ahead = r;
+ auto ahead = r.save;
if (!ahead.empty)
{
for (ahead.popFront(); !ahead.empty; r.popFront(), ahead.popFront())
@@ -4680,6 +4772,9 @@ unittest
assert(p.empty);
p = findAdjacent!"a > b"(a);
assert(p.empty);
+ ReferenceForwardRange!int rfr = new ReferenceForwardRange!int([1, 2, 3, 2, 2, 3]);
+ assert(equal(findAdjacent(rfr), [2, 2, 3]));
+
}
// findAmong
@@ -4897,16 +4992,54 @@ double[] c = [ 1.005, 2, 4, 3];
assert(equal!(approxEqual)(b, c));
----
*/
-bool equal(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2)
-if (isInputRange!(Range1) && isInputRange!(Range2)
+bool equal(Range1, Range2)(Range1 r1, Range2 r2)
+ if (isInputRange!Range1 && isInputRange!Range2
+ && is(typeof(r1.front == r2.front)))
+{
+ static if (isArray!Range1 && isArray!Range2
+ && is(typeof(r1 == r2)))
+ {
+ //Ranges are comparable. Let the compiler do the comparison.
+ return r1 == r2;
+ }
+ else
+ {
+ //Need to do an actual compare, delegate to predicate version
+ return equal!"a==b"(r1, r2);
+ }
+}
+
+/// Ditto
+bool equal(alias pred, Range1, Range2)(Range1 r1, Range2 r2)
+ if (isInputRange!Range1 && isInputRange!Range2
&& is(typeof(binaryFun!pred(r1.front, r2.front))))
{
- for (; !r1.empty; r1.popFront(), r2.popFront())
+ //Try a fast implementation when the ranges have comparable lengths
+ static if (hasLength!Range1 && hasLength!Range2
+ && is(typeof(r1.length == r2.length)))
{
- if (r2.empty) return false;
- if (!binaryFun!(pred)(r1.front, r2.front)) return false;
+ auto len1 = r1.length;
+ auto len2 = r2.length;
+ if (len1 != len2) return false; //Short circuit return
+
+ //Lengths are the same, so we need to do an actual comparison
+ //Good news is we can sqeeze out a bit of performance by not checking if r2 is empty
+ for (; !r1.empty; r1.popFront(), r2.popFront())
+ {
+ if (!binaryFun!(pred)(r1.front, r2.front)) return false;
+ }
+ return true;
+ }
+ else
+ {
+ //Generic case, we have to walk both ranges making sure neither is empty
+ for (; !r1.empty; r1.popFront(), r2.popFront())
+ {
+ if (r2.empty) return false;
+ if (!binaryFun!(pred)(r1.front, r2.front)) return false;
+ }
+ return r2.empty;
}
- return r2.empty;
}
unittest
@@ -4925,8 +5058,54 @@ unittest
double[] c = [ 1.005, 2, 4, 3];
assert(equal!(approxEqual)(b, c));
- // utf-8 strings
- assert(equal("æøå", "æøå"));
+ // various strings
+ assert(equal("æøå", "æøå")); //UTF8 vs UTF8
+ assert(!equal("???", "æøå")); //UTF8 vs UTF8
+ assert(equal("æøå"w, "æøå"d)); //UTF16 vs UTF32
+ assert(!equal("???"w, "æøå"d));//UTF16 vs UTF32
+ assert(equal("æøå"d, "æøå"d)); //UTF32 vs UTF32
+ assert(!equal("???"d, "æøå"d));//UTF32 vs UTF32
+ assert(!equal("hello", "world"));
+
+ // same strings, but "explicit non default" comparison (to test the non optimized array comparison)
+ assert( equal!("a==b")("æøå", "æøå")); //UTF8 vs UTF8
+ assert(!equal!("a==b")("???", "æøå")); //UTF8 vs UTF8
+ assert( equal!("a==b")("æøå"w, "æøå"d)); //UTF16 vs UTF32
+ assert(!equal!("a==b")("???"w, "æøå"d));//UTF16 vs UTF32
+ assert( equal!("a==b")("æøå"d, "æøå"d)); //UTF32 vs UTF32
+ assert(!equal!("a==b")("???"d, "æøå"d));//UTF32 vs UTF32
+ assert(!equal!("a==b")("hello", "world"));
+
+ //Array of string
+ assert(equal(["hello", "world"], ["hello", "world"]));
+ assert(!equal(["hello", "world"], ["hello"]));
+ assert(!equal(["hello", "world"], ["hello", "Bob!"]));
+
+ //Should not compile, because "string == dstring" is illegal
+ static assert(!is(typeof(equal(["hello", "world"], ["hello"d, "world"d]))));
+ //However, arrays of non-matching string can be compared using equal!equal. Neat-o!
+ equal!equal(["hello", "world"], ["hello"d, "world"d]);
+
+ //Tests, with more fancy map ranges
+ assert(equal([2, 4, 8, 6], map!"a*2"(a)));
+ assert(equal!approxEqual(map!"a*2"(b), map!"a*2"(c)));
+ assert(!equal([2, 4, 1, 3], map!"a*2"(a)));
+ assert(!equal([2, 4, 1], map!"a*2"(a)));
+ assert(!equal!approxEqual(map!"a*3"(b), map!"a*2"(c)));
+
+ //Tests with some fancy reference ranges.
+ ReferenceInputRange!int cir = new ReferenceInputRange!int([1, 2, 4, 3]);
+ ReferenceForwardRange!int cfr = new ReferenceForwardRange!int([1, 2, 4, 3]);
+ assert(equal(cir, a));
+ cir = new ReferenceInputRange!int([1, 2, 4, 3]);
+ assert(equal(cir, cfr.save));
+ assert(equal(cfr.save, cfr.save));
+ cir = new ReferenceInputRange!int([1, 2, 8, 1]);
+ assert(!equal(cir, cfr));
+
+ //Test with an infinte range
+ ReferenceInfiniteForwardRange!int ifr = new ReferenceInfiniteForwardRange!int;
+ assert(!equal(a, ifr));
}
// cmp
@@ -5249,17 +5428,19 @@ assert(minCount!("a > b")(a) == tuple(4, 2));
*/
Tuple!(ElementType!(Range), size_t)
minCount(alias pred = "a < b", Range)(Range range)
+ if (isInputRange!Range && !isInfinite!Range)
{
- if (range.empty) return typeof(return)();
- auto p = &(range.front());
+ enforce(!range.empty, "Can't count elements from an empty range");
size_t occurrences = 1;
+ auto p = range.front;
for (range.popFront(); !range.empty; range.popFront())
{
- if (binaryFun!(pred)(*p, range.front)) continue;
- if (binaryFun!(pred)(range.front, *p))
+ auto p2 = range.front;
+ if (binaryFun!(pred)(p, p2)) continue;
+ if (binaryFun!(pred)(p2, p))
{
// change the min
- p = &(range.front());
+ move(p2, p);
occurrences = 1;
}
else
@@ -5267,7 +5448,7 @@ minCount(alias pred = "a < b", Range)(Range range)
++occurrences;
}
}
- return tuple(*p, occurrences);
+ return tuple(p, occurrences);
}
unittest
@@ -5280,6 +5461,14 @@ unittest
int[][] b = [ [4], [2, 4], [4], [4] ];
auto c = minCount!("a[0] < b[0]")(b);
assert(c == tuple([2, 4], 1), text(c[0]));
+
+ //Test empty range
+ assertThrown(minCount(a[$..$]));
+
+ //test with reference ranges. Test both input and forward.
+ assert(minCount(new ReferenceInputRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2));
+ assert(minCount(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2));
+
}
// minPos
@@ -5300,15 +5489,22 @@ assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]);
----
*/
Range minPos(alias pred = "a < b", Range)(Range range)
+ if (isForwardRange!Range && !isInfinite!Range)
{
if (range.empty) return range;
- auto result = range;
+ auto result = range.save;
+ auto p = result.front;
for (range.popFront(); !range.empty; range.popFront())
{
- if (binaryFun!(pred)(result.front, range.front)
- || !binaryFun!(pred)(range.front, result.front)) continue;
- // change the min
- result = range;
+ auto p2 = range.front;
+
+ //Note: Unlike minCount, we do not care to find equivalence, so a single pred call is enough
+ if (binaryFun!pred(p2, p))
+ {
+ // change the min
+ result = range.save;
+ move(p2, p);
+ }
}
return result;
}
@@ -5318,10 +5514,17 @@ unittest
debug(std_algorithm) scope(success)
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
-// Minimum is 1 and first occurs in position 3
+ // Minimum is 1 and first occurs in position 3
assert(minPos(a) == [ 1, 2, 4, 1, 1, 2 ]);
-// Maximum is 4 and first occurs in position 5
+ // Maximum is 4 and first occurs in position 5
assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]);
+
+ //Test that an empty range works
+ int[] b = a[$..$];
+ assert(equal(minPos(b), b));
+
+ //test with reference range.
+ assert( equal( minPos(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])), [0, 2, 0] ) );
}
// mismatch
@@ -8739,6 +8942,42 @@ version(unittest)
}
return result;
}
+
+ //Reference type input range
+ private class ReferenceInputRange(T)
+ {
+ this(Range)(Range r) if (isInputRange!Range) {_payload = array(r);}
+ final @property ref T front(){return _payload.front;}
+ final void popFront(){_payload.popFront();}
+ final @property bool empty(){return _payload.empty;}
+ protected T[] _payload;
+ }
+
+ //Reference forward range
+ private class ReferenceForwardRange(T) : ReferenceInputRange!T
+ {
+ this(Range)(Range r) if (isInputRange!Range) {super(r);}
+ final @property ReferenceForwardRange save()
+ {return new ReferenceForwardRange!T(_payload);}
+ }
+
+ //Infinite input range
+ private class ReferenceInfiniteInputRange(T)
+ {
+ this(T first = T.init) {_val = first;}
+ final @property T front(){return _val;}
+ final void popFront(){++_val;}
+ enum bool empty = false;
+ protected T _val;
+ }
+
+ //Infinite forward range
+ private class ReferenceInfiniteForwardRange(T) : ReferenceInfiniteInputRange!T
+ {
+ this(T first = T.init) {super(first);}
+ final @property ReferenceInfiniteForwardRange save()
+ {return new ReferenceInfiniteForwardRange!T(_val);}
+ }
}
// NWayUnion
Please sign in to comment.
Something went wrong with that request. Please try again.