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); }