// Written in the D programming language. /** This package implements generic algorithms oriented towards the processing of sequences. Sequences processed by these functions define range-based interfaces. See also $(LINK2 std_range.html, Reference on ranges) and $(WEB ddili.org/ders/d.en/ranges.html, tutorial on ranges). Algorithms are categorized into the following submodules: $(BOOKTABLE , $(TR $(TH Category) $(TH Submodule) $(TH Functions) ) $(TR $(TDNW Searching) $(TDNW $(SUBMODULE searching)) $(TD $(SUBREF searching, all) $(SUBREF searching, any) $(SUBREF searching, balancedParens) $(SUBREF searching, boyerMooreFinder) $(SUBREF searching, canFind) $(SUBREF searching, commonPrefix) $(SUBREF searching, count) $(SUBREF searching, countUntil) $(SUBREF searching, endsWith) $(SUBREF searching, find) $(SUBREF searching, findAdjacent) $(SUBREF searching, findAmong) $(SUBREF searching, findSkip) $(SUBREF searching, findSplit) $(SUBREF searching, findSplitAfter) $(SUBREF searching, findSplitBefore) $(SUBREF searching, minCount) $(SUBREF searching, minPos) $(SUBREF searching, skipOver) $(SUBREF searching, startsWith) $(SUBREF searching, until) ) ) $(TR $(TDNW Comparison) $(TDNW $(SUBMODULE comparison)) $(TD $(SUBREF comparison, among) $(SUBREF comparison, castSwitch) $(SUBREF comparison, clamp) $(SUBREF comparison, cmp) $(SUBREF comparison, equal) $(SUBREF comparison, levenshteinDistance) $(SUBREF comparison, levenshteinDistanceAndPath) $(SUBREF comparison, max) $(SUBREF comparison, min) $(SUBREF comparison, mismatch) $(SUBREF comparison, predSwitch) ) ) $(TR $(TDNW Iteration) $(TDNW $(SUBMODULE iteration)) $(TD $(SUBREF iteration, cache) $(SUBREF iteration, cacheBidirectional) $(SUBREF iteration, each) $(SUBREF iteration, filter) $(SUBREF iteration, filterBidirectional) $(SUBREF iteration, group) $(SUBREF iteration, groupBy) $(SUBREF iteration, joiner) $(SUBREF iteration, map) $(SUBREF iteration, reduce) $(SUBREF iteration, splitter) $(SUBREF iteration, sum) $(SUBREF iteration, uniq) ) ) $(TR $(TDNW Sorting) $(TDNW $(SUBMODULE sorting)) $(TD $(SUBREF sorting, completeSort) $(SUBREF sorting, isPartitioned) $(SUBREF sorting, isSorted) $(SUBREF sorting, makeIndex) $(SUBREF sorting, multiSort) $(SUBREF sorting, nextEvenPermutation) $(SUBREF sorting, nextPermutation) $(SUBREF sorting, partialSort) $(SUBREF sorting, partition) $(SUBREF sorting, partition3) $(SUBREF sorting, schwartzSort) $(SUBREF sorting, sort) $(SUBREF sorting, topN) $(SUBREF sorting, topNCopy) ) ) $(TR $(TDNW Set operations) $(TDNW $(SUBMODULE setops)) $(TD $(SUBREF setops, cartesianProduct) $(SUBREF setops, largestPartialIntersection) $(SUBREF setops, largestPartialIntersectionWeighted) $(SUBREF setops, nWayUnion) $(SUBREF setops, setDifference) $(SUBREF setops, setIntersection) $(SUBREF setops, setSymmetricDifference) $(SUBREF setops, setUnion) ) ) $(TR $(TDNW Mutation) $(TDNW $(SUBMODULE mutation)) $(TD $(SUBREF mutation, bringToFront) $(SUBREF mutation, copy) $(SUBREF mutation, fill) $(SUBREF mutation, initializeAll) $(SUBREF mutation, move) $(SUBREF mutation, moveAll) $(SUBREF mutation, moveSome) $(SUBREF mutation, remove) $(SUBREF mutation, reverse) $(SUBREF mutation, strip) $(SUBREF mutation, stripLeft) $(SUBREF mutation, stripRight) $(SUBREF mutation, swap) $(SUBREF mutation, swapRanges) $(SUBREF mutation, uninitializedFill) ) ) $(TR $(TDNW Utility) $(TDNW -) $(TD $(MYREF forward) ) )) Many functions in this package 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 ---- $(BOOKTABLE Cheat Sheet, $(TR $(TH Function Name) $(TH Description)) $(LEADINGROW Searching) $(T2 searching, all, $(D all!"a > 0"([1, 2, 3, 4])) returns $(D true) because all elements are positive) $(T2 searching, any, $(D any!"a > 0"([1, 2, -3, -4])) returns $(D true) because at least one element is positive) $(T2 searching, balancedParens, $(D balancedParens("((1 + 1) / 2)")) returns $(D true) because the string has balanced parentheses.) $(T2 searching, boyerMooreFinder, $(D find("hello world", boyerMooreFinder("or"))) returns $(D "orld") using the $(LUCKY Boyer-Moore _algorithm).) $(T2 searching, canFind, $(D canFind("hello world", "or")) returns $(D true).) $(T2 searching, count, Counts elements that are equal to a specified value or satisfy a predicate. $(D count([1, 2, 1], 1)) returns $(D 2) and $(D count!"a < 0"([1, -3, 0])) returns $(D 1).) $(T2 searching, countUntil, $(D countUntil(a, b)) returns the number of steps taken in $(D a) to reach $(D b); for example, $(D countUntil("hello!", "o")) returns $(D 4).) $(T2 searching, commonPrefix, $(D commonPrefix("parakeet", "parachute")) returns $(D "para").) $(T2 searching, endsWith, $(D endsWith("rocks", "ks")) returns $(D true).) $(T2 searching, find, $(D find("hello world", "or")) returns $(D "orld") using linear search. (For binary search refer to $(XREF range,sortedRange).)) $(T2 searching, findAdjacent, $(D findAdjacent([1, 2, 3, 3, 4])) returns the subrange starting with two equal adjacent elements, i.e. $(D [3, 3, 4]).) $(T2 searching, findAmong, $(D findAmong("abcd", "qcx")) returns $(D "cd") because $(D 'c') is among $(D "qcx").) $(T2 searching, findSkip, If $(D a = "abcde"), then $(D findSkip(a, "x")) returns $(D false) and leaves $(D a) unchanged, whereas $(D findSkip(a, 'c')) advances $(D a) to $(D "cde") and returns $(D true).) $(T2 searching, findSplit, $(D findSplit("abcdefg", "de")) returns the three ranges $(D "abc"), $(D "de"), and $(D "fg").) $(T2 searching, findSplitAfter, $(D findSplitAfter("abcdefg", "de")) returns the two ranges $(D "abcde") and $(D "fg").) $(T2 searching, findSplitBefore, $(D findSplitBefore("abcdefg", "de")) returns the two ranges $(D "abc") and $(D "defg").) $(T2 searching, minCount, $(D minCount([2, 1, 1, 4, 1])) returns $(D tuple(1, 3)).) $(T2 searching, minPos, $(D minPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [1, 3, 4, 1]), i.e., positions the range at the first occurrence of its minimal element.) $(T2 searching, mismatch, $(D mismatch("parakeet", "parachute")) returns the two ranges $(D "keet") and $(D "chute").) $(T2 searching, skipOver, Assume $(D a = "blah"). Then $(D skipOver(a, "bi")) leaves $(D a) unchanged and returns $(D false), whereas $(D skipOver(a, "bl")) advances $(D a) to refer to $(D "ah") and returns $(D true).) $(T2 searching, startsWith, $(D startsWith("hello, world", "hello")) returns $(D true).) $(T2 searching, until, Lazily iterates a range until a specific value is found.) $(LEADINGROW Comparison) $(T2 comparison, among, Checks if a value is among a set of values, e.g. $(D if (v.among(1, 2, 3)) // `v` is 1, 2 or 3)) $(T2 comparison, castSwitch, $(D (new A()).castSwitch((A a)=>1,(B b)=>2)) returns $(D 1).) $(T2 comparison, clamp, $(D clamp(1, 3, 6)) returns $(D 3). $(D clamp(4, 3, 6)) returns $(D 4).) $(T2 comparison, cmp, $(D cmp("abc", "abcd")) is $(D -1), $(D cmp("abc", "aba")) is $(D 1), and $(D cmp("abc", "abc")) is $(D 0).) $(T2 comparison, equal, Compares ranges for element-by-element equality, e.g. $(D equal([1, 2, 3], [1.0, 2.0, 3.0])) returns $(D true).) $(T2 comparison, levenshteinDistance, $(D levenshteinDistance("kitten", "sitting")) returns $(D 3) by using the $(LUCKY Levenshtein distance _algorithm).) $(T2 comparison, levenshteinDistanceAndPath, $(D levenshteinDistanceAndPath("kitten", "sitting")) returns $(D tuple(3, "snnnsni")) by using the $(LUCKY Levenshtein distance _algorithm).) $(T2 comparison, max, $(D max(3, 4, 2)) returns $(D 4).) $(T2 comparison, min, $(D min(3, 4, 2)) returns $(D 2).) $(T2 comparison, mismatch, $(D mismatch("oh hi", "ohayo")) returns $(D tuple(" hi", "ayo")).) $(T2 comparison, predSwitch, $(D 2.predSwitch(1, "one", 2, "two", 3, "three")) returns $(D "two").) $(LEADINGROW Iteration) $(T2 iteration, cache, Eagerly evaluates and caches another range's $(D front).) $(T2 iteration, cacheBidirectional, As above, but also provides $(D back) and $(D popBack).) $(T2 iteration, each, $(D each!writeln([1, 2, 3])) eagerly prints the numbers $(D 1), $(D 2) and $(D 3) on their own lines.) $(T2 iteration, filter, $(D filter!"a > 0"([1, -1, 2, 0, -3])) iterates over elements $(D 1) and $(D 2).) $(T2 iteration, filterBidirectional, Similar to $(D filter), but also provides $(D back) and $(D popBack) at a small increase in cost.) $(T2 iteration, group, $(D group([5, 2, 2, 3, 3])) returns a range containing the tuples $(D tuple(5, 1)), $(D tuple(2, 2)), and $(D tuple(3, 2)).) $(T2 iteration, groupBy, $(D groupBy!((a,b) => a[1] == b[1])([[1, 1], [1, 2], [2, 2], [2, 1]])) returns a range containing 3 subranges: the first with just $(D [1, 1]); the second with the elements $(D [1, 2]) and $(D [2, 2]); and the third with just $(D [2, 1]).) $(T2 iteration, joiner, $(D joiner(["hello", "world!"], "; ")) returns a range that iterates over the characters $(D "hello; world!"). No new string is created - the existing inputs are iterated.) $(T2 iteration, map, $(D map!"2 * a"([1, 2, 3])) lazily returns a range with the numbers $(D 2), $(D 4), $(D 6).) $(T2 iteration, reduce, $(D reduce!"a + b"([1, 2, 3, 4])) returns $(D 10).) $(T2 iteration, splitter, Lazily splits a range by a separator.) $(T2 iteration, sum, Same as $(D reduce), but specialized for accurate summation.) $(T2 iteration, uniq, Iterates over the unique elements in a range, which is assumed sorted.) $(LEADINGROW Sorting) $(T2 sorting, completeSort, If $(D a = [10, 20, 30]) and $(D b = [40, 6, 15]), then $(D completeSort(a, b)) leaves $(D a = [6, 10, 15]) and $(D b = [20, 30, 40]). The range $(D a) must be sorted prior to the call, and as a result the combination $(D $(XREF range,chain)(a, b)) is sorted.) $(T2 sorting, isPartitioned, $(D isPartitioned!"a < 0"([-1, -2, 1, 0, 2])) returns $(D true) because the predicate is $(D true) for a portion of the range and $(D false) afterwards.) $(T2 sorting, isSorted, $(D isSorted([1, 1, 2, 3])) returns $(D true).) $(T2 sorting, makeIndex, Creates a separate index for a range.) $(T2 sorting, nextEvenPermutation, Computes the next lexicographically greater even permutation of a range in-place.) $(T2 sorting, nextPermutation, Computes the next lexicographically greater permutation of a range in-place.) $(T2 sorting, partialSort, If $(D a = [5, 4, 3, 2, 1]), then $(D partialSort(a, 3)) leaves $(D a[0 .. 3] = [1, 2, 3]). The other elements of $(D a) are left in an unspecified order.) $(T2 sorting, partition, Partitions a range according to a predicate.) $(T2 sorting, partition3, Partitions a range in three parts (less than, equal, greater than the given pivot).) $(T2 sorting, schwartzSort, Sorts with the help of the $(LUCKY Schwartzian transform).) $(T2 sorting, sort, Sorts.) $(T2 sorting, topN, Separates the top elements in a range.) $(T2 sorting, topNCopy, Copies out the top elements of a range.) $(LEADINGROW Set operations) $(T2 setops, cartesianProduct, Computes Cartesian product of two ranges.) $(T2 setops, largestPartialIntersection, Copies out the values that occur most frequently in a range of ranges.) $(T2 setops, largestPartialIntersectionWeighted, Copies out the values that occur most frequently (multiplied by per-value weights) in a range of ranges.) $(T2 setops, nWayUnion, Computes the union of a set of sets implemented as a range of sorted ranges.) $(T2 setops, setDifference, Lazily computes the set difference of two or more sorted ranges.) $(T2 setops, setIntersection, Lazily computes the intersection of two or more sorted ranges.) $(T2 setops, setSymmetricDifference, Lazily computes the symmetric set difference of two or more sorted ranges.) $(T2 setops, setUnion, Lazily computes the set union of two or more sorted ranges.) $(LEADINGROW Mutation) $(T2 mutation, bringToFront, If $(D a = [1, 2, 3]) and $(D b = [4, 5, 6, 7]), $(D bringToFront(a, b)) leaves $(D a = [4, 5, 6]) and $(D b = [7, 1, 2, 3]).) $(T2 mutation, copy, Copies a range to another. If $(D a = [1, 2, 3]) and $(D b = new int[5]), then $(D copy(a, b)) leaves $(D b = [1, 2, 3, 0, 0]) and returns $(D b[3 .. $]).) $(T2 mutation, fill, Fills a range with a pattern, e.g., if $(D a = new int[3]), then $(D fill(a, 4)) leaves $(D a = [4, 4, 4]) and $(D fill(a, [3, 4])) leaves $(D a = [3, 4, 3]).) $(T2 mutation, initializeAll, If $(D a = [1.2, 3.4]), then $(D initializeAll(a)) leaves $(D a = [double.init, double.init]).) $(T2 mutation, move, $(D move(a, b)) moves $(D a) into $(D b). $(D move(a)) reads $(D a) destructively.) $(T2 mutation, moveAll, Moves all elements from one range to another.) $(T2 mutation, moveSome, Moves as many elements as possible from one range to another.) $(T2 mutation, remove, Removes elements from a range in-place, and returns the shortened range.) $(T2 mutation, reverse, If $(D a = [1, 2, 3]), $(D reverse(a)) changes it to $(D [3, 2, 1]).) $(T2 mutation, strip, Strips all leading and trailing elements equal to a value, or that satisfy a predicate. If $(D a = [1, 1, 0, 1, 1]), then $(D strip(a, 1)) and $(D strip!(e => e == 1)(a)) returns $(D [0]).) $(T2 mutation, stripLeft, Strips all leading elements equal to a value, or that satisfy a predicate. If $(D a = [1, 1, 0, 1, 1]), then $(D stripLeft(a, 1)) and $(D stripLeft!(e => e == 1)(a)) returns $(D [0, 1, 1]).) $(T2 mutation, stripRight, Strips all trailing elements equal to a value, or that satisfy a predicate. If $(D a = [1, 1, 0, 1, 1]), then $(D stripRight(a, 1)) and $(D stripRight!(e => e == 1)(a)) returns $(D [1, 1, 0]).) $(T2 mutation, swap, Swaps two values.) $(T2 mutation, swapRanges, Swaps all elements of two ranges.) $(T2 mutation, uninitializedFill, Fills a range (assumed uninitialized) with a value.) ) Macros: T2=$(TR $(TD $(SUBREF $1, $2)) $(TD $(T2_SKIP $+))) T2_SKIP=$+ WIKI = Phobos/StdAlgorithm SUBMODULE = $(LINK2 std_algorithm_$1.html, std.algorithm.$1) SUBREF = $(LINK2 std_algorithm_$1.html#.$2, $(TT $2))$(NBSP) Copyright: Andrei Alexandrescu 2008-. License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(WEB erdani.com, Andrei Alexandrescu) Source: $(PHOBOSSRC std/_algorithm.d) */ module std.algorithm; //debug = std_algorithm; public import std.algorithm.comparison; public import std.algorithm.iteration; public import std.algorithm.mutation; public import std.algorithm.setops; public import std.algorithm.searching; public import std.algorithm.sorting; // FIXME import std.functional; // : unaryFun, binaryFun; import std.range.primitives; // FIXME import std.range; // : SortedRange; import std.traits; // FIXME import std.typecons; // : tuple, Tuple; // FIXME import std.typetuple; // : TypeTuple, staticMap, allSatisfy, anySatisfy; version(unittest) debug(std_algorithm) import std.stdio; package T* addressOf(T)(ref T val) { return &val; } // Same as std.string.format, but "self-importing". // Helps reduce code and imports, particularly in static asserts. // Also helps with missing imports errors. package template algoFormat() { import std.format : format; alias algoFormat = format; } /** Forwards function arguments with saving ref-ness. */ template forward(args...) { import std.typetuple; static if (args.length) { alias arg = args[0]; static if (__traits(isRef, arg)) alias fwd = arg; else @property fwd()(){ return move(arg); } alias forward = TypeTuple!(fwd, forward!(args[1..$])); } else alias forward = TypeTuple!(); } /// @safe unittest { class C { static int foo(int n) { return 1; } static int foo(ref int n) { return 2; } } int bar()(auto ref int x) { return C.foo(forward!x); } assert(bar(1) == 1); int i; assert(bar(i) == 2); } /// @safe unittest { void foo(int n, ref string s) { s = null; foreach (i; 0..n) s ~= "Hello"; } // forwards all arguments which are bound to parameter tuple void bar(Args...)(auto ref Args args) { return foo(forward!args); } // forwards all arguments with swapping order void baz(Args...)(auto ref Args args) { return foo(forward!args[$/2..$], forward!args[0..$/2]); } string s; bar(1, s); assert(s == "Hello"); baz(s, 2); assert(s == "HelloHello"); } @safe unittest { auto foo(TL...)(auto ref TL args) { string result = ""; foreach (i, _; args) { //pragma(msg, "[",i,"] ", __traits(isRef, args[i]) ? "L" : "R"); result ~= __traits(isRef, args[i]) ? "L" : "R"; } return result; } string bar(TL...)(auto ref TL args) { return foo(forward!args); } string baz(TL...)(auto ref TL args) { int x; return foo(forward!args[3], forward!args[2], 1, forward!args[1], forward!args[0], x); } struct S {} S makeS(){ return S(); } int n; string s; assert(bar(S(), makeS(), n, s) == "RRLL"); assert(baz(S(), makeS(), n, s) == "LLRRRL"); } @safe unittest { ref int foo(ref int a) { return a; } ref int bar(Args)(auto ref Args args) { return foo(forward!args); } static assert(!__traits(compiles, { auto x1 = bar(3); })); // case of NG int value = 3; auto x2 = bar(value); // case of OK } // 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 (r.empty) return r; // //auto target = begin(r), e = end(r); // auto target = r; // auto source = r; // source.popFront(); // while (!source.empty) // { // if (!pred(target.front, source.front)) // { // target.popFront(); // continue; // } // // found an equal *source and *target // for (;;) // { // //@@@ // //move(source.front, target.front); // target[0] = source[0]; // source.popFront(); // if (source.empty) break; // if (!pred(target.front, source.front)) target.popFront(); // } // 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 ]); // } // 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 ss = SwapStrategy.unstable, // alias move = .move, // Range)(Range r) // { // alias It = Iterator!(Range); // static void assignIter(It a, It b) { move(*b, *a); } // return range(begin(r), partitionold!(not!(pred), ss, assignIter, 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).empty); // } /* * Reduces $(D r) by overwriting all elements $(D x) that satisfy $(D pred(x, v)). Returns the reduced range. 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 = "a == b", // SwapStrategy ss = SwapStrategy.semistable, // Range, Value)(Range r, Value v) // { // alias It = Iterator!(Range); // bool comp(typeof(*It) a) { return !binaryFun!(pred)(a, v); } // static void assignIterB(It a, It b) { *a = *b; } // return range(begin(r), // partitionold!(comp, // ss, assignIterB, Range)(r)); // } // 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 ]); // } /** Specifies whether the output of certain algorithm is desired in sorted format. */ enum SortOutput { no, /// Don't sort output yes, /// Sort output } void topNIndex( alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range, RangeIndex)(Range r, RangeIndex index, SortOutput sorted = SortOutput.no) if (isIntegral!(ElementType!(RangeIndex))) { import std.container : BinaryHeap; import std.exception : enforce; if (index.empty) return; enforce(ElementType!(RangeIndex).max >= index.length, "Index type too small"); bool indirectLess(ElementType!(RangeIndex) a, ElementType!(RangeIndex) b) { return binaryFun!(less)(r[a], r[b]); } auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0); foreach (i; 0 .. r.length) { heap.conditionalInsert(cast(ElementType!RangeIndex) i); } if (sorted == SortOutput.yes) { while (!heap.empty) heap.removeFront(); } } void topNIndex( alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range, RangeIndex)(Range r, RangeIndex index, SortOutput sorted = SortOutput.no) if (is(ElementType!(RangeIndex) == ElementType!(Range)*)) { import std.container : BinaryHeap; if (index.empty) return; static bool indirectLess(const ElementType!(RangeIndex) a, const ElementType!(RangeIndex) b) { return binaryFun!less(*a, *b); } auto heap = BinaryHeap!(RangeIndex, indirectLess)(index, 0); foreach (i; 0 .. r.length) { heap.conditionalInsert(&r[i]); } if (sorted == SortOutput.yes) { while (!heap.empty) heap.removeFront(); } } unittest { import std.conv : text; debug(std_algorithm) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); { int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ]; int*[] b = new int*[5]; topNIndex!("a > b")(a, b, SortOutput.yes); //foreach (e; b) writeln(*e); assert(b == [ &a[0], &a[2], &a[1], &a[6], &a[5]]); } { int[] a = [ 10, 8, 9, 2, 4, 6, 7, 1, 3, 5 ]; auto b = new ubyte[5]; topNIndex!("a > b")(a, b, SortOutput.yes); //foreach (e; b) writeln(e, ":", a[e]); assert(b == [ cast(ubyte) 0, cast(ubyte)2, cast(ubyte)1, cast(ubyte)6, cast(ubyte)5], text(b)); } } /+ // topNIndexImpl // @@@BUG1904 /*private*/ void topNIndexImpl( alias less, bool sortAfter, SwapStrategy ss, SRange, TRange)(SRange source, TRange target) { alias lessFun = binaryFun!(less); static assert(ss == SwapStrategy.unstable, "Stable indexing not yet implemented"); alias SIter = Iterator!(SRange); alias TElem = std.iterator.ElementType!(TRange); enum usingInt = isIntegral!(TElem); static if (usingInt) { enforce(source.length <= TElem.max, "Numeric overflow at risk in computing topNIndexImpl"); } // types and functions used within SIter index2iter(TElem a) { static if (!usingInt) return a; else return begin(source) + a; } bool indirectLess(TElem a, TElem b) { return lessFun(*index2iter(a), *index2iter(b)); } void indirectCopy(SIter from, ref TElem to) { static if (!usingInt) to = from; else to = cast(TElem)(from - begin(source)); } // copy beginning of collection into the target auto sb = begin(source), se = end(source), tb = begin(target), te = end(target); for (; sb != se; ++sb, ++tb) { if (tb == te) break; indirectCopy(sb, *tb); } // if the index's size is same as the source size, just quicksort it // otherwise, heap-insert stuff in it. if (sb == se) { // everything in source is now in target... just sort the thing static if (sortAfter) sort!(indirectLess, ss)(target); } else { // heap-insert te = tb; tb = begin(target); target = range(tb, te); makeHeap!(indirectLess)(target); // add stuff to heap for (; sb != se; ++sb) { if (!lessFun(*sb, *index2iter(*tb))) continue; // copy the source over the smallest indirectCopy(sb, *tb); heapify!(indirectLess)(target, tb); } static if (sortAfter) sortHeap!(indirectLess)(target); } } /** topNIndex */ void topNIndex( alias less, SwapStrategy ss = SwapStrategy.unstable, SRange, TRange)(SRange source, TRange target) { return .topNIndexImpl!(less, false, ss)(source, target); } /// Ditto void topNIndex( string less, SwapStrategy ss = SwapStrategy.unstable, SRange, TRange)(SRange source, TRange target) { return .topNIndexImpl!(binaryFun!(less), false, ss)(source, target); } // partialIndex /** Computes an index for $(D source) based on the comparison $(D less) and deposits the result in $(D target). It is acceptable that $(D target.length < source.length), in which case only the smallest $(D target.length) elements in $(D source) get indexed. The target provides a sorted "view" into $(D source). This technique is similar to sorting and partial sorting, but it is more flexible because (1) it allows "sorting" of immutable collections, (2) allows binary search even if the original collection does not offer random access, (3) allows multiple indexes, each on a different comparison criterion, (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 $(BIGOH source.length * log(target.length)). Two types of indexes are accepted. They are selected by simply passing the appropriate $(D target) argument: $(OL $(LI Indexes of type $(D Iterator!(Source)), in which case the index will be sorted with the predicate $(D less(*a, *b));) $(LI Indexes of an integral type (e.g. $(D size_t)), in which case the index will be sorted with the predicate $(D less(source[a], source[b])).)) Example: ---- immutable arr = [ 2, 3, 1 ]; int* index[3]; partialIndex(arr, index); assert(*index[0] == 1 && *index[1] == 2 && *index[2] == 3); assert(isSorted!("*a < *b")(index)); ---- */ void partialIndex( alias less, SwapStrategy ss = SwapStrategy.unstable, SRange, TRange)(SRange source, TRange target) { return .topNIndexImpl!(less, true, ss)(source, target); } unittest { debug(std_algorithm) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); immutable arr = [ 2, 3, 1 ]; auto index = new immutable(int)*[3]; partialIndex!(binaryFun!("a < b"))(arr, index); assert(*index[0] == 1 && *index[1] == 2 && *index[2] == 3); assert(isSorted!("*a < *b")(index)); } unittest { debug(std_algorithm) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done."); static bool less(int a, int b) { return a < b; } { string[] x = ([ "c", "a", "b", "d" ]).dup; // test with integrals auto index1 = new size_t[x.length]; partialIndex!(q{a < b})(x, index1); assert(index1[0] == 1 && index1[1] == 2 && index1[2] == 0 && index1[3] == 3); // half-sized index1 = new size_t[x.length / 2]; partialIndex!(q{a < b})(x, index1); assert(index1[0] == 1 && index1[1] == 2); // and with iterators auto index = new string*[x.length]; partialIndex!(q{a < b})(x, index); assert(isSorted!(q{*a < *b})(index)); assert(*index[0] == "a" && *index[1] == "b" && *index[2] == "c" && *index[3] == "d"); } { immutable arr = [ 2, 3, 1 ]; auto index = new immutable(int)*[arr.length]; partialIndex!(less)(arr, index); assert(*index[0] == 1 && *index[1] == 2 && *index[2] == 3); assert(isSorted!(q{*a < *b})(index)); } // random data auto b = rndstuff!(string)(); auto index = new string*[b.length]; partialIndex!((a, b) => std.uni.toUpper(a) < std.uni.toUpper(b))(b, index); assert(isSorted!((a, b) => std.uni.toUpper(*a) < std.uni.toUpper(*b))(index)); // random data with indexes auto index1 = new size_t[b.length]; bool cmp(string x, string y) { return std.uni.toUpper(x) < std.uni.toUpper(y); } partialIndex!(cmp)(b, index1); bool check(size_t x, size_t y) { return std.uni.toUpper(b[x]) < std.uni.toUpper(b[y]); } assert(isSorted!(check)(index1)); } // Commented out for now, needs reimplementation // // 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 ss = SwapStrategy.unstable, // Range)(Range r) // { // alias Iter = Iterator!(Range); // 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 // alias Transformed = typeof(transform(*result[0])); // static bool indirectLess(Transformed a, Transformed b) // { // return less(a, b); // } // static Transformed indirectTransform(Iter a) // { // return transform(*a); // } // schwartzSort!(indirectTransform, less, ss)(result); // return result; // } // /// Ditto // Iterator!(Range)[] schwartzMakeIndex( // alias transform, // string less = q{a < b}, // SwapStrategy ss = SwapStrategy.unstable, // Range)(Range r) // { // return .schwartzMakeIndex!( // transform, binaryFun!(less), ss, Range)(r); // } // version (wyda) 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)); // } +/ // Internal random array generators version(unittest) { package enum size_t maxArraySize = 50; package enum size_t minArraySize = maxArraySize - 1; package string[] rndstuff(T : string)() { import std.random : Random, unpredictableSeed, uniform; static Random rnd; static bool first = true; if (first) { rnd = Random(unpredictableSeed); first = false; } string[] result = new string[uniform(minArraySize, maxArraySize, rnd)]; string alpha = "abcdefghijABCDEFGHIJ"; foreach (ref s; result) { foreach (i; 0 .. uniform(0u, 20u, rnd)) { auto j = uniform(0, alpha.length - 1, rnd); s ~= alpha[j]; } } return result; } package int[] rndstuff(T : int)() { import std.random : Random, unpredictableSeed, uniform; static Random rnd; static bool first = true; if (first) { rnd = Random(unpredictableSeed); first = false; } int[] result = new int[uniform(minArraySize, maxArraySize, rnd)]; foreach (ref i; result) { i = uniform(-100, 100, rnd); } return result; } package double[] rndstuff(T : double)() { double[] result; foreach (i; rndstuff!(int)()) { result ~= i / 50.0; } return result; } }