From 29f3cc23f76c97a2782848033136d2d7ec7ca57e Mon Sep 17 00:00:00 2001 From: Andrei Alexandrescu Date: Tue, 19 Feb 2008 07:00:56 +0000 Subject: [PATCH] std.math: minor change in approxEqual. std.contracts: added functions pointsTo() std.numeric: minor unittest fixes. std.bitmanip: fixed code bloat issue, reintroduced FloatRep and DoubleRep. std.conv: minor simplification of implementation. std.regexp: added reference to ECMA standard in the documentation. std.getopt: changed return type from bool to void, error is signaled by use of exceptions. std.functional: added unaryFun, binaryFun, adjoin. std.string: updated documentation, changed code to compile with warnings enabled. std.traits: changed FieldTypeTuple; added RepresentationTypeTuple, hasAliasing; fixed bug 1826; added call to flush() from within write; fixed unlisted bug in lines(). std.algorithm: added map, reduce, filter, inPlace, move, swap, overwriteAdjacent, find, findRange, findBoyerMoore, findAdjacent, findAmong, findAmongSorted, canFind, canFindAmong, canFindAmongSorted, count, equal, overlap, min, max, mismatch, EditOp, none, substitute, insert, remove, levenshteinDistance, levenshteinDistanceAndPath, copy, copyIf, iterSwap, swapRanges, reverse, rotate, SwapStrategy, Unstable, Semistable, Stable, eliminate, partition, nthElement, sort, schwartzSort, partialSort, isSorted, makeIndex, schwartzMakeIndex, lowerBound, upperBound, equalRange, canFindSorted. std.thread: fixed so it compiles with warnings enabled. std.file: made getSize() faster under Linux. std.random: fixed so it compiles with warnings enabled; improved function uniform so it deduces type generated from its arguments. std.format: added fixes to make formatting work with const data. std.path: minor documentation changes. --- std/algorithm.d | 3275 ++++++++++++++++++++++++++++++++++++++++++---- std/bitmanip.d | 112 +- std/contracts.d | 90 +- std/conv.d | 21 +- std/file.d | 58 +- std/format.d | 32 +- std/functional.d | 224 +++- std/getopt.d | 80 +- std/math.d | 4 - std/numeric.d | 6 +- std/path.d | 20 +- std/random.d | 45 +- std/regexp.d | 12 +- std/stdio.d | 13 +- std/string.d | 22 +- std/thread.d | 2 + std/traits.d | 425 +++++- 17 files changed, 3948 insertions(+), 493 deletions(-) diff --git a/std/algorithm.d b/std/algorithm.d index 5834e67b0..544f92ec3 100644 --- a/std/algorithm.d +++ b/std/algorithm.d @@ -1,15 +1,52 @@ // Written in the D programming language. /** -This module is a port of a growing fragment of the $(D_PARAM -algorithm) header in Alexander Stepanov's -$(LINK2 http://www.sgi.com/tech/stl/,Standard Template Library). +Implements algorithms oriented mainly towards processing of +sequences. Some functions are semantic equivalents or supersets of +those found in the $(D algorithm) header in $(WEB sgi.com/tech/stl/, +Alexander Stepanov's Standard Template Library) for C++. + +Author: +$(WEB erdani.org, Andrei Alexandrescu) + +Note: + +Many functions in this module are parameterized with a function or a +$(GLOSSARY predicate). The predicate may be passed either as a +function name, a delegate name, a $(GLOSSARY functor) name, or a +compile-time string. The string may consist of $(B any) legal D +expression that uses the symbol $(D a) (for unary functions) or the +symbols $(D a) and $(D b) (for binary functions). These names will NOT +interfere with other homonym symbols in user code because they are +evaluated in a different context. The default for all binary +comparison predicates is $(D "a == b") for unordered operations and +$(D "a < b") for ordered operations. + +Example: + +---- +int[] a = ...; +static bool greater(int a, int b) +{ + return a > b; +} +sort!(greater)(a); // predicate as alias +sort!("a > b")(a); // predicate as string + // (no ambiguity with array name) +sort(a); // no predicate, "a < b" is implicit +---- + +Some functions are additionally parameterized with primitives such as +$(D move) (defaulting to $(XREF _algorithm,move)) or $(D iterSwap) +primitive (defaulting to $(XREF _algorithm,iterSwap)). These +parameters distill the way in which data is manipulated, and the +algorithms guarantee they only use them to touch values. There is +sometimes a need to override that default behavior. Possible uses +include notifying observers, counting the number of operations, or +manipulating multiple collections in lockstep. Macros: WIKI = Phobos/StdAlgorithm - -Author: -Andrei Alexandrescu */ /* @@ -40,242 +77,1706 @@ private import std.math; private import std.random; private import std.date; private import std.functional; +private import std.iterator; +private import std.conv; +private import std.typecons; +private import std.typetuple; +private import std.metastrings; +private import std.contracts; +private import std.traits; +private import std.c.string; -/* The iterator-related part below is undocumented and might - * change in future releases. Do NOT rely on it. +/** +Implements the homonym function (also known as $(D transform)) present +in many languages of functional flavor. The call $(D map!(fun)(range1, +range2, ..., rangeN)) returns a new range of which elements are +obtained by applying $(D fun(x)) left to right for all $(D x) in $(D +range1), then all $(D x) in $(D range2), ..., all $(D x) in $(D +rangeN). The original ranges are not changed. + +Example: +---- +int[] arr1 = [ 1, 2, 3, 4 ]; +int[] arr2 = [ 5, 6 ]; +auto squares = map!("a * a")(arr1, arr2); +assert(squares == [ 1, 4, 9, 16, 25, 36 ]); +---- + +In all cases, the type of the result is the same as of the type of the +first range passed in. If a different type of range is needed, just +supply an empty range of the needed type as the first argument. + +Example: +---- +short[] arr = [ 1, 2 ]; +auto squares = map!("a * a")(cast(int[]) null, arr); +assert(is(typeof(squares) == int[])); +---- */ - -template IteratorType(T : U[], U) +Ranges[0] map(string fun, Ranges...)(Ranges rs) { - alias U* IteratorType; + return .map!(unaryFun!(fun), Ranges)(rs); } -template ElementType(T : U[], U) +/// Ditto +Ranges[0] map(alias fun, Ranges...)(Ranges rs) { - alias U ElementType; -} - -IteratorType!(T[]) begin(T)(T[] range) -{ - return range.ptr; -} - -ElementType!(T[]) front(T)(T[] range) -{ - assert(range.length); - return *range.ptr; -} - -bool isEmpty(T)(T[] range) -{ - return !range.length; -} - -IteratorType!(T[]) end(T)(T[] range) -{ - return range.ptr + range.length; -} - -void next(T)(ref T[] range) -{ - range = range.ptr[1 .. range.length];; -} - -IteratorType!(R) adjacentFind(R, E)(R range) -{ - if (range.isEmpty()) return range.end(); - auto result = range.begin(); - range.next(); - if (range.isEmpty()) return range.end(); - for (; !range.isEmpty(); range.next()) + typeof(return) result; + foreach (r, R; Ranges) { - auto next = range.begin(); - if (*result == *next) return result; - result = next; + foreach (i; begin(rs[r]) .. end(rs[r])) + { + result ~= fun(*i); + } } - return range.end(); -} - -IteratorType!(Range) find(Range, E)(Range haystack, E needle) -{ - ElementType!(Range) e; - for (; !isEmpty(haystack); next(haystack)) - { - if (front(haystack) == needle) break; - } - return begin(haystack); + return result; } unittest { - int[] a = ([ 1, 2, 3 ]).dup; - assert(find(a, 5) == a.ptr + a.length); - assert(find(a, 2) == &a[1]); + int[] arr1 = [ 1, 2, 3, 4 ]; + int[] arr2 = [ 5, 6 ]; + auto squares = map!("a * a")(arr1, arr2); + assert(squares == [ 1, 4, 9, 16, 25, 36 ]); + + short[] arr = [ 1, 2 ]; + auto squares2 = map!("a * a")(cast(int[])null, arr); + assert(is(typeof(squares2) == int[])); } -/** - Swaps $(D_PARAM lhs) and $(D_PARAM rhs). -*/ -void swap(T)(ref T lhs, ref T rhs) +// reduce +private template NxNHelper(F...) { - auto t = lhs; - lhs = rhs; - rhs = t; + private template For(Args...) + { + enum uint fs = TypeTuple!(F).length; + static assert( + fs, + "reduce: too few arguments. You must pass at least a function"); + static assert( + Args.length > fs, + "reduce: too few arguments. You must pass one seed for" + " each function (total "~ToString!(fs)~")" + ", followed by the ranges to operate on."); + // Result type + static if (F.length > 1) + alias Tuple!(Args[0 .. F.length]) Result; + else + alias Args[0] Result; + + // Element type + enum functions = F.length; + //alias typeof(*Args[functions]) Element; + + // Apply predicate + R apply(uint n, R, E)(R a, E b) + { + alias typeof(F[n]) thisFun; + static if (is(typeof(thisFun~""))) // (!is(typeof(F[n](a, b)))) + { + return binaryFun!(""~F[n])(a, b); + } + else + { + return F[n](a, b); + } + } + } } /** -Implements C.A.R. Hoare's -$(LINK2 http://en.wikipedia.org/wiki/Selection_algorithm#Partition-based_general_selection_algorithm, -partition) algorithm. Specifically, reorders the range [$(D_PARAM -left), $(D_PARAM right)$(RPAREN) such that everything strictly smaller -(according to the predicate $(D_PARAM compare)) than $(D_PARAM *mid) -is to the left of the returned pointer, and everything else is at the -right of the returned pointer. - -Precondition: - -$(D_PARAM left == mid && mid == right -|| -left <= mid && mid < right). - -Returns: - -If $(D_PARAM left == right), returns $(D_PARAM left). Otherwise, -return a value $(D_PARAM p) such that the following three conditions -are simultaneously true: -$(OL -$(LI $(D_PARAM *p == *mid)) -$(LI $(D_PARAM compare(*p1, *p)) for all $(D_PARAM p1) in [$(D_PARAM -left), $(D_PARAM p)$(RPAREN)) -$(LI $(D_PARAM !compare(*p2, *p)) for all $(D_PARAM p2) in [$(D_PARAM p), -$(D_PARAM right)$(RPAREN))) +Implements the homonym function (also known as $(D accumulate), $(D +compress), $(D inject), or $(D foldl)) present in various programming +languages of functional flavor. The call $(D reduce!(fun)(seed, +range)) first assigns $(D seed) to an internal variable $(D result), +also called the accumulator. Then, for each element $(D x) in $(D +range), $(D result = fun(result, x)) gets evaluated. Finally, $(D +result) is returned. Many aggregate range operations turn out to be +solved with $(D reduce) quickly and easily. The example below +illustrates $(D reduce)'s remarkable power and flexibility. Example: - ---- -auto a = [3, 3, 2].dup; -p = partition!(less)(a.ptr, a.ptr, a.ptr + a.length); -assert(p == a.ptr + 1 && a == [2, 3, 3]); +int[] arr = [ 1, 2, 3, 4, 5 ]; +// Sum all elements +auto sum = reduce!("a + b")(0, arr); +assert(sum == 15); + +// Compute the maximum of all elements +auto largest = reduce!(max)(arr[0], arr[1 .. $]); +assert(largest == 5); + +// Compute the number of odd elements +auto odds = reduce!("a + (b & 1)")(0, arr); +assert(odds == 3); + +// Compute the sum of squares +auto ssquares = reduce!("a + b * b")(0, arr); +assert(ssquares == 55); +---- + +$(DDOC_SECTION_H Multiple ranges:) It is possible to pass any number +of ranges to $(D reduce), as in $(D reduce!(fun)(seed, range1, range2, +range3)). Then $(D reduce) will simply apply its algorithm in +succession to each range, from left to right. + +Example: +---- +int[] a = [ 3, 4 ]; +int[] b = [ 100 ]; +auto r = reduce!("a + b")(0, a, b); +assert(r == 107); + +// Mixing convertible types is fair game, too +double[] c = [ 2.5, 3.0 ]; +auto r1 = reduce!("a + b")(0.0, a, b, c); +assert(r1 == 112.5); +---- + +$(DDOC_SECTION_H Multiple functions:) Sometimes it is very useful to +compute multiple aggregates in one pass. One advantage is that the +computation is faster because the looping overhead is shared. That's +why $(D reduce) accepts multiple functions. If two or more functions +are passed, $(D reduce) returns a $(XREF typecons, Tuple) object with +one member per passed-in function. The number of seeds must be +correspondingly increased. + +Example: +---- +double[] a = [ 3.0, 4, 7, 11, 3, 2, 5 ]; +// Compute minimum and maximum in one pass +auto r = reduce!(min, max)(double.max, -double.max, a); +// The type of r is Tuple!(double, double) +assert(r._0 == 2); // minimum +assert(r._1 == 11); // maximum + +// Compute sum and sum of squares in one pass +r = reduce!("a + b", "a + b * b")(0.0, 0.0, a); +assert(r._0 == 35); // sum +assert(r._1 == 233); // sum of squares +// Compute average and standard deviation from the above +auto avg = r._0 / a.length; +auto stdev = sqrt(r._1 / a.length - avg * avg); +---- + +$(DDOC_SECTION_H Multiple ranges and functions:) The most general form +of $(D reduce) accepts multiple functions and multiple ranges +simultaneously. The call $(D reduce!(fun1, ..., funN)(seed1, ..., +seedN, range1, ..., rangeM)) applies the reduction algorithm for all +functions and all ranges. + +Example: +---- +int[] a = [ 3, 4, 7, 11, 3, 2, 5 ]; +double[] b = [ 2.5, 4, -4.5, 2, 10.9 ]; +// Compute minimum and maximum in one pass over a and b +auto r = reduce!(min, max)(double.max, -double.max, a, b); +assert(r._0 == -4.5); // minimum +assert(r._1 == 11); // maximum ---- */ -template partition(alias compare) + +template reduce(F...) { - /// - T partition(T)(T left, T mid, T right) + NxNHelper!(F).For!(Args).Result reduce(Args...)(Args args) { - if (left == right) return left; - assert(left <= mid && mid < right); - auto pivot = *mid; - --right; - swap(*mid, *right); // Move pivot to end - auto result = left; - for (auto i = left; i != right; ++i) { - if (!compare(*i, pivot)) continue; - swap(*result, *i); - ++result; + alias NxNHelper!(F).For!(Args) Aux; + typeof(return) result; + // Prime the result + static if (F.length > 1) + { + foreach (j, f; F) // for all functions + { + // @@@BUG@@@ + auto p = mixin("&result.field!("~ToString!(j)~")"); + *p = args[j]; + } + } + else + { + result = args[0]; + } + // Accumulate + foreach (i, range; args[F.length .. $]) // all inputs + { + foreach (it; begin(range) .. end(range)) // current input + { + // @@@BUG@@@ + //foreach (j, f; F) // for all functions + foreach (j, unused; Args[0 .. F.length]) // for all functions + { + static if (F.length > 1) + { + // @@@BUG@@@ + auto p = mixin("&result.field!("~ToString!(j)~")"); + } + else + { + auto p = &result; + } + *p = Aux.apply!(j, typeof(*p), typeof(*it))(*p, *it); + } + } } - swap(*right, *result); // Move pivot to its final place - assert(*result == pivot); return result; } } unittest { - int[] a = null; - auto p = partition!(less)(a.ptr, a.ptr, a.ptr + a.length); - assert(p is null); + int[] a = [ 3, 4 ]; + auto r = reduce!("a + b")(0, a); + assert(r == 7); + r = reduce!(min)(int.max, a); + assert(r == 3); + double[] b = [ 100 ]; + auto r1 = reduce!("a + b")(0.0, a, b); + assert(r1 == 107); - a = [2].dup; - p = partition!(less)(a.ptr, a.ptr, a.ptr + a.length); - assert(p == a.ptr); - - a = [2, 2].dup; - p = partition!(less)(a.ptr, a.ptr, a.ptr + a.length); - assert(p == a.ptr); - - p = partition!(less)(a.ptr, a.ptr + 1, a.ptr + a.length); - assert(p == a.ptr); - - a = [3, 3, 2].dup; - p = partition!(less)(a.ptr, a.ptr, a.ptr + a.length); - assert(p == a.ptr + 1); + a = [ 1, 2, 3, 4, 5 ]; + // Stringize with commas + string rep = reduce!("a ~ `, ` ~ to!(string)(b)")(cast(string) null, a); + assert(rep[2 .. $] == "1, 2, 3, 4, 5"); } +// filter /** - Reorders the range [$(D_PARAM b), $(D_PARAM e)$(RPAREN) such that - $(D_PARAM nth) points to the element that would fall there if the - range were fully sorted. Effectively, it finds the nth smallest - (according to $(D_PARAM compare)) element in the range [$(D_PARAM b), - $(D_PARAM e)$(RPAREN). - -Example: +Implements the homonym function present in various programming +languages of functional flavor. The call $(D filter!(fun)(range)) +returns a new range only containing elements $(D x) in $(D r) for +which $(pred(x)) is $(D true). +Example: ---- -auto v = ([ 25, 7, 9, 2, 0, 5, 21 ]).dup; -auto n = 4; -nthElement!(less)(v.ptr, v.ptr + n, v.ptr + v.length); -assert(v[n] == 9); +int[] arr = [ 1, 2, 3, 4, 5 ]; +// Sum all elements +auto small = filter!("a < 3")(arr); +assert(small == [ 1, 2 ]); +---- + +$(DDOC_SECTION_H Multiple ranges:) It is possible to pass any number +of ranges to $(D filter), as in $(D filter!(fun)(range1, range2, +range3)). Then $(D filter) will simply apply its algorithm in +succession to each range, from left to right. The type returned is +that of the first range. + +Example: +---- +int[] a = [ 3, -2, 400 ]; +int[] b = [ 100, -101, 102 ]; +auto r = filter!("a > 0")(a, b); +assert(r == [ 3, 400, 100, 102 ]); + +// Mixing convertible types is fair game, too +double[] c = [ 2.5, 3.0 ]; +auto r1 = filter!("cast(int) a != a")(c, a, b); +assert(r1 == [ 2.5 ]); ---- */ -template nthElement(alias compare) + +Ranges[0] filter(alias pred, Ranges...)(Ranges rs) { - /// - void nthElement(T)(T b, T nth, T e) + typeof(return) result; + // Accumulate + foreach (i, range; rs[0 .. $]) // all inputs { - assert(b <= nth && nth < e); - for (;;) { - auto pivot = b + (e - b) / 2; - pivot = partition!(compare)(b, pivot, e); - if (pivot == nth) return; - if (pivot < nth) b = pivot + 1; - else e = pivot; + foreach (it; begin(range) .. end(range)) // current input + { + if (pred(*it)) result ~= *it; } } + return result; +} + +Ranges[0] filter(string pred, Ranges...)(Ranges rs) +{ + return .filter!(unaryFun!(pred), Ranges)(rs); +} + +unittest +{ + int[] a = [ 3, 4 ]; + auto r = filter!("a > 3")(a); + assert(r == [ 4 ]); + + a = [ 1, 22, 3, 42, 5 ]; + auto under10 = filter!("a < 10")(a); + assert(under10 == [1, 3, 5]); +} + +// inPlace +/** +Similar to $(D map), but it manipulates the passed-in ranges in place +and returns $(D void). The call $(D inPlace!(fun)(range1, range2, ..., +rangeN)) applies $(D fun(x)) left to right for all $(D ref x) in $(D +range1), then all $(D ref x) in $(D range2), ..., all $(D ref x) in +$(D rangeN). + +Example: +---- +int[] arr1 = [ 1, 2, 3 ]; +inPlace!(writeln)(arr1); // print the array +double[] arr2 = [ 4.0, 8.5, 13 ]; +inPlace!("++a")(arr1, arr2); +assert(arr1 == [ 2, 3, 4 ]); +assert(arr2 == [ 5.0, 9.5, 14 ]); +---- +*/ +void inPlace(alias fun, Range, Ranges...)(Range r, Ranges rs) +// @@@BUG@@ This should work: +// void inPlace(alias fun, Ranges...)(Ranges rs) +{ + foreach (j; begin(r) .. end(r)) fun(*j); + foreach (i, x; rs) + { + foreach (j; begin(x) .. end(x)) fun(*j); + } +} + +/// Ditto +void inPlace(string fun, Ranges...)(Ranges rs) +{ + return .inPlace!(unaryFun!(fun, true), Ranges)(rs); +} + +unittest +{ + // fill with 42 + int[] a = [ 1, 2 ]; + double[] b = [ 2, 4 ]; + inPlace!("a = 42")(a); + assert(a[0] == 42 && a[1] == 42); + //assert(b[0] == 42 && b[1] == 42); + + int[] arr1 = [ 1, 2, 3 ]; + double[] arr2 = [ 4.0, 8.5, 13 ]; + inPlace!("++a")(arr1, arr2); + assert(arr1 == [ 2, 3, 4 ]); + assert(arr2 == [ 5.0, 9.5, 14 ]); +} + +// move +/** +Moves $(D source) into $(D target) via a destructive +copy. Specifically: $(UL $(LI If $(D hasAliasing!(T)) is true (see +$(XREF traits, hasAliasing)), then the representation of $(D source) +is bitwise copied into $(D target) and then $(D source = T.init) is +evaluated.) $(LI Otherwise, $(D target = source) is evaluated.)) See +also $(XREF contracts, pointsTo). + +Preconditions: +$(D !pointsTo(source, source)) +*/ +void move(T)(ref T source, ref T target) +{ + assert(!pointsTo(source, source)); + static if (hasAliasing!(T)) + { + static if (is(T == class)) + { + target = source; + } + else + { + memcpy(&target, &source, target.sizeof); + } + source = T.init; + } + else + { + target = source; + } } unittest { - scope(failure) writeln(stderr, "Failure testing algorithm"); - auto v = ([ 25, 7, 9, 2, 0, 5, 21 ]).dup; - auto n = 4; - nthElement!(less)(v.ptr, v.ptr + n, v.ptr + v.length); - assert(v[n] == 9); - // - v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; - n = 3; - nthElement!(less)(v.ptr, v.ptr + n, v.ptr + v.length); - assert(v[n] == 3); - // - v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; - n = 1; - nthElement!(less)(v.ptr, v.ptr + n, v.ptr + v.length); - assert(v[n] == 2); - // - v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; - n = v.length - 1; - nthElement!(less)(v.ptr, v.ptr + n, v.ptr + v.length); - assert(v[n] == 7); - // - v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; - n = 0; - nthElement!(less)(v.ptr, v.ptr + n, v.ptr + v.length); - assert(v[n] == 1); + Object obj1 = new Object; + Object obj2 = obj1; + Object obj3; + move(obj2, obj3); + assert(obj2 is null && obj3 is obj1); + + 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); + + struct S2 { int a = 1; int * b; } + S2 s21 = { 10, new int }; + S2 s22; + move(s21, s22); + assert(s21.a == 1 && s21.b == null && s22.a == 10 && s22.b != null); +} + +// swap +/** +Swaps $(D lhs) and $(D rhs). See also $(XREF contracts, pointsTo). + +Preconditions: + +$(D !pointsTo(lhs, lhs) && !pointsTo(lhs, rhs) && !pointsTo(rhs, lhs) +&& !pointsTo(rhs, rhs)) +*/ +void swap(T)(ref T lhs, ref T rhs) +{ + assert(!pointsTo(lhs, lhs) && !pointsTo(lhs, rhs) + && !pointsTo(rhs, lhs) && !pointsTo(rhs, rhs)); + auto t = lhs; + lhs = rhs; + rhs = t; +} + +// overwriteAdjacent +/** +Reduces $(D r) by shifting it to the left until no adjacent elements +$(D a), $(D b) remain in $(D r) such that $(D pred(a, b)). Shifting is +performed by evaluating $(D move(source, target)) as a primitive. The +algorithm is stable and runs in $(BIGOH r.length) time. Returns the +reduced range. + +The default $(XREF _algorithm, move) performs a potentially +destructive assignment of $(D source) to $(D target), so the objects +beyond the returned range should be considered "empty". By default $(D +pred) compares for equality, in which case $(D overwriteAdjacent) +collapses adjacent duplicate elements to one (functionality akin to +the $(WEB wikipedia.org/wiki/Uniq, uniq) system utility). + +Example: +---- +int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; +auto r = overwriteAdjacent(arr); +assert(r == [ 1, 2, 3, 4, 5 ]); +---- +*/ +Range overwriteAdjacent(alias pred, alias move, Range)(Range r) +{ + if (isEmpty(r)) return r; + auto target = begin(r), e = end(r); + foreach (source; target + 1 .. e) + { + if (!pred(*target, *source)) + { + ++target; + continue; + } + // found an equal *source and *target + for (;;) + { + move(*source, *target); + ++source; + if (source == e) break; + if (!pred(*target, *source)) ++target; + } + break; + } + return range(begin(r), target + 1); +} + +/// Ditto +Range overwriteAdjacent( + string fun = "a == b", + alias move = .move, + Range)(Range r) +{ + return .overwriteAdjacent!(binaryFun!(fun), move, Range)(r); +} + +unittest +{ + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + auto r = overwriteAdjacent(arr); + assert(r == [ 1, 2, 3, 4, 5 ]); + assert(arr == [ 1, 2, 3, 4, 5, 3, 4, 4, 4, 5 ]); + +} + +// find +/** +Finds the first occurrence of $(D needle) in $(D haystack) by linear +search and returns an iterator to it. An optional binary predicate +$(D pred) instructs $(D find) on how to perform the comparison (with +the current collection element in the first position and $(D needle) +in the second position). By default, comparison is for +equality. Performs $(BIGOH haystack.length) evaluations of $(D +pred). See also $(WEB sgi.com/tech/stl/_find.html, STL's _find). + +To find the last occurence of $(D needle) in $(D haystack), call $(D +find(retro(haystack), needle)) and compare the result against $(D +rEnd(haystack)). See also $(XREF iterator, retro). + +Example: +---- +auto a = [ 1, 2, 3 ]; +assert(find(a, 5) == end(a)); // not found +assert(find(a, 2) == begin(a) + 1); // found + +// Case-insensitive find of a string +string[] s = [ "Hello", "world", "!" ]; +assert(find!("toupper(a) == toupper(b)")(s, "hello") == begin(s)); +---- +*/ +Iterator!(Range) find(alias pred, Range, E)(Range haystack, E needle) +{ + //foreach (i; begin(haystack) .. end(haystack)) + for (auto i = begin(haystack); i != end(haystack); ++i) + { + if (pred(*i, needle)) return i; + } + return end(haystack); +} + +/// Ditto +Iterator!(Range) find(string pred = "a == b", Range, E)( + Range haystack, E needle) +{ + return .find!(binaryFun!(pred), Range, E)(haystack, needle); +} + +unittest +{ + int[] a = [ 1, 2, 3 ]; + assert(find(a, 5) == end(a)); + assert(find(a, 2) == begin(a) + 1); + + foreach (T; TypeTuple!(int, double)) + { + auto b = rndstuff!(T)(); + if (!b.length) continue; + b[$ / 2] = 200; + b[$ / 4] = 200; + assert(find(b, 200) == begin(b) + b.length / 4); + } + +// Case-insensitive find of a string + string[] s = [ "Hello", "world", "!" ]; + assert(find!("toupper(a) == toupper(b)")(s, "hello") == begin(s)); +} + +unittest +{ + int[] a = [ 1, 2, 3, 2, 6 ]; + assert(find(retro(a), 5) == rEnd(a)); + assert(find(retro(a), 2) == rBegin(a) + 1); + + foreach (T; TypeTuple!(int, double)) + { + auto b = rndstuff!(T)(); + if (!b.length) continue; + b[$ / 2] = 200; + b[$ / 4] = 200; + assert(find(retro(b), 200) == rBegin(b) + (b.length - 1) / 2); + } } /** - Reverses $(D_PARAM range) in-place. +Finds the first element in a range satisfying the unary predicate $(D +pred). Performs $(BIGOH haystack.length) evaluations of $(D pred). See +also $(WEB sgi.com/tech/stl/find_if.html, STL's find_if). + +To find the last element of $(D haystack) satisfying $(D pred), call +$(D find!(pred)(retro(haystack))) and compare the result against $(D +rEnd(haystack)). See also $(XREF iterator, retro). + +Example: +---- +auto arr = [ 1, 2, 3 ]; +assert(find!("a > 2")(arr) == end(arr) - 1); + +// with predicate alias +bool pred(int x) { return x + 1 > 1.5; } +assert(find!(pred)(arr) == begin(arr)); +---- */ -void reverse(T)(T range) +Iterator!(Range) find(alias pred, Range)(Range haystack) { - auto len = range.length; - const limit = len / 2; - --len; - for (uint i = 0; i != limit; ++i) + foreach (i; begin(haystack) .. end(haystack)) { - auto t = range[i]; - range[i] = range[len - i]; - range[len - i] = t; + if (pred(*i)) return i; + } + return end(haystack); +} + +/// Ditto +Iterator!(Range) find(string pred, Range)(Range haystack) +{ + return find!(unaryFun!(pred), Range)(haystack); +} + +unittest +{ + auto a = [ 1, 2, 3 ]; + assert(find!("a > 2")(a) == end(a) - 1); + bool pred(int x) { return x + 1 > 1.5; } + assert(find!(pred)(a) == begin(a)); +} + +// findRange +/** +Finds the first occurrence of $(D subseq) in $(D seq) by repeated +linear searches. Performs $(BIGOH seq.length * subseq.length) +evaluations of $(D pred), which makes it unrecommended for very large +ranges, for which $(XREF algorithm, findBoyerMoore) may be more +appropriate. See also $(WEB sgi.com/tech/stl/search.html, STL's +search). + +Example: +---- +int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; +int[] b = [ 1, 2, 3 ]; +assert(findRange(a, b) == begin(a) + 2); +assert(findRange(b, a) == end(b)); +---- +*/ +Iterator!(Range1) findRange(alias pred, Range1, Range2)( + Range1 seq, Range2 subseq) +{ + auto e1 = end(seq); + if (seq.length < subseq.length) return e1; + auto e11 = e1 - subseq.length + 1; + auto e2 = end(subseq); + foreach (i; begin(seq) .. e11) + { + auto m = mismatch!(pred)(range(i, e1), subseq); + if (m._1 == e2) return i; + } + return e1; +} + +/// Ditto +Iterator!(Range1) findRange( + string pred = q{a == b}, Range1, Range2)(Range1 seq, Range2 subseq) +{ + return .findRange!(binaryFun!(pred), Range1, Range2)(seq, subseq); +} + +unittest +{ + int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + assert(findRange(a, b) == begin(a) + 2); + assert(findRange(b, a) == end(b)); +} + +// findBoyerMoore +private struct BoyerMooreFinder(alias pred, Range) +{ +private: + size_t skip[]; + int[typeof(Range[0])] occ; + Range needle; + + int occurrence(char c) + { + auto p = c in occ; + return p ? *p : -1; + } + +/* This helper function checks, whether the last "portion" bytes + * of "needle" (which is "nlen" bytes long) exist within the "needle" + * at offset "offset" (counted from the end of the string), + * and whether the character preceding "offset" is not a match. + * Notice that the range being checked may reach beyond the + * beginning of the string. Such range is ignored. + */ + static bool needlematch(R)(R needle, + size_t portion, size_t offset) + { + int virtual_begin = needle.length - offset - portion; + int ignore = 0; + if (virtual_begin < 0) { + ignore = -virtual_begin; + virtual_begin = 0; + } + if (virtual_begin > 0 + && needle[virtual_begin - 1] == needle[$ - portion - 1]) + return 0; + + invariant delta = portion - ignore; + return equal(range(end(needle) - delta, end(needle)), + range(begin(needle) + virtual_begin, + begin(needle) + virtual_begin + delta)); + } + +public: + static BoyerMooreFinder opCall(Range needle) + { + BoyerMooreFinder result; + if (!needle.length) return result; + result.needle = needle; + /* Populate table with the analysis of the needle */ + /* But ignoring the last letter */ + foreach (i, n ; needle[0 .. $ - 1]) + { + result.occ[n] = i; + } + /* Preprocess #2: init skip[] */ + /* Note: This step could be made a lot faster. + * A simple implementation is shown here. */ + result.skip = new size_t[needle.length]; + foreach (a; 0 .. needle.length) + { + size_t value = 0; + while (value < needle.length + && !needlematch(needle, a, value)) + { + ++value; + } + result.skip[needle.length - a - 1] = value; + } + return result; + } + + Iterator!(Range) inspect(Range haystack) + { + if (!needle.length) return begin(haystack); + if (needle.length > haystack.length) return end(haystack); + /* Search: */ + auto limit = haystack.length - needle.length; + for (size_t hpos = 0; hpos <= limit; ) + { + size_t npos = needle.length - 1; + while (pred(needle[npos], haystack[npos+hpos])) + { + if (npos == 0) return begin(haystack) + hpos; + --npos; + } + hpos += max(skip[npos], npos - occurrence(haystack[npos+hpos])); + } + return end(haystack); + } + + size_t length() + { + return needle.length; + } +} + +/** +Finds the first occurrence of $(D subseq) in $(D seq) by using the +$(WEB www-igm.univ-mlv.fr/~lecroq/string/node14.html, Boyer-Moore +algorithm). The algorithm has an upfront cost but scales sublinearly, +so it is most suitable for large sequences. Performs $(BIGOH +seq.length) evaluations of $(D pred) in the worst case and $(BIGOH +seq.length / subseq.length) evaluations in the best case. + +Example: +---- +int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; +int[] b = [ 1, 2, 3 ]; +assert(findBoyerMoore(a, b) == begin(a) + 2); +assert(findBoyerMoore(b, a) == end(b)); +---- + +BUGS: + +Should cache the scaffolding built for the last $(D subseq) in +thread-safe storage so it is not rebuilt repeatedly. +*/ +Iterator!(Range) findBoyerMoore(alias pred, Range)(Range seq, + Range subseq) +{ + return BoyerMooreFinder!(pred, Range)(subseq).inspect(seq); +} + +/// Ditto +Iterator!(Range) findBoyerMoore(string pred = q{a == b}, Range)( + Range seq, Range subseq) +{ + return .findBoyerMoore!(binaryFun!(pred), Range)(seq, subseq); +} + +unittest +{ + debug(string) writefln("Boyer-Moore implementation unittest\n"); + string h = "/homes/aalexand/d/dmd/bin/../lib/libphobos.a(dmain2.o)" + "(.gnu.linkonce.tmain+0x74): In function `main' undefined reference" + " to `_Dmain':"; + string[] ns = ["libphobos", "function", " undefined", "`", ":"]; + foreach (n ; ns) { + auto p = findBoyerMoore(h, n); + assert(p != end(h) && range(p, p + n.length) == n); + } + + int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + assert(findBoyerMoore(a, b) == begin(a) + 2); + assert(findBoyerMoore(b, a) == end(b)); +} + +// findAdjacent +/** +Finds the first two adjacent elements $(D a), $(D b) in the range $(D +r) that satisfy $(D pred(a, b)). Performs $(BIGOH r.length) +evaluations of $(D pred). See also $(WEB +sgi.com/tech/stl/adjacent_find.html, STL's adjacent_find). + +Example: +---- +int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ]; +auto p = findAdjacent(a); +assert(p == begin(a) + 1); +p = findAdjacent!("a < b")(a); +assert(p == begin(a) + 6); +---- +*/ +Iterator!(Range) findAdjacent(alias pred, Range)(Range r) +{ + auto first = begin(r), last = end(r); + auto next = first; + if (first != last) + { + for (++next; next != last; ++first, ++next) + if (pred(*first, *next)) return first; + } + return last; +} + +/// Ditto +Iterator!(Range) findAdjacent(string pred = q{a == b}, Range)(Range r) +{ + return .findAdjacent!(binaryFun!(pred), Range)(r); +} + +unittest +{ + int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ]; + auto p = findAdjacent(a); + assert(p == begin(a) + 1); + p = findAdjacent!("a < b")(a); + assert(p == begin(a) + 6); +} + +// findAmong +/** +Finds the first element in $(D seq) that compares equal (according to +$(D pred)) with some element in $(D choices). Choices are sought by +linear search. Performs $(BIGOH seq.length * choices.length) +evaluations of $(D pred). See also $(WEB +sgi.com/tech/stl/find_first_of.html, STL's find_first_of). + +Example: +---- +int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; +int[] b = [ 3, 1, 2 ]; +assert(findAmong(a, b) == begin(a) + 2); +assert(findAmong(b, a) == begin(b)); +---- +*/ +Iterator!(Range1) findAmong(alias pred, Range1, Range2)( + Range1 seq, Range2 choices) +{ + foreach (i, e; seq) + { + if (find!(pred)(choices, e) != end(choices)) return begin(seq) + i; + } + return end(seq); +} + +/// Ditto +Iterator!(Range1) findAmong( + string pred = q{a == b}, Range1, Range2)(Range1 seq, Range2 choices) +{ + return .findAmong!(binaryFun!(pred), Range1, Range2)(seq, choices); +} + +unittest +{ + int[] a = [ -1, 0, 2, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + assert(findAmong(a, b) == begin(a) + 2); + assert(findAmong(b, [ 4, 6, 7 ]) == end(b)); +} + +// findAmongSorted +/** +Finds the first element $(D x) in $(D seq) that compares equal with +some element $(D y) in $(D choices) (meaning $(D !less(x, y) && +!less(y, x))). The $(D choices) range is sought by binary +search. Consequently $(D choices) is assumed to be sorted according to +$(D pred), which by default is $(D "a < b"). Performs $(BIGOH +seq.length * log(choices.length)) evaluations of $(D less). + +To find the last element of $(D seq) instead of the first, call $(D +findAmongSorted(retro(seq), choices)) and compare the result against +$(D rEnd(seq)). See also $(XREF iterator, retro). + +Example: +---- +int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; +int[] b = [ 1, 2, 3 ]; +assert(findAmongSorted(a, b) == begin(a) + 2); +assert(findAmongSorted(b, a) == end(b)); +---- +*/ +Iterator!(Range1) findAmongSorted(alias less, Range1, Range2)( + Range1 seq, in Range2 choices) +{ + assert(isSorted!(less)(choices)); + foreach (i, e; seq) + { + if (canFindSorted!(less)(choices, e)) return begin(seq) + i; + } + return end(seq); +} + +/// Ditto +Iterator!(Range1) findAmongSorted( + string less = q{a < b}, Range1, Range2)(Range1 seq, in Range2 subseq) +{ + return .findAmongSorted!(binaryFun!(less), Range1, Range2)(seq, subseq); +} + +unittest +{ + int[] a = [ -1, 0, 2, 1, 2, 3, 4, 5 ]; + int[] b = [ 1, 2, 3 ]; + assert(findAmongSorted(a, b) == begin(a) + 2); + assert(findAmongSorted(b, [ 4, 6, 7 ]) == end(b)); +} + +// canFind +/** +Convenience functions returning $(D true) if and only if the +corresponding $(D find*) functions return an iterator different from +$(D end(r)). They are handy in the numerous situations when the +success of the $(D find*) functions is queried but the actual position +found is unimportant. + +Example: +---- +int[] a = [ -1, 0, 1, 2, 3, 4, 5 ]; +assert(canFind(a, 4)); +assert(!canFind(a, 10)); +assert(canFind!("a - 1 < b")(a, 4)); +assert(!canFind!("a > 5")(a)); +---- +*/ +bool canFind(alias pred, Range, E)(Range haystack, E needle) +{ + return find!(pred)(haystack, needle) != end(haystack); +} + +/// Ditto +bool canFind(string pred = q{a == b}, Range, E)(Range haystack, E needle) +{ + return find!(pred)(haystack, needle) != end(haystack); +} + +/// Ditto +bool canFind(alias pred, Range, E)(Range haystack) +{ + return find!(pred)(haystack) != end(haystack); +} + +/// Ditto +bool canFind(string pred, Range, E)(Range haystack) +{ + return find!(pred)(haystack) != end(haystack); +} + +/// Ditto +bool canFindAmong(alias pred, Range1, Range2)(Range seq, Range2 choices) +{ + return findAmong!(pred)(seq, choices) != end(seq); +} + +/// Ditto +bool canFindAmong(string pred, Range1, Range2)(Range seq, Range2 choices) +{ + return findAmong!(pred)(seq, choices) != end(seq); +} + +/// Ditto +bool canFindAmongSorted(alias pred, Range1, Range2)(Range seq, Range2 choices) +{ + return canFindAmongSorted!(pred)(seq, choices) != end(seq); +} + +/// Ditto +bool canFindAmongSorted(string pred, Range1, Range2)( + Range seq, Range2 choices) +{ + return canFindAmongSorted!(pred)(seq, choices) != end(seq); +} + +// count +/** +Counts the number of elements $(D x) in $(D r) for which $(D pred(x, +value)) is $(D true). $(D pred) defaults to equality. Performs $(BIGOH +r.length) evaluations of $(D pred). + +Example: +---- +int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; +assert(count(a, 2) == 3); +assert(count!("a > b")(a, 2) == 5); +---- +*/ + +size_t count(alias pred, Range, E)(Range r, E value) +{ + bool pred2(typeof(*begin(r)) a) { return pred(a, value); } + return count!(pred2)(r); +} + +/// Ditto +size_t count(string pred = "a == b", Range, E)( + Range r, E value) +{ + return count!(binaryFun!(pred), Range, E)(r, value); +} + +unittest +{ + int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; + assert(count(a, 2) == 3); + assert(count!("a > b")(a, 2) == 5); +} + +/** +Counts the number of elements $(D x) in $(D r) for which $(D pred(x)) +is $(D true). Performs $(BIGOH r.length) evaluations of $(D pred). + +Example: +---- +int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; +assert(count!("a > 1")(a) == 8); +---- +*/ +size_t count(alias pred, Range)(Range r) +{ + size_t result; + foreach (i; begin(r) .. end(r)) + { + if (pred(*i)) ++result; + } + return result; +} + +/// Ditto +size_t count(string pred, Range)(Range r) +{ + return count!(unaryFun!(pred), Range)(r); +} + +unittest +{ + int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ]; + assert(count!("a == 3")(a) == 2); +} + +// equal +/** +Returns $(D true) if and only if the two ranges compare equal element +for element, according to binary predicate $(D pred). The ranges may +have different element types, as long as $(D pred(a, b)) evaluates to +$(D bool) for $(D a) in $(D r1) and $(D b) in $(D r2). Performs +$(BIGOH min(r1.length, r2.length)) evaluations of $(D pred). See also +$(WEB sgi.com/tech/stl/_equal.html, STL's equal). + +Example: +---- +int[] a = [ 1, 2, 4, 3 ]; +assert(!equal(a, a[1..$])); +assert(equal(a, a)); + +// different types +double[] b = [ 1., 2, 4, 3]; +assert(!equal(a, b[1..$])); +assert(equal(a, b)); + +// predicated: ensure that two vectors are approximately equal +double[] c = [ 1.005, 2, 4, 3]; +assert(equal!(approxEqual)(b, c)); +---- +*/ +bool equal(alias pred, Range1, Range2)(Range1 r1, Range2 r2) +{ + if (r1.length != r2.length) return false; + auto result = mismatch!(pred)(r1, r2); + return result._0 == end(r1) && result._1 == end(r2); +} + +/// Ditto +bool equal(string pred = q{a == b}, Range1, Range2)(Range1 r1, Range2 r2) +{ + return equal!(binaryFun!(pred), Range1, Range2)(r1, r2); +} + +unittest +{ + int[] a = [ 1, 2, 4, 3]; + assert(!equal(a, a[1..$])); + assert(equal(a, a)); + // test with different types + double[] b = [ 1., 2, 4, 3]; + assert(!equal(a, b[1..$])); + assert(equal(a, b)); + + // predicated + double[] c = [ 1.005, 2, 4, 3]; + assert(equal!(approxEqual)(b, c)); +} + +// overlap +/** +Returns the overlapping range, if any, of two ranges. Unlike $(D +equal), $(D overlap) only compares the iterators in the ranges, not +the values referred by them. If $(D r1) and $(D r2) have an +overlapping range, returns that range. Otherwise, returns an empty +range. Performs $(BIGOH min(r1.length, r2.length)) iterator increment +operations and comparisons if the ranges are forward, and $(BIGOH 1) +operations if the ranges have random access. + +Example: +---- +int[] a = [ 10, 11, 12, 13, 14 ]; +int[] b = a[1 .. 3]; +assert(overlap(a, b) == [ 11, 12 ]); +b = b.dup; +// overlap disappears even though the content is the same +assert(isEmpty(overlap(a, b))); +---- +*/ +Range overlap(Range)(Range r1, Range r2) +{ + auto b = max(begin(r1), begin(r2)); + auto e = min(end(r1), end(r2)); + return range(b, max(b, e)); +} + +unittest +{ + int[] a = [ 10, 11, 12, 13, 14 ]; + int[] b = a[1 .. 3]; + a[1] = 100; + assert(overlap(a, b) == [ 100, 12 ]); +} + +// min +/** +Returns the minimum of the passed-in values. The type of the result is +computed by using $(XREF traits, CommonType). +*/ +CommonType!(T1, T2, T) min(T1, T2, T...)(T1 a, T2 b, T xs) +{ + static if (T.length == 0) + { + return b < a ? b : a; + } + else + { + return min(min(a, b), xs); + } +} + +unittest +{ + int a = 5; + short b = 6; + double c = 2; + auto d = min(a, b); + assert(is(typeof(d) == int)); + assert(d == 5); + auto e = min(a, b, c); + assert(is(typeof(e) == double)); + assert(e == 2); +} + +// max +/** +Returns the maximum of the passed-in values. The type of the result is +computed by using $(XREF traits, CommonType). + +Example: +---- +int a = 5; +short b = 6; +double c = 2; +auto d = max(a, b); +assert(is(typeof(d) == int)); +assert(d == 6); +auto e = min(a, b, c); +assert(is(typeof(e) == double)); +assert(e == 2); +---- +*/ +CommonType!(T1, T2, T) max(T1, T2, T...)(T1 a, T2 b, T xs) +{ + static if (T.length == 0) + { + return b > a ? b : a; + } + else + { + return max(max(a, b), xs); + } +} + +unittest +{ + int a = 5; + short b = 6; + double c = 2; + auto d = max(a, b); + assert(is(typeof(d) == int)); + assert(d == 6); + auto e = max(a, b, c); + assert(is(typeof(e) == double)); + assert(e == 6); +} + +// mismatch +/** +Sequentially compares elements in $(D r1) and $(D r2) in lockstep, and +stops at the first mismatch (according to $(D pred), by default +equality). Returns a tuple with the iterators that refer to the two +mismatched values. Performs $(BIGOH min(r1.length, r2.length)) +evaluations of $(D pred). See also $(WEB +sgi.com/tech/stl/_mismatch.html, STL's mismatch). + +Example: +---- +int[] x = [ 1, 5, 2, 7, 4, 3 ]; +double[] y = [ 1., 5, 2, 7.3, 4, 8 ]; +auto m = mismatch(x, y); +assert(m._0 == begin(x) + 3); +assert(m._1 == begin(y) + 3); +---- +*/ + +Tuple!(Iterator!(Range1), Iterator!(Range2)) +mismatch(alias pred, Range1, Range2)(Range1 r1, Range2 r2) +{ + auto i1 = begin(r1), i2 = begin(r2), e1 = end(r1), e2 = end(r2); + for (; i1 != e1 && i2 != e2; ++i1, ++i2) + { + if (!pred(*i1, *i2)) break; + } + return tuple(i1, i2); +} + +/// Ditto +Tuple!(Iterator!(Range1), Iterator!(Range2)) +mismatch(string pred = q{a == b}, Range1, Range2)(Range1 r1, Range2 r2) +{ + return .mismatch!(binaryFun!(pred), Range1, Range2)(r1, r2); +} + +unittest +{ + // doc example + int[] x = [ 1, 5, 2, 7, 4, 3 ]; + double[] y = [ 1., 5, 2, 7.3, 4, 8 ]; + auto m = mismatch(x, y); + assert(m._0 == begin(x) + 3); + assert(m._1 == begin(y) + 3); + + int[] a = [ 1, 2, 3 ]; + int[] b = [ 1, 2, 4, 5 ]; + auto mm = mismatch(a, b); + assert(mm._0 == begin(a) + 2); + assert(mm._1 == begin(b) + 2); +} + +// levenshteinDistance +/** +Encodes $(WEB realityinteractive.com/rgrzywinski/archives/000249.html, +edit operations) necessary to transform one sequence into +another. Given sequences $(D s) (source) and $(D t) (target), a +sequence of $(D EditOp) encodes the steps that need to be taken to +convert $(D s) into $(D t). For example, if $(D s = "cat") and $(D +"cars"), the minimal sequence that transforms $(D s) into $(D t) is: +skip two characters, replace 't' with 'r', and insert an 's'. Working +with edit operations is useful in applications such as spell-checkers +(to find the closest word to a given misspelled word), approximate +searches, diff-style programs that compute the difference between +files, efficient encoding of patches, DNA sequence analysis, and +plagiarism detection. +*/ + +enum EditOp : char +{ + /** Current items are equal; no editing is necessary. */ + none = 'n', + /** Substitute current item in target with current item in source. */ + substitute = 's', + /** Insert current item from the source into the target. */ + insert = 'i', + /** Remove current item from the target. */ + remove = 'r' +} + +struct Levenshtein(Range, alias equals, CostType = size_t) +{ + void deletionIncrement(CostType n) + { + _deletionIncrement = n; + InitMatrix(); + } + + void insertionIncrement(CostType n) + { + _insertionIncrement = n; + InitMatrix(); + } + + CostType distance(Range s, Range t) + { + AllocMatrix(s.length + 1, t.length + 1); + foreach (i; 1 .. rows) + { + foreach (j; 1 .. cols) + { + auto cSub = _matrix[i - 1][j - 1] + + (equals(s[i - 1], t[j - 1]) ? 0 : _substitutionIncrement); + auto cIns = _matrix[i][j - 1] + _insertionIncrement; + auto cDel = _matrix[i - 1][j] + _deletionIncrement; + switch (min_index(cSub, cIns, cDel)) { + case 0: + _matrix[i][j] = cSub; + break; + case 1: + _matrix[i][j] = cIns; + break; + default: + _matrix[i][j] = cDel; + break; + } + } + } + return _matrix[s.length][t.length]; + } + + EditOp[] path(Range s, Range t) + { + distance(s, t); + return path(); + } + + EditOp[] path() + { + EditOp[] result; + uint i = rows - 1, j = cols - 1; + // restore the path + while (i || j) { + auto cIns = j == 0 ? CostType.max : _matrix[i][j - 1]; + auto cDel = i == 0 ? CostType.max : _matrix[i - 1][j]; + auto cSub = i == 0 || j == 0 + ? CostType.max + : _matrix[i - 1][j - 1]; + switch (min_index(cSub, cIns, cDel)) { + case 0: + result ~= _matrix[i - 1][j - 1] == _matrix[i][j] + ? EditOp.none + : EditOp.substitute; + --i; + --j; + break; + case 1: + result ~= EditOp.insert; + --j; + break; + default: + result ~= EditOp.remove; + --i; + break; + } + } + reverse(result); + return result; + } + +private: + CostType _deletionIncrement = 1, + _insertionIncrement = 1, + _substitutionIncrement = 1; + CostType[][] _matrix; + uint rows, cols; + + void AllocMatrix(uint r, uint c) { + rows = r; + cols = c; + if (!_matrix || _matrix.length < r || _matrix[0].length < c) { + delete _matrix; + _matrix = new CostType[][](r, c); + InitMatrix(); + } + } + + void InitMatrix() { + foreach (i, row; _matrix) { + row[0] = i * _deletionIncrement; + } + if (!_matrix) return; + for (auto i = 0u; i != _matrix[0].length; ++i) { + _matrix[0][i] = i * _insertionIncrement; + } + } + + uint min_index(CostType i0, CostType i1, CostType i2) + { + if (i0 <= i1) + { + return i0 <= i2 ? 0 : 2; + } + else + { + return i1 <= i2 ? 1 : 2; + } + } +} + +/** +Returns the $(WEB wikipedia.org/wiki/Levenshtein_distance, Levenshtein +distance) between $(D s) and $(D t). The Levenshtein distance computes +the minimal amount of edit operations necessary to transform $(D s) +into $(D t). Performs $(BIGOH s.length * t.length) evaluations of $(D +equals) and occupies $(BIGOH s.length * t.length) storage. + +Example: +---- +assert(levenshteinDistance("cat", "rat") == 1); +assert(levenshteinDistance("parks", "spark") == 2); +assert(levenshteinDistance("kitten", "sitting") == 3); +// ignore case +assert(levenshteinDistance!("toupper(a) == toupper(b)") + ("parks", "SPARK") == 2); +---- +*/ +size_t levenshteinDistance(alias equals, Range1, Range2)(Range1 s, Range2 t) +{ + Levenshtein!(typeof(range(begin(s), end(s))), equals, uint) lev; + return lev.distance(s, t); +} + +/// Ditto +size_t levenshteinDistance(string equals = "a == b", Range1, Range2)( + Range1 s, Range2 t) +{ + return levenshteinDistance!(binaryFun!(equals), Range1, Range2)(s, t); +} + +/** +Returns the Levenshtein distance and the edit path between $(D s) and +$(D t). + +Example: +--- +string a = "Saturday", b = "Sunday"; +auto p = levenshteinDistanceAndPath(a, b); +assert(p._0, 3); +assert(equals(p._1, "nrrnsnnn")); +--- +*/ +Tuple!(size_t, EditOp[]) +levenshteinDistanceAndPath(alias equals, Range1, Range2)(Range1 s, Range2 t) +{ + Levenshtein!(Range, equals) lev; + auto d = lev.distance(s, t); + return tuple(d, lev.path); +} + +/// Ditto +Tuple!(size_t, EditOp[]) +levenshteinDistanceAndPath(string equals = "a == b", + Range1, Range2)(Range1 s, Range2 t) +{ + return levenshteinDistanceAndPath!(binaryFun!(equals), Range1, Range2)( + s, t); +} + +unittest +{ + assert(levenshteinDistance("a", "a") == 0); + assert(levenshteinDistance("a", "b") == 1); + assert(levenshteinDistance("aa", "ab") == 1); + assert(levenshteinDistance("aa", "abc") == 2); + assert(levenshteinDistance("Saturday", "Sunday") == 3); + assert(levenshteinDistance("kitten", "sitting") == 3); + //lev.deletionIncrement = 2; + //lev.insertionIncrement = 100; + string a = "Saturday", b = "Sunday"; + // @@@BUG@@@ + //auto p = levenshteinDistanceAndPath(a, b); + //writefln(p); + //assert(cast(string) p._1 == "nrrnsnnn", cast(string) p); +} + +// copy +/** +Copies the content of $(D source) into $(D target) and returns the +remaining (unfilled) part of $(D target). See also $(WEB +sgi.com/tech/stl/_copy.html, STL's copy). If a behavior similar to +$(WEB sgi.com/tech/stl/copy_backward.html, STL's copy_backward) is +needed, use $(D copy(retro(source), retro(target))). See also $(XREF +iterator, retro). + +Example: +---- +int[] a = [ 1, 5 ]; +int[] b = [ 9, 8 ]; +int[] c = new int[a.length + b.length + 10]; +auto d = copy(b, copy(a, c)); +assert(c[0 .. a.length + b.length] == a ~ b); +assert(d.length == 10); +---- + +As long as the target range elements support assignment from source +range elements, different types of ranges are accepted. + +Example: +---- +float[] a = [ 1.0f, 5 ]; +double[] b = new double[a.length]; +auto d = copy(a, b); +---- +*/ + +Range2 copy(Range1, Range2)(Range1 source, Range2 target) +{ + auto t = begin(target), te = end(target); + foreach (s; begin(source) .. end(source)) + { + if (t == te) enforce(false, + "copy: insufficient space in target range"); + *t = *s; + ++t; + } + return range(t, te); +} + +unittest +{ + int[] a = [ 1, 5 ]; + int[] b = [ 9, 8 ]; + int[] c = new int[a.length + b.length + 10]; + auto d = copy(b, copy(a, c)); + assert(c[0 .. a.length + b.length] == a ~ b); + assert(d.length == 10); +} + +// copyIf +/** +Copies in increasing order the elements $(D x) of $(D source) +satisfying $(D pred(x)) into $(D target) and returns the remaining +(unfilled) part of $(D target). See also $(WEB +sgi.com/tech/stl/copy_if.html, STL's copy_if). + +Example: +---- +int[] a = [ 1, 5, 8, 9, 10, 1, 2, 0 ]; +auto b = new int[a.length]; +auto c = copyIf!("(a & 1) == 1")(a, b); +assert(b[0 .. $ - c.length] == [ 1, 5, 9, 1 ]); +---- + +As long as the target range elements support assignment from source +range elements, different types of ranges are accepted. + +Example: +---- +float[] a = [ 1.0f, 5, -3, -5, 0, 4, -3 ]; +double[] b = new double[a.length]; +auto d = copyIf!("a > 0")(a, b); +assert(a == [ 1.0f, 5, 0, 4 ]); +---- +*/ + +Range2 copyIf(alias pred, Range1, Range2)(Range1 source, Range2 target) +{ + //static assert(false, "Not yet implemented due to bugs in the compiler"); + auto t = begin(target), te = end(target); + foreach (s; begin(source) .. end(source)) + { + if (!pred(*s)) continue; + if (t == te) enforce(false, + "copyIf: insufficient space in target range"); + *t = *s; + ++t; + } + return range(t, te); +} + +Range2 copyIf(string pred, Range1, Range2)(Range1 source, Range2 target) +{ + return .copyIf!(unaryFun!(pred), Range1, Range2)(source, target); +} + +unittest +{ + int[] a = [ 1, 5 ]; + int[] b = [ 9, 8 ]; + auto e = copyIf!("a > 1")(a, b); + assert(b[0] == 5 && e.length == 1); +} + +// iterSwap +/** +Swaps $(D *lhs) and $(D *rhs). + +Preconditions: +Same as for $(D swap(*lhs, *rhs)). +*/ +void iterSwap(It)(It lhs, It rhs) +{ + assert(!pointsTo(*lhs, *lhs), It.stringof); + swap(*lhs, *rhs); +} + +// swapRanges +/** +Swaps all elements of $(D r1) with successive elements in $(D r2) +using $(D iterSwap) as a primitive. $(D r1) must contain less or the +same number of elements as $(D r2); an exception will be thrown +otherwise. Returns the tail portion of $(D r2) that was not swapped. + +Example: +---- +int[] a = [ 100, 101, 102, 103 ]; +int[] b = [ 0, 1, 2, 3 ]; +auto c = swapRanges(a[1 .. 2], b[2 .. 3]); +assert(!c.length); +assert(a == [ 100, 2, 3, 103 ]); +assert(b == [ 0, 1, 101, 102 ]); +---- +*/ +Range2 swapRanges(alias iterSwap = .iterSwap, Range1, Range2)(T r1, T r2) +{ + enforce(r1.length <= r2.length, + "swapRanges: too short range in the second position"); + auto t = begin(r2); + foreach (s; begin(r1) .. end(r1)) + { + iterSwap(t, s); + ++t; + } + return range(t, end(r2)); +} + +// reverse +/** +Reverses $(D r) in-place. Performs $(D r.length) evaluations of $(D +iterSwap). See also $(WEB sgi.com/tech/stl/_reverse.html, STL's +reverse). + +Example: +---- +int[] arr = [ 1, 2, 3 ]; +reverse(arr); +assert(arr == [ 3, 2, 1 ]); +---- +*/ +void reverse(alias iterSwap = .iterSwap, Range)(Range r) +{ + auto b = begin(r), e = end(r); + assert(b <= e); + for (; b != e; ++b) + { + --e; + if (b == e) break; + iterSwap(b, e); } } @@ -283,83 +1784,699 @@ unittest { int[] range = null; reverse(range); - range = [ 1 ].dup; + range = [ 1 ]; reverse(range); assert(range == [1]); - range = [1, 2].dup; + range = [1, 2]; reverse(range); assert(range == [2, 1]); - range = [1, 2, 3].dup; + range = [1, 2, 3]; reverse(range); assert(range == [3, 2, 1]); } +// rotate /** -Sorts a random-access range according to predicate $(D_PARAM comp), -which must be a function name. +Rotates the range $(D r = [first, last$(RPAREN)) such that the slice +$(D [middle, last$(RPAREN)) gets moved in front of the slice $(D +[first, middle$(RPAREN)). Performs $(BIGOH r.length) evaluations of +$(D iterSwap). See also $(WEB sgi.com/tech/stl/_rotate.html, STL's +rotate). + +Preconditions: + +$(D first <= middle && middle <= last); + +Returns: + +The position in which $(D first) has been rotated. Example: ---- -int[] array = ([ 1, 2, 3, 4 ]).dup; -sort!(greater)(array); -assert(array == [ 4, 3, 2, 1 ]); -bool myComp(int x, int y) { return x < y; } -sort!(myComp)(array); -assert(array == [ 1, 2, 3, 4 ]); +auto arr = [4, 5, 6, 7, 1, 2, 3]; +auto p = rotate(arr, begin(arr) + 4); +assert(p - begin(arr) == 3); +assert(arr == [ 1, 2, 3, 4, 5, 6, 7 ]); ---- */ - -template sort(alias comp) +It rotate(alias iterSwap = .iterSwap, Range, It)(Range r, It middle) { - void sort(Range)(Range r) + auto first = begin(r), last = end(r); + if (first == middle) return last; + if (last == middle) return first; + + auto first2 = middle; + do { - static if (is(typeof(comp(*begin(r), *end(r))) == bool)) - { - sortImpl!(comp)(begin(r), end(r)); - assert(isSorted!(comp)(r)); - } - else - { - static assert(false, typeof(&comp).stringof); - } + iterSwap(first, first2); + ++first; + ++first2; + if (first == middle) + middle = first2; } + while (first2 != last); + + auto newMiddle = first; + first2 = middle; + + while (first2 != last) { + iterSwap(first, first2); + ++first; + ++first2; + if (first == middle) + middle = first2; + else if (first2 == last) + first2 = middle; + } + + return newMiddle; +} + +unittest +{ + // doc example + auto arr = [4, 5, 6, 7, 1, 2, 3]; + auto p = rotate(arr, arr.ptr + 4); + assert(p - arr.ptr == 3); + assert(arr == [ 1, 2, 3, 4, 5, 6, 7 ]); + + // The signature taking range and mid + arr[] = [4, 5, 6, 7, 1, 2, 3]; + p = rotate(arr, arr.ptr + 4); + assert(p - arr.ptr == 3); + assert(arr == [ 1, 2, 3, 4, 5, 6, 7 ]); + + // a more elaborate test + auto rnd = Random(unpredictableSeed); + int[] a = new int[uniform!(int)(rnd, 100, 200)]; + int[] b = new int[uniform!(int)(rnd, 100, 200)]; + foreach (ref e; a) e = uniform!(int)(rnd, -100, 100); + foreach (ref e; b) e = uniform!(int)(rnd, -100, 100); + int[] c = a ~ b; + auto n = rotate(c, c.ptr + a.length); + assert(n == c.ptr + b.length); + assert(c == b ~ a); + + // test with custom iterSwap + bool called; + void mySwap(int* a, int* b) { iterSwap(a, b); called = true; } + rotate!(mySwap)(c, c.ptr + a.length); + assert(called); +} + +// 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 $(D toupper(a) < toupper(b)). That +algorithm might choose to swap the two equivalent strings $(D "abc") +and $(D "aBc"). That does not affect the sorting since both $(D [ +"abc", "aBc", "b" ]) and $(D [ "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 +$(D [ "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 $(D Stable) ordering strategy may be more costly in +time and/or space than the other two because it imposes additional +constraints. Similarly, $(D Semistable) may be costlier than $(D +Unstable). As (semi-)stability is not needed very often, the ordering +algorithms in this module parameterized by $(D SwapStrategy) all +choose $(D 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, +} + +// eliminate +/** +Reduces $(D r) by overwriting all elements $(D x) that satisfy $(D +pred(x)). Returns the reduced range. + +Example: +---- +int[] arr = [ 1, 2, 3, 4, 5 ]; +// eliminate even elements +auto r = eliminate!("(a & 1) == 0")(arr); +assert(r == [ 1, 3, 5 ]); +assert(arr == [ 1, 3, 5, 4, 5 ]); +---- +*/ +Range eliminate(alias pred, + SwapStrategy os = SwapStrategy.Unstable, + alias move = .move, + Range)(Range r) +{ + alias Iterator!(Range) It; + static void assignIter(It a, It b) { move(*b, *a); } + return range(begin(r), partition!(not!(pred), os, assignIter, Range)(r)); +} + +/// Ditto +Range eliminate(string fun, SwapStrategy os = SwapStrategy.Unstable, + alias move = .move, Range)(Range r) +{ + return .eliminate!(unaryFun!(fun), os, move, Range)(r); +} + +unittest +{ + int[] arr = [ 1, 2, 3, 4, 5 ]; +// eliminate even elements + auto r = eliminate!("(a & 1) == 0")(arr); + assert(find!("(a & 1) == 0")(r) == end(r)); } /** - Sorts a random-access range according to predicate $(D_PARAM comp), - expressed as a string. The string can use the names "a" and "b" for - the two elements being compared. +Reduces $(D r) by overwriting all elements $(D x) that satisfy $(D +pred(x, v)). Returns the reduced range. - Example: +Example: +---- +int[] arr = [ 1, 2, 3, 2, 4, 5, 2 ]; +// keep elements different from 2 +auto r = eliminate(arr, 2); +assert(r == [ 1, 3, 4, 5 ]); +assert(arr == [ 1, 3, 4, 5, 4, 5, 2 ]); +---- +*/ +Range eliminate(alias pred, + SwapStrategy os = SwapStrategy.Semistable, + Range, Value)(Range r, Value v) +{ + alias Iterator!(Range) It; + bool comp(typeof(*It) a) { return !pred(a, v); } + static void assignIterB(It a, It b) { *a = *b; } + return range(begin(r), + partition!(comp, + os, assignIterB, Range)(r)); +} + +/// Ditto +Range eliminate(string pred = "a == b", + SwapStrategy os = SwapStrategy.Semistable, + Range, Value)(Range r, Value v) +{ + return .eliminate!(binaryFun!(pred), os, Range, Value)(r, v); +} + +unittest +{ + int[] arr = [ 1, 2, 3, 2, 4, 5, 2 ]; +// keep elements different from 2 + auto r = eliminate(arr, 2); + assert(r == [ 1, 3, 4, 5 ]); + assert(arr == [ 1, 3, 4, 5, 4, 5, 2 ]); +} + +// partition +/** +Partitions a range in two using $(D pred) as a predicate and $(D +iterSwap) as a primitive to swap two elements. Specifically, reorders +the range $(D r = [left, right$(RPAREN)) using $(D iterSwap) such that +all elements $(D i) for which $(D pred(i)) is $(D true) come before +all elements $(D j) for which $(D pred(j)) returns $(D false). + +Performs $(BIGOH r.length) (if unstable or semistable) or $(BIGOH +r.length * log(r.length)) (if stable) evaluations of $(D less) and $(D +iterSwap). The unstable version computes the minimum possible +evaluations of $(D iterSwap) (roughly half of those performed by the +semistable version). + +$(D partition) always calls $(D iterSwap(i, j)) for iterators +satisfying $(D i < j && !pred(*i) && pred(*j)). After the call to $(D +iterSwap(i, j)), $(D partition) makes no assumption on the values of +$(D *i) and $(D *j). Therefore, $(D partition) can be used to actually +copy partitioned data to a different container or overwrite part of +the array (in fact $(D eliminate) uses $(D partition) with a custom +$(D iterSwap)). + +See also STL's $(WEB sgi.com/tech/stl/_partition.html, partition) and +$(WEB sgi.com/tech/stl/stable_partition.html, stable_partition). + +Returns: + +An iterator $(D p) such that the following conditions are +simultaneously true: +$(OL +$(LI $(D pred(*p1)) for all $(D p1) in [$(D left), +$(D p)$(RPAREN), if any) +$(LI $(D !pred(*p2)) for all $(D p2) in [$(D p), +$(D right)$(RPAREN), if any)) +If $(D os == SwapStrategy.Stable), $(D partition) preserves the +relative ordering of all elements $(D a), $(D b) in $(D r) for which +$(D pred(a) == pred(b)). If $(D os == SwapStrategy.Semistable), $(D +partition) preserves the relative ordering of all elements $(D a), $(D +b) in $(D begin(r) .. p) for which $(D pred(a) == pred(b)). + +Example: ---- -int[] array = ([ 1, 2, 3, 4 ]).dup; +auto Arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +auto arr = Arr.dup; +static bool even(int a) { return (a & 1) == 0; } +// Partition a such that even numbers come first +auto p = partition!(even)(arr); +// Now arr is separated in evens and odds. +// Numbers may have become shuffled due to instability +assert(p == arr.ptr + 5); +assert(count!(even)(range(begin(arr), p)) == p - begin(arr)); +assert(find!(even)(range(p, end(arr))) == end(arr)); + +// Can also specify the predicate as a string. +// Use 'a' as the predicate argument name +arr[] = Arr[]; +p = partition!(q{(a & 1) == 0})(arr); +assert(p == arr.ptr + 5); + +// Now for a stable partition: +arr[] = Arr[]; +p = partition!(q{(a & 1) == 0}, SwapStrategy.Stable)(arr); +// Now arr is [2 4 6 8 10 1 3 5 7 9], and p points to 1 +assert(arr == [2, 4, 6, 8, 10, 1, 3, 5, 7, 9] && p == arr.ptr + 5); + +// In case the predicate needs to hold its own state, use a delegate: +arr[] = Arr[]; +int x = 3; +// Put stuff greater than 3 on the left +bool fun(int a) { return a > x; } +p = partition!(fun, SwapStrategy.Semistable)(arr); +// Now arr is [4 5 6 7 8 9 10 2 3 1] and p points to 2 +assert(arr == [4, 5, 6, 7, 8, 9, 10, 2, 3, 1] && p == arr.ptr + 7); +---- +*/ +Iterator!(Range) partition(alias pred, + SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, Range)(Range r) +{ + typeof(return) result = void; + auto left = begin(r), right = end(r); + if (left == right) return left; + static if (os == SwapStrategy.Stable) + { + if (right - left == 1) + { + result = pred(*left) ? right : left; + return result; + } + auto middle = left + (right - left) / 2; + alias .partition!(pred, os, iterSwap, Range) recurse; + auto lower = recurse(range(left, middle)); + auto upper = recurse(range(middle, right)); + result = rotate!(iterSwap, Range, Iterator!(Range))( + range(lower, upper), middle); + } + else static if (os == SwapStrategy.Semistable) + { + result = right; + auto i = left; + for (; i != right; ++i) + { + // skip the initial portion of "correct" elements + if (pred(*i)) continue; + // hit the first "bad" element + result = i; + for (++i; i != right; ++i) + { + if (!pred(*i)) continue; + iterSwap(result, i); + ++result; + } + break; + } + } + else // os == SwapStrategy.Unstable + { + // Inspired from www.stepanovpapers.com/PAM3-partition_notes.pdf, + // section "Bidirectional Partition Algorithm (Hoare)" + for (;;) + { + for (;;) + { + if (left == right) return left; + if (!pred(*left)) break; + ++left; + } + // found the left bound + assert(left != right); + for (;;) + { + --right; + if (left == right) return left; + if (pred(*right)) break; + } + // found the right bound, swap & make progress + iterSwap(left, right); + ++left; + } + result = left; + } + return result; +} + +/// Ditto +Iterator!(Range) partition( + string pred, + SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, + Range)(Range r) +{ + return .partition!(unaryFun!(pred), os, iterSwap, Range)(r); +} + +unittest // partition +{ + auto Arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto arr = Arr.dup; + static bool even(int a) { return (a & 1) == 0; } +// Partition a such that even numbers come first + auto p = partition!(even)(arr); +// Now arr is separated in evens and odds. + assert(p == arr.ptr + 5); + assert(count!(even)(range(begin(arr), p)) == p - begin(arr)); + assert(find!(even)(range(p, end(arr))) == end(arr)); +// Notice that numbers have become shuffled due to instability + arr[] = Arr[]; +// Can also specify the predicate as a string. +// Use 'a' as the predicate argument name + p = partition!(q{(a & 1) == 0})(arr); + assert(p == arr.ptr + 5); +// Same result as above. Now for a stable partition: + arr[] = Arr[]; + p = partition!(q{(a & 1) == 0}, SwapStrategy.Stable)(arr); +// Now arr is [2 4 6 8 10 1 3 5 7 9], and p points to 1 + assert(arr == [2, 4, 6, 8, 10, 1, 3, 5, 7, 9] && p == arr.ptr + 5); +// In case the predicate needs to hold its own state, use a delegate: + arr[] = Arr[]; + int x = 3; +// Put stuff greater than 3 on the left + bool fun(int a) { return a > x; } + p = partition!(fun, SwapStrategy.Semistable)(arr); +// Now arr is [4 5 6 7 8 9 10 2 3 1] and p points to 2 + assert(arr == [4, 5, 6, 7, 8, 9, 10, 2, 3, 1] && p == arr.ptr + 7); + + // test with random data + auto a = rndstuff!(int)(); + partition!(even)(a); + assert(isPartitioned!(even)(a)); + auto b = rndstuff!(string); + partition!(`a.length < 5`)(b); + assert(isPartitioned!(`a.length < 5`)(b)); +} + +// // partitionPivot +// /** +// Partitions $(D r) algorithm around a pivot +// $(D m). Specifically, reorders the range $(D r = left .. right) such +// that elements less than $(D *m) are on the left and elements greater +// than or equal to $(D *m) are on the right, then returns an iterator +// pointing to the first element in the second partition. Performs +// $(BIGOH r.length) (if unstable or semistable) or $(BIGOH r.length * +// log(r.length)) (if stable) evaluations of $(D less) and $(D iterSwap). + +// Precondition: + +// $(D left <= pivot && pivot < right). + +// Returns: + +// Let $(D pivotVal) be $(D *pivot) before the +// call. The result of $(D partitionPivot) is a value $(D +// mid) such that: +// $(OL +// $(LI $(D less(*p1, pivotVal)) for all $(D p1) in +// [$(D left), $(D mid)$(RPAREN)) +// $(LI $(D !less(*p2, pivotVal)) for all $(D p2) in [$(D mid), $(D right)$(RPAREN))) +// For the unstable and semistable partitions, the following condition +// also holds: $(D *mid == pivotVal). +// */ +// It partitionPivot(alias less, +// SwapStrategy os = SwapStrategy.Unstable, +// alias iterSwap = .iterSwap, Range, It)(Range r, It m) +// { +// auto b = begin(r), e = end(r); +// if (b == e) return b; +// assert(b <= m && m < e); +// alias typeof(*b) E; +// static if (os == SwapStrategy.Unstable) +// { +// --e; +// // swap the pivot to end +// iterSwap(m, e); +// // partition on predicate +// auto pivotCached = *e; +// bool pred(E a) { return less(a, pivotCached); } +// auto result = partition!(pred, os, iterSwap)(range(b, e)); +// // swap back +// iterSwap(result, e); +// } +// else +// { +// // copy the pivot so it's not messed up +// auto pivot = *m; +// bool pred(E a) { return less(a, pivot); } +// auto result = partition!(pred, os, iterSwap)(r); +// } +// return result; +// } + +// /// Ditto +// It partitionPivot(string less = q{a < b}, +// SwapStrategy os = SwapStrategy.Unstable, +// alias iterSwap = .iterSwap, Range, It)(Range r, It m) +// { +// return .partitionPivot!(binaryFun!(less), os, iterSwap, Range, It)(r, m); +// } + +// unittest +// { +// auto a = [3, 3, 2]; +// bool less(int a, int b) { return a < b; } +// auto p = partitionPivot!(less)(a, a.ptr); +// assert(p == a.ptr + 1 && a == [2, 3, 3]); +// // Use default less +// a[] = [3, 3, 2]; +// p = partitionPivot(a, a.ptr); +// assert(p == a.ptr + 1 && a == [2, 3, 3]); + +// // test with random data +// // @@@BUG@@@ The whole type tuple should work +// foreach (T; TypeTuple!(int/*, double, string*/)) +// {{ +// auto i = rndstuff!(T)(); +// if (!i.length) continue; +// auto pivot = i[0]; +// partitionPivot!(`a > b`)(i, begin(i)); +// bool pred2(int a) { return a > pivot; } +// assert(isPartitioned!(pred2)(i)); +// }} +// } + +template isPartitioned(alias pred) +{ + bool isPartitioned(T)(T range) + { + auto left = begin(range), right = end(range); + if (left == right) return true; + for (; left != right; ++left) + { + if (!pred(*left)) break; + } + for (; left != right; ++left) + { + if (pred(*left)) return false; + } + return true; + } +} + +template isPartitioned(string pred) +{ + alias .isPartitioned!(unaryFun!(pred)) isPartitioned; +} + +// nthElement +/** +Reorders the range $(D r = [first, last$(RPAREN)) using $(D iterSwap) +as a swapping primitive such that $(D nth) points to the element that +would fall there if the range were fully sorted. Effectively, it finds +the nth smallest (according to $(D less)) element in $(D r). In +addition, it also partitions $(D r) such that all elements $(D p1) in +$(D [first, nth$(RPAREN)) satisfy $(D less(*p1, *nth)), and all +elements $(D p2) in $(D [nth, last$(RPAREN)) satisfy $(D !less(*p2, +nth)). Performs $(BIGOH r.length) (if unstable) or $(BIGOH r.length * +log(r.length)) (if stable) evaluations of $(D less) and $(D +iterSwap). See also $(WEB sgi.com/tech/stl/nth_element.html, STL's +nth_element). + +Example: + +---- +int[] v = [ 25, 7, 9, 2, 0, 5, 21 ]; +auto n = 4; +nthElement!(less)(v, begin(v) + n); +assert(v[n] == 9); +// Equivalent form: +nthElement!("a < b")(v, begin(v) + n); +assert(v[n] == 9); +---- + +BUGS: + +Stable nthElement has not been implemented yet. +*/ +void nthElement(alias less, + SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, Range, It)(Range r, It nth) +{ + static assert(os == SwapStrategy.Unstable, + "Stable nthElement not yet implemented"); + auto b = begin(r), e = end(r); + assert(b < e); + assert(b <= nth && nth < e); + for (;;) + { + auto pivot = b + (e - b) / 2; + auto pivotVal = *pivot; + bool pred(ElementType!(Range) a) { return a < pivotVal; } + iterSwap(pivot, e - 1); + pivot = partition!(pred, os, iterSwap)(range(b, e)); + iterSwap(pivot, e - 1); + if (pivot == nth) return; + if (pivot < nth) b = pivot + 1; + else e = pivot; + } +} + +/// Ditto +void nthElement(string less = q{a < b}, + SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, Range, It)(Range r, It nth) +{ + return .nthElement!(binaryFun!(less), os, iterSwap, Range, It)(r, nth); +} + +unittest +{ + scope(failure) writeln(stderr, "Failure testing algorithm"); + //auto v = ([ 25, 7, 9, 2, 0, 5, 21 ]).dup; + int[] v = [ 7, 6, 5, 4, 3, 2, 1, 0 ]; + auto n = 3; + nthElement!("a < b")(v, v.ptr + n); + assert(v[n] == n); + // + v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; + n = 3; + nthElement(v, v.ptr + n); + assert(v[n] == 3); + // + v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; + n = 1; + nthElement(v, v.ptr + n); + assert(v[n] == 2); + // + v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; + n = v.length - 1; + nthElement(v, v.ptr + n); + assert(v[n] == 7); + // + v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup; + n = 0; + nthElement(v, v.ptr + n); + assert(v[n] == 1); +} + +// sort +/** +Sorts a random-access range according to predicate $(D less). Performs +$(BIGOH r.length * log(r.length)) (if unstable) or $(BIGOH r.length * +log(r.length) * log(r.length)) (if stable) evaluations of $(D less) +and $(D iterSwap). See also STL's $(WEB sgi.com/tech/stl/_sort.html, +sort) and $(WEB sgi.com/tech/stl/stable_sort.html, stable_sort). + +Example: + +---- +int[] array = [ 1, 2, 3, 4 ]; +// sort in descending order sort!("a > b")(array); assert(array == [ 4, 3, 2, 1 ]); +// sort in ascending order +sort(array); +assert(array == [ 1, 2, 3, 4 ]); +// sort with a delegate +bool myComp(int x, int y) { return x > y; } +sort!(myComp)(array); +assert(array == [ 4, 3, 2, 1 ]); +// Showcase stable sorting +string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; +sort!("toupper(a) < toupper(b)", SwapStrategy.Stable)(words); +assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]); ---- */ -template sort(string comp) +void sort(alias less, SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, Range)(Range r) { - void sort(Range)(Range r) + static if (is(typeof(less(*begin(r), *end(r))) == bool)) { - alias typeof(*begin(r)) ElementType; - static ElementType a, b; - alias typeof(mixin(comp)) ResultType; - static ResultType compFn(ElementType a, ElementType b) + sortImpl!(less, os, iterSwap)(r); + //assert(isSorted!(less)(r)); + if (!isSorted!(less)(r)) { - return mixin(comp); + writeln(os); + writeln(r); + assert(false); } - .sort!(compFn)(r); + } + else + { + static assert(false, typeof(&less).stringof); } } +/// Ditto +void sort(string less = q{a < b}, SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, Range)(Range r) +{ + return .sort!(binaryFun!(less), os, iterSwap, Range)(r); +} + +import std.string; + unittest { // sort using delegate int a[] = new int[100]; - auto rnd = Random(getUTCtime); + auto rnd = Random(unpredictableSeed); foreach (ref e; a) { e = uniform!(int)(rnd, -100, 100); } @@ -372,68 +2489,724 @@ unittest // sort using string sort!("a < b")(a); - assert(isSorted!(less)(a)); + assert(isSorted!("a < b")(a)); // sort using function; all elements equal foreach (ref e; a) { e = 5; } + static bool less(int a, int b) { return a < b; } sort!(less)(a); assert(isSorted!(less)(a)); + string[] words = [ "aBc", "a", "abc", "b", "ABC", "c" ]; + bool lessi(string a, string b) { return toupper(a) < toupper(b); } + sort!(lessi, SwapStrategy.Stable)(words); + assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]); + // sort using ternary predicate //sort!("b - a")(a); //assert(isSorted!(less)(a)); + + a = rndstuff!(int); + sort(a); + assert(isSorted(a)); + auto b = rndstuff!(string); + sort!("tolower(a) < tolower(b)")(b); + assert(isSorted!("toupper(a) < toupper(b)")(b)); } -/*private*/ template getPivot(alias compare) +/*private*/ +Iter getPivot(alias less, Iter)(Iter b, Iter e) { - Iter getPivot(Iter)(Iter b, Iter e) + //static Random rnd; + //auto r = b + uniform(rnd, 0, e - b); + auto r = b + (e - b) / 2; + //auto r = b; + return r; +} + +/*private*/ +void optimisticInsertionSort(alias less, alias iterSwap, Range)(Range r) +{ + auto b = begin(r), e = end(r); + if (e - b <= 1) return; + for (auto i = 1 + b; i != e; ) { - auto r = b + (e - b) / 2; - return r; + // move down to find the insertion point + auto p = i - 1; + for (;;) + { + if (!less(*i, *p)) + { + ++p; + break; + } + if (p == b) break; + --p; + } + // move up to see how many we can insert + auto iOld = i, iPrev = i; + ++i; + while (i != e && less(*i, *p) && !less(*i, *iPrev)) ++i, ++iPrev; + // do the insertion + rotate!(iterSwap)(range(p, i), iOld); } } -/*private*/ template sortImpl(alias comp) +/*private*/ +void sortImpl(alias less, SwapStrategy os, alias iterSwap, Range)(Range r) { - void sortImpl(Iter)(Iter b, Iter e) + alias ElementType!(Range) Elem; + enum uint optimisticInsertionSortGetsBetter = 1; + static assert(optimisticInsertionSortGetsBetter >= 1); + auto b = begin(r), e = end(r); + while (e - b > optimisticInsertionSortGetsBetter) { - while (e - b > 1) + auto pivotPtr = getPivot!(less)(b, e); + auto pivot = *pivotPtr; + // partition + static if (os == SwapStrategy.Unstable) { - auto m = partition!(comp)(b, getPivot!(comp)(b, e), e); - assert(b <= m && m < e); - .sortImpl!(comp)(b, m); - b = ++m; + // partition + iterSwap(pivotPtr, e - 1); + bool pred(Elem a) { return less(a, pivot); } + auto mid = partition!(pred, os, iterSwap)(range(b, e)); + iterSwap(mid, e - 1); + // done with partitioning + assert(!less(pivot, *mid) && !less(*mid, pivot)); + if (b == mid) + { + // worst case: *b <= everything (also pivot <= everything) + // avoid quadratic behavior + do ++b; while (b != e && !less(pivot, *b)); + } + else + { + .sortImpl!(less, os, iterSwap, Range)(range(b, mid)); + b = mid + 1; + } + } + else // handle semistable and stable the same + { + static assert(os != SwapStrategy.Semistable); + bool pred(Elem a) { return less(a, pivot); } + auto mid = partition!(pred, os, iterSwap)(range(b, e)); + if (b == mid) + { + // bad, bad pivot. pivot <= everything + // find the first occurrence of the pivot + bool pred1(Elem a) { return !less(pivot, a); } + auto firstPivotPos = find!(pred1)(range(b, e)); + assert(firstPivotPos != e); + assert(!less(*firstPivotPos, pivot) + && !less(pivot, *firstPivotPos)); + // find the last occurrence of the pivot + bool pred2(Elem a) { return less(pivot, a); } + auto lastPivotPos = find!(pred2)(range(firstPivotPos + 1, e)); + // now rotate firstPivotPos..lastPivotPos to the front + b = rotate!(iterSwap)(range(b, lastPivotPos), firstPivotPos); + } + else + { + .sortImpl!(less, os, iterSwap, Range)(range(b, mid)); + b = mid; + } } } + // residual sort + static if (optimisticInsertionSortGetsBetter > 1) + { + optimisticInsertionSort!(less, iterSwap, Range)(r); + } +} + +// schwartzSort +/** +Sorts a range using an algorithm akin to the $(WEB +wikipedia.org/wiki/Schwartzian_transform, Schwartzian transform), also +known as the decorate-sort-undecorate pattern in Python and Lisp. (Not +to be confused with $(WEB youtube.com/watch?v=S25Zf8svHZQ, the other +Schwartz).) This function is helpful when the sort comparison includes +an expensive computation. The complexity is the same as that of the +corresponding $(D sort), but $(D schwartzSort) evaluates $(D +transform) only $(D r.length) times (less than half when compared to +regular sorting). The usage can be best illustrated with an example. + +Example: + +---- +uint hashFun(string) { ... expensive computation ... } +string[] array = ...; +// Sort strings by hash, slow +sort!("hashFun(a) < hashFun(b)")(array); +// Sort strings by hash, fast (only computes arr.length hashes): +schwartzSort!(hashFun, "a < b")(array); +---- + +The $(D schwartzSort) function might require less temporary data and +be faster than the Perl idiom or the decorate-sort-undecorate idiom +present in Python and Lisp. This is because sorting is done in-place +and only minimal extra data (one array of transformed elements) is +created. +*/ +void schwartzSort(alias transform, alias less, + SwapStrategy os = SwapStrategy.Unstable, Range)(Range r) +{ + alias typeof(transform(*begin(r))) XformType; + auto xform = new XformType[r.length]; + alias Iterator!(XformType[]) InnerIter; + foreach (i, e; r) + { + xform[i] = transform(e); + } + // primitive to swap the two collections in lockstep + void mySwap(InnerIter a, InnerIter b) + { + iterSwap(a, b); + invariant i = a - begin(xform), j = b - begin(xform); + assert(i >= 0 && i < xform.length && j >= 0 && j < xform.length); + swap(r[i], r[j]); + } + sort!(less, os, mySwap)(xform); } +/// Ditto +void schwartzSort(alias transform, string less = q{a < b}, + SwapStrategy os = SwapStrategy.Unstable, Range)(Range r) +{ + return .schwartzSort!(transform, binaryFun!(less), os, Range)(r); +} + +unittest +{ + static double entropy(double[] probs) { + double result = 0; + foreach (p; probs) { + if (!p) continue; + //enforce(p > 0 && p <= 1, "Wrong probability passed to entropy"); + result -= p * log(p); + } + return result; + } + + auto lowEnt = ([ 1.0, 0, 0 ]).dup, + midEnt = ([ 0.1, 0.1, 0.8 ]).dup, + highEnt = ([ 0.31, 0.29, 0.4 ]).dup; + double arr[][] = new double[][3]; + arr[0] = midEnt; + arr[1] = lowEnt; + arr[2] = highEnt; + schwartzSort!(entropy, q{a < b})(arr); + assert(arr[0] == lowEnt); + assert(arr[1] == midEnt); + assert(arr[2] == highEnt); + + schwartzSort!(entropy, q{a > b})(arr); + assert(arr[0] == highEnt); + assert(arr[1] == midEnt); + assert(arr[2] == lowEnt); + + // random data + auto b = rndstuff!(string); + schwartzSort!(tolower)(b); + assert(isSorted!("toupper(a) < toupper(b)")(b)); +} + +// partialSort /** - Checks whether a random-access range is sorted according to the - comparison operation $(D_PARAM comp). +Reorders $(D r) such that the range $(D begin(r) .. mid) is the same +as if $(D r) were sorted, and leaves the range $(D mid .. end(r)) in +no particular order. Performs $(BIGOH r.length * log(mid - begin(r))) +evaluations of $(D pred). + +Example: +---- +int[] a = [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]; +partialSort(a, begin(a) + 5); +assert(a[0 .. 5] == [ 0, 1, 2, 3, 4 ]); +---- +*/ +void partialSort(alias less, SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, Range, It)(Range r, It mid) +{ + nthElement!(less, os, iterSwap)(r, mid); + sort!(less, os, iterSwap, Range)(range(begin(r), mid)); +} + +/// Ditto +void partialSort(string less = "a < b", + SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, Range, It)(Range r, It mid) +{ + return .partialSort!(binaryFun!(less), os, iterSwap, Range, It)(r, mid); +} + +unittest +{ + int[] a = [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]; + partialSort(a, begin(a) + 5); + assert(a[0 .. 5] == [ 0, 1, 2, 3, 4 ]); +} + +// isSorted +/** +Checks whether a random-access range is sorted according to the +comparison operation $(D less). Performs $(BIGOH r.length) evaluations +of $(D less). Example: ---- -int[] arr = ([4, 3, 2, 1]).dup; -assert(!isSorted!(less)(arr)); -sort!(less)(arr); -assert(isSorted!(less)(arr)); +int[] arr = [4, 3, 2, 1]; +assert(!isSorted(arr)); +sort(arr); +assert(isSorted(arr)); +sort!("a > b")(arr); +assert(isSorted!("a > b")(arr)); ---- */ -template isSorted(alias comp) +bool isSorted(alias less, Range)(Range r) { - bool isSorted(Range)(Range r) + bool pred(typeof(*begin(r)) a, typeof(*begin(r)) b) { return less(b, a); } + return findAdjacent!(pred)(r) == end(r); +} + +/// Ditto +bool isSorted(string less = "a < b", Range)(Range r) +{ + return .isSorted!(binaryFun!(less), Range)(r); +} + +// makeIndex +/** +Computes an index for $(D r) based on the comparison $(D less). The +returned index is a sorted array of iterators into the original +range. This technique is similar to sorting, but it is more flexible +because (1) it allows "sorting" of invariant collections, (2) allows +binary search even if the original collection does not offer random +access, (3) allows multiple indexes, each on a different predicate, +and (4) may be faster when dealing with large objects. However, using +an index may also be slower under certain circumstances due to the +extra indirection, and is always larger than a sorting-based solution +because it needs space for the index in addition to the original +collection. The complexity is the same as $(D sort)'s. + +Example: + +---- +invariant arr = [ 2, 3, 1 ]; +auto index = makeIndex!(less)(arr); +assert(*index[0] == 1 && *index[1] == 2 && *index[2] == 3); +assert(isSorted!("*a < *b")(index)); +---- +*/ +Iterator!(Range)[] makeIndex( + alias less, + SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, + Range)(Range r) +{ + alias Iterator!(Range) Iter; + auto result = new Iter[r.length]; + // assume collection already ordered + size_t i = 0; + foreach (it; begin(r) .. end(r)) { - auto b = begin(r), e = end(r); - if (e - b <= 1) return true; - auto next = b + 1; - for (; next < e; ++b, ++next) { - if (comp(*next, *b)) return false; + result[i++] = it; + } + // sort the index + static bool indirectLess(Iter a, Iter b) + { + return less(*a, *b); + } + sort!(indirectLess, os, iterSwap)(result); + return result; +} + + +/// Ditto +Iterator!(Range)[] makeIndex( + string less = q{a < b}, + SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, + Range)(Range r) +{ + return .makeIndex!(binaryFun!(less), os, iterSwap, Range)(r); +} + +unittest +{ + static bool less(int a, int b) { return a < b; } + { + invariant arr = [ 2, 3, 1 ]; + auto index = makeIndex!(less)(arr); + assert(*index[0] == 1 && *index[1] == 2 && *index[2] == 3); + assert(isSorted!(q{*a < *b})(index)); + } + { + string[] x = ([ "c", "a", "b", "d" ]).dup; + auto index = makeIndex!(q{a < b})(x); + assert(isSorted!(q{*a < *b})(index)); + assert(*index[0] == "a" && *index[1] == "b" && *index[2] == "c" + && *index[3] == "d"); + } + + // random data + auto b = rndstuff!(string); + auto index = makeIndex!("toupper(a) < toupper(b)")(b); + assert(isSorted!("toupper(*a) < toupper(*b)")(index)); +} + +// schwartzMakeIndex +/** +Similar to $(D makeIndex) but using $(D schwartzSort) to sort the +index. + +Example: + +---- +string[] arr = [ "ab", "c", "Ab", "C" ]; +auto index = schwartzMakeIndex!(toupper, less, SwapStrategy.Stable)(arr); +assert(*index[0] == "ab" && *index[1] == "Ab" + && *index[2] == "c" && *index[2] == "C"); +assert(isSorted!("toupper(*a) < toupper(*b)")(index)); +---- +*/ +Iterator!(Range)[] schwartzMakeIndex( + alias transform, + alias less, + SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, + Range)(Range r) +{ + alias Iterator!(Range) Iter; + auto result = new Iter[r.length]; + // assume collection already ordered + size_t i = 0; + foreach (it; begin(r) .. end(r)) + { + result[i++] = it; + } + // sort the index + static bool indirectLess(Iter a, Iter b) + { + return less(*a, *b); + } + static typeof(transform(*result[0])) indirectTransform(Iter a) + { + return transform(*a); + } + schwartzSort!(indirectTransform, less, os)(result); + return result; +} + +/// Ditto +Iterator!(Range)[] schwartzMakeIndex( + alias transform, + string less = q{a < b}, + SwapStrategy os = SwapStrategy.Unstable, + alias iterSwap = .iterSwap, + Range)(Range r) +{ + return .schwartzMakeIndex!( + transform, binaryFun!(less), os, iterSwap, Range)(r); +} + +unittest +{ + string[] arr = [ "D", "ab", "c", "Ab", "C" ]; + auto index = schwartzMakeIndex!(toupper, "a < b", + SwapStrategy.Stable)(arr); + assert(isSorted!(q{toupper(*a) < toupper(*b)})(index)); + assert(*index[0] == "ab" && *index[1] == "Ab" + && *index[2] == "c" && *index[3] == "C"); + + // random data + auto b = rndstuff!(string); + auto index1 = schwartzMakeIndex!(toupper)(b); + assert(isSorted!("toupper(*a) < toupper(*b)")(index1)); +} + +// lowerBound +/** +Returns the leftmost position in $(D range) such that all other values +$(D x) to the left of that position satisfy $(D less(x, +value)). Performs $(BIGOH log(r.length)) evaluations of $(D less). See +also STL's $(WEB sgi.com/tech/stl/lower_bound.html, lower_bound). + +Precondition: +$(D isSorted!(less)(r)) + +Returns: +$(D i) such that $(D less(*p, i)) for all p in $(D [begin(r), i$(RPAREN)). + +Example: +---- +int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; +auto p = lowerBound!(less)(a, 4); +assert(*p == 4); +p = lowerBound(a, 4); // uses less by default +assert(*p == 4); +p = lowerBound!("a < b")(a, 4); // predicate as string +assert(*p == 4); +---- +*/ +Iterator!(Range) lowerBound(alias less, Range, V)(Range r, V value) +{ + assert(isSorted!(less)(r)); + auto first = begin(r), last = end(r); + auto count = last - first; + while (count > 0) + { + invariant step = count / 2; + auto it = first + step; + if (less(*it, value)) + { + first = it + 1; + count -= step + 1; } - return true; + else + { + count = step; + } + } + return first; +} + +/// Ditto +Iterator!(Range) lowerBound(string less = q{a < b}, Range, V)(Range r, V value) +{ + return .lowerBound!(binaryFun!(less), Range, V)(r, value); +} + +unittest +{ + int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; + auto p = lowerBound!("a < b")(a, 4); + assert(*p == 4); + p = lowerBound(a, 5); + assert(*p == 5); + p = lowerBound!(q{a < b})(a, 6); + assert(*p == 6); +} + +// upperBound +/** +Returns the rightmost position in $(D r) such that all other elements +$(D x) to the left of that position satisfy $(D !less(value, x)). +Performs $(BIGOH log(r.length)) evaluations of $(D less). See also +STL's $(WEB sgi.com/tech/stl/upper_bound.html, upper_bound). + +Precondition: +$(D isSorted!(less)(r)) + +Returns: $(D i) such that $(D less(*p, value)) for all p in $(D +[begin(r), i$(RPAREN)). + +Example: +---- +auto a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; +auto p = upperBound(a, 3); +assert(p == begin(a) + 5); +---- +*/ +Iterator!(Range) upperBound(alias less, Range, V)(Range r, V value) +{ + //assert(isSorted!(less)(r)); + if (!isSorted!(less)(r)) + { + writeln(r); + assert(false); + } + auto first = begin(r), last = end(r); + size_t count = last - first; + while (count > 0) + { + auto step = count / 2; + auto it = first + step; + if (!less(value,*it)) + { + first = it + 1; + count -= step + 1; + } + else count = step; + } + return first; +} + +/// Ditto +Iterator!(Range) upperBound(string less = q{a < b}, Range, V)(Range r, V value) +{ + return .upperBound!(binaryFun!(less), Range, V)(r, value); +} + +unittest +{ + auto a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; + auto p = upperBound(a, 3); + assert(p == begin(a) + 5); +} + +// equalRange +/** +The call $(D equalRange!(less)(r, v)) returns $(D range($(D +lowerBound!(less)(r, v), $(D upperBound!(less)(r, v))))) but a bit +more efficiently than calling both functions. Performs $(BIGOH +log(r.length)) evaluations of $(D less). See also STL's $(WEB +sgi.com/tech/stl/equal_range.html, equal_range). + +Precondition: +$(D isSorted!(less)(range)) + +Returns: + +The largest subrange of $(D r) such that for all $(D p) in that range, +$(D !less(*p, value) && !less(value, *p)). + +Example: +---- +auto a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; +auto r = equalRange(a, 3); +assert(r == [ 3, 3, 3 ]); +---- +*/ +Range equalRange(alias less, Range, V)(Range r, V value) +{ + assert(isSorted!(less)(r)); + auto first = begin(r), last = end(r); + for (size_t count = last - first; count > 0; ) + { + auto step = count / 2; + auto middle = first + step; + if (less(*middle, value)) + { + first = middle + 1; + count -= step + 1; + } + else if (less(value, *middle)) + { + count = step; + } + else + { + // we're straight in the range! + auto left = lowerBound!(less)(range(first, middle), value); + first += count; + auto right = upperBound!(less)(range(++middle, first), value); + return range(left, right); + } + } + return range(first, first); +} + +/// Ditto +Range equalRange(string less = q{a < b}, Range, V)(Range r, V value) +{ + return .equalRange!(binaryFun!(less), Range, V)(r, value); +} + +unittest +{ + int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; + auto p = equalRange(a, 3); + assert(p == [ 3, 3, 3 ]); + p = equalRange(a, 4); + assert(p == [ 4, 4 ]); + p = equalRange(a, 2); + assert(p == [ 2 ]); +} + +// canFindSorted +/** +Returns $(D true) if and only if $(D value) can be found in $(D +range), which is assumed to be sorted. Performs $(BIGOH log(r.length)) +evaluations of $(D less). See also STL's $(WEB +sgi.com/tech/stl/binary_search.html, binary_search). +*/ + +bool canFindSorted(alias less, T, V)(T range, V value) +{ + auto p = lowerBound!(less)(range, value); + return p != end(range) && !less(value, *p); +} + +bool canFindSorted(string less = q{a < b}, T, V)(T range, V value) +{ + return .canFindSorted!(binaryFun!(less), T, V)(range, value); +} + +unittest +{ + auto a = rndstuff!(int); + if (a.length) + { + auto b = a[a.length / 2]; + sort(a); + assert(canFindSorted(a, b)); } } +// Internal random array generators + +enum size_t maxArraySize = 50; +enum size_t minArraySize = maxArraySize - 1; + +private string[] rndstuff(T : string)() +{ + static Random rnd; + static bool first = true; + if (first) + { + rnd = Random(unpredictableSeed); + first = false; + } + string[] result = new string[uniform(rnd, minArraySize, maxArraySize)]; + string alpha = "abcdefghijABCDEFGHIJ"; + foreach (ref s; result) + { + foreach (i; 0 .. uniform!(uint)(rnd, 0u, 20u)) + { + auto j = uniform(rnd, 0, alpha.length - 1); + s ~= alpha[j]; + } + } + return result; +} + +private int[] rndstuff(T : int)() +{ + static Random rnd; + static bool first = true; + if (first) + { + rnd = Random(unpredictableSeed); + first = false; + } + int[] result = new int[uniform(rnd, minArraySize, maxArraySize)]; + foreach (ref i; result) + { + i = uniform(rnd, -100, 100); + } + return result; +} + +private double[] rndstuff(T : double)() +{ + double[] result; + foreach (i; rndstuff!(int)()) + { + result ~= i / 50.; + } + return result; +} + +import std.c.process; +unittest +{ +// exit(0); +// writeln(randomStrings); +// writeln(randomInts); +// writeln(randomFloats); +} \ No newline at end of file diff --git a/std/bitmanip.d b/std/bitmanip.d index 000287917..8bc49e4e7 100644 --- a/std/bitmanip.d +++ b/std/bitmanip.d @@ -1,10 +1,15 @@ // Written in the D programming language /** - Bit-level manipulation facilities. +Bit-level manipulation facilities. + +Authors: + +$(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei +Alexandrescu) - Macros: - WIKI = StdBitarray +Macros: +WIKI = StdBitarray */ module std.bitmanip; @@ -12,28 +17,23 @@ module std.bitmanip; //debug = bitarray; // uncomment to turn on debugging printf's private import std.intrinsic; -private import std.metastrings; -private import std.stdio; -private string myToString(ulong n) +private template myToString(ulong n, string suffix = n > uint.max ? "UL" : "U") { - return n < 10 - ? "" ~ cast(char) (n + '0') - : myToString(n / 10) ~ myToString(n % 10); + static if (n < 10) + enum myToString = cast(char) (n + '0') ~ suffix; + else + enum myToString = .myToString!(n / 10, "") + ~ .myToString!(n % 10, "") ~ suffix; } -private string toStringSfx(ulong n) -{ - return myToString(n) ~ (n > uint.max ? "UL" : "U"); -} - -private string createAccessors( - string store, T, string name, size_t len, size_t offset)() +private template createAccessors( + string store, T, string name, size_t len, size_t offset) { static if (!name.length) { // No need to create any accessor - return ""; + enum result = ""; } else { @@ -47,52 +47,49 @@ private string createAccessors( signBitCheck = 1uL << (len - 1), extendSign = ~((cast(MasksType)1u << len) - 1); - string result; static if (is(T == bool)) { static assert(len == 1); + enum result = // getter - result ~= "bool " ~ name ~ "(){ return " - "("~store~" & "~toStringSfx(maskAllElse)~") != 0;}\n"; + "bool " ~ name ~ "(){ return " + ~"("~store~" & "~myToString!(maskAllElse)~") != 0;}\n" // setter - result ~= "void " ~ name ~ "(bool v){" - "if (v) "~store~" |= "~toStringSfx(maskAllElse)~";" - "else "~store~" &= "~toStringSfx(maskMyself)~";}\n"; + ~"void " ~ name ~ "(bool v){" + ~"if (v) "~store~" |= "~myToString!(maskAllElse)~";" + ~"else "~store~" &= "~myToString!(maskMyself)~";}\n"; } else { // getter - result ~= T.stringof ~ " " ~ name ~ "(){ auto result = " + enum result = T.stringof ~ " " ~ name ~ "(){ auto result = " "("~store~" & " - ~ toStringSfx(maskAllElse) ~ ") >>" - ~ toStringSfx(offset) ~ ";"; - static if (T.min < 0) - { - result ~= "if (result >= " ~ toStringSfx(signBitCheck) - ~ ") result |= " ~ toStringSfx(extendSign) ~ ";"; - } - result ~= " return cast("~T.stringof~") result;}\n"; + ~ myToString!(maskAllElse) ~ ") >>" + ~ myToString!(offset) ~ ";" + ~ (T.min < 0 + ? "if (result >= " ~ myToString!(signBitCheck) + ~ ") result |= " ~ myToString!(extendSign) ~ ";" + : "") + ~ " return cast("~T.stringof~") result;}\n" // setter - result ~= "void " ~ name ~ "(" ~ T.stringof - ~ " v){ "~store~" = ("~store~" & " - ~ toStringSfx(maskMyself) ~ ") | " - ~ "((cast(typeof("~store~")) v << " ~ toStringSfx(offset) - ~ ") & " ~ toStringSfx(maskAllElse) - ~ ");}\n"; + ~"void "~name~"("~T.stringof~" v){ " + ~store~" = cast(typeof("~store~"))" + " (("~store~" & "~myToString!(maskMyself)~")" + " | ((cast(typeof("~store~")) v << "~myToString!(offset)~")" + " & "~myToString!(maskAllElse)~"));}\n"; } - return result; } } -private string createStoreName(Ts...)() +private template createStoreName(Ts...) { - static if (Ts.length == 0) - return ""; + static if (Ts.length < 2) + enum createStoreName = ""; else - return Ts[1] ~ createStoreName!(Ts[3 .. $])(); + enum createStoreName = Ts[1] ~ createStoreName!(Ts[3 .. $]); } -private string createFields(string store, size_t offset, Ts...)() +private template createFields(string store, size_t offset, Ts...) { static if (!Ts.length) { @@ -106,12 +103,13 @@ private string createFields(string store, size_t offset, Ts...)() alias ulong StoreType; else static assert(false, myToString(offset)); - return "private " ~ StoreType.stringof ~ " " ~ store ~ ";"; + enum result = "private " ~ StoreType.stringof ~ " " ~ store ~ ";"; } else { - return createAccessors!(store, Ts[0], Ts[1], Ts[2], offset)() - ~ createFields!(store, offset + Ts[2], Ts[3 .. $])(); + enum result + = createAccessors!(store, Ts[0], Ts[1], Ts[2], offset).result + ~ createFields!(store, offset + Ts[2], Ts[3 .. $]).result; } } @@ -164,11 +162,9 @@ bool), followed by unsigned types, followed by signed types. template bitfields(T...) { - enum string bitfields = createFields!(createStoreName!(T)(), 0, T)(); + enum { bitfields = createFields!(createStoreName!(T), 0, T).result } } -version(none) -{ /** Allows manipulating the fraction, exponent, and sign parts of a $(D_PARAM float) separately. The definition is: @@ -258,7 +254,6 @@ unittest ubyte, "z", 5)); } } -} /** * An array of bits. @@ -305,7 +300,7 @@ struct BitArray /********************************************** * Support for [$(I index)] operation for BitArray. */ - bool opIndex(size_t i) + bool opIndex(size_t i) const in { assert(i < len); @@ -315,6 +310,19 @@ struct BitArray return cast(bool)bt(ptr, i); } + unittest + { + void Fun(const BitArray arr) + { + auto x = arr[0]; + assert(x == 1); + } + BitArray a; + a.length = 3; + a[0] = 1; + Fun(a); + } + /** ditto */ bool opIndexAssign(bool b, size_t i) in diff --git a/std/contracts.d b/std/contracts.d index 8692dfeb6..cb9da4fa7 100644 --- a/std/contracts.d +++ b/std/contracts.d @@ -29,7 +29,7 @@ * * Author: * - * Andrei Alexandrescu + * $(WEB erdani.org, Andrei Alexandrescu) * * Credits: * @@ -38,6 +38,8 @@ module std.contracts; private import std.conv; +private import std.algorithm; +private import std.iterator; /* * Copyright (C) 2004-2006 by Digital Mars, www.digitalmars.com @@ -263,14 +265,13 @@ unittest * The call will duplicate the array appropriately. * * Checking for uniqueness during compilation is possible in certain - * cases (see the $(D_PARAM unique) and $(D_PARAM lent) keywords in the - * $(LINK2 http://archjava.fluid.cs.cmu.edu/papers/oopsla02.pdf,ArchJava) + * cases (see the $(D_PARAM unique) and $(D_PARAM lent) keywords in + * the $(WEB archjava.fluid.cs.cmu.edu/papers/oopsla02.pdf, ArchJava) * language), but complicates the language considerably. The downside - * of $(D_PARAM assumeUnique)'s - * convention-based usage is that at this time there is no formal - * checking of the correctness of the assumption; on the upside, the - * idiomatic use of $(D_PARAM assumeUnique) is simple and rare enough - * to make it tolerable. + * of $(D_PARAM assumeUnique)'s convention-based usage is that at this + * time there is no formal checking of the correctness of the + * assumption; on the upside, the idiomatic use of $(D_PARAM + * assumeUnique) is simple and rare enough to be tolerable. * */ @@ -287,3 +288,76 @@ unittest auto arr1 = assumeUnique(arr); assert(is(typeof(arr1) == invariant(int)[]) && arr == null); } + +invariant(T[U]) assumeUnique(T, U)(ref T[U] array) +{ + auto result = cast(invariant(T[U])) array; + array = null; + return result; +} + +unittest +{ + int[string] arr = ["a":1]; + auto arr1 = assumeUnique(arr); + assert(is(typeof(arr1) == invariant(int[string])) && arr == null); +} + +/** +Returns $(D true) if $(D source)'s representation embeds a pointer +that points to $(D target)'s representation or somewhere inside +it. Note that evaluating $(D pointsTo(x, x)) checks whether $(D x) has +internal pointers. +*/ +bool pointsTo(S, T)(ref S source, ref T target) +{ + static if (is(S P : U*, U)) + { + const void * m = source, b = &target, e = b + target.sizeof; + return b <= m && m < e; + } + else static if (is(S == struct)) + { + foreach (i, subobj; source.tupleof) + { + if (pointsTo(subobj, target)) return true; + } + return false; + } + else static if (is(S A : U[], U)) + { + const void* p1 = source.ptr, p2 = p1 + source.length, + b = &target, e = b + target.sizeof; + return overlap(range(p1, p2), range(b, e)).length != 0; + } + else + { + return false; + } +} + +unittest +{ + struct S1 { int a; S1 * b; } + S1 a1; + S1 * p = &a1; + assert(pointsTo(p, a1)); + + S1 a2; + a2.b = &a1; + assert(pointsTo(a2, a1)); + + struct S3 { int[10] a; } + S3 a3; + auto a4 = a3.a[2 .. 3]; + assert(pointsTo(a4, a3)); + + auto a5 = new double[4]; + auto a6 = a5[1 .. 2]; + assert(!pointsTo(a5, a6)); + + auto a7 = new double[3]; + auto a8 = new double[][1]; + a8[0] = a7; + assert(!pointsTo(a8[0], a8[0])); +} diff --git a/std/conv.d b/std/conv.d index ea5062a60..26ef212fb 100644 --- a/std/conv.d +++ b/std/conv.d @@ -1,4 +1,3 @@ - // Written in the D programming language. /* @@ -26,9 +25,13 @@ */ /*********** - * A one-stop shop for converting values from one type to another. - * - */ +A one-stop shop for converting values from one type to another. + +Authors: + +$(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei +Alexandrescu) +*/ module std.conv; @@ -196,14 +199,9 @@ might fail the range check. Macros: WIKI=Phobos/StdConv */ -template to(Target) +Target to(Target, Source)(Source value) { - Target to(Source)(Source value) - { - // Need to forward because of problems when recursively invoking - // a member with the same name as a template - return toImpl!(Source, Target)(value); - } + return toImpl!(Source, Target)(value); } private T toSomeString(S, T)(S s) @@ -2059,4 +2057,3 @@ private bool feq(in creal r1, in creal r2) return feq(r1a, r2b, 0.000001L); } - diff --git a/std/file.d b/std/file.d index fd43c496f..b75c3e657 100644 --- a/std/file.d +++ b/std/file.d @@ -1,9 +1,17 @@ // Written in the D programming language. /** - * Macros: - * WIKI = Phobos/StdFile - */ +Utilities for manipulating files and scanning directories. + +Authors: + +$(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei +Alexandrescu) + +Macros: + +WIKI = Phobos/StdFile +*/ /* * Copyright (C) 2001-2004 by Digital Mars, www.digitalmars.com @@ -1037,42 +1045,24 @@ void remove(in string name) ulong getSize(in string name) { - uint size; - int fd; struct_stat statbuf; - - auto namez = toStringz(name); - //printf("file.getSize('%s')\n",namez); - fd = std.c.linux.linux.open(namez, O_RDONLY); - if (fd == -1) + if (std.c.linux.linux.stat(toStringz(name), &statbuf)) { - //printf("\topen error, errno = %d\n",getErrno()); - goto err1; + throw new FileException(name, getErrno()); } - - //printf("\tfile opened\n"); - if (std.c.linux.linux.fstat(fd, &statbuf)) - { - //printf("\tfstat error, errno = %d\n",getErrno()); - goto err2; - } - size = statbuf.st_size; - - if (std.c.linux.linux.close(fd) == -1) - { - //printf("\tclose error, errno = %d\n",getErrno()); - goto err; - } - - return size; - -err2: - std.c.linux.linux.close(fd); -err: -err1: - throw new FileException(name.idup, getErrno()); + return statbuf.st_size; } +unittest +{ + scope(exit) system("rm -f /tmp/deleteme"); + // create a file of size 1 + assert(system("echo > /tmp/deleteme") == 0); + assert(getSize("/tmp/deleteme") == 1); + // create a file of size 3 + assert(system("echo ab > /tmp/deleteme") == 0); + assert(getSize("/tmp/deleteme") == 3); +} /*************************************************** * Get file attributes. diff --git a/std/format.d b/std/format.d index 400a9b38d..19a6f6a9a 100644 --- a/std/format.d +++ b/std/format.d @@ -1960,33 +1960,39 @@ private void formatFloat(Writer, D)(ref Writer w, D obj, FormatInfo f) private void formatGeneric(Writer, D)(ref Writer w, const(void)* arg, FormatInfo f) { - D obj = *cast(D*) arg; - static if (is(D == void[])) { - char[] s = cast(char[]) obj; + auto obj = *cast(D*) arg; + static if (is(const(D) == const(void[]))) { + auto s = cast(const char[]) obj; w.write(s); } else static if (is(D Original == typedef)) { formatGeneric!(Writer, Original)(w, arg, f); - } else static if (is(D == float) || is(D == double) || is(D == real)) { + } else static if (is(const D == const(float)) + || is(const(D) == const(double)) + || is(const(D) == const(real))) { formatFloat(w, obj, f); - } else static if (is(D == ifloat)) { + } else static if (is(const(D) == const ifloat)) { formatFloat(w, *cast(float*) &obj, f); - } else static if (is(D == idouble)) { + } else static if (is(const(D) == const idouble)) { formatFloat(w, *cast(double*) &obj, f); - } else static if (is(D == ireal)) { + } else static if (is(const(D) == const ireal)) { formatFloat(w, *cast(real*) &obj, f); - } else static if (is(D == cfloat) || is(D == cdouble) || is(D == creal)) { + } else static if (is(const(D) == const cfloat) + || is(const(D) == const cdouble) + || is(const(D) == const creal)) { formatFloat(w, obj.re, f); w.write("+"); formatFloat(w, obj.im, f); w.write("i"); - } else static if (is(D : long) || is(D : ulong)) { - static if (is(D == bool)) { + } else static if (is(const(D) : const long) || is(const(D) : const ulong)) { + static if (is(const(D) == const bool)) { if (f.spec == 's') { w.write(obj ? "true" : "false"); } else { formatIntegral(w, cast(int) obj, f); } - } else static if (is(D == char) || is(D == wchar) || is(D == dchar)) { + } else static if (is(const(D) == const char) + || is(const(D) == const wchar) + || is(const(D) == const dchar)) { if (f.spec == 's') { w.putchar(obj); } else { @@ -2023,11 +2029,11 @@ private void formatGeneric(Writer, D)(ref Writer w, const(void)* arg, formatGeneric!(Writer, typeof(e))(w, &e, f); } w.putchar(']'); - } else static if (is(D : void*)) { + } else static if (is(const(D) : const void*)) { f.spec = 'X'; ulong fake = cast(ulong) obj; formatGeneric!(Writer, ulong)(w, &fake, f); - } else static if (is(D : Object)) { + } else static if (is(const(D) : const Object)) { if (obj is null) w.write("null"); else w.write(obj.toString); } else static if (isAssociativeArray!(D)) { diff --git a/std/functional.d b/std/functional.d index d1a412b5b..fdfc4f0c4 100644 --- a/std/functional.d +++ b/std/functional.d @@ -1,17 +1,15 @@ // Written in the D programming language. /** -This module is a port of a growing fragment of the $(D_PARAM -functional) header in Alexander Stepanov's -$(LINK2 http://sgi.com/tech/stl, Standard Template Library). +Functions that manipulate other functions. - Macros: +Macros: - WIKI = Phobos/StdFunctional +WIKI = Phobos/StdFunctional - Author: +Author: - Andrei Alexandrescu +$(WEB erdani.org, Andrei Alexandrescu) */ /* @@ -38,12 +36,214 @@ $(LINK2 http://sgi.com/tech/stl, Standard Template Library). module std.functional; -/** - Predicate that returns $(D_PARAM a < b). -*/ -bool less(T)(T a, T b) { return a < b; } +import std.string; // for making string functions visible in *naryFun +import std.conv; // for making conversions visible in *naryFun +import std.typetuple; +import std.typecons; +import std.stdio; +import std.metastrings; /** +Transforms a string representing an expression into a unary +function. The string must use symbol name $(D a) as the parameter. + +Example: + +---- +alias unaryFun!("(a & 1) == 0") isEven; +assert(isEven(2) && !isEven(1)); +---- +*/ + +template unaryFun(string comp, bool byRef = false) { + static if (byRef) + { + void unaryFun(ElementType)(ref ElementType a) + { + mixin(comp ~ ";"); + } + } + else + { + // @@@BUG1816@@@: typeof(mixin(comp)) should work + typeof({ ElementType a; return mixin(comp);}()) + unaryFun(ElementType)(ElementType a) + { + return mixin(comp); + } + } +} + +/** +Transforms a string representing an expression into a Boolean binary +predicate. The string must use symbol names $(D a) and $(D b) as the +compared elements. + + Example: + +---- +alias binaryFun!("a < b") less; +assert(less(1, 2) && !less(2, 1)); +alias binaryFun!("a > b") greater; +assert(!greater("1", "2") && greater("2", "1")); +---- +*/ + +template binaryFun(string comp) { + // @@@BUG1816@@@: typeof(mixin(comp)) should work + typeof({ ElementType1 a; ElementType2 b; return mixin(comp);}()) + binaryFun(ElementType1, ElementType2)(ElementType1 a, ElementType2 b) + { + return mixin(comp); + } +} + +unittest +{ + alias binaryFun!(q{a < b}) less; + assert(less(1, 2) && !less(2, 1)); + assert(less("1", "2") && !less("2", "1")); +} + + +/* + Predicate that returns $(D_PARAM a < b). +*/ +//bool less(T)(T a, T b) { return a < b; } +//alias binaryFun!(q{a < b}) less; + +/* Predicate that returns $(D_PARAM a > b). */ -bool greater(T)(T a, T b) { return a > b; } +//alias binaryFun!(q{a > b}) greater; + +/* + Predicate that returns $(D_PARAM a == b). +*/ +//alias binaryFun!(q{a == b}) equalTo; + +/* + Binary predicate that reverses the order of arguments, e.g., given + $(D pred(a, b)), returns $(D pred(b, a)). +*/ +template binaryRevertArgs(alias pred) +{ + typeof({ ElementType1 a; ElementType2 b; return pred(b, a);}()) + binaryRevertArgs(ElementType1, ElementType2)(ElementType1 a, ElementType2 b) + { + return pred(b, a); + } +} + +unittest +{ + alias binaryRevertArgs!(binaryFun!("a < b")) gt; + assert(gt(2, 1) && !gt(1, 1)); + int x = 42; + bool xyz(int a, int b) { return a * x < b / x; } + auto foo = &xyz; + foo(4, 5); + alias binaryRevertArgs!(foo) zyx; + assert(zyx(5, 4) == foo(4, 5)); +} + +template not(alias pred) +{ + bool not(T...)(T args) { return !pred(args); } +} + +/*private*/ template Adjoin(F...) +{ + template For(V...) + { + static if (F.length == 0) + { + alias TypeTuple!() Result; + } + else + { + alias F[0] headFun; + alias typeof({ V values; return headFun(values); }()) Head; + alias TypeTuple!(Head, Adjoin!(F[1 .. $]).For!(V).Result) Result; + } + } +} + +/** +Takes multiple functions and adjoins them together. The result is a +$(XREF typecons, Tuple) with one element per passed-in function. Upon +invocation, the returned tuple is the adjoined results of all +functions. + +Example: + +---- +static bool f1(int a) { return a != 0; } +static int f2(int a) { return a / 2; } +auto x = adjoin!(f1, f2)(5); +assert(is(typeof(x) == Tuple!(bool, int))); +assert(x._0 == true && x._1 == 2); +---- +*/ +template adjoin(F...) +{ + Tuple!(Adjoin!(F).For!(V).Result) adjoin(V...)(V a) + { + typeof(return) result; + foreach (i, r; F) + { + // @@@BUG@@@ + mixin("result.field!("~ToString!(i)~") = F[i](a);"); + } + return result; + } +} + +unittest +{ + static bool F1(int a) { return a != 0; } + static int F2(int a) { return a / 2; } + auto x = adjoin!(F1, F2)(5); + alias Adjoin!(F1, F2).For!(int).Result R; + assert(is(typeof(x) == Tuple!(bool, int))); + assert(x._0 == true && x._1 == 2); +} + +// /*private*/ template NaryFun(string fun, string letter, V...) +// { +// static if (V.length == 0) +// { +// enum args = ""; +// } +// else +// { +// enum args = V[0].stringof~" "~letter~"; " +// ~NaryFun!(fun, [letter[0] + 1], V[1..$]).args; +// enum code = args ~ "return "~fun~";"; +// } +// alias void Result; +// } + +// unittest +// { +// writeln(NaryFun!("a * b * 2", "a", int, double).code); +// } + +// /** +// naryFun +// */ +// template naryFun(string fun) +// { +// //NaryFun!(fun, "a", V).Result +// int naryFun(V...)(V values) +// { +// enum string code = NaryFun!(fun, "a", V).code; +// mixin(code); +// } +// } + +// unittest +// { +// alias naryFun!("a + b") test; +// test(1, 2); +// } diff --git a/std/getopt.d b/std/getopt.d index 8dd162cb6..8f6231c87 100644 --- a/std/getopt.d +++ b/std/getopt.d @@ -11,7 +11,11 @@ * as was the case with the more traditional single-letter approach, * is provided but not enabled by default. * - * Credits: + Author: + + $(WEB erdani.org, Andrei Alexandrescu) +* +* Credits: * * This module and its documentation are inspired by Perl's * $(LINK2 http://perldoc.perl.org/Getopt/Long.html,Getopt::Long) module. The @@ -55,13 +59,13 @@ import std.stdio; // for testing only --------- import std.getopt; -string data = "file.dat"; +string data = "file.dat"; int length = 24; bool verbose; void main(string[] args) { - bool result = getopt( + getopt( args, "length", &length, // numeric "file", &data, // string @@ -77,16 +81,15 @@ void main(string[] args) right (the "bound" pointer). The option string in the call to $(D_PARAM getopt) should not start with a dash. - In all cases, the command-line options that were parsed and - used by $(D_PARAM getopt) are removed from $(D_PARAM args). Whatever - in the arguments did not look like an option is left in $(D_PARAM - args) for further processing by the program. Values that were - unaffected by the options are not touched, so a common idiom is to - initialize options to their defaults and then invoke $(D_PARAM - getopt). If a command-line argument is recognized as an option with a - parameter and the parameter cannot be parsed properly (e.g. a number - is expected but not present), a $(D_PARAM ConvError) exception is - thrown. + In all cases, the command-line options that were parsed and used by + $(D_PARAM getopt) are removed from $(D_PARAM args). Whatever in the + arguments did not look like an option is left in $(D_PARAM args) for + further processing by the program. Values that were unaffected by the + options are not touched, so a common idiom is to initialize options + to their defaults and then invoke $(D_PARAM getopt). If a + command-line argument is recognized as an option with a parameter and + the parameter cannot be parsed properly (e.g. a number is expected + but not present), a $(D_PARAM ConvError) exception is thrown. Depending on the type of the pointer being bound, $(D_PARAM getopt) recognizes the following kinds of options: @@ -96,7 +99,7 @@ void main(string[] args) --------- bool verbose, debugging; - bool result = getopt(args, "verbose", &verbose, "debug", &debugging); + getopt(args, "verbose", &verbose, "debug", &debugging); --------- $(LI $(I Numeric options.) If an option is bound to a numeric type, a @@ -105,7 +108,7 @@ void main(string[] args) --------- uint timeout; - bool result = getopt(args, "timeout", &timeout); + getopt(args, "timeout", &timeout); --------- Invoking the program with "--timeout=5" or "--timeout 5" will set @@ -117,7 +120,7 @@ void main(string[] args) --------- uint paranoid; - bool result = getopt(args, "paranoid+", ¶noid); + getopt(args, "paranoid+", ¶noid); --------- Invoking the program with "--paranoid --paranoid --paranoid" will set @@ -133,7 +136,7 @@ void main(string[] args) --------- string outputFile; - bool result = getopt(args, "output", &outputFile); + getopt(args, "output", &outputFile); --------- Invoking the program with "--output=myfile.txt" or "--output @@ -146,7 +149,7 @@ void main(string[] args) --------- string[] outputFiles; - bool result = getopt(args, "output", &outputFiles); + getopt(args, "output", &outputFiles); --------- Invoking the program with "--output=myfile.txt --output=yourfile.txt" @@ -159,7 +162,7 @@ void main(string[] args) --------- double[string] tuningParms; - bool result = getopt(args, "tune", &tuningParms); + getopt(args, "tune", &tuningParms); --------- Invoking the program with e.g. "--tune=alpha=0.5 --tune beta=0.6" @@ -191,7 +194,7 @@ void main(string[] args) verbosityLevel = 2; } } - bool result = getopt(args, "verbose", &myHandler, "quiet", &myHandler); + getopt(args, "verbose", &myHandler, "quiet", &myHandler); } --------- @@ -218,7 +221,7 @@ void main(string[] args) exit(1); } } - bool result = getopt(args, "verbosity", &myHandler); + getopt(args, "verbosity", &myHandler); } --------- )))) @@ -237,7 +240,9 @@ getopt(args, "verbose|loquacious|garrulous", &verbose); $(B Case) -By default options are case-insensitive. You can change that behavior by passing $(D_PARAM getopt) the $(D_PARAM caseSensitive) directive like this: +By default options are case-insensitive. You can change that behavior +by passing $(D_PARAM getopt) the $(D_PARAM caseSensitive) directive +like this: --------- bool foo, bar; @@ -297,7 +302,7 @@ $(B Options Terminator) A lonesome double-dash terminates $(D_PARAM getopt) gathering. It is used to separate program options from other parameters (e.g. options to be passed to another program). Invoking the example above with "--foo -- --bar" parses foo but leaves "--bar" in $(D_PARAM args). The double-dash itself is removed from the argument array. */ -bool getopt(T...)(ref string[] args, T opts) { +void getopt(T...)(ref string[] args, T opts) { configuration cfg; return getoptImpl(args, cfg, opts); } @@ -323,7 +328,7 @@ enum config { noPassThrough, }; -private bool getoptImpl(T...)(ref string[] args, +private void getoptImpl(T...)(ref string[] args, ref configuration cfg, T opts) { static if (opts.length) { @@ -337,6 +342,7 @@ private bool getoptImpl(T...)(ref string[] args, case config.noBundling: cfg.bundling = false; break; case config.passThrough: cfg.passThrough = true; break; case config.noPassThrough: cfg.passThrough = false; break; + default: assert(false); break; } return getoptImpl(args, cfg, opts[1 .. $]); } @@ -414,12 +420,10 @@ private bool getoptImpl(T...)(ref string[] args, if (a == "--") break; // end of options if (!cfg.passThrough) { - writeln(stderr, "Unrecognized option ", a); - return false; + throw new Exception("Unrecognized option "~a); } } } - return true; } const @@ -465,19 +469,19 @@ unittest uint paranoid = 2; string[] args = (["program.name", "--paranoid", "--paranoid", "--paranoid"]).dup; - assert(getopt(args, "paranoid+", ¶noid)); + getopt(args, "paranoid+", ¶noid); assert(paranoid == 5, to!(string)(paranoid)); - string data = "file.dat"; + string data = "file.dat"; int length = 24; bool verbose = true; args = (["program.name", "--length=5", "--file", "dat.file", "--verbose"]).dup; - assert(getopt( + getopt( args, "length", &length, "file", &data, - "verbose", &verbose)); + "verbose", &verbose); assert(args.length == 1); assert(data == "dat.file"); assert(length == 5); @@ -487,14 +491,14 @@ unittest string[] outputFiles; args = (["program.name", "--output=myfile.txt", "--output", "yourfile.txt"]).dup; - assert(getopt(args, "output", &outputFiles)); + getopt(args, "output", &outputFiles); assert(outputFiles.length == 2 && outputFiles[0] == "myfile.txt" && outputFiles[0] == "myfile.txt"); args = (["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"]).dup; double[string] tuningParms; - assert(getopt(args, "tune", &tuningParms)); + getopt(args, "tune", &tuningParms); assert(args.length == 1); assert(tuningParms.length == 2); assert(tuningParms["alpha"] == 0.5); @@ -514,10 +518,10 @@ unittest } } args = (["program.name", "--quiet"]).dup; - assert(getopt(args, "verbose", &myHandler, "quiet", &myHandler)); + getopt(args, "verbose", &myHandler, "quiet", &myHandler); assert(verbosityLevel == 0); args = (["program.name", "--verbose"]).dup; - assert(getopt(args, "verbose", &myHandler, "quiet", &myHandler)); + getopt(args, "verbose", &myHandler, "quiet", &myHandler); assert(verbosityLevel == 2); verbosityLevel = 1; @@ -527,15 +531,15 @@ unittest verbosityLevel = 2; } args = (["program.name", "--verbose", "2"]).dup; - assert(getopt(args, "verbose", &myHandler2)); + getopt(args, "verbose", &myHandler2); assert(verbosityLevel == 2); bool foo, bar; args = (["program.name", "--foo", "--bAr"]).dup; - assert(getopt(args, + getopt(args, std.getopt.config.caseSensitive, std.getopt.config.passThrough, "foo", &foo, - "bar", &bar)); + "bar", &bar); assert(args[1] == "--bAr"); } diff --git a/std/math.d b/std/math.d index a1550e799..a02650ed4 100644 --- a/std/math.d +++ b/std/math.d @@ -1946,11 +1946,7 @@ unittest bool approxEqual(T, U, V)(T lhs, U rhs, V maxRelDiff, V maxAbsDiff = 0) { static if (isArray!(T)) { -<<<<<<< .mine invariant n = lhs.length; -======= - auto n = lhs.length; ->>>>>>> .r582 static if (isArray!(U)) { // Two arrays assert(n == rhs.length); diff --git a/std/numeric.d b/std/numeric.d index a87655a62..229dd7268 100644 --- a/std/numeric.d +++ b/std/numeric.d @@ -11,7 +11,7 @@ WIKI = Phobos/StdNumeric Author: -Andrei Alexandrescu +$(WEB erdani.org, Andrei Alexandrescu) */ /* @@ -52,7 +52,7 @@ Example: float f(float x) { return cos(x) - x*x*x; } -final x = secantMethod(&f, 0f, 1f); +auto x = secantMethod(&f, 0f, 1f); assert(approxEqual(x, 0.865474)); ---- */ @@ -79,7 +79,7 @@ unittest float f(float x) { return cos(x) - x*x*x; } - auto x = secantMethod!(f)(0f, 1f); + invariant x = secantMethod!(f)(0f, 1f); assert(approxEqual(x, 0.865474)); } diff --git a/std/path.d b/std/path.d index 44377e7dd..3a85a5925 100644 --- a/std/path.d +++ b/std/path.d @@ -1,20 +1,24 @@ // Written in the D programming language. /** + * This module is used to parse file names. All the operations work + * only on strings; they don't perform any input/output + * operations. This means that if a path contains a directory name + * with a dot, functions like $(D getExt()) will work with it just as + * if it was a file. To differentiate these cases, use the std.file + * module first (i.e. $(D std.file.isDir())). + * + * Authors: + * + * $(WEB digitalmars.com, Walter Bright), Grzegorz Adam Hankiewicz, +Thomas Kühne, $(WEB erdani.org, Andrei Alexandrescu) + * * Macros: * WIKI = Phobos/StdPath * Copyright: * Placed into public domain. * www.digitalmars.com * - * Grzegorz Adam Hankiewicz added some documentation. - * - * This module is used to parse file names. All the operations - * work only on strings; they don't perform any input/output - * operations. This means that if a path contains a directory name - * with a dot, functions like getExt() will work with it just as - * if it was a file. To differentiate these cases, - * use the std.file module first (i.e. std.file.isDir()). */ module std.path; diff --git a/std/random.d b/std/random.d index 1992a0107..adc87364d 100644 --- a/std/random.d +++ b/std/random.d @@ -37,7 +37,7 @@ integers and real numbers have been implemented. Author: -Andrei Alexandrescu +$(WEB erdani.org, Andrei Alexandrescu) Credits: @@ -181,7 +181,7 @@ struct LinearCongruentialEngine(UIntType, UIntType a, UIntType c, UIntType m) UIntType next() { static if (m) - _x = (cast(ulong) a * _x + c) % m; + _x = cast(UIntType) ((cast(ulong) a * _x + c) % m); else _x = a * _x + c; return _x; @@ -334,6 +334,7 @@ struct MersenneTwisterEngine( } for (mti = 1; mti < n; ++mti) { mt[mti] = + cast(UIntType) (1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> (w - 2))) + mti); /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ /* In the previous versions, MSBs of the seed affect */ @@ -367,15 +368,18 @@ struct MersenneTwisterEngine( for (; kk < n - m; ++kk) { y = (mt[kk] & upperMask)|(mt[kk + 1] & lowerMask); - mt[kk] = mt[kk + m] ^ (y >> 1) ^ mag01[y & 0x1UL]; + mt[kk] = cast(UIntType) (mt[kk + m] ^ (y >> 1) + ^ mag01[cast(UIntType) y & 0x1U]); } for (; kk < n - 1; ++kk) { y = (mt[kk] & upperMask)|(mt[kk + 1] & lowerMask); - mt[kk] = mt[kk + (m -n)] ^ (y >> 1) ^ mag01[y & 0x1UL]; + mt[kk] = cast(UIntType) (mt[kk + (m -n)] ^ (y >> 1) + ^ mag01[cast(UIntType) y & 0x1U]); } y = (mt[n -1] & upperMask)|(mt[0] & lowerMask); - mt[n - 1] = mt[m - 1] ^ (y >> 1) ^ mag01[y & 0x1UL]; + mt[n - 1] = cast(UIntType) (mt[m - 1] ^ (y >> 1) + ^ mag01[cast(UIntType) y & 0x1U]); mti = 0; } @@ -388,7 +392,7 @@ struct MersenneTwisterEngine( y ^= (y << temperingT) & temperingC; y ^= (y >> temperingL); - return y; + return cast(UIntType) y; } /** @@ -442,7 +446,7 @@ unittest for the minutiae of the method being used. */ -typedef Mt19937 Random; +alias Mt19937 Random; /** A "good" seed for initializing random number engines. Initializing @@ -458,9 +462,9 @@ auto n = rnd.next; ---- */ -ulong unpredictableSeed() +uint unpredictableSeed() { - return cast(ulong) (getpid ^ getUTCtime); + return cast(uint) (getpid ^ getUTCtime); } /** @@ -619,28 +623,27 @@ unittest ---- Random gen(unpredictableSeed); // Generate an integer in [0, 1024] -auto a = uniform!(int)(gen, 0, 1024); +auto a = uniform(gen, 0, 1024); // Generate a float in [0, 1$(RPAREN) -auto a = uniform!(float)(gen, 0.0f, 1.0f); +auto a = uniform(gen, 0.0f, 1.0f); ---- */ -template uniform(T, char leftLim = '[', char rightLim = ')') +T1 uniform(T1, char leftLim = '[', char rightLim = ')', + UniformRandomNumberGenerator, T2) +(ref UniformRandomNumberGenerator gen, T1 a, T2 b) { - T uniform(UniformRandomNumberGenerator) - (ref UniformRandomNumberGenerator gen, T a, T b) - { - auto dist = UniformDistribution!(T, leftLim, rightLim)(a, b); - return dist.next(gen); - } + alias typeof(return) Result; + auto dist = UniformDistribution!(Result, leftLim, rightLim)(a, b); + return dist.next(gen); } unittest { auto gen = Mt19937(unpredictableSeed); - auto a = uniform!(int)(gen, 0, 1024); + auto a = uniform(gen, 0, 1024); assert(0 <= a && a <= 1024); - auto b = uniform!(float)(gen, 0.0f, 1.0f); + auto b = uniform(gen, 0.0f, 1.0f); assert(0 <= b && b < 1, to!(string)(b)); } @@ -654,7 +657,7 @@ void randomShuffle(T, SomeRandomGen)(T[] array, ref SomeRandomGen r) foreach (i; 0 .. array.length) { // generate a random number i .. n - auto which = i + uniform!(size_t)(r, 0u, array.length - i); + invariant which = i + uniform!(size_t)(r, 0u, array.length - i); swap(array[i], array[which]); } } diff --git a/std/regexp.d b/std/regexp.d index 17836f667..64f579281 100644 --- a/std/regexp.d +++ b/std/regexp.d @@ -25,11 +25,13 @@ */ /********************************************** - * $(LINK2 http://www.digitalmars.com/ctg/regular.html, Regular expressions) - * are a powerful method of string pattern matching. - * The regular expression - * language used is the same as that commonly used, however, some of the very - * advanced forms may behave slightly differently. + * $(LINK2 http://www.digitalmars.com/ctg/regular.html, Regular + * expressions) are a powerful method of string pattern matching. The + * regular expression language used in this library is the same as + * that commonly used, however, some of the very advanced forms may + * behave slightly differently. The standard observed is the $(WEB + * www.ecma-international.org/publications/standards/Ecma-262.htm, + * ECMA standard) for regular expressions. * * std.regexp is designed to work only with valid UTF strings as input. * To validate untrusted input, use std.utf.validate(). diff --git a/std/stdio.d b/std/stdio.d index cff13f2c0..1a3886e11 100644 --- a/std/stdio.d +++ b/std/stdio.d @@ -10,6 +10,12 @@ * Standard I/O functions that extend $(B std.c.stdio). * $(B std.c.stdio) is $(D_PARAM public)ally imported when importing * $(B std.stdio). + * + * Authors: + * + * $(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei +Alexandrescu) + * * Macros: * WIKI=Phobos/StdStdio */ @@ -241,6 +247,9 @@ void write(T...)(T args) static const first = 0; } writef(target, "", args[first .. $]); + static if (args.length && is(typeof(args[$ - 1]) : dchar)) { + if (args[$ - 1] == '\n') fflush(target); + } } unittest @@ -1231,7 +1240,7 @@ struct chunks // if (fileName.length && fclose(f)) // StdioException("Could not close file `"~fileName~"'"); // } - const maxStackSize = 500 * 1024; + const maxStackSize = 1024 * 16; ubyte[] buffer = void; if (size < maxStackSize) buffer = (cast(ubyte*) alloca(size))[0 .. size]; @@ -1247,7 +1256,7 @@ struct chunks { // error occured if (!feof(f)) throw new StdioException(ferror(f)); - if (!r) break; // done reading + buffer.length = r; } if ((result = dg(buffer)) != 0) break; } diff --git a/std/string.d b/std/string.d index bc700ceff..ee207fcf0 100644 --- a/std/string.d +++ b/std/string.d @@ -2,15 +2,17 @@ // Written in the D programming language. /** - * String handling functions. + * String handling functions. Objects of types $(D string), $(D + * wstring), and $(D dstring) are value types and cannot be mutated + * element-by-element. For using mutation during building strings, use + * $(D char[]), $(D wchar[]), or $(D dchar[]). The $(D *string) types + * are preferable because they don't exhibit undesired aliasing, thus + * making code more robust. * - * To copy or not to copy? - * When a function takes a string as a parameter, and returns a string, - * is that string the same as the input string, modified in place, or - * is it a modified copy of the input string? The D array convention is - * "copy-on-write". This means that if no modifications are done, the - * original string (or slices of it) can be returned. If any modifications - * are done, the returned string is a copy. + * Authors: + * + * $(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei +Alexandrescu) * * Macros: * WIKI = Phobos/StdString @@ -1607,8 +1609,8 @@ bool startsWith(A1, A2)(A1 longer, A2 shorter) if (longer[i] != e) return false; } } + return true; } - return true; } unittest @@ -1697,8 +1699,8 @@ bool endsWith(A1, A2)(A1 longer, A2 shorter) if (shorter[i] != e) return false; } } + return true; } - return true; } unittest diff --git a/std/thread.d b/std/thread.d index 68194b463..cd1ddb69e 100644 --- a/std/thread.d +++ b/std/thread.d @@ -821,6 +821,8 @@ class Thread static Thread[] getAll() { synchronized (Thread.classinfo) return allThreads[0 .. allThreadsDim]; + // @@@BUG1819@@@ + return null; } void pause() diff --git a/std/traits.d b/std/traits.d index 83642066c..ea8af68f7 100644 --- a/std/traits.d +++ b/std/traits.d @@ -1,23 +1,20 @@ - // Written in the D programming language. /** * Templates with which to extract information about * types at compile time. * + * Authors: + * + * $(WEB digitalmars.com, Walter Bright), Tomasz Stachowiak ($(D + * isExpressionTuple)), $(WEB erdani.org, Andrei Alexandrescu) + * * Macros: * WIKI = Phobos/StdTraits * Copyright: * Public Domain */ -/* - * Authors: - * Walter Bright, Digital Mars, www.digitalmars.com - * Tomasz Stachowiak (isExpressionTuple) - * Andrei Alexandrescu, www.erdani.org - */ - module std.traits; import std.typetuple; @@ -119,12 +116,348 @@ template ParameterTypeTuple(dg) template FieldTypeTuple(S) { - static if (is(S == struct) || is(S == class)) + static if (is(S == struct) || is(S == class) || is(S == union)) alias typeof(S.tupleof) FieldTypeTuple; else - static assert(0, "argument is not struct or class"); + alias S FieldTypeTuple; + //static assert(0, "argument is not struct or class"); } +// // FieldOffsetsTuple +// private template FieldOffsetsTupleImpl(size_t n, T...) +// { +// static if (T.length == 0) +// { +// alias TypeTuple!() Result; +// } +// else +// { +// //private alias FieldTypeTuple!(T[0]) Types; +// private enum size_t myOffset = +// ((n + T[0].alignof - 1) / T[0].alignof) * T[0].alignof; +// static if (is(T[0] == struct)) +// { +// alias FieldTypeTuple!(T[0]) MyRep; +// alias FieldOffsetsTupleImpl!(myOffset, MyRep, T[1 .. $]).Result +// Result; +// } +// else +// { +// private enum size_t mySize = T[0].sizeof; +// alias TypeTuple!(myOffset) Head; +// static if (is(T == union)) +// { +// alias FieldOffsetsTupleImpl!(myOffset, T[1 .. $]).Result +// Tail; +// } +// else +// { +// alias FieldOffsetsTupleImpl!(myOffset + mySize, +// T[1 .. $]).Result +// Tail; +// } +// alias TypeTuple!(Head, Tail) Result; +// } +// } +// } + +// template FieldOffsetsTuple(T...) +// { +// alias FieldOffsetsTupleImpl!(0, T).Result FieldOffsetsTuple; +// } + +// unittest +// { +// alias FieldOffsetsTuple!(int) T1; +// assert(T1.length == 1 && T1[0] == 0); +// // +// struct S2 { char a; int b; char c; double d; char e, f; } +// alias FieldOffsetsTuple!(S2) T2; +// //pragma(msg, T2); +// static assert(T2.length == 6 +// && T2[0] == 0 && T2[1] == 4 && T2[2] == 8 && T2[3] == 16 +// && T2[4] == 24&& T2[5] == 25); +// // +// class C { int a, b, c, d; } +// struct S3 { char a; C b; char c; } +// alias FieldOffsetsTuple!(S3) T3; +// //pragma(msg, T2); +// static assert(T3.length == 3 +// && T3[0] == 0 && T3[1] == 4 && T3[2] == 8); +// // +// struct S4 { char a; union { int b; char c; } int d; } +// alias FieldOffsetsTuple!(S4) T4; +// //pragma(msg, FieldTypeTuple!(S4)); +// static assert(T4.length == 4 +// && T4[0] == 0 && T4[1] == 4 && T4[2] == 8); +// } + +// /*** +// Get the offsets of the fields of a struct or class. +// */ + +// template FieldOffsetsTuple(S) +// { +// static if (is(S == struct) || is(S == class)) +// alias typeof(S.tupleof) FieldTypeTuple; +// else +// static assert(0, "argument is not struct or class"); +// } + +/*** +Get the primitive types of the fields of a struct or class, in +topological order. + +Example: +---- +struct S1 { int a; float b; } +struct S2 { char[] a; union { S1 b; S1 * c; } } +alias RepresentationTypeTuple!(S2) R; +assert(R.length == 4 + && is(R[0] == char[]) && is(R[1] == int) + && is(R[2] == float) && is(R[3] == S1*)); +---- +*/ + +template RepresentationTypeTuple(T...) +{ + static if (T.length == 0) + { + alias TypeTuple!() RepresentationTypeTuple; + } + else + { + static if (is(T[0] == struct) || is(T[0] == union)) +// @@@BUG@@@ this should work +// alias .RepresentationTypes!(T[0].tupleof) +// RepresentationTypes; + alias .RepresentationTypeTuple!(FieldTypeTuple!(T[0]), + T[1 .. $]) + RepresentationTypeTuple; + else static if (is(T[0] U == typedef)) + { + alias .RepresentationTypeTuple!(FieldTypeTuple!(U), + T[1 .. $]) + RepresentationTypeTuple; + } + else + { + alias TypeTuple!(T[0], RepresentationTypeTuple!(T[1 .. $])) + RepresentationTypeTuple; + } + } +} + +unittest +{ + alias RepresentationTypeTuple!(int) S1; + static assert(is(S1 == TypeTuple!(int))); + struct S2 { int a; } + static assert(is(RepresentationTypeTuple!(S2) == TypeTuple!(int))); + struct S3 { int a; char b; } + static assert(is(RepresentationTypeTuple!(S3) == TypeTuple!(int, char))); + struct S4 { S1 a; int b; S3 c; } + static assert(is(RepresentationTypeTuple!(S4) == + TypeTuple!(int, int, int, char))); + + struct S11 { int a; float b; } + struct S21 { char[] a; union { S11 b; S11 * c; } } + alias RepresentationTypeTuple!(S21) R; + assert(R.length == 4 + && is(R[0] == char[]) && is(R[1] == int) + && is(R[2] == float) && is(R[3] == S11*)); +} + +/* +RepresentationOffsets +*/ + +// private template Repeat(size_t n, T...) +// { +// static if (n == 0) alias TypeTuple!() Repeat; +// else alias TypeTuple!(T, Repeat!(n - 1, T)) Repeat; +// } + +// template RepresentationOffsetsImpl(size_t n, T...) +// { +// static if (T.length == 0) +// { +// alias TypeTuple!() Result; +// } +// else +// { +// private enum size_t myOffset = +// ((n + T[0].alignof - 1) / T[0].alignof) * T[0].alignof; +// static if (!is(T[0] == union)) +// { +// alias Repeat!(n, FieldTypeTuple!(T[0])).Result +// Head; +// } +// static if (is(T[0] == struct)) +// { +// alias .RepresentationOffsetsImpl!(n, FieldTypeTuple!(T[0])).Result +// Head; +// } +// else +// { +// alias TypeTuple!(myOffset) Head; +// } +// alias TypeTuple!(Head, +// RepresentationOffsetsImpl!( +// myOffset + T[0].sizeof, T[1 .. $]).Result) +// Result; +// } +// } + +// template RepresentationOffsets(T) +// { +// alias RepresentationOffsetsImpl!(0, T).Result +// RepresentationOffsets; +// } + +// unittest +// { +// struct S1 { char c; int i; } +// alias RepresentationOffsets!(S1) Offsets; +// static assert(Offsets[0] == 0); +// //pragma(msg, Offsets[1]); +// static assert(Offsets[1] == 4); +// } + +// hasRawAliasing + +private template HasRawPointerImpl(T...) +{ + static if (T.length == 0) + { + enum result = false; + } + else + { + static if (is(T[0] foo : U*, U)) + enum hasRawAliasing = !is(U == invariant); + else static if (is(T[0] foo : U[], U)) + enum hasRawAliasing = !is(U == invariant); + else + enum hasRawAliasing = false; + enum result = hasRawAliasing || HasRawPointerImpl!(T[1 .. $]).result; + } +} + +/* +Statically evaluates to $(D true) if and only if $(D T)'s +representation contains at least one field of pointer or array type. +Members of class types are not considered raw pointers. Pointers to +invariant objects are not considered raw aliasing. + +Example: +--- +// simple types +static assert(!hasRawAliasing!(int)); +static assert(hasRawAliasing!(char*)); +// references aren't raw pointers +static assert(!hasRawAliasing!(Object)); +// built-in arrays do contain raw pointers +static assert(hasRawAliasing!(int[])); +// aggregate of simple types +struct S1 { int a; double b; } +static assert(!hasRawAliasing!(S1)); +// indirect aggregation +struct S2 { S1 a; double b; } +static assert(!hasRawAliasing!(S2)); +// struct with a pointer member +struct S3 { int a; double * b; } +static assert(hasRawAliasing!(S3)); +// struct with an indirect pointer member +struct S4 { S3 a; double b; } +static assert(hasRawAliasing!(S4)); +---- +*/ +private template hasRawAliasing(T...) +{ + enum hasRawAliasing + = HasRawPointerImpl!(RepresentationTypeTuple!(T)).result; +} + +unittest +{ +// simple types + static assert(!hasRawAliasing!(int)); + static assert(hasRawAliasing!(char*)); +// references aren't raw pointers + static assert(!hasRawAliasing!(Object)); + static assert(!hasRawAliasing!(int)); + struct S1 { int z; } + static assert(!hasRawAliasing!(S1)); + struct S2 { int* z; } + static assert(hasRawAliasing!(S2)); + struct S3 { int a; int* z; int c; } + static assert(hasRawAliasing!(S3)); + struct S4 { int a; int z; int c; } + static assert(!hasRawAliasing!(S4)); + struct S5 { int a; Object z; int c; } + static assert(!hasRawAliasing!(S5)); + union S6 { int a; int b; } + static assert(!hasRawAliasing!(S6)); + union S7 { int a; int * b; } + static assert(hasRawAliasing!(S7)); + typedef int* S8; + static assert(hasRawAliasing!(S8)); + enum S9 { a }; + static assert(!hasRawAliasing!(S9)); + // indirect members + struct S10 { S7 a; int b; } + static assert(hasRawAliasing!(S10)); + struct S11 { S6 a; int b; } + static assert(!hasRawAliasing!(S11)); +} + +/* +Statically evaluates to $(D true) if and only if $(D T)'s +representation includes at least one non-invariant object reference. +*/ + +private template hasObjects(T...) +{ + static if (T.length == 0) + { + enum hasObjects = false; + } + else static if (is(T[0] U == typedef)) + { + enum hasObjects = hasObjects!(U, T[1 .. $]); + } + else static if (is(T[0] == struct)) + { + enum hasObjects = hasObjects!( + RepresentationTypeTuple!(T[0]), T[1 .. $]); + } + else + { + enum hasObjects = is(T[0] == class) || hasObjects!(T[1 .. $]); + } +} + +/** +Returns $(D true) if and only if $(D T)'s representation includes at +least one of the following: $(OL $(LI a raw pointer $(D U*) and $(D U) +is not invariant;) $(LI an array $(D U[]) and $(D U) is not +invariant;) $(LI a reference to a class type $(D C) and $(D C) is not +invariant.)) +*/ + +template hasAliasing(T...) +{ + enum hasAliasing = hasRawAliasing!(T) || hasObjects!(T); +} + +unittest +{ + struct S1 { int a; Object b; } + static assert(hasAliasing!(S1)); + struct S2 { string a; } + static assert(!hasAliasing!(S2)); +} /*** * Get a $(D_PARAM TypeTuple) of the base class and base interfaces of @@ -209,18 +542,68 @@ template BaseClassesTuple(T) } } +/** + * Get a $(D_PARAM TypeTuple) of $(I all) interfaces directly or + * indirectly inherited by this class or interface. Interfaces do not + * repeat if multiply implemented. $(D_PARAM InterfacesTuple!(Object)) + * yields the empty type tuple. + * + * Example: + * --- + * import std.traits, std.typetuple, std.stdio; + * interface I1 { } + * interface I2 { } + * class A : I1, I2 { } + * class B : A, I1 { } + * class C : B { } + * + * void main() + * { + * alias InterfacesTuple!(C) TL; + * writeln(typeid(TL)); // prints: (I1, I2) + * } + * --- + */ + +template InterfacesTuple(T) +{ + static if (is(T == Object)) + { + alias TypeTuple!() InterfacesTuple; + } + static if (is(BaseTypeTuple!(T)[0] == Object)) + { + alias TypeTuple!(BaseTypeTuple!(T)[1 .. $]) InterfacesTuple; + } + else + { + alias NoDuplicates!( + TypeTuple!(BaseTypeTuple!(T)[1 .. $], // direct interfaces + InterfacesTuple!(BaseTypeTuple!(T)[0]))) + InterfacesTuple; + } +} + unittest { interface I1 {} interface I2 {} - class B1 {} + { + // doc example + class A : I1, I2 { } + class B : A, I1 { } + class C : B { } + alias InterfacesTuple!(C) TL; + assert(is(TL[0] == I1) && is(TL[1] == I2)); + } + class B1 : I1, I2 {} class B2 : B1, I1 {} class B3 : B2, I2 {} - alias BaseClassesTuple!(B3) TL; - assert(TL.length == 3); - assert(is (TL[0] == B2)); - assert(is (TL[1] == B1)); - assert(is (TL[2] == Object)); + alias InterfacesTuple!(B3) TL; + // + assert(TL.length == 2); + assert(is (TL[0] == I2)); + assert(is (TL[1] == I1)); } /** @@ -251,23 +634,25 @@ template TransitiveBaseTypeTuple(T) alias TypeTuple!() TransitiveBaseTypeTuple; else alias TypeTuple!(BaseClassesTuple!(T), - BaseTypeTuple!(T)[1 .. $]) + InterfacesTuple!(T)) TransitiveBaseTypeTuple; } unittest { interface I1 {} + interface I2 {} class B1 {} - class B2 : B1 {} + class B2 : B1, I1, I2 {} class B3 : B2, I1 {} alias TransitiveBaseTypeTuple!(B3) TL; - assert(TL.length == 4); + assert(TL.length == 5); assert(is (TL[0] == B2)); assert(is (TL[1] == B1)); assert(is (TL[2] == Object)); assert(is (TL[3] == I1)); - + assert(is (TL[4] == I2)); + assert(TransitiveBaseTypeTuple!(Object).length == 0); }