mirror of
https://github.com/dlang/phobos.git
synced 2025-04-28 22:21:09 +03:00
14609 lines
427 KiB
D
14609 lines
427 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
<script type="text/javascript">inhibitQuickIndex = 1</script>
|
|
|
|
$(BOOKTABLE ,
|
|
$(TR $(TH Category) $(TH Functions)
|
|
)
|
|
$(TR $(TDNW Searching) $(TD $(MYREF all) $(MYREF any) $(MYREF balancedParens) $(MYREF
|
|
boyerMooreFinder) $(MYREF canFind) $(MYREF commonPrefix) $(MYREF count)
|
|
$(MYREF countUntil) $(MYREF endsWith) $(MYREF find) $(MYREF
|
|
findAdjacent) $(MYREF findAmong) $(MYREF findSkip) $(MYREF findSplit)
|
|
$(MYREF findSplitAfter) $(MYREF findSplitBefore) $(MYREF minCount)
|
|
$(MYREF minPos) $(MYREF mismatch) $(MYREF skipOver) $(MYREF startsWith)
|
|
$(MYREF until) )
|
|
)
|
|
$(TR $(TDNW Comparison) $(TD $(MYREF among) $(MYREF castSwitch) $(MYREF cmp)
|
|
$(MYREF equal) $(MYREF levenshteinDistance) $(MYREF levenshteinDistanceAndPath)
|
|
$(MYREF max) $(MYREF min) $(MYREF mismatch) $(MYREF clamp) $(MYREF
|
|
predSwitch))
|
|
)
|
|
$(TR $(TDNW Iteration) $(TD $(MYREF cache) $(MYREF cacheBidirectional)
|
|
$(MYREF filter) $(MYREF filterBidirectional)
|
|
$(MYREF group) $(MYREF groupBy) $(MYREF joiner) $(MYREF map) $(MYREF reduce)
|
|
$(MYREF splitter) $(MYREF sum) $(MYREF uniq) )
|
|
)
|
|
$(TR $(TDNW Sorting) $(TD $(MYREF completeSort) $(MYREF isPartitioned)
|
|
$(MYREF isSorted) $(MYREF makeIndex) $(MYREF multiSort) $(MYREF nextPermutation)
|
|
$(MYREF nextEvenPermutation) $(MYREF partialSort) $(MYREF
|
|
partition) $(MYREF partition3) $(MYREF schwartzSort) $(MYREF sort)
|
|
$(MYREF topN) $(MYREF topNCopy) )
|
|
)
|
|
$(TR $(TDNW Set operations) $(TD $(MYREF cartesianProduct) $(MYREF
|
|
largestPartialIntersection) $(MYREF largestPartialIntersectionWeighted)
|
|
$(MYREF nWayUnion) $(MYREF setDifference) $(MYREF setIntersection) $(MYREF
|
|
setSymmetricDifference) $(MYREF setUnion) )
|
|
)
|
|
$(TR $(TDNW Mutation) $(TD $(MYREF bringToFront) $(MYREF copy) $(MYREF
|
|
fill) $(MYREF initializeAll) $(MYREF move) $(MYREF moveAll) $(MYREF
|
|
moveSome) $(MYREF remove) $(MYREF reverse) $(MYREF strip) $(MYREF stripLeft)
|
|
$(MYREF stripRight) $(MYREF swap) $(MYREF swapRanges) $(MYREF uninitializedFill) )
|
|
)
|
|
$(TR $(TDNW Utility) $(TD $(MYREF forward) ))
|
|
)
|
|
|
|
Implements algorithms oriented mainly towards processing of
|
|
sequences. Some functions are semantic equivalents or supersets of
|
|
those found in the $(D $(LESS)_algorithm$(GREATER)) header in $(WEB
|
|
sgi.com/tech/stl/, Alexander Stepanov's Standard Template Library) for
|
|
C++. Sequences processed by these functions define range-based interfaces.
|
|
|
|
$(LINK2 std_range.html, Reference on ranges)$(BR)
|
|
$(LINK2 http://ddili.org/ders/d.en/ranges.html, Tutorial on ranges)
|
|
|
|
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
|
|
----
|
|
|
|
$(BOOKTABLE Cheat Sheet,
|
|
$(TR $(TH Function Name) $(TH Description)
|
|
)
|
|
$(LEADINGROW Searching
|
|
)
|
|
$(TR $(TDNW $(LREF all)) $(TD $(D all!"a > 0"([1, 2, 3, 4])) returns $(D true) because all elements are positive)
|
|
)
|
|
$(TR $(TDNW $(LREF any)) $(TD $(D any!"a > 0"([1, 2, -3, -4])) returns $(D true) because at least one element is positive)
|
|
)
|
|
$(TR $(TDNW $(LREF balancedParens)) $(TD $(D
|
|
balancedParens("((1 + 1) / 2)")) returns $(D true) because the string
|
|
has balanced parentheses.)
|
|
)
|
|
$(TR $(TDNW $(LREF boyerMooreFinder)) $(TD $(D find("hello
|
|
world", boyerMooreFinder("or"))) returns $(D "orld") using the $(LUCKY
|
|
Boyer-Moore _algorithm).)
|
|
)
|
|
$(TR $(TDNW $(LREF canFind)) $(TD $(D canFind("hello world",
|
|
"or")) returns $(D true).)
|
|
)
|
|
$(TR $(TDNW $(LREF count)) $(TD 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).)
|
|
)
|
|
$(TR $(TDNW $(LREF countUntil)) $(TD $(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).)
|
|
)
|
|
$(TR $(TDNW $(LREF commonPrefix)) $(TD $(D commonPrefix("parakeet",
|
|
"parachute")) returns $(D "para").)
|
|
)
|
|
$(TR $(TDNW $(LREF endsWith)) $(TD $(D endsWith("rocks", "ks"))
|
|
returns $(D true).)
|
|
)
|
|
$(TR $(TD $(LREF find)) $(TD $(D find("hello world",
|
|
"or")) returns $(D "orld") using linear search. (For binary search refer
|
|
to $(XREF range,sortedRange).))
|
|
)
|
|
$(TR $(TDNW $(LREF findAdjacent)) $(TD $(D findAdjacent([1, 2,
|
|
3, 3, 4])) returns the subrange starting with two equal adjacent
|
|
elements, i.e. $(D [3, 3, 4]).)
|
|
)
|
|
$(TR $(TDNW $(LREF findAmong)) $(TD $(D findAmong("abcd",
|
|
"qcx")) returns $(D "cd") because $(D 'c') is among $(D "qcx").)
|
|
)
|
|
$(TR $(TDNW $(LREF findSkip)) $(TD 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).)
|
|
)
|
|
$(TR $(TDNW $(LREF findSplit)) $(TD $(D findSplit("abcdefg",
|
|
"de")) returns the three ranges $(D "abc"), $(D "de"), and $(D
|
|
"fg").)
|
|
)
|
|
$(TR $(TDNW $(LREF findSplitAfter)) $(TD $(D
|
|
findSplitAfter("abcdefg", "de")) returns the two ranges $(D "abcde")
|
|
and $(D "fg").)
|
|
)
|
|
$(TR $(TDNW $(LREF findSplitBefore)) $(TD $(D
|
|
findSplitBefore("abcdefg", "de")) returns the two ranges $(D "abc") and
|
|
$(D "defg").)
|
|
)
|
|
$(TR $(TDNW $(LREF minCount)) $(TD $(D minCount([2, 1, 1, 4,
|
|
1])) returns $(D tuple(1, 3)).)
|
|
)
|
|
$(TR $(TDNW $(LREF minPos)) $(TD $(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.)
|
|
)
|
|
$(TR $(TDNW $(LREF mismatch)) $(TD $(D mismatch("parakeet", "parachute"))
|
|
returns the two ranges $(D "keet") and $(D "chute").)
|
|
)
|
|
$(TR $(TDNW $(LREF skipOver)) $(TD 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).)
|
|
)
|
|
$(TR $(TDNW $(LREF startsWith)) $(TD $(D startsWith("hello,
|
|
world", "hello")) returns $(D true).)
|
|
)
|
|
$(TR $(TDNW $(LREF until)) $(TD Lazily iterates a range
|
|
until a specific value is found.)
|
|
)
|
|
$(LEADINGROW Comparison
|
|
)
|
|
$(TR $(TDNW $(LREF among)) $(TD 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))
|
|
)
|
|
$(TR $(TDNW $(LREF castSwitch)) $(TD $(D (new A()).castSwitch((A a)=>1,(B
|
|
b)=>2)) returns $(D 1).)
|
|
)
|
|
$(TR $(TDNW $(LREF cmp)) $(TD $(D cmp("abc", "abcd")) is $(D
|
|
-1), $(D cmp("abc", "aba")) is $(D 1), and $(D cmp("abc", "abc")) is
|
|
$(D 0).)
|
|
)
|
|
$(TR $(TDNW $(LREF equal)) $(TD Compares ranges for
|
|
element-by-element equality, e.g. $(D equal([1, 2, 3], [1.0, 2.0,
|
|
3.0])) returns $(D true).)
|
|
)
|
|
$(TR $(TDNW $(LREF levenshteinDistance)) $(TD $(D
|
|
levenshteinDistance("kitten", "sitting")) returns $(D 3) by using the
|
|
$(LUCKY Levenshtein distance _algorithm).)
|
|
)
|
|
$(TR $(TDNW $(LREF levenshteinDistanceAndPath)) $(TD $(D
|
|
levenshteinDistanceAndPath("kitten", "sitting")) returns $(D tuple(3,
|
|
"snnnsni")) by using the $(LUCKY Levenshtein distance _algorithm).)
|
|
)
|
|
$(TR $(TDNW $(LREF max)) $(TD $(D max(3, 4, 2)) returns $(D
|
|
4).)
|
|
)
|
|
$(TR $(TDNW $(LREF min)) $(TD $(D min(3, 4, 2)) returns $(D
|
|
2).)
|
|
)
|
|
$(TR $(TDNW $(LREF clamp)) $(TD $(D clamp(1, 3, 6)) returns $(D
|
|
3). $(D clamp(4, 3, 6)) return $(D 4).)
|
|
)
|
|
$(TR $(TDNW $(LREF mismatch)) $(TD $(D mismatch("oh hi",
|
|
"ohayo")) returns $(D tuple(" hi", "ayo")).)
|
|
)
|
|
$(TR $(TDNW $(LREF predSwitch)) $(TD 2.predSwitch(1, "one", 2, "two", 3,
|
|
"three") returns $(D "two").)
|
|
)
|
|
$(LEADINGROW Iteration
|
|
)
|
|
$(TR $(TDNW $(LREF cache)) $(TD Eagerly evaluates and caches
|
|
another range's $(D front).)
|
|
)
|
|
$(TR $(TDNW $(LREF cacheBidirectional)) $(TD As above, but also
|
|
provides $(D back) and $(D popBack).)
|
|
)
|
|
$(TR $(TDNW $(LREF filter)) $(TD $(D filter!"a > 0"([1, -1, 2,
|
|
0, -3])) iterates over elements $(D 1) and $(D 2).)
|
|
)
|
|
$(TR $(TDNW $(LREF filterBidirectional)) $(TD Similar to $(D
|
|
filter), but also provides $(D back) and $(D popBack) at a small
|
|
increase in cost.)
|
|
)
|
|
$(TR $(TDNW $(LREF group)) $(TD $(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)).)
|
|
)
|
|
$(TR $(TDNW $(LREF groupBy)) $(TD $(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]).)
|
|
)
|
|
$(TR $(TDNW $(LREF joiner)) $(TD $(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.)
|
|
)
|
|
$(TR $(TDNW $(LREF map)) $(TD $(D map!"2 * a"([1, 2, 3]))
|
|
lazily returns a range with the numbers $(D 2), $(D 4), $(D 6).)
|
|
)
|
|
$(TR $(TDNW $(LREF reduce)) $(TD $(D reduce!"a + b"([1, 2, 3,
|
|
4])) returns $(D 10).)
|
|
)
|
|
$(TR $(TDNW $(LREF splitter)) $(TD Lazily splits a range by a
|
|
separator.)
|
|
)
|
|
$(TR $(TDNW $(LREF sum)) $(TD Same as $(D reduce), but specialized for
|
|
accurate summation.)
|
|
)
|
|
$(TR $(TDNW $(LREF uniq)) $(TD Iterates over the unique elements
|
|
in a range, which is assumed sorted.)
|
|
)
|
|
$(LEADINGROW Sorting
|
|
)
|
|
$(TR $(TDNW $(LREF completeSort)) $(TD 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.)
|
|
)
|
|
$(TR $(TDNW $(LREF isPartitioned)) $(TD $(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.)
|
|
)
|
|
$(TR $(TDNW $(LREF isSorted)) $(TD $(D isSorted([1, 1, 2, 3]))
|
|
returns $(D true).)
|
|
)
|
|
$(TR $(TDNW $(LREF makeIndex)) $(TD Creates a separate index
|
|
for a range.)
|
|
)
|
|
$(TR $(TDNW $(LREF nextPermutation)) $(TD Computes the next lexicographically
|
|
greater permutation of a range in-place.)
|
|
)
|
|
$(TR $(TDNW $(LREF nextEvenPermutation)) $(TD Computes the next
|
|
lexicographically greater even permutation of a range in-place.)
|
|
)
|
|
$(TR $(TDNW $(LREF partialSort)) $(TD 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.)
|
|
)
|
|
$(TR $(TDNW $(LREF partition)) $(TD Partitions a range
|
|
according to a predicate.)
|
|
)
|
|
$(TR $(TDNW $(LREF partition3)) $(TD Partitions a range
|
|
in three parts (less than, equal, greater than the given pivot).)
|
|
)
|
|
$(TR $(TDNW $(LREF schwartzSort)) $(TD Sorts with the help of
|
|
the $(LUCKY Schwartzian transform).)
|
|
)
|
|
$(TR $(TDNW $(LREF sort)) $(TD Sorts.)
|
|
)
|
|
$(TR $(TDNW $(LREF topN)) $(TD Separates the top elements in a
|
|
range.)
|
|
)
|
|
$(TR $(TDNW $(LREF topNCopy)) $(TD Copies out the top elements
|
|
of a range.)
|
|
)
|
|
$(LEADINGROW Set operations
|
|
)
|
|
$(TR $(TDNW $(LREF cartesianProduct)) $(TD Computes Cartesian product of two
|
|
ranges.)
|
|
)
|
|
$(TR $(TDNW $(LREF largestPartialIntersection)) $(TD Copies out
|
|
the values that occur most frequently in a range of ranges.)
|
|
)
|
|
$(TR $(TDNW $(LREF largestPartialIntersectionWeighted)) $(TD
|
|
Copies out the values that occur most frequently (multiplied by
|
|
per-value weights) in a range of ranges.)
|
|
)
|
|
$(TR $(TDNW $(LREF nWayUnion)) $(TD Computes the union of a set
|
|
of sets implemented as a range of sorted ranges.)
|
|
)
|
|
$(TR $(TDNW $(LREF setDifference)) $(TD Lazily computes the set
|
|
difference of two or more sorted ranges.)
|
|
)
|
|
$(TR $(TDNW $(LREF setIntersection)) $(TD Lazily computes the
|
|
intersection of two or more sorted ranges.)
|
|
)
|
|
$(TR $(TDNW $(LREF setSymmetricDifference)) $(TD Lazily
|
|
computes the symmetric set difference of two or more sorted ranges.)
|
|
)
|
|
$(TR $(TDNW $(LREF setUnion)) $(TD Lazily computes the set
|
|
union of two or more sorted ranges.)
|
|
)
|
|
$(LEADINGROW Mutation
|
|
)
|
|
$(TR $(TDNW $(LREF bringToFront)) $(TD 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]).)
|
|
)
|
|
$(TR $(TDNW $(LREF copy)) $(TD 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 .. $]).)
|
|
)
|
|
$(TR $(TDNW $(LREF fill)) $(TD 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]).)
|
|
)
|
|
$(TR $(TDNW $(LREF initializeAll)) $(TD If $(D a = [1.2, 3.4]),
|
|
then $(D initializeAll(a)) leaves $(D a = [double.init,
|
|
double.init]).)
|
|
)
|
|
$(TR $(TDNW $(LREF move)) $(TD $(D move(a, b)) moves $(D a)
|
|
into $(D b). $(D move(a)) reads $(D a) destructively.)
|
|
)
|
|
$(TR $(TDNW $(LREF moveAll)) $(TD Moves all elements from one
|
|
range to another.)
|
|
)
|
|
$(TR $(TDNW $(LREF moveSome)) $(TD Moves as many elements as
|
|
possible from one range to another.)
|
|
)
|
|
$(TR $(TDNW $(LREF remove)) $(TD Removes elements from a range
|
|
in-place, and returns the shortened range.)
|
|
)
|
|
$(TR $(TDNW $(LREF reverse)) $(TD If $(D a = [1, 2, 3]), $(D
|
|
reverse(a)) changes it to $(D [3, 2, 1]).)
|
|
)
|
|
$(TR $(TDNW $(LREF strip)) $(TD 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]).)
|
|
)
|
|
$(TR $(TDNW $(LREF stripLeft)) $(TD 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]).)
|
|
)
|
|
$(TR $(TDNW $(LREF stripRight)) $(TD 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]).)
|
|
)
|
|
$(TR $(TDNW $(LREF swap)) $(TD Swaps two values.)
|
|
)
|
|
$(TR $(TDNW $(LREF swapRanges)) $(TD Swaps all elements of two
|
|
ranges.)
|
|
)
|
|
$(TR $(TDNW $(LREF uninitializedFill)) $(TD Fills a range
|
|
(assumed uninitialized) with a value.)
|
|
)
|
|
)
|
|
|
|
Macros:
|
|
WIKI = Phobos/StdAlgorithm
|
|
MYREF = <font face='Consolas, "Bitstream Vera Sans Mono", "Andale Mono", Monaco, "DejaVu Sans Mono", "Lucida Console", monospace'><a href="#.$1">$1</a> </font>
|
|
|
|
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;
|
|
|
|
import std.functional : unaryFun, binaryFun;
|
|
import std.range;
|
|
import std.traits;
|
|
import std.typecons : tuple, Tuple;
|
|
import std.typetuple : TypeTuple, staticMap, allSatisfy, anySatisfy;
|
|
|
|
version(unittest)
|
|
{
|
|
debug(std_algorithm) import std.stdio;
|
|
mixin(dummyRanges);
|
|
}
|
|
|
|
private 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.
|
|
private template algoFormat()
|
|
{
|
|
import std.string : format;
|
|
alias algoFormat = std.string.format;
|
|
}
|
|
|
|
/**
|
|
$(D auto map(Range)(Range r) if (isInputRange!(Unqual!Range));)
|
|
|
|
Implements the homonym function (also known as $(D transform)) present
|
|
in many languages of functional flavor. The call $(D map!(fun)(range))
|
|
returns a range of which elements are obtained by applying $(D fun(x))
|
|
left to right for all $(D x) in $(D range). The original ranges are
|
|
not changed. Evaluation is done lazily.
|
|
*/
|
|
template map(fun...) if (fun.length >= 1)
|
|
{
|
|
auto map(Range)(Range r) if (isInputRange!(Unqual!Range))
|
|
{
|
|
alias AppliedReturnType(alias f) = typeof(f(r.front));
|
|
|
|
static if (fun.length > 1)
|
|
{
|
|
import std.functional : adjoin;
|
|
import std.typetuple : staticIndexOf;
|
|
|
|
alias _funs = staticMap!(unaryFun, fun);
|
|
alias _fun = adjoin!_funs;
|
|
|
|
alias ReturnTypes = staticMap!(AppliedReturnType, _funs);
|
|
static assert(staticIndexOf!(void, ReturnTypes) == -1,
|
|
"All mapping functions must not return void.");
|
|
}
|
|
else
|
|
{
|
|
alias _fun = unaryFun!fun;
|
|
|
|
static assert(!is(AppliedReturnType!_fun == void),
|
|
"Mapping function must not return void.");
|
|
}
|
|
|
|
return MapResult!(_fun, Range)(r);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] arr1 = [ 1, 2, 3, 4 ];
|
|
int[] arr2 = [ 5, 6 ];
|
|
auto squares = map!(a => a * a)(chain(arr1, arr2));
|
|
assert(equal(squares, [ 1, 4, 9, 16, 25, 36 ]));
|
|
}
|
|
|
|
/**
|
|
Multiple functions can be passed to $(D map). In that case, the
|
|
element type of $(D map) is a tuple containing one element for each
|
|
function.
|
|
*/
|
|
@safe unittest
|
|
{
|
|
auto sums = [2, 4, 6, 8];
|
|
auto products = [1, 4, 9, 16];
|
|
|
|
size_t i = 0;
|
|
foreach (result; [ 1, 2, 3, 4 ].map!("a + a", "a * a"))
|
|
{
|
|
assert(result[0] == sums[i]);
|
|
assert(result[1] == products[i]);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
/**
|
|
You may alias $(D map) with some function(s) to a symbol and use
|
|
it separately:
|
|
*/
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
|
|
alias stringize = map!(to!string);
|
|
assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ]));
|
|
}
|
|
|
|
private struct MapResult(alias fun, Range)
|
|
{
|
|
alias R = Unqual!Range;
|
|
R _input;
|
|
|
|
static if (isBidirectionalRange!R)
|
|
{
|
|
@property auto ref back()()
|
|
{
|
|
return fun(_input.back);
|
|
}
|
|
|
|
void popBack()()
|
|
{
|
|
_input.popBack();
|
|
}
|
|
}
|
|
|
|
this(R input)
|
|
{
|
|
_input = input;
|
|
}
|
|
|
|
static if (isInfinite!R)
|
|
{
|
|
// Propagate infinite-ness.
|
|
enum bool empty = false;
|
|
}
|
|
else
|
|
{
|
|
@property bool empty()
|
|
{
|
|
return _input.empty;
|
|
}
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
_input.popFront();
|
|
}
|
|
|
|
@property auto ref front()
|
|
{
|
|
return fun(_input.front);
|
|
}
|
|
|
|
static if (isRandomAccessRange!R)
|
|
{
|
|
static if (is(typeof(_input[ulong.max])))
|
|
private alias opIndex_t = ulong;
|
|
else
|
|
private alias opIndex_t = uint;
|
|
|
|
auto ref opIndex(opIndex_t index)
|
|
{
|
|
return fun(_input[index]);
|
|
}
|
|
}
|
|
|
|
static if (hasLength!R)
|
|
{
|
|
@property auto length()
|
|
{
|
|
return _input.length;
|
|
}
|
|
|
|
alias opDollar = length;
|
|
}
|
|
|
|
static if (hasSlicing!R)
|
|
{
|
|
static if (is(typeof(_input[ulong.max .. ulong.max])))
|
|
private alias opSlice_t = ulong;
|
|
else
|
|
private alias opSlice_t = uint;
|
|
|
|
static if (hasLength!R)
|
|
{
|
|
auto opSlice(opSlice_t low, opSlice_t high)
|
|
{
|
|
return typeof(this)(_input[low .. high]);
|
|
}
|
|
}
|
|
else static if (is(typeof(_input[opSlice_t.max .. $])))
|
|
{
|
|
struct DollarToken{}
|
|
enum opDollar = DollarToken.init;
|
|
auto opSlice(opSlice_t low, DollarToken)
|
|
{
|
|
return typeof(this)(_input[low .. $]);
|
|
}
|
|
|
|
auto opSlice(opSlice_t low, opSlice_t high)
|
|
{
|
|
return this[low .. $].take(high - low);
|
|
}
|
|
}
|
|
}
|
|
|
|
static if (isForwardRange!R)
|
|
{
|
|
@property auto save()
|
|
{
|
|
return typeof(this)(_input.save);
|
|
}
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
import std.functional : adjoin;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
alias stringize = map!(to!string);
|
|
assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ]));
|
|
|
|
uint counter;
|
|
alias count = map!((a) { return counter++; });
|
|
assert(equal(count([ 10, 2, 30, 4 ]), [ 0, 1, 2, 3 ]));
|
|
|
|
counter = 0;
|
|
adjoin!((a) { return counter++; }, (a) { return counter++; })(1);
|
|
alias countAndSquare = map!((a) { return counter++; }, (a) { return counter++; });
|
|
//assert(equal(countAndSquare([ 10, 2 ]), [ tuple(0u, 100), tuple(1u, 4) ]));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.ascii : toUpper;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] arr1 = [ 1, 2, 3, 4 ];
|
|
const int[] arr1Const = arr1;
|
|
int[] arr2 = [ 5, 6 ];
|
|
auto squares = map!("a * a")(arr1Const);
|
|
assert(squares[$ - 1] == 16);
|
|
assert(equal(squares, [ 1, 4, 9, 16 ][]));
|
|
assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][]));
|
|
|
|
// Test the caching stuff.
|
|
assert(squares.back == 16);
|
|
auto squares2 = squares.save;
|
|
assert(squares2.back == 16);
|
|
|
|
assert(squares2.front == 1);
|
|
squares2.popFront();
|
|
assert(squares2.front == 4);
|
|
squares2.popBack();
|
|
assert(squares2.front == 4);
|
|
assert(squares2.back == 9);
|
|
|
|
assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][]));
|
|
|
|
uint i;
|
|
foreach (e; map!("a", "a * a")(arr1))
|
|
{
|
|
assert(e[0] == ++i);
|
|
assert(e[1] == i * i);
|
|
}
|
|
|
|
// Test length.
|
|
assert(squares.length == 4);
|
|
assert(map!"a * a"(chain(arr1, arr2)).length == 6);
|
|
|
|
// Test indexing.
|
|
assert(squares[0] == 1);
|
|
assert(squares[1] == 4);
|
|
assert(squares[2] == 9);
|
|
assert(squares[3] == 16);
|
|
|
|
// Test slicing.
|
|
auto squareSlice = squares[1..squares.length - 1];
|
|
assert(equal(squareSlice, [4, 9][]));
|
|
assert(squareSlice.back == 9);
|
|
assert(squareSlice[1] == 9);
|
|
|
|
// Test on a forward range to make sure it compiles when all the fancy
|
|
// stuff is disabled.
|
|
auto fibsSquares = map!"a * a"(recurrence!("a[n-1] + a[n-2]")(1, 1));
|
|
assert(fibsSquares.front == 1);
|
|
fibsSquares.popFront();
|
|
fibsSquares.popFront();
|
|
assert(fibsSquares.front == 4);
|
|
fibsSquares.popFront();
|
|
assert(fibsSquares.front == 9);
|
|
|
|
auto repeatMap = map!"a"(repeat(1));
|
|
static assert(isInfinite!(typeof(repeatMap)));
|
|
|
|
auto intRange = map!"a"([1,2,3]);
|
|
static assert(isRandomAccessRange!(typeof(intRange)));
|
|
|
|
foreach (DummyType; AllDummyRanges)
|
|
{
|
|
DummyType d;
|
|
auto m = map!"a * a"(d);
|
|
|
|
static assert(propagatesRangeType!(typeof(m), DummyType));
|
|
assert(equal(m, [1,4,9,16,25,36,49,64,81,100]));
|
|
}
|
|
|
|
//Test string access
|
|
string s1 = "hello world!";
|
|
dstring s2 = "日本語";
|
|
dstring s3 = "hello world!"d;
|
|
auto ms1 = map!(std.ascii.toUpper)(s1);
|
|
auto ms2 = map!(std.ascii.toUpper)(s2);
|
|
auto ms3 = map!(std.ascii.toUpper)(s3);
|
|
static assert(!is(ms1[0])); //narrow strings can't be indexed
|
|
assert(ms2[0] == '日');
|
|
assert(ms3[0] == 'H');
|
|
static assert(!is(ms1[0..1])); //narrow strings can't be sliced
|
|
assert(equal(ms2[0..2], "日本"w));
|
|
assert(equal(ms3[0..2], "HE"));
|
|
|
|
// Issue 5753
|
|
static void voidFun(int) {}
|
|
static int nonvoidFun(int) { return 0; }
|
|
static assert(!__traits(compiles, map!voidFun([1])));
|
|
static assert(!__traits(compiles, map!(voidFun, voidFun)([1])));
|
|
static assert(!__traits(compiles, map!(nonvoidFun, voidFun)([1])));
|
|
static assert(!__traits(compiles, map!(voidFun, nonvoidFun)([1])));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto LL = iota(1L, 4L);
|
|
auto m = map!"a*a"(LL);
|
|
assert(equal(m, [1L, 4L, 9L]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Issue #10130 - map of iota with const step.
|
|
const step = 2;
|
|
static assert(__traits(compiles, map!(i => i)(iota(0, 10, step))));
|
|
|
|
// Need these to all by const to repro the float case, due to the
|
|
// CommonType template used in the float specialization of iota.
|
|
const floatBegin = 0.0;
|
|
const floatEnd = 1.0;
|
|
const floatStep = 0.02;
|
|
static assert(__traits(compiles, map!(i => i)(iota(floatBegin, floatEnd, floatStep))));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//slicing infinites
|
|
auto rr = iota(0, 5).cycle().map!"a * a"();
|
|
alias RR = typeof(rr);
|
|
static assert(hasSlicing!RR);
|
|
rr = rr[6 .. $]; //Advances 1 cycle and 1 unit
|
|
assert(equal(rr[0 .. 5], [1, 4, 9, 16, 0]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
struct S {int* p;}
|
|
auto m = immutable(S).init.repeat().map!"a".save;
|
|
}
|
|
|
|
/++
|
|
$(D cache) eagerly evaluates $(D front) of $(D range)
|
|
on each construction or call to $(D popFront),
|
|
to store the result in a cache.
|
|
The result is then directly returned when $(D front) is called,
|
|
rather than re-evaluated.
|
|
|
|
This can be a useful function to place in a chain, after functions
|
|
that have expensive evaluation, as a lazy alternative to $(XREF array,array).
|
|
In particular, it can be placed after a call to $(D map), or before a call
|
|
to $(D filter).
|
|
|
|
$(D cache) may provide bidirectional iteration if needed, but since
|
|
this comes at an increased cost, it must be explicitly requested via the
|
|
call to $(D cacheBidirectional). Furthermore, a bidirectional cache will
|
|
evaluate the "center" element twice, when there is only one element left in
|
|
the range.
|
|
|
|
$(D cache) does not provide random access primitives,
|
|
as $(D cache) would be unable to cache the random accesses.
|
|
If $(D Range) provides slicing primitives,
|
|
then $(D cache) will provide the same slicing primitives,
|
|
but $(D hasSlicing!Cache) will not yield true (as the $(XREF range,hasSlicing)
|
|
trait also checks for random access).
|
|
+/
|
|
auto cache(Range)(Range range)
|
|
if (isInputRange!Range)
|
|
{
|
|
return Cache!(Range, false)(range);
|
|
}
|
|
|
|
/// ditto
|
|
auto cacheBidirectional(Range)(Range range)
|
|
if (isBidirectionalRange!Range)
|
|
{
|
|
return Cache!(Range, true)(range);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.stdio, std.range;
|
|
import std.typecons : tuple;
|
|
|
|
ulong counter = 0;
|
|
double fun(int x)
|
|
{
|
|
++counter;
|
|
// http://en.wikipedia.org/wiki/Quartic_function
|
|
return ( (x + 4.0) * (x + 1.0) * (x - 1.0) * (x - 3.0) ) / 14.0 + 0.5;
|
|
}
|
|
// Without cache, with array (greedy)
|
|
auto result1 = iota(-4, 5).map!(a =>tuple(a, fun(a)))()
|
|
.filter!"a[1]<0"()
|
|
.map!"a[0]"()
|
|
.array();
|
|
|
|
// the values of x that have a negative y are:
|
|
assert(equal(result1, [-3, -2, 2]));
|
|
|
|
// Check how many times fun was evaluated.
|
|
// As many times as the number of items in both source and result.
|
|
assert(counter == iota(-4, 5).length + result1.length);
|
|
|
|
counter = 0;
|
|
// Without array, with cache (lazy)
|
|
auto result2 = iota(-4, 5).map!(a =>tuple(a, fun(a)))()
|
|
.cache()
|
|
.filter!"a[1]<0"()
|
|
.map!"a[0]"();
|
|
|
|
// the values of x that have a negative y are:
|
|
assert(equal(result2, [-3, -2, 2]));
|
|
|
|
// Check how many times fun was evaluated.
|
|
// Only as many times as the number of items in source.
|
|
assert(counter == iota(-4, 5).length);
|
|
}
|
|
|
|
/++
|
|
Tip: $(D cache) is eager when evaluating elements. If calling front on the
|
|
underlying range has a side effect, it will be observeable before calling
|
|
front on the actual cached range.
|
|
|
|
Furtermore, care should be taken composing $(D cache) with $(XREF range,take).
|
|
By placing $(D take) before $(D cache), then $(D cache) will be "aware"
|
|
of when the range ends, and correctly stop caching elements when needed.
|
|
If calling front has no side effect though, placing $(D take) after $(D cache)
|
|
may yield a faster range.
|
|
|
|
Either way, the resulting ranges will be equivalent, but maybe not at the
|
|
same cost or side effects.
|
|
+/
|
|
@safe unittest
|
|
{
|
|
import std.range;
|
|
int i = 0;
|
|
|
|
auto r = iota(0, 4).tee!((a){i = a;}, No.pipeOnPop);
|
|
auto r1 = r.take(3).cache();
|
|
auto r2 = r.cache().take(3);
|
|
|
|
assert(equal(r1, [0, 1, 2]));
|
|
assert(i == 2); //The last "seen" element was 2. The data in cache has been cleared.
|
|
|
|
assert(equal(r2, [0, 1, 2]));
|
|
assert(i == 3); //cache has accessed 3. It is still stored internally by cache.
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto a = [1, 2, 3, 4];
|
|
assert(equal(a.map!"(a - 1)*a"().cache(), [ 0, 2, 6, 12]));
|
|
assert(equal(a.map!"(a - 1)*a"().cacheBidirectional().retro(), [12, 6, 2, 0]));
|
|
auto r1 = [1, 2, 3, 4].cache() [1 .. $];
|
|
auto r2 = [1, 2, 3, 4].cacheBidirectional()[1 .. $];
|
|
assert(equal(r1, [2, 3, 4]));
|
|
assert(equal(r2, [2, 3, 4]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//immutable test
|
|
static struct S
|
|
{
|
|
int i;
|
|
this(int i)
|
|
{
|
|
//this.i = i;
|
|
}
|
|
}
|
|
immutable(S)[] s = [S(1), S(2), S(3)];
|
|
assert(equal(s.cache(), s));
|
|
assert(equal(s.cacheBidirectional(), s));
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
//safety etc
|
|
auto a = [1, 2, 3, 4];
|
|
assert(equal(a.cache(), a));
|
|
assert(equal(a.cacheBidirectional(), a));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
char[][] stringbufs = ["hello".dup, "world".dup];
|
|
auto strings = stringbufs.map!((a)=>a.idup)().cache();
|
|
assert(strings.front is strings.front);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto c = [1, 2, 3].cycle().cache();
|
|
c = c[1 .. $];
|
|
auto d = c[0 .. 1];
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static struct Range
|
|
{
|
|
bool initialized = false;
|
|
bool front() @property {return initialized = true;}
|
|
void popFront() {initialized = false;}
|
|
enum empty = false;
|
|
}
|
|
auto r = Range().cache();
|
|
assert(r.source.initialized == true);
|
|
}
|
|
|
|
private struct Cache(R, bool bidir)
|
|
{
|
|
import core.exception : RangeError;
|
|
|
|
private
|
|
{
|
|
alias E = ElementType!R;
|
|
alias UE = Unqual!E;
|
|
|
|
R source;
|
|
|
|
static if (bidir) alias CacheTypes = TypeTuple!(UE, UE);
|
|
else alias CacheTypes = TypeTuple!UE;
|
|
CacheTypes caches;
|
|
|
|
static assert(isAssignable!(UE, E) && is(UE : E),
|
|
algoFormat("Cannot instantiate range with %s because %s elements are not assignable to %s.", R.stringof, E.stringof, UE.stringof));
|
|
}
|
|
|
|
this(R range)
|
|
{
|
|
source = range;
|
|
if (!range.empty)
|
|
{
|
|
caches[0] = source.front;
|
|
static if (bidir)
|
|
caches[1] = source.back;
|
|
}
|
|
}
|
|
|
|
static if (isInfinite!R)
|
|
enum empty = false;
|
|
else
|
|
bool empty() @property
|
|
{
|
|
return source.empty;
|
|
}
|
|
|
|
static if (hasLength!R) auto length() @property
|
|
{
|
|
return source.length;
|
|
}
|
|
|
|
E front() @property
|
|
{
|
|
version(assert) if (empty) throw new RangeError();
|
|
return caches[0];
|
|
}
|
|
static if (bidir) E back() @property
|
|
{
|
|
version(assert) if (empty) throw new RangeError();
|
|
return caches[1];
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
version(assert) if (empty) throw new RangeError();
|
|
source.popFront();
|
|
if (!source.empty)
|
|
caches[0] = source.front;
|
|
else
|
|
caches = CacheTypes.init;
|
|
}
|
|
static if (bidir) void popBack()
|
|
{
|
|
version(assert) if (empty) throw new RangeError();
|
|
source.popBack();
|
|
if (!source.empty)
|
|
caches[1] = source.back;
|
|
else
|
|
caches = CacheTypes.init;
|
|
}
|
|
|
|
static if (isForwardRange!R)
|
|
{
|
|
private this(R source, ref CacheTypes caches)
|
|
{
|
|
this.source = source;
|
|
this.caches = caches;
|
|
}
|
|
typeof(this) save() @property
|
|
{
|
|
return typeof(this)(source.save, caches);
|
|
}
|
|
}
|
|
|
|
static if (hasSlicing!R)
|
|
{
|
|
enum hasEndSlicing = is(typeof(source[size_t.max .. $]));
|
|
|
|
static if (hasEndSlicing)
|
|
{
|
|
private static struct DollarToken{}
|
|
enum opDollar = DollarToken.init;
|
|
|
|
auto opSlice(size_t low, DollarToken)
|
|
{
|
|
return typeof(this)(source[low .. $]);
|
|
}
|
|
}
|
|
|
|
static if (!isInfinite!R)
|
|
{
|
|
typeof(this) opSlice(size_t low, size_t high)
|
|
{
|
|
return typeof(this)(source[low .. high]);
|
|
}
|
|
}
|
|
else static if (hasEndSlicing)
|
|
{
|
|
auto opSlice(size_t low, size_t high)
|
|
in
|
|
{
|
|
assert(low <= high);
|
|
}
|
|
body
|
|
{
|
|
return this[low .. $].take(high - low);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/++
|
|
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. The one-argument version $(D reduce!(fun)(range))
|
|
works similarly, but it uses the first element of the range as the
|
|
seed (the range must be non-empty).
|
|
|
|
See also: $(LREF sum) is similar to $(D reduce!((a, b) => a + b)) that offers
|
|
precise summing of floating point numbers.
|
|
+/
|
|
template reduce(fun...) if (fun.length >= 1)
|
|
{
|
|
alias binfuns = staticMap!(binaryFun, fun);
|
|
static if (fun.length > 1)
|
|
import std.typecons : tuple, isTuple;
|
|
|
|
/++
|
|
No-seed version. The first element of $(D r) is used as the seed's value.
|
|
|
|
For each function $(D f) in $(D fun), the corresponding
|
|
seed type $(D S) is $(D Unqual!(typeof(f(e, e)))), where $(D e) is an
|
|
element of $(D r): $(D ElementType!R) for ranges,
|
|
and $(D ForeachType!R) otherwise.
|
|
|
|
Once S has been determined, then $(D S s = e;) and $(D s = f(s, e);)
|
|
must both be legal.
|
|
|
|
If $(D r) is empty, an $(D Exception) is thrown.
|
|
+/
|
|
auto reduce(R)(R r)
|
|
if (isIterable!R)
|
|
{
|
|
import std.exception : enforce;
|
|
alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R);
|
|
alias Args = staticMap!(ReduceSeedType!E, binfuns);
|
|
|
|
static if (isInputRange!R)
|
|
{
|
|
enforce(!r.empty);
|
|
Args result = r.front;
|
|
r.popFront();
|
|
return reduceImpl!false(r, result);
|
|
}
|
|
else
|
|
{
|
|
auto result = Args.init;
|
|
return reduceImpl!true(r, result);
|
|
}
|
|
}
|
|
|
|
/++
|
|
Seed version. The seed should be a single value if $(D fun) is a
|
|
single function. If $(D fun) is multiple functions, then $(D seed)
|
|
should be a $(XREF typecons,Tuple), with one field per function in $(D f).
|
|
|
|
For convenience, if the seed is const, or has qualified fields, then
|
|
$(D reduce) will operate on an unqualified copy. If this happens
|
|
then the returned type will not perfectly match $(D S).
|
|
+/
|
|
auto reduce(S, R)(S seed, R r)
|
|
if (isIterable!R)
|
|
{
|
|
static if (fun.length == 1)
|
|
return reducePreImpl(r, seed);
|
|
else
|
|
{
|
|
static assert(isTuple!S, algoFormat("Seed %s should be a Tuple", S.stringof));
|
|
return reducePreImpl(r, seed.expand);
|
|
}
|
|
}
|
|
|
|
private auto reducePreImpl(R, Args...)(R r, ref Args args)
|
|
{
|
|
alias Result = staticMap!(Unqual, Args);
|
|
static if (is(Result == Args))
|
|
alias result = args;
|
|
else
|
|
Result result = args;
|
|
return reduceImpl!false(r, result);
|
|
}
|
|
|
|
private auto reduceImpl(bool mustInitialize, R, Args...)(R r, ref Args args)
|
|
if (isIterable!R)
|
|
{
|
|
static assert(Args.length == fun.length,
|
|
algoFormat("Seed %s does not have the correct amount of fields (should be %s)", Args.stringof, fun.length));
|
|
alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R);
|
|
|
|
static if (mustInitialize) bool initialized = false;
|
|
foreach (/+auto ref+/ E e; r) // @@@4707@@@
|
|
{
|
|
foreach (i, f; binfuns)
|
|
static assert(is(typeof(args[i] = f(args[i], e))),
|
|
algoFormat("Incompatible function/seed/element: %s/%s/%s", fullyQualifiedName!f, Args[i].stringof, E.stringof));
|
|
|
|
static if (mustInitialize) if (initialized == false)
|
|
{
|
|
import std.conv : emplaceRef;
|
|
foreach (i, f; binfuns)
|
|
emplaceRef!(Args[i])(args[i], e);
|
|
initialized = true;
|
|
continue;
|
|
}
|
|
|
|
foreach (i, f; binfuns)
|
|
args[i] = f(args[i], e);
|
|
}
|
|
static if (mustInitialize) if (!initialized) throw new Exception("Cannot reduce an empty iterable w/o an explicit seed value.");
|
|
|
|
static if (Args.length == 1)
|
|
return args[0];
|
|
else
|
|
return tuple(args);
|
|
}
|
|
}
|
|
|
|
//Helper for Reduce
|
|
private template ReduceSeedType(E)
|
|
{
|
|
static template ReduceSeedType(alias fun)
|
|
{
|
|
E e = E.init;
|
|
static alias ReduceSeedType = Unqual!(typeof(fun(e, e)));
|
|
|
|
//Check the Seed type is useable.
|
|
ReduceSeedType s = ReduceSeedType.init;
|
|
static assert(is(typeof({ReduceSeedType s = e;})) && is(typeof(s = fun(s, e))),
|
|
algoFormat("Unable to deduce an acceptable seed type for %s with element type %s.", fullyQualifiedName!fun, E.stringof));
|
|
}
|
|
}
|
|
|
|
/**
|
|
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.
|
|
*/
|
|
@safe unittest
|
|
{
|
|
import std.math : approxEqual;
|
|
|
|
int[] arr = [ 1, 2, 3, 4, 5 ];
|
|
// Sum all elements
|
|
auto sum = reduce!((a,b) => a + b)(0, arr);
|
|
assert(sum == 15);
|
|
|
|
// Sum again, using a string predicate with "a" and "b"
|
|
sum = reduce!"a + b"(0, arr);
|
|
assert(sum == 15);
|
|
|
|
// Compute the maximum of all elements
|
|
auto largest = reduce!(max)(arr);
|
|
assert(largest == 5);
|
|
|
|
// Max again, but with Uniform Function Call Syntax (UFCS)
|
|
largest = arr.reduce!(max);
|
|
assert(largest == 5);
|
|
|
|
// Compute the number of odd elements
|
|
auto odds = reduce!((a,b) => a + (b & 1))(0, arr);
|
|
assert(odds == 3);
|
|
|
|
// Compute the sum of squares
|
|
auto ssquares = reduce!((a,b) => a + b * b)(0, arr);
|
|
assert(ssquares == 55);
|
|
|
|
// Chain multiple ranges into seed
|
|
int[] a = [ 3, 4 ];
|
|
int[] b = [ 100 ];
|
|
auto r = reduce!("a + b")(chain(a, b));
|
|
assert(r == 107);
|
|
|
|
// Mixing convertible types is fair game, too
|
|
double[] c = [ 2.5, 3.0 ];
|
|
auto r1 = reduce!("a + b")(chain(a, b, c));
|
|
assert(approxEqual(r1, 112.5));
|
|
|
|
// To minimize nesting of parentheses, Uniform Function Call Syntax can be used
|
|
auto r2 = chain(a, b, c).reduce!("a + b");
|
|
assert(approxEqual(r2, 112.5));
|
|
}
|
|
|
|
/**
|
|
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.
|
|
*/
|
|
@safe unittest
|
|
{
|
|
import std.math : approxEqual, sqrt;
|
|
|
|
double[] a = [ 3.0, 4, 7, 11, 3, 2, 5 ];
|
|
// Compute minimum and maximum in one pass
|
|
auto r = reduce!(min, max)(a);
|
|
// The type of r is Tuple!(int, int)
|
|
assert(approxEqual(r[0], 2)); // minimum
|
|
assert(approxEqual(r[1], 11)); // maximum
|
|
|
|
// Compute sum and sum of squares in one pass
|
|
r = reduce!("a + b", "a + b * b")(tuple(0.0, 0.0), a);
|
|
assert(approxEqual(r[0], 35)); // sum
|
|
assert(approxEqual(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);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
double[] a = [ 3, 4 ];
|
|
auto r = reduce!("a + b")(0.0, a);
|
|
assert(r == 7);
|
|
r = reduce!("a + b")(a);
|
|
assert(r == 7);
|
|
r = reduce!(min)(a);
|
|
assert(r == 3);
|
|
double[] b = [ 100 ];
|
|
auto r1 = reduce!("a + b")(chain(a, b));
|
|
assert(r1 == 107);
|
|
|
|
// two funs
|
|
auto r2 = reduce!("a + b", "a - b")(tuple(0.0, 0.0), a);
|
|
assert(r2[0] == 7 && r2[1] == -7);
|
|
auto r3 = reduce!("a + b", "a - b")(a);
|
|
assert(r3[0] == 7 && r3[1] == -1);
|
|
|
|
a = [ 1, 2, 3, 4, 5 ];
|
|
// Stringize with commas
|
|
string rep = reduce!("a ~ `, ` ~ to!(string)(b)")("", a);
|
|
assert(rep[2 .. $] == "1, 2, 3, 4, 5", "["~rep[2 .. $]~"]");
|
|
|
|
// Test the opApply case.
|
|
static struct OpApply
|
|
{
|
|
bool actEmpty;
|
|
|
|
int opApply(int delegate(ref int) dg)
|
|
{
|
|
int res;
|
|
if (actEmpty) return res;
|
|
|
|
foreach (i; 0..100)
|
|
{
|
|
res = dg(i);
|
|
if (res) break;
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
OpApply oa;
|
|
auto hundredSum = reduce!"a + b"(iota(100));
|
|
assert(reduce!"a + b"(5, oa) == hundredSum + 5);
|
|
assert(reduce!"a + b"(oa) == hundredSum);
|
|
assert(reduce!("a + b", max)(oa) == tuple(hundredSum, 99));
|
|
assert(reduce!("a + b", max)(tuple(5, 0), oa) == tuple(hundredSum + 5, 99));
|
|
|
|
// Test for throwing on empty range plus no seed.
|
|
assertThrown(reduce!"a + b"([1, 2][0..0]));
|
|
|
|
oa.actEmpty = true;
|
|
assertThrown(reduce!"a + b"(oa));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
const float a = 0.0;
|
|
const float[] b = [ 1.2, 3, 3.3 ];
|
|
float[] c = [ 1.2, 3, 3.3 ];
|
|
auto r = reduce!"a + b"(a, b);
|
|
r = reduce!"a + b"(a, c);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Issue #10408 - Two-function reduce of a const array.
|
|
const numbers = [10, 30, 20];
|
|
immutable m = reduce!(min)(numbers);
|
|
assert(m == 10);
|
|
immutable minmax = reduce!(min, max)(numbers);
|
|
assert(minmax == tuple(10, 30));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//10709
|
|
enum foo = "a + 0.5 * b";
|
|
auto r = [0, 1, 2, 3];
|
|
auto r1 = reduce!foo(r);
|
|
auto r2 = reduce!(foo, foo)(r);
|
|
assert(r1 == 3);
|
|
assert(r2 == tuple(3, 3));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int i = 0;
|
|
static struct OpApply
|
|
{
|
|
int opApply(int delegate(ref int) dg)
|
|
{
|
|
int[] a = [1, 2, 3];
|
|
|
|
int res = 0;
|
|
foreach (ref e; a)
|
|
{
|
|
res = dg(e);
|
|
if (res) break;
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
//test CTFE and functions with context
|
|
int fun(int a, int b){return a + b + 1;}
|
|
auto foo()
|
|
{
|
|
auto a = reduce!(fun)([1, 2, 3]);
|
|
auto b = reduce!(fun, fun)([1, 2, 3]);
|
|
auto c = reduce!(fun)(0, [1, 2, 3]);
|
|
auto d = reduce!(fun, fun)(tuple(0, 0), [1, 2, 3]);
|
|
auto e = reduce!(fun)(0, OpApply());
|
|
auto f = reduce!(fun, fun)(tuple(0, 0), OpApply());
|
|
|
|
return max(a, b.expand, c, d.expand);
|
|
}
|
|
auto a = foo();
|
|
enum b = foo();
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//http://forum.dlang.org/thread/oghtttkopzjshsuflelk@forum.dlang.org
|
|
//Seed is tuple of const.
|
|
static auto minmaxElement(alias F = min, alias G = max, R)(in R range)
|
|
@safe pure nothrow if (isInputRange!R)
|
|
{
|
|
return reduce!(F, G)(tuple(ElementType!R.max,
|
|
ElementType!R.min), range);
|
|
}
|
|
assert(minmaxElement([1, 2, 3])== tuple(1, 3));
|
|
}
|
|
|
|
@safe unittest //12569
|
|
{
|
|
import std.typecons: tuple;
|
|
dchar c = 'a';
|
|
reduce!(min, max)(tuple(c, c), "hello"); // OK
|
|
static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello"))));
|
|
static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello"))));
|
|
|
|
|
|
//"Seed dchar should be a Tuple"
|
|
static assert(!is(typeof(reduce!(min, max)(c, "hello"))));
|
|
//"Seed (dchar) does not have the correct amount of fields (should be 2)"
|
|
static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello"))));
|
|
//"Seed (dchar, dchar, dchar) does not have the correct amount of fields (should be 2)"
|
|
static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello"))));
|
|
//"Incompatable function/seed/element: all(alias pred = "a")/int/dchar"
|
|
static assert(!is(typeof(reduce!all(1, "hello"))));
|
|
static assert(!is(typeof(reduce!(all, all)(tuple(1, 1), "hello"))));
|
|
}
|
|
|
|
@safe unittest //13304
|
|
{
|
|
int[] data;
|
|
static assert(is(typeof(reduce!((a, b)=>a+b)(data))));
|
|
}
|
|
|
|
// sum
|
|
/**
|
|
Sums elements of $(D r), which must be a finite input range. Although
|
|
conceptually $(D sum(r)) is equivalent to $(D reduce!((a, b) => a +
|
|
b)(0, r)), $(D sum) uses specialized algorithms to maximize accuracy,
|
|
as follows.
|
|
|
|
$(UL
|
|
$(LI If $(D ElementType!R) is a floating-point type and $(D R) is a
|
|
random-access range with length and slicing, then $(D sum) uses the
|
|
$(WEB en.wikipedia.org/wiki/Pairwise_summation, pairwise summation)
|
|
algorithm.)
|
|
$(LI If $(D ElementType!R) is a floating-point type and $(D R) is a
|
|
finite input range (but not a random-access range with slicing), then
|
|
$(D sum) uses the $(WEB en.wikipedia.org/wiki/Kahan_summation,
|
|
Kahan summation) algorithm.)
|
|
$(LI In all other cases, a simple element by element addition is done.)
|
|
)
|
|
|
|
For floating point inputs, calculations are made in $(D real)
|
|
precision for $(D real) inputs and in $(D double) precision otherwise
|
|
(Note this is a special case that deviates from $(D reduce)'s behavior,
|
|
which would have kept $(D float) precision for a $(D float) range).
|
|
For all other types, the calculations are done in the same type obtained
|
|
from from adding two elements of the range, which may be a different
|
|
type from the elements themselves (for example, in case of integral promotion).
|
|
|
|
A seed may be passed to $(D sum). Not only will this seed be used as an initial
|
|
value, but its type will override all the above, and determine the algorithm
|
|
and precision used for sumation.
|
|
|
|
Note that these specialized summing algorithms execute more primitive operations
|
|
than vanilla summation. Therefore, if in certain cases maximum speed is required
|
|
at expense of precision, one can use $(D reduce!((a, b) => a + b)(0, r)), which
|
|
is not specialized for summation.
|
|
*/
|
|
auto sum(R)(R r)
|
|
if (isInputRange!R && !isInfinite!R && is(typeof(r.front + r.front)))
|
|
{
|
|
alias E = Unqual!(ElementType!R);
|
|
static if (isFloatingPoint!E)
|
|
alias Seed = typeof(E.init + 0.0); //biggest of double/real
|
|
else
|
|
alias Seed = typeof(r.front + r.front);
|
|
return sum(r, Unqual!Seed(0));
|
|
}
|
|
/// ditto
|
|
auto sum(R, E)(R r, E seed)
|
|
if (isInputRange!R && !isInfinite!R && is(typeof(seed = seed + r.front)))
|
|
{
|
|
static if (isFloatingPoint!E)
|
|
{
|
|
static if (hasLength!R && hasSlicing!R)
|
|
return seed + sumPairwise!E(r);
|
|
else
|
|
return sumKahan!E(seed, r);
|
|
}
|
|
else
|
|
{
|
|
return reduce!"a + b"(seed, r);
|
|
}
|
|
}
|
|
|
|
// Pairwise summation http://en.wikipedia.org/wiki/Pairwise_summation
|
|
private auto sumPairwise(Result, R)(R r)
|
|
{
|
|
static assert (isFloatingPoint!Result);
|
|
switch (r.length)
|
|
{
|
|
case 0: return cast(Result) 0;
|
|
case 1: return cast(Result) r.front;
|
|
case 2: return cast(Result) r[0] + cast(Result) r[1];
|
|
default: return sumPairwise!Result(r[0 .. $ / 2]) + sumPairwise!Result(r[$ / 2 .. $]);
|
|
}
|
|
}
|
|
|
|
// Kahan algo http://en.wikipedia.org/wiki/Kahan_summation_algorithm
|
|
private auto sumKahan(Result, R)(Result result, R r)
|
|
{
|
|
static assert (isFloatingPoint!Result && isMutable!Result);
|
|
Result c = 0;
|
|
for (; !r.empty; r.popFront())
|
|
{
|
|
auto y = r.front - c;
|
|
auto t = result + y;
|
|
c = (t - result) - y;
|
|
result = t;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Ditto
|
|
@safe pure nothrow unittest
|
|
{
|
|
//simple integral sumation
|
|
assert(sum([ 1, 2, 3, 4]) == 10);
|
|
|
|
//with integral promotion
|
|
assert(sum([false, true, true, false, true]) == 3);
|
|
assert(sum(ubyte.max.repeat(100)) == 25500);
|
|
|
|
//The result may overflow
|
|
assert(uint.max.repeat(3).sum() == 4294967293U );
|
|
//But a seed can be used to change the sumation primitive
|
|
assert(uint.max.repeat(3).sum(ulong.init) == 12884901885UL);
|
|
|
|
//Floating point sumation
|
|
assert(sum([1.0, 2.0, 3.0, 4.0]) == 10);
|
|
|
|
//Floating point operations have double precision minimum
|
|
static assert(is(typeof(sum([1F, 2F, 3F, 4F])) == double));
|
|
assert(sum([1F, 2, 3, 4]) == 10);
|
|
|
|
//Force pair-wise floating point sumation on large integers
|
|
import std.math : approxEqual;
|
|
assert(iota(ulong.max / 2, ulong.max / 2 + 4096).sum(0.0)
|
|
.approxEqual((ulong.max / 2) * 4096.0 + 4096^^2 / 2));
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
static assert(is(typeof(sum([cast( byte)1])) == int));
|
|
static assert(is(typeof(sum([cast(ubyte)1])) == int));
|
|
static assert(is(typeof(sum([ 1, 2, 3, 4])) == int));
|
|
static assert(is(typeof(sum([ 1U, 2U, 3U, 4U])) == uint));
|
|
static assert(is(typeof(sum([ 1L, 2L, 3L, 4L])) == long));
|
|
static assert(is(typeof(sum([1UL, 2UL, 3UL, 4UL])) == ulong));
|
|
|
|
int[] empty;
|
|
assert(sum(empty) == 0);
|
|
assert(sum([42]) == 42);
|
|
assert(sum([42, 43]) == 42 + 43);
|
|
assert(sum([42, 43, 44]) == 42 + 43 + 44);
|
|
assert(sum([42, 43, 44, 45]) == 42 + 43 + 44 + 45);
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
static assert(is(typeof(sum([1.0, 2.0, 3.0, 4.0])) == double));
|
|
static assert(is(typeof(sum([ 1F, 2F, 3F, 4F])) == double));
|
|
const(float[]) a = [1F, 2F, 3F, 4F];
|
|
static assert(is(typeof(sum(a)) == double));
|
|
const(float)[] b = [1F, 2F, 3F, 4F];
|
|
static assert(is(typeof(sum(a)) == double));
|
|
|
|
double[] empty;
|
|
assert(sum(empty) == 0);
|
|
assert(sum([42.]) == 42);
|
|
assert(sum([42., 43.]) == 42 + 43);
|
|
assert(sum([42., 43., 44.]) == 42 + 43 + 44);
|
|
assert(sum([42., 43., 44., 45.5]) == 42 + 43 + 44 + 45.5);
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
import std.container;
|
|
static assert(is(typeof(sum(SList!float()[])) == double));
|
|
static assert(is(typeof(sum(SList!double()[])) == double));
|
|
static assert(is(typeof(sum(SList!real()[])) == real));
|
|
|
|
assert(sum(SList!double()[]) == 0);
|
|
assert(sum(SList!double(1)[]) == 1);
|
|
assert(sum(SList!double(1, 2)[]) == 1 + 2);
|
|
assert(sum(SList!double(1, 2, 3)[]) == 1 + 2 + 3);
|
|
assert(sum(SList!double(1, 2, 3, 4)[]) == 10);
|
|
}
|
|
|
|
@safe pure nothrow unittest // 12434
|
|
{
|
|
immutable a = [10, 20];
|
|
auto s1 = sum(a); // Error
|
|
auto s2 = a.map!(x => x).sum; // Error
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.bigint;
|
|
immutable BigInt[] a = BigInt("1_000_000_000_000_000_000").repeat(10).array();
|
|
immutable ulong[] b = (ulong.max/2).repeat(10).array();
|
|
auto sa = a.sum();
|
|
auto sb = b.sum(BigInt(0)); //reduce ulongs into bigint
|
|
assert(sa == BigInt("10_000_000_000_000_000_000"));
|
|
assert(sb == (BigInt(ulong.max/2) * 10));
|
|
}
|
|
|
|
/**
|
|
Fills $(D range) with a $(D filler).
|
|
*/
|
|
void fill(Range, Value)(Range range, Value filler)
|
|
if (isInputRange!Range && is(typeof(range.front = filler)))
|
|
{
|
|
alias T = ElementType!Range;
|
|
|
|
static if (is(typeof(range[] = filler)))
|
|
{
|
|
range[] = filler;
|
|
}
|
|
else static if (is(typeof(range[] = T(filler))))
|
|
{
|
|
range[] = T(filler);
|
|
}
|
|
else
|
|
{
|
|
for ( ; !range.empty; range.popFront() )
|
|
{
|
|
range.front = filler;
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 2, 3, 4 ];
|
|
fill(a, 5);
|
|
assert(a == [ 5, 5, 5, 5 ]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
int[] a = [ 1, 2, 3 ];
|
|
fill(a, 6);
|
|
assert(a == [ 6, 6, 6 ], text(a));
|
|
|
|
void fun0()
|
|
{
|
|
foreach (i; 0 .. 1000)
|
|
{
|
|
foreach (ref e; a) e = 6;
|
|
}
|
|
}
|
|
void fun1() { foreach (i; 0 .. 1000) fill(a, 6); }
|
|
//void fun2() { foreach (i; 0 .. 1000) fill2(a, 6); }
|
|
//writeln(benchmark!(fun0, fun1, fun2)(10000));
|
|
|
|
// fill should accept InputRange
|
|
alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input);
|
|
enum filler = uint.max;
|
|
InputRange range;
|
|
fill(range, filler);
|
|
foreach (value; range.arr)
|
|
assert(value == filler);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//ER8638_1 IS_NOT self assignable
|
|
static struct ER8638_1
|
|
{
|
|
void opAssign(int){}
|
|
}
|
|
|
|
//ER8638_1 IS self assignable
|
|
static struct ER8638_2
|
|
{
|
|
void opAssign(ER8638_2){}
|
|
void opAssign(int){}
|
|
}
|
|
|
|
auto er8638_1 = new ER8638_1[](10);
|
|
auto er8638_2 = new ER8638_2[](10);
|
|
er8638_1.fill(5); //generic case
|
|
er8638_2.fill(5); //opSlice(T.init) case
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
{
|
|
int[] a = [1, 2, 3];
|
|
immutable(int) b = 0;
|
|
static assert(__traits(compiles, a.fill(b)));
|
|
}
|
|
{
|
|
double[] a = [1, 2, 3];
|
|
immutable(int) b = 0;
|
|
static assert(__traits(compiles, a.fill(b)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
Fills $(D range) with a pattern copied from $(D filler). The length of
|
|
$(D range) does not have to be a multiple of the length of $(D
|
|
filler). If $(D filler) is empty, an exception is thrown.
|
|
*/
|
|
void fill(Range1, Range2)(Range1 range, Range2 filler)
|
|
if (isInputRange!Range1
|
|
&& (isForwardRange!Range2
|
|
|| (isInputRange!Range2 && isInfinite!Range2))
|
|
&& is(typeof(Range1.init.front = Range2.init.front)))
|
|
{
|
|
static if (isInfinite!Range2)
|
|
{
|
|
//Range2 is infinite, no need for bounds checking or saving
|
|
static if (hasSlicing!Range2 && hasLength!Range1
|
|
&& is(typeof(filler[0 .. range.length])))
|
|
{
|
|
copy(filler[0 .. range.length], range);
|
|
}
|
|
else
|
|
{
|
|
//manual feed
|
|
for ( ; !range.empty; range.popFront(), filler.popFront())
|
|
{
|
|
range.front = filler.front;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(!filler.empty, "Cannot fill range with an empty filler");
|
|
|
|
static if (hasLength!Range1 && hasLength!Range2
|
|
&& is(typeof(range.length > filler.length)))
|
|
{
|
|
//Case we have access to length
|
|
auto len = filler.length;
|
|
//Start by bulk copies
|
|
while (range.length > len)
|
|
{
|
|
range = copy(filler.save, range);
|
|
}
|
|
|
|
//and finally fill the partial range. No need to save here.
|
|
static if (hasSlicing!Range2 && is(typeof(filler[0 .. range.length])))
|
|
{
|
|
//use a quick copy
|
|
auto len2 = range.length;
|
|
range = copy(filler[0 .. len2], range);
|
|
}
|
|
else
|
|
{
|
|
//iterate. No need to check filler, it's length is longer than range's
|
|
for (; !range.empty; range.popFront(), filler.popFront())
|
|
{
|
|
range.front = filler.front;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Most basic case.
|
|
auto bck = filler.save;
|
|
for (; !range.empty; range.popFront(), filler.popFront())
|
|
{
|
|
if (filler.empty) filler = bck.save;
|
|
range.front = filler.front;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 2, 3, 4, 5 ];
|
|
int[] b = [ 8, 9 ];
|
|
fill(a, b);
|
|
assert(a == [ 8, 9, 8, 9, 8 ]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
int[] a = [ 1, 2, 3, 4, 5 ];
|
|
int[] b = [1, 2];
|
|
fill(a, b);
|
|
assert(a == [ 1, 2, 1, 2, 1 ]);
|
|
|
|
// fill should accept InputRange
|
|
alias InputRange = DummyRange!(ReturnBy.Reference, Length.No, RangeType.Input);
|
|
InputRange range;
|
|
fill(range,[1,2]);
|
|
foreach (i,value;range.arr)
|
|
assert(value == (i%2==0?1:2));
|
|
|
|
//test with a input being a "reference forward" range
|
|
fill(a, new ReferenceForwardRange!int([8, 9]));
|
|
assert(a == [8, 9, 8, 9, 8]);
|
|
|
|
//test with a input being an "infinite input" range
|
|
fill(a, new ReferenceInfiniteInputRange!int());
|
|
assert(a == [0, 1, 2, 3, 4]);
|
|
|
|
//empty filler test
|
|
assertThrown(fill(a, a[$..$]));
|
|
}
|
|
|
|
/**
|
|
Fills a range with a value. Assumes that the range does not currently
|
|
contain meaningful content. This is of interest for structs that
|
|
define copy constructors (for all other types, fill and
|
|
uninitializedFill are equivalent).
|
|
|
|
uninitializedFill will only operate on ranges that expose references to its
|
|
members and have assignable elements.
|
|
|
|
Example:
|
|
----
|
|
struct S { ... }
|
|
S[] s = (cast(S*) malloc(5 * S.sizeof))[0 .. 5];
|
|
uninitializedFill(s, 42);
|
|
assert(s == [ 42, 42, 42, 42, 42 ]);
|
|
----
|
|
*/
|
|
void uninitializedFill(Range, Value)(Range range, Value filler)
|
|
if (isInputRange!Range && hasLvalueElements!Range && is(typeof(range.front = filler)))
|
|
{
|
|
alias T = ElementType!Range;
|
|
static if (hasElaborateAssign!T)
|
|
{
|
|
import std.conv : emplaceRef;
|
|
|
|
// Must construct stuff by the book
|
|
for (; !range.empty; range.popFront())
|
|
emplaceRef!T(range.front, filler);
|
|
}
|
|
else
|
|
// Doesn't matter whether fill is initialized or not
|
|
return fill(range, filler);
|
|
}
|
|
|
|
/**
|
|
Initializes all elements of a range with their $(D .init)
|
|
value. Assumes that the range does not currently contain meaningful
|
|
content.
|
|
|
|
initializeAll will operate on ranges that expose references to its
|
|
members and have assignable elements, as well as on (mutable) strings.
|
|
|
|
Example:
|
|
----
|
|
struct S { ... }
|
|
S[] s = (cast(S*) malloc(5 * S.sizeof))[0 .. 5];
|
|
initializeAll(s);
|
|
assert(s == [ 0, 0, 0, 0, 0 ]);
|
|
----
|
|
*/
|
|
void initializeAll(Range)(Range range)
|
|
if (isInputRange!Range && hasLvalueElements!Range && hasAssignableElements!Range)
|
|
{
|
|
import core.stdc.string : memset, memcpy;
|
|
|
|
alias T = ElementType!Range;
|
|
static if (hasElaborateAssign!T)
|
|
{
|
|
//Elaborate opAssign. Must go the memcpy road.
|
|
//We avoid calling emplace here, because our goal is to initialize to
|
|
//the static state of T.init,
|
|
//So we want to avoid any un-necassarilly CC'ing of T.init
|
|
auto p = typeid(T).init().ptr;
|
|
if (p)
|
|
for ( ; !range.empty ; range.popFront() )
|
|
memcpy(addressOf(range.front), p, T.sizeof);
|
|
else
|
|
static if (isDynamicArray!Range)
|
|
memset(range.ptr, 0, range.length * T.sizeof);
|
|
else
|
|
for ( ; !range.empty ; range.popFront() )
|
|
memset(addressOf(range.front), 0, T.sizeof);
|
|
}
|
|
else
|
|
fill(range, T.init);
|
|
}
|
|
|
|
// ditto
|
|
void initializeAll(Range)(Range range)
|
|
if (is(Range == char[]) || is(Range == wchar[]))
|
|
{
|
|
alias T = ElementEncodingType!Range;
|
|
range[] = T.init;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
//Test strings:
|
|
//Must work on narrow strings.
|
|
//Must reject const
|
|
char[3] a = void;
|
|
a[].initializeAll();
|
|
assert(a[] == [char.init, char.init, char.init]);
|
|
string s;
|
|
assert(!__traits(compiles, s.initializeAll()));
|
|
|
|
//Note: Cannot call uninitializedFill on narrow strings
|
|
|
|
enum e {e1, e2}
|
|
e[3] b1 = void;
|
|
b1[].initializeAll();
|
|
assert(b1[] == [e.e1, e.e1, e.e1]);
|
|
e[3] b2 = void;
|
|
b2[].uninitializedFill(e.e2);
|
|
assert(b2[] == [e.e2, e.e2, e.e2]);
|
|
|
|
static struct S1
|
|
{
|
|
int i;
|
|
}
|
|
static struct S2
|
|
{
|
|
int i = 1;
|
|
}
|
|
static struct S3
|
|
{
|
|
int i;
|
|
this(this){}
|
|
}
|
|
static struct S4
|
|
{
|
|
int i = 1;
|
|
this(this){}
|
|
}
|
|
static assert (!hasElaborateAssign!S1);
|
|
static assert (!hasElaborateAssign!S2);
|
|
static assert ( hasElaborateAssign!S3);
|
|
static assert ( hasElaborateAssign!S4);
|
|
assert (!typeid(S1).init().ptr);
|
|
assert ( typeid(S2).init().ptr);
|
|
assert (!typeid(S3).init().ptr);
|
|
assert ( typeid(S4).init().ptr);
|
|
|
|
foreach(S; TypeTuple!(S1, S2, S3, S4))
|
|
{
|
|
//initializeAll
|
|
{
|
|
//Array
|
|
S[3] ss1 = void;
|
|
ss1[].initializeAll();
|
|
assert(ss1[] == [S.init, S.init, S.init]);
|
|
|
|
//Not array
|
|
S[3] ss2 = void;
|
|
auto sf = ss2[].filter!"true"();
|
|
|
|
sf.initializeAll();
|
|
assert(ss2[] == [S.init, S.init, S.init]);
|
|
}
|
|
//uninitializedFill
|
|
{
|
|
//Array
|
|
S[3] ss1 = void;
|
|
ss1[].uninitializedFill(S(2));
|
|
assert(ss1[] == [S(2), S(2), S(2)]);
|
|
|
|
//Not array
|
|
S[3] ss2 = void;
|
|
auto sf = ss2[].filter!"true"();
|
|
sf.uninitializedFill(S(2));
|
|
assert(ss2[] == [S(2), S(2), S(2)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
$(D auto filter(Range)(Range rs) if (isInputRange!(Unqual!Range));)
|
|
|
|
Implements the homonym function present in various programming
|
|
languages of functional flavor. The call $(D filter!(predicate)(range))
|
|
returns a new range only containing elements $(D x) in $(D range) for
|
|
which $(D predicate(x)) is $(D true).
|
|
*/
|
|
template filter(alias pred) if (is(typeof(unaryFun!pred)))
|
|
{
|
|
auto filter(Range)(Range rs) if (isInputRange!(Unqual!Range))
|
|
{
|
|
return FilterResult!(unaryFun!pred, Range)(rs);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.math : approxEqual;
|
|
|
|
int[] arr = [ 1, 2, 3, 4, 5 ];
|
|
|
|
// Sum all elements
|
|
auto small = filter!(a => a < 3)(arr);
|
|
assert(equal(small, [ 1, 2 ]));
|
|
|
|
// Sum again, but with Uniform Function Call Syntax (UFCS)
|
|
auto sum = arr.filter!(a => a < 3);
|
|
assert(equal(sum, [ 1, 2 ]));
|
|
|
|
// In combination with chain() to span multiple ranges
|
|
int[] a = [ 3, -2, 400 ];
|
|
int[] b = [ 100, -101, 102 ];
|
|
auto r = chain(a, b).filter!(a => a > 0);
|
|
assert(equal(r, [ 3, 400, 100, 102 ]));
|
|
|
|
// Mixing convertible types is fair game, too
|
|
double[] c = [ 2.5, 3.0 ];
|
|
auto r1 = chain(c, a, b).filter!(a => cast(int) a != a);
|
|
assert(approxEqual(r1, [ 2.5 ]));
|
|
}
|
|
|
|
private struct FilterResult(alias pred, Range)
|
|
{
|
|
alias R = Unqual!Range;
|
|
R _input;
|
|
|
|
this(R r)
|
|
{
|
|
_input = r;
|
|
while (!_input.empty && !pred(_input.front))
|
|
{
|
|
_input.popFront();
|
|
}
|
|
}
|
|
|
|
auto opSlice() { return this; }
|
|
|
|
static if (isInfinite!Range)
|
|
{
|
|
enum bool empty = false;
|
|
}
|
|
else
|
|
{
|
|
@property bool empty() { return _input.empty; }
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
do
|
|
{
|
|
_input.popFront();
|
|
} while (!_input.empty && !pred(_input.front));
|
|
}
|
|
|
|
@property auto ref front()
|
|
{
|
|
return _input.front;
|
|
}
|
|
|
|
static if (isForwardRange!R)
|
|
{
|
|
@property auto save()
|
|
{
|
|
return typeof(this)(_input.save);
|
|
}
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ 3, 4, 2 ];
|
|
auto r = filter!("a > 3")(a);
|
|
static assert(isForwardRange!(typeof(r)));
|
|
assert(equal(r, [ 4 ][]));
|
|
|
|
a = [ 1, 22, 3, 42, 5 ];
|
|
auto under10 = filter!("a < 10")(a);
|
|
assert(equal(under10, [1, 3, 5][]));
|
|
static assert(isForwardRange!(typeof(under10)));
|
|
under10.front = 4;
|
|
assert(equal(under10, [4, 3, 5][]));
|
|
under10.front = 40;
|
|
assert(equal(under10, [40, 3, 5][]));
|
|
under10.front = 1;
|
|
|
|
auto infinite = filter!"a > 2"(repeat(3));
|
|
static assert(isInfinite!(typeof(infinite)));
|
|
static assert(isForwardRange!(typeof(infinite)));
|
|
|
|
foreach (DummyType; AllDummyRanges) {
|
|
DummyType d;
|
|
auto f = filter!"a & 1"(d);
|
|
assert(equal(f, [1,3,5,7,9]));
|
|
|
|
static if (isForwardRange!DummyType) {
|
|
static assert(isForwardRange!(typeof(f)));
|
|
}
|
|
}
|
|
|
|
// With delegates
|
|
int x = 10;
|
|
int overX(int a) { return a > x; }
|
|
typeof(filter!overX(a)) getFilter()
|
|
{
|
|
return filter!overX(a);
|
|
}
|
|
auto r1 = getFilter();
|
|
assert(equal(r1, [22, 42]));
|
|
|
|
// With chain
|
|
auto nums = [0,1,2,3,4];
|
|
assert(equal(filter!overX(chain(a, nums)), [22, 42]));
|
|
|
|
// With copying of inner struct Filter to Map
|
|
auto arr = [1,2,3,4,5];
|
|
auto m = map!"a + 1"(filter!"a < 4"(arr));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 3, 4 ];
|
|
const aConst = a;
|
|
auto r = filter!("a > 3")(aConst);
|
|
assert(equal(r, [ 4 ][]));
|
|
|
|
a = [ 1, 22, 3, 42, 5 ];
|
|
auto under10 = filter!("a < 10")(a);
|
|
assert(equal(under10, [1, 3, 5][]));
|
|
assert(equal(under10.save, [1, 3, 5][]));
|
|
assert(equal(under10.save, under10));
|
|
|
|
// With copying of inner struct Filter to Map
|
|
auto arr = [1,2,3,4,5];
|
|
auto m = map!"a + 1"(filter!"a < 4"(arr));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.functional : compose, pipe;
|
|
|
|
assert(equal(compose!(map!"2 * a", filter!"a & 1")([1,2,3,4,5]),
|
|
[2,6,10]));
|
|
assert(equal(pipe!(filter!"a & 1", map!"2 * a")([1,2,3,4,5]),
|
|
[2,6,10]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
int x = 10;
|
|
int underX(int a) { return a < x; }
|
|
const(int)[] list = [ 1, 2, 10, 11, 3, 4 ];
|
|
assert(equal(filter!underX(list), [ 1, 2, 3, 4 ]));
|
|
}
|
|
|
|
/**
|
|
* $(D auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range));)
|
|
*
|
|
* Similar to $(D filter), except it defines a bidirectional
|
|
* range. There is a speed disadvantage - the constructor spends time
|
|
* finding the last element in the range that satisfies the filtering
|
|
* condition (in addition to finding the first one). The advantage is
|
|
* that the filtered range can be spanned from both directions. Also,
|
|
* $(XREF range, retro) can be applied against the filtered range.
|
|
*
|
|
*/
|
|
template filterBidirectional(alias pred)
|
|
{
|
|
auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range))
|
|
{
|
|
return FilterBidiResult!(unaryFun!pred, Range)(r);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] arr = [ 1, 2, 3, 4, 5 ];
|
|
auto small = filterBidirectional!("a < 3")(arr);
|
|
static assert(isBidirectionalRange!(typeof(small)));
|
|
assert(small.back == 2);
|
|
assert(equal(small, [ 1, 2 ]));
|
|
assert(equal(retro(small), [ 2, 1 ]));
|
|
// In combination with chain() to span multiple ranges
|
|
int[] a = [ 3, -2, 400 ];
|
|
int[] b = [ 100, -101, 102 ];
|
|
auto r = filterBidirectional!("a > 0")(chain(a, b));
|
|
assert(r.back == 102);
|
|
}
|
|
|
|
private struct FilterBidiResult(alias pred, Range)
|
|
{
|
|
alias R = Unqual!Range;
|
|
R _input;
|
|
|
|
this(R r)
|
|
{
|
|
_input = r;
|
|
while (!_input.empty && !pred(_input.front)) _input.popFront();
|
|
while (!_input.empty && !pred(_input.back)) _input.popBack();
|
|
}
|
|
|
|
@property bool empty() { return _input.empty; }
|
|
|
|
void popFront()
|
|
{
|
|
do
|
|
{
|
|
_input.popFront();
|
|
} while (!_input.empty && !pred(_input.front));
|
|
}
|
|
|
|
@property auto ref front()
|
|
{
|
|
return _input.front;
|
|
}
|
|
|
|
void popBack()
|
|
{
|
|
do
|
|
{
|
|
_input.popBack();
|
|
} while (!_input.empty && !pred(_input.back));
|
|
}
|
|
|
|
@property auto ref back()
|
|
{
|
|
return _input.back;
|
|
}
|
|
|
|
@property auto save()
|
|
{
|
|
return typeof(this)(_input.save);
|
|
}
|
|
}
|
|
|
|
// move
|
|
/**
|
|
Moves $(D source) into $(D target) via a destructive
|
|
copy.
|
|
*/
|
|
void move(T)(ref T source, ref T target)
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
|
|
static if (hasAliasing!T) if (!__ctfe)
|
|
{
|
|
import std.exception : doesPointTo;
|
|
assert(!doesPointTo(source, source), "Cannot move object with internal pointer.");
|
|
}
|
|
|
|
static if (is(T == struct))
|
|
{
|
|
if (&source == &target) return;
|
|
// Most complicated case. Destroy whatever target had in it
|
|
// and bitblast source over it
|
|
static if (hasElaborateDestructor!T) typeid(T).destroy(&target);
|
|
|
|
static if (hasElaborateAssign!T || !isAssignable!T)
|
|
memcpy(&target, &source, T.sizeof);
|
|
else
|
|
target = source;
|
|
|
|
// If the source defines a destructor or a postblit hook, we must obliterate the
|
|
// object in order to avoid double freeing and undue aliasing
|
|
static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T)
|
|
{
|
|
static T empty;
|
|
static if (T.tupleof.length > 0 &&
|
|
T.tupleof[$-1].stringof.endsWith("this"))
|
|
{
|
|
// If T is nested struct, keep original context pointer
|
|
memcpy(&source, &empty, T.sizeof - (void*).sizeof);
|
|
}
|
|
else
|
|
{
|
|
memcpy(&source, &empty, T.sizeof);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Primitive data (including pointers and arrays) or class -
|
|
// assignment works great
|
|
target = source;
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
import std.exception : assertCTFEable;
|
|
assertCTFEable!((){
|
|
Object obj1 = new Object;
|
|
Object obj2 = obj1;
|
|
Object obj3;
|
|
move(obj2, obj3);
|
|
assert(obj3 is obj1);
|
|
|
|
static 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);
|
|
|
|
static struct S2 { int a = 1; int * b; }
|
|
S2 s21 = { 10, null };
|
|
s21.b = new int;
|
|
S2 s22;
|
|
move(s21, s22);
|
|
assert(s21 == s22);
|
|
});
|
|
// Issue 5661 test(1)
|
|
static struct S3
|
|
{
|
|
static struct X { int n = 0; ~this(){n = 0;} }
|
|
X x;
|
|
}
|
|
static assert(hasElaborateDestructor!S3);
|
|
S3 s31, s32;
|
|
s31.x.n = 1;
|
|
move(s31, s32);
|
|
assert(s31.x.n == 0);
|
|
assert(s32.x.n == 1);
|
|
|
|
// Issue 5661 test(2)
|
|
static struct S4
|
|
{
|
|
static struct X { int n = 0; this(this){n = 0;} }
|
|
X x;
|
|
}
|
|
static assert(hasElaborateCopyConstructor!S4);
|
|
S4 s41, s42;
|
|
s41.x.n = 1;
|
|
move(s41, s42);
|
|
assert(s41.x.n == 0);
|
|
assert(s42.x.n == 1);
|
|
}
|
|
|
|
/// Ditto
|
|
T move(T)(ref T source)
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
|
|
static if (hasAliasing!T) if (!__ctfe)
|
|
{
|
|
import std.exception : doesPointTo;
|
|
assert(!doesPointTo(source, source), "Cannot move object with internal pointer.");
|
|
}
|
|
|
|
T result = void;
|
|
static if (is(T == struct))
|
|
{
|
|
// Can avoid destructing result.
|
|
static if (hasElaborateAssign!T || !isAssignable!T)
|
|
memcpy(&result, &source, T.sizeof);
|
|
else
|
|
result = source;
|
|
|
|
// If the source defines a destructor or a postblit hook, we must obliterate the
|
|
// object in order to avoid double freeing and undue aliasing
|
|
static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T)
|
|
{
|
|
static T empty;
|
|
static if (T.tupleof.length > 0 &&
|
|
T.tupleof[$-1].stringof.endsWith("this"))
|
|
{
|
|
// If T is nested struct, keep original context pointer
|
|
memcpy(&source, &empty, T.sizeof - (void*).sizeof);
|
|
}
|
|
else
|
|
{
|
|
memcpy(&source, &empty, T.sizeof);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Primitive data (including pointers and arrays) or class -
|
|
// assignment works great
|
|
result = source;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
import std.exception : assertCTFEable;
|
|
assertCTFEable!((){
|
|
Object obj1 = new Object;
|
|
Object obj2 = obj1;
|
|
Object obj3 = move(obj2);
|
|
assert(obj3 is obj1);
|
|
|
|
static struct S1 { int a = 1, b = 2; }
|
|
S1 s11 = { 10, 11 };
|
|
S1 s12 = move(s11);
|
|
assert(s11.a == 10 && s11.b == 11 && s12.a == 10 && s12.b == 11);
|
|
|
|
static struct S2 { int a = 1; int * b; }
|
|
S2 s21 = { 10, null };
|
|
s21.b = new int;
|
|
S2 s22 = move(s21);
|
|
assert(s21 == s22);
|
|
});
|
|
|
|
// Issue 5661 test(1)
|
|
static struct S3
|
|
{
|
|
static struct X { int n = 0; ~this(){n = 0;} }
|
|
X x;
|
|
}
|
|
static assert(hasElaborateDestructor!S3);
|
|
S3 s31;
|
|
s31.x.n = 1;
|
|
S3 s32 = move(s31);
|
|
assert(s31.x.n == 0);
|
|
assert(s32.x.n == 1);
|
|
|
|
// Issue 5661 test(2)
|
|
static struct S4
|
|
{
|
|
static struct X { int n = 0; this(this){n = 0;} }
|
|
X x;
|
|
}
|
|
static assert(hasElaborateCopyConstructor!S4);
|
|
S4 s41;
|
|
s41.x.n = 1;
|
|
S4 s42 = move(s41);
|
|
assert(s41.x.n == 0);
|
|
assert(s42.x.n == 1);
|
|
}
|
|
|
|
unittest//Issue 6217
|
|
{
|
|
auto x = map!"a"([1,2,3]);
|
|
x = move(x);
|
|
}
|
|
|
|
unittest// Issue 8055
|
|
{
|
|
static struct S
|
|
{
|
|
int x;
|
|
~this()
|
|
{
|
|
assert(x == 0);
|
|
}
|
|
}
|
|
S foo(S s)
|
|
{
|
|
return move(s);
|
|
}
|
|
S a;
|
|
a.x = 0;
|
|
auto b = foo(a);
|
|
assert(b.x == 0);
|
|
}
|
|
|
|
unittest// Issue 8057
|
|
{
|
|
int n = 10;
|
|
struct S
|
|
{
|
|
int x;
|
|
~this()
|
|
{
|
|
// Access to enclosing scope
|
|
assert(n == 10);
|
|
}
|
|
}
|
|
S foo(S s)
|
|
{
|
|
// Move nested struct
|
|
return move(s);
|
|
}
|
|
S a;
|
|
a.x = 1;
|
|
auto b = foo(a);
|
|
assert(b.x == 1);
|
|
|
|
// Regression 8171
|
|
static struct Array(T)
|
|
{
|
|
// nested struct has no member
|
|
struct Payload
|
|
{
|
|
~this() {}
|
|
}
|
|
}
|
|
Array!int.Payload x = void;
|
|
static assert(__traits(compiles, move(x) ));
|
|
static assert(__traits(compiles, move(x, x) ));
|
|
}
|
|
|
|
// moveAll
|
|
/**
|
|
For each element $(D a) in $(D src) and each element $(D b) in $(D
|
|
tgt) in lockstep in increasing order, calls $(D move(a, b)). Returns
|
|
the leftover portion of $(D tgt). Throws an exception if there is not
|
|
enough room in $(D tgt) to accommodate all of $(D src).
|
|
|
|
Preconditions:
|
|
$(D walkLength(src) <= walkLength(tgt))
|
|
*/
|
|
Range2 moveAll(Range1, Range2)(Range1 src, Range2 tgt)
|
|
if (isInputRange!Range1 && isInputRange!Range2
|
|
&& is(typeof(move(src.front, tgt.front))))
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
static if (isRandomAccessRange!Range1 && hasLength!Range1 && hasLength!Range2
|
|
&& hasSlicing!Range2 && isRandomAccessRange!Range2)
|
|
{
|
|
auto toMove = src.length;
|
|
enforce(toMove <= tgt.length); // shouldn't this be an assert?
|
|
foreach (idx; 0 .. toMove)
|
|
move(src[idx], tgt[idx]);
|
|
return tgt[toMove .. tgt.length];
|
|
}
|
|
else
|
|
{
|
|
for (; !src.empty; src.popFront(), tgt.popFront())
|
|
{
|
|
enforce(!tgt.empty); //ditto?
|
|
move(src.front, tgt.front);
|
|
}
|
|
return tgt;
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ 1, 2, 3 ];
|
|
int[] b = new int[5];
|
|
assert(moveAll(a, b) is b[3 .. $]);
|
|
assert(a == b[0 .. 3]);
|
|
assert(a == [ 1, 2, 3 ]);
|
|
}
|
|
|
|
// moveSome
|
|
/**
|
|
For each element $(D a) in $(D src) and each element $(D b) in $(D
|
|
tgt) in lockstep in increasing order, calls $(D move(a, b)). Stops
|
|
when either $(D src) or $(D tgt) have been exhausted. Returns the
|
|
leftover portions of the two ranges.
|
|
*/
|
|
Tuple!(Range1, Range2) moveSome(Range1, Range2)(Range1 src, Range2 tgt)
|
|
if (isInputRange!Range1 && isInputRange!Range2
|
|
&& is(typeof(move(src.front, tgt.front))))
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
for (; !src.empty && !tgt.empty; src.popFront(), tgt.popFront())
|
|
{
|
|
enforce(!tgt.empty);
|
|
move(src.front, tgt.front);
|
|
}
|
|
return tuple(src, tgt);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ 1, 2, 3, 4, 5 ];
|
|
int[] b = new int[3];
|
|
assert(moveSome(a, b)[0] is a[3 .. $]);
|
|
assert(a[0 .. 3] == b);
|
|
assert(a == [ 1, 2, 3, 4, 5 ]);
|
|
}
|
|
|
|
// swap
|
|
/**
|
|
Swaps $(D lhs) and $(D rhs). The instances $(D lhs) and $(D rhs) are moved in
|
|
memory, without ever calling $(D opAssign), nor any other function. $(D T)
|
|
need not be assignable at all to be swapped.
|
|
|
|
If $(D lhs) and $(D rhs) reference the same instance, then nothing is done.
|
|
|
|
$(D lhs) and $(D rhs) must be mutable. If $(D T) is a struct or union, then
|
|
its fields must also all be (recursivelly) mutable.
|
|
*/
|
|
void swap(T)(ref T lhs, ref T rhs) @trusted pure nothrow
|
|
if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs))))
|
|
{
|
|
static if (hasAliasing!T) if (!__ctfe)
|
|
{
|
|
import std.exception : doesPointTo;
|
|
assert(!doesPointTo(lhs, lhs), "Swap: lhs internal pointer.");
|
|
assert(!doesPointTo(rhs, rhs), "Swap: rhs internal pointer.");
|
|
assert(!doesPointTo(lhs, rhs), "Swap: lhs points to rhs.");
|
|
assert(!doesPointTo(rhs, lhs), "Swap: rhs points to lhs.");
|
|
}
|
|
|
|
static if (hasElaborateAssign!T || !isAssignable!T)
|
|
{
|
|
if (&lhs != &rhs)
|
|
{
|
|
// For structs with non-trivial assignment, move memory directly
|
|
ubyte[T.sizeof] t = void;
|
|
auto a = (cast(ubyte*) &lhs)[0 .. T.sizeof];
|
|
auto b = (cast(ubyte*) &rhs)[0 .. T.sizeof];
|
|
t[] = a[];
|
|
a[] = b[];
|
|
b[] = t[];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Avoid assigning overlapping arrays. Dynamic arrays are fine, because
|
|
//it's their ptr and length properties which get assigned rather
|
|
//than their elements when assigning them, but static arrays are value
|
|
//types and therefore all of their elements get copied as part of
|
|
//assigning them, which would be assigning overlapping arrays if lhs
|
|
//and rhs were the same array.
|
|
static if (isStaticArray!T)
|
|
{
|
|
if (lhs.ptr == rhs.ptr)
|
|
return;
|
|
}
|
|
|
|
// For non-struct types, suffice to do the classic swap
|
|
auto tmp = lhs;
|
|
lhs = rhs;
|
|
rhs = tmp;
|
|
}
|
|
}
|
|
|
|
// Not yet documented
|
|
void swap(T)(ref T lhs, ref T rhs) if (is(typeof(lhs.proxySwap(rhs))))
|
|
{
|
|
lhs.proxySwap(rhs);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int a = 42, b = 34;
|
|
swap(a, b);
|
|
assert(a == 34 && b == 42);
|
|
|
|
static struct S { int x; char c; int[] y; }
|
|
S s1 = { 0, 'z', [ 1, 2 ] };
|
|
S s2 = { 42, 'a', [ 4, 6 ] };
|
|
//writeln(s2.tupleof.stringof);
|
|
swap(s1, s2);
|
|
assert(s1.x == 42);
|
|
assert(s1.c == 'a');
|
|
assert(s1.y == [ 4, 6 ]);
|
|
|
|
assert(s2.x == 0);
|
|
assert(s2.c == 'z');
|
|
assert(s2.y == [ 1, 2 ]);
|
|
|
|
immutable int imm1, imm2;
|
|
static assert(!__traits(compiles, swap(imm1, imm2)));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static struct NoCopy
|
|
{
|
|
this(this) { assert(0); }
|
|
int n;
|
|
string s;
|
|
}
|
|
NoCopy nc1, nc2;
|
|
nc1.n = 127; nc1.s = "abc";
|
|
nc2.n = 513; nc2.s = "uvwxyz";
|
|
swap(nc1, nc2);
|
|
assert(nc1.n == 513 && nc1.s == "uvwxyz");
|
|
assert(nc2.n == 127 && nc2.s == "abc");
|
|
swap(nc1, nc1);
|
|
swap(nc2, nc2);
|
|
assert(nc1.n == 513 && nc1.s == "uvwxyz");
|
|
assert(nc2.n == 127 && nc2.s == "abc");
|
|
|
|
static struct NoCopyHolder
|
|
{
|
|
NoCopy noCopy;
|
|
}
|
|
NoCopyHolder h1, h2;
|
|
h1.noCopy.n = 31; h1.noCopy.s = "abc";
|
|
h2.noCopy.n = 65; h2.noCopy.s = null;
|
|
swap(h1, h2);
|
|
assert(h1.noCopy.n == 65 && h1.noCopy.s == null);
|
|
assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc");
|
|
swap(h1, h1);
|
|
swap(h2, h2);
|
|
assert(h1.noCopy.n == 65 && h1.noCopy.s == null);
|
|
assert(h2.noCopy.n == 31 && h2.noCopy.s == "abc");
|
|
|
|
const NoCopy const1, const2;
|
|
static assert(!__traits(compiles, swap(const1, const2)));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//Bug# 4789
|
|
int[1] s = [1];
|
|
swap(s, s);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static struct NoAssign
|
|
{
|
|
int i;
|
|
void opAssign(NoAssign) @disable;
|
|
}
|
|
auto s1 = NoAssign(1);
|
|
auto s2 = NoAssign(2);
|
|
swap(s1, s2);
|
|
assert(s1.i == 2);
|
|
assert(s2.i == 1);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
struct S
|
|
{
|
|
const int i;
|
|
}
|
|
S s;
|
|
static assert(!__traits(compiles, swap(s, s)));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//11853
|
|
alias T = Tuple!(int, double);
|
|
static assert(isAssignable!T);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// 12024
|
|
import std.datetime;
|
|
SysTime a, b;
|
|
}
|
|
|
|
unittest // 9975
|
|
{
|
|
import std.exception : doesPointTo, mayPointTo;
|
|
static struct S2
|
|
{
|
|
union
|
|
{
|
|
size_t sz;
|
|
string s;
|
|
}
|
|
}
|
|
S2 a , b;
|
|
a.sz = -1;
|
|
assert(!doesPointTo(a, b));
|
|
assert( mayPointTo(a, b));
|
|
swap(a, b);
|
|
|
|
//Note: we can catch an error here, because there is no RAII in this test
|
|
import std.exception : assertThrown;
|
|
void* p, pp;
|
|
p = &p;
|
|
assertThrown!Error(move(p));
|
|
assertThrown!Error(move(p, pp));
|
|
assertThrown!Error(swap(p, pp));
|
|
}
|
|
|
|
void swapFront(R1, R2)(R1 r1, R2 r2)
|
|
if (isInputRange!R1 && isInputRange!R2)
|
|
{
|
|
static if (is(typeof(swap(r1.front, r2.front))))
|
|
{
|
|
swap(r1.front, r2.front);
|
|
}
|
|
else
|
|
{
|
|
auto t1 = moveFront(r1), t2 = moveFront(r2);
|
|
r1.front = move(t2);
|
|
r2.front = move(t1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
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
|
|
}
|
|
|
|
// splitter
|
|
/**
|
|
Splits a range using an element as a separator. This can be used with
|
|
any narrow string type or sliceable range type, but is most popular
|
|
with string types.
|
|
|
|
Two adjacent separators are considered to surround an empty element in
|
|
the split range. Use $(D filter!(a => !a.empty)) on the result to compress
|
|
empty elements.
|
|
|
|
If the empty range is given, the result is a range with one empty
|
|
element. If a range with one separator is given, the result is a range
|
|
with two empty elements.
|
|
|
|
If splitting a string on whitespace and token compression is desired,
|
|
consider using $(D splitter) without specifying a separator (see overload
|
|
below).
|
|
|
|
See also $(XREF regex, splitter) for a version that splits using a regular
|
|
expression defined separator.
|
|
*/
|
|
auto splitter(Range, Separator)(Range r, Separator s)
|
|
if (is(typeof(ElementType!Range.init == Separator.init))
|
|
&& ((hasSlicing!Range && hasLength!Range) || isNarrowString!Range))
|
|
{
|
|
import std.conv : unsigned;
|
|
|
|
static struct Result
|
|
{
|
|
private:
|
|
Range _input;
|
|
Separator _separator;
|
|
// Do we need hasLength!Range? popFront uses _input.length...
|
|
alias IndexType = typeof(unsigned(_input.length));
|
|
enum IndexType _unComputed = IndexType.max - 1, _atEnd = IndexType.max;
|
|
IndexType _frontLength = _unComputed;
|
|
IndexType _backLength = _unComputed;
|
|
|
|
static if (isNarrowString!Range)
|
|
{
|
|
size_t _separatorLength;
|
|
}
|
|
else
|
|
{
|
|
enum _separatorLength = 1;
|
|
}
|
|
|
|
static if (isBidirectionalRange!Range)
|
|
{
|
|
static IndexType lastIndexOf(Range haystack, Separator needle)
|
|
{
|
|
auto r = haystack.retro().find(needle);
|
|
return r.retro().length - 1;
|
|
}
|
|
}
|
|
|
|
public:
|
|
this(Range input, Separator separator)
|
|
{
|
|
_input = input;
|
|
_separator = separator;
|
|
|
|
static if (isNarrowString!Range)
|
|
{
|
|
import std.utf : codeLength;
|
|
|
|
_separatorLength = codeLength!(ElementEncodingType!Range)(separator);
|
|
}
|
|
if (_input.empty)
|
|
_frontLength = _atEnd;
|
|
}
|
|
|
|
static if (isInfinite!Range)
|
|
{
|
|
enum bool empty = false;
|
|
}
|
|
else
|
|
{
|
|
@property bool empty()
|
|
{
|
|
return _frontLength == _atEnd;
|
|
}
|
|
}
|
|
|
|
@property Range front()
|
|
{
|
|
assert(!empty);
|
|
if (_frontLength == _unComputed)
|
|
{
|
|
auto r = _input.find(_separator);
|
|
_frontLength = _input.length - r.length;
|
|
}
|
|
return _input[0 .. _frontLength];
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
assert(!empty);
|
|
if (_frontLength == _unComputed)
|
|
{
|
|
front;
|
|
}
|
|
assert(_frontLength <= _input.length);
|
|
if (_frontLength == _input.length)
|
|
{
|
|
// no more input and need to fetch => done
|
|
_frontLength = _atEnd;
|
|
|
|
// Probably don't need this, but just for consistency:
|
|
_backLength = _atEnd;
|
|
}
|
|
else
|
|
{
|
|
_input = _input[_frontLength + _separatorLength .. _input.length];
|
|
_frontLength = _unComputed;
|
|
}
|
|
}
|
|
|
|
static if (isForwardRange!Range)
|
|
{
|
|
@property typeof(this) save()
|
|
{
|
|
auto ret = this;
|
|
ret._input = _input.save;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static if (isBidirectionalRange!Range)
|
|
{
|
|
@property Range back()
|
|
{
|
|
assert(!empty);
|
|
if (_backLength == _unComputed)
|
|
{
|
|
immutable lastIndex = lastIndexOf(_input, _separator);
|
|
if (lastIndex == -1)
|
|
{
|
|
_backLength = _input.length;
|
|
}
|
|
else
|
|
{
|
|
_backLength = _input.length - lastIndex - 1;
|
|
}
|
|
}
|
|
return _input[_input.length - _backLength .. _input.length];
|
|
}
|
|
|
|
void popBack()
|
|
{
|
|
assert(!empty);
|
|
if (_backLength == _unComputed)
|
|
{
|
|
// evaluate back to make sure it's computed
|
|
back;
|
|
}
|
|
assert(_backLength <= _input.length);
|
|
if (_backLength == _input.length)
|
|
{
|
|
// no more input and need to fetch => done
|
|
_frontLength = _atEnd;
|
|
_backLength = _atEnd;
|
|
}
|
|
else
|
|
{
|
|
_input = _input[0 .. _input.length - _backLength - _separatorLength];
|
|
_backLength = _unComputed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result(r, s);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(equal(splitter("hello world", ' '), [ "hello", "", "world" ]));
|
|
int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ];
|
|
int[][] w = [ [1, 2], [], [3], [4, 5], [] ];
|
|
assert(equal(splitter(a, 0), w));
|
|
a = [ 0 ];
|
|
assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ]));
|
|
a = [ 0, 1 ];
|
|
assert(equal(splitter(a, 0), [ [], [1] ]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
assert(equal(splitter("hello world", ' '), [ "hello", "", "world" ]));
|
|
assert(equal(splitter("žlutoučkýřkůň", 'ř'), [ "žlutoučký", "kůň" ]));
|
|
int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ];
|
|
int[][] w = [ [1, 2], [], [3], [4, 5], [] ];
|
|
static assert(isForwardRange!(typeof(splitter(a, 0))));
|
|
|
|
// foreach (x; splitter(a, 0)) {
|
|
// writeln("[", x, "]");
|
|
// }
|
|
assert(equal(splitter(a, 0), w));
|
|
a = null;
|
|
assert(equal(splitter(a, 0), (int[][]).init));
|
|
a = [ 0 ];
|
|
assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ][]));
|
|
a = [ 0, 1 ];
|
|
assert(equal(splitter(a, 0), [ [], [1] ][]));
|
|
|
|
// Thoroughly exercise the bidirectional stuff.
|
|
auto str = "abc abcd abcde ab abcdefg abcdefghij ab ac ar an at ada";
|
|
assert(equal(
|
|
retro(splitter(str, 'a')),
|
|
retro(array(splitter(str, 'a')))
|
|
));
|
|
|
|
// Test interleaving front and back.
|
|
auto split = splitter(str, 'a');
|
|
assert(split.front == "");
|
|
assert(split.back == "");
|
|
split.popBack();
|
|
assert(split.back == "d");
|
|
split.popFront();
|
|
assert(split.front == "bc ");
|
|
assert(split.back == "d");
|
|
split.popFront();
|
|
split.popBack();
|
|
assert(split.back == "t ");
|
|
split.popBack();
|
|
split.popBack();
|
|
split.popFront();
|
|
split.popFront();
|
|
assert(split.front == "b ");
|
|
assert(split.back == "r ");
|
|
|
|
foreach (DummyType; AllDummyRanges) { // Bug 4408
|
|
static if (isRandomAccessRange!DummyType) {
|
|
static assert(isBidirectionalRange!DummyType);
|
|
DummyType d;
|
|
auto s = splitter(d, 5);
|
|
assert(equal(s.front, [1,2,3,4]));
|
|
assert(equal(s.back, [6,7,8,9,10]));
|
|
|
|
auto s2 = splitter(d, [4, 5]);
|
|
assert(equal(s2.front, [1,2,3]));
|
|
}
|
|
}
|
|
}
|
|
@safe unittest
|
|
{
|
|
auto L = retro(iota(1L, 10L));
|
|
auto s = splitter(L, 5L);
|
|
assert(equal(s.front, [9L, 8L, 7L, 6L]));
|
|
s.popFront();
|
|
assert(equal(s.front, [4L, 3L, 2L, 1L]));
|
|
s.popFront();
|
|
assert(s.empty);
|
|
}
|
|
|
|
/**
|
|
Splits a range using another range as a separator. This can be used
|
|
with any narrow string type or sliceable range type, but is most popular
|
|
with string types.
|
|
|
|
Two adjacent separators are considered to surround an empty element in
|
|
the split range. Use $(D filter!(a => !a.empty)) on the result to compress
|
|
empty elements.
|
|
|
|
See also $(XREF regex, splitter) for a version that splits using a regular
|
|
expression defined separator.
|
|
*/
|
|
auto splitter(Range, Separator)(Range r, Separator s)
|
|
if (is(typeof(Range.init.front == Separator.init.front) : bool)
|
|
&& (hasSlicing!Range || isNarrowString!Range)
|
|
&& isForwardRange!Separator
|
|
&& (hasLength!Separator || isNarrowString!Separator))
|
|
{
|
|
import std.conv : unsigned;
|
|
|
|
static struct Result
|
|
{
|
|
private:
|
|
Range _input;
|
|
Separator _separator;
|
|
alias RIndexType = typeof(unsigned(_input.length));
|
|
// _frontLength == size_t.max means empty
|
|
RIndexType _frontLength = RIndexType.max;
|
|
static if (isBidirectionalRange!Range)
|
|
RIndexType _backLength = RIndexType.max;
|
|
|
|
@property auto separatorLength() { return _separator.length; }
|
|
|
|
void ensureFrontLength()
|
|
{
|
|
if (_frontLength != _frontLength.max) return;
|
|
assert(!_input.empty);
|
|
// compute front length
|
|
_frontLength = (_separator.empty) ? 1 :
|
|
_input.length - find(_input, _separator).length;
|
|
static if (isBidirectionalRange!Range)
|
|
if (_frontLength == _input.length) _backLength = _frontLength;
|
|
}
|
|
|
|
void ensureBackLength()
|
|
{
|
|
static if (isBidirectionalRange!Range)
|
|
if (_backLength != _backLength.max) return;
|
|
assert(!_input.empty);
|
|
// compute back length
|
|
static if (isBidirectionalRange!Range && isBidirectionalRange!Separator)
|
|
{
|
|
_backLength = _input.length -
|
|
find(retro(_input), retro(_separator)).source.length;
|
|
}
|
|
}
|
|
|
|
public:
|
|
this(Range input, Separator separator)
|
|
{
|
|
_input = input;
|
|
_separator = separator;
|
|
}
|
|
|
|
@property Range front()
|
|
{
|
|
assert(!empty);
|
|
ensureFrontLength();
|
|
return _input[0 .. _frontLength];
|
|
}
|
|
|
|
static if (isInfinite!Range)
|
|
{
|
|
enum bool empty = false; // Propagate infiniteness
|
|
}
|
|
else
|
|
{
|
|
@property bool empty()
|
|
{
|
|
return _frontLength == RIndexType.max && _input.empty;
|
|
}
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
assert(!empty);
|
|
ensureFrontLength();
|
|
if (_frontLength == _input.length)
|
|
{
|
|
// done, there's no separator in sight
|
|
_input = _input[_frontLength .. _frontLength];
|
|
_frontLength = _frontLength.max;
|
|
static if (isBidirectionalRange!Range)
|
|
_backLength = _backLength.max;
|
|
return;
|
|
}
|
|
if (_frontLength + separatorLength == _input.length)
|
|
{
|
|
// Special case: popping the first-to-last item; there is
|
|
// an empty item right after this.
|
|
_input = _input[_input.length .. _input.length];
|
|
_frontLength = 0;
|
|
static if (isBidirectionalRange!Range)
|
|
_backLength = 0;
|
|
return;
|
|
}
|
|
// Normal case, pop one item and the separator, get ready for
|
|
// reading the next item
|
|
_input = _input[_frontLength + separatorLength .. _input.length];
|
|
// mark _frontLength as uninitialized
|
|
_frontLength = _frontLength.max;
|
|
}
|
|
|
|
static if (isForwardRange!Range)
|
|
{
|
|
@property typeof(this) save()
|
|
{
|
|
auto ret = this;
|
|
ret._input = _input.save;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// Bidirectional functionality as suggested by Brad Roberts.
|
|
static if (isBidirectionalRange!Range && isBidirectionalRange!Separator)
|
|
{
|
|
//Deprecated. It will be removed in December 2015
|
|
deprecated("splitter!(Range, Range) cannot be iterated backwards (due to separator overlap).")
|
|
@property Range back()
|
|
{
|
|
ensureBackLength();
|
|
return _input[_input.length - _backLength .. _input.length];
|
|
}
|
|
|
|
//Deprecated. It will be removed in December 2015
|
|
deprecated("splitter!(Range, Range) cannot be iterated backwards (due to separator overlap).")
|
|
void popBack()
|
|
{
|
|
ensureBackLength();
|
|
if (_backLength == _input.length)
|
|
{
|
|
// done
|
|
_input = _input[0 .. 0];
|
|
_frontLength = _frontLength.max;
|
|
_backLength = _backLength.max;
|
|
return;
|
|
}
|
|
if (_backLength + separatorLength == _input.length)
|
|
{
|
|
// Special case: popping the first-to-first item; there is
|
|
// an empty item right before this. Leave the separator in.
|
|
_input = _input[0 .. 0];
|
|
_frontLength = 0;
|
|
_backLength = 0;
|
|
return;
|
|
}
|
|
// Normal case
|
|
_input = _input[0 .. _input.length - _backLength - separatorLength];
|
|
_backLength = _backLength.max;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result(r, s);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
auto s = ",abc, de, fg,hi,";
|
|
auto sp0 = splitter(s, ',');
|
|
// //foreach (e; sp0) writeln("[", e, "]");
|
|
assert(equal(sp0, ["", "abc", " de", " fg", "hi", ""][]));
|
|
|
|
auto s1 = ", abc, de, fg, hi, ";
|
|
auto sp1 = splitter(s1, ", ");
|
|
//foreach (e; sp1) writeln("[", e, "]");
|
|
assert(equal(sp1, ["", "abc", "de", " fg", "hi", ""][]));
|
|
static assert(isForwardRange!(typeof(sp1)));
|
|
|
|
int[] a = [ 1, 2, 0, 3, 0, 4, 5, 0 ];
|
|
int[][] w = [ [1, 2], [3], [4, 5], [] ];
|
|
uint i;
|
|
foreach (e; splitter(a, 0))
|
|
{
|
|
assert(i < w.length);
|
|
assert(e == w[i++]);
|
|
}
|
|
assert(i == w.length);
|
|
// // Now go back
|
|
// auto s2 = splitter(a, 0);
|
|
|
|
// foreach (e; retro(s2))
|
|
// {
|
|
// assert(i > 0);
|
|
// assert(equal(e, w[--i]), text(e));
|
|
// }
|
|
// assert(i == 0);
|
|
|
|
wstring names = ",peter,paul,jerry,";
|
|
auto words = split(names, ",");
|
|
assert(walkLength(words) == 5, text(walkLength(words)));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
auto s6 = ",";
|
|
auto sp6 = splitter(s6, ',');
|
|
foreach (e; sp6)
|
|
{
|
|
//writeln("{", e, "}");
|
|
}
|
|
assert(equal(sp6, ["", ""][]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Issue 10773
|
|
auto s = splitter("abc", "");
|
|
assert(s.equal(["a", "b", "c"]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Test by-reference separator
|
|
class RefSep {
|
|
@safe:
|
|
string _impl;
|
|
this(string s) { _impl = s; }
|
|
@property empty() { return _impl.empty; }
|
|
@property auto front() { return _impl.front; }
|
|
void popFront() { _impl = _impl[1..$]; }
|
|
@property RefSep save() { return new RefSep(_impl); }
|
|
@property auto length() { return _impl.length; }
|
|
}
|
|
auto sep = new RefSep("->");
|
|
auto data = "i->am->pointing";
|
|
auto words = splitter(data, sep);
|
|
assert(words.equal([ "i", "am", "pointing" ]));
|
|
}
|
|
|
|
///ditto
|
|
auto splitter(alias isTerminator, Range)(Range input)
|
|
if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(input.front))))
|
|
{
|
|
return SplitterResult!(unaryFun!isTerminator, Range)(input);
|
|
}
|
|
|
|
private struct SplitterResult(alias isTerminator, Range)
|
|
{
|
|
enum fullSlicing = (hasLength!Range && hasSlicing!Range) || isSomeString!Range;
|
|
|
|
private Range _input;
|
|
private size_t _end = 0;
|
|
static if(!fullSlicing)
|
|
private Range _next;
|
|
|
|
private void findTerminator()
|
|
{
|
|
static if (fullSlicing)
|
|
{
|
|
auto r = find!isTerminator(_input.save);
|
|
_end = _input.length - r.length;
|
|
}
|
|
else
|
|
for ( _end = 0; !_next.empty ; _next.popFront)
|
|
{
|
|
if (isTerminator(_next.front))
|
|
break;
|
|
++_end;
|
|
}
|
|
}
|
|
|
|
this(Range input)
|
|
{
|
|
_input = input;
|
|
static if(!fullSlicing)
|
|
_next = _input.save;
|
|
|
|
if (!_input.empty)
|
|
findTerminator();
|
|
else
|
|
_end = size_t.max;
|
|
}
|
|
|
|
static if (isInfinite!Range)
|
|
{
|
|
enum bool empty = false; // Propagate infiniteness.
|
|
}
|
|
else
|
|
{
|
|
@property bool empty()
|
|
{
|
|
return _end == size_t.max;
|
|
}
|
|
}
|
|
|
|
@property auto front()
|
|
{
|
|
version(assert)
|
|
{
|
|
import core.exception : RangeError;
|
|
if (empty)
|
|
throw new RangeError();
|
|
}
|
|
static if (fullSlicing)
|
|
return _input[0 .. _end];
|
|
else
|
|
return _input.takeExactly(_end);
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
version(assert)
|
|
{
|
|
import core.exception : RangeError;
|
|
if (empty)
|
|
throw new RangeError();
|
|
}
|
|
|
|
static if (fullSlicing)
|
|
{
|
|
_input = _input[_end .. _input.length];
|
|
if (_input.empty)
|
|
{
|
|
_end = size_t.max;
|
|
return;
|
|
}
|
|
_input.popFront();
|
|
}
|
|
else
|
|
{
|
|
if (_next.empty)
|
|
{
|
|
_input = _next;
|
|
_end = size_t.max;
|
|
return;
|
|
}
|
|
_next.popFront();
|
|
_input = _next.save;
|
|
}
|
|
findTerminator();
|
|
}
|
|
|
|
@property typeof(this) save()
|
|
{
|
|
auto ret = this;
|
|
ret._input = _input.save;
|
|
static if (!fullSlicing)
|
|
ret._next = _next.save;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto L = iota(1L, 10L);
|
|
auto s = splitter(L, [5L, 6L]);
|
|
assert(equal(s.front, [1L, 2L, 3L, 4L]));
|
|
s.popFront();
|
|
assert(equal(s.front, [7L, 8L, 9L]));
|
|
s.popFront();
|
|
assert(s.empty);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
void compare(string sentence, string[] witness)
|
|
{
|
|
auto r = splitter!"a == ' '"(sentence);
|
|
assert(equal(r.save, witness), algoFormat("got: %(%s, %) expected: %(%s, %)", r, witness));
|
|
}
|
|
|
|
compare(" Mary has a little lamb. ",
|
|
["", "Mary", "", "has", "a", "little", "lamb.", "", "", ""]);
|
|
compare("Mary has a little lamb. ",
|
|
["Mary", "", "has", "a", "little", "lamb.", "", "", ""]);
|
|
compare("Mary has a little lamb.",
|
|
["Mary", "", "has", "a", "little", "lamb."]);
|
|
compare("", (string[]).init);
|
|
compare(" ", ["", ""]);
|
|
|
|
static assert(isForwardRange!(typeof(splitter!"a == ' '"("ABC"))));
|
|
|
|
foreach (DummyType; AllDummyRanges)
|
|
{
|
|
static if (isRandomAccessRange!DummyType)
|
|
{
|
|
auto rangeSplit = splitter!"a == 5"(DummyType.init);
|
|
assert(equal(rangeSplit.front, [1,2,3,4]));
|
|
rangeSplit.popFront();
|
|
assert(equal(rangeSplit.front, [6,7,8,9,10]));
|
|
}
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
struct Entry
|
|
{
|
|
int low;
|
|
int high;
|
|
int[][] result;
|
|
}
|
|
Entry[] entries = [
|
|
Entry(0, 0, []),
|
|
Entry(0, 1, [[0]]),
|
|
Entry(1, 2, [[], []]),
|
|
Entry(2, 7, [[2], [4], [6]]),
|
|
Entry(1, 8, [[], [2], [4], [6], []]),
|
|
];
|
|
foreach ( entry ; entries )
|
|
{
|
|
auto a = iota(entry.low, entry.high).filter!"true"();
|
|
auto b = splitter!"a%2"(a);
|
|
assert(equal!equal(b.save, entry.result), algoFormat("got: %(%s, %) expected: %(%s, %)", b, entry.result));
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.uni : isWhite;
|
|
|
|
//@@@6791@@@
|
|
assert(equal(std.array.splitter("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"]));
|
|
assert(equal(splitter!(std.uni.isWhite)("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"]));
|
|
assert(equal(splitter!"a=='本'"("日本語"), ["日", "語"]));
|
|
}
|
|
|
|
/++
|
|
Lazily splits the string $(D s) into words, using whitespace as the delimiter.
|
|
|
|
This function is string specific and, contrary to
|
|
$(D splitter!(std.uni.isWhite)), runs of whitespace will be merged together
|
|
(no empty tokens will be produced).
|
|
+/
|
|
auto splitter(C)(C[] s)
|
|
if (isSomeChar!C)
|
|
{
|
|
static struct Result
|
|
{
|
|
private:
|
|
import core.exception;
|
|
C[] _s;
|
|
size_t _frontLength;
|
|
|
|
void getFirst() pure @safe
|
|
{
|
|
import std.uni : isWhite;
|
|
|
|
auto r = find!(std.uni.isWhite)(_s);
|
|
_frontLength = _s.length - r.length;
|
|
}
|
|
|
|
public:
|
|
this(C[] s) pure @safe
|
|
{
|
|
import std.string;
|
|
_s = s.strip();
|
|
getFirst();
|
|
}
|
|
|
|
@property C[] front() pure @safe
|
|
{
|
|
version(assert) if (empty) throw new RangeError();
|
|
return _s[0 .. _frontLength];
|
|
}
|
|
|
|
void popFront() pure @safe
|
|
{
|
|
import std.string : stripLeft;
|
|
version(assert) if (empty) throw new RangeError();
|
|
_s = _s[_frontLength .. $].stripLeft();
|
|
getFirst();
|
|
}
|
|
|
|
@property bool empty() const @safe pure nothrow
|
|
{
|
|
return _s.empty;
|
|
}
|
|
|
|
@property inout(Result) save() inout @safe pure nothrow
|
|
{
|
|
return this;
|
|
}
|
|
}
|
|
return Result(s);
|
|
}
|
|
|
|
///
|
|
@safe pure unittest
|
|
{
|
|
auto a = " a bcd ef gh ";
|
|
assert(equal(splitter(a), ["a", "bcd", "ef", "gh"][]));
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
foreach(S; TypeTuple!(string, wstring, dstring))
|
|
{
|
|
import std.conv : to;
|
|
S a = " a bcd ef gh ";
|
|
assert(equal(splitter(a), [to!S("a"), to!S("bcd"), to!S("ef"), to!S("gh")]));
|
|
a = "";
|
|
assert(splitter(a).empty);
|
|
}
|
|
|
|
immutable string s = " a bcd ef gh ";
|
|
assert(equal(splitter(s), ["a", "bcd", "ef", "gh"][]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
import std.string : strip;
|
|
|
|
// TDPL example, page 8
|
|
uint[string] dictionary;
|
|
char[][3] lines;
|
|
lines[0] = "line one".dup;
|
|
lines[1] = "line \ttwo".dup;
|
|
lines[2] = "yah last line\ryah".dup;
|
|
foreach (line; lines) {
|
|
foreach (word; std.array.splitter(std.string.strip(line))) {
|
|
if (word in dictionary) continue; // Nothing to do
|
|
auto newID = dictionary.length;
|
|
dictionary[to!string(word)] = cast(uint)newID;
|
|
}
|
|
}
|
|
assert(dictionary.length == 5);
|
|
assert(dictionary["line"]== 0);
|
|
assert(dictionary["one"]== 1);
|
|
assert(dictionary["two"]== 2);
|
|
assert(dictionary["yah"]== 3);
|
|
assert(dictionary["last"]== 4);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : text;
|
|
import std.string : split;
|
|
|
|
// Check consistency:
|
|
// All flavors of split should produce the same results
|
|
foreach (input; [(int[]).init,
|
|
[0],
|
|
[0, 1, 0],
|
|
[1, 1, 0, 0, 1, 1],
|
|
])
|
|
{
|
|
foreach (s; [0, 1])
|
|
{
|
|
auto result = split(input, s);
|
|
|
|
assert(equal(result, split(input, [s])), algoFormat(`"[%(%s,%)]"`, split(input, [s])));
|
|
//assert(equal(result, split(input, [s].filter!"true"()))); //Not yet implemented
|
|
assert(equal(result, split!((a) => a == s)(input)), text(split!((a) => a == s)(input)));
|
|
|
|
//assert(equal!equal(result, split(input.filter!"true"(), s))); //Not yet implemented
|
|
//assert(equal!equal(result, split(input.filter!"true"(), [s]))); //Not yet implemented
|
|
//assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented
|
|
assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"())));
|
|
|
|
assert(equal(result, splitter(input, s)));
|
|
assert(equal(result, splitter(input, [s])));
|
|
//assert(equal(result, splitter(input, [s].filter!"true"()))); //Not yet implemented
|
|
assert(equal(result, splitter!((a) => a == s)(input)));
|
|
|
|
//assert(equal!equal(result, splitter(input.filter!"true"(), s))); //Not yet implemented
|
|
//assert(equal!equal(result, splitter(input.filter!"true"(), [s]))); //Not yet implemented
|
|
//assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented
|
|
assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"())));
|
|
}
|
|
}
|
|
foreach (input; [string.init,
|
|
" ",
|
|
" hello ",
|
|
"hello hello",
|
|
" hello what heck this ? "
|
|
])
|
|
{
|
|
foreach (s; [' ', 'h'])
|
|
{
|
|
auto result = split(input, s);
|
|
|
|
assert(equal(result, split(input, [s])));
|
|
//assert(equal(result, split(input, [s].filter!"true"()))); //Not yet implemented
|
|
assert(equal(result, split!((a) => a == s)(input)));
|
|
|
|
//assert(equal!equal(result, split(input.filter!"true"(), s))); //Not yet implemented
|
|
//assert(equal!equal(result, split(input.filter!"true"(), [s]))); //Not yet implemented
|
|
//assert(equal!equal(result, split(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented
|
|
assert(equal!equal(result, split!((a) => a == s)(input.filter!"true"())));
|
|
|
|
assert(equal(result, splitter(input, s)));
|
|
assert(equal(result, splitter(input, [s])));
|
|
//assert(equal(result, splitter(input, [s].filter!"true"()))); //Not yet implemented
|
|
assert(equal(result, splitter!((a) => a == s)(input)));
|
|
|
|
//assert(equal!equal(result, splitter(input.filter!"true"(), s))); //Not yet implemented
|
|
//assert(equal!equal(result, splitter(input.filter!"true"(), [s]))); //Not yet implemented
|
|
//assert(equal!equal(result, splitter(input.filter!"true"(), [s].filter!"true"()))); //Not yet implemented
|
|
assert(equal!equal(result, splitter!((a) => a == s)(input.filter!"true"())));
|
|
}
|
|
}
|
|
}
|
|
// joiner
|
|
/**
|
|
Lazily joins a range of ranges with a separator. The separator itself
|
|
is a range. If you do not provide a separator, then the ranges are
|
|
joined directly without anything in between them.
|
|
|
|
*/
|
|
auto joiner(RoR, Separator)(RoR r, Separator sep)
|
|
if (isInputRange!RoR && isInputRange!(ElementType!RoR)
|
|
&& isForwardRange!Separator
|
|
&& is(ElementType!Separator : ElementType!(ElementType!RoR)))
|
|
{
|
|
static struct Result
|
|
{
|
|
private RoR _items;
|
|
private ElementType!RoR _current;
|
|
private Separator _sep, _currentSep;
|
|
|
|
// This is a mixin instead of a function for the following reason (as
|
|
// explained by Kenji Hara): "This is necessary from 2.061. If a
|
|
// struct has a nested struct member, it must be directly initialized
|
|
// in its constructor to avoid leaving undefined state. If you change
|
|
// setItem to a function, the initialization of _current field is
|
|
// wrapped into private member function, then compiler could not detect
|
|
// that is correctly initialized while constructing. To avoid the
|
|
// compiler error check, string mixin is used."
|
|
private enum setItem =
|
|
q{
|
|
if (!_items.empty)
|
|
{
|
|
// If we're exporting .save, we must not consume any of the
|
|
// subranges, since RoR.save does not guarantee that the states
|
|
// of the subranges are also saved.
|
|
static if (isForwardRange!RoR &&
|
|
isForwardRange!(ElementType!RoR))
|
|
_current = _items.front.save;
|
|
else
|
|
_current = _items.front;
|
|
}
|
|
};
|
|
|
|
private void useSeparator()
|
|
{
|
|
// Separator must always come after an item.
|
|
assert(_currentSep.empty && !_items.empty,
|
|
"joiner: internal error");
|
|
_items.popFront();
|
|
|
|
// If there are no more items, we're done, since separators are not
|
|
// terminators.
|
|
if (_items.empty) return;
|
|
|
|
if (_sep.empty)
|
|
{
|
|
// Advance to the next range in the
|
|
// input
|
|
while (_items.front.empty)
|
|
{
|
|
_items.popFront();
|
|
if (_items.empty) return;
|
|
}
|
|
mixin(setItem);
|
|
}
|
|
else
|
|
{
|
|
_currentSep = _sep.save;
|
|
assert(!_currentSep.empty);
|
|
}
|
|
}
|
|
|
|
private enum useItem =
|
|
q{
|
|
// FIXME: this will crash if either _currentSep or _current are
|
|
// class objects, because .init is null when the ctor invokes this
|
|
// mixin.
|
|
//assert(_currentSep.empty && _current.empty,
|
|
// "joiner: internal error");
|
|
|
|
// Use the input
|
|
if (_items.empty) return;
|
|
mixin(setItem);
|
|
if (_current.empty)
|
|
{
|
|
// No data in the current item - toggle to use the separator
|
|
useSeparator();
|
|
}
|
|
};
|
|
|
|
this(RoR items, Separator sep)
|
|
{
|
|
_items = items;
|
|
_sep = sep;
|
|
|
|
//mixin(useItem); // _current should be initialized in place
|
|
if (_items.empty)
|
|
_current = _current.init; // set invalid state
|
|
else
|
|
{
|
|
// If we're exporting .save, we must not consume any of the
|
|
// subranges, since RoR.save does not guarantee that the states
|
|
// of the subranges are also saved.
|
|
static if (isForwardRange!RoR &&
|
|
isForwardRange!(ElementType!RoR))
|
|
_current = _items.front.save;
|
|
else
|
|
_current = _items.front;
|
|
|
|
if (_current.empty)
|
|
{
|
|
// No data in the current item - toggle to use the separator
|
|
useSeparator();
|
|
}
|
|
}
|
|
}
|
|
|
|
@property auto empty()
|
|
{
|
|
return _items.empty;
|
|
}
|
|
|
|
@property ElementType!(ElementType!RoR) front()
|
|
{
|
|
if (!_currentSep.empty) return _currentSep.front;
|
|
assert(!_current.empty);
|
|
return _current.front;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
assert(!_items.empty);
|
|
// Using separator?
|
|
if (!_currentSep.empty)
|
|
{
|
|
_currentSep.popFront();
|
|
if (!_currentSep.empty) return;
|
|
mixin(useItem);
|
|
}
|
|
else
|
|
{
|
|
// we're using the range
|
|
_current.popFront();
|
|
if (!_current.empty) return;
|
|
useSeparator();
|
|
}
|
|
}
|
|
|
|
static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
|
|
{
|
|
@property auto save()
|
|
{
|
|
Result copy = this;
|
|
copy._items = _items.save;
|
|
copy._current = _current.save;
|
|
copy._sep = _sep.save;
|
|
copy._currentSep = _currentSep.save;
|
|
return copy;
|
|
}
|
|
}
|
|
}
|
|
return Result(r, sep);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
static assert(isInputRange!(typeof(joiner([""], ""))));
|
|
static assert(isForwardRange!(typeof(joiner([""], ""))));
|
|
assert(equal(joiner([""], "xyz"), ""), text(joiner([""], "xyz")));
|
|
assert(equal(joiner(["", ""], "xyz"), "xyz"), text(joiner(["", ""], "xyz")));
|
|
assert(equal(joiner(["", "abc"], "xyz"), "xyzabc"));
|
|
assert(equal(joiner(["abc", ""], "xyz"), "abcxyz"));
|
|
assert(equal(joiner(["abc", "def"], "xyz"), "abcxyzdef"));
|
|
assert(equal(joiner(["Mary", "has", "a", "little", "lamb"], "..."),
|
|
"Mary...has...a...little...lamb"));
|
|
assert(equal(joiner(["abc", "def"]), "abcdef"));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// joiner() should work for non-forward ranges too.
|
|
InputRange!string r = inputRangeObject(["abc", "def"]);
|
|
assert (equal(joiner(r, "xyz"), "abcxyzdef"));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// Related to issue 8061
|
|
auto r = joiner([
|
|
inputRangeObject("abc"),
|
|
inputRangeObject("def"),
|
|
], "-*-");
|
|
|
|
assert(equal(r, "abc-*-def"));
|
|
|
|
// Test case where separator is specified but is empty.
|
|
auto s = joiner([
|
|
inputRangeObject("abc"),
|
|
inputRangeObject("def"),
|
|
], "");
|
|
|
|
assert(equal(s, "abcdef"));
|
|
|
|
// Test empty separator with some empty elements
|
|
auto t = joiner([
|
|
inputRangeObject("abc"),
|
|
inputRangeObject(""),
|
|
inputRangeObject("def"),
|
|
inputRangeObject(""),
|
|
], "");
|
|
|
|
assert(equal(t, "abcdef"));
|
|
|
|
// Test empty elements with non-empty separator
|
|
auto u = joiner([
|
|
inputRangeObject(""),
|
|
inputRangeObject("abc"),
|
|
inputRangeObject(""),
|
|
inputRangeObject("def"),
|
|
inputRangeObject(""),
|
|
], "+-");
|
|
|
|
assert(equal(u, "+-abc+-+-def+-"));
|
|
|
|
// Issue 13441: only(x) as separator
|
|
string[][] lines = [null];
|
|
lines
|
|
.joiner(only("b"))
|
|
.array();
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Transience correctness test
|
|
struct TransientRange
|
|
{
|
|
@safe:
|
|
int[][] src;
|
|
int[] buf;
|
|
|
|
this(int[][] _src)
|
|
{
|
|
src = _src;
|
|
buf.length = 100;
|
|
}
|
|
@property bool empty() { return src.empty; }
|
|
@property int[] front()
|
|
{
|
|
assert(src.front.length <= buf.length);
|
|
buf[0 .. src.front.length] = src.front[0..$];
|
|
return buf[0 .. src.front.length];
|
|
}
|
|
void popFront() { src.popFront(); }
|
|
}
|
|
|
|
// Test embedded empty elements
|
|
auto tr1 = TransientRange([[], [1,2,3], [], [4]]);
|
|
assert(equal(joiner(tr1, [0]), [0,1,2,3,0,0,4]));
|
|
|
|
// Test trailing empty elements
|
|
auto tr2 = TransientRange([[], [1,2,3], []]);
|
|
assert(equal(joiner(tr2, [0]), [0,1,2,3,0]));
|
|
|
|
// Test no empty elements
|
|
auto tr3 = TransientRange([[1,2], [3,4]]);
|
|
assert(equal(joiner(tr3, [0,1]), [1,2,0,1,3,4]));
|
|
|
|
// Test consecutive empty elements
|
|
auto tr4 = TransientRange([[1,2], [], [], [], [3,4]]);
|
|
assert(equal(joiner(tr4, [0,1]), [1,2,0,1,0,1,0,1,0,1,3,4]));
|
|
|
|
// Test consecutive trailing empty elements
|
|
auto tr5 = TransientRange([[1,2], [3,4], [], []]);
|
|
assert(equal(joiner(tr5, [0,1]), [1,2,0,1,3,4,0,1,0,1]));
|
|
}
|
|
|
|
/// Ditto
|
|
auto joiner(RoR)(RoR r)
|
|
if (isInputRange!RoR && isInputRange!(ElementType!RoR))
|
|
{
|
|
static struct Result
|
|
{
|
|
private:
|
|
RoR _items;
|
|
ElementType!RoR _current;
|
|
enum prepare =
|
|
q{
|
|
// Skip over empty subranges.
|
|
if (_items.empty) return;
|
|
while (_items.front.empty)
|
|
{
|
|
_items.popFront();
|
|
if (_items.empty) return;
|
|
}
|
|
// We cannot export .save method unless we ensure subranges are not
|
|
// consumed when a .save'd copy of ourselves is iterated over. So
|
|
// we need to .save each subrange we traverse.
|
|
static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
|
|
_current = _items.front.save;
|
|
else
|
|
_current = _items.front;
|
|
};
|
|
public:
|
|
this(RoR r)
|
|
{
|
|
_items = r;
|
|
//mixin(prepare); // _current should be initialized in place
|
|
|
|
// Skip over empty subranges.
|
|
while (!_items.empty && _items.front.empty)
|
|
_items.popFront();
|
|
|
|
if (_items.empty)
|
|
_current = _current.init; // set invalid state
|
|
else
|
|
{
|
|
// We cannot export .save method unless we ensure subranges are not
|
|
// consumed when a .save'd copy of ourselves is iterated over. So
|
|
// we need to .save each subrange we traverse.
|
|
static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
|
|
_current = _items.front.save;
|
|
else
|
|
_current = _items.front;
|
|
}
|
|
}
|
|
static if (isInfinite!RoR)
|
|
{
|
|
enum bool empty = false;
|
|
}
|
|
else
|
|
{
|
|
@property auto empty()
|
|
{
|
|
return _items.empty;
|
|
}
|
|
}
|
|
@property auto ref front()
|
|
{
|
|
assert(!empty);
|
|
return _current.front;
|
|
}
|
|
void popFront()
|
|
{
|
|
assert(!_current.empty);
|
|
_current.popFront();
|
|
if (_current.empty)
|
|
{
|
|
assert(!_items.empty);
|
|
_items.popFront();
|
|
mixin(prepare);
|
|
}
|
|
}
|
|
static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
|
|
{
|
|
@property auto save()
|
|
{
|
|
Result copy = this;
|
|
copy._items = _items.save;
|
|
copy._current = _current.save;
|
|
return copy;
|
|
}
|
|
}
|
|
}
|
|
return Result(r);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
static assert(isInputRange!(typeof(joiner([""]))));
|
|
static assert(isForwardRange!(typeof(joiner([""]))));
|
|
assert(equal(joiner([""]), ""));
|
|
assert(equal(joiner(["", ""]), ""));
|
|
assert(equal(joiner(["", "abc"]), "abc"));
|
|
assert(equal(joiner(["abc", ""]), "abc"));
|
|
assert(equal(joiner(["abc", "def"]), "abcdef"));
|
|
assert(equal(joiner(["Mary", "has", "a", "little", "lamb"]),
|
|
"Maryhasalittlelamb"));
|
|
assert(equal(joiner(std.range.repeat("abc", 3)), "abcabcabc"));
|
|
|
|
// joiner allows in-place mutation!
|
|
auto a = [ [1, 2, 3], [42, 43] ];
|
|
auto j = joiner(a);
|
|
j.front = 44;
|
|
assert(a == [ [44, 2, 3], [42, 43] ]);
|
|
|
|
// bugzilla 8240
|
|
assert(equal(joiner([inputRangeObject("")]), ""));
|
|
|
|
// issue 8792
|
|
auto b = [[1], [2], [3]];
|
|
auto jb = joiner(b);
|
|
auto js = jb.save;
|
|
assert(equal(jb, js));
|
|
|
|
auto js2 = jb.save;
|
|
jb.popFront();
|
|
assert(!equal(jb, js));
|
|
assert(equal(js2, js));
|
|
js.popFront();
|
|
assert(equal(jb, js));
|
|
assert(!equal(js2, js));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
struct TransientRange
|
|
{
|
|
@safe:
|
|
int[] _buf;
|
|
int[][] _values;
|
|
this(int[][] values)
|
|
{
|
|
_values = values;
|
|
_buf = new int[128];
|
|
}
|
|
@property bool empty()
|
|
{
|
|
return _values.length == 0;
|
|
}
|
|
@property auto front()
|
|
{
|
|
foreach (i; 0 .. _values.front.length)
|
|
{
|
|
_buf[i] = _values[0][i];
|
|
}
|
|
return _buf[0 .. _values.front.length];
|
|
}
|
|
void popFront()
|
|
{
|
|
_values = _values[1 .. $];
|
|
}
|
|
}
|
|
|
|
auto rr = TransientRange([[1,2], [3,4,5], [], [6,7]]);
|
|
|
|
// Can't use array() or equal() directly because they fail with transient
|
|
// .front.
|
|
int[] result;
|
|
foreach (c; rr.joiner()) {
|
|
result ~= c;
|
|
}
|
|
|
|
assert(equal(result, [1,2,3,4,5,6,7]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
struct TransientRange
|
|
{
|
|
@safe:
|
|
dchar[] _buf;
|
|
dstring[] _values;
|
|
this(dstring[] values)
|
|
{
|
|
_buf.length = 128;
|
|
_values = values;
|
|
}
|
|
@property bool empty()
|
|
{
|
|
return _values.length == 0;
|
|
}
|
|
@property auto front()
|
|
{
|
|
foreach (i; 0 .. _values.front.length)
|
|
{
|
|
_buf[i] = _values[0][i];
|
|
}
|
|
return _buf[0 .. _values.front.length];
|
|
}
|
|
void popFront()
|
|
{
|
|
_values = _values[1 .. $];
|
|
}
|
|
}
|
|
|
|
auto rr = TransientRange(["abc"d, "12"d, "def"d, "34"d]);
|
|
|
|
// Can't use array() or equal() directly because they fail with transient
|
|
// .front.
|
|
dchar[] result;
|
|
foreach (c; rr.joiner()) {
|
|
result ~= c;
|
|
}
|
|
|
|
assert(equal(result, "abc12def34"d),
|
|
"Unexpected result: '%s'"d.algoFormat(result));
|
|
}
|
|
|
|
// Issue 8061
|
|
unittest
|
|
{
|
|
import std.conv : to;
|
|
|
|
auto r = joiner([inputRangeObject("ab"), inputRangeObject("cd")]);
|
|
assert(isForwardRange!(typeof(r)));
|
|
|
|
auto str = to!string(r);
|
|
assert(str == "abcd");
|
|
}
|
|
|
|
// uniq
|
|
/**
|
|
Iterates unique consecutive elements of the given range (functionality
|
|
akin to the $(WEB wikipedia.org/wiki/_Uniq, _uniq) system
|
|
utility). Equivalence of elements is assessed by using the predicate
|
|
$(D pred), by default $(D "a == b"). If the given range is
|
|
bidirectional, $(D uniq) also yields a bidirectional range.
|
|
*/
|
|
auto uniq(alias pred = "a == b", Range)(Range r)
|
|
if (isInputRange!Range && is(typeof(binaryFun!pred(r.front, r.front)) == bool))
|
|
{
|
|
return UniqResult!(binaryFun!pred, Range)(r);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
|
|
assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ][]));
|
|
|
|
// Filter duplicates in-place using copy
|
|
arr.length -= arr.uniq().copy(arr).length;
|
|
assert(arr == [ 1, 2, 3, 4, 5 ]);
|
|
}
|
|
|
|
private struct UniqResult(alias pred, Range)
|
|
{
|
|
Range _input;
|
|
|
|
this(Range input)
|
|
{
|
|
_input = input;
|
|
}
|
|
|
|
auto opSlice()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
auto last = _input.front;
|
|
do
|
|
{
|
|
_input.popFront();
|
|
}
|
|
while (!_input.empty && pred(last, _input.front));
|
|
}
|
|
|
|
@property ElementType!Range front() { return _input.front; }
|
|
|
|
static if (isBidirectionalRange!Range)
|
|
{
|
|
void popBack()
|
|
{
|
|
auto last = _input.back;
|
|
do
|
|
{
|
|
_input.popBack();
|
|
}
|
|
while (!_input.empty && pred(last, _input.back));
|
|
}
|
|
|
|
@property ElementType!Range back() { return _input.back; }
|
|
}
|
|
|
|
static if (isInfinite!Range)
|
|
{
|
|
enum bool empty = false; // Propagate infiniteness.
|
|
}
|
|
else
|
|
{
|
|
@property bool empty() { return _input.empty; }
|
|
}
|
|
|
|
static if (isForwardRange!Range) {
|
|
@property typeof(this) save() {
|
|
return typeof(this)(_input.save);
|
|
}
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
|
|
auto r = uniq(arr);
|
|
static assert(isForwardRange!(typeof(r)));
|
|
|
|
assert(equal(r, [ 1, 2, 3, 4, 5 ][]));
|
|
assert(equal(retro(r), retro([ 1, 2, 3, 4, 5 ][])));
|
|
|
|
foreach (DummyType; AllDummyRanges) {
|
|
DummyType d;
|
|
auto u = uniq(d);
|
|
assert(equal(u, [1,2,3,4,5,6,7,8,9,10]));
|
|
|
|
static assert(d.rt == RangeType.Input || isForwardRange!(typeof(u)));
|
|
|
|
static if (d.rt >= RangeType.Bidirectional) {
|
|
assert(equal(retro(u), [10,9,8,7,6,5,4,3,2,1]));
|
|
}
|
|
}
|
|
}
|
|
|
|
// group
|
|
/**
|
|
Similarly to $(D uniq), $(D group) iterates unique consecutive
|
|
elements of the given range. The element type is $(D
|
|
Tuple!(ElementType!R, uint)) because it includes the count of
|
|
equivalent elements seen. Equivalence of elements is assessed by using
|
|
the predicate $(D pred), by default $(D "a == b").
|
|
|
|
$(D Group) is an input range if $(D R) is an input range, and a
|
|
forward range in all other cases.
|
|
*/
|
|
struct Group(alias pred, R) if (isInputRange!R)
|
|
{
|
|
private R _input;
|
|
private Tuple!(ElementType!R, uint) _current;
|
|
private alias comp = binaryFun!pred;
|
|
|
|
this(R input)
|
|
{
|
|
_input = input;
|
|
if (!_input.empty) popFront();
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
if (_input.empty)
|
|
{
|
|
_current[1] = 0;
|
|
}
|
|
else
|
|
{
|
|
_current = tuple(_input.front, 1u);
|
|
_input.popFront();
|
|
while (!_input.empty && comp(_current[0], _input.front))
|
|
{
|
|
++_current[1];
|
|
_input.popFront();
|
|
}
|
|
}
|
|
}
|
|
|
|
static if (isInfinite!R)
|
|
{
|
|
enum bool empty = false; // Propagate infiniteness.
|
|
}
|
|
else
|
|
{
|
|
@property bool empty()
|
|
{
|
|
return _current[1] == 0;
|
|
}
|
|
}
|
|
|
|
@property ref Tuple!(ElementType!R, uint) front()
|
|
{
|
|
assert(!empty);
|
|
return _current;
|
|
}
|
|
|
|
static if (isForwardRange!R) {
|
|
@property typeof(this) save() {
|
|
typeof(this) ret = this;
|
|
ret._input = this._input.save;
|
|
ret._current = this._current;
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
Group!(pred, Range) group(alias pred = "a == b", Range)(Range r)
|
|
{
|
|
return typeof(return)(r);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
|
|
assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u),
|
|
tuple(4, 3u), tuple(5, 1u) ][]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
|
|
assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u),
|
|
tuple(4, 3u), tuple(5, 1u) ][]));
|
|
static assert(isForwardRange!(typeof(group(arr))));
|
|
|
|
foreach (DummyType; AllDummyRanges) {
|
|
DummyType d;
|
|
auto g = group(d);
|
|
|
|
static assert(d.rt == RangeType.Input || isForwardRange!(typeof(g)));
|
|
|
|
assert(equal(g, [tuple(1, 1u), tuple(2, 1u), tuple(3, 1u), tuple(4, 1u),
|
|
tuple(5, 1u), tuple(6, 1u), tuple(7, 1u), tuple(8, 1u),
|
|
tuple(9, 1u), tuple(10, 1u)]));
|
|
}
|
|
}
|
|
|
|
|
|
// Used by implementation of groupBy.
|
|
private struct GroupByChunkImpl(alias equivFun, Range)
|
|
{
|
|
alias equiv = binaryFun!equivFun;
|
|
|
|
private Range r;
|
|
|
|
/* For forward ranges, using .save is more reliable than blindly assuming
|
|
* that the current value of .front will persist past a .popFront. However,
|
|
* if Range is only an input range, then we have no choice but to save the
|
|
* value of .front. */
|
|
static if (isForwardRange!Range)
|
|
{
|
|
private Range prev;
|
|
this(Range _r, Range _prev)
|
|
{
|
|
r = _r.save;
|
|
prev = _prev.save;
|
|
}
|
|
@property bool empty()
|
|
{
|
|
return r.empty || !equiv(prev.front, r.front);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
private ElementType!Range prev;
|
|
this(Range _r, ElementType!Range _prev)
|
|
{
|
|
r = _r;
|
|
prev = _prev;
|
|
}
|
|
@property bool empty()
|
|
{
|
|
return r.empty || !equiv(prev, r.front);
|
|
}
|
|
}
|
|
|
|
@property ElementType!Range front() { return r.front; }
|
|
|
|
void popFront()
|
|
in
|
|
{
|
|
import core.exception : RangeError;
|
|
if (r.empty) throw new RangeError();
|
|
}
|
|
body
|
|
{
|
|
r.popFront();
|
|
}
|
|
|
|
static if (isForwardRange!Range)
|
|
{
|
|
@property typeof(this) save()
|
|
{
|
|
typeof(this) copy;
|
|
copy.r = r.save;
|
|
copy.prev = prev.save;
|
|
return copy;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implementation of groupBy.
|
|
private struct GroupByImpl(alias equivFun, Range)
|
|
{
|
|
alias equiv = binaryFun!equivFun;
|
|
|
|
private Range r;
|
|
|
|
/* For forward ranges, using .save is more reliable than blindly assuming
|
|
* that the current value of .front will persist past a .popFront. However,
|
|
* if Range is only an input range, then we have no choice but to save the
|
|
* value of .front. */
|
|
static if (isForwardRange!Range)
|
|
{
|
|
private Range _prev;
|
|
private void savePrev() { _prev = r.save; }
|
|
private @property ElementType!Range prev() { return _prev.front; }
|
|
}
|
|
else
|
|
{
|
|
private ElementType!Range _prev;
|
|
private void savePrev() { _prev = r.front; }
|
|
private alias prev = _prev;
|
|
}
|
|
|
|
this(Range _r)
|
|
{
|
|
r = _r;
|
|
if (!empty)
|
|
savePrev();
|
|
}
|
|
@property bool empty() { return r.empty; }
|
|
|
|
@property auto front()
|
|
in
|
|
{
|
|
import core.exception : RangeError;
|
|
if (r.empty) throw new RangeError();
|
|
}
|
|
body
|
|
{
|
|
return GroupByChunkImpl!(equivFun, Range)(r, _prev);
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
while (!r.empty)
|
|
{
|
|
if (!equiv(prev, r.front))
|
|
{
|
|
savePrev();
|
|
return;
|
|
}
|
|
r.popFront();
|
|
}
|
|
}
|
|
|
|
static if (isForwardRange!Range)
|
|
{
|
|
@property typeof(this) save()
|
|
{
|
|
typeof(this) copy;
|
|
copy.r = r.save;
|
|
copy._prev = _prev.save;
|
|
return copy;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Chunks an input range into subranges of equivalent adjacent elements.
|
|
*
|
|
* Equivalence is defined by the predicate $(D equiv), which can be either
|
|
* binary or unary. In the binary form, two _range elements $(D a) and $(D b)
|
|
* are considered equivalent if $(D equiv(a,b)) is true. In unary form, two
|
|
* elements are considered equivalent if $(D equiv(a) == equiv(b)) is true.
|
|
*
|
|
* Params:
|
|
* equiv = Predicate for determining equivalence.
|
|
* r = The range to be chunked.
|
|
*
|
|
* Returns: A range of ranges in which all elements in a given subrange are
|
|
* equivalent under the given predicate.
|
|
*
|
|
* Notes:
|
|
*
|
|
* Equivalent elements separated by an intervening non-equivalent element will
|
|
* appear in separate subranges; this function only considers adjacent
|
|
* equivalence. Elements in the subranges will always appear in the same order
|
|
* they appear in the original range.
|
|
*
|
|
* Being an equivalence relation, the binary form of $(D equiv) is assumed to
|
|
* be reflexive (i.e., $(D equiv(a,a)) must be true). If not, unexpected
|
|
* results may be produced.
|
|
*
|
|
* See_also:
|
|
* $(XREF algorithm,group), which collapses adjacent equivalent elements into a
|
|
* single element.
|
|
*/
|
|
auto groupBy(alias equiv, Range)(Range r)
|
|
if (isInputRange!Range)
|
|
{
|
|
static if (is(typeof(binaryFun!equiv(ElementType!Range.init,
|
|
ElementType!Range.init)) : bool))
|
|
return GroupByImpl!(equiv, Range)(r);
|
|
else static if (is(typeof(
|
|
unaryFun!equiv(ElementType!Range.init) ==
|
|
unaryFun!equiv(ElementType!Range.init))))
|
|
return GroupByImpl!((a,b) => equiv(a) == equiv(b), Range)(r);
|
|
else
|
|
static assert(0, "groupBy expects either a binary predicate or "~
|
|
"a unary predicate on range elements of type: "~
|
|
ElementType!Range.stringof);
|
|
}
|
|
|
|
/// Showing usage with binary predicate:
|
|
@safe unittest
|
|
{
|
|
// Grouping by particular attribute of each element:
|
|
auto data = [
|
|
[1, 1],
|
|
[1, 2],
|
|
[2, 2],
|
|
[2, 3]
|
|
];
|
|
|
|
auto r1 = data.groupBy!((a,b) => a[0] == b[0]);
|
|
assert(r1.equal!equal([
|
|
[[1, 1], [1, 2]],
|
|
[[2, 2], [2, 3]]
|
|
]));
|
|
|
|
auto r2 = data.groupBy!((a,b) => a[1] == b[1]);
|
|
assert(r2.equal!equal([
|
|
[[1, 1]],
|
|
[[1, 2], [2, 2]],
|
|
[[2, 3]]
|
|
]));
|
|
}
|
|
|
|
/// Showing usage with unary predicate:
|
|
pure @safe nothrow unittest
|
|
{
|
|
// Grouping by particular attribute of each element:
|
|
auto range =
|
|
[
|
|
[1, 1],
|
|
[1, 1],
|
|
[1, 2],
|
|
[2, 2],
|
|
[2, 3],
|
|
[2, 3],
|
|
[3, 3]
|
|
];
|
|
|
|
auto byX = groupBy!(a => a[0])(range);
|
|
auto expected1 =
|
|
[
|
|
[[1, 1], [1, 1], [1, 2]],
|
|
[[2, 2], [2, 3], [2, 3]],
|
|
[[3, 3]]
|
|
];
|
|
foreach (e; byX)
|
|
{
|
|
assert(!expected1.empty);
|
|
assert(e.equal(expected1.front));
|
|
expected1.popFront();
|
|
}
|
|
|
|
auto byY = groupBy!(a => a[1])(range);
|
|
auto expected2 =
|
|
[
|
|
[[1, 1], [1, 1]],
|
|
[[1, 2], [2, 2]],
|
|
[[2, 3], [2, 3], [3, 3]]
|
|
];
|
|
foreach (e; byY)
|
|
{
|
|
assert(!expected2.empty);
|
|
assert(e.equal(expected2.front));
|
|
expected2.popFront();
|
|
}
|
|
}
|
|
|
|
pure @safe nothrow unittest
|
|
{
|
|
struct Item { int x, y; }
|
|
|
|
// Force R to have only an input range API with reference semantics, so
|
|
// that we're not unknowingly making use of array semantics outside of the
|
|
// range API.
|
|
class RefInputRange(R)
|
|
{
|
|
R data;
|
|
this(R _data) pure @safe nothrow { data = _data; }
|
|
@property bool empty() pure @safe nothrow { return data.empty; }
|
|
@property auto front() pure @safe nothrow { return data.front; }
|
|
void popFront() pure @safe nothrow { data.popFront(); }
|
|
}
|
|
auto refInputRange(R)(R range) { return new RefInputRange!R(range); }
|
|
|
|
{
|
|
auto arr = [ Item(1,2), Item(1,3), Item(2,3) ];
|
|
static assert(isForwardRange!(typeof(arr)));
|
|
|
|
auto byX = groupBy!(a => a.x)(arr);
|
|
static assert(isForwardRange!(typeof(byX)));
|
|
|
|
auto byX_subrange1 = byX.front.save;
|
|
auto byX_subrange2 = byX.front.save;
|
|
static assert(isForwardRange!(typeof(byX_subrange1)));
|
|
static assert(isForwardRange!(typeof(byX_subrange2)));
|
|
|
|
byX.popFront();
|
|
assert(byX_subrange1.equal([ Item(1,2), Item(1,3) ]));
|
|
byX_subrange1.popFront();
|
|
assert(byX_subrange1.equal([ Item(1,3) ]));
|
|
assert(byX_subrange2.equal([ Item(1,2), Item(1,3) ]));
|
|
|
|
auto byY = groupBy!(a => a.y)(arr);
|
|
static assert(isForwardRange!(typeof(byY)));
|
|
|
|
auto byY2 = byY.save;
|
|
static assert(is(typeof(byY) == typeof(byY2)));
|
|
byY.popFront();
|
|
assert(byY.front.equal([ Item(1,3), Item(2,3) ]));
|
|
assert(byY2.front.equal([ Item(1,2) ]));
|
|
}
|
|
|
|
// Test non-forward input ranges.
|
|
{
|
|
auto range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]);
|
|
auto byX = groupBy!(a => a.x)(range);
|
|
assert(byX.front.equal([ Item(1,1), Item(1,2) ]));
|
|
byX.popFront();
|
|
assert(byX.front.equal([ Item(2,2) ]));
|
|
byX.popFront();
|
|
assert(byX.empty);
|
|
assert(range.empty);
|
|
|
|
range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]);
|
|
auto byY = groupBy!(a => a.y)(range);
|
|
assert(byY.front.equal([ Item(1,1) ]));
|
|
byY.popFront();
|
|
assert(byY.front.equal([ Item(1,2), Item(2,2) ]));
|
|
byY.popFront();
|
|
assert(byY.empty);
|
|
assert(range.empty);
|
|
}
|
|
}
|
|
|
|
|
|
// 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 ]);
|
|
|
|
// }
|
|
|
|
// find
|
|
/**
|
|
Finds an individual element in an input range. Elements of $(D
|
|
haystack) are compared with $(D needle) by using predicate $(D
|
|
pred). Performs $(BIGOH walkLength(haystack)) evaluations of $(D
|
|
pred).
|
|
|
|
To _find the last occurrence of $(D needle) in $(D haystack), call $(D
|
|
find(retro(haystack), needle)). See $(XREF range, retro).
|
|
|
|
Params:
|
|
|
|
haystack = The range searched in.
|
|
|
|
needle = The element searched for.
|
|
|
|
Constraints:
|
|
|
|
$(D isInputRange!InputRange && is(typeof(binaryFun!pred(haystack.front, needle)
|
|
: bool)))
|
|
|
|
Returns:
|
|
|
|
$(D haystack) advanced such that $(D binaryFun!pred(haystack.front,
|
|
needle)) is $(D true) (if no such position exists, returns $(D
|
|
haystack) after exhaustion).
|
|
|
|
See_Also:
|
|
$(WEB sgi.com/tech/stl/_find.html, STL's _find)
|
|
*/
|
|
InputRange find(alias pred = "a == b", InputRange, Element)(InputRange haystack, Element needle)
|
|
if (isInputRange!InputRange &&
|
|
is (typeof(binaryFun!pred(haystack.front, needle)) : bool))
|
|
{
|
|
alias R = InputRange;
|
|
alias E = Element;
|
|
alias predFun = binaryFun!pred;
|
|
static if (is(typeof(pred == "a == b")))
|
|
enum isDefaultPred = pred == "a == b";
|
|
else
|
|
enum isDefaultPred = false;
|
|
enum isIntegralNeedle = isSomeChar!E || isIntegral!E || isBoolean!E;
|
|
|
|
alias EType = ElementType!R;
|
|
|
|
static if (isNarrowString!R)
|
|
{
|
|
alias EEType = ElementEncodingType!R;
|
|
alias UEEType = Unqual!EEType;
|
|
|
|
//These are two special cases which can search without decoding the UTF stream.
|
|
static if (isDefaultPred && isIntegralNeedle)
|
|
{
|
|
import std.utf : canSearchInCodeUnits;
|
|
|
|
//This special case deals with UTF8 search, when the needle
|
|
//is represented by a single code point.
|
|
//Note: "needle <= 0x7F" properly handles sign via unsigned promotion
|
|
static if (is(UEEType == char))
|
|
{
|
|
if (!__ctfe && canSearchInCodeUnits!char(needle))
|
|
{
|
|
static R trustedMemchr(ref R haystack, ref E needle) @trusted nothrow pure
|
|
{
|
|
import core.stdc.string : memchr;
|
|
auto ptr = memchr(haystack.ptr, needle, haystack.length);
|
|
return ptr ?
|
|
haystack[ptr - haystack.ptr .. $] :
|
|
haystack[$ .. $];
|
|
}
|
|
return trustedMemchr(haystack, needle);
|
|
}
|
|
}
|
|
|
|
//Ditto, but for UTF16
|
|
static if (is(UEEType == wchar))
|
|
{
|
|
if (canSearchInCodeUnits!wchar(needle))
|
|
{
|
|
foreach (i, ref EEType e; haystack)
|
|
{
|
|
if (e == needle)
|
|
return haystack[i .. $];
|
|
}
|
|
return haystack[$ .. $];
|
|
}
|
|
}
|
|
}
|
|
|
|
//Previous conditonal optimizations did not succeed. Fallback to
|
|
//unconditional implementations
|
|
static if (isDefaultPred)
|
|
{
|
|
import std.utf : encode;
|
|
|
|
//In case of default pred, it is faster to do string/string search.
|
|
UEEType[is(UEEType == char) ? 4 : 2] buf;
|
|
|
|
size_t len = encode(buf, needle);
|
|
return find(haystack, buf[0 .. len]);
|
|
}
|
|
else
|
|
{
|
|
import std.utf : decode;
|
|
|
|
//Explicit pred: we must test each character by the book.
|
|
//We choose a manual decoding approach, because it is faster than
|
|
//the built-in foreach, or doing a front/popFront for-loop.
|
|
immutable len = haystack.length;
|
|
size_t i = 0, next = 0;
|
|
while (next < len)
|
|
{
|
|
if (predFun(decode(haystack, next), needle))
|
|
return haystack[i .. $];
|
|
i = next;
|
|
}
|
|
return haystack[$ .. $];
|
|
}
|
|
}
|
|
else static if (isArray!R)
|
|
{
|
|
//10403 optimization
|
|
static if (isDefaultPred && isIntegral!EType && EType.sizeof == 1 && isIntegralNeedle)
|
|
{
|
|
R findHelper(ref R haystack, ref E needle) @trusted nothrow pure
|
|
{
|
|
import core.stdc.string : memchr;
|
|
|
|
EType* ptr = null;
|
|
//Note: we use "min/max" to handle sign mismatch.
|
|
if (min(EType.min, needle) == EType.min &&
|
|
max(EType.max, needle) == EType.max)
|
|
{
|
|
ptr = cast(EType*) memchr(haystack.ptr, needle,
|
|
haystack.length);
|
|
}
|
|
|
|
return ptr ?
|
|
haystack[ptr - haystack.ptr .. $] :
|
|
haystack[$ .. $];
|
|
}
|
|
|
|
if (!__ctfe)
|
|
return findHelper(haystack, needle);
|
|
}
|
|
|
|
//Default implementation.
|
|
foreach (i, ref e; haystack)
|
|
if (predFun(e, needle))
|
|
return haystack[i .. $];
|
|
return haystack[$ .. $];
|
|
}
|
|
else
|
|
{
|
|
//Everything else. Walk.
|
|
for ( ; !haystack.empty; haystack.popFront() )
|
|
{
|
|
if (predFun(haystack.front, needle))
|
|
break;
|
|
}
|
|
return haystack;
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.container : SList;
|
|
|
|
assert(find("hello, world", ',') == ", world");
|
|
assert(find([1, 2, 3, 5], 4) == []);
|
|
assert(equal(find(SList!int(1, 2, 3, 4, 5)[], 4), SList!int(4, 5)[]));
|
|
assert(find!"a > b"([1, 2, 3, 5], 2) == [3, 5]);
|
|
|
|
auto a = [ 1, 2, 3 ];
|
|
assert(find(a, 5).empty); // not found
|
|
assert(!find(a, 2).empty); // found
|
|
|
|
// Case-insensitive find of a string
|
|
string[] s = [ "Hello", "world", "!" ];
|
|
assert(!find!("toLower(a) == b")(s, "hello").empty);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.container : SList;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
auto lst = SList!int(1, 2, 5, 7, 3);
|
|
assert(lst.front == 1);
|
|
auto r = find(lst[], 5);
|
|
assert(equal(r, SList!int(5, 7, 3)[]));
|
|
assert(find([1, 2, 3, 5], 4).empty);
|
|
assert(equal(find!"a>b"("hello", 'k'), "llo"));
|
|
}
|
|
|
|
@safe pure nothrow unittest
|
|
{
|
|
int[] a1 = [1, 2, 3];
|
|
assert(!find ([1, 2, 3], 2).empty);
|
|
assert(!find!((a,b)=>a==b)([1, 2, 3], 2).empty);
|
|
ubyte[] a2 = [1, 2, 3];
|
|
ubyte b2 = 2;
|
|
assert(!find ([1, 2, 3], 2).empty);
|
|
assert(!find!((a,b)=>a==b)([1, 2, 3], 2).empty);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
foreach(R; TypeTuple!(string, wstring, dstring))
|
|
{
|
|
foreach(E; TypeTuple!(char, wchar, dchar))
|
|
{
|
|
R r1 = "hello world";
|
|
E e1 = 'w';
|
|
assert(find ("hello world", 'w') == "world");
|
|
assert(find!((a,b)=>a==b)("hello world", 'w') == "world");
|
|
R r2 = "日c語";
|
|
E e2 = 'c';
|
|
assert(find ("日c語", 'c') == "c語");
|
|
assert(find!((a,b)=>a==b)("日c語", 'c') == "c語");
|
|
static if (E.sizeof >= 2)
|
|
{
|
|
R r3 = "hello world";
|
|
E e3 = 'w';
|
|
assert(find ("日本語", '本') == "本語");
|
|
assert(find!((a,b)=>a==b)("日本語", '本') == "本語");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//CTFE
|
|
static assert (find("abc", 'b') == "bc");
|
|
static assert (find("日b語", 'b') == "b語");
|
|
static assert (find("日本語", '本') == "本語");
|
|
static assert (find([1, 2, 3], 2) == [2, 3]);
|
|
|
|
int[] a1 = [1, 2, 3];
|
|
static assert(find ([1, 2, 3], 2));
|
|
static assert(find!((a,b)=>a==b)([1, 2, 3], 2));
|
|
ubyte[] a2 = [1, 2, 3];
|
|
ubyte b2 = 2;
|
|
static assert(find ([1, 2, 3], 2));
|
|
static assert(find!((a,b)=>a==b)([1, 2, 3], 2));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertCTFEable;
|
|
|
|
void dg() @safe pure nothrow
|
|
{
|
|
byte[] sarr = [1, 2, 3, 4];
|
|
ubyte[] uarr = [1, 2, 3, 4];
|
|
foreach(arr; TypeTuple!(sarr, uarr))
|
|
{
|
|
foreach(T; TypeTuple!(byte, ubyte, int, uint))
|
|
{
|
|
assert(find(arr, cast(T) 3) == arr[2 .. $]);
|
|
assert(find(arr, cast(T) 9) == arr[$ .. $]);
|
|
}
|
|
assert(find(arr, 256) == arr[$ .. $]);
|
|
}
|
|
}
|
|
dg();
|
|
assertCTFEable!dg;
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Bugzilla 11603
|
|
enum Foo : ubyte { A }
|
|
assert([Foo.A].find(Foo.A).empty == false);
|
|
|
|
ubyte x = 0;
|
|
assert([x].find(x).empty == false);
|
|
}
|
|
|
|
/**
|
|
Finds a forward range in another. Elements are compared for
|
|
equality. Performs $(BIGOH walkLength(haystack) * walkLength(needle))
|
|
comparisons in the worst case. Specializations taking advantage of
|
|
bidirectional or random access (where present) may accelerate search
|
|
depending on the statistics of the two ranges' content.
|
|
|
|
Params:
|
|
|
|
haystack = The range searched in.
|
|
|
|
needle = The range searched for.
|
|
|
|
Constraints:
|
|
|
|
$(D isForwardRange!R1 && isForwardRange!R2 &&
|
|
is(typeof(binaryFun!pred(haystack.front, needle.front) : bool)))
|
|
|
|
Returns:
|
|
|
|
$(D haystack) advanced such that $(D needle) is a prefix of it (if no
|
|
such position exists, returns $(D haystack) advanced to termination).
|
|
*/
|
|
R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
|
|
if (isForwardRange!R1 && isForwardRange!R2
|
|
&& is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)
|
|
&& !isRandomAccessRange!R1)
|
|
{
|
|
static if (is(typeof(pred == "a == b")) && pred == "a == b" && isSomeString!R1 && isSomeString!R2
|
|
&& haystack[0].sizeof == needle[0].sizeof)
|
|
{
|
|
//return cast(R1) find(representation(haystack), representation(needle));
|
|
// Specialization for simple string search
|
|
alias Representation =
|
|
Select!(haystack[0].sizeof == 1, ubyte[],
|
|
Select!(haystack[0].sizeof == 2, ushort[], uint[]));
|
|
// Will use the array specialization
|
|
static TO force(TO, T)(T r) @trusted { return cast(TO)r; }
|
|
return force!R1(.find!(pred, Representation, Representation)
|
|
(force!Representation(haystack), force!Representation(needle)));
|
|
}
|
|
else
|
|
{
|
|
return simpleMindedFind!pred(haystack, needle);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.container : SList;
|
|
|
|
assert(find("hello, world", "World").empty);
|
|
assert(find("hello, world", "wo") == "world");
|
|
assert([1, 2, 3, 4].find(SList!int(2, 3)[]) == [2, 3, 4]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.container : SList;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
auto lst = SList!int(1, 2, 5, 7, 3);
|
|
static assert(isForwardRange!(int[]));
|
|
static assert(isForwardRange!(typeof(lst[])));
|
|
auto r = find(lst[], [2, 5]);
|
|
assert(equal(r, SList!int(2, 5, 7, 3)[]));
|
|
}
|
|
|
|
// Specialization for searching a random-access range for a
|
|
// bidirectional range
|
|
R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
|
|
if (isRandomAccessRange!R1 && isBidirectionalRange!R2
|
|
&& is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
|
|
{
|
|
if (needle.empty) return haystack;
|
|
const needleLength = walkLength(needle.save);
|
|
if (needleLength > haystack.length)
|
|
{
|
|
// @@@BUG@@@
|
|
//return haystack[$ .. $];
|
|
return haystack[haystack.length .. haystack.length];
|
|
}
|
|
// @@@BUG@@@
|
|
// auto needleBack = moveBack(needle);
|
|
// Stage 1: find the step
|
|
size_t step = 1;
|
|
auto needleBack = needle.back;
|
|
needle.popBack();
|
|
for (auto i = needle.save; !i.empty && !binaryFun!pred(i.back, needleBack);
|
|
i.popBack(), ++step)
|
|
{
|
|
}
|
|
// Stage 2: linear find
|
|
size_t scout = needleLength - 1;
|
|
for (;;)
|
|
{
|
|
if (scout >= haystack.length)
|
|
{
|
|
return haystack[haystack.length .. haystack.length];
|
|
}
|
|
if (!binaryFun!pred(haystack[scout], needleBack))
|
|
{
|
|
++scout;
|
|
continue;
|
|
}
|
|
// Found a match with the last element in the needle
|
|
auto cand = haystack[scout + 1 - needleLength .. haystack.length];
|
|
if (startsWith!pred(cand, needle))
|
|
{
|
|
// found
|
|
return cand;
|
|
}
|
|
// Continue with the stride
|
|
scout += step;
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
// @@@BUG@@@ removing static below makes unittest fail
|
|
static struct BiRange
|
|
{
|
|
int[] payload;
|
|
@property bool empty() { return payload.empty; }
|
|
@property BiRange save() { return this; }
|
|
@property ref int front() { return payload[0]; }
|
|
@property ref int back() { return payload[$ - 1]; }
|
|
void popFront() { return payload.popFront(); }
|
|
void popBack() { return payload.popBack(); }
|
|
}
|
|
//static assert(isBidirectionalRange!BiRange);
|
|
auto r = BiRange([1, 2, 3, 10, 11, 4]);
|
|
//assert(equal(find(r, [3, 10]), BiRange([3, 10, 11, 4])));
|
|
//assert(find("abc", "bc").length == 2);
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
//assert(find!"a == b"("abc", "bc").length == 2);
|
|
}
|
|
|
|
// Leftover specialization: searching a random-access range for a
|
|
// non-bidirectional forward range
|
|
R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
|
|
if (isRandomAccessRange!R1 && isForwardRange!R2 && !isBidirectionalRange!R2 &&
|
|
is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
|
|
{
|
|
static if (!is(ElementType!R1 == ElementType!R2))
|
|
{
|
|
return simpleMindedFind!pred(haystack, needle);
|
|
}
|
|
else
|
|
{
|
|
// Prepare the search with needle's first element
|
|
if (needle.empty)
|
|
return haystack;
|
|
|
|
haystack = .find!pred(haystack, needle.front);
|
|
|
|
static if (hasLength!R1 && hasLength!R2 && is(typeof(takeNone(haystack)) == R1))
|
|
{
|
|
if (needle.length > haystack.length)
|
|
return takeNone(haystack);
|
|
}
|
|
else
|
|
{
|
|
if (haystack.empty)
|
|
return haystack;
|
|
}
|
|
|
|
needle.popFront();
|
|
size_t matchLen = 1;
|
|
|
|
// Loop invariant: haystack[0 .. matchLen] matches everything in
|
|
// the initial needle that was popped out of needle.
|
|
for (;;)
|
|
{
|
|
// Extend matchLength as much as possible
|
|
for (;;)
|
|
{
|
|
if (needle.empty || haystack.empty)
|
|
return haystack;
|
|
|
|
static if (hasLength!R1 && is(typeof(takeNone(haystack)) == R1))
|
|
{
|
|
if (matchLen == haystack.length)
|
|
return takeNone(haystack);
|
|
}
|
|
|
|
if (!binaryFun!pred(haystack[matchLen], needle.front))
|
|
break;
|
|
|
|
++matchLen;
|
|
needle.popFront();
|
|
}
|
|
|
|
auto bestMatch = haystack[0 .. matchLen];
|
|
haystack.popFront();
|
|
haystack = .find!pred(haystack, bestMatch);
|
|
}
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.container : SList;
|
|
|
|
assert(find([ 1, 2, 3 ], SList!int(2, 3)[]) == [ 2, 3 ]);
|
|
assert(find([ 1, 2, 1, 2, 3, 3 ], SList!int(2, 3)[]) == [ 2, 3, 3 ]);
|
|
}
|
|
|
|
//Bug# 8334
|
|
@safe unittest
|
|
{
|
|
auto haystack = [1, 2, 3, 4, 1, 9, 12, 42];
|
|
auto needle = [12, 42, 27];
|
|
|
|
//different overload of find, but it's the base case.
|
|
assert(find(haystack, needle).empty);
|
|
|
|
assert(find(haystack, takeExactly(filter!"true"(needle), 3)).empty);
|
|
assert(find(haystack, filter!"true"(needle)).empty);
|
|
}
|
|
|
|
// Internally used by some find() overloads above. Can't make it
|
|
// private due to bugs in the compiler.
|
|
/*private*/ R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, R2 needle)
|
|
{
|
|
enum estimateNeedleLength = hasLength!R1 && !hasLength!R2;
|
|
|
|
static if (hasLength!R1)
|
|
{
|
|
static if (hasLength!R2)
|
|
size_t estimatedNeedleLength = 0;
|
|
else
|
|
immutable size_t estimatedNeedleLength = needle.length;
|
|
}
|
|
|
|
bool haystackTooShort()
|
|
{
|
|
static if (estimateNeedleLength)
|
|
{
|
|
return haystack.length < estimatedNeedleLength;
|
|
}
|
|
else
|
|
{
|
|
return haystack.empty;
|
|
}
|
|
}
|
|
|
|
searching:
|
|
for (;; haystack.popFront())
|
|
{
|
|
if (haystackTooShort())
|
|
{
|
|
// Failed search
|
|
static if (hasLength!R1)
|
|
{
|
|
static if (is(typeof(haystack[haystack.length ..
|
|
haystack.length]) : R1))
|
|
return haystack[haystack.length .. haystack.length];
|
|
else
|
|
return R1.init;
|
|
}
|
|
else
|
|
{
|
|
assert(haystack.empty);
|
|
return haystack;
|
|
}
|
|
}
|
|
static if (estimateNeedleLength)
|
|
size_t matchLength = 0;
|
|
for (auto h = haystack.save, n = needle.save;
|
|
!n.empty;
|
|
h.popFront(), n.popFront())
|
|
{
|
|
if (h.empty || !binaryFun!pred(h.front, n.front))
|
|
{
|
|
// Failed searching n in h
|
|
static if (estimateNeedleLength)
|
|
{
|
|
if (estimatedNeedleLength < matchLength)
|
|
estimatedNeedleLength = matchLength;
|
|
}
|
|
continue searching;
|
|
}
|
|
static if (estimateNeedleLength)
|
|
++matchLength;
|
|
}
|
|
break;
|
|
}
|
|
return haystack;
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Test simpleMindedFind for the case where both haystack and needle have
|
|
// length.
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
struct CustomString
|
|
{
|
|
@safe:
|
|
string _impl;
|
|
|
|
// This is what triggers issue 7992.
|
|
@property size_t length() const { return _impl.length; }
|
|
@property void length(size_t len) { _impl.length = len; }
|
|
|
|
// This is for conformance to the forward range API (we deliberately
|
|
// make it non-random access so that we will end up in
|
|
// simpleMindedFind).
|
|
@property bool empty() const { return _impl.empty; }
|
|
@property dchar front() const { return _impl.front; }
|
|
void popFront() { _impl.popFront(); }
|
|
@property CustomString save() { return this; }
|
|
}
|
|
|
|
// If issue 7992 occurs, this will throw an exception from calling
|
|
// popFront() on an empty range.
|
|
auto r = find(CustomString("a"), CustomString("b"));
|
|
}
|
|
|
|
/**
|
|
Finds two or more $(D needles) into a $(D haystack). The predicate $(D
|
|
pred) is used throughout to compare elements. By default, elements are
|
|
compared for equality.
|
|
|
|
$(D BoyerMooreFinder) allocates GC memory.
|
|
|
|
Params:
|
|
|
|
haystack = The target of the search. Must be an $(GLOSSARY input
|
|
range). If any of $(D needles) is a range with elements comparable to
|
|
elements in $(D haystack), then $(D haystack) must be a $(GLOSSARY
|
|
forward range) such that the search can backtrack.
|
|
|
|
needles = One or more items to search for. Each of $(D needles) must
|
|
be either comparable to one element in $(D haystack), or be itself a
|
|
$(GLOSSARY forward range) with elements comparable with elements in
|
|
$(D haystack).
|
|
|
|
Returns:
|
|
|
|
A tuple containing $(D haystack) positioned to match one of the
|
|
needles and also the 1-based index of the matching element in $(D
|
|
needles) (0 if none of $(D needles) matched, 1 if $(D needles[0])
|
|
matched, 2 if $(D needles[1]) matched...). The first needle to be found
|
|
will be the one that matches. If multiple needles are found at the
|
|
same spot in the range, then the shortest one is the one which matches
|
|
(if multiple needles of the same length are found at the same spot (e.g
|
|
$(D "a") and $(D 'a')), then the left-most of them in the argument list
|
|
matches).
|
|
|
|
The relationship between $(D haystack) and $(D needles) simply means
|
|
that one can e.g. search for individual $(D int)s or arrays of $(D
|
|
int)s in an array of $(D int)s. In addition, if elements are
|
|
individually comparable, searches of heterogeneous types are allowed
|
|
as well: a $(D double[]) can be searched for an $(D int) or a $(D
|
|
short[]), and conversely a $(D long) can be searched for a $(D float)
|
|
or a $(D double[]). This makes for efficient searches without the need
|
|
to coerce one side of the comparison into the other's side type.
|
|
|
|
The complexity of the search is $(BIGOH haystack.length *
|
|
max(needles.length)). (For needles that are individual items, length
|
|
is considered to be 1.) The strategy used in searching several
|
|
subranges at once maximizes cache usage by moving in $(D haystack) as
|
|
few times as possible.
|
|
*/
|
|
Tuple!(Range, size_t) find(alias pred = "a == b", Range, Ranges...)
|
|
(Range haystack, Ranges needles)
|
|
if (Ranges.length > 1 && is(typeof(startsWith!pred(haystack, needles))))
|
|
{
|
|
for (;; haystack.popFront())
|
|
{
|
|
size_t r = startsWith!pred(haystack, needles);
|
|
if (r || haystack.empty)
|
|
{
|
|
return tuple(haystack, r);
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 4, 2, 3 ];
|
|
assert(find(a, 4) == [ 4, 2, 3 ]);
|
|
assert(find(a, [ 1, 4 ]) == [ 1, 4, 2, 3 ]);
|
|
assert(find(a, [ 1, 3 ], 4) == tuple([ 4, 2, 3 ], 2));
|
|
// Mixed types allowed if comparable
|
|
assert(find(a, 5, [ 1.2, 3.5 ], 2.0) == tuple([ 2, 3 ], 3));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
auto s1 = "Mary has a little lamb";
|
|
//writeln(find(s1, "has a", "has an"));
|
|
assert(find(s1, "has a", "has an") == tuple("has a little lamb", 1));
|
|
assert(find(s1, 't', "has a", "has an") == tuple("has a little lamb", 2));
|
|
assert(find(s1, 't', "has a", 'y', "has an") == tuple("y has a little lamb", 3));
|
|
assert(find("abc", "bc").length == 2);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.string : toUpper;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
int[] a = [ 1, 2, 3 ];
|
|
assert(find(a, 5).empty);
|
|
assert(find(a, 2) == [2, 3]);
|
|
|
|
foreach (T; TypeTuple!(int, double))
|
|
{
|
|
auto b = rndstuff!(T)();
|
|
if (!b.length) continue;
|
|
b[$ / 2] = 200;
|
|
b[$ / 4] = 200;
|
|
assert(find(b, 200).length == b.length - b.length / 4);
|
|
}
|
|
|
|
// Case-insensitive find of a string
|
|
string[] s = [ "Hello", "world", "!" ];
|
|
//writeln(find!("toUpper(a) == toUpper(b)")(s, "hello"));
|
|
assert(find!("toUpper(a) == toUpper(b)")(s, "hello").length == 3);
|
|
|
|
static bool f(string a, string b) { return toUpper(a) == toUpper(b); }
|
|
assert(find!(f)(s, "hello").length == 3);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
int[] a = [ 1, 2, 3, 2, 6 ];
|
|
assert(find(std.range.retro(a), 5).empty);
|
|
assert(equal(find(std.range.retro(a), 2), [ 2, 3, 2, 1 ][]));
|
|
|
|
foreach (T; TypeTuple!(int, double))
|
|
{
|
|
auto b = rndstuff!(T)();
|
|
if (!b.length) continue;
|
|
b[$ / 2] = 200;
|
|
b[$ / 4] = 200;
|
|
assert(find(std.range.retro(b), 200).length ==
|
|
b.length - (b.length - 1) / 2);
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ -1, 0, 1, 2, 3, 4, 5 ];
|
|
int[] b = [ 1, 2, 3 ];
|
|
assert(find(a, b) == [ 1, 2, 3, 4, 5 ]);
|
|
assert(find(b, a).empty);
|
|
|
|
foreach (DummyType; AllDummyRanges) {
|
|
DummyType d;
|
|
auto findRes = find(d, 5);
|
|
assert(equal(findRes, [5,6,7,8,9,10]));
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
struct BoyerMooreFinder(alias pred, Range)
|
|
{
|
|
private:
|
|
size_t[] skip; // GC allocated
|
|
ptrdiff_t[ElementType!(Range)] occ; // GC allocated
|
|
Range needle;
|
|
|
|
ptrdiff_t occurrence(ElementType!(Range) 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)
|
|
{
|
|
ptrdiff_t virtual_begin = needle.length - offset - portion;
|
|
ptrdiff_t 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;
|
|
|
|
immutable delta = portion - ignore;
|
|
return equal(needle[needle.length - delta .. needle.length],
|
|
needle[virtual_begin .. virtual_begin + delta]);
|
|
}
|
|
|
|
public:
|
|
this(Range needle)
|
|
{
|
|
if (!needle.length) return;
|
|
this.needle = needle;
|
|
/* Populate table with the analysis of the needle */
|
|
/* But ignoring the last letter */
|
|
foreach (i, n ; needle[0 .. $ - 1])
|
|
{
|
|
this.occ[n] = i;
|
|
}
|
|
/* Preprocess #2: init skip[] */
|
|
/* Note: This step could be made a lot faster.
|
|
* A simple implementation is shown here. */
|
|
this.skip = new size_t[needle.length];
|
|
foreach (a; 0 .. needle.length)
|
|
{
|
|
size_t value = 0;
|
|
while (value < needle.length
|
|
&& !needlematch(needle, a, value))
|
|
{
|
|
++value;
|
|
}
|
|
this.skip[needle.length - a - 1] = value;
|
|
}
|
|
}
|
|
|
|
Range beFound(Range haystack)
|
|
{
|
|
if (!needle.length) return haystack;
|
|
if (needle.length > haystack.length) return 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 haystack[hpos .. $];
|
|
--npos;
|
|
}
|
|
hpos += max(skip[npos], cast(sizediff_t) npos - occurrence(haystack[npos+hpos]));
|
|
}
|
|
return haystack[$ .. $];
|
|
}
|
|
|
|
@property size_t length()
|
|
{
|
|
return needle.length;
|
|
}
|
|
|
|
alias opDollar = length;
|
|
}
|
|
|
|
/// Ditto
|
|
BoyerMooreFinder!(binaryFun!(pred), Range) boyerMooreFinder
|
|
(alias pred = "a == b", Range)
|
|
(Range needle) if (isRandomAccessRange!(Range) || isSomeString!Range)
|
|
{
|
|
return typeof(return)(needle);
|
|
}
|
|
|
|
// Oddly this is not disabled by bug 4759
|
|
Range1 find(Range1, alias pred, Range2)(
|
|
Range1 haystack, BoyerMooreFinder!(pred, Range2) needle)
|
|
{
|
|
return needle.beFound(haystack);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
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 = find(h, boyerMooreFinder(n));
|
|
assert(!p.empty);
|
|
}
|
|
|
|
int[] a = [ -1, 0, 1, 2, 3, 4, 5 ];
|
|
int[] b = [ 1, 2, 3 ];
|
|
//writeln(find(a, boyerMooreFinder(b)));
|
|
assert(find(a, boyerMooreFinder(b)) == [ 1, 2, 3, 4, 5 ]);
|
|
assert(find(b, boyerMooreFinder(a)).empty);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto bm = boyerMooreFinder("for");
|
|
auto match = find("Moor", bm);
|
|
assert(match.empty);
|
|
}
|
|
|
|
/**
|
|
Advances the input range $(D haystack) by calling $(D haystack.popFront)
|
|
until either $(D pred(haystack.front)), or $(D
|
|
haystack.empty). Performs $(BIGOH haystack.length) evaluations of $(D
|
|
pred).
|
|
|
|
To find the last element of a bidirectional $(D haystack) satisfying
|
|
$(D pred), call $(D find!(pred)(retro(haystack))). See $(XREF
|
|
range, retro).
|
|
|
|
See_Also:
|
|
$(WEB sgi.com/tech/stl/find_if.html, STL's find_if)
|
|
*/
|
|
InputRange find(alias pred, InputRange)(InputRange haystack)
|
|
if (isInputRange!InputRange)
|
|
{
|
|
alias R = InputRange;
|
|
alias predFun = unaryFun!pred;
|
|
static if (isNarrowString!R)
|
|
{
|
|
import std.utf : decode;
|
|
|
|
immutable len = haystack.length;
|
|
size_t i = 0, next = 0;
|
|
while (next < len)
|
|
{
|
|
if (predFun(decode(haystack, next)))
|
|
return haystack[i .. $];
|
|
i = next;
|
|
}
|
|
return haystack[$ .. $];
|
|
}
|
|
else static if (!isInfinite!R && hasSlicing!R && is(typeof(haystack[cast(size_t)0 .. $])))
|
|
{
|
|
size_t i = 0;
|
|
foreach (ref e; haystack)
|
|
{
|
|
if (predFun(e))
|
|
return haystack[i .. $];
|
|
++i;
|
|
}
|
|
return haystack[$ .. $];
|
|
}
|
|
else
|
|
{
|
|
//standard range
|
|
for ( ; !haystack.empty; haystack.popFront() )
|
|
{
|
|
if (predFun(haystack.front))
|
|
break;
|
|
}
|
|
return haystack;
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto arr = [ 1, 2, 3, 4, 1 ];
|
|
assert(find!("a > 2")(arr) == [ 3, 4, 1 ]);
|
|
|
|
// with predicate alias
|
|
bool pred(int x) { return x + 1 > 1.5; }
|
|
assert(find!(pred)(arr) == arr);
|
|
}
|
|
|
|
@safe pure unittest
|
|
{
|
|
//scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] r = [ 1, 2, 3 ];
|
|
assert(find!(a=>a > 2)(r) == [3]);
|
|
bool pred(int x) { return x + 1 > 1.5; }
|
|
assert(find!(pred)(r) == r);
|
|
|
|
assert(find!(a=>a > 'v')("hello world") == "world");
|
|
assert(find!(a=>a%4 == 0)("日本語") == "本語");
|
|
}
|
|
|
|
// findSkip
|
|
/**
|
|
* If $(D needle) occurs in $(D haystack), positions $(D haystack)
|
|
* right after the first occurrence of $(D needle) and returns $(D
|
|
* true). Otherwise, leaves $(D haystack) as is and returns $(D
|
|
* false).
|
|
*/
|
|
bool findSkip(alias pred = "a == b", R1, R2)(ref R1 haystack, R2 needle)
|
|
if (isForwardRange!R1 && isForwardRange!R2
|
|
&& is(typeof(binaryFun!pred(haystack.front, needle.front))))
|
|
{
|
|
auto parts = findSplit!pred(haystack, needle);
|
|
if (parts[1].empty) return false;
|
|
// found
|
|
haystack = parts[2];
|
|
return true;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
string s = "abcdef";
|
|
assert(findSkip(s, "cd") && s == "ef");
|
|
s = "abcdef";
|
|
assert(!findSkip(s, "cxd") && s == "abcdef");
|
|
s = "abcdef";
|
|
assert(findSkip(s, "def") && s.empty);
|
|
}
|
|
|
|
/**
|
|
These functions find the first occurrence of $(D needle) in $(D
|
|
haystack) and then split $(D haystack) as follows.
|
|
|
|
$(D findSplit) returns a tuple $(D result) containing $(I three)
|
|
ranges. $(D result[0]) is the portion of $(D haystack) before $(D
|
|
needle), $(D result[1]) is the portion of $(D haystack) that matches
|
|
$(D needle), and $(D result[2]) is the portion of $(D haystack) after
|
|
the match. If $(D needle) was not found, $(D result[0])
|
|
comprehends $(D haystack) entirely and $(D result[1]) and $(D result[2])
|
|
are empty.
|
|
|
|
$(D findSplitBefore) returns a tuple $(D result) containing two
|
|
ranges. $(D result[0]) is the portion of $(D haystack) before $(D
|
|
needle), and $(D result[1]) is the balance of $(D haystack) starting
|
|
with the match. If $(D needle) was not found, $(D result[0])
|
|
comprehends $(D haystack) entirely and $(D result[1]) is empty.
|
|
|
|
$(D findSplitAfter) returns a tuple $(D result) containing two ranges.
|
|
$(D result[0]) is the portion of $(D haystack) up to and including the
|
|
match, and $(D result[1]) is the balance of $(D haystack) starting
|
|
after the match. If $(D needle) was not found, $(D result[0]) is empty
|
|
and $(D result[1]) is $(D haystack).
|
|
|
|
In all cases, the concatenation of the returned ranges spans the
|
|
entire $(D haystack).
|
|
|
|
If $(D haystack) is a random-access range, all three components of the
|
|
tuple have the same type as $(D haystack). Otherwise, $(D haystack)
|
|
must be a forward range and the type of $(D result[0]) and $(D
|
|
result[1]) is the same as $(XREF range,takeExactly).
|
|
*/
|
|
auto findSplit(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
|
|
if (isForwardRange!R1 && isForwardRange!R2)
|
|
{
|
|
static if (isSomeString!R1 && isSomeString!R2
|
|
|| isRandomAccessRange!R1 && hasLength!R2)
|
|
{
|
|
auto balance = find!pred(haystack, needle);
|
|
immutable pos1 = haystack.length - balance.length;
|
|
immutable pos2 = balance.empty ? pos1 : pos1 + needle.length;
|
|
return tuple(haystack[0 .. pos1],
|
|
haystack[pos1 .. pos2],
|
|
haystack[pos2 .. haystack.length]);
|
|
}
|
|
else
|
|
{
|
|
auto original = haystack.save;
|
|
auto h = haystack.save;
|
|
auto n = needle.save;
|
|
size_t pos1, pos2;
|
|
while (!n.empty && !h.empty)
|
|
{
|
|
if (binaryFun!pred(h.front, n.front))
|
|
{
|
|
h.popFront();
|
|
n.popFront();
|
|
++pos2;
|
|
}
|
|
else
|
|
{
|
|
haystack.popFront();
|
|
n = needle.save;
|
|
h = haystack.save;
|
|
pos2 = ++pos1;
|
|
}
|
|
}
|
|
return tuple(takeExactly(original, pos1),
|
|
takeExactly(haystack, pos2 - pos1),
|
|
h);
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
auto findSplitBefore(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
|
|
if (isForwardRange!R1 && isForwardRange!R2)
|
|
{
|
|
static if (isSomeString!R1 && isSomeString!R2
|
|
|| isRandomAccessRange!R1 && hasLength!R2)
|
|
{
|
|
auto balance = find!pred(haystack, needle);
|
|
immutable pos = haystack.length - balance.length;
|
|
return tuple(haystack[0 .. pos], haystack[pos .. haystack.length]);
|
|
}
|
|
else
|
|
{
|
|
auto original = haystack.save;
|
|
auto h = haystack.save;
|
|
auto n = needle.save;
|
|
size_t pos;
|
|
while (!n.empty && !h.empty)
|
|
{
|
|
if (binaryFun!pred(h.front, n.front))
|
|
{
|
|
h.popFront();
|
|
n.popFront();
|
|
}
|
|
else
|
|
{
|
|
haystack.popFront();
|
|
n = needle.save;
|
|
h = haystack.save;
|
|
++pos;
|
|
}
|
|
}
|
|
return tuple(takeExactly(original, pos), haystack);
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
auto findSplitAfter(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
|
|
if (isForwardRange!R1 && isForwardRange!R2)
|
|
{
|
|
static if (isSomeString!R1 && isSomeString!R2
|
|
|| isRandomAccessRange!R1 && hasLength!R2)
|
|
{
|
|
auto balance = find!pred(haystack, needle);
|
|
immutable pos = balance.empty ? 0 : haystack.length - balance.length + needle.length;
|
|
return tuple(haystack[0 .. pos], haystack[pos .. haystack.length]);
|
|
}
|
|
else
|
|
{
|
|
auto original = haystack.save;
|
|
auto h = haystack.save;
|
|
auto n = needle.save;
|
|
size_t pos1, pos2;
|
|
while (!n.empty)
|
|
{
|
|
if (h.empty)
|
|
{
|
|
// Failed search
|
|
return tuple(takeExactly(original, 0), original);
|
|
}
|
|
if (binaryFun!pred(h.front, n.front))
|
|
{
|
|
h.popFront();
|
|
n.popFront();
|
|
++pos2;
|
|
}
|
|
else
|
|
{
|
|
haystack.popFront();
|
|
n = needle.save;
|
|
h = haystack.save;
|
|
pos2 = ++pos1;
|
|
}
|
|
}
|
|
return tuple(takeExactly(original, pos2), h);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto a = "Carl Sagan Memorial Station";
|
|
auto r = findSplit(a, "Velikovsky");
|
|
assert(r[0] == a);
|
|
assert(r[1].empty);
|
|
assert(r[2].empty);
|
|
r = findSplit(a, " ");
|
|
assert(r[0] == "Carl");
|
|
assert(r[1] == " ");
|
|
assert(r[2] == "Sagan Memorial Station");
|
|
auto r1 = findSplitBefore(a, "Sagan");
|
|
assert(r1[0] == "Carl ", r1[0]);
|
|
assert(r1[1] == "Sagan Memorial Station");
|
|
auto r2 = findSplitAfter(a, "Sagan");
|
|
assert(r2[0] == "Carl Sagan");
|
|
assert(r2[1] == " Memorial Station");
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
|
|
auto r = findSplit(a, [9, 1]);
|
|
assert(r[0] == a);
|
|
assert(r[1].empty);
|
|
assert(r[2].empty);
|
|
r = findSplit(a, [3]);
|
|
assert(r[0] == a[0 .. 2]);
|
|
assert(r[1] == a[2 .. 3]);
|
|
assert(r[2] == a[3 .. $]);
|
|
|
|
auto r1 = findSplitBefore(a, [9, 1]);
|
|
assert(r1[0] == a);
|
|
assert(r1[1].empty);
|
|
r1 = findSplitBefore(a, [3, 4]);
|
|
assert(r1[0] == a[0 .. 2]);
|
|
assert(r1[1] == a[2 .. $]);
|
|
|
|
r1 = findSplitAfter(a, [9, 1]);
|
|
assert(r1[0].empty);
|
|
assert(r1[1] == a);
|
|
r1 = findSplitAfter(a, [3, 4]);
|
|
assert(r1[0] == a[0 .. 4]);
|
|
assert(r1[1] == a[4 .. $]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
|
|
auto fwd = filter!"a > 0"(a);
|
|
auto r = findSplit(fwd, [9, 1]);
|
|
assert(equal(r[0], a));
|
|
assert(r[1].empty);
|
|
assert(r[2].empty);
|
|
r = findSplit(fwd, [3]);
|
|
assert(equal(r[0], a[0 .. 2]));
|
|
assert(equal(r[1], a[2 .. 3]));
|
|
assert(equal(r[2], a[3 .. $]));
|
|
|
|
auto r1 = findSplitBefore(fwd, [9, 1]);
|
|
assert(equal(r1[0], a));
|
|
assert(r1[1].empty);
|
|
r1 = findSplitBefore(fwd, [3, 4]);
|
|
assert(equal(r1[0], a[0 .. 2]));
|
|
assert(equal(r1[1], a[2 .. $]));
|
|
|
|
r1 = findSplitAfter(fwd, [9, 1]);
|
|
assert(r1[0].empty);
|
|
assert(equal(r1[1], a));
|
|
r1 = findSplitAfter(fwd, [3, 4]);
|
|
assert(equal(r1[0], a[0 .. 4]));
|
|
assert(equal(r1[1], a[4 .. $]));
|
|
}
|
|
|
|
/++
|
|
Returns the number of elements which must be popped from the front of
|
|
$(D haystack) before reaching an element for which
|
|
$(D startsWith!pred(haystack, needles)) is $(D true). If
|
|
$(D startsWith!pred(haystack, needles)) is not $(D true) for any element in
|
|
$(D haystack), then $(D -1) is returned.
|
|
|
|
$(D needles) may be either an element or a range.
|
|
+/
|
|
ptrdiff_t countUntil(alias pred = "a == b", R, Rs...)(R haystack, Rs needles)
|
|
if (isForwardRange!R
|
|
&& Rs.length > 0
|
|
&& isForwardRange!(Rs[0]) == isInputRange!(Rs[0])
|
|
&& is(typeof(startsWith!pred(haystack, needles[0])))
|
|
&& (Rs.length == 1
|
|
|| is(typeof(countUntil!pred(haystack, needles[1 .. $])))))
|
|
{
|
|
typeof(return) result;
|
|
|
|
static if (needles.length == 1)
|
|
{
|
|
static if (hasLength!R) //Note: Narrow strings don't have length.
|
|
{
|
|
//We delegate to find because find is very efficient.
|
|
//We store the length of the haystack so we don't have to save it.
|
|
auto len = haystack.length;
|
|
auto r2 = find!pred(haystack, needles[0]);
|
|
if (!r2.empty)
|
|
return cast(typeof(return)) (len - r2.length);
|
|
}
|
|
else
|
|
{
|
|
if (needles[0].empty)
|
|
return 0;
|
|
|
|
//Default case, slower route doing startsWith iteration
|
|
for ( ; !haystack.empty ; ++result )
|
|
{
|
|
//We compare the first elements of the ranges here before
|
|
//forwarding to startsWith. This avoids making useless saves to
|
|
//haystack/needle if they aren't even going to be mutated anyways.
|
|
//It also cuts down on the amount of pops on haystack.
|
|
if (binaryFun!pred(haystack.front, needles[0].front))
|
|
{
|
|
//Here, we need to save the needle before popping it.
|
|
//haystack we pop in all paths, so we do that, and then save.
|
|
haystack.popFront();
|
|
if (startsWith!pred(haystack.save, needles[0].save.dropOne()))
|
|
return result;
|
|
}
|
|
else
|
|
haystack.popFront();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (i, Ri; Rs)
|
|
{
|
|
static if (isForwardRange!Ri)
|
|
{
|
|
if (needles[i].empty)
|
|
return 0;
|
|
}
|
|
}
|
|
Tuple!Rs t;
|
|
foreach (i, Ri; Rs)
|
|
{
|
|
static if (!isForwardRange!Ri)
|
|
{
|
|
t[i] = needles[i];
|
|
}
|
|
}
|
|
for (; !haystack.empty ; ++result, haystack.popFront())
|
|
{
|
|
foreach (i, Ri; Rs)
|
|
{
|
|
static if (isForwardRange!Ri)
|
|
{
|
|
t[i] = needles[i].save;
|
|
}
|
|
}
|
|
if (startsWith!pred(haystack.save, t.expand))
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement"
|
|
static if (isInfinite!R) assert(0);
|
|
else return -1;
|
|
}
|
|
|
|
/// ditto
|
|
ptrdiff_t countUntil(alias pred = "a == b", R, N)(R haystack, N needle)
|
|
if (isInputRange!R &&
|
|
is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
|
|
{
|
|
bool pred2(ElementType!R a) { return binaryFun!pred(a, needle); }
|
|
return countUntil!pred2(haystack);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(countUntil("hello world", "world") == 6);
|
|
assert(countUntil("hello world", 'r') == 8);
|
|
assert(countUntil("hello world", "programming") == -1);
|
|
assert(countUntil("日本語", "本語") == 1);
|
|
assert(countUntil("日本語", '語') == 2);
|
|
assert(countUntil("日本語", "五") == -1);
|
|
assert(countUntil("日本語", '五') == -1);
|
|
assert(countUntil([0, 7, 12, 22, 9], [12, 22]) == 2);
|
|
assert(countUntil([0, 7, 12, 22, 9], 9) == 4);
|
|
assert(countUntil!"a > b"([0, 7, 12, 22, 9], 20) == 3);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
assert(countUntil("日本語", "") == 0);
|
|
assert(countUntil("日本語"d, "") == 0);
|
|
|
|
assert(countUntil("", "") == 0);
|
|
assert(countUntil("".filter!"true"(), "") == 0);
|
|
|
|
auto rf = [0, 20, 12, 22, 9].filter!"true"();
|
|
assert(rf.countUntil!"a > b"((int[]).init) == 0);
|
|
assert(rf.countUntil!"a > b"(20) == 3);
|
|
assert(rf.countUntil!"a > b"([20, 8]) == 3);
|
|
assert(rf.countUntil!"a > b"([20, 10]) == -1);
|
|
assert(rf.countUntil!"a > b"([20, 8, 0]) == -1);
|
|
|
|
auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]);
|
|
auto r2 = new ReferenceForwardRange!int([3, 4]);
|
|
auto r3 = new ReferenceForwardRange!int([3, 5]);
|
|
assert(r.save.countUntil(3) == 3);
|
|
assert(r.save.countUntil(r2) == 3);
|
|
assert(r.save.countUntil(7) == -1);
|
|
assert(r.save.countUntil(r3) == -1);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
assert(countUntil("hello world", "world", "asd") == 6);
|
|
assert(countUntil("hello world", "world", "ello") == 1);
|
|
assert(countUntil("hello world", "world", "") == 0);
|
|
assert(countUntil("hello world", "world", 'l') == 2);
|
|
}
|
|
|
|
/++
|
|
Returns the number of elements which must be popped from $(D haystack)
|
|
before $(D pred(haystack.front)) is $(D true).
|
|
+/
|
|
ptrdiff_t countUntil(alias pred, R)(R haystack)
|
|
if (isInputRange!R &&
|
|
is(typeof(unaryFun!pred(haystack.front)) : bool))
|
|
{
|
|
typeof(return) i;
|
|
static if (isRandomAccessRange!R)
|
|
{
|
|
//Optimized RA implementation. Since we want to count *and* iterate at
|
|
//the same time, it is more efficient this way.
|
|
static if (hasLength!R)
|
|
{
|
|
immutable len = cast(typeof(return)) haystack.length;
|
|
for ( ; i < len ; ++i )
|
|
if (unaryFun!pred(haystack[i])) return i;
|
|
}
|
|
else //if (isInfinite!R)
|
|
{
|
|
for ( ; ; ++i )
|
|
if (unaryFun!pred(haystack[i])) return i;
|
|
}
|
|
}
|
|
else static if (hasLength!R)
|
|
{
|
|
//For those odd ranges that have a length, but aren't RA.
|
|
//It is faster to quick find, and then compare the lengths
|
|
auto r2 = find!pred(haystack.save);
|
|
if (!r2.empty) return cast(typeof(return)) (haystack.length - r2.length);
|
|
}
|
|
else //Everything else
|
|
{
|
|
alias T = ElementType!R; //For narrow strings forces dchar iteration
|
|
foreach (T elem; haystack)
|
|
{
|
|
if (unaryFun!pred(elem)) return i;
|
|
++i;
|
|
}
|
|
}
|
|
|
|
//Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement"
|
|
static if (isInfinite!R) assert(0);
|
|
else return -1;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.ascii : isDigit;
|
|
import std.uni : isWhite;
|
|
|
|
assert(countUntil!(std.uni.isWhite)("hello world") == 5);
|
|
assert(countUntil!(std.ascii.isDigit)("hello world") == -1);
|
|
assert(countUntil!"a > 20"([0, 7, 12, 22, 9]) == 3);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// References
|
|
{
|
|
// input
|
|
ReferenceInputRange!int r;
|
|
r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]);
|
|
assert(r.countUntil(3) == 3);
|
|
r = new ReferenceInputRange!int([0, 1, 2, 3, 4, 5, 6]);
|
|
assert(r.countUntil(7) == -1);
|
|
}
|
|
{
|
|
// forward
|
|
auto r = new ReferenceForwardRange!int([0, 1, 2, 3, 4, 5, 6]);
|
|
assert(r.save.countUntil([3, 4]) == 3);
|
|
assert(r.save.countUntil(3) == 3);
|
|
assert(r.save.countUntil([3, 7]) == -1);
|
|
assert(r.save.countUntil(7) == -1);
|
|
}
|
|
{
|
|
// infinite forward
|
|
auto r = new ReferenceInfiniteForwardRange!int(0);
|
|
assert(r.save.countUntil([3, 4]) == 3);
|
|
assert(r.save.countUntil(3) == 3);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Interval option specifier for $(D until) (below) and others.
|
|
*/
|
|
enum OpenRight
|
|
{
|
|
no, /// Interval is closed to the right (last element included)
|
|
yes /// Interval is open to the right (last element is not included)
|
|
}
|
|
|
|
/**
|
|
Lazily iterates $(D range) until value $(D sentinel) is found, at
|
|
which point it stops.
|
|
*/
|
|
struct Until(alias pred, Range, Sentinel) if (isInputRange!Range)
|
|
{
|
|
private Range _input;
|
|
static if (!is(Sentinel == void))
|
|
private Sentinel _sentinel;
|
|
// mixin(bitfields!(
|
|
// OpenRight, "_openRight", 1,
|
|
// bool, "_done", 1,
|
|
// uint, "", 6));
|
|
// OpenRight, "_openRight", 1,
|
|
// bool, "_done", 1,
|
|
OpenRight _openRight;
|
|
bool _done;
|
|
|
|
static if (!is(Sentinel == void))
|
|
this(Range input, Sentinel sentinel,
|
|
OpenRight openRight = OpenRight.yes)
|
|
{
|
|
_input = input;
|
|
_sentinel = sentinel;
|
|
_openRight = openRight;
|
|
_done = _input.empty || openRight && predSatisfied();
|
|
}
|
|
else
|
|
this(Range input, OpenRight openRight = OpenRight.yes)
|
|
{
|
|
_input = input;
|
|
_openRight = openRight;
|
|
_done = _input.empty || openRight && predSatisfied();
|
|
}
|
|
|
|
@property bool empty()
|
|
{
|
|
return _done;
|
|
}
|
|
|
|
@property auto ref front()
|
|
{
|
|
assert(!empty);
|
|
return _input.front;
|
|
}
|
|
|
|
private bool predSatisfied()
|
|
{
|
|
static if (is(Sentinel == void))
|
|
return unaryFun!pred(_input.front);
|
|
else
|
|
return startsWith!pred(_input, _sentinel);
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
assert(!empty);
|
|
if (!_openRight)
|
|
{
|
|
_done = predSatisfied();
|
|
_input.popFront();
|
|
_done = _done || _input.empty;
|
|
}
|
|
else
|
|
{
|
|
_input.popFront();
|
|
_done = _input.empty || predSatisfied();
|
|
}
|
|
}
|
|
|
|
static if (isForwardRange!Range)
|
|
{
|
|
static if (!is(Sentinel == void))
|
|
@property Until save()
|
|
{
|
|
Until result = this;
|
|
result._input = _input.save;
|
|
result._sentinel = _sentinel;
|
|
result._openRight = _openRight;
|
|
result._done = _done;
|
|
return result;
|
|
}
|
|
else
|
|
@property Until save()
|
|
{
|
|
Until result = this;
|
|
result._input = _input.save;
|
|
result._openRight = _openRight;
|
|
result._done = _done;
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
Until!(pred, Range, Sentinel)
|
|
until(alias pred = "a == b", Range, Sentinel)
|
|
(Range range, Sentinel sentinel, OpenRight openRight = OpenRight.yes)
|
|
if (!is(Sentinel == OpenRight))
|
|
{
|
|
return typeof(return)(range, sentinel, openRight);
|
|
}
|
|
|
|
/// Ditto
|
|
Until!(pred, Range, void)
|
|
until(alias pred, Range)
|
|
(Range range, OpenRight openRight = OpenRight.yes)
|
|
{
|
|
return typeof(return)(range, openRight);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5];
|
|
assert(equal(a.until(7), [1, 2, 4][]));
|
|
assert(equal(a.until(7, OpenRight.no), [1, 2, 4, 7][]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5];
|
|
|
|
static assert(isForwardRange!(typeof(a.until(7))));
|
|
static assert(isForwardRange!(typeof(until!"a == 2"(a, OpenRight.no))));
|
|
|
|
assert(equal(a.until(7), [1, 2, 4][]));
|
|
assert(equal(a.until([7, 2]), [1, 2, 4, 7][]));
|
|
assert(equal(a.until(7, OpenRight.no), [1, 2, 4, 7][]));
|
|
assert(equal(until!"a == 2"(a, OpenRight.no), [1, 2][]));
|
|
}
|
|
|
|
unittest // bugzilla 13171
|
|
{
|
|
auto a = [1, 2, 3, 4];
|
|
assert(equal(refRange(&a).until(3, OpenRight.no), [1, 2, 3]));
|
|
assert(a == [4]);
|
|
}
|
|
|
|
@safe unittest // Issue 10460
|
|
{
|
|
auto a = [1, 2, 3, 4];
|
|
foreach (ref e; a.until(3))
|
|
e = 0;
|
|
assert(equal(a, [0, 0, 3, 4]));
|
|
}
|
|
|
|
/**
|
|
If the range $(D doesThisStart) starts with $(I any) of the $(D
|
|
withOneOfThese) ranges or elements, returns 1 if it starts with $(D
|
|
withOneOfThese[0]), 2 if it starts with $(D withOneOfThese[1]), and so
|
|
on. If none match, returns 0. In the case where $(D doesThisStart) starts
|
|
with multiple of the ranges or elements in $(D withOneOfThese), then the
|
|
shortest one matches (if there are two which match which are of the same
|
|
length (e.g. $(D "a") and $(D 'a')), then the left-most of them in the argument
|
|
list matches).
|
|
*/
|
|
uint startsWith(alias pred = "a == b", Range, Needles...)(Range doesThisStart, Needles withOneOfThese)
|
|
if (isInputRange!Range && Needles.length > 1 &&
|
|
is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[0])) : bool ) &&
|
|
is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[1 .. $])) : uint))
|
|
{
|
|
alias haystack = doesThisStart;
|
|
alias needles = withOneOfThese;
|
|
|
|
// Make one pass looking for empty ranges in needles
|
|
foreach (i, Unused; Needles)
|
|
{
|
|
// Empty range matches everything
|
|
static if (!is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool))
|
|
{
|
|
if (needles[i].empty) return i + 1;
|
|
}
|
|
}
|
|
|
|
for (; !haystack.empty; haystack.popFront())
|
|
{
|
|
foreach (i, Unused; Needles)
|
|
{
|
|
static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool))
|
|
{
|
|
// Single-element
|
|
if (binaryFun!pred(haystack.front, needles[i]))
|
|
{
|
|
// found, but instead of returning, we just stop searching.
|
|
// This is to account for one-element
|
|
// range matches (consider startsWith("ab", "a",
|
|
// 'a') should return 1, not 2).
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (binaryFun!pred(haystack.front, needles[i].front))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// This code executed on failure to match
|
|
// Out with this guy, check for the others
|
|
uint result = startsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]);
|
|
if (result > i) ++result;
|
|
return result;
|
|
}
|
|
|
|
// If execution reaches this point, then the front matches for all
|
|
// needle ranges, or a needle element has been matched.
|
|
// What we need to do now is iterate, lopping off the front of
|
|
// the range and checking if the result is empty, or finding an
|
|
// element needle and returning.
|
|
// If neither happens, we drop to the end and loop.
|
|
foreach (i, Unused; Needles)
|
|
{
|
|
static if (is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool))
|
|
{
|
|
// Test has passed in the previous loop
|
|
return i + 1;
|
|
}
|
|
else
|
|
{
|
|
needles[i].popFront();
|
|
if (needles[i].empty) return i + 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// Ditto
|
|
bool startsWith(alias pred = "a == b", R1, R2)(R1 doesThisStart, R2 withThis)
|
|
if (isInputRange!R1 &&
|
|
isInputRange!R2 &&
|
|
is(typeof(binaryFun!pred(doesThisStart.front, withThis.front)) : bool))
|
|
{
|
|
alias haystack = doesThisStart;
|
|
alias needle = withThis;
|
|
|
|
static if (is(typeof(pred) : string))
|
|
enum isDefaultPred = pred == "a == b";
|
|
else
|
|
enum isDefaultPred = false;
|
|
|
|
//Note: While narrow strings don't have a "true" length, for a narrow string to start with another
|
|
//narrow string *of the same type*, it must have *at least* as many code units.
|
|
static if ((hasLength!R1 && hasLength!R2) ||
|
|
(isNarrowString!R1 && isNarrowString!R2 && ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof))
|
|
{
|
|
if (haystack.length < needle.length)
|
|
return false;
|
|
}
|
|
|
|
static if (isDefaultPred && isArray!R1 && isArray!R2 &&
|
|
is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2)))
|
|
{
|
|
//Array slice comparison mode
|
|
return haystack[0 .. needle.length] == needle;
|
|
}
|
|
else static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 && hasLength!R2)
|
|
{
|
|
//RA dual indexing mode
|
|
foreach (j; 0 .. needle.length)
|
|
{
|
|
if (!binaryFun!pred(needle[j], haystack[j]))
|
|
// not found
|
|
return false;
|
|
}
|
|
// found!
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
//Standard input range mode
|
|
if (needle.empty) return true;
|
|
static if (hasLength!R1 && hasLength!R2)
|
|
{
|
|
//We have previously checked that haystack.length > needle.length,
|
|
//So no need to check haystack.empty during iteration
|
|
for ( ; ; haystack.popFront() )
|
|
{
|
|
if (!binaryFun!pred(haystack.front, needle.front)) break;
|
|
needle.popFront();
|
|
if (needle.empty) return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( ; !haystack.empty ; haystack.popFront() )
|
|
{
|
|
if (!binaryFun!pred(haystack.front, needle.front)) break;
|
|
needle.popFront();
|
|
if (needle.empty) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
bool startsWith(alias pred = "a == b", R, E)(R doesThisStart, E withThis)
|
|
if (isInputRange!R &&
|
|
is(typeof(binaryFun!pred(doesThisStart.front, withThis)) : bool))
|
|
{
|
|
return doesThisStart.empty
|
|
? false
|
|
: binaryFun!pred(doesThisStart.front, withThis);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(startsWith("abc", ""));
|
|
assert(startsWith("abc", "a"));
|
|
assert(!startsWith("abc", "b"));
|
|
assert(startsWith("abc", 'a', "b") == 1);
|
|
assert(startsWith("abc", "b", "a") == 2);
|
|
assert(startsWith("abc", "a", "a") == 1);
|
|
assert(startsWith("abc", "ab", "a") == 2);
|
|
assert(startsWith("abc", "x", "a", "b") == 2);
|
|
assert(startsWith("abc", "x", "aa", "ab") == 3);
|
|
assert(startsWith("abc", "x", "aaa", "sab") == 0);
|
|
assert(startsWith("abc", "x", "aaa", "a", "sab") == 3);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring))
|
|
{
|
|
assert(!startsWith(to!S("abc"), 'c'));
|
|
assert(startsWith(to!S("abc"), 'a', 'c') == 1);
|
|
assert(!startsWith(to!S("abc"), 'x', 'n', 'b'));
|
|
assert(startsWith(to!S("abc"), 'x', 'n', 'a') == 3);
|
|
assert(startsWith(to!S("\uFF28abc"), 'a', '\uFF28', 'c') == 2);
|
|
|
|
foreach (T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring))
|
|
{
|
|
//Lots of strings
|
|
assert(startsWith(to!S("abc"), to!T("")));
|
|
assert(startsWith(to!S("ab"), to!T("a")));
|
|
assert(startsWith(to!S("abc"), to!T("a")));
|
|
assert(!startsWith(to!S("abc"), to!T("b")));
|
|
assert(!startsWith(to!S("abc"), to!T("b"), "bc", "abcd", "xyz"));
|
|
assert(startsWith(to!S("abc"), to!T("ab"), 'a') == 2);
|
|
assert(startsWith(to!S("abc"), to!T("a"), "b") == 1);
|
|
assert(startsWith(to!S("abc"), to!T("b"), "a") == 2);
|
|
assert(startsWith(to!S("abc"), to!T("a"), 'a') == 1);
|
|
assert(startsWith(to!S("abc"), 'a', to!T("a")) == 1);
|
|
assert(startsWith(to!S("abc"), to!T("x"), "a", "b") == 2);
|
|
assert(startsWith(to!S("abc"), to!T("x"), "aa", "ab") == 3);
|
|
assert(startsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0);
|
|
assert(startsWith(to!S("abc"), 'a'));
|
|
assert(!startsWith(to!S("abc"), to!T("sab")));
|
|
assert(startsWith(to!S("abc"), 'x', to!T("aaa"), 'a', "sab") == 3);
|
|
|
|
//Unicode
|
|
assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("\uFF28el")));
|
|
assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("Hel"), to!T("\uFF28el")) == 2);
|
|
assert(startsWith(to!S("日本語"), to!T("日本")));
|
|
assert(startsWith(to!S("日本語"), to!T("日本語")));
|
|
assert(!startsWith(to!S("日本"), to!T("日本語")));
|
|
|
|
//Empty
|
|
assert(startsWith(to!S(""), T.init));
|
|
assert(!startsWith(to!S(""), 'a'));
|
|
assert(startsWith(to!S("a"), T.init));
|
|
assert(startsWith(to!S("a"), T.init, "") == 1);
|
|
assert(startsWith(to!S("a"), T.init, 'a') == 1);
|
|
assert(startsWith(to!S("a"), 'a', T.init) == 2);
|
|
}
|
|
}
|
|
|
|
//Length but no RA
|
|
assert(!startsWith("abc".takeExactly(3), "abcd".takeExactly(4)));
|
|
assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(3)));
|
|
assert(startsWith("abc".takeExactly(3), "abcd".takeExactly(1)));
|
|
|
|
foreach (T; TypeTuple!(int, short))
|
|
{
|
|
immutable arr = cast(T[])[0, 1, 2, 3, 4, 5];
|
|
|
|
//RA range
|
|
assert(startsWith(arr, cast(int[])null));
|
|
assert(!startsWith(arr, 5));
|
|
assert(!startsWith(arr, 1));
|
|
assert(startsWith(arr, 0));
|
|
assert(startsWith(arr, 5, 0, 1) == 2);
|
|
assert(startsWith(arr, [0]));
|
|
assert(startsWith(arr, [0, 1]));
|
|
assert(startsWith(arr, [0, 1], 7) == 1);
|
|
assert(!startsWith(arr, [0, 1, 7]));
|
|
assert(startsWith(arr, [0, 1, 7], [0, 1, 2]) == 2);
|
|
|
|
//Normal input range
|
|
assert(!startsWith(filter!"true"(arr), 1));
|
|
assert(startsWith(filter!"true"(arr), 0));
|
|
assert(startsWith(filter!"true"(arr), [0]));
|
|
assert(startsWith(filter!"true"(arr), [0, 1]));
|
|
assert(startsWith(filter!"true"(arr), [0, 1], 7) == 1);
|
|
assert(!startsWith(filter!"true"(arr), [0, 1, 7]));
|
|
assert(startsWith(filter!"true"(arr), [0, 1, 7], [0, 1, 2]) == 2);
|
|
assert(startsWith(arr, filter!"true"([0, 1])));
|
|
assert(startsWith(arr, filter!"true"([0, 1]), 7) == 1);
|
|
assert(!startsWith(arr, filter!"true"([0, 1, 7])));
|
|
assert(startsWith(arr, [0, 1, 7], filter!"true"([0, 1, 2])) == 2);
|
|
|
|
//Non-default pred
|
|
assert(startsWith!("a%10 == b%10")(arr, [10, 11]));
|
|
assert(!startsWith!("a%10 == b%10")(arr, [10, 12]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
If $(D startsWith(r1, r2)), consume the corresponding elements off $(D
|
|
r1) and return $(D true). Otherwise, leave $(D r1) unchanged and
|
|
return $(D false).
|
|
*/
|
|
bool skipOver(alias pred = "a == b", R1, R2)(ref R1 r1, R2 r2)
|
|
if (is(typeof(binaryFun!pred(r1.front, r2.front))))
|
|
{
|
|
auto r = r1.save;
|
|
while (!r2.empty && !r.empty && binaryFun!pred(r.front, r2.front))
|
|
{
|
|
r.popFront();
|
|
r2.popFront();
|
|
}
|
|
if (r2.empty)
|
|
r1 = r;
|
|
return r2.empty;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto s1 = "Hello world";
|
|
assert(!skipOver(s1, "Ha"));
|
|
assert(s1 == "Hello world");
|
|
assert(skipOver(s1, "Hell") && s1 == "o world");
|
|
|
|
string[] r1 = ["abc", "def", "hij"];
|
|
dstring[] r2 = ["abc"d];
|
|
assert(!skipOver!((a, b) => a.equal(b))(r1, ["def"d]));
|
|
assert(r1 == ["abc", "def", "hij"]);
|
|
assert(skipOver!((a, b) => a.equal(b))(r1, r2));
|
|
assert(r1 == ["def", "hij"]);
|
|
}
|
|
|
|
/**
|
|
Checks whether a range starts with an element, and if so, consume that
|
|
element off $(D r) and return $(D true). Otherwise, leave $(D r)
|
|
unchanged and return $(D false).
|
|
*/
|
|
bool skipOver(alias pred = "a == b", R, E)(ref R r, E e)
|
|
if (is(typeof(binaryFun!pred(r.front, e))))
|
|
{
|
|
if (r.empty || !binaryFun!pred(r.front, e))
|
|
return false;
|
|
r.popFront();
|
|
return true;
|
|
}
|
|
|
|
///
|
|
@safe unittest {
|
|
auto s1 = "Hello world";
|
|
assert(!skipOver(s1, 'a'));
|
|
assert(s1 == "Hello world");
|
|
assert(skipOver(s1, 'H') && s1 == "ello world");
|
|
|
|
string[] r = ["abc", "def", "hij"];
|
|
dstring e = "abc"d;
|
|
assert(!skipOver!((a, b) => a.equal(b))(r, "def"d));
|
|
assert(r == ["abc", "def", "hij"]);
|
|
assert(skipOver!((a, b) => a.equal(b))(r, e));
|
|
assert(r == ["def", "hij"]);
|
|
|
|
auto s2 = "";
|
|
assert(!s2.skipOver('a'));
|
|
}
|
|
|
|
/* (Not yet documented.)
|
|
Consume all elements from $(D r) that are equal to one of the elements
|
|
$(D es).
|
|
*/
|
|
void skipAll(alias pred = "a == b", R, Es...)(ref R r, Es es)
|
|
//if (is(typeof(binaryFun!pred(r1.front, es[0]))))
|
|
{
|
|
loop:
|
|
for (; !r.empty; r.popFront())
|
|
{
|
|
foreach (i, E; Es)
|
|
{
|
|
if (binaryFun!pred(r.front, es[i]))
|
|
{
|
|
continue loop;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
auto s1 = "Hello world";
|
|
skipAll(s1, 'H', 'e');
|
|
assert(s1 == "llo world");
|
|
}
|
|
|
|
/**
|
|
The reciprocal of $(D startsWith).
|
|
*/
|
|
uint endsWith(alias pred = "a == b", Range, Needles...)(Range doesThisEnd, Needles withOneOfThese)
|
|
if (isBidirectionalRange!Range && Needles.length > 1 &&
|
|
is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[0])) : bool) &&
|
|
is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[1 .. $])) : uint))
|
|
{
|
|
alias haystack = doesThisEnd;
|
|
alias needles = withOneOfThese;
|
|
|
|
// Make one pass looking for empty ranges in needles
|
|
foreach (i, Unused; Needles)
|
|
{
|
|
// Empty range matches everything
|
|
static if (!is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
|
|
{
|
|
if (needles[i].empty) return i + 1;
|
|
}
|
|
}
|
|
|
|
for (; !haystack.empty; haystack.popBack())
|
|
{
|
|
foreach (i, Unused; Needles)
|
|
{
|
|
static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
|
|
{
|
|
// Single-element
|
|
if (binaryFun!pred(haystack.back, needles[i]))
|
|
{
|
|
// found, but continue to account for one-element
|
|
// range matches (consider endsWith("ab", "b",
|
|
// 'b') should return 1, not 2).
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (binaryFun!pred(haystack.back, needles[i].back))
|
|
continue;
|
|
}
|
|
|
|
// This code executed on failure to match
|
|
// Out with this guy, check for the others
|
|
uint result = endsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]);
|
|
if (result > i) ++result;
|
|
return result;
|
|
}
|
|
|
|
// If execution reaches this point, then the back matches for all
|
|
// needles ranges. What we need to do now is to lop off the back of
|
|
// all ranges involved and recurse.
|
|
foreach (i, Unused; Needles)
|
|
{
|
|
static if (is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
|
|
{
|
|
// Test has passed in the previous loop
|
|
return i + 1;
|
|
}
|
|
else
|
|
{
|
|
needles[i].popBack();
|
|
if (needles[i].empty) return i + 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// Ditto
|
|
bool endsWith(alias pred = "a == b", R1, R2)(R1 doesThisEnd, R2 withThis)
|
|
if (isBidirectionalRange!R1 &&
|
|
isBidirectionalRange!R2 &&
|
|
is(typeof(binaryFun!pred(doesThisEnd.back, withThis.back)) : bool))
|
|
{
|
|
alias haystack = doesThisEnd;
|
|
alias needle = withThis;
|
|
|
|
static if (is(typeof(pred) : string))
|
|
enum isDefaultPred = pred == "a == b";
|
|
else
|
|
enum isDefaultPred = false;
|
|
|
|
static if (isDefaultPred && isArray!R1 && isArray!R2 &&
|
|
is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2)))
|
|
{
|
|
if (haystack.length < needle.length) return false;
|
|
|
|
return haystack[$ - needle.length .. $] == needle;
|
|
}
|
|
else
|
|
{
|
|
return startsWith!pred(retro(doesThisEnd), retro(withThis));
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
bool endsWith(alias pred = "a == b", R, E)(R doesThisEnd, E withThis)
|
|
if (isBidirectionalRange!R &&
|
|
is(typeof(binaryFun!pred(doesThisEnd.back, withThis)) : bool))
|
|
{
|
|
return doesThisEnd.empty
|
|
? false
|
|
: binaryFun!pred(doesThisEnd.back, withThis);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(endsWith("abc", ""));
|
|
assert(!endsWith("abc", "b"));
|
|
assert(endsWith("abc", "a", 'c') == 2);
|
|
assert(endsWith("abc", "c", "a") == 1);
|
|
assert(endsWith("abc", "c", "c") == 1);
|
|
assert(endsWith("abc", "bc", "c") == 2);
|
|
assert(endsWith("abc", "x", "c", "b") == 2);
|
|
assert(endsWith("abc", "x", "aa", "bc") == 3);
|
|
assert(endsWith("abc", "x", "aaa", "sab") == 0);
|
|
assert(endsWith("abc", "x", "aaa", 'c', "sab") == 3);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
foreach (S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring))
|
|
{
|
|
assert(!endsWith(to!S("abc"), 'a'));
|
|
assert(endsWith(to!S("abc"), 'a', 'c') == 2);
|
|
assert(!endsWith(to!S("abc"), 'x', 'n', 'b'));
|
|
assert(endsWith(to!S("abc"), 'x', 'n', 'c') == 3);
|
|
assert(endsWith(to!S("abc\uFF28"), 'a', '\uFF28', 'c') == 2);
|
|
|
|
foreach (T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring))
|
|
{
|
|
//Lots of strings
|
|
assert(endsWith(to!S("abc"), to!T("")));
|
|
assert(!endsWith(to!S("abc"), to!T("a")));
|
|
assert(!endsWith(to!S("abc"), to!T("b")));
|
|
assert(endsWith(to!S("abc"), to!T("bc"), 'c') == 2);
|
|
assert(endsWith(to!S("abc"), to!T("a"), "c") == 2);
|
|
assert(endsWith(to!S("abc"), to!T("c"), "a") == 1);
|
|
assert(endsWith(to!S("abc"), to!T("c"), "c") == 1);
|
|
assert(endsWith(to!S("abc"), to!T("x"), 'c', "b") == 2);
|
|
assert(endsWith(to!S("abc"), 'x', to!T("aa"), "bc") == 3);
|
|
assert(endsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0);
|
|
assert(endsWith(to!S("abc"), to!T("x"), "aaa", "c", "sab") == 3);
|
|
assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")));
|
|
assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2);
|
|
|
|
//Unicode
|
|
assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")));
|
|
assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2);
|
|
assert(endsWith(to!S("日本語"), to!T("本語")));
|
|
assert(endsWith(to!S("日本語"), to!T("日本語")));
|
|
assert(!endsWith(to!S("本語"), to!T("日本語")));
|
|
|
|
//Empty
|
|
assert(endsWith(to!S(""), T.init));
|
|
assert(!endsWith(to!S(""), 'a'));
|
|
assert(endsWith(to!S("a"), T.init));
|
|
assert(endsWith(to!S("a"), T.init, "") == 1);
|
|
assert(endsWith(to!S("a"), T.init, 'a') == 1);
|
|
assert(endsWith(to!S("a"), 'a', T.init) == 2);
|
|
}
|
|
}
|
|
|
|
foreach (T; TypeTuple!(int, short))
|
|
{
|
|
immutable arr = cast(T[])[0, 1, 2, 3, 4, 5];
|
|
|
|
//RA range
|
|
assert(endsWith(arr, cast(int[])null));
|
|
assert(!endsWith(arr, 0));
|
|
assert(!endsWith(arr, 4));
|
|
assert(endsWith(arr, 5));
|
|
assert(endsWith(arr, 0, 4, 5) == 3);
|
|
assert(endsWith(arr, [5]));
|
|
assert(endsWith(arr, [4, 5]));
|
|
assert(endsWith(arr, [4, 5], 7) == 1);
|
|
assert(!endsWith(arr, [2, 4, 5]));
|
|
assert(endsWith(arr, [2, 4, 5], [3, 4, 5]) == 2);
|
|
|
|
//Normal input range
|
|
assert(!endsWith(filterBidirectional!"true"(arr), 4));
|
|
assert(endsWith(filterBidirectional!"true"(arr), 5));
|
|
assert(endsWith(filterBidirectional!"true"(arr), [5]));
|
|
assert(endsWith(filterBidirectional!"true"(arr), [4, 5]));
|
|
assert(endsWith(filterBidirectional!"true"(arr), [4, 5], 7) == 1);
|
|
assert(!endsWith(filterBidirectional!"true"(arr), [2, 4, 5]));
|
|
assert(endsWith(filterBidirectional!"true"(arr), [2, 4, 5], [3, 4, 5]) == 2);
|
|
assert(endsWith(arr, filterBidirectional!"true"([4, 5])));
|
|
assert(endsWith(arr, filterBidirectional!"true"([4, 5]), 7) == 1);
|
|
assert(!endsWith(arr, filterBidirectional!"true"([2, 4, 5])));
|
|
assert(endsWith(arr, [2, 4, 5], filterBidirectional!"true"([3, 4, 5])) == 2);
|
|
|
|
//Non-default pred
|
|
assert(endsWith!("a%10 == b%10")(arr, [14, 15]));
|
|
assert(!endsWith!("a%10 == b%10")(arr, [15, 14]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns the common prefix of two ranges.
|
|
|
|
If the first argument is a string, then the result is a slice of $(D r1) which
|
|
contains the characters that both ranges start with. For all other types, the
|
|
type of the result is the same as the result of $(D takeExactly(r1, n)), where
|
|
$(D n) is the number of elements that both ranges start with.
|
|
|
|
See_Also:
|
|
$(XREF range, takeExactly)
|
|
*/
|
|
auto commonPrefix(alias pred = "a == b", R1, R2)(R1 r1, R2 r2)
|
|
if (isForwardRange!R1 && isInputRange!R2 &&
|
|
!isNarrowString!R1 &&
|
|
is(typeof(binaryFun!pred(r1.front, r2.front))))
|
|
{
|
|
static if (isRandomAccessRange!R1 && isRandomAccessRange!R2 &&
|
|
hasLength!R1 && hasLength!R2 &&
|
|
hasSlicing!R1)
|
|
{
|
|
immutable limit = min(r1.length, r2.length);
|
|
foreach (i; 0 .. limit)
|
|
{
|
|
if (!binaryFun!pred(r1[i], r2[i]))
|
|
{
|
|
return r1[0 .. i];
|
|
}
|
|
}
|
|
return r1[0 .. limit];
|
|
}
|
|
else
|
|
{
|
|
auto result = r1.save;
|
|
size_t i = 0;
|
|
for (;
|
|
!r1.empty && !r2.empty && binaryFun!pred(r1.front, r2.front);
|
|
++i, r1.popFront(), r2.popFront())
|
|
{}
|
|
return takeExactly(result, i);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(commonPrefix("hello, world", "hello, there") == "hello, ");
|
|
}
|
|
|
|
auto commonPrefix(alias pred, R1, R2)(R1 r1, R2 r2)
|
|
if (isNarrowString!R1 && isInputRange!R2 &&
|
|
is(typeof(binaryFun!pred(r1.front, r2.front))))
|
|
{
|
|
import std.utf : decode;
|
|
|
|
auto result = r1.save;
|
|
immutable len = r1.length;
|
|
size_t i = 0;
|
|
|
|
for (size_t j = 0; i < len && !r2.empty; r2.popFront(), i = j)
|
|
{
|
|
immutable f = decode(r1, j);
|
|
if (!binaryFun!pred(f, r2.front))
|
|
break;
|
|
}
|
|
|
|
return result[0 .. i];
|
|
}
|
|
|
|
auto commonPrefix(R1, R2)(R1 r1, R2 r2)
|
|
if (isNarrowString!R1 && isInputRange!R2 && !isNarrowString!R2 &&
|
|
is(typeof(r1.front == r2.front)))
|
|
{
|
|
return commonPrefix!"a == b"(r1, r2);
|
|
}
|
|
|
|
auto commonPrefix(R1, R2)(R1 r1, R2 r2)
|
|
if (isNarrowString!R1 && isNarrowString!R2)
|
|
{
|
|
static if (ElementEncodingType!R1.sizeof == ElementEncodingType!R2.sizeof)
|
|
{
|
|
import std.utf : stride, UTFException;
|
|
|
|
immutable limit = min(r1.length, r2.length);
|
|
for (size_t i = 0; i < limit;)
|
|
{
|
|
immutable codeLen = std.utf.stride(r1, i);
|
|
size_t j = 0;
|
|
|
|
for (; j < codeLen && i < limit; ++i, ++j)
|
|
{
|
|
if (r1[i] != r2[i])
|
|
return r1[0 .. i - j];
|
|
}
|
|
|
|
if (i == limit && j < codeLen)
|
|
throw new UTFException("Invalid UTF-8 sequence", i);
|
|
}
|
|
return r1[0 .. limit];
|
|
}
|
|
else
|
|
return commonPrefix!"a == b"(r1, r2);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
import std.exception : assertThrown;
|
|
import std.utf : UTFException;
|
|
|
|
assert(commonPrefix([1, 2, 3], [1, 2, 3, 4, 5]) == [1, 2, 3]);
|
|
assert(commonPrefix([1, 2, 3, 4, 5], [1, 2, 3]) == [1, 2, 3]);
|
|
assert(commonPrefix([1, 2, 3, 4], [1, 2, 3, 4]) == [1, 2, 3, 4]);
|
|
assert(commonPrefix([1, 2, 3], [7, 2, 3, 4, 5]).empty);
|
|
assert(commonPrefix([7, 2, 3, 4, 5], [1, 2, 3]).empty);
|
|
assert(commonPrefix([1, 2, 3], cast(int[])null).empty);
|
|
assert(commonPrefix(cast(int[])null, [1, 2, 3]).empty);
|
|
assert(commonPrefix(cast(int[])null, cast(int[])null).empty);
|
|
|
|
foreach (S; TypeTuple!(char[], const(char)[], string,
|
|
wchar[], const(wchar)[], wstring,
|
|
dchar[], const(dchar)[], dstring))
|
|
{
|
|
foreach(T; TypeTuple!(string, wstring, dstring))
|
|
{
|
|
assert(commonPrefix(to!S(""), to!T("")).empty);
|
|
assert(commonPrefix(to!S(""), to!T("hello")).empty);
|
|
assert(commonPrefix(to!S("hello"), to!T("")).empty);
|
|
assert(commonPrefix(to!S("hello, world"), to!T("hello, there")) == to!S("hello, "));
|
|
assert(commonPrefix(to!S("hello, there"), to!T("hello, world")) == to!S("hello, "));
|
|
assert(commonPrefix(to!S("hello, "), to!T("hello, world")) == to!S("hello, "));
|
|
assert(commonPrefix(to!S("hello, world"), to!T("hello, ")) == to!S("hello, "));
|
|
assert(commonPrefix(to!S("hello, world"), to!T("hello, world")) == to!S("hello, world"));
|
|
|
|
//Bug# 8890
|
|
assert(commonPrefix(to!S("Пиво"), to!T("Пони"))== to!S("П"));
|
|
assert(commonPrefix(to!S("Пони"), to!T("Пиво"))== to!S("П"));
|
|
assert(commonPrefix(to!S("Пиво"), to!T("Пиво"))== to!S("Пиво"));
|
|
assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFE"),
|
|
to!T("\U0010FFFF\U0010FFFB\U0010FFFC")) == to!S("\U0010FFFF\U0010FFFB"));
|
|
assert(commonPrefix(to!S("\U0010FFFF\U0010FFFB\U0010FFFC"),
|
|
to!T("\U0010FFFF\U0010FFFB\U0010FFFE")) == to!S("\U0010FFFF\U0010FFFB"));
|
|
assert(commonPrefix!"a != b"(to!S("Пиво"), to!T("онво")) == to!S("Пи"));
|
|
assert(commonPrefix!"a != b"(to!S("онво"), to!T("Пиво")) == to!S("он"));
|
|
}
|
|
|
|
static assert(is(typeof(commonPrefix(to!S("Пиво"), filter!"true"("Пони"))) == S));
|
|
assert(equal(commonPrefix(to!S("Пиво"), filter!"true"("Пони")), to!S("П")));
|
|
|
|
static assert(is(typeof(commonPrefix(filter!"true"("Пиво"), to!S("Пони"))) ==
|
|
typeof(takeExactly(filter!"true"("П"), 1))));
|
|
assert(equal(commonPrefix(filter!"true"("Пиво"), to!S("Пони")), takeExactly(filter!"true"("П"), 1)));
|
|
}
|
|
|
|
assertThrown!UTFException(commonPrefix("\U0010FFFF\U0010FFFB", "\U0010FFFF\U0010FFFB"[0 .. $ - 1]));
|
|
|
|
assert(commonPrefix("12345"d, [49, 50, 51, 60, 60]) == "123"d);
|
|
assert(commonPrefix([49, 50, 51, 60, 60], "12345" ) == [49, 50, 51]);
|
|
assert(commonPrefix([49, 50, 51, 60, 60], "12345"d) == [49, 50, 51]);
|
|
|
|
assert(commonPrefix!"a == ('0' + b)"("12345" , [1, 2, 3, 9, 9]) == "123");
|
|
assert(commonPrefix!"a == ('0' + b)"("12345"d, [1, 2, 3, 9, 9]) == "123"d);
|
|
assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345" ) == [1, 2, 3]);
|
|
assert(commonPrefix!"('0' + a) == b"([1, 2, 3, 9, 9], "12345"d) == [1, 2, 3]);
|
|
}
|
|
|
|
// findAdjacent
|
|
/**
|
|
Advances $(D r) until it finds the first two adjacent elements $(D a),
|
|
$(D b) 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)
|
|
*/
|
|
Range findAdjacent(alias pred = "a == b", Range)(Range r)
|
|
if (isForwardRange!(Range))
|
|
{
|
|
auto ahead = r.save;
|
|
if (!ahead.empty)
|
|
{
|
|
for (ahead.popFront(); !ahead.empty; r.popFront(), ahead.popFront())
|
|
{
|
|
if (binaryFun!(pred)(r.front, ahead.front)) return r;
|
|
}
|
|
}
|
|
static if (!isInfinite!Range)
|
|
return ahead;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ];
|
|
auto r = findAdjacent(a);
|
|
assert(r == [ 10, 10, 9, 8, 8, 7, 8, 9 ]);
|
|
auto p = findAdjacent!("a < b")(a);
|
|
assert(p == [ 7, 8, 9 ]);
|
|
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ 11, 10, 10, 9, 8, 8, 7, 8, 9 ];
|
|
auto p = findAdjacent(a);
|
|
assert(p == [10, 10, 9, 8, 8, 7, 8, 9 ]);
|
|
p = findAdjacent!("a < b")(a);
|
|
assert(p == [7, 8, 9]);
|
|
// empty
|
|
a = [];
|
|
p = findAdjacent(a);
|
|
assert(p.empty);
|
|
// not found
|
|
a = [ 1, 2, 3, 4, 5 ];
|
|
p = findAdjacent(a);
|
|
assert(p.empty);
|
|
p = findAdjacent!"a > b"(a);
|
|
assert(p.empty);
|
|
ReferenceForwardRange!int rfr = new ReferenceForwardRange!int([1, 2, 3, 2, 2, 3]);
|
|
assert(equal(findAdjacent(rfr), [2, 2, 3]));
|
|
|
|
// Issue 9350
|
|
assert(!repeat(1).findAdjacent().empty);
|
|
}
|
|
|
|
// findAmong
|
|
/**
|
|
Advances $(D seq) by calling $(D seq.popFront) until either $(D
|
|
find!(pred)(choices, seq.front)) is $(D true), or $(D seq) becomes
|
|
empty. 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)
|
|
*/
|
|
Range1 findAmong(alias pred = "a == b", Range1, Range2)(
|
|
Range1 seq, Range2 choices)
|
|
if (isInputRange!Range1 && isForwardRange!Range2)
|
|
{
|
|
for (; !seq.empty && find!pred(choices, seq.front).empty; seq.popFront())
|
|
{
|
|
}
|
|
return seq;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ -1, 0, 1, 2, 3, 4, 5 ];
|
|
int[] b = [ 3, 1, 2 ];
|
|
assert(findAmong(a, b) == a[2 .. $]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ -1, 0, 2, 1, 2, 3, 4, 5 ];
|
|
int[] b = [ 1, 2, 3 ];
|
|
assert(findAmong(a, b) == [2, 1, 2, 3, 4, 5 ]);
|
|
assert(findAmong(b, [ 4, 6, 7 ][]).empty);
|
|
assert(findAmong!("a==b")(a, b).length == a.length - 2);
|
|
assert(findAmong!("a==b")(b, [ 4, 6, 7 ][]).empty);
|
|
}
|
|
|
|
// count
|
|
/**
|
|
The first version 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 haystack.length) evaluations of $(D pred).
|
|
|
|
The second version returns the number of times $(D needle) occurs in
|
|
$(D haystack). Throws an exception if $(D needle.empty), as the _count
|
|
of the empty range in any range would be infinite. Overlapped counts
|
|
are not considered, for example $(D count("aaa", "aa")) is $(D 1), not
|
|
$(D 2).
|
|
|
|
The third version counts the elements for which $(D pred(x)) is $(D
|
|
true). Performs $(BIGOH haystack.length) evaluations of $(D pred).
|
|
|
|
Note: Regardless of the overload, $(D count) will not accept
|
|
infinite ranges for $(D haystack).
|
|
*/
|
|
size_t count(alias pred = "a == b", Range, E)(Range haystack, E needle)
|
|
if (isInputRange!Range && !isInfinite!Range &&
|
|
is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
|
|
{
|
|
bool pred2(ElementType!Range a) { return binaryFun!pred(a, needle); }
|
|
return count!pred2(haystack);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.uni : toLower;
|
|
|
|
// count elements in range
|
|
int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ];
|
|
assert(count(a, 2) == 3);
|
|
assert(count!("a > b")(a, 2) == 5);
|
|
// count range in range
|
|
assert(count("abcadfabf", "ab") == 2);
|
|
assert(count("ababab", "abab") == 1);
|
|
assert(count("ababab", "abx") == 0);
|
|
// fuzzy count range in range
|
|
assert(count!((a, b) => std.uni.toLower(a) == std.uni.toLower(b))("AbcAdFaBf", "ab") == 2);
|
|
// count predicate in range
|
|
assert(count!("a > 1")(a) == 8);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ];
|
|
assert(count(a, 2) == 3, text(count(a, 2)));
|
|
assert(count!("a > b")(a, 2) == 5, text(count!("a > b")(a, 2)));
|
|
|
|
// check strings
|
|
assert(count("日本語") == 3);
|
|
assert(count("日本語"w) == 3);
|
|
assert(count("日本語"d) == 3);
|
|
|
|
assert(count!("a == '日'")("日本語") == 1);
|
|
assert(count!("a == '本'")("日本語"w) == 1);
|
|
assert(count!("a == '語'")("日本語"d) == 1);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) printf("algorithm.count.unittest\n");
|
|
string s = "This is a fofofof list";
|
|
string sub = "fof";
|
|
assert(count(s, sub) == 2);
|
|
}
|
|
|
|
/// Ditto
|
|
size_t count(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
|
|
if (isForwardRange!R1 && !isInfinite!R1 &&
|
|
isForwardRange!R2 &&
|
|
is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
|
|
{
|
|
assert(!needle.empty, "Cannot count occurrences of an empty range");
|
|
|
|
static if (isInfinite!R2)
|
|
{
|
|
//Note: This is the special case of looking for an infinite inside a finite...
|
|
//"How many instances of the Fibonacci sequence can you count in [1, 2, 3]?" - "None."
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
size_t result;
|
|
//Note: haystack is not saved, because findskip is designed to modify it
|
|
for ( ; findSkip!pred(haystack, needle.save) ; ++result)
|
|
{}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
size_t count(alias pred = "true", R)(R haystack)
|
|
if (isInputRange!R && !isInfinite!R &&
|
|
is(typeof(unaryFun!pred(haystack.front)) : bool))
|
|
{
|
|
size_t result;
|
|
alias T = ElementType!R; //For narrow strings forces dchar iteration
|
|
foreach (T elem; haystack)
|
|
if (unaryFun!pred(elem)) ++result;
|
|
return result;
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ 1, 2, 4, 3, 2, 5, 3, 2, 4 ];
|
|
assert(count!("a == 3")(a) == 2);
|
|
assert(count("日本語") == 3);
|
|
}
|
|
|
|
// Issue 11253
|
|
@safe nothrow unittest
|
|
{
|
|
assert([1, 2, 3].count([2, 3]) == 1);
|
|
}
|
|
|
|
// balancedParens
|
|
/**
|
|
Checks whether $(D r) has "balanced parentheses", i.e. all instances
|
|
of $(D lPar) are closed by corresponding instances of $(D rPar). The
|
|
parameter $(D maxNestingLevel) controls the nesting level allowed. The
|
|
most common uses are the default or $(D 0). In the latter case, no
|
|
nesting is allowed.
|
|
*/
|
|
bool balancedParens(Range, E)(Range r, E lPar, E rPar,
|
|
size_t maxNestingLevel = size_t.max)
|
|
if (isInputRange!(Range) && is(typeof(r.front == lPar)))
|
|
{
|
|
size_t count;
|
|
for (; !r.empty; r.popFront())
|
|
{
|
|
if (r.front == lPar)
|
|
{
|
|
if (count > maxNestingLevel) return false;
|
|
++count;
|
|
}
|
|
else if (r.front == rPar)
|
|
{
|
|
if (!count) return false;
|
|
--count;
|
|
}
|
|
}
|
|
return count == 0;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto s = "1 + (2 * (3 + 1 / 2)";
|
|
assert(!balancedParens(s, '(', ')'));
|
|
s = "1 + (2 * (3 + 1) / 2)";
|
|
assert(balancedParens(s, '(', ')'));
|
|
s = "1 + (2 * (3 + 1) / 2)";
|
|
assert(!balancedParens(s, '(', ')', 0));
|
|
s = "1 + (2 * 3 + 1) / (2 - 5)";
|
|
assert(balancedParens(s, '(', ')', 0));
|
|
}
|
|
|
|
// equal
|
|
/**
|
|
Compares two ranges for equality, as defined by predicate $(D pred)
|
|
(which is $(D ==) by default).
|
|
*/
|
|
template equal(alias pred = "a == b")
|
|
{
|
|
/++
|
|
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)
|
|
+/
|
|
bool equal(Range1, Range2)(Range1 r1, Range2 r2)
|
|
if (isInputRange!Range1 && isInputRange!Range2 && is(typeof(binaryFun!pred(r1.front, r2.front))))
|
|
{
|
|
//Start by detecting default pred and compatible dynamicarray.
|
|
static if (is(typeof(pred) == string) && pred == "a == b" &&
|
|
isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2)))
|
|
{
|
|
return r1 == r2;
|
|
}
|
|
//Try a fast implementation when the ranges have comparable lengths
|
|
else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length)))
|
|
{
|
|
auto len1 = r1.length;
|
|
auto len2 = r2.length;
|
|
if (len1 != len2) return false; //Short circuit return
|
|
|
|
//Lengths are the same, so we need to do an actual comparison
|
|
//Good news is we can sqeeze out a bit of performance by not checking if r2 is empty
|
|
for (; !r1.empty; r1.popFront(), r2.popFront())
|
|
{
|
|
if (!binaryFun!(pred)(r1.front, r2.front)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
//Generic case, we have to walk both ranges making sure neither is empty
|
|
for (; !r1.empty; r1.popFront(), r2.popFront())
|
|
{
|
|
if (r2.empty) return false;
|
|
if (!binaryFun!(pred)(r1.front, r2.front)) return false;
|
|
}
|
|
return r2.empty;
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.math : approxEqual;
|
|
import std.algorithm : equal;
|
|
|
|
int[] a = [ 1, 2, 4, 3 ];
|
|
assert(!equal(a, a[1..$]));
|
|
assert(equal(a, a));
|
|
|
|
// different types
|
|
double[] b = [ 1.0, 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));
|
|
}
|
|
|
|
/++
|
|
Tip: $(D equal) can itself be used as a predicate to other functions.
|
|
This can be very useful when the element type of a range is itself a
|
|
range. In particular, $(D equal) can be its own predicate, allowing
|
|
range of range (of range...) comparisons.
|
|
+/
|
|
@safe unittest
|
|
{
|
|
import std.algorithm : equal;
|
|
import std.range : iota, chunks;
|
|
assert(equal!(equal!equal)(
|
|
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]],
|
|
iota(0, 8).chunks(2).chunks(2)
|
|
));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.math : approxEqual;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
// various strings
|
|
assert(equal("æøå", "æøå")); //UTF8 vs UTF8
|
|
assert(!equal("???", "æøå")); //UTF8 vs UTF8
|
|
assert(equal("æøå"w, "æøå"d)); //UTF16 vs UTF32
|
|
assert(!equal("???"w, "æøå"d));//UTF16 vs UTF32
|
|
assert(equal("æøå"d, "æøå"d)); //UTF32 vs UTF32
|
|
assert(!equal("???"d, "æøå"d));//UTF32 vs UTF32
|
|
assert(!equal("hello", "world"));
|
|
|
|
// same strings, but "explicit non default" comparison (to test the non optimized array comparison)
|
|
assert( equal!("a==b")("æøå", "æøå")); //UTF8 vs UTF8
|
|
assert(!equal!("a==b")("???", "æøå")); //UTF8 vs UTF8
|
|
assert( equal!("a==b")("æøå"w, "æøå"d)); //UTF16 vs UTF32
|
|
assert(!equal!("a==b")("???"w, "æøå"d));//UTF16 vs UTF32
|
|
assert( equal!("a==b")("æøå"d, "æøå"d)); //UTF32 vs UTF32
|
|
assert(!equal!("a==b")("???"d, "æøå"d));//UTF32 vs UTF32
|
|
assert(!equal!("a==b")("hello", "world"));
|
|
|
|
//Array of string
|
|
assert(equal(["hello", "world"], ["hello", "world"]));
|
|
assert(!equal(["hello", "world"], ["hello"]));
|
|
assert(!equal(["hello", "world"], ["hello", "Bob!"]));
|
|
|
|
//Should not compile, because "string == dstring" is illegal
|
|
static assert(!is(typeof(equal(["hello", "world"], ["hello"d, "world"d]))));
|
|
//However, arrays of non-matching string can be compared using equal!equal. Neat-o!
|
|
equal!equal(["hello", "world"], ["hello"d, "world"d]);
|
|
|
|
//Tests, with more fancy map ranges
|
|
int[] a = [ 1, 2, 4, 3 ];
|
|
assert(equal([2, 4, 8, 6], map!"a*2"(a)));
|
|
double[] b = [ 1.0, 2, 4, 3];
|
|
double[] c = [ 1.005, 2, 4, 3];
|
|
assert(equal!approxEqual(map!"a*2"(b), map!"a*2"(c)));
|
|
assert(!equal([2, 4, 1, 3], map!"a*2"(a)));
|
|
assert(!equal([2, 4, 1], map!"a*2"(a)));
|
|
assert(!equal!approxEqual(map!"a*3"(b), map!"a*2"(c)));
|
|
|
|
//Tests with some fancy reference ranges.
|
|
ReferenceInputRange!int cir = new ReferenceInputRange!int([1, 2, 4, 3]);
|
|
ReferenceForwardRange!int cfr = new ReferenceForwardRange!int([1, 2, 4, 3]);
|
|
assert(equal(cir, a));
|
|
cir = new ReferenceInputRange!int([1, 2, 4, 3]);
|
|
assert(equal(cir, cfr.save));
|
|
assert(equal(cfr.save, cfr.save));
|
|
cir = new ReferenceInputRange!int([1, 2, 8, 1]);
|
|
assert(!equal(cir, cfr));
|
|
|
|
//Test with an infinte range
|
|
ReferenceInfiniteForwardRange!int ifr = new ReferenceInfiniteForwardRange!int;
|
|
assert(!equal(a, ifr));
|
|
}
|
|
|
|
// cmp
|
|
/**********************************
|
|
Performs three-way lexicographical comparison on two input ranges
|
|
according to predicate $(D pred). Iterating $(D r1) and $(D r2) in
|
|
lockstep, $(D cmp) compares each element $(D e1) of $(D r1) with the
|
|
corresponding element $(D e2) in $(D r2). If $(D binaryFun!pred(e1,
|
|
e2)), $(D cmp) returns a negative value. If $(D binaryFun!pred(e2,
|
|
e1)), $(D cmp) returns a positive value. If one of the ranges has been
|
|
finished, $(D cmp) returns a negative value if $(D r1) has fewer
|
|
elements than $(D r2), a positive value if $(D r1) has more elements
|
|
than $(D r2), and $(D 0) if the ranges have the same number of
|
|
elements.
|
|
|
|
If the ranges are strings, $(D cmp) performs UTF decoding
|
|
appropriately and compares the ranges one code point at a time.
|
|
*/
|
|
int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2)
|
|
if (isInputRange!R1 && isInputRange!R2 && !(isSomeString!R1 && isSomeString!R2))
|
|
{
|
|
for (;; r1.popFront(), r2.popFront())
|
|
{
|
|
if (r1.empty) return -cast(int)!r2.empty;
|
|
if (r2.empty) return !r1.empty;
|
|
auto a = r1.front, b = r2.front;
|
|
if (binaryFun!pred(a, b)) return -1;
|
|
if (binaryFun!pred(b, a)) return 1;
|
|
}
|
|
}
|
|
|
|
// Specialization for strings (for speed purposes)
|
|
int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) if (isSomeString!R1 && isSomeString!R2)
|
|
{
|
|
import core.stdc.string : memcmp;
|
|
import std.utf : decode;
|
|
|
|
static if (is(typeof(pred) : string))
|
|
enum isLessThan = pred == "a < b";
|
|
else
|
|
enum isLessThan = false;
|
|
|
|
// For speed only
|
|
static int threeWay(size_t a, size_t b)
|
|
{
|
|
static if (size_t.sizeof == int.sizeof && isLessThan)
|
|
return a - b;
|
|
else
|
|
return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0;
|
|
}
|
|
// For speed only
|
|
// @@@BUG@@@ overloading should be allowed for nested functions
|
|
static int threeWayInt(int a, int b)
|
|
{
|
|
static if (isLessThan)
|
|
return a - b;
|
|
else
|
|
return binaryFun!pred(b, a) ? 1 : binaryFun!pred(a, b) ? -1 : 0;
|
|
}
|
|
|
|
static if (typeof(r1[0]).sizeof == typeof(r2[0]).sizeof && isLessThan)
|
|
{
|
|
static if (typeof(r1[0]).sizeof == 1)
|
|
{
|
|
immutable len = min(r1.length, r2.length);
|
|
immutable result = __ctfe ?
|
|
{
|
|
foreach (i; 0 .. len)
|
|
{
|
|
if (r1[i] != r2[i])
|
|
return threeWayInt(r1[i], r2[i]);
|
|
}
|
|
return 0;
|
|
}()
|
|
: () @trusted { return core.stdc.string.memcmp(r1.ptr, r2.ptr, len); }();
|
|
if (result) return result;
|
|
}
|
|
else
|
|
{
|
|
auto p1 = r1.ptr, p2 = r2.ptr,
|
|
pEnd = p1 + min(r1.length, r2.length);
|
|
for (; p1 != pEnd; ++p1, ++p2)
|
|
{
|
|
if (*p1 != *p2) return threeWayInt(cast(int) *p1, cast(int) *p2);
|
|
}
|
|
}
|
|
return threeWay(r1.length, r2.length);
|
|
}
|
|
else
|
|
{
|
|
for (size_t i1, i2;;)
|
|
{
|
|
if (i1 == r1.length) return threeWay(i2, r2.length);
|
|
if (i2 == r2.length) return threeWay(r1.length, i1);
|
|
immutable c1 = std.utf.decode(r1, i1),
|
|
c2 = std.utf.decode(r2, i2);
|
|
if (c1 != c2) return threeWayInt(cast(int) c1, cast(int) c2);
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int result;
|
|
|
|
debug(string) printf("string.cmp.unittest\n");
|
|
result = cmp("abc", "abc");
|
|
assert(result == 0);
|
|
// result = cmp(null, null);
|
|
// assert(result == 0);
|
|
result = cmp("", "");
|
|
assert(result == 0);
|
|
result = cmp("abc", "abcd");
|
|
assert(result < 0);
|
|
result = cmp("abcd", "abc");
|
|
assert(result > 0);
|
|
result = cmp("abc"d, "abd");
|
|
assert(result < 0);
|
|
result = cmp("bbc", "abc"w);
|
|
assert(result > 0);
|
|
result = cmp("aaa", "aaaa"d);
|
|
assert(result < 0);
|
|
result = cmp("aaaa", "aaa"d);
|
|
assert(result > 0);
|
|
result = cmp("aaa", "aaa"d);
|
|
assert(result == 0);
|
|
result = cmp(cast(int[])[], cast(int[])[]);
|
|
assert(result == 0);
|
|
result = cmp([1, 2, 3], [1, 2, 3]);
|
|
assert(result == 0);
|
|
result = cmp([1, 3, 2], [1, 2, 3]);
|
|
assert(result > 0);
|
|
result = cmp([1, 2, 3], [1L, 2, 3, 4]);
|
|
assert(result < 0);
|
|
result = cmp([1L, 2, 3], [1, 2]);
|
|
assert(result > 0);
|
|
}
|
|
|
|
// MinType
|
|
private template MinType(T...)
|
|
if (T.length >= 1)
|
|
{
|
|
static if (T.length == 1)
|
|
{
|
|
alias MinType = T[0];
|
|
}
|
|
else static if (T.length == 2)
|
|
{
|
|
static if (!is(typeof(T[0].min)))
|
|
alias MinType = CommonType!T;
|
|
else
|
|
{
|
|
enum hasMostNegative = is(typeof(mostNegative!(T[0]))) &&
|
|
is(typeof(mostNegative!(T[1])));
|
|
static if (hasMostNegative && mostNegative!(T[1]) < mostNegative!(T[0]))
|
|
alias MinType = T[1];
|
|
else static if (hasMostNegative && mostNegative!(T[1]) > mostNegative!(T[0]))
|
|
alias MinType = T[0];
|
|
else static if (T[1].max < T[0].max)
|
|
alias MinType = T[1];
|
|
else
|
|
alias MinType = T[0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
alias MinType = MinType!(MinType!(T[0 .. ($+1)/2]), MinType!(T[($+1)/2 .. $]));
|
|
}
|
|
}
|
|
|
|
// min
|
|
/**
|
|
Returns the minimum of the passed-in values.
|
|
*/
|
|
MinType!T min(T...)(T args)
|
|
if (T.length >= 2)
|
|
{
|
|
//Get "a"
|
|
static if (T.length <= 2)
|
|
alias args[0] a;
|
|
else
|
|
auto a = min(args[0 .. ($+1)/2]);
|
|
alias typeof(a) T0;
|
|
|
|
//Get "b"
|
|
static if (T.length <= 3)
|
|
alias args[$-1] b;
|
|
else
|
|
auto b = min(args[($+1)/2 .. $]);
|
|
alias typeof(b) T1;
|
|
|
|
static assert (is(typeof(a < b)),
|
|
algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof));
|
|
|
|
//Do the "min" proper with a and b
|
|
import std.functional : lessThan;
|
|
immutable chooseA = lessThan!(T0, T1)(a, b);
|
|
return cast(typeof(return)) (chooseA ? a : b);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int a = 5;
|
|
short b = 6;
|
|
double c = 2;
|
|
auto d = min(a, b);
|
|
static assert(is(typeof(d) == int));
|
|
assert(d == 5);
|
|
auto e = min(a, b, c);
|
|
static assert(is(typeof(e) == double));
|
|
assert(e == 2);
|
|
// mixed signedness test
|
|
a = -10;
|
|
uint f = 10;
|
|
static assert(is(typeof(min(a, f)) == int));
|
|
assert(min(a, f) == -10);
|
|
|
|
//Test user-defined types
|
|
import std.datetime;
|
|
assert(min(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(1982, 1, 4));
|
|
assert(min(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(1982, 1, 4));
|
|
assert(min(Date(1982, 1, 4), Date.min) == Date.min);
|
|
assert(min(Date.min, Date(1982, 1, 4)) == Date.min);
|
|
assert(min(Date(1982, 1, 4), Date.max) == Date(1982, 1, 4));
|
|
assert(min(Date.max, Date(1982, 1, 4)) == Date(1982, 1, 4));
|
|
assert(min(Date.min, Date.max) == Date.min);
|
|
assert(min(Date.max, Date.min) == Date.min);
|
|
}
|
|
|
|
// MaxType
|
|
private template MaxType(T...)
|
|
if (T.length >= 1)
|
|
{
|
|
static if (T.length == 1)
|
|
{
|
|
alias MaxType = T[0];
|
|
}
|
|
else static if (T.length == 2)
|
|
{
|
|
static if (!is(typeof(T[0].min)))
|
|
alias MaxType = CommonType!T;
|
|
else static if (T[1].max > T[0].max)
|
|
alias MaxType = T[1];
|
|
else
|
|
alias MaxType = T[0];
|
|
}
|
|
else
|
|
{
|
|
alias MaxType = MaxType!(MaxType!(T[0 .. ($+1)/2]), MaxType!(T[($+1)/2 .. $]));
|
|
}
|
|
}
|
|
|
|
// max
|
|
/**
|
|
Returns the maximum of the passed-in values.
|
|
*/
|
|
MaxType!T max(T...)(T args)
|
|
if (T.length >= 2)
|
|
{
|
|
//Get "a"
|
|
static if (T.length <= 2)
|
|
alias args[0] a;
|
|
else
|
|
auto a = max(args[0 .. ($+1)/2]);
|
|
alias typeof(a) T0;
|
|
|
|
//Get "b"
|
|
static if (T.length <= 3)
|
|
alias args[$-1] b;
|
|
else
|
|
auto b = max(args[($+1)/2 .. $]);
|
|
alias typeof(b) T1;
|
|
|
|
static assert (is(typeof(a < b)),
|
|
algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof));
|
|
|
|
//Do the "max" proper with a and b
|
|
import std.functional : lessThan;
|
|
immutable chooseB = lessThan!(T0, T1)(a, b);
|
|
return cast(typeof(return)) (chooseB ? b : a);
|
|
}
|
|
|
|
///
|
|
@safe 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 = min(a, b, c);
|
|
assert(is(typeof(e) == double));
|
|
assert(e == 2);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int a = 5;
|
|
short b = 6;
|
|
double c = 2;
|
|
auto d = max(a, b);
|
|
static assert(is(typeof(d) == int));
|
|
assert(d == 6);
|
|
auto e = max(a, b, c);
|
|
static assert(is(typeof(e) == double));
|
|
assert(e == 6);
|
|
// mixed sign
|
|
a = -5;
|
|
uint f = 5;
|
|
static assert(is(typeof(max(a, f)) == uint));
|
|
assert(max(a, f) == 5);
|
|
|
|
//Test user-defined types
|
|
import std.datetime;
|
|
assert(max(Date(2012, 12, 21), Date(1982, 1, 4)) == Date(2012, 12, 21));
|
|
assert(max(Date(1982, 1, 4), Date(2012, 12, 21)) == Date(2012, 12, 21));
|
|
assert(max(Date(1982, 1, 4), Date.min) == Date(1982, 1, 4));
|
|
assert(max(Date.min, Date(1982, 1, 4)) == Date(1982, 1, 4));
|
|
assert(max(Date(1982, 1, 4), Date.max) == Date.max);
|
|
assert(max(Date.max, Date(1982, 1, 4)) == Date.max);
|
|
assert(max(Date.min, Date.max) == Date.max);
|
|
assert(max(Date.max, Date.min) == Date.max);
|
|
}
|
|
|
|
/**
|
|
Returns $(D val), if it is between $(D lower) and $(D upper).
|
|
Otherwise returns the nearest of the two. Equivalent to $(D max(lower,
|
|
min(upper,val))).
|
|
*/
|
|
auto clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper)
|
|
in
|
|
{
|
|
import std.functional : greaterThan;
|
|
assert(!lower.greaterThan(upper));
|
|
}
|
|
body
|
|
{
|
|
return max(lower, min(upper, val));
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(clamp(2, 1, 3) == 2);
|
|
assert(clamp(0, 1, 3) == 1);
|
|
assert(clamp(4, 1, 3) == 3);
|
|
|
|
assert(clamp(1, 1, 1) == 1);
|
|
|
|
assert(clamp(5, -1, 2u) == 2);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int a = 1;
|
|
short b = 6;
|
|
double c = 2;
|
|
static assert(is(typeof(clamp(c,a,b)) == double));
|
|
assert(clamp(c, a, b) == c);
|
|
assert(clamp(a-c, a, b) == a);
|
|
assert(clamp(b+c, a, b) == b);
|
|
// mixed sign
|
|
a = -5;
|
|
uint f = 5;
|
|
static assert(is(typeof(clamp(f, a, b)) == int));
|
|
assert(clamp(f, a, b) == f);
|
|
// similar type deduction for (u)long
|
|
static assert(is(typeof(clamp(-1L, -2L, 2UL)) == long));
|
|
|
|
// user-defined types
|
|
import std.datetime;
|
|
assert(clamp(Date(1982, 1, 4), Date(1012, 12, 21), Date(2012, 12, 21)) == Date(1982, 1, 4));
|
|
assert(clamp(Date(1982, 1, 4), Date.min, Date.max) == Date(1982, 1, 4));
|
|
// UFCS style
|
|
assert(Date(1982, 1, 4).clamp(Date.min, Date.max) == Date(1982, 1, 4));
|
|
|
|
}
|
|
|
|
/**
|
|
Returns the minimum element of a range together with the number of
|
|
occurrences. The function can actually be used for counting the
|
|
maximum or any other ordering predicate (that's why $(D maxCount) is
|
|
not provided).
|
|
*/
|
|
Tuple!(ElementType!Range, size_t)
|
|
minCount(alias pred = "a < b", Range)(Range range)
|
|
if (isInputRange!Range && !isInfinite!Range &&
|
|
is(typeof(binaryFun!pred(range.front, range.front))))
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
alias T = ElementType!Range;
|
|
alias UT = Unqual!T;
|
|
alias RetType = Tuple!(T, size_t);
|
|
|
|
static assert (is(typeof(RetType(range.front, 1))),
|
|
algoFormat("Error: Cannot call minCount on a %s, because it is not possible "~
|
|
"to copy the result value (a %s) into a Tuple.", Range.stringof, T.stringof));
|
|
|
|
enforce(!range.empty, "Can't count elements from an empty range");
|
|
size_t occurrences = 1;
|
|
|
|
static if (isForwardRange!Range)
|
|
{
|
|
Range least = range.save;
|
|
for (range.popFront(); !range.empty; range.popFront())
|
|
{
|
|
if (binaryFun!pred(least.front, range.front)) continue;
|
|
if (binaryFun!pred(range.front, least.front))
|
|
{
|
|
// change the min
|
|
least = range.save;
|
|
occurrences = 1;
|
|
}
|
|
else
|
|
++occurrences;
|
|
}
|
|
return RetType(least.front, occurrences);
|
|
}
|
|
else static if (isAssignable!(UT, T) || (!hasElaborateAssign!UT && isAssignable!UT))
|
|
{
|
|
UT v = UT.init;
|
|
static if (isAssignable!(UT, T)) v = range.front;
|
|
else v = cast(UT)range.front;
|
|
|
|
for (range.popFront(); !range.empty; range.popFront())
|
|
{
|
|
if (binaryFun!pred(*cast(T*)&v, range.front)) continue;
|
|
if (binaryFun!pred(range.front, *cast(T*)&v))
|
|
{
|
|
// change the min
|
|
static if (isAssignable!(UT, T)) v = range.front;
|
|
else v = cast(UT)range.front; //Safe because !hasElaborateAssign!UT
|
|
occurrences = 1;
|
|
}
|
|
else
|
|
++occurrences;
|
|
}
|
|
return RetType(*cast(T*)&v, occurrences);
|
|
}
|
|
else static if (hasLvalueElements!Range)
|
|
{
|
|
T* p = addressOf(range.front);
|
|
for (range.popFront(); !range.empty; range.popFront())
|
|
{
|
|
if (binaryFun!pred(*p, range.front)) continue;
|
|
if (binaryFun!pred(range.front, *p))
|
|
{
|
|
// change the min
|
|
p = addressOf(range.front);
|
|
occurrences = 1;
|
|
}
|
|
else
|
|
++occurrences;
|
|
}
|
|
return RetType(*p, occurrences);
|
|
}
|
|
else
|
|
static assert(false,
|
|
algoFormat("Sorry, can't find the minCount of a %s: Don't know how "~
|
|
"to keep track of the smallest %s element.", Range.stringof, T.stringof));
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
|
|
// Minimum is 1 and occurs 3 times
|
|
assert(minCount(a) == tuple(1, 3));
|
|
// Maximum is 4 and occurs 2 times
|
|
assert(minCount!("a > b")(a) == tuple(4, 2));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.conv : text;
|
|
import std.exception : assertThrown;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
int[][] b = [ [4], [2, 4], [4], [4] ];
|
|
auto c = minCount!("a[0] < b[0]")(b);
|
|
assert(c == tuple([2, 4], 1), text(c[0]));
|
|
|
|
//Test empty range
|
|
assertThrown(minCount(b[$..$]));
|
|
|
|
//test with reference ranges. Test both input and forward.
|
|
assert(minCount(new ReferenceInputRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2));
|
|
assert(minCount(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])) == tuple(0, 2));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
static struct R(T) //input range
|
|
{
|
|
T[] arr;
|
|
alias arr this;
|
|
}
|
|
|
|
immutable a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
|
|
R!(immutable int) b = R!(immutable int)(a);
|
|
|
|
assert(minCount(a) == tuple(1, 3));
|
|
assert(minCount(b) == tuple(1, 3));
|
|
assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(a) == tuple(4, 2));
|
|
assert(minCount!((ref immutable int a, ref immutable int b) => (a > b))(b) == tuple(4, 2));
|
|
|
|
immutable(int[])[] c = [ [4], [2, 4], [4], [4] ];
|
|
assert(minCount!("a[0] < b[0]")(c) == tuple([2, 4], 1), text(c[0]));
|
|
|
|
static struct S1
|
|
{
|
|
int i;
|
|
}
|
|
alias IS1 = immutable(S1);
|
|
static assert( isAssignable!S1);
|
|
static assert( isAssignable!(S1, IS1));
|
|
|
|
static struct S2
|
|
{
|
|
int* p;
|
|
this(ref immutable int i) immutable {p = &i;}
|
|
this(ref int i) {p = &i;}
|
|
@property ref inout(int) i() inout {return *p;}
|
|
bool opEquals(const S2 other) const {return i == other.i;}
|
|
}
|
|
alias IS2 = immutable(S2);
|
|
static assert( isAssignable!S2);
|
|
static assert(!isAssignable!(S2, IS2));
|
|
static assert(!hasElaborateAssign!S2);
|
|
|
|
static struct S3
|
|
{
|
|
int i;
|
|
void opAssign(ref S3 other) @disable;
|
|
}
|
|
static assert(!isAssignable!S3);
|
|
|
|
foreach (Type; TypeTuple!(S1, IS1, S2, IS2, S3))
|
|
{
|
|
static if (is(Type == immutable)) alias V = immutable int;
|
|
else alias V = int;
|
|
V one = 1, two = 2;
|
|
auto r1 = [Type(two), Type(one), Type(one)];
|
|
auto r2 = R!Type(r1);
|
|
assert(minCount!"a.i < b.i"(r1) == tuple(Type(one), 2));
|
|
assert(minCount!"a.i < b.i"(r2) == tuple(Type(one), 2));
|
|
assert(one == 1 && two == 2);
|
|
}
|
|
}
|
|
|
|
// minPos
|
|
/**
|
|
Returns the position of the minimum element of forward range $(D
|
|
range), i.e. a subrange of $(D range) starting at the position of its
|
|
smallest element and with the same ending as $(D range). The function
|
|
can actually be used for finding the maximum or any other ordering
|
|
predicate (that's why $(D maxPos) is not provided).
|
|
*/
|
|
Range minPos(alias pred = "a < b", Range)(Range range)
|
|
if (isForwardRange!Range && !isInfinite!Range &&
|
|
is(typeof(binaryFun!pred(range.front, range.front))))
|
|
{
|
|
if (range.empty) return range;
|
|
auto result = range.save;
|
|
|
|
for (range.popFront(); !range.empty; range.popFront())
|
|
{
|
|
//Note: Unlike minCount, we do not care to find equivalence, so a single pred call is enough
|
|
if (binaryFun!pred(range.front, result.front))
|
|
{
|
|
// change the min
|
|
result = range.save;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
|
|
// Minimum is 1 and first occurs in position 3
|
|
assert(minPos(a) == [ 1, 2, 4, 1, 1, 2 ]);
|
|
// Maximum is 4 and first occurs in position 2
|
|
assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
|
|
//Test that an empty range works
|
|
int[] b = a[$..$];
|
|
assert(equal(minPos(b), b));
|
|
|
|
//test with reference range.
|
|
assert( equal( minPos(new ReferenceForwardRange!int([1, 2, 1, 0, 2, 0])), [0, 2, 0] ) );
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//Rvalue range
|
|
import std.container : Array;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
assert(Array!int(2, 3, 4, 1, 2, 4, 1, 1, 2)
|
|
[]
|
|
.minPos()
|
|
.equal([ 1, 2, 4, 1, 1, 2 ]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
//BUG 9299
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
immutable a = [ 2, 3, 4, 1, 2, 4, 1, 1, 2 ];
|
|
// Minimum is 1 and first occurs in position 3
|
|
assert(minPos(a) == [ 1, 2, 4, 1, 1, 2 ]);
|
|
// Maximum is 4 and first occurs in position 5
|
|
assert(minPos!("a > b")(a) == [ 4, 1, 2, 4, 1, 1, 2 ]);
|
|
|
|
immutable(int[])[] b = [ [4], [2, 4], [4], [4] ];
|
|
assert(minPos!("a[0] < b[0]")(b) == [ [2, 4], [4], [4] ]);
|
|
}
|
|
|
|
// 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 reduced ranges that start with 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)
|
|
*/
|
|
Tuple!(Range1, Range2)
|
|
mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2)
|
|
if (isInputRange!(Range1) && isInputRange!(Range2))
|
|
{
|
|
for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront())
|
|
{
|
|
if (!binaryFun!(pred)(r1.front, r2.front)) break;
|
|
}
|
|
return tuple(r1, r2);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] x = [ 1, 5, 2, 7, 4, 3 ];
|
|
double[] y = [ 1.0, 5, 2, 7.3, 4, 8 ];
|
|
auto m = mismatch(x, y);
|
|
assert(m[0] == x[3 .. $]);
|
|
assert(m[1] == y[3 .. $]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
int[] a = [ 1, 2, 3 ];
|
|
int[] b = [ 1, 2, 4, 5 ];
|
|
auto mm = mismatch(a, b);
|
|
assert(mm[0] == [3]);
|
|
assert(mm[1] == [4, 5]);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
auto slen = walkLength(s.save), tlen = walkLength(t.save);
|
|
AllocMatrix(slen + 1, tlen + 1);
|
|
foreach (i; 1 .. rows)
|
|
{
|
|
auto sfront = s.front;
|
|
s.popFront();
|
|
auto tt = t;
|
|
foreach (j; 1 .. cols)
|
|
{
|
|
auto cSub = matrix(i - 1,j - 1)
|
|
+ (equals(sfront, tt.front) ? 0 : _substitutionIncrement);
|
|
tt.popFront();
|
|
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(slen,tlen);
|
|
}
|
|
|
|
EditOp[] path(Range s, Range t)
|
|
{
|
|
distanceWithPath(s, t);
|
|
return path();
|
|
}
|
|
|
|
EditOp[] path()
|
|
{
|
|
EditOp[] result;
|
|
size_t 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;
|
|
size_t rows, cols;
|
|
|
|
// Treat _matrix as a rectangular array
|
|
ref CostType matrix(size_t row, size_t col) { return _matrix[row * cols + col]; }
|
|
|
|
void AllocMatrix(size_t r, size_t c) {
|
|
rows = r;
|
|
cols = c;
|
|
if (_matrix.length < r * c) {
|
|
delete _matrix;
|
|
_matrix = new CostType[r * c];
|
|
InitMatrix();
|
|
}
|
|
}
|
|
|
|
void InitMatrix() {
|
|
foreach (r; 0 .. rows)
|
|
matrix(r,0) = r * _deletionIncrement;
|
|
foreach (c; 0 .. cols)
|
|
matrix(0,c) = c * _insertionIncrement;
|
|
}
|
|
|
|
static uint min_index(CostType i0, CostType i1, CostType i2)
|
|
{
|
|
if (i0 <= i1)
|
|
{
|
|
return i0 <= i2 ? 0 : 2;
|
|
}
|
|
else
|
|
{
|
|
return i1 <= i2 ? 1 : 2;
|
|
}
|
|
}
|
|
|
|
CostType distanceWithPath(Range s, Range t)
|
|
{
|
|
auto slen = walkLength(s.save), tlen = walkLength(t.save);
|
|
AllocMatrix(slen + 1, tlen + 1);
|
|
foreach (i; 1 .. rows)
|
|
{
|
|
auto sfront = s.front;
|
|
auto tt = t.save;
|
|
foreach (j; 1 .. cols)
|
|
{
|
|
auto cSub = matrix(i - 1,j - 1)
|
|
+ (equals(sfront, tt.front) ? 0 : _substitutionIncrement);
|
|
tt.popFront();
|
|
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;
|
|
}
|
|
}
|
|
s.popFront();
|
|
}
|
|
return matrix(slen,tlen);
|
|
}
|
|
|
|
CostType distanceLowMem(Range s, Range t, CostType slen, CostType tlen)
|
|
{
|
|
CostType lastdiag, olddiag;
|
|
AllocMatrix(slen + 1, 1);
|
|
foreach (y; 1 .. slen + 1)
|
|
{
|
|
_matrix[y] = y;
|
|
}
|
|
foreach (x; 1 .. tlen + 1)
|
|
{
|
|
auto tfront = t.front;
|
|
auto ss = s.save;
|
|
_matrix[0] = x;
|
|
lastdiag = x - 1;
|
|
foreach (y; 1 .. rows)
|
|
{
|
|
olddiag = _matrix[y];
|
|
auto cSub = lastdiag + (equals(ss.front, tfront) ? 0 : _substitutionIncrement);
|
|
ss.popFront();
|
|
auto cIns = _matrix[y - 1] + _insertionIncrement;
|
|
auto cDel = _matrix[y] + _deletionIncrement;
|
|
switch (min_index(cSub, cIns, cDel))
|
|
{
|
|
case 0:
|
|
_matrix[y] = cSub;
|
|
break;
|
|
case 1:
|
|
_matrix[y] = cIns;
|
|
break;
|
|
default:
|
|
_matrix[y] = cDel;
|
|
break;
|
|
}
|
|
lastdiag = olddiag;
|
|
}
|
|
t.popFront();
|
|
}
|
|
return _matrix[slen];
|
|
}
|
|
}
|
|
|
|
/**
|
|
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.
|
|
|
|
Allocates GC memory.
|
|
*/
|
|
size_t levenshteinDistance(alias equals = "a == b", Range1, Range2)
|
|
(Range1 s, Range2 t)
|
|
if (isForwardRange!(Range1) && isForwardRange!(Range2))
|
|
{
|
|
Levenshtein!(Range1, binaryFun!(equals), size_t) lev;
|
|
auto slen = walkLength(s.save);
|
|
auto tlen = walkLength(t.save);
|
|
if (slen > tlen)
|
|
{
|
|
return lev.distanceLowMem(s, t, slen, tlen);
|
|
}
|
|
else
|
|
{
|
|
return lev.distanceLowMem(t, s, tlen, slen);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.uni : toUpper;
|
|
|
|
assert(levenshteinDistance("cat", "rat") == 1);
|
|
assert(levenshteinDistance("parks", "spark") == 2);
|
|
assert(levenshteinDistance("kitten", "sitting") == 3);
|
|
assert(levenshteinDistance!((a, b) => std.uni.toUpper(a) == std.uni.toUpper(b))
|
|
("parks", "SPARK") == 2);
|
|
assert(levenshteinDistance("parks".filter!"true", "spark".filter!"true") == 2);
|
|
assert(levenshteinDistance("ID", "I♥D") == 1);
|
|
}
|
|
|
|
/**
|
|
Returns the Levenshtein distance and the edit path between $(D s) and
|
|
$(D t).
|
|
|
|
Allocates GC memory.
|
|
*/
|
|
Tuple!(size_t, EditOp[])
|
|
levenshteinDistanceAndPath(alias equals = "a == b", Range1, Range2)
|
|
(Range1 s, Range2 t)
|
|
if (isForwardRange!(Range1) && isForwardRange!(Range2))
|
|
{
|
|
Levenshtein!(Range1, binaryFun!(equals)) lev;
|
|
auto d = lev.distanceWithPath(s, t);
|
|
return tuple(d, lev.path());
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
string a = "Saturday", b = "Sunday";
|
|
auto p = levenshteinDistanceAndPath(a, b);
|
|
assert(p[0] == 3);
|
|
assert(equal(p[1], "nrrnsnnn"));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
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);
|
|
}
|
|
|
|
// copy
|
|
/**
|
|
Copies the content of $(D source) into $(D target) and returns the
|
|
remaining (unfilled) part of $(D target).
|
|
|
|
Preconditions: $(D target) shall have enough room to accomodate
|
|
$(D source).
|
|
|
|
See_Also:
|
|
$(WEB sgi.com/tech/stl/_copy.html, STL's _copy)
|
|
*/
|
|
Range2 copy(Range1, Range2)(Range1 source, Range2 target)
|
|
if (isInputRange!Range1 && isOutputRange!(Range2, ElementType!Range1))
|
|
{
|
|
static Range2 genericImpl(Range1 source, Range2 target)
|
|
{
|
|
// Specialize for 2 random access ranges.
|
|
// Typically 2 random access ranges are faster iterated by common
|
|
// index then by x.popFront(), y.popFront() pair
|
|
static if (isRandomAccessRange!Range1 && hasLength!Range1
|
|
&& hasSlicing!Range2 && isRandomAccessRange!Range2 && hasLength!Range2)
|
|
{
|
|
assert(target.length >= source.length,
|
|
"Cannot copy a source range into a smaller target range.");
|
|
|
|
auto len = source.length;
|
|
foreach (idx; 0 .. len)
|
|
target[idx] = source[idx];
|
|
return target[len .. target.length];
|
|
}
|
|
else
|
|
{
|
|
put(target, source);
|
|
return target;
|
|
}
|
|
}
|
|
|
|
static if (isArray!Range1 && isArray!Range2 &&
|
|
is(Unqual!(typeof(source[0])) == Unqual!(typeof(target[0]))))
|
|
{
|
|
immutable overlaps = () @trusted {
|
|
return source.ptr < target.ptr + target.length &&
|
|
target.ptr < source.ptr + source.length; }();
|
|
|
|
if (overlaps)
|
|
{
|
|
return genericImpl(source, target);
|
|
}
|
|
else
|
|
{
|
|
// Array specialization. This uses optimized memory copying
|
|
// routines under the hood and is about 10-20x faster than the
|
|
// generic implementation.
|
|
assert(target.length >= source.length,
|
|
"Cannot copy a source array into a smaller target array.");
|
|
target[0..source.length] = source[];
|
|
|
|
return target[source.length..$];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return genericImpl(source, target);
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 5 ];
|
|
int[] b = [ 9, 8 ];
|
|
int[] buf = new int[a.length + b.length + 10];
|
|
auto rem = copy(a, buf); // copy a into buf
|
|
rem = copy(b, rem); // copy b into remainder of buf
|
|
assert(buf[0 .. a.length + b.length] == [1, 5, 9, 8]);
|
|
assert(rem.length == 10); // unused slots in buf
|
|
}
|
|
|
|
/**
|
|
As long as the target range elements support assignment from source
|
|
range elements, different types of ranges are accepted:
|
|
*/
|
|
@safe unittest
|
|
{
|
|
float[] src = [ 1.0f, 5 ];
|
|
double[] dest = new double[src.length];
|
|
copy(src, dest);
|
|
}
|
|
|
|
/**
|
|
To _copy at most $(D n) elements from a range, you may want to use
|
|
$(XREF range, take):
|
|
*/
|
|
@safe unittest
|
|
{
|
|
int[] src = [ 1, 5, 8, 9, 10 ];
|
|
auto dest = new int[3];
|
|
copy(take(src, dest.length), dest);
|
|
assert(dest[0 .. $] == [ 1, 5, 8 ]);
|
|
}
|
|
|
|
/**
|
|
To _copy just those elements from a range that satisfy a predicate you
|
|
may want to use $(LREF filter):
|
|
*/
|
|
@safe unittest
|
|
{
|
|
int[] src = [ 1, 5, 8, 9, 10, 1, 2, 0 ];
|
|
auto dest = new int[src.length];
|
|
auto rem = copy(src.filter!(a => (a & 1) == 1), dest);
|
|
assert(dest[0 .. $ - rem.length] == [ 1, 5, 9, 1 ]);
|
|
}
|
|
|
|
/**
|
|
$(XREF range, retro) can be used to achieve behavior similar to
|
|
$(WEB sgi.com/tech/stl/copy_backward.html, STL's copy_backward'):
|
|
*/
|
|
@safe unittest
|
|
{
|
|
import std.algorithm, std.range;
|
|
int[] src = [1, 2, 4];
|
|
int[] dest = [0, 0, 0, 0, 0];
|
|
copy(src.retro, dest.retro);
|
|
assert(dest == [0, 0, 1, 2, 4]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
{
|
|
int[] a = [ 1, 5 ];
|
|
int[] b = [ 9, 8 ];
|
|
auto e = copy(filter!("a > 1")(a), b);
|
|
assert(b[0] == 5 && e.length == 1);
|
|
}
|
|
|
|
{
|
|
int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
copy(a[5..10], a[4..9]);
|
|
assert(a[4..9] == [6, 7, 8, 9, 10]);
|
|
}
|
|
|
|
{ // Test for bug 7898
|
|
enum v =
|
|
{
|
|
import std.algorithm;
|
|
int[] arr1 = [10, 20, 30, 40, 50];
|
|
int[] arr2 = arr1.dup;
|
|
copy(arr1, arr2);
|
|
return 35;
|
|
}();
|
|
}
|
|
}
|
|
|
|
// swapRanges
|
|
/**
|
|
Swaps all elements of $(D r1) with successive elements in $(D r2).
|
|
Returns a tuple containing the remainder portions of $(D r1) and $(D
|
|
r2) that were not swapped (one of them will be empty). The ranges may
|
|
be of different types but must have the same element type and support
|
|
swapping.
|
|
*/
|
|
Tuple!(Range1, Range2)
|
|
swapRanges(Range1, Range2)(Range1 r1, Range2 r2)
|
|
if (isInputRange!(Range1) && isInputRange!(Range2)
|
|
&& hasSwappableElements!(Range1) && hasSwappableElements!(Range2)
|
|
&& is(ElementType!(Range1) == ElementType!(Range2)))
|
|
{
|
|
for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront())
|
|
{
|
|
swap(r1.front, r2.front);
|
|
}
|
|
return tuple(r1, r2);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 100, 101, 102, 103 ];
|
|
int[] b = [ 0, 1, 2, 3 ];
|
|
auto c = swapRanges(a[1 .. 3], b[2 .. 4]);
|
|
assert(c[0].empty && c[1].empty);
|
|
assert(a == [ 100, 2, 3, 103 ]);
|
|
assert(b == [ 0, 1, 101, 102 ]);
|
|
}
|
|
|
|
// reverse
|
|
/**
|
|
Reverses $(D r) in-place. Performs $(D r.length / 2) evaluations of $(D
|
|
swap).
|
|
|
|
See_Also:
|
|
$(WEB sgi.com/tech/stl/_reverse.html, STL's _reverse)
|
|
*/
|
|
void reverse(Range)(Range r)
|
|
if (isBidirectionalRange!Range && !isRandomAccessRange!Range
|
|
&& hasSwappableElements!Range)
|
|
{
|
|
while (!r.empty)
|
|
{
|
|
swap(r.front, r.back);
|
|
r.popFront();
|
|
if (r.empty) break;
|
|
r.popBack();
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] arr = [ 1, 2, 3 ];
|
|
reverse(arr);
|
|
assert(arr == [ 3, 2, 1 ]);
|
|
}
|
|
|
|
///ditto
|
|
void reverse(Range)(Range r)
|
|
if (isRandomAccessRange!Range && hasLength!Range)
|
|
{
|
|
//swapAt is in fact the only way to swap non lvalue ranges
|
|
immutable last = r.length-1;
|
|
immutable steps = r.length/2;
|
|
for (size_t i = 0; i < steps; i++)
|
|
{
|
|
swapAt(r, i, last-i);
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] range = null;
|
|
reverse(range);
|
|
range = [ 1 ];
|
|
reverse(range);
|
|
assert(range == [1]);
|
|
range = [1, 2];
|
|
reverse(range);
|
|
assert(range == [2, 1]);
|
|
range = [1, 2, 3];
|
|
reverse(range);
|
|
assert(range == [3, 2, 1]);
|
|
}
|
|
|
|
/**
|
|
Reverses $(D r) in-place, where $(D r) is a narrow string (having
|
|
elements of type $(D char) or $(D wchar)). UTF sequences consisting of
|
|
multiple code units are preserved properly.
|
|
*/
|
|
void reverse(Char)(Char[] s)
|
|
if (isNarrowString!(Char[]) && !is(Char == const) && !is(Char == immutable))
|
|
{
|
|
import std.string : representation;
|
|
import std.utf : stride;
|
|
|
|
auto r = representation(s);
|
|
for (size_t i = 0; i < s.length; )
|
|
{
|
|
immutable step = std.utf.stride(s, i);
|
|
if (step > 1)
|
|
{
|
|
.reverse(r[i .. i + step]);
|
|
i += step;
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
reverse(r);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
char[] arr = "hello\U00010143\u0100\U00010143".dup;
|
|
reverse(arr);
|
|
assert(arr == "\U00010143\u0100\U00010143olleh");
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
void test(string a, string b)
|
|
{
|
|
auto c = a.dup;
|
|
reverse(c);
|
|
assert(c == b, c ~ " != " ~ b);
|
|
}
|
|
|
|
test("a", "a");
|
|
test(" ", " ");
|
|
test("\u2029", "\u2029");
|
|
test("\u0100", "\u0100");
|
|
test("\u0430", "\u0430");
|
|
test("\U00010143", "\U00010143");
|
|
test("abcdefcdef", "fedcfedcba");
|
|
test("hello\U00010143\u0100\U00010143", "\U00010143\u0100\U00010143olleh");
|
|
}
|
|
|
|
/**
|
|
The strip group of functions allow stripping of either leading, trailing,
|
|
or both leading and trailing elements.
|
|
|
|
The $(D stripLeft) function will strip the $(D front) of the range,
|
|
the $(D stripRight) function will strip the $(D back) of the range,
|
|
while the $(D strip) function will strip both the $(D front) and $(D back)
|
|
of the range.
|
|
|
|
Note that the $(D strip) and $(D stripRight) functions require the range to
|
|
be a $(LREF BidirectionalRange) range.
|
|
|
|
All of these functions come in two varieties: one takes a target element,
|
|
where the range will be stripped as long as this element can be found.
|
|
The other takes a lambda predicate, where the range will be stripped as
|
|
long as the predicate returns true.
|
|
*/
|
|
Range strip(Range, E)(Range range, E element)
|
|
if (isBidirectionalRange!Range && is(typeof(range.front == element) : bool))
|
|
{
|
|
return range.stripLeft(element).stripRight(element);
|
|
}
|
|
|
|
/// ditto
|
|
Range strip(alias pred, Range)(Range range)
|
|
if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool))
|
|
{
|
|
return range.stripLeft!pred().stripRight!pred();
|
|
}
|
|
|
|
/// ditto
|
|
Range stripLeft(Range, E)(Range range, E element)
|
|
if (isInputRange!Range && is(typeof(range.front == element) : bool))
|
|
{
|
|
return find!((auto ref a) => a != element)(range);
|
|
}
|
|
|
|
/// ditto
|
|
Range stripLeft(alias pred, Range)(Range range)
|
|
if (isInputRange!Range && is(typeof(pred(range.front)) : bool))
|
|
{
|
|
import std.functional : not;
|
|
|
|
return find!(not!pred)(range);
|
|
}
|
|
|
|
/// ditto
|
|
Range stripRight(Range, E)(Range range, E element)
|
|
if (isBidirectionalRange!Range && is(typeof(range.back == element) : bool))
|
|
{
|
|
for (; !range.empty; range.popBack())
|
|
{
|
|
if (range.back != element)
|
|
break;
|
|
}
|
|
return range;
|
|
}
|
|
|
|
/// ditto
|
|
Range stripRight(alias pred, Range)(Range range)
|
|
if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool))
|
|
{
|
|
for (; !range.empty; range.popBack())
|
|
{
|
|
if (!pred(range.back))
|
|
break;
|
|
}
|
|
return range;
|
|
}
|
|
|
|
/// Strip leading and trailing elements equal to the target element.
|
|
@safe pure unittest
|
|
{
|
|
assert(" foobar ".strip(' ') == "foobar");
|
|
assert("00223.444500".strip('0') == "223.4445");
|
|
assert("ëëêéüŗōpéêëë".strip('ë') == "êéüŗōpéê");
|
|
assert([1, 1, 0, 1, 1].strip(1) == [0]);
|
|
assert([0.0, 0.01, 0.01, 0.0].strip(0).length == 2);
|
|
}
|
|
|
|
/// Strip leading and trailing elements while the predicate returns true.
|
|
@safe pure unittest
|
|
{
|
|
assert(" foobar ".strip!(a => a == ' ')() == "foobar");
|
|
assert("00223.444500".strip!(a => a == '0')() == "223.4445");
|
|
assert("ëëêéüŗōpéêëë".strip!(a => a == 'ë')() == "êéüŗōpéê");
|
|
assert([1, 1, 0, 1, 1].strip!(a => a == 1)() == [0]);
|
|
assert([0.0, 0.01, 0.5, 0.6, 0.01, 0.0].strip!(a => a < 0.4)().length == 2);
|
|
}
|
|
|
|
/// Strip leading elements equal to the target element.
|
|
@safe pure unittest
|
|
{
|
|
assert(" foobar ".stripLeft(' ') == "foobar ");
|
|
assert("00223.444500".stripLeft('0') == "223.444500");
|
|
assert("ůůűniçodêéé".stripLeft('ů') == "űniçodêéé");
|
|
assert([1, 1, 0, 1, 1].stripLeft(1) == [0, 1, 1]);
|
|
assert([0.0, 0.01, 0.01, 0.0].stripLeft(0).length == 3);
|
|
}
|
|
|
|
/// Strip leading elements while the predicate returns true.
|
|
@safe pure unittest
|
|
{
|
|
assert(" foobar ".stripLeft!(a => a == ' ')() == "foobar ");
|
|
assert("00223.444500".stripLeft!(a => a == '0')() == "223.444500");
|
|
assert("ůůűniçodêéé".stripLeft!(a => a == 'ů')() == "űniçodêéé");
|
|
assert([1, 1, 0, 1, 1].stripLeft!(a => a == 1)() == [0, 1, 1]);
|
|
assert([0.0, 0.01, 0.10, 0.5, 0.6].stripLeft!(a => a < 0.4)().length == 2);
|
|
}
|
|
|
|
/// Strip trailing elements equal to the target element.
|
|
@safe pure unittest
|
|
{
|
|
assert(" foobar ".stripRight(' ') == " foobar");
|
|
assert("00223.444500".stripRight('0') == "00223.4445");
|
|
assert("ùniçodêéé".stripRight('é') == "ùniçodê");
|
|
assert([1, 1, 0, 1, 1].stripRight(1) == [1, 1, 0]);
|
|
assert([0.0, 0.01, 0.01, 0.0].stripRight(0).length == 3);
|
|
}
|
|
|
|
/// Strip trailing elements while the predicate returns true.
|
|
@safe pure unittest
|
|
{
|
|
assert(" foobar ".stripRight!(a => a == ' ')() == " foobar");
|
|
assert("00223.444500".stripRight!(a => a == '0')() == "00223.4445");
|
|
assert("ùniçodêéé".stripRight!(a => a == 'é')() == "ùniçodê");
|
|
assert([1, 1, 0, 1, 1].stripRight!(a => a == 1)() == [1, 1, 0]);
|
|
assert([0.0, 0.01, 0.10, 0.5, 0.6].stripRight!(a => a > 0.4)().length == 3);
|
|
}
|
|
|
|
// bringToFront
|
|
/**
|
|
The $(D bringToFront) function has considerable flexibility and
|
|
usefulness. It can rotate elements in one buffer left or right, swap
|
|
buffers of equal length, and even move elements across disjoint
|
|
buffers of different types and different lengths.
|
|
|
|
$(D bringToFront) takes two ranges $(D front) and $(D back), which may
|
|
be of different types. Considering the concatenation of $(D front) and
|
|
$(D back) one unified range, $(D bringToFront) rotates that unified
|
|
range such that all elements in $(D back) are brought to the beginning
|
|
of the unified range. The relative ordering of elements in $(D front)
|
|
and $(D back), respectively, remains unchanged.
|
|
|
|
Performs $(BIGOH max(front.length, back.length)) evaluations of $(D
|
|
swap).
|
|
|
|
Preconditions:
|
|
|
|
Either $(D front) and $(D back) are disjoint, or $(D back) is
|
|
reachable from $(D front) and $(D front) is not reachable from $(D
|
|
back).
|
|
|
|
Returns:
|
|
|
|
The number of elements brought to the front, i.e., the length of $(D
|
|
back).
|
|
|
|
See_Also:
|
|
$(WEB sgi.com/tech/stl/_rotate.html, STL's rotate)
|
|
*/
|
|
size_t bringToFront(Range1, Range2)(Range1 front, Range2 back)
|
|
if (isInputRange!Range1 && isForwardRange!Range2)
|
|
{
|
|
enum bool sameHeadExists = is(typeof(front.sameHead(back)));
|
|
size_t result;
|
|
for (bool semidone; !front.empty && !back.empty; )
|
|
{
|
|
static if (sameHeadExists)
|
|
{
|
|
if (front.sameHead(back)) break; // shortcut
|
|
}
|
|
// Swap elements until front and/or back ends.
|
|
auto back0 = back.save;
|
|
size_t nswaps;
|
|
do
|
|
{
|
|
static if (sameHeadExists)
|
|
{
|
|
// Detect the stepping-over condition.
|
|
if (front.sameHead(back0)) back0 = back.save;
|
|
}
|
|
swapFront(front, back);
|
|
++nswaps;
|
|
front.popFront();
|
|
back.popFront();
|
|
}
|
|
while (!front.empty && !back.empty);
|
|
|
|
if (!semidone) result += nswaps;
|
|
|
|
// Now deal with the remaining elements.
|
|
if (back.empty)
|
|
{
|
|
if (front.empty) break;
|
|
// Right side was shorter, which means that we've brought
|
|
// all the back elements to the front.
|
|
semidone = true;
|
|
// Next pass: bringToFront(front, back0) to adjust the rest.
|
|
back = back0;
|
|
}
|
|
else
|
|
{
|
|
assert(front.empty);
|
|
// Left side was shorter. Let's step into the back.
|
|
static if (is(Range1 == Take!Range2))
|
|
{
|
|
front = take(back0, nswaps);
|
|
}
|
|
else
|
|
{
|
|
immutable subresult = bringToFront(take(back0, nswaps),
|
|
back);
|
|
if (!semidone) result += subresult;
|
|
break; // done
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
The simplest use of $(D bringToFront) is for rotating elements in a
|
|
buffer. For example:
|
|
*/
|
|
@safe unittest
|
|
{
|
|
auto arr = [4, 5, 6, 7, 1, 2, 3];
|
|
auto p = bringToFront(arr[0 .. 4], arr[4 .. $]);
|
|
assert(p == arr.length - 4);
|
|
assert(arr == [ 1, 2, 3, 4, 5, 6, 7 ]);
|
|
}
|
|
|
|
/**
|
|
The $(D front) range may actually "step over" the $(D back)
|
|
range. This is very useful with forward ranges that cannot compute
|
|
comfortably right-bounded subranges like $(D arr[0 .. 4]) above. In
|
|
the example below, $(D r2) is a right subrange of $(D r1).
|
|
*/
|
|
@safe unittest
|
|
{
|
|
import std.container : SList;
|
|
|
|
auto list = SList!(int)(4, 5, 6, 7, 1, 2, 3);
|
|
auto r1 = list[];
|
|
auto r2 = list[]; popFrontN(r2, 4);
|
|
assert(equal(r2, [ 1, 2, 3 ]));
|
|
bringToFront(r1, r2);
|
|
assert(equal(list[], [ 1, 2, 3, 4, 5, 6, 7 ]));
|
|
}
|
|
|
|
|
|
/**
|
|
Elements can be swapped across ranges of different types:
|
|
*/
|
|
@safe unittest
|
|
{
|
|
import std.container : SList;
|
|
|
|
auto list = SList!(int)(4, 5, 6, 7);
|
|
auto vec = [ 1, 2, 3 ];
|
|
bringToFront(list[], vec);
|
|
assert(equal(list[], [ 1, 2, 3, 4 ]));
|
|
assert(equal(vec, [ 5, 6, 7 ]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : text;
|
|
import std.random : Random, unpredictableSeed, uniform;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
// a more elaborate test
|
|
{
|
|
auto rnd = Random(unpredictableSeed);
|
|
int[] a = new int[uniform(100, 200, rnd)];
|
|
int[] b = new int[uniform(100, 200, rnd)];
|
|
foreach (ref e; a) e = uniform(-100, 100, rnd);
|
|
foreach (ref e; b) e = uniform(-100, 100, rnd);
|
|
int[] c = a ~ b;
|
|
// writeln("a= ", a);
|
|
// writeln("b= ", b);
|
|
auto n = bringToFront(c[0 .. a.length], c[a.length .. $]);
|
|
//writeln("c= ", c);
|
|
assert(n == b.length);
|
|
assert(c == b ~ a, text(c, "\n", a, "\n", b));
|
|
}
|
|
// different types, moveFront, no sameHead
|
|
{
|
|
static struct R(T)
|
|
{
|
|
T[] data;
|
|
size_t i;
|
|
@property
|
|
{
|
|
R save() { return this; }
|
|
bool empty() { return i >= data.length; }
|
|
T front() { return data[i]; }
|
|
T front(real e) { return data[i] = cast(T) e; }
|
|
}
|
|
void popFront() { ++i; }
|
|
}
|
|
auto a = R!int([1, 2, 3, 4, 5]);
|
|
auto b = R!real([6, 7, 8, 9]);
|
|
auto n = bringToFront(a, b);
|
|
assert(n == 4);
|
|
assert(a.data == [6, 7, 8, 9, 1]);
|
|
assert(b.data == [2, 3, 4, 5]);
|
|
}
|
|
// front steps over back
|
|
{
|
|
int[] arr, r1, r2;
|
|
|
|
// back is shorter
|
|
arr = [4, 5, 6, 7, 1, 2, 3];
|
|
r1 = arr;
|
|
r2 = arr[4 .. $];
|
|
bringToFront(r1, r2) == 3 || assert(0);
|
|
assert(equal(arr, [1, 2, 3, 4, 5, 6, 7]));
|
|
|
|
// front is shorter
|
|
arr = [5, 6, 7, 1, 2, 3, 4];
|
|
r1 = arr;
|
|
r2 = arr[3 .. $];
|
|
bringToFront(r1, r2) == 4 || assert(0);
|
|
assert(equal(arr, [1, 2, 3, 4, 5, 6, 7]));
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
|
|
/**
|
|
Eliminates elements at given offsets from $(D range) and returns the
|
|
shortened range. In the simplest call, one element is removed.
|
|
|
|
----
|
|
int[] a = [ 3, 5, 7, 8 ];
|
|
assert(remove(a, 1) == [ 3, 7, 8 ]);
|
|
assert(a == [ 3, 7, 8, 8 ]);
|
|
----
|
|
|
|
In the case above the element at offset $(D 1) is removed and $(D
|
|
remove) returns the range smaller by one element. The original array
|
|
has remained of the same length because all functions in $(D
|
|
std.algorithm) only change $(I content), not $(I topology). The value
|
|
$(D 8) is repeated because $(XREF algorithm, move) was invoked to move
|
|
elements around and on integers $(D move) simply copies the source to
|
|
the destination. To replace $(D a) with the effect of the removal,
|
|
simply assign $(D a = remove(a, 1)). The slice will be rebound to the
|
|
shorter array and the operation completes with maximal efficiency.
|
|
|
|
Multiple indices can be passed into $(D remove). In that case,
|
|
elements at the respective indices are all removed. The indices must
|
|
be passed in increasing order, otherwise an exception occurs.
|
|
|
|
----
|
|
int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
assert(remove(a, 1, 3, 5) ==
|
|
[ 0, 2, 4, 6, 7, 8, 9, 10 ]);
|
|
----
|
|
|
|
(Note how all indices refer to slots in the $(I original) array, not
|
|
in the array as it is being progressively shortened.) Finally, any
|
|
combination of integral offsets and tuples composed of two integral
|
|
offsets can be passed in.
|
|
|
|
----
|
|
int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
assert(remove(a, 1, tuple(3, 5), 9) == [ 0, 2, 6, 7, 8, 10 ]);
|
|
----
|
|
|
|
In this case, the slots at positions 1, 3, 4, and 9 are removed from
|
|
the array. The tuple passes in a range closed to the left and open to
|
|
the right (consistent with built-in slices), e.g. $(D tuple(3, 5))
|
|
means indices $(D 3) and $(D 4) but not $(D 5).
|
|
|
|
If the need is to remove some elements in the range but the order of
|
|
the remaining elements does not have to be preserved, you may want to
|
|
pass $(D SwapStrategy.unstable) to $(D remove).
|
|
|
|
----
|
|
int[] a = [ 0, 1, 2, 3 ];
|
|
assert(remove!(SwapStrategy.unstable)(a, 1) == [ 0, 3, 2 ]);
|
|
----
|
|
|
|
In the case above, the element at slot $(D 1) is removed, but replaced
|
|
with the last element of the range. Taking advantage of the relaxation
|
|
of the stability requirement, $(D remove) moved elements from the end
|
|
of the array over the slots to be removed. This way there is less data
|
|
movement to be done which improves the execution time of the function.
|
|
|
|
The function $(D remove) works on any forward range. The moving
|
|
strategy is (listed from fastest to slowest): $(UL $(LI If $(D s ==
|
|
SwapStrategy.unstable && isRandomAccessRange!Range && hasLength!Range
|
|
&& hasLvalueElements!Range), then elements are moved from the end
|
|
of the range into the slots to be filled. In this case, the absolute
|
|
minimum of moves is performed.) $(LI Otherwise, if $(D s ==
|
|
SwapStrategy.unstable && isBidirectionalRange!Range && hasLength!Range
|
|
&& hasLvalueElements!Range), then elements are still moved from the
|
|
end of the range, but time is spent on advancing between slots by repeated
|
|
calls to $(D range.popFront).) $(LI Otherwise, elements are moved
|
|
incrementally towards the front of $(D range); a given element is never
|
|
moved several times, but more elements are moved than in the previous
|
|
cases.))
|
|
*/
|
|
Range remove
|
|
(SwapStrategy s = SwapStrategy.stable, Range, Offset...)
|
|
(Range range, Offset offset)
|
|
if (s != SwapStrategy.stable
|
|
&& isBidirectionalRange!Range
|
|
&& hasLvalueElements!Range
|
|
&& hasLength!Range
|
|
&& Offset.length >= 1)
|
|
{
|
|
Tuple!(size_t, "pos", size_t, "len")[offset.length] blackouts;
|
|
foreach (i, v; offset)
|
|
{
|
|
static if (is(typeof(v[0]) : size_t) && is(typeof(v[1]) : size_t))
|
|
{
|
|
blackouts[i].pos = v[0];
|
|
blackouts[i].len = v[1] - v[0];
|
|
}
|
|
else
|
|
{
|
|
static assert(is(typeof(v) : size_t), typeof(v).stringof);
|
|
blackouts[i].pos = v;
|
|
blackouts[i].len = 1;
|
|
}
|
|
static if (i > 0)
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
enforce(blackouts[i - 1].pos + blackouts[i - 1].len
|
|
<= blackouts[i].pos,
|
|
"remove(): incorrect ordering of elements to remove");
|
|
}
|
|
}
|
|
|
|
size_t left = 0, right = offset.length - 1;
|
|
auto tgt = range.save;
|
|
size_t steps = 0;
|
|
|
|
while (left <= right)
|
|
{
|
|
// Look for a blackout on the right
|
|
if (blackouts[right].pos + blackouts[right].len >= range.length)
|
|
{
|
|
range.popBackExactly(blackouts[right].len);
|
|
|
|
// Since right is unsigned, we must check for this case, otherwise
|
|
// we might turn it into size_t.max and the loop condition will not
|
|
// fail when it should.
|
|
if (right > 0)
|
|
{
|
|
--right;
|
|
continue;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
// Advance to next blackout on the left
|
|
assert(blackouts[left].pos >= steps);
|
|
tgt.popFrontExactly(blackouts[left].pos - steps);
|
|
steps = blackouts[left].pos;
|
|
auto toMove = min(
|
|
blackouts[left].len,
|
|
range.length - (blackouts[right].pos + blackouts[right].len));
|
|
foreach (i; 0 .. toMove)
|
|
{
|
|
move(range.back, tgt.front);
|
|
range.popBack();
|
|
tgt.popFront();
|
|
}
|
|
steps += toMove;
|
|
if (toMove == blackouts[left].len)
|
|
{
|
|
// Filled the entire left hole
|
|
++left;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return range;
|
|
}
|
|
|
|
// Ditto
|
|
Range remove
|
|
(SwapStrategy s = SwapStrategy.stable, Range, Offset...)
|
|
(Range range, Offset offset)
|
|
if (s == SwapStrategy.stable
|
|
&& isBidirectionalRange!Range
|
|
&& hasLvalueElements!Range
|
|
&& Offset.length >= 1)
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
auto result = range;
|
|
auto src = range, tgt = range;
|
|
size_t pos;
|
|
foreach (pass, i; offset)
|
|
{
|
|
static if (is(typeof(i[0])) && is(typeof(i[1])))
|
|
{
|
|
auto from = i[0], delta = i[1] - i[0];
|
|
}
|
|
else
|
|
{
|
|
auto from = i;
|
|
enum delta = 1;
|
|
}
|
|
enforce(pos <= from,
|
|
"remove(): incorrect ordering of elements to remove");
|
|
if (pass > 0)
|
|
{
|
|
for (; pos < from; ++pos, src.popFront(), tgt.popFront())
|
|
{
|
|
move(src.front, tgt.front);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
src.popFrontExactly(from);
|
|
tgt.popFrontExactly(from);
|
|
pos = from;
|
|
}
|
|
// now skip source to the "to" position
|
|
src.popFrontExactly(delta);
|
|
result.popBackExactly(delta);
|
|
pos += delta;
|
|
}
|
|
// leftover move
|
|
moveAll(src, tgt);
|
|
return result;
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
// http://d.puremagic.com/issues/show_bug.cgi?id=10173
|
|
int[] test = iota(0, 10).array();
|
|
assertThrown(remove!(SwapStrategy.stable)(test, tuple(2, 4), tuple(1, 3)));
|
|
assertThrown(remove!(SwapStrategy.unstable)(test, tuple(2, 4), tuple(1, 3)));
|
|
assertThrown(remove!(SwapStrategy.stable)(test, 2, 4, 1, 3));
|
|
assertThrown(remove!(SwapStrategy.unstable)(test, 2, 4, 1, 3));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
//writeln(remove!(SwapStrategy.stable)(a, 1));
|
|
a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
assert(remove!(SwapStrategy.stable)(a, 1) ==
|
|
[ 0, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]);
|
|
|
|
a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
assert(remove!(SwapStrategy.unstable)(a, 0, 10) ==
|
|
[ 9, 1, 2, 3, 4, 5, 6, 7, 8 ]);
|
|
|
|
a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
assert(remove!(SwapStrategy.unstable)(a, 0, tuple(9, 11)) ==
|
|
[ 8, 1, 2, 3, 4, 5, 6, 7 ]);
|
|
// http://d.puremagic.com/issues/show_bug.cgi?id=5224
|
|
a = [ 1, 2, 3, 4 ];
|
|
assert(remove!(SwapStrategy.unstable)(a, 2) ==
|
|
[ 1, 2, 4 ]);
|
|
|
|
a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
//writeln(remove!(SwapStrategy.stable)(a, 1, 5));
|
|
a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
assert(remove!(SwapStrategy.stable)(a, 1, 5) ==
|
|
[ 0, 2, 3, 4, 6, 7, 8, 9, 10 ]);
|
|
|
|
a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
//writeln(remove!(SwapStrategy.stable)(a, 1, 3, 5));
|
|
a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
assert(remove!(SwapStrategy.stable)(a, 1, 3, 5)
|
|
== [ 0, 2, 4, 6, 7, 8, 9, 10]);
|
|
a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
//writeln(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5)));
|
|
a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5))
|
|
== [ 0, 2, 5, 6, 7, 8, 9, 10]);
|
|
|
|
a = iota(0, 10).array();
|
|
assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4), tuple(6, 7))
|
|
== [0, 9, 8, 7, 4, 5]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Issue 11576
|
|
auto arr = [1,2,3];
|
|
arr = arr.remove!(SwapStrategy.unstable)(2);
|
|
assert(arr == [1,2]);
|
|
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Bug# 12889
|
|
int[1][] arr = [[0], [1], [2], [3], [4], [5], [6]];
|
|
auto orig = arr.dup;
|
|
foreach (i; iota(arr.length))
|
|
{
|
|
assert(orig == arr.remove!(SwapStrategy.unstable)(tuple(i,i)));
|
|
assert(orig == arr.remove!(SwapStrategy.stable)(tuple(i,i)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
Reduces the length of the bidirectional range $(D range) by removing
|
|
elements that satisfy $(D pred). If $(D s = SwapStrategy.unstable),
|
|
elements are moved from the right end of the range over the elements
|
|
to eliminate. If $(D s = SwapStrategy.stable) (the default),
|
|
elements are moved progressively to front such that their relative
|
|
order is preserved. Returns the filtered range.
|
|
*/
|
|
Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range)
|
|
(Range range)
|
|
if (isBidirectionalRange!Range
|
|
&& hasLvalueElements!Range)
|
|
{
|
|
auto result = range;
|
|
static if (s != SwapStrategy.stable)
|
|
{
|
|
for (;!range.empty;)
|
|
{
|
|
if (!unaryFun!pred(range.front))
|
|
{
|
|
range.popFront();
|
|
continue;
|
|
}
|
|
move(range.back, range.front);
|
|
range.popBack();
|
|
result.popBack();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto tgt = range;
|
|
for (; !range.empty; range.popFront())
|
|
{
|
|
if (unaryFun!(pred)(range.front))
|
|
{
|
|
// yank this guy
|
|
result.popBack();
|
|
continue;
|
|
}
|
|
// keep this guy
|
|
move(range.front, tgt.front);
|
|
tgt.popFront();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
static immutable base = [1, 2, 3, 2, 4, 2, 5, 2];
|
|
|
|
int[] arr = base[].dup;
|
|
|
|
// using a string-based predicate
|
|
assert(remove!("a == 2")(arr) == [ 1, 3, 4, 5 ]);
|
|
|
|
// The original array contents have been modified,
|
|
// so we need to reset it to its original state.
|
|
// The length is unmodified however.
|
|
arr[] = base[];
|
|
|
|
// using a lambda predicate
|
|
assert(remove!(a => a == 2)(arr) == [ 1, 3, 4, 5 ]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
int[] a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ];
|
|
assert(remove!("a == 2", SwapStrategy.unstable)(a) ==
|
|
[ 1, 6, 3, 5, 3, 4, 5 ]);
|
|
a = [ 1, 2, 3, 2, 3, 4, 5, 2, 5, 6 ];
|
|
//writeln(remove!("a != 2", SwapStrategy.stable)(a));
|
|
assert(remove!("a == 2", SwapStrategy.stable)(a) ==
|
|
[ 1, 3, 3, 4, 5, 5, 6 ]);
|
|
}
|
|
|
|
// 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 ]);
|
|
// }
|
|
|
|
// partition
|
|
/**
|
|
Partitions a range in two using $(D pred) as a
|
|
predicate. Specifically, reorders the range $(D r = [left,
|
|
right$(RPAREN)) using $(D swap) 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
|
|
swap). The unstable version computes the minimum possible evaluations
|
|
of $(D swap) (roughly half of those performed by the semistable
|
|
version).
|
|
|
|
Returns:
|
|
|
|
The right part of $(D r) after partitioning.
|
|
|
|
If $(D ss == 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 ss == SwapStrategy.semistable), $(D
|
|
partition) preserves the relative ordering of all elements $(D a), $(D
|
|
b) in the left part of $(D r) for which $(D pred(a) == pred(b)).
|
|
|
|
See_Also:
|
|
STL's $(WEB sgi.com/tech/stl/_partition.html, _partition)$(BR)
|
|
STL's $(WEB sgi.com/tech/stl/stable_partition.html, stable_partition)
|
|
*/
|
|
Range partition(alias predicate,
|
|
SwapStrategy ss = SwapStrategy.unstable, Range)(Range r)
|
|
if ((ss == SwapStrategy.stable && isRandomAccessRange!(Range))
|
|
|| (ss != SwapStrategy.stable && isForwardRange!(Range)))
|
|
{
|
|
alias pred = unaryFun!(predicate);
|
|
if (r.empty) return r;
|
|
static if (ss == SwapStrategy.stable)
|
|
{
|
|
if (r.length == 1)
|
|
{
|
|
if (pred(r.front)) r.popFront();
|
|
return r;
|
|
}
|
|
const middle = r.length / 2;
|
|
alias recurse = .partition!(pred, ss, Range);
|
|
auto lower = recurse(r[0 .. middle]);
|
|
auto upper = recurse(r[middle .. $]);
|
|
bringToFront(lower, r[middle .. r.length - upper.length]);
|
|
return r[r.length - lower.length - upper.length .. r.length];
|
|
}
|
|
else static if (ss == SwapStrategy.semistable)
|
|
{
|
|
for (; !r.empty; r.popFront())
|
|
{
|
|
// skip the initial portion of "correct" elements
|
|
if (pred(r.front)) continue;
|
|
// hit the first "bad" element
|
|
auto result = r;
|
|
for (r.popFront(); !r.empty; r.popFront())
|
|
{
|
|
if (!pred(r.front)) continue;
|
|
swap(result.front, r.front);
|
|
result.popFront();
|
|
}
|
|
return result;
|
|
}
|
|
return r;
|
|
}
|
|
else // ss == SwapStrategy.unstable
|
|
{
|
|
// Inspired from www.stepanovpapers.com/PAM3-partition_notes.pdf,
|
|
// section "Bidirectional Partition Algorithm (Hoare)"
|
|
auto result = r;
|
|
for (;;)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (r.empty) return result;
|
|
if (!pred(r.front)) break;
|
|
r.popFront();
|
|
result.popFront();
|
|
}
|
|
// found the left bound
|
|
assert(!r.empty);
|
|
for (;;)
|
|
{
|
|
if (pred(r.back)) break;
|
|
r.popBack();
|
|
if (r.empty) return result;
|
|
}
|
|
// found the right bound, swap & make progress
|
|
static if (is(typeof(swap(r.front, r.back))))
|
|
{
|
|
swap(r.front, r.back);
|
|
}
|
|
else
|
|
{
|
|
auto t1 = moveFront(r), t2 = moveBack(r);
|
|
r.front = t2;
|
|
r.back = t1;
|
|
}
|
|
r.popFront();
|
|
result.popFront();
|
|
r.popBack();
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
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 arr such that even numbers come first
|
|
auto r = partition!(even)(arr);
|
|
// Now arr is separated in evens and odds.
|
|
// Numbers may have become shuffled due to instability
|
|
assert(r == arr[5 .. $]);
|
|
assert(count!(even)(arr[0 .. 5]) == 5);
|
|
assert(find!(even)(r).empty);
|
|
|
|
// Can also specify the predicate as a string.
|
|
// Use 'a' as the predicate argument name
|
|
arr[] = Arr[];
|
|
r = partition!(q{(a & 1) == 0})(arr);
|
|
assert(r == arr[5 .. $]);
|
|
|
|
// Now for a stable partition:
|
|
arr[] = Arr[];
|
|
r = partition!(q{(a & 1) == 0}, SwapStrategy.stable)(arr);
|
|
// Now arr is [2 4 6 8 10 1 3 5 7 9], and r points to 1
|
|
assert(arr == [2, 4, 6, 8, 10, 1, 3, 5, 7, 9] && r == arr[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; }
|
|
r = partition!(fun, SwapStrategy.semistable)(arr);
|
|
// Now arr is [4 5 6 7 8 9 10 2 3 1] and r points to 2
|
|
assert(arr == [4, 5, 6, 7, 8, 9, 10, 2, 3, 1] && r == arr[7 .. $]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static bool even(int a) { return (a & 1) == 0; }
|
|
|
|
// 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));
|
|
}
|
|
|
|
/**
|
|
Returns $(D true) if $(D r) is partitioned according to predicate $(D
|
|
pred).
|
|
*/
|
|
bool isPartitioned(alias pred, Range)(Range r)
|
|
if (isForwardRange!(Range))
|
|
{
|
|
for (; !r.empty; r.popFront())
|
|
{
|
|
if (unaryFun!(pred)(r.front)) continue;
|
|
for (r.popFront(); !r.empty; r.popFront())
|
|
{
|
|
if (unaryFun!(pred)(r.front)) return false;
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] r = [ 1, 3, 5, 7, 8, 2, 4, ];
|
|
assert(isPartitioned!"a & 1"(r));
|
|
}
|
|
|
|
// partition3
|
|
/**
|
|
Rearranges elements in $(D r) in three adjacent ranges and returns
|
|
them. The first and leftmost range only contains elements in $(D r)
|
|
less than $(D pivot). The second and middle range only contains
|
|
elements in $(D r) that are equal to $(D pivot). Finally, the third
|
|
and rightmost range only contains elements in $(D r) that are greater
|
|
than $(D pivot). The less-than test is defined by the binary function
|
|
$(D less).
|
|
|
|
BUGS: stable $(D partition3) has not been implemented yet.
|
|
*/
|
|
auto partition3(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range, E)
|
|
(Range r, E pivot)
|
|
if (ss == SwapStrategy.unstable && isRandomAccessRange!Range
|
|
&& hasSwappableElements!Range && hasLength!Range
|
|
&& is(typeof(binaryFun!less(r.front, pivot)) == bool)
|
|
&& is(typeof(binaryFun!less(pivot, r.front)) == bool)
|
|
&& is(typeof(binaryFun!less(r.front, r.front)) == bool))
|
|
{
|
|
// The algorithm is described in "Engineering a sort function" by
|
|
// Jon Bentley et al, pp 1257.
|
|
|
|
alias lessFun = binaryFun!less;
|
|
size_t i, j, k = r.length, l = k;
|
|
|
|
bigloop:
|
|
for (;;)
|
|
{
|
|
for (;; ++j)
|
|
{
|
|
if (j == k) break bigloop;
|
|
assert(j < r.length);
|
|
if (lessFun(r[j], pivot)) continue;
|
|
if (lessFun(pivot, r[j])) break;
|
|
swap(r[i++], r[j]);
|
|
}
|
|
assert(j < k);
|
|
for (;;)
|
|
{
|
|
assert(k > 0);
|
|
if (!lessFun(pivot, r[--k]))
|
|
{
|
|
if (lessFun(r[k], pivot)) break;
|
|
swap(r[k], r[--l]);
|
|
}
|
|
if (j == k) break bigloop;
|
|
}
|
|
// Here we know r[j] > pivot && r[k] < pivot
|
|
swap(r[j++], r[k]);
|
|
}
|
|
|
|
// Swap the equal ranges from the extremes into the middle
|
|
auto strictlyLess = j - i, strictlyGreater = l - k;
|
|
auto swapLen = min(i, strictlyLess);
|
|
swapRanges(r[0 .. swapLen], r[j - swapLen .. j]);
|
|
swapLen = min(r.length - l, strictlyGreater);
|
|
swapRanges(r[k .. k + swapLen], r[r.length - swapLen .. r.length]);
|
|
return tuple(r[0 .. strictlyLess],
|
|
r[strictlyLess .. r.length - strictlyGreater],
|
|
r[r.length - strictlyGreater .. r.length]);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto a = [ 8, 3, 4, 1, 4, 7, 4 ];
|
|
auto pieces = partition3(a, 4);
|
|
assert(pieces[0] == [ 1, 3 ]);
|
|
assert(pieces[1] == [ 4, 4, 4 ]);
|
|
assert(pieces[2] == [ 8, 7 ]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.random : uniform;
|
|
|
|
auto a = new int[](uniform(0, 100));
|
|
foreach (ref e; a)
|
|
{
|
|
e = uniform(0, 50);
|
|
}
|
|
auto pieces = partition3(a, 25);
|
|
assert(pieces[0].length + pieces[1].length + pieces[2].length == a.length);
|
|
foreach (e; pieces[0])
|
|
{
|
|
assert(e < 25);
|
|
}
|
|
foreach (e; pieces[1])
|
|
{
|
|
assert(e == 25);
|
|
}
|
|
foreach (e; pieces[2])
|
|
{
|
|
assert(e > 25);
|
|
}
|
|
}
|
|
|
|
// topN
|
|
/**
|
|
Reorders the range $(D r) using $(D swap) such that $(D r[nth]) refers
|
|
to the element that would fall there if the range were fully
|
|
sorted. In addition, it also partitions $(D r) such that all elements
|
|
$(D e1) from $(D r[0]) to $(D r[nth]) satisfy $(D !less(r[nth], e1)),
|
|
and all elements $(D e2) from $(D r[nth]) to $(D r[r.length]) satisfy
|
|
$(D !less(e2, r[nth])). Effectively, it finds the nth smallest
|
|
(according to $(D less)) elements in $(D r). Performs an expected
|
|
$(BIGOH r.length) (if unstable) or $(BIGOH r.length * log(r.length))
|
|
(if stable) evaluations of $(D less) and $(D swap).
|
|
|
|
If $(D n >= r.length), the algorithm has no effect.
|
|
|
|
See_Also:
|
|
$(WEB sgi.com/tech/stl/nth_element.html, STL's nth_element)
|
|
|
|
BUGS:
|
|
|
|
Stable topN has not been implemented yet.
|
|
*/
|
|
void topN(alias less = "a < b",
|
|
SwapStrategy ss = SwapStrategy.unstable,
|
|
Range)(Range r, size_t nth)
|
|
if (isRandomAccessRange!(Range) && hasLength!Range)
|
|
{
|
|
import std.random : uniform;
|
|
|
|
static assert(ss == SwapStrategy.unstable,
|
|
"Stable topN not yet implemented");
|
|
while (r.length > nth)
|
|
{
|
|
auto pivot = uniform(0, r.length);
|
|
swap(r[pivot], r.back);
|
|
assert(!binaryFun!(less)(r.back, r.back));
|
|
auto right = partition!((a) => binaryFun!less(a, r.back), ss)(r);
|
|
assert(right.length >= 1);
|
|
swap(right.front, r.back);
|
|
pivot = r.length - right.length;
|
|
if (pivot == nth)
|
|
{
|
|
return;
|
|
}
|
|
if (pivot < nth)
|
|
{
|
|
++pivot;
|
|
r = r[pivot .. $];
|
|
nth -= pivot;
|
|
}
|
|
else
|
|
{
|
|
assert(pivot < r.length);
|
|
r = r[0 .. pivot];
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] v = [ 25, 7, 9, 2, 0, 5, 21 ];
|
|
auto n = 4;
|
|
topN!"a < b"(v, n);
|
|
assert(v[n] == 9);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
//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 ];
|
|
ptrdiff_t n = 3;
|
|
topN!("a < b")(v, n);
|
|
assert(reduce!max(v[0 .. n]) <= v[n]);
|
|
assert(reduce!min(v[n + 1 .. $]) >= v[n]);
|
|
//
|
|
v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup;
|
|
n = 3;
|
|
topN(v, n);
|
|
assert(reduce!max(v[0 .. n]) <= v[n]);
|
|
assert(reduce!min(v[n + 1 .. $]) >= v[n]);
|
|
//
|
|
v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup;
|
|
n = 1;
|
|
topN(v, n);
|
|
assert(reduce!max(v[0 .. n]) <= v[n]);
|
|
assert(reduce!min(v[n + 1 .. $]) >= v[n]);
|
|
//
|
|
v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup;
|
|
n = v.length - 1;
|
|
topN(v, n);
|
|
assert(v[n] == 7);
|
|
//
|
|
v = ([3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5]).dup;
|
|
n = 0;
|
|
topN(v, n);
|
|
assert(v[n] == 1);
|
|
|
|
double[][] v1 = [[-10, -5], [-10, -3], [-10, -5], [-10, -4],
|
|
[-10, -5], [-9, -5], [-9, -3], [-9, -5],];
|
|
|
|
// double[][] v1 = [ [-10, -5], [-10, -4], [-9, -5], [-9, -5],
|
|
// [-10, -5], [-10, -3], [-10, -5], [-9, -3],];
|
|
double[]*[] idx = [ &v1[0], &v1[1], &v1[2], &v1[3], &v1[4], &v1[5], &v1[6],
|
|
&v1[7], ];
|
|
|
|
auto mid = v1.length / 2;
|
|
topN!((a, b){ return (*a)[1] < (*b)[1]; })(idx, mid);
|
|
foreach (e; idx[0 .. mid]) assert((*e)[1] <= (*idx[mid])[1]);
|
|
foreach (e; idx[mid .. $]) assert((*e)[1] >= (*idx[mid])[1]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.random : uniform;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
int[] a = new int[uniform(1, 10000)];
|
|
foreach (ref e; a) e = uniform(-1000, 1000);
|
|
auto k = uniform(0, a.length);
|
|
topN(a, k);
|
|
if (k > 0)
|
|
{
|
|
auto left = reduce!max(a[0 .. k]);
|
|
assert(left <= a[k]);
|
|
}
|
|
if (k + 1 < a.length)
|
|
{
|
|
auto right = reduce!min(a[k + 1 .. $]);
|
|
assert(right >= a[k]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Stores the smallest elements of the two ranges in the left-hand range.
|
|
*/
|
|
void topN(alias less = "a < b",
|
|
SwapStrategy ss = SwapStrategy.unstable,
|
|
Range1, Range2)(Range1 r1, Range2 r2)
|
|
if (isRandomAccessRange!(Range1) && hasLength!Range1 &&
|
|
isInputRange!Range2 && is(ElementType!Range1 == ElementType!Range2))
|
|
{
|
|
import std.container : BinaryHeap;
|
|
|
|
static assert(ss == SwapStrategy.unstable,
|
|
"Stable topN not yet implemented");
|
|
auto heap = BinaryHeap!Range1(r1);
|
|
for (; !r2.empty; r2.popFront())
|
|
{
|
|
heap.conditionalInsert(r2.front);
|
|
}
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
int[] a = [ 5, 7, 2, 6, 7 ];
|
|
int[] b = [ 2, 1, 5, 6, 7, 3, 0 ];
|
|
topN(a, b);
|
|
sort(a);
|
|
assert(a == [0, 1, 2, 2, 3]);
|
|
}
|
|
|
|
// sort
|
|
/**
|
|
Sorts a random-access range according to the predicate $(D less). Performs
|
|
$(BIGOH r.length * log(r.length)) evaluations of $(D less). Stable sorting
|
|
requires $(D hasAssignableElements!Range) to be true.
|
|
|
|
$(D sort) returns a $(XREF range, SortedRange) over the original range, which
|
|
functions that can take advantage of sorted data can then use to know that the
|
|
range is sorted and adjust accordingly. The $(XREF range, SortedRange) is a
|
|
wrapper around the original range, so both it and the original range are sorted,
|
|
but other functions won't know that the original range has been sorted, whereas
|
|
they $(I can) know that $(XREF range, SortedRange) has been sorted.
|
|
|
|
The predicate is expected to satisfy certain rules in order for $(D sort) to
|
|
behave as expected - otherwise, the program may fail on certain inputs (but not
|
|
others) when not compiled in release mode, due to the cursory $(D assumeSorted)
|
|
check. Specifically, $(D sort) expects $(D less(a,b) && less(b,c)) to imply
|
|
$(D less(a,c)) (transitivity), and, conversely, $(D !less(a,b) && !less(b,c)) to
|
|
imply $(D !less(a,c)). Note that the default predicate ($(D "a < b")) does not
|
|
always satisfy these conditions for floating point types, because the expression
|
|
will always be $(D false) when either $(D a) or $(D b) is NaN.
|
|
|
|
Returns: The initial range wrapped as a $(D SortedRange) with the predicate
|
|
$(D binaryFun!less).
|
|
|
|
Algorithms: $(WEB en.wikipedia.org/wiki/Introsort) is used for unstable sorting and
|
|
$(WEB en.wikipedia.org/wiki/Timsort, Timsort) is used for stable sorting.
|
|
Each algorithm has benefits beyond stability. Introsort is generally faster but
|
|
Timsort may achieve greater speeds on data with low entropy or if predicate calls
|
|
are expensive. Introsort performs no allocations whereas Timsort will perform one
|
|
or more allocations per call. Both algorithms have $(BIGOH n log n) worst-case
|
|
time complexity.
|
|
|
|
See_Also:
|
|
$(XREF range, assumeSorted)$(BR)
|
|
$(XREF range, SortedRange)$(BR)
|
|
$(XREF algorithm, SwapStrategy)$(BR)
|
|
$(XREF functional, binaryFun)
|
|
*/
|
|
|
|
SortedRange!(Range, less)
|
|
sort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable,
|
|
Range)(Range r)
|
|
if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range ||
|
|
hasAssignableElements!Range)) ||
|
|
(ss != SwapStrategy.unstable && hasAssignableElements!Range)) &&
|
|
isRandomAccessRange!Range &&
|
|
hasSlicing!Range &&
|
|
hasLength!Range)
|
|
/+ Unstable sorting uses the quicksort algorithm, which uses swapAt,
|
|
which either uses swap(...), requiring swappable elements, or just
|
|
swaps using assignment.
|
|
Stable sorting uses TimSort, which needs to copy elements into a buffer,
|
|
requiring assignable elements. +/
|
|
{
|
|
alias lessFun = binaryFun!(less);
|
|
alias LessRet = typeof(lessFun(r.front, r.front)); // instantiate lessFun
|
|
static if (is(LessRet == bool))
|
|
{
|
|
static if (ss == SwapStrategy.unstable)
|
|
quickSortImpl!(lessFun)(r, r.length);
|
|
else //use Tim Sort for semistable & stable
|
|
TimSortImpl!(lessFun, Range).sort(r, null);
|
|
|
|
enum maxLen = 8;
|
|
assert(isSorted!lessFun(r), "Failed to sort range of type " ~ Range.stringof);
|
|
}
|
|
else
|
|
{
|
|
static assert(false, "Invalid predicate passed to sort: " ~ less.stringof);
|
|
}
|
|
return assumeSorted!less(r);
|
|
}
|
|
|
|
///
|
|
@safe pure nothrow unittest
|
|
{
|
|
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) @safe pure nothrow { return x > y; }
|
|
sort!(myComp)(array);
|
|
assert(array == [ 4, 3, 2, 1 ]);
|
|
}
|
|
///
|
|
unittest
|
|
{
|
|
// 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" ]);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.random : Random, unpredictableSeed, uniform;
|
|
import std.string : toUpper;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
// sort using delegate
|
|
auto a = new int[100];
|
|
auto rnd = Random(unpredictableSeed);
|
|
foreach (ref e; a) {
|
|
e = uniform(-100, 100, rnd);
|
|
}
|
|
|
|
int i = 0;
|
|
bool greater2(int a, int b) { return a + i > b + i; }
|
|
bool delegate(int, int) greater = &greater2;
|
|
sort!(greater)(a);
|
|
assert(isSorted!(greater)(a));
|
|
|
|
// sort using string
|
|
sort!("a < b")(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));
|
|
|
|
{
|
|
// Issue 10317
|
|
enum E_10317 { a, b }
|
|
auto a_10317 = new E_10317[10];
|
|
sort(a_10317);
|
|
}
|
|
|
|
{
|
|
// Issue 7767
|
|
// Unstable sort should complete without an excessive number of predicate calls
|
|
// This would suggest it's running in quadratic time
|
|
|
|
// Compilation error if predicate is not static, i.e. a nested function
|
|
static uint comp;
|
|
static bool pred(size_t a, size_t b)
|
|
{
|
|
++comp;
|
|
return a < b;
|
|
}
|
|
|
|
size_t[] arr;
|
|
arr.length = 1024;
|
|
|
|
foreach(k; 0..arr.length) arr[k] = k;
|
|
swapRanges(arr[0..$/2], arr[$/2..$]);
|
|
|
|
sort!(pred, SwapStrategy.unstable)(arr);
|
|
assert(comp < 25_000);
|
|
}
|
|
|
|
{
|
|
bool proxySwapCalled;
|
|
struct S
|
|
{
|
|
int i;
|
|
alias i this;
|
|
void proxySwap(ref S other) { swap(i, other.i); proxySwapCalled = true; }
|
|
@disable void opAssign(S value);
|
|
}
|
|
|
|
alias R = S[];
|
|
R r = [S(3), S(2), S(1)];
|
|
static assert(hasSwappableElements!R);
|
|
static assert(!hasAssignableElements!R);
|
|
r.sort();
|
|
assert(proxySwapCalled);
|
|
}
|
|
}
|
|
|
|
private template validPredicates(E, less...) {
|
|
static if (less.length == 0)
|
|
enum validPredicates = true;
|
|
else static if (less.length == 1 && is(typeof(less[0]) == SwapStrategy))
|
|
enum validPredicates = true;
|
|
else
|
|
enum validPredicates =
|
|
is(typeof((E a, E b){ bool r = binaryFun!(less[0])(a, b); }))
|
|
&& validPredicates!(E, less[1 .. $]);
|
|
}
|
|
|
|
/**
|
|
$(D void multiSort(Range)(Range r)
|
|
if (validPredicates!(ElementType!Range, less));)
|
|
|
|
Sorts a range by multiple keys. The call $(D multiSort!("a.id < b.id",
|
|
"a.date > b.date")(r)) sorts the range $(D r) by $(D id) ascending,
|
|
and sorts elements that have the same $(D id) by $(D date)
|
|
descending. Such a call is equivalent to $(D sort!"a.id != b.id ? a.id
|
|
< b.id : a.date > b.date"(r)), but $(D multiSort) is faster because it
|
|
does fewer comparisons (in addition to being more convenient).
|
|
*/
|
|
template multiSort(less...) //if (less.length > 1)
|
|
{
|
|
void multiSort(Range)(Range r)
|
|
if (validPredicates!(ElementType!Range, less))
|
|
{
|
|
static if (is(typeof(less[$ - 1]) == SwapStrategy))
|
|
{
|
|
enum ss = less[$ - 1];
|
|
alias funs = less[0 .. $ - 1];
|
|
}
|
|
else
|
|
{
|
|
alias ss = SwapStrategy.unstable;
|
|
alias funs = less;
|
|
}
|
|
alias lessFun = binaryFun!(funs[0]);
|
|
|
|
static if (funs.length > 1)
|
|
{
|
|
while (r.length > 1)
|
|
{
|
|
auto p = getPivot!lessFun(r);
|
|
auto t = partition3!(less[0], ss)(r, r[p]);
|
|
if (t[0].length <= t[2].length)
|
|
{
|
|
.multiSort!less(t[0]);
|
|
.multiSort!(less[1 .. $])(t[1]);
|
|
r = t[2];
|
|
}
|
|
else
|
|
{
|
|
.multiSort!(less[1 .. $])(t[1]);
|
|
.multiSort!less(t[2]);
|
|
r = t[0];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sort!(lessFun, ss)(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
static struct Point { int x, y; }
|
|
auto pts1 = [ Point(0, 0), Point(5, 5), Point(0, 1), Point(0, 2) ];
|
|
auto pts2 = [ Point(0, 0), Point(0, 1), Point(0, 2), Point(5, 5) ];
|
|
multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1);
|
|
assert(pts1 == pts2);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
static struct Point { int x, y; }
|
|
auto pts1 = [ Point(5, 6), Point(1, 0), Point(5, 7), Point(1, 1), Point(1, 2), Point(0, 1) ];
|
|
auto pts2 = [ Point(0, 1), Point(1, 0), Point(1, 1), Point(1, 2), Point(5, 6), Point(5, 7) ];
|
|
static assert(validPredicates!(Point, "a.x < b.x", "a.y < b.y"));
|
|
multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts1);
|
|
assert(pts1 == pts2);
|
|
|
|
auto pts3 = indexed(pts1, iota(pts1.length));
|
|
multiSort!("a.x < b.x", "a.y < b.y", SwapStrategy.unstable)(pts3);
|
|
assert(equal(pts3, pts2));
|
|
}
|
|
|
|
@safe unittest //issue 9160 (L-value only comparators)
|
|
{
|
|
static struct A
|
|
{
|
|
int x;
|
|
int y;
|
|
}
|
|
|
|
static bool byX(const ref A lhs, const ref A rhs)
|
|
{
|
|
return lhs.x < rhs.x;
|
|
}
|
|
|
|
static bool byY(const ref A lhs, const ref A rhs)
|
|
{
|
|
return lhs.y < rhs.y;
|
|
}
|
|
|
|
auto points = [ A(4, 1), A(2, 4)];
|
|
multiSort!(byX, byY)(points);
|
|
assert(points[0] == A(2, 4));
|
|
assert(points[1] == A(4, 1));
|
|
}
|
|
|
|
private size_t getPivot(alias less, Range)(Range r)
|
|
{
|
|
// This algorithm sorts the first, middle and last elements of r,
|
|
// then returns the index of the middle element. In effect, it uses the
|
|
// median-of-three heuristic.
|
|
|
|
alias pred = binaryFun!(less);
|
|
immutable len = r.length;
|
|
immutable size_t mid = len / 2;
|
|
immutable uint result = ((cast(uint) (pred(r[0], r[mid]))) << 2) |
|
|
((cast(uint) (pred(r[0], r[len - 1]))) << 1) |
|
|
(cast(uint) (pred(r[mid], r[len - 1])));
|
|
|
|
switch(result) {
|
|
case 0b001:
|
|
swapAt(r, 0, len - 1);
|
|
swapAt(r, 0, mid);
|
|
break;
|
|
case 0b110:
|
|
swapAt(r, mid, len - 1);
|
|
break;
|
|
case 0b011:
|
|
swapAt(r, 0, mid);
|
|
break;
|
|
case 0b100:
|
|
swapAt(r, mid, len - 1);
|
|
swapAt(r, 0, mid);
|
|
break;
|
|
case 0b000:
|
|
swapAt(r, 0, len - 1);
|
|
break;
|
|
case 0b111:
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
return mid;
|
|
}
|
|
|
|
private void optimisticInsertionSort(alias less, Range)(Range r)
|
|
{
|
|
alias pred = binaryFun!(less);
|
|
if (r.length < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
immutable maxJ = r.length - 1;
|
|
for (size_t i = r.length - 2; i != size_t.max; --i)
|
|
{
|
|
size_t j = i;
|
|
|
|
static if (hasAssignableElements!Range)
|
|
{
|
|
auto temp = r[i];
|
|
|
|
for (; j < maxJ && pred(r[j + 1], temp); ++j)
|
|
{
|
|
r[j] = r[j + 1];
|
|
}
|
|
|
|
r[j] = temp;
|
|
}
|
|
else
|
|
{
|
|
for (; j < maxJ && pred(r[j + 1], r[j]); ++j)
|
|
{
|
|
swapAt(r, j, j + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.random : Random, uniform;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
auto rnd = Random(1);
|
|
auto a = new int[uniform(100, 200, rnd)];
|
|
foreach (ref e; a) {
|
|
e = uniform(-100, 100, rnd);
|
|
}
|
|
|
|
optimisticInsertionSort!(binaryFun!("a < b"), int[])(a);
|
|
assert(isSorted(a));
|
|
}
|
|
|
|
//private
|
|
void swapAt(R)(R r, size_t i1, size_t i2)
|
|
{
|
|
static if (is(typeof(&r[i1])))
|
|
{
|
|
swap(r[i1], r[i2]);
|
|
}
|
|
else
|
|
{
|
|
if (i1 == i2) return;
|
|
auto t1 = moveAt(r, i1);
|
|
auto t2 = moveAt(r, i2);
|
|
r[i2] = t1;
|
|
r[i1] = t2;
|
|
}
|
|
}
|
|
|
|
private void quickSortImpl(alias less, Range)(Range r, size_t depth)
|
|
{
|
|
alias Elem = ElementType!(Range);
|
|
enum size_t optimisticInsertionSortGetsBetter = 25;
|
|
static assert(optimisticInsertionSortGetsBetter >= 1);
|
|
|
|
// partition
|
|
while (r.length > optimisticInsertionSortGetsBetter)
|
|
{
|
|
if (depth == 0)
|
|
{
|
|
HeapSortImpl!(less, Range).heapSort(r);
|
|
return;
|
|
}
|
|
depth = depth >= depth.max / 2 ? (depth / 3) * 2 : (depth * 2) / 3;
|
|
|
|
const pivotIdx = getPivot!(less)(r);
|
|
auto pivot = r[pivotIdx];
|
|
|
|
alias pred = binaryFun!(less);
|
|
|
|
// partition
|
|
swapAt(r, pivotIdx, r.length - 1);
|
|
size_t lessI = size_t.max, greaterI = r.length - 1;
|
|
|
|
while (true)
|
|
{
|
|
while (pred(r[++lessI], pivot)) {}
|
|
while (greaterI > 0 && pred(pivot, r[--greaterI])) {}
|
|
|
|
if (lessI >= greaterI)
|
|
{
|
|
break;
|
|
}
|
|
swapAt(r, lessI, greaterI);
|
|
}
|
|
|
|
swapAt(r, r.length - 1, lessI);
|
|
auto right = r[lessI + 1 .. r.length];
|
|
|
|
auto left = r[0 .. min(lessI, greaterI + 1)];
|
|
if (right.length > left.length)
|
|
{
|
|
swap(left, right);
|
|
}
|
|
.quickSortImpl!(less, Range)(right, depth);
|
|
r = left;
|
|
}
|
|
// residual sort
|
|
static if (optimisticInsertionSortGetsBetter > 1)
|
|
{
|
|
optimisticInsertionSort!(less, Range)(r);
|
|
}
|
|
}
|
|
|
|
// Bottom-Up Heap-Sort Implementation
|
|
private template HeapSortImpl(alias less, Range)
|
|
{
|
|
static assert(isRandomAccessRange!Range);
|
|
static assert(hasLength!Range);
|
|
static assert(hasSwappableElements!Range || hasAssignableElements!Range);
|
|
|
|
alias lessFun = binaryFun!less;
|
|
|
|
//template because of @@@12410@@@
|
|
void heapSort()(Range r)
|
|
{
|
|
// If true, there is nothing to do
|
|
if(r.length < 2) return;
|
|
|
|
// Build Heap
|
|
size_t i = r.length / 2;
|
|
while(i > 0) sift(r, --i, r.length);
|
|
|
|
// Sort
|
|
i = r.length - 1;
|
|
while(i > 0)
|
|
{
|
|
swapAt(r, 0, i);
|
|
sift(r, 0, i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
//template because of @@@12410@@@
|
|
void sift()(Range r, size_t parent, immutable size_t end)
|
|
{
|
|
immutable root = parent;
|
|
size_t child = void;
|
|
|
|
// Sift down
|
|
while(true)
|
|
{
|
|
child = parent * 2 + 1;
|
|
|
|
if(child >= end) break;
|
|
|
|
if(child + 1 < end && lessFun(r[child], r[child + 1])) child += 1;
|
|
|
|
swapAt(r, parent, child);
|
|
parent = child;
|
|
}
|
|
|
|
child = parent;
|
|
|
|
// Sift up
|
|
while(child > root)
|
|
{
|
|
parent = (child - 1) / 2;
|
|
if(lessFun(r[parent], r[child]))
|
|
{
|
|
swapAt(r, parent, child);
|
|
child = parent;
|
|
}
|
|
else break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tim Sort implementation
|
|
private template TimSortImpl(alias pred, R)
|
|
{
|
|
import core.bitop : bsr;
|
|
|
|
static assert(isRandomAccessRange!R);
|
|
static assert(hasLength!R);
|
|
static assert(hasSlicing!R);
|
|
static assert(hasAssignableElements!R);
|
|
|
|
alias T = ElementType!R;
|
|
|
|
alias less = binaryFun!pred;
|
|
bool greater(T a, T b){ return less(b, a); }
|
|
bool greaterEqual(T a, T b){ return !less(a, b); }
|
|
bool lessEqual(T a, T b){ return !less(b, a); }
|
|
|
|
enum minimalMerge = 128;
|
|
enum minimalGallop = 7;
|
|
enum minimalStorage = 256;
|
|
enum stackSize = 40;
|
|
|
|
struct Slice{ size_t base, length; }
|
|
|
|
// Entry point for tim sort
|
|
void sort(R range, T[] temp)
|
|
{
|
|
// Do insertion sort on small range
|
|
if (range.length <= minimalMerge)
|
|
{
|
|
binaryInsertionSort(range);
|
|
return;
|
|
}
|
|
|
|
immutable minRun = minRunLength(range.length);
|
|
immutable minTemp = min(range.length / 2, minimalStorage);
|
|
size_t minGallop = minimalGallop;
|
|
Slice[stackSize] stack = void;
|
|
size_t stackLen = 0;
|
|
|
|
// Allocate temporary memory if not provided by user
|
|
if (temp.length < minTemp)
|
|
{
|
|
if (__ctfe) temp.length = minTemp;
|
|
else temp = uninitializedArray!(T[])(minTemp);
|
|
}
|
|
|
|
for (size_t i = 0; i < range.length; )
|
|
{
|
|
// Find length of first run in list
|
|
size_t runLen = firstRun(range[i .. range.length]);
|
|
|
|
// If run has less than minRun elements, extend using insertion sort
|
|
if (runLen < minRun)
|
|
{
|
|
// Do not run farther than the length of the range
|
|
immutable force = range.length - i > minRun ? minRun : range.length - i;
|
|
binaryInsertionSort(range[i .. i + force], runLen);
|
|
runLen = force;
|
|
}
|
|
|
|
// Push run onto stack
|
|
stack[stackLen++] = Slice(i, runLen);
|
|
i += runLen;
|
|
|
|
// Collapse stack so that (e1 >= e2 + e3 && e2 >= e3)
|
|
// STACK is | ... e1 e2 e3 >
|
|
while (stackLen > 1)
|
|
{
|
|
immutable run3 = stackLen - 1;
|
|
immutable run2 = stackLen - 2;
|
|
immutable run1 = stackLen - 3;
|
|
if (stackLen >= 3 && stack[run1].length <= stack[run2].length + stack[run3].length)
|
|
{
|
|
immutable at = stack[run1].length <= stack[run3].length
|
|
? run1 : run2;
|
|
mergeAt(range, stack[0 .. stackLen], at, minGallop, temp);
|
|
--stackLen;
|
|
}
|
|
else if (stack[run2].length <= stack[run3].length)
|
|
{
|
|
mergeAt(range, stack[0 .. stackLen], run2, minGallop, temp);
|
|
--stackLen;
|
|
}
|
|
else break;
|
|
}
|
|
}
|
|
|
|
// Force collapse stack until there is only one run left
|
|
while (stackLen > 1)
|
|
{
|
|
immutable run3 = stackLen - 1;
|
|
immutable run2 = stackLen - 2;
|
|
immutable run1 = stackLen - 3;
|
|
immutable at = stackLen >= 3 && stack[run1].length <= stack[run3].length
|
|
? run1 : run2;
|
|
mergeAt(range, stack[0 .. stackLen], at, minGallop, temp);
|
|
--stackLen;
|
|
}
|
|
}
|
|
|
|
// Calculates optimal value for minRun:
|
|
// take first 6 bits of n and add 1 if any lower bits are set
|
|
pure size_t minRunLength(size_t n)
|
|
{
|
|
immutable shift = bsr(n)-5;
|
|
auto result = (n>>shift) + !!(n & ~((1<<shift)-1));
|
|
return result;
|
|
}
|
|
|
|
// Returns length of first run in range
|
|
size_t firstRun(R range)
|
|
out(ret)
|
|
{
|
|
assert(ret <= range.length);
|
|
}
|
|
body
|
|
{
|
|
if (range.length < 2) return range.length;
|
|
|
|
size_t i = 2;
|
|
if (lessEqual(range[0], range[1]))
|
|
{
|
|
while (i < range.length && lessEqual(range[i-1], range[i])) ++i;
|
|
}
|
|
else
|
|
{
|
|
while (i < range.length && greater(range[i-1], range[i])) ++i;
|
|
reverse(range[0 .. i]);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
// A binary insertion sort for building runs up to minRun length
|
|
void binaryInsertionSort(R range, size_t sortedLen = 1)
|
|
out
|
|
{
|
|
if (!__ctfe) assert(isSorted!pred(range));
|
|
}
|
|
body
|
|
{
|
|
for (; sortedLen < range.length; ++sortedLen)
|
|
{
|
|
T item = moveAt(range, sortedLen);
|
|
size_t lower = 0;
|
|
size_t upper = sortedLen;
|
|
while (upper != lower)
|
|
{
|
|
size_t center = (lower + upper) / 2;
|
|
if (less(item, range[center])) upper = center;
|
|
else lower = center + 1;
|
|
}
|
|
//Currently (DMD 2.061) moveAll+retro is slightly less
|
|
//efficient then stright 'for' loop
|
|
//11 instructions vs 7 in the innermost loop [checked on Win32]
|
|
//moveAll(retro(range[lower .. sortedLen]),
|
|
// retro(range[lower+1 .. sortedLen+1]));
|
|
for(upper=sortedLen; upper>lower; upper--)
|
|
range[upper] = moveAt(range, upper-1);
|
|
range[lower] = move(item);
|
|
}
|
|
}
|
|
|
|
// Merge two runs in stack (at, at + 1)
|
|
void mergeAt(R range, Slice[] stack, immutable size_t at, ref size_t minGallop, ref T[] temp)
|
|
in
|
|
{
|
|
assert(stack.length >= 2);
|
|
assert(at == stack.length - 2 || at == stack.length - 3);
|
|
}
|
|
body
|
|
{
|
|
immutable base = stack[at].base;
|
|
immutable mid = stack[at].length;
|
|
immutable len = stack[at + 1].length + mid;
|
|
|
|
// Pop run from stack
|
|
stack[at] = Slice(base, len);
|
|
if (at == stack.length - 3) stack[$ - 2] = stack[$ - 1];
|
|
|
|
// Merge runs (at, at + 1)
|
|
return merge(range[base .. base + len], mid, minGallop, temp);
|
|
}
|
|
|
|
// Merge two runs in a range. Mid is the starting index of the second run.
|
|
// minGallop and temp are references; The calling function must receive the updated values.
|
|
void merge(R range, size_t mid, ref size_t minGallop, ref T[] temp)
|
|
in
|
|
{
|
|
if (!__ctfe)
|
|
{
|
|
assert(isSorted!pred(range[0 .. mid]));
|
|
assert(isSorted!pred(range[mid .. range.length]));
|
|
}
|
|
}
|
|
body
|
|
{
|
|
assert(mid < range.length);
|
|
|
|
// Reduce range of elements
|
|
immutable firstElement = gallopForwardUpper(range[0 .. mid], range[mid]);
|
|
immutable lastElement = gallopReverseLower(range[mid .. range.length], range[mid - 1]) + mid;
|
|
range = range[firstElement .. lastElement];
|
|
mid -= firstElement;
|
|
|
|
if (mid == 0 || mid == range.length) return;
|
|
|
|
// Call function which will copy smaller run into temporary memory
|
|
if (mid <= range.length / 2)
|
|
{
|
|
temp = ensureCapacity(mid, temp);
|
|
minGallop = mergeLo(range, mid, minGallop, temp);
|
|
}
|
|
else
|
|
{
|
|
temp = ensureCapacity(range.length - mid, temp);
|
|
minGallop = mergeHi(range, mid, minGallop, temp);
|
|
}
|
|
}
|
|
|
|
// Enlarge size of temporary memory if needed
|
|
T[] ensureCapacity(size_t minCapacity, T[] temp)
|
|
out(ret)
|
|
{
|
|
assert(ret.length >= minCapacity);
|
|
}
|
|
body
|
|
{
|
|
if (temp.length < minCapacity)
|
|
{
|
|
size_t newSize = 1<<(bsr(minCapacity)+1);
|
|
//Test for overflow
|
|
if (newSize < minCapacity) newSize = minCapacity;
|
|
|
|
if (__ctfe) temp.length = newSize;
|
|
else temp = uninitializedArray!(T[])(newSize);
|
|
}
|
|
return temp;
|
|
}
|
|
|
|
// Merge front to back. Returns new value of minGallop.
|
|
// temp must be large enough to store range[0 .. mid]
|
|
size_t mergeLo(R range, immutable size_t mid, size_t minGallop, T[] temp)
|
|
out
|
|
{
|
|
if (!__ctfe) assert(isSorted!pred(range));
|
|
}
|
|
body
|
|
{
|
|
assert(mid <= range.length);
|
|
assert(temp.length >= mid);
|
|
|
|
// Copy run into temporary memory
|
|
temp = temp[0 .. mid];
|
|
copy(range[0 .. mid], temp);
|
|
|
|
// Move first element into place
|
|
range[0] = range[mid];
|
|
|
|
size_t i = 1, lef = 0, rig = mid + 1;
|
|
size_t count_lef, count_rig;
|
|
immutable lef_end = temp.length - 1;
|
|
|
|
if (lef < lef_end && rig < range.length)
|
|
outer: while(true)
|
|
{
|
|
count_lef = 0;
|
|
count_rig = 0;
|
|
|
|
// Linear merge
|
|
while ((count_lef | count_rig) < minGallop)
|
|
{
|
|
if (lessEqual(temp[lef], range[rig]))
|
|
{
|
|
range[i++] = temp[lef++];
|
|
if(lef >= lef_end) break outer;
|
|
++count_lef;
|
|
count_rig = 0;
|
|
}
|
|
else
|
|
{
|
|
range[i++] = range[rig++];
|
|
if(rig >= range.length) break outer;
|
|
count_lef = 0;
|
|
++count_rig;
|
|
}
|
|
}
|
|
|
|
// Gallop merge
|
|
do
|
|
{
|
|
count_lef = gallopForwardUpper(temp[lef .. $], range[rig]);
|
|
foreach (j; 0 .. count_lef) range[i++] = temp[lef++];
|
|
if(lef >= temp.length) break outer;
|
|
|
|
count_rig = gallopForwardLower(range[rig .. range.length], temp[lef]);
|
|
foreach (j; 0 .. count_rig) range[i++] = range[rig++];
|
|
if (rig >= range.length) while(true)
|
|
{
|
|
range[i++] = temp[lef++];
|
|
if(lef >= temp.length) break outer;
|
|
}
|
|
|
|
if (minGallop > 0) --minGallop;
|
|
}
|
|
while (count_lef >= minimalGallop || count_rig >= minimalGallop);
|
|
|
|
minGallop += 2;
|
|
}
|
|
|
|
// Move remaining elements from right
|
|
while (rig < range.length)
|
|
range[i++] = range[rig++];
|
|
|
|
// Move remaining elements from left
|
|
while (lef < temp.length)
|
|
range[i++] = temp[lef++];
|
|
|
|
return minGallop > 0 ? minGallop : 1;
|
|
}
|
|
|
|
// Merge back to front. Returns new value of minGallop.
|
|
// temp must be large enough to store range[mid .. range.length]
|
|
size_t mergeHi(R range, immutable size_t mid, size_t minGallop, T[] temp)
|
|
out
|
|
{
|
|
if (!__ctfe) assert(isSorted!pred(range));
|
|
}
|
|
body
|
|
{
|
|
assert(mid <= range.length);
|
|
assert(temp.length >= range.length - mid);
|
|
|
|
// Copy run into temporary memory
|
|
temp = temp[0 .. range.length - mid];
|
|
copy(range[mid .. range.length], temp);
|
|
|
|
// Move first element into place
|
|
range[range.length - 1] = range[mid - 1];
|
|
|
|
size_t i = range.length - 2, lef = mid - 2, rig = temp.length - 1;
|
|
size_t count_lef, count_rig;
|
|
|
|
outer:
|
|
while(true)
|
|
{
|
|
count_lef = 0;
|
|
count_rig = 0;
|
|
|
|
// Linear merge
|
|
while((count_lef | count_rig) < minGallop)
|
|
{
|
|
if(greaterEqual(temp[rig], range[lef]))
|
|
{
|
|
range[i--] = temp[rig];
|
|
if(rig == 1)
|
|
{
|
|
// Move remaining elements from left
|
|
while(true)
|
|
{
|
|
range[i--] = range[lef];
|
|
if(lef == 0) break;
|
|
--lef;
|
|
}
|
|
|
|
// Move last element into place
|
|
range[i] = temp[0];
|
|
|
|
break outer;
|
|
}
|
|
--rig;
|
|
count_lef = 0;
|
|
++count_rig;
|
|
}
|
|
else
|
|
{
|
|
range[i--] = range[lef];
|
|
if(lef == 0) while(true)
|
|
{
|
|
range[i--] = temp[rig];
|
|
if(rig == 0) break outer;
|
|
--rig;
|
|
}
|
|
--lef;
|
|
++count_lef;
|
|
count_rig = 0;
|
|
}
|
|
}
|
|
|
|
// Gallop merge
|
|
do
|
|
{
|
|
count_rig = rig - gallopReverseLower(temp[0 .. rig], range[lef]);
|
|
foreach(j; 0 .. count_rig)
|
|
{
|
|
range[i--] = temp[rig];
|
|
if(rig == 0) break outer;
|
|
--rig;
|
|
}
|
|
|
|
count_lef = lef - gallopReverseUpper(range[0 .. lef], temp[rig]);
|
|
foreach(j; 0 .. count_lef)
|
|
{
|
|
range[i--] = range[lef];
|
|
if(lef == 0) while(true)
|
|
{
|
|
range[i--] = temp[rig];
|
|
if(rig == 0) break outer;
|
|
--rig;
|
|
}
|
|
--lef;
|
|
}
|
|
|
|
if(minGallop > 0) --minGallop;
|
|
}
|
|
while(count_lef >= minimalGallop || count_rig >= minimalGallop);
|
|
|
|
minGallop += 2;
|
|
}
|
|
|
|
return minGallop > 0 ? minGallop : 1;
|
|
}
|
|
|
|
// false = forward / lower, true = reverse / upper
|
|
template gallopSearch(bool forwardReverse, bool lowerUpper)
|
|
{
|
|
// Gallop search on range according to attributes forwardReverse and lowerUpper
|
|
size_t gallopSearch(R)(R range, T value)
|
|
out(ret)
|
|
{
|
|
assert(ret <= range.length);
|
|
}
|
|
body
|
|
{
|
|
size_t lower = 0, center = 1, upper = range.length;
|
|
alias gap = center;
|
|
|
|
static if (forwardReverse)
|
|
{
|
|
static if (!lowerUpper) alias comp = lessEqual; // reverse lower
|
|
static if (lowerUpper) alias comp = less; // reverse upper
|
|
|
|
// Gallop Search Reverse
|
|
while (gap <= upper)
|
|
{
|
|
if (comp(value, range[upper - gap]))
|
|
{
|
|
upper -= gap;
|
|
gap *= 2;
|
|
}
|
|
else
|
|
{
|
|
lower = upper - gap;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Binary Search Reverse
|
|
while (upper != lower)
|
|
{
|
|
center = lower + (upper - lower) / 2;
|
|
if (comp(value, range[center])) upper = center;
|
|
else lower = center + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static if (!lowerUpper) alias comp = greater; // forward lower
|
|
static if (lowerUpper) alias comp = greaterEqual; // forward upper
|
|
|
|
// Gallop Search Forward
|
|
while (lower + gap < upper)
|
|
{
|
|
if (comp(value, range[lower + gap]))
|
|
{
|
|
lower += gap;
|
|
gap *= 2;
|
|
}
|
|
else
|
|
{
|
|
upper = lower + gap;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Binary Search Forward
|
|
while (lower != upper)
|
|
{
|
|
center = lower + (upper - lower) / 2;
|
|
if (comp(value, range[center])) lower = center + 1;
|
|
else upper = center;
|
|
}
|
|
}
|
|
|
|
return lower;
|
|
}
|
|
}
|
|
|
|
alias gallopForwardLower = gallopSearch!(false, false);
|
|
alias gallopForwardUpper = gallopSearch!(false, true);
|
|
alias gallopReverseLower = gallopSearch!( true, false);
|
|
alias gallopReverseUpper = gallopSearch!( true, true);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.random : Random, uniform, randomShuffle;
|
|
|
|
// Element type with two fields
|
|
static struct E
|
|
{
|
|
size_t value, index;
|
|
}
|
|
|
|
// Generates data especially for testing sorting with Timsort
|
|
static E[] genSampleData(uint seed)
|
|
{
|
|
auto rnd = Random(seed);
|
|
|
|
E[] arr;
|
|
arr.length = 64 * 64;
|
|
|
|
// We want duplicate values for testing stability
|
|
foreach(i, ref v; arr) v.value = i / 64;
|
|
|
|
// Swap ranges at random middle point (test large merge operation)
|
|
immutable mid = uniform(arr.length / 4, arr.length / 4 * 3, rnd);
|
|
swapRanges(arr[0 .. mid], arr[mid .. $]);
|
|
|
|
// Shuffle last 1/8 of the array (test insertion sort and linear merge)
|
|
randomShuffle(arr[$ / 8 * 7 .. $], rnd);
|
|
|
|
// Swap few random elements (test galloping mode)
|
|
foreach(i; 0 .. arr.length / 64)
|
|
{
|
|
immutable a = uniform(0, arr.length, rnd), b = uniform(0, arr.length, rnd);
|
|
swap(arr[a], arr[b]);
|
|
}
|
|
|
|
// Now that our test array is prepped, store original index value
|
|
// This will allow us to confirm the array was sorted stably
|
|
foreach(i, ref v; arr) v.index = i;
|
|
|
|
return arr;
|
|
}
|
|
|
|
// Tests the Timsort function for correctness and stability
|
|
static bool testSort(uint seed)
|
|
{
|
|
auto arr = genSampleData(seed);
|
|
|
|
// Now sort the array!
|
|
static bool comp(E a, E b)
|
|
{
|
|
return a.value < b.value;
|
|
}
|
|
|
|
sort!(comp, SwapStrategy.stable)(arr);
|
|
|
|
// Test that the array was sorted correctly
|
|
assert(isSorted!comp(arr));
|
|
|
|
// Test that the array was sorted stably
|
|
foreach(i; 0 .. arr.length - 1)
|
|
{
|
|
if(arr[i].value == arr[i + 1].value) assert(arr[i].index < arr[i + 1].index);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum seed = 310614065;
|
|
testSort(seed);
|
|
|
|
//@@BUG: Timsort fails with CTFE as of DMD 2.060
|
|
// enum result = testSort(seed);
|
|
}
|
|
|
|
unittest
|
|
{//bugzilla 4584
|
|
assert(isSorted!"a<b"(sort!("a<b", SwapStrategy.stable)(
|
|
[83, 42, 85, 86, 87, 22, 89, 30, 91, 46, 93, 94, 95, 6,
|
|
97, 14, 33, 10, 101, 102, 103, 26, 105, 106, 107, 6]
|
|
)));
|
|
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//test stable sort + zip
|
|
auto x = [10, 50, 60, 60, 20];
|
|
dchar[] y = "abcde"d.dup;
|
|
|
|
sort!("a[0] < b[0]", SwapStrategy.stable)(zip(x, y));
|
|
assert(x == [10, 20, 50, 60, 60]);
|
|
assert(y == "aebcd"d);
|
|
}
|
|
|
|
// 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=UHw6KXbvazs, 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.
|
|
|
|
Examples:
|
|
----
|
|
uint hashFun(string) { ... expensive computation ... }
|
|
string[] array = ...;
|
|
// Sort strings by hash, slow
|
|
sort!((a, b) => 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.
|
|
|
|
To check whether an array was sorted and benefit of the speedup of
|
|
Schwartz sorting, a function $(D schwartzIsSorted) is not provided
|
|
because the effect can be achieved by calling $(D
|
|
isSorted!less(map!transform(r))).
|
|
|
|
Returns: The initial range wrapped as a $(D SortedRange) with the
|
|
predicate $(D (a, b) => binaryFun!less(transform(a),
|
|
transform(b))).
|
|
*/
|
|
SortedRange!(R, ((a, b) => binaryFun!less(unaryFun!transform(a),
|
|
unaryFun!transform(b))))
|
|
schwartzSort(alias transform, alias less = "a < b",
|
|
SwapStrategy ss = SwapStrategy.unstable, R)(R r)
|
|
if (isRandomAccessRange!R && hasLength!R)
|
|
{
|
|
import core.stdc.stdlib : malloc, free;
|
|
import std.conv : emplace;
|
|
import std.string : representation;
|
|
|
|
alias T = typeof(unaryFun!transform(r.front));
|
|
auto xform1 = (cast(T*) malloc(r.length * T.sizeof))[0 .. r.length];
|
|
size_t length;
|
|
scope(exit)
|
|
{
|
|
static if (hasElaborateDestructor!T)
|
|
{
|
|
foreach (i; 0 .. length) collectException(destroy(xform1[i]));
|
|
}
|
|
free(xform1.ptr);
|
|
}
|
|
for (; length != r.length; ++length)
|
|
{
|
|
emplace(xform1.ptr + length, unaryFun!transform(r[length]));
|
|
}
|
|
// Make sure we use ubyte[] and ushort[], not char[] and wchar[]
|
|
// for the intermediate array, lest zip gets confused.
|
|
static if (isNarrowString!(typeof(xform1)))
|
|
{
|
|
auto xform = xform1.representation();
|
|
}
|
|
else
|
|
{
|
|
alias xform = xform1;
|
|
}
|
|
zip(xform, r).sort!((a, b) => binaryFun!less(a[0], b[0]), ss)();
|
|
return typeof(return)(r);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// issue 4909
|
|
Tuple!(char)[] chars;
|
|
schwartzSort!"a[0]"(chars);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// issue 5924
|
|
Tuple!(char)[] chars;
|
|
schwartzSort!((Tuple!(char) c){ return c[0]; })(chars);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.math : log2;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
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 * log2(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;
|
|
auto arr = new double[][3];
|
|
arr[0] = midEnt;
|
|
arr[1] = lowEnt;
|
|
arr[2] = highEnt;
|
|
|
|
schwartzSort!(entropy, q{a > b})(arr);
|
|
assert(arr[0] == highEnt);
|
|
assert(arr[1] == midEnt);
|
|
assert(arr[2] == lowEnt);
|
|
assert(isSorted!("a > b")(map!(entropy)(arr)));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.math : log2;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
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 * log2(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;
|
|
auto 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);
|
|
assert(isSorted!("a < b")(map!(entropy)(arr)));
|
|
}
|
|
|
|
// partialSort
|
|
/**
|
|
Reorders the random-access range $(D r) such that the range $(D r[0
|
|
.. mid]) is the same as if the entire $(D r) were sorted, and leaves
|
|
the range $(D r[mid .. r.length]) in no particular order. Performs
|
|
$(BIGOH r.length * log(mid)) evaluations of $(D pred). The
|
|
implementation simply calls $(D topN!(less, ss)(r, n)) and then $(D
|
|
sort!(less, ss)(r[0 .. n])).
|
|
*/
|
|
void partialSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable,
|
|
Range)(Range r, size_t n)
|
|
if (isRandomAccessRange!(Range) && hasLength!(Range) && hasSlicing!(Range))
|
|
{
|
|
topN!(less, ss)(r, n);
|
|
sort!(less, ss)(r[0 .. n]);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ];
|
|
partialSort(a, 5);
|
|
assert(a[0 .. 5] == [ 0, 1, 2, 3, 4 ]);
|
|
}
|
|
|
|
// completeSort
|
|
/**
|
|
Sorts the random-access range $(D chain(lhs, rhs)) according to
|
|
predicate $(D less). The left-hand side of the range $(D lhs) is
|
|
assumed to be already sorted; $(D rhs) is assumed to be unsorted. The
|
|
exact strategy chosen depends on the relative sizes of $(D lhs) and
|
|
$(D rhs). Performs $(BIGOH lhs.length + rhs.length * log(rhs.length))
|
|
(best case) to $(BIGOH (lhs.length + rhs.length) * log(lhs.length +
|
|
rhs.length)) (worst-case) evaluations of $(D swap).
|
|
*/
|
|
void completeSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable,
|
|
Range1, Range2)(SortedRange!(Range1, less) lhs, Range2 rhs)
|
|
if (hasLength!(Range2) && hasSlicing!(Range2))
|
|
{
|
|
// Probably this algorithm can be optimized by using in-place
|
|
// merge
|
|
auto lhsOriginal = lhs.release();
|
|
foreach (i; 0 .. rhs.length)
|
|
{
|
|
auto sortedSoFar = chain(lhsOriginal, rhs[0 .. i]);
|
|
auto ub = assumeSorted!less(sortedSoFar).upperBound(rhs[i]);
|
|
if (!ub.length) continue;
|
|
bringToFront(ub.release(), rhs[i .. i + 1]);
|
|
}
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
int[] a = [ 1, 2, 3 ];
|
|
int[] b = [ 4, 0, 6, 5 ];
|
|
completeSort(assumeSorted(a), b);
|
|
assert(a == [ 0, 1, 2 ]);
|
|
assert(b == [ 3, 4, 5, 6 ]);
|
|
}
|
|
|
|
// isSorted
|
|
/**
|
|
Checks whether a forward range is sorted according to the comparison
|
|
operation $(D less). Performs $(BIGOH r.length) evaluations of $(D
|
|
less).
|
|
*/
|
|
bool isSorted(alias less = "a < b", Range)(Range r) if (isForwardRange!(Range))
|
|
{
|
|
if (r.empty) return true;
|
|
|
|
static if (isRandomAccessRange!Range && hasLength!Range)
|
|
{
|
|
immutable limit = r.length - 1;
|
|
foreach (i; 0 .. limit)
|
|
{
|
|
if (!binaryFun!less(r[i + 1], r[i])) continue;
|
|
assert(
|
|
!binaryFun!less(r[i], r[i + 1]),
|
|
"Predicate for isSorted is not antisymmetric. Both" ~
|
|
" pred(a, b) and pred(b, a) are true for certain values.");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto ahead = r;
|
|
ahead.popFront();
|
|
size_t i;
|
|
|
|
for (; !ahead.empty; ahead.popFront(), r.popFront(), ++i)
|
|
{
|
|
if (!binaryFun!less(ahead.front, r.front)) continue;
|
|
// Check for antisymmetric predicate
|
|
assert(
|
|
!binaryFun!less(r.front, ahead.front),
|
|
"Predicate for isSorted is not antisymmetric. Both" ~
|
|
" pred(a, b) and pred(b, a) are true for certain values.");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] arr = [4, 3, 2, 1];
|
|
assert(!isSorted(arr));
|
|
sort(arr);
|
|
assert(isSorted(arr));
|
|
sort!("a > b")(arr);
|
|
assert(isSorted!("a > b")(arr));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
|
|
// Issue 9457
|
|
auto x = "abcd";
|
|
assert(isSorted(x));
|
|
auto y = "acbd";
|
|
assert(!isSorted(y));
|
|
|
|
int[] a = [1, 2, 3];
|
|
assert(isSorted(a));
|
|
int[] b = [1, 3, 2];
|
|
assert(!isSorted(b));
|
|
|
|
dchar[] ds = "コーヒーが好きです"d.dup;
|
|
sort(ds);
|
|
string s = to!string(ds);
|
|
assert(isSorted(ds)); // random-access
|
|
assert(isSorted(s)); // bidirectional
|
|
}
|
|
|
|
// makeIndex
|
|
/**
|
|
Computes an index for $(D r) based on the comparison $(D less). The
|
|
index is a sorted array of pointers or indices into the original
|
|
range. This technique is similar to 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 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.
|
|
|
|
The first overload of $(D makeIndex) writes to a range containing
|
|
pointers, and the second writes to a range containing offsets. The
|
|
first overload requires $(D Range) to be a forward range, and the
|
|
latter requires it to be a random-access range.
|
|
|
|
$(D makeIndex) overwrites its second argument with the result, but
|
|
never reallocates it.
|
|
|
|
Returns: The pointer-based version returns a $(D SortedRange) wrapper
|
|
over index, of type $(D SortedRange!(RangeIndex, (a, b) =>
|
|
binaryFun!less(*a, *b))) thus reflecting the ordering of the
|
|
index. The index-based version returns $(D void) because the ordering
|
|
relation involves not only $(D index) but also $(D r).
|
|
|
|
Throws: If the second argument's length is less than that of the range
|
|
indexed, an exception is thrown.
|
|
*/
|
|
SortedRange!(RangeIndex, (a, b) => binaryFun!less(*a, *b))
|
|
makeIndex(
|
|
alias less = "a < b",
|
|
SwapStrategy ss = SwapStrategy.unstable,
|
|
Range,
|
|
RangeIndex)
|
|
(Range r, RangeIndex index)
|
|
if (isForwardRange!(Range) && isRandomAccessRange!(RangeIndex)
|
|
&& is(ElementType!(RangeIndex) : ElementType!(Range)*))
|
|
{
|
|
import std.exception : enforce;
|
|
|
|
// assume collection already ordered
|
|
size_t i;
|
|
for (; !r.empty; r.popFront(), ++i)
|
|
index[i] = addressOf(r.front);
|
|
enforce(index.length == i);
|
|
// sort the index
|
|
sort!((a, b) => binaryFun!less(*a, *b), ss)(index);
|
|
return typeof(return)(index);
|
|
}
|
|
|
|
/// Ditto
|
|
void makeIndex(
|
|
alias less = "a < b",
|
|
SwapStrategy ss = SwapStrategy.unstable,
|
|
Range,
|
|
RangeIndex)
|
|
(Range r, RangeIndex index)
|
|
if (isRandomAccessRange!Range && !isInfinite!Range &&
|
|
isRandomAccessRange!RangeIndex && !isInfinite!RangeIndex &&
|
|
isIntegral!(ElementType!RangeIndex))
|
|
{
|
|
import std.exception : enforce;
|
|
import std.conv : to;
|
|
|
|
alias IndexType = Unqual!(ElementType!RangeIndex);
|
|
enforce(r.length == index.length,
|
|
"r and index must be same length for makeIndex.");
|
|
static if (IndexType.sizeof < size_t.sizeof)
|
|
{
|
|
enforce(r.length <= IndexType.max, "Cannot create an index with " ~
|
|
"element type " ~ IndexType.stringof ~ " with length " ~
|
|
to!string(r.length) ~ ".");
|
|
}
|
|
|
|
for (IndexType i = 0; i < r.length; ++i)
|
|
{
|
|
index[cast(size_t) i] = i;
|
|
}
|
|
|
|
// sort the index
|
|
sort!((a, b) => binaryFun!less(r[cast(size_t) a], r[cast(size_t) b]), ss)
|
|
(index);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
immutable(int[]) arr = [ 2, 3, 1, 5, 0 ];
|
|
// index using pointers
|
|
auto index1 = new immutable(int)*[arr.length];
|
|
makeIndex!("a < b")(arr, index1);
|
|
assert(isSorted!("*a < *b")(index1));
|
|
// index using offsets
|
|
auto index2 = new size_t[arr.length];
|
|
makeIndex!("a < b")(arr, index2);
|
|
assert(isSorted!
|
|
((size_t a, size_t b){ return arr[a] < arr[b];})
|
|
(index2));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
immutable(int)[] arr = [ 2, 3, 1, 5, 0 ];
|
|
// index using pointers
|
|
auto index1 = new immutable(int)*[arr.length];
|
|
alias ImmRange = typeof(arr);
|
|
alias ImmIndex = typeof(index1);
|
|
static assert(isForwardRange!(ImmRange));
|
|
static assert(isRandomAccessRange!(ImmIndex));
|
|
static assert(!isIntegral!(ElementType!(ImmIndex)));
|
|
static assert(is(ElementType!(ImmIndex) : ElementType!(ImmRange)*));
|
|
makeIndex!("a < b")(arr, index1);
|
|
assert(isSorted!("*a < *b")(index1));
|
|
|
|
// index using offsets
|
|
auto index2 = new long[arr.length];
|
|
makeIndex(arr, index2);
|
|
assert(isSorted!
|
|
((long a, long b){
|
|
return arr[cast(size_t) a] < arr[cast(size_t) b];
|
|
})(index2));
|
|
|
|
// index strings using offsets
|
|
string[] arr1 = ["I", "have", "no", "chocolate"];
|
|
auto index3 = new byte[arr1.length];
|
|
makeIndex(arr1, index3);
|
|
assert(isSorted!
|
|
((byte a, byte b){ return arr1[a] < arr1[b];})
|
|
(index3));
|
|
}
|
|
|
|
/**
|
|
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));
|
|
// }
|
|
|
|
+/
|
|
|
|
// canFind
|
|
/++
|
|
Convenience function. Like find, but only returns whether or not the search
|
|
was successful.
|
|
|
|
See_Also:
|
|
$(LREF among) for checking a value against multiple possibilities.
|
|
+/
|
|
template canFind(alias pred="a == b")
|
|
{
|
|
/++
|
|
Returns $(D true) if and only if any value $(D v) found in the
|
|
input range $(D range) satisfies the predicate $(D pred).
|
|
Performs (at most) $(BIGOH haystack.length) evaluations of $(D pred).
|
|
+/
|
|
bool canFind(Range)(Range haystack)
|
|
if (is(typeof(find!pred(haystack))))
|
|
{
|
|
return any!pred(haystack);
|
|
}
|
|
|
|
/++
|
|
Returns $(D true) if and only if $(D needle) can be found in $(D
|
|
range). Performs $(BIGOH haystack.length) evaluations of $(D pred).
|
|
+/
|
|
bool canFind(Range, Element)(Range haystack, Element needle)
|
|
if (is(typeof(find!pred(haystack, needle))))
|
|
{
|
|
return !find!pred(haystack, needle).empty;
|
|
}
|
|
|
|
/++
|
|
Returns the 1-based index of the first needle found in $(D haystack). If no
|
|
needle is found, then $(D 0) is returned.
|
|
|
|
So, if used directly in the condition of an if statement or loop, the result
|
|
will be $(D true) if one of the needles is found and $(D false) if none are
|
|
found, whereas if the result is used elsewhere, it can either be cast to
|
|
$(D bool) for the same effect or used to get which needle was found first
|
|
without having to deal with the tuple that $(D LREF find) returns for the
|
|
same operation.
|
|
+/
|
|
size_t canFind(Range, Ranges...)(Range haystack, Ranges needles)
|
|
if (Ranges.length > 1 &&
|
|
allSatisfy!(isForwardRange, Ranges) &&
|
|
is(typeof(find!pred(haystack, needles))))
|
|
{
|
|
return find!pred(haystack, needles)[1];
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(canFind([0, 1, 2, 3], 2) == true);
|
|
assert(canFind([0, 1, 2, 3], [1, 2], [2, 3]));
|
|
assert(canFind([0, 1, 2, 3], [1, 2], [2, 3]) == 1);
|
|
assert(canFind([0, 1, 2, 3], [1, 7], [2, 3]));
|
|
assert(canFind([0, 1, 2, 3], [1, 7], [2, 3]) == 2);
|
|
|
|
assert(canFind([0, 1, 2, 3], 4) == false);
|
|
assert(!canFind([0, 1, 2, 3], [1, 3], [2, 4]));
|
|
assert(canFind([0, 1, 2, 3], [1, 3], [2, 4]) == 0);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
auto a = rndstuff!(int)();
|
|
if (a.length)
|
|
{
|
|
auto b = a[a.length / 2];
|
|
assert(canFind(a, b));
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
assert(equal!(canFind!"a < b")([[1, 2, 3], [7, 8, 9]], [2, 8]));
|
|
}
|
|
|
|
/++
|
|
Checks if $(I _any) of the elements verifies $(D pred).
|
|
$(D !any) can be used to verify that $(I none) of the elements verify
|
|
$(D pred).
|
|
+/
|
|
template any(alias pred = "a")
|
|
{
|
|
/++
|
|
Returns $(D true) if and only if $(I _any) value $(D v) found in the
|
|
input range $(D range) satisfies the predicate $(D pred).
|
|
Performs (at most) $(BIGOH range.length) evaluations of $(D pred).
|
|
+/
|
|
bool any(Range)(Range range)
|
|
if (isInputRange!Range && is(typeof(unaryFun!pred(range.front))))
|
|
{
|
|
return !find!pred(range).empty;
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.ascii : isWhite;
|
|
assert( all!(any!isWhite)(["a a", "b b"]));
|
|
assert(!any!(all!isWhite)(["a a", "b b"]));
|
|
}
|
|
|
|
/++
|
|
$(D any) can also be used without a predicate, if its items can be
|
|
evaluated to true or false in a conditional statement. $(D !any) can be a
|
|
convenient way to quickly test that $(I none) of the elements of a range
|
|
evaluate to true.
|
|
+/
|
|
@safe unittest
|
|
{
|
|
int[3] vals1 = [0, 0, 0];
|
|
assert(!any(vals1[])); //none of vals1 evaluate to true
|
|
|
|
int[3] vals2 = [2, 0, 2];
|
|
assert( any(vals2[]));
|
|
assert(!all(vals2[]));
|
|
|
|
int[3] vals3 = [3, 3, 3];
|
|
assert( any(vals3[]));
|
|
assert( all(vals3[]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
auto a = [ 1, 2, 0, 4 ];
|
|
assert(any!"a == 2"(a));
|
|
}
|
|
|
|
/++
|
|
Checks if $(I _all) of the elements verify $(D pred).
|
|
+/
|
|
template all(alias pred = "a")
|
|
{
|
|
/++
|
|
Returns $(D true) if and only if $(I _all) values $(D v) found in the
|
|
input range $(D range) satisfy the predicate $(D pred).
|
|
Performs (at most) $(BIGOH range.length) evaluations of $(D pred).
|
|
+/
|
|
bool all(Range)(Range range)
|
|
if (isInputRange!Range && is(typeof(unaryFun!pred(range.front))))
|
|
{
|
|
import std.functional : not;
|
|
|
|
return find!(not!(unaryFun!pred))(range).empty;
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert( all!"a & 1"([1, 3, 5, 7, 9]));
|
|
assert(!all!"a & 1"([1, 2, 3, 5, 7, 9]));
|
|
}
|
|
|
|
/++
|
|
$(D all) can also be used without a predicate, if its items can be
|
|
evaluated to true or false in a conditional statement. This can be a
|
|
convenient way to quickly evaluate that $(I _all) of the elements of a range
|
|
are true.
|
|
+/
|
|
@safe unittest
|
|
{
|
|
int[3] vals = [5, 3, 18];
|
|
assert( all(vals[]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
int x = 1;
|
|
assert(all!(a => a > x)([2, 3]));
|
|
}
|
|
|
|
/**
|
|
Copies the top $(D n) elements of the input range $(D source) into the
|
|
random-access range $(D target), where $(D n =
|
|
target.length). Elements of $(D source) are not touched. If $(D
|
|
sorted) is $(D true), the target is sorted. Otherwise, the target
|
|
respects the $(WEB en.wikipedia.org/wiki/Binary_heap, heap property).
|
|
*/
|
|
TRange topNCopy(alias less = "a < b", SRange, TRange)
|
|
(SRange source, TRange target, SortOutput sorted = SortOutput.no)
|
|
if (isInputRange!(SRange) && isRandomAccessRange!(TRange)
|
|
&& hasLength!(TRange) && hasSlicing!(TRange))
|
|
{
|
|
import std.container : BinaryHeap;
|
|
|
|
if (target.empty) return target;
|
|
auto heap = BinaryHeap!(TRange, less)(target, 0);
|
|
foreach (e; source) heap.conditionalInsert(e);
|
|
auto result = target[0 .. heap.length];
|
|
if (sorted == SortOutput.yes)
|
|
{
|
|
while (!heap.empty) heap.removeFront();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
int[] a = [ 10, 16, 2, 3, 1, 5, 0 ];
|
|
int[] b = new int[3];
|
|
topNCopy(a, b, SortOutput.yes);
|
|
assert(b == [ 0, 1, 2 ]);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.random : Random, unpredictableSeed, uniform, randomShuffle;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
auto r = Random(unpredictableSeed);
|
|
ptrdiff_t[] a = new ptrdiff_t[uniform(1, 1000, r)];
|
|
foreach (i, ref e; a) e = i;
|
|
randomShuffle(a, r);
|
|
auto n = uniform(0, a.length, r);
|
|
ptrdiff_t[] b = new ptrdiff_t[n];
|
|
topNCopy!(binaryFun!("a < b"))(a, b, SortOutput.yes);
|
|
assert(isSorted!(binaryFun!("a < b"))(b));
|
|
}
|
|
|
|
/**
|
|
Lazily computes the union of two or more ranges $(D rs). The ranges
|
|
are assumed to be sorted by $(D less). Elements in the output are not
|
|
unique; the length of the output is the sum of the lengths of the
|
|
inputs. (The $(D length) member is offered if all ranges also have
|
|
length.) The element types of all ranges must have a common type.
|
|
*/
|
|
struct SetUnion(alias less = "a < b", Rs...) if (allSatisfy!(isInputRange, Rs))
|
|
{
|
|
private:
|
|
Rs _r;
|
|
alias comp = binaryFun!(less);
|
|
uint _crt;
|
|
|
|
void adjustPosition(uint candidate = 0)()
|
|
{
|
|
static if (candidate == Rs.length)
|
|
{
|
|
_crt = _crt.max;
|
|
}
|
|
else
|
|
{
|
|
if (_r[candidate].empty)
|
|
{
|
|
adjustPosition!(candidate + 1)();
|
|
return;
|
|
}
|
|
foreach (i, U; Rs[candidate + 1 .. $])
|
|
{
|
|
enum j = candidate + i + 1;
|
|
if (_r[j].empty) continue;
|
|
if (comp(_r[j].front, _r[candidate].front))
|
|
{
|
|
// a new candidate was found
|
|
adjustPosition!(j)();
|
|
return;
|
|
}
|
|
}
|
|
// Found a successful candidate
|
|
_crt = candidate;
|
|
}
|
|
}
|
|
|
|
public:
|
|
alias ElementType = CommonType!(staticMap!(.ElementType, Rs));
|
|
|
|
this(Rs rs)
|
|
{
|
|
this._r = rs;
|
|
adjustPosition();
|
|
}
|
|
|
|
@property bool empty()
|
|
{
|
|
return _crt == _crt.max;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
// Assumes _crt is correct
|
|
assert(!empty);
|
|
foreach (i, U; Rs)
|
|
{
|
|
if (i < _crt) continue;
|
|
// found _crt
|
|
assert(!_r[i].empty);
|
|
_r[i].popFront();
|
|
adjustPosition();
|
|
return;
|
|
}
|
|
assert(false);
|
|
}
|
|
|
|
@property ElementType front()
|
|
{
|
|
assert(!empty);
|
|
// Assume _crt is correct
|
|
foreach (i, U; Rs)
|
|
{
|
|
if (i < _crt) continue;
|
|
assert(!_r[i].empty);
|
|
return _r[i].front;
|
|
}
|
|
assert(false);
|
|
}
|
|
|
|
static if (allSatisfy!(isForwardRange, Rs))
|
|
{
|
|
@property auto save()
|
|
{
|
|
auto ret = this;
|
|
foreach (ti, elem; _r)
|
|
{
|
|
ret._r[ti] = elem.save;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static if (allSatisfy!(hasLength, Rs))
|
|
{
|
|
@property size_t length()
|
|
{
|
|
size_t result;
|
|
foreach (i, U; Rs)
|
|
{
|
|
result += _r[i].length;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
alias opDollar = length;
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
SetUnion!(less, Rs) setUnion(alias less = "a < b", Rs...)
|
|
(Rs rs)
|
|
{
|
|
return typeof(return)(rs);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 2, 4, 5, 7, 9 ];
|
|
int[] b = [ 0, 1, 2, 4, 7, 8 ];
|
|
int[] c = [ 10 ];
|
|
|
|
assert(setUnion(a, b).length == a.length + b.length);
|
|
assert(equal(setUnion(a, b), [0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9][]));
|
|
assert(equal(setUnion(a, c, b),
|
|
[0, 1, 1, 2, 2, 4, 4, 5, 7, 7, 8, 9, 10][]));
|
|
|
|
static assert(isForwardRange!(typeof(setUnion(a, b))));
|
|
}
|
|
|
|
/**
|
|
Lazily computes the intersection of two or more input ranges $(D
|
|
ranges). The ranges are assumed to be sorted by $(D less). The element
|
|
types of the ranges must have a common type.
|
|
*/
|
|
struct SetIntersection(alias less = "a < b", Rs...)
|
|
if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) &&
|
|
!is(CommonType!(staticMap!(ElementType, Rs)) == void))
|
|
{
|
|
private:
|
|
Rs _input;
|
|
alias comp = binaryFun!less;
|
|
alias ElementType = CommonType!(staticMap!(.ElementType, Rs));
|
|
|
|
// Positions to the first elements that are all equal
|
|
void adjustPosition()
|
|
{
|
|
if (empty) return;
|
|
|
|
size_t done = Rs.length;
|
|
static if (Rs.length > 1) while (true)
|
|
{
|
|
foreach (i, ref r; _input)
|
|
{
|
|
alias next = _input[(i + 1) % Rs.length];
|
|
|
|
if (comp(next.front, r.front))
|
|
{
|
|
do {
|
|
next.popFront();
|
|
if (next.empty) return;
|
|
} while(comp(next.front, r.front));
|
|
done = Rs.length;
|
|
}
|
|
if (--done == 0) return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
this(Rs input)
|
|
{
|
|
this._input = input;
|
|
// position to the first element
|
|
adjustPosition();
|
|
}
|
|
|
|
@property bool empty()
|
|
{
|
|
foreach (ref r; _input)
|
|
{
|
|
if (r.empty) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
assert(!empty);
|
|
static if (Rs.length > 1) foreach (i, ref r; _input)
|
|
{
|
|
alias next = _input[(i + 1) % Rs.length];
|
|
assert(!comp(r.front, next.front));
|
|
}
|
|
|
|
foreach (ref r; _input)
|
|
{
|
|
r.popFront();
|
|
}
|
|
adjustPosition();
|
|
}
|
|
|
|
@property ElementType front()
|
|
{
|
|
assert(!empty);
|
|
return _input[0].front;
|
|
}
|
|
|
|
static if (allSatisfy!(isForwardRange, Rs))
|
|
{
|
|
@property SetIntersection save()
|
|
{
|
|
auto ret = this;
|
|
foreach (i, ref r; _input)
|
|
{
|
|
ret._input[i] = r.save;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
SetIntersection!(less, Rs) setIntersection(alias less = "a < b", Rs...)(Rs ranges)
|
|
if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) &&
|
|
!is(CommonType!(staticMap!(ElementType, Rs)) == void))
|
|
{
|
|
return typeof(return)(ranges);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 2, 4, 5, 7, 9 ];
|
|
int[] b = [ 0, 1, 2, 4, 7, 8 ];
|
|
int[] c = [ 0, 1, 4, 5, 7, 8 ];
|
|
assert(equal(setIntersection(a, a), a));
|
|
assert(equal(setIntersection(a, b), [1, 2, 4, 7]));
|
|
assert(equal(setIntersection(a, b, c), [1, 4, 7]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 2, 4, 5, 7, 9 ];
|
|
int[] b = [ 0, 1, 2, 4, 7, 8 ];
|
|
int[] c = [ 0, 1, 4, 5, 7, 8 ];
|
|
int[] d = [ 1, 3, 4 ];
|
|
int[] e = [ 4, 5 ];
|
|
|
|
assert(equal(setIntersection(a, a), a));
|
|
assert(equal(setIntersection(a, a, a), a));
|
|
assert(equal(setIntersection(a, b), [1, 2, 4, 7]));
|
|
assert(equal(setIntersection(a, b, c), [1, 4, 7]));
|
|
assert(equal(setIntersection(a, b, c, d), [1, 4]));
|
|
assert(equal(setIntersection(a, b, c, d, e), [4]));
|
|
|
|
auto inpA = a.filter!(_ => true), inpB = b.filter!(_ => true);
|
|
auto inpC = c.filter!(_ => true), inpD = d.filter!(_ => true);
|
|
assert(equal(setIntersection(inpA, inpB, inpC, inpD), [1, 4]));
|
|
|
|
assert(equal(setIntersection(a, b, b, a), [1, 2, 4, 7]));
|
|
assert(equal(setIntersection(a, c, b), [1, 4, 7]));
|
|
assert(equal(setIntersection(b, a, c), [1, 4, 7]));
|
|
assert(equal(setIntersection(b, c, a), [1, 4, 7]));
|
|
assert(equal(setIntersection(c, a, b), [1, 4, 7]));
|
|
assert(equal(setIntersection(c, b, a), [1, 4, 7]));
|
|
}
|
|
|
|
/**
|
|
Lazily computes the difference of $(D r1) and $(D r2). The two ranges
|
|
are assumed to be sorted by $(D less). The element types of the two
|
|
ranges must have a common type.
|
|
*/
|
|
struct SetDifference(alias less = "a < b", R1, R2)
|
|
if (isInputRange!(R1) && isInputRange!(R2))
|
|
{
|
|
private:
|
|
R1 r1;
|
|
R2 r2;
|
|
alias comp = binaryFun!(less);
|
|
|
|
void adjustPosition()
|
|
{
|
|
while (!r1.empty)
|
|
{
|
|
if (r2.empty || comp(r1.front, r2.front)) break;
|
|
if (comp(r2.front, r1.front))
|
|
{
|
|
r2.popFront();
|
|
}
|
|
else
|
|
{
|
|
// both are equal
|
|
r1.popFront();
|
|
r2.popFront();
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
this(R1 r1, R2 r2)
|
|
{
|
|
this.r1 = r1;
|
|
this.r2 = r2;
|
|
// position to the first element
|
|
adjustPosition();
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
r1.popFront();
|
|
adjustPosition();
|
|
}
|
|
|
|
@property auto ref front()
|
|
{
|
|
assert(!empty);
|
|
return r1.front;
|
|
}
|
|
|
|
static if (isForwardRange!R1 && isForwardRange!R2)
|
|
{
|
|
@property typeof(this) save()
|
|
{
|
|
auto ret = this;
|
|
ret.r1 = r1.save;
|
|
ret.r2 = r2.save;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
@property bool empty() { return r1.empty; }
|
|
}
|
|
|
|
/// Ditto
|
|
SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2)
|
|
(R1 r1, R2 r2)
|
|
{
|
|
return typeof(return)(r1, r2);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 2, 4, 5, 7, 9 ];
|
|
int[] b = [ 0, 1, 2, 4, 7, 8 ];
|
|
assert(equal(setDifference(a, b), [5, 9][]));
|
|
static assert(isForwardRange!(typeof(setDifference(a, b))));
|
|
}
|
|
|
|
@safe unittest // Issue 10460
|
|
{
|
|
int[] a = [1, 2, 3, 4, 5];
|
|
int[] b = [2, 4];
|
|
foreach (ref e; setDifference(a, b))
|
|
e = 0;
|
|
assert(equal(a, [0, 2, 0, 4, 0]));
|
|
}
|
|
|
|
/**
|
|
Lazily computes the symmetric difference of $(D r1) and $(D r2),
|
|
i.e. the elements that are present in exactly one of $(D r1) and $(D
|
|
r2). The two ranges are assumed to be sorted by $(D less), and the
|
|
output is also sorted by $(D less). The element types of the two
|
|
ranges must have a common type.
|
|
|
|
If both arguments are ranges of L-values of the same type then
|
|
$(D SetSymmetricDifference) will also be a range of L-values of
|
|
that type.
|
|
*/
|
|
struct SetSymmetricDifference(alias less = "a < b", R1, R2)
|
|
if (isInputRange!(R1) && isInputRange!(R2))
|
|
{
|
|
private:
|
|
R1 r1;
|
|
R2 r2;
|
|
//bool usingR2;
|
|
alias comp = binaryFun!(less);
|
|
|
|
void adjustPosition()
|
|
{
|
|
while (!r1.empty && !r2.empty)
|
|
{
|
|
if (comp(r1.front, r2.front) || comp(r2.front, r1.front))
|
|
{
|
|
break;
|
|
}
|
|
// equal, pop both
|
|
r1.popFront();
|
|
r2.popFront();
|
|
}
|
|
}
|
|
|
|
public:
|
|
this(R1 r1, R2 r2)
|
|
{
|
|
this.r1 = r1;
|
|
this.r2 = r2;
|
|
// position to the first element
|
|
adjustPosition();
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
assert(!empty);
|
|
if (r1.empty) r2.popFront();
|
|
else if (r2.empty) r1.popFront();
|
|
else
|
|
{
|
|
// neither is empty
|
|
if (comp(r1.front, r2.front))
|
|
{
|
|
r1.popFront();
|
|
}
|
|
else
|
|
{
|
|
assert(comp(r2.front, r1.front));
|
|
r2.popFront();
|
|
}
|
|
}
|
|
adjustPosition();
|
|
}
|
|
|
|
@property auto ref front()
|
|
{
|
|
assert(!empty);
|
|
bool chooseR1 = r2.empty || !r1.empty && comp(r1.front, r2.front);
|
|
assert(chooseR1 || r1.empty || comp(r2.front, r1.front));
|
|
return chooseR1 ? r1.front : r2.front;
|
|
}
|
|
|
|
static if (isForwardRange!R1 && isForwardRange!R2)
|
|
{
|
|
@property typeof(this) save()
|
|
{
|
|
auto ret = this;
|
|
ret.r1 = r1.save;
|
|
ret.r2 = r2.save;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ref auto opSlice() { return this; }
|
|
|
|
@property bool empty() { return r1.empty && r2.empty; }
|
|
}
|
|
|
|
/// Ditto
|
|
SetSymmetricDifference!(less, R1, R2)
|
|
setSymmetricDifference(alias less = "a < b", R1, R2)
|
|
(R1 r1, R2 r2)
|
|
{
|
|
return typeof(return)(r1, r2);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
int[] a = [ 1, 2, 4, 5, 7, 9 ];
|
|
int[] b = [ 0, 1, 2, 4, 7, 8 ];
|
|
assert(equal(setSymmetricDifference(a, b), [0, 5, 8, 9][]));
|
|
static assert(isForwardRange!(typeof(setSymmetricDifference(a, b))));
|
|
}
|
|
|
|
@safe unittest // Issue 10460
|
|
{
|
|
int[] a = [1, 2];
|
|
double[] b = [2.0, 3.0];
|
|
int[] c = [2, 3];
|
|
|
|
alias R1 = typeof(setSymmetricDifference(a, b));
|
|
static assert(is(ElementType!R1 == double));
|
|
static assert(!hasLvalueElements!R1);
|
|
|
|
alias R2 = typeof(setSymmetricDifference(a, c));
|
|
static assert(is(ElementType!R2 == int));
|
|
static assert(hasLvalueElements!R2);
|
|
}
|
|
|
|
// Internal random array generators
|
|
version(unittest)
|
|
{
|
|
private enum size_t maxArraySize = 50;
|
|
private enum size_t minArraySize = maxArraySize - 1;
|
|
|
|
private 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;
|
|
}
|
|
|
|
private 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;
|
|
}
|
|
|
|
private double[] rndstuff(T : double)()
|
|
{
|
|
double[] result;
|
|
foreach (i; rndstuff!(int)())
|
|
{
|
|
result ~= i / 50.0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//Reference type input range
|
|
private class ReferenceInputRange(T)
|
|
{
|
|
this(Range)(Range r) if (isInputRange!Range) {_payload = array(r);}
|
|
final @property ref T front(){return _payload.front;}
|
|
final void popFront(){_payload.popFront();}
|
|
final @property bool empty(){return _payload.empty;}
|
|
protected T[] _payload;
|
|
}
|
|
|
|
//Reference forward range
|
|
private class ReferenceForwardRange(T) : ReferenceInputRange!T
|
|
{
|
|
this(Range)(Range r) if (isInputRange!Range) {super(r);}
|
|
final @property ReferenceForwardRange save()
|
|
{return new ReferenceForwardRange!T(_payload);}
|
|
}
|
|
|
|
//Infinite input range
|
|
private class ReferenceInfiniteInputRange(T)
|
|
{
|
|
this(T first = T.init) {_val = first;}
|
|
final @property T front(){return _val;}
|
|
final void popFront(){++_val;}
|
|
enum bool empty = false;
|
|
protected T _val;
|
|
}
|
|
|
|
//Infinite forward range
|
|
private class ReferenceInfiniteForwardRange(T) : ReferenceInfiniteInputRange!T
|
|
{
|
|
this(T first = T.init) {super(first);}
|
|
final @property ReferenceInfiniteForwardRange save()
|
|
{return new ReferenceInfiniteForwardRange!T(_val);}
|
|
}
|
|
}
|
|
|
|
// NWayUnion
|
|
/**
|
|
Computes the union of multiple sets. The input sets are passed as a
|
|
range of ranges and each is assumed to be sorted by $(D
|
|
less). Computation is done lazily, one union element at a time. The
|
|
complexity of one $(D popFront) operation is $(BIGOH
|
|
log(ror.length)). However, the length of $(D ror) decreases as ranges
|
|
in it are exhausted, so the complexity of a full pass through $(D
|
|
NWayUnion) is dependent on the distribution of the lengths of ranges
|
|
contained within $(D ror). If all ranges have the same length $(D n)
|
|
(worst case scenario), the complexity of a full pass through $(D
|
|
NWayUnion) is $(BIGOH n * ror.length * log(ror.length)), i.e., $(D
|
|
log(ror.length)) times worse than just spanning all ranges in
|
|
turn. The output comes sorted (unstably) by $(D less).
|
|
|
|
Warning: Because $(D NWayUnion) does not allocate extra memory, it
|
|
will leave $(D ror) modified. Namely, $(D NWayUnion) assumes ownership
|
|
of $(D ror) and discretionarily swaps and advances elements of it. If
|
|
you want $(D ror) to preserve its contents after the call, you may
|
|
want to pass a duplicate to $(D NWayUnion) (and perhaps cache the
|
|
duplicate in between calls).
|
|
*/
|
|
struct NWayUnion(alias less, RangeOfRanges)
|
|
{
|
|
import std.container : BinaryHeap;
|
|
|
|
private alias ElementType = .ElementType!(.ElementType!RangeOfRanges);
|
|
private alias comp = binaryFun!less;
|
|
private RangeOfRanges _ror;
|
|
static bool compFront(.ElementType!RangeOfRanges a,
|
|
.ElementType!RangeOfRanges b)
|
|
{
|
|
// revert comparison order so we get the smallest elements first
|
|
return comp(b.front, a.front);
|
|
}
|
|
BinaryHeap!(RangeOfRanges, compFront) _heap;
|
|
|
|
this(RangeOfRanges ror)
|
|
{
|
|
// Preemptively get rid of all empty ranges in the input
|
|
// No need for stability either
|
|
_ror = remove!("a.empty", SwapStrategy.unstable)(ror);
|
|
//Build the heap across the range
|
|
_heap.acquire(_ror);
|
|
}
|
|
|
|
@property bool empty() { return _ror.empty; }
|
|
|
|
@property auto ref front()
|
|
{
|
|
return _heap.front.front;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
_heap.removeFront();
|
|
// let's look at the guy just popped
|
|
_ror.back.popFront();
|
|
if (_ror.back.empty)
|
|
{
|
|
_ror.popBack();
|
|
// nothing else to do: the empty range is not in the
|
|
// heap and not in _ror
|
|
return;
|
|
}
|
|
// Put the popped range back in the heap
|
|
_heap.conditionalInsert(_ror.back) || assert(false);
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
NWayUnion!(less, RangeOfRanges) nWayUnion
|
|
(alias less = "a < b", RangeOfRanges)
|
|
(RangeOfRanges ror)
|
|
{
|
|
return typeof(return)(ror);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
double[][] a =
|
|
[
|
|
[ 1, 4, 7, 8 ],
|
|
[ 1, 7 ],
|
|
[ 1, 7, 8],
|
|
[ 4 ],
|
|
[ 7 ],
|
|
];
|
|
auto witness = [
|
|
1, 1, 1, 4, 4, 7, 7, 7, 7, 8, 8
|
|
];
|
|
assert(equal(nWayUnion(a), witness));
|
|
}
|
|
|
|
// largestPartialIntersection
|
|
/**
|
|
Given a range of sorted forward ranges $(D ror), copies to $(D tgt)
|
|
the elements that are common to most ranges, along with their number
|
|
of occurrences. All ranges in $(D ror) are assumed to be sorted by $(D
|
|
less). Only the most frequent $(D tgt.length) elements are returned.
|
|
|
|
Example:
|
|
----
|
|
// Figure which number can be found in most arrays of the set of
|
|
// arrays below.
|
|
double[][] a =
|
|
[
|
|
[ 1, 4, 7, 8 ],
|
|
[ 1, 7 ],
|
|
[ 1, 7, 8],
|
|
[ 4 ],
|
|
[ 7 ],
|
|
];
|
|
auto b = new Tuple!(double, uint)[1];
|
|
largestPartialIntersection(a, b);
|
|
// First member is the item, second is the occurrence count
|
|
assert(b[0] == tuple(7.0, 4u));
|
|
----
|
|
|
|
$(D 7.0) is the correct answer because it occurs in $(D 4) out of the
|
|
$(D 5) inputs, more than any other number. The second member of the
|
|
resulting tuple is indeed $(D 4) (recording the number of occurrences
|
|
of $(D 7.0)). If more of the top-frequent numbers are needed, just
|
|
create a larger $(D tgt) range. In the example above, creating $(D b)
|
|
with length $(D 2) yields $(D tuple(1.0, 3u)) in the second position.
|
|
|
|
The function $(D largestPartialIntersection) is useful for
|
|
e.g. searching an $(LUCKY inverted index) for the documents most
|
|
likely to contain some terms of interest. The complexity of the search
|
|
is $(BIGOH n * log(tgt.length)), where $(D n) is the sum of lengths of
|
|
all input ranges. This approach is faster than keeping an associative
|
|
array of the occurrences and then selecting its top items, and also
|
|
requires less memory ($(D largestPartialIntersection) builds its
|
|
result directly in $(D tgt) and requires no extra memory).
|
|
|
|
Warning: Because $(D largestPartialIntersection) does not allocate
|
|
extra memory, it will leave $(D ror) modified. Namely, $(D
|
|
largestPartialIntersection) assumes ownership of $(D ror) and
|
|
discretionarily swaps and advances elements of it. If you want $(D
|
|
ror) to preserve its contents after the call, you may want to pass a
|
|
duplicate to $(D largestPartialIntersection) (and perhaps cache the
|
|
duplicate in between calls).
|
|
*/
|
|
void largestPartialIntersection
|
|
(alias less = "a < b", RangeOfRanges, Range)
|
|
(RangeOfRanges ror, Range tgt, SortOutput sorted = SortOutput.no)
|
|
{
|
|
struct UnitWeights
|
|
{
|
|
static int opIndex(ElementType!(ElementType!RangeOfRanges)) { return 1; }
|
|
}
|
|
return largestPartialIntersectionWeighted!less(ror, tgt, UnitWeights(),
|
|
sorted);
|
|
}
|
|
|
|
// largestPartialIntersectionWeighted
|
|
/**
|
|
Similar to $(D largestPartialIntersection), but associates a weight
|
|
with each distinct element in the intersection.
|
|
|
|
Example:
|
|
----
|
|
// Figure which number can be found in most arrays of the set of
|
|
// arrays below, with specific per-element weights
|
|
double[][] a =
|
|
[
|
|
[ 1, 4, 7, 8 ],
|
|
[ 1, 7 ],
|
|
[ 1, 7, 8],
|
|
[ 4 ],
|
|
[ 7 ],
|
|
];
|
|
auto b = new Tuple!(double, uint)[1];
|
|
double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ];
|
|
largestPartialIntersectionWeighted(a, b, weights);
|
|
// First member is the item, second is the occurrence count
|
|
assert(b[0] == tuple(4.0, 2u));
|
|
----
|
|
|
|
The correct answer in this case is $(D 4.0), which, although only
|
|
appears two times, has a total weight $(D 4.6) (three times its weight
|
|
$(D 2.3)). The value $(D 7) is weighted with $(D 1.1) and occurs four
|
|
times for a total weight $(D 4.4).
|
|
*/
|
|
void largestPartialIntersectionWeighted
|
|
(alias less = "a < b", RangeOfRanges, Range, WeightsAA)
|
|
(RangeOfRanges ror, Range tgt, WeightsAA weights, SortOutput sorted = SortOutput.no)
|
|
{
|
|
if (tgt.empty) return;
|
|
alias InfoType = ElementType!Range;
|
|
bool heapComp(InfoType a, InfoType b)
|
|
{
|
|
return weights[a[0]] * a[1] > weights[b[0]] * b[1];
|
|
}
|
|
topNCopy!heapComp(group(nWayUnion!less(ror)), tgt, sorted);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
double[][] a =
|
|
[
|
|
[ 1, 4, 7, 8 ],
|
|
[ 1, 7 ],
|
|
[ 1, 7, 8],
|
|
[ 4 ],
|
|
[ 7 ],
|
|
];
|
|
auto b = new Tuple!(double, uint)[2];
|
|
largestPartialIntersection(a, b, SortOutput.yes);
|
|
//sort(b);
|
|
//writeln(b);
|
|
assert(b == [ tuple(7.0, 4u), tuple(1.0, 3u) ][], text(b));
|
|
assert(a[0].empty);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.conv : text;
|
|
|
|
debug(std_algorithm) scope(success)
|
|
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
|
|
string[][] a =
|
|
[
|
|
[ "1", "4", "7", "8" ],
|
|
[ "1", "7" ],
|
|
[ "1", "7", "8"],
|
|
[ "4" ],
|
|
[ "7" ],
|
|
];
|
|
auto b = new Tuple!(string, uint)[2];
|
|
largestPartialIntersection(a, b, SortOutput.yes);
|
|
//writeln(b);
|
|
assert(b == [ tuple("7", 4u), tuple("1", 3u) ][], text(b));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " done.");
|
|
// Figure which number can be found in most arrays of the set of
|
|
// arrays below, with specific per-element weights
|
|
double[][] a =
|
|
[
|
|
[ 1, 4, 7, 8 ],
|
|
[ 1, 7 ],
|
|
[ 1, 7, 8],
|
|
[ 4 ],
|
|
[ 7 ],
|
|
];
|
|
auto b = new Tuple!(double, uint)[1];
|
|
double[double] weights = [ 1:1.2, 4:2.3, 7:1.1, 8:1.1 ];
|
|
largestPartialIntersectionWeighted(a, b, weights);
|
|
// First member is the item, second is the occurrence count
|
|
//writeln(b[0]);
|
|
assert(b[0] == tuple(4.0, 2u));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.container : Array;
|
|
|
|
alias T = Tuple!(uint, uint);
|
|
const Array!T arrayOne = Array!T( [ T(1,2), T(3,4) ] );
|
|
const Array!T arrayTwo = Array!T([ T(1,2), T(3,4) ] );
|
|
|
|
assert(arrayOne == arrayTwo);
|
|
}
|
|
|
|
// nextPermutation
|
|
/**
|
|
* Permutes $(D range) in-place to the next lexicographically greater
|
|
* permutation.
|
|
*
|
|
* The predicate $(D less) defines the lexicographical ordering to be used on
|
|
* the range.
|
|
*
|
|
* If the range is currently the lexicographically greatest permutation, it is
|
|
* permuted back to the least permutation and false is returned. Otherwise,
|
|
* true is returned. One can thus generate all permutations of a range by
|
|
* sorting it according to $(D less), which produces the lexicographically
|
|
* least permutation, and then calling nextPermutation until it returns false.
|
|
* This is guaranteed to generate all distinct permutations of the range
|
|
* exactly once. If there are $(I N) elements in the range and all of them are
|
|
* unique, then $(I N)! permutations will be generated. Otherwise, if there are
|
|
* some duplicated elements, fewer permutations will be produced.
|
|
----
|
|
// Enumerate all permutations
|
|
int[] a = [1,2,3,4,5];
|
|
do
|
|
{
|
|
// use the current permutation and
|
|
// proceed to the next permutation of the array.
|
|
} while (nextPermutation(a));
|
|
----
|
|
* Returns: false if the range was lexicographically the greatest, in which
|
|
* case the range is reversed back to the lexicographically smallest
|
|
* permutation; otherwise returns true.
|
|
*/
|
|
bool nextPermutation(alias less="a<b", BidirectionalRange)
|
|
(BidirectionalRange range)
|
|
if (isBidirectionalRange!BidirectionalRange &&
|
|
hasSwappableElements!BidirectionalRange)
|
|
{
|
|
// Ranges of 0 or 1 element have no distinct permutations.
|
|
if (range.empty) return false;
|
|
|
|
auto i = retro(range);
|
|
auto last = i.save;
|
|
|
|
// Find last occurring increasing pair of elements
|
|
size_t n = 1;
|
|
for (i.popFront(); !i.empty; i.popFront(), last.popFront(), n++)
|
|
{
|
|
if (binaryFun!less(i.front, last.front))
|
|
break;
|
|
}
|
|
|
|
if (i.empty) {
|
|
// Entire range is decreasing: it's lexicographically the greatest. So
|
|
// wrap it around.
|
|
range.reverse();
|
|
return false;
|
|
}
|
|
|
|
// Find last element greater than i.front.
|
|
auto j = find!((a) => binaryFun!less(i.front, a))(
|
|
takeExactly(retro(range), n));
|
|
|
|
assert(!j.empty); // shouldn't happen since i.front < last.front
|
|
swap(i.front, j.front);
|
|
reverse(takeExactly(retro(range), n));
|
|
|
|
return true;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
// Step through all permutations of a sorted array in lexicographic order
|
|
int[] a = [1,2,3];
|
|
assert(nextPermutation(a) == true);
|
|
assert(a == [1,3,2]);
|
|
assert(nextPermutation(a) == true);
|
|
assert(a == [2,1,3]);
|
|
assert(nextPermutation(a) == true);
|
|
assert(a == [2,3,1]);
|
|
assert(nextPermutation(a) == true);
|
|
assert(a == [3,1,2]);
|
|
assert(nextPermutation(a) == true);
|
|
assert(a == [3,2,1]);
|
|
assert(nextPermutation(a) == false);
|
|
assert(a == [1,2,3]);
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
// Step through permutations of an array containing duplicate elements:
|
|
int[] a = [1,1,2];
|
|
assert(nextPermutation(a) == true);
|
|
assert(a == [1,2,1]);
|
|
assert(nextPermutation(a) == true);
|
|
assert(a == [2,1,1]);
|
|
assert(nextPermutation(a) == false);
|
|
assert(a == [1,1,2]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Boundary cases: arrays of 0 or 1 element.
|
|
int[] a1 = [];
|
|
assert(!nextPermutation(a1));
|
|
assert(a1 == []);
|
|
|
|
int[] a2 = [1];
|
|
assert(!nextPermutation(a2));
|
|
assert(a2 == [1]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto a1 = [1, 2, 3, 4];
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [1, 2, 4, 3]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [1, 3, 2, 4]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [1, 3, 4, 2]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [1, 4, 2, 3]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [1, 4, 3, 2]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [2, 1, 3, 4]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [2, 1, 4, 3]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [2, 3, 1, 4]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [2, 3, 4, 1]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [2, 4, 1, 3]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [2, 4, 3, 1]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [3, 1, 2, 4]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [3, 1, 4, 2]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [3, 2, 1, 4]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [3, 2, 4, 1]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [3, 4, 1, 2]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [3, 4, 2, 1]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [4, 1, 2, 3]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [4, 1, 3, 2]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [4, 2, 1, 3]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [4, 2, 3, 1]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [4, 3, 1, 2]));
|
|
|
|
assert(nextPermutation(a1));
|
|
assert(equal(a1, [4, 3, 2, 1]));
|
|
|
|
assert(!nextPermutation(a1));
|
|
assert(equal(a1, [1, 2, 3, 4]));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Test with non-default sorting order
|
|
int[] a = [3,2,1];
|
|
assert(nextPermutation!"a > b"(a) == true);
|
|
assert(a == [3,1,2]);
|
|
assert(nextPermutation!"a > b"(a) == true);
|
|
assert(a == [2,3,1]);
|
|
assert(nextPermutation!"a > b"(a) == true);
|
|
assert(a == [2,1,3]);
|
|
assert(nextPermutation!"a > b"(a) == true);
|
|
assert(a == [1,3,2]);
|
|
assert(nextPermutation!"a > b"(a) == true);
|
|
assert(a == [1,2,3]);
|
|
assert(nextPermutation!"a > b"(a) == false);
|
|
assert(a == [3,2,1]);
|
|
}
|
|
|
|
// Issue 13594
|
|
@safe unittest
|
|
{
|
|
int[3] a = [1,2,3];
|
|
assert(nextPermutation(a[]));
|
|
assert(a == [1,3,2]);
|
|
}
|
|
|
|
// nextEvenPermutation
|
|
/**
|
|
* Permutes $(D range) in-place to the next lexicographically greater $(I even)
|
|
* permutation.
|
|
*
|
|
* The predicate $(D less) defines the lexicographical ordering to be used on
|
|
* the range.
|
|
*
|
|
* An even permutation is one which is produced by swapping an even number of
|
|
* pairs of elements in the original range. The set of $(I even) permutations
|
|
* is distinct from the set of $(I all) permutations only when there are no
|
|
* duplicate elements in the range. If the range has $(I N) unique elements,
|
|
* then there are exactly $(I N)!/2 even permutations.
|
|
*
|
|
* If the range is already the lexicographically greatest even permutation, it
|
|
* is permuted back to the least even permutation and false is returned.
|
|
* Otherwise, true is returned, and the range is modified in-place to be the
|
|
* lexicographically next even permutation.
|
|
*
|
|
* One can thus generate the even permutations of a range with unique elements
|
|
* by starting with the lexicographically smallest permutation, and repeatedly
|
|
* calling nextEvenPermutation until it returns false.
|
|
----
|
|
// Enumerate even permutations
|
|
int[] a = [1,2,3,4,5];
|
|
do
|
|
{
|
|
// use the current permutation and
|
|
// proceed to the next even permutation of the array.
|
|
} while (nextEvenPermutation(a));
|
|
----
|
|
* One can also generate the $(I odd) permutations of a range by noting that
|
|
* permutations obey the rule that even + even = even, and odd + even = odd.
|
|
* Thus, by swapping the last two elements of a lexicographically least range,
|
|
* it is turned into the first odd permutation. Then calling
|
|
* nextEvenPermutation on this first odd permutation will generate the next
|
|
* even permutation relative to this odd permutation, which is actually the
|
|
* next odd permutation of the original range. Thus, by repeatedly calling
|
|
* nextEvenPermutation until it returns false, one enumerates the odd
|
|
* permutations of the original range.
|
|
----
|
|
// Enumerate odd permutations
|
|
int[] a = [1,2,3,4,5];
|
|
swap(a[$-2], a[$-1]); // a is now the first odd permutation of [1,2,3,4,5]
|
|
do
|
|
{
|
|
// use the current permutation and
|
|
// proceed to the next odd permutation of the original array
|
|
// (which is an even permutation of the first odd permutation).
|
|
} while (nextEvenPermutation(a));
|
|
----
|
|
*
|
|
* Warning: Since even permutations are only distinct from all permutations
|
|
* when the range elements are unique, this function assumes that there are no
|
|
* duplicate elements under the specified ordering. If this is not _true, some
|
|
* permutations may fail to be generated. When the range has non-unique
|
|
* elements, you should use $(MYREF nextPermutation) instead.
|
|
*
|
|
* Returns: false if the range was lexicographically the greatest, in which
|
|
* case the range is reversed back to the lexicographically smallest
|
|
* permutation; otherwise returns true.
|
|
*/
|
|
bool nextEvenPermutation(alias less="a<b", BidirectionalRange)
|
|
(BidirectionalRange range)
|
|
if (isBidirectionalRange!BidirectionalRange &&
|
|
hasSwappableElements!BidirectionalRange)
|
|
{
|
|
// Ranges of 0 or 1 element have no distinct permutations.
|
|
if (range.empty) return false;
|
|
|
|
bool oddParity = false;
|
|
bool ret = true;
|
|
do
|
|
{
|
|
auto i = retro(range);
|
|
auto last = i.save;
|
|
|
|
// Find last occurring increasing pair of elements
|
|
size_t n = 1;
|
|
for (i.popFront(); !i.empty;
|
|
i.popFront(), last.popFront(), n++)
|
|
{
|
|
if (binaryFun!less(i.front, last.front))
|
|
break;
|
|
}
|
|
|
|
if (!i.empty)
|
|
{
|
|
// Find last element greater than i.front.
|
|
auto j = find!((a) => binaryFun!less(i.front, a))(
|
|
takeExactly(retro(range), n));
|
|
|
|
// shouldn't happen since i.front < last.front
|
|
assert(!j.empty);
|
|
|
|
swap(i.front, j.front);
|
|
oddParity = !oddParity;
|
|
}
|
|
else
|
|
{
|
|
// Entire range is decreasing: it's lexicographically
|
|
// the greatest.
|
|
ret = false;
|
|
}
|
|
|
|
reverse(takeExactly(retro(range), n));
|
|
if ((n / 2) % 2 == 1)
|
|
oddParity = !oddParity;
|
|
} while(oddParity);
|
|
|
|
return ret;
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
// Step through even permutations of a sorted array in lexicographic order
|
|
int[] a = [1,2,3];
|
|
assert(nextEvenPermutation(a) == true);
|
|
assert(a == [2,3,1]);
|
|
assert(nextEvenPermutation(a) == true);
|
|
assert(a == [3,1,2]);
|
|
assert(nextEvenPermutation(a) == false);
|
|
assert(a == [1,2,3]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto a3 = [ 1, 2, 3, 4 ];
|
|
int count = 1;
|
|
while (nextEvenPermutation(a3)) count++;
|
|
assert(count == 12);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Test with non-default sorting order
|
|
auto a = [ 3, 2, 1 ];
|
|
|
|
assert(nextEvenPermutation!"a > b"(a) == true);
|
|
assert(a == [ 2, 1, 3 ]);
|
|
assert(nextEvenPermutation!"a > b"(a) == true);
|
|
assert(a == [ 1, 3, 2 ]);
|
|
assert(nextEvenPermutation!"a > b"(a) == false);
|
|
assert(a == [ 3, 2, 1 ]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Test various cases of rollover
|
|
auto a = [ 3, 1, 2 ];
|
|
assert(nextEvenPermutation(a) == false);
|
|
assert(a == [ 1, 2, 3 ]);
|
|
|
|
auto b = [ 3, 2, 1 ];
|
|
assert(nextEvenPermutation(b) == false);
|
|
assert(b == [ 1, 3, 2 ]);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Issue 13594
|
|
int[3] a = [1,2,3];
|
|
assert(nextEvenPermutation(a[]));
|
|
assert(a == [2,3,1]);
|
|
}
|
|
|
|
/**
|
|
Even permutations are useful for generating coordinates of certain geometric
|
|
shapes. Here's a non-trivial example:
|
|
*/
|
|
@safe unittest
|
|
{
|
|
import std.math : sqrt;
|
|
|
|
// Print the 60 vertices of a uniform truncated icosahedron (soccer ball)
|
|
enum real Phi = (1.0 + sqrt(5.0)) / 2.0; // Golden ratio
|
|
real[][] seeds = [
|
|
[0.0, 1.0, 3.0*Phi],
|
|
[1.0, 2.0+Phi, 2.0*Phi],
|
|
[Phi, 2.0, Phi^^3]
|
|
];
|
|
size_t n;
|
|
foreach (seed; seeds)
|
|
{
|
|
// Loop over even permutations of each seed
|
|
do
|
|
{
|
|
// Loop over all sign changes of each permutation
|
|
size_t i;
|
|
do
|
|
{
|
|
// Generate all possible sign changes
|
|
for (i=0; i < seed.length; i++)
|
|
{
|
|
if (seed[i] != 0.0)
|
|
{
|
|
seed[i] = -seed[i];
|
|
if (seed[i] < 0.0)
|
|
break;
|
|
}
|
|
}
|
|
n++;
|
|
} while (i < seed.length);
|
|
} while (nextEvenPermutation(seed));
|
|
}
|
|
assert(n == 60);
|
|
}
|
|
|
|
// Used in castSwitch to find the first choice that overshadows the last choice
|
|
// in a tuple.
|
|
private template indexOfFirstOvershadowingChoiceOnLast(choices...)
|
|
{
|
|
alias firstParameterTypes = ParameterTypeTuple!(choices[0]);
|
|
alias lastParameterTypes = ParameterTypeTuple!(choices[$ - 1]);
|
|
|
|
static if (lastParameterTypes.length == 0)
|
|
{
|
|
// If the last is null-typed choice, check if the first is null-typed.
|
|
enum isOvershadowing = firstParameterTypes.length == 0;
|
|
}
|
|
else static if (firstParameterTypes.length == 1)
|
|
{
|
|
// If the both first and last are not null-typed, check for overshadowing.
|
|
enum isOvershadowing =
|
|
is(firstParameterTypes[0] == Object) // Object overshadows all other classes!(this is needed for interfaces)
|
|
|| is(lastParameterTypes[0] : firstParameterTypes[0]);
|
|
}
|
|
else
|
|
{
|
|
// If the first is null typed and the last is not - the is no overshadowing.
|
|
enum isOvershadowing = false;
|
|
}
|
|
|
|
static if (isOvershadowing)
|
|
{
|
|
enum indexOfFirstOvershadowingChoiceOnLast = 0;
|
|
}
|
|
else
|
|
{
|
|
enum indexOfFirstOvershadowingChoiceOnLast =
|
|
1 + indexOfFirstOvershadowingChoiceOnLast!(choices[1..$]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Executes and returns one of a collection of handlers based on the type of the
|
|
switch object.
|
|
|
|
$(D choices) needs to be composed of function or delegate handlers that accept
|
|
one argument. The first choice that $(D switchObject) can be casted to the type
|
|
of argument it accepts will be called with $(D switchObject) casted to that
|
|
type, and the value it'll return will be returned by $(D castSwitch).
|
|
|
|
There can also be a choice that accepts zero arguments. That choice will be
|
|
invoked if $(D switchObject) is null.
|
|
|
|
If a choice's return type is void, the choice must throw an exception, unless
|
|
all the choices are void. In that case, castSwitch itself will return void.
|
|
|
|
Throws: If none of the choice matches, a $(D SwitchError) will be thrown. $(D
|
|
SwitchError) will also be thrown if not all the choices are void and a void
|
|
choice was executed without throwing anything.
|
|
|
|
Note: $(D castSwitch) can only be used with object types.
|
|
*/
|
|
auto castSwitch(choices...)(Object switchObject)
|
|
{
|
|
import core.exception : SwitchError;
|
|
|
|
// Check to see if all handlers return void.
|
|
enum areAllHandlersVoidResult={
|
|
foreach(index, choice; choices)
|
|
{
|
|
if(!is(ReturnType!choice == void))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}();
|
|
|
|
if (switchObject !is null)
|
|
{
|
|
|
|
// Checking for exact matches:
|
|
ClassInfo classInfo = typeid(switchObject);
|
|
foreach (index, choice; choices)
|
|
{
|
|
static assert(isCallable!choice,
|
|
"A choice handler must be callable");
|
|
|
|
alias choiceParameterTypes = ParameterTypeTuple!choice;
|
|
static assert(choiceParameterTypes.length <= 1,
|
|
"A choice handler can not have more than one argument.");
|
|
|
|
static if (choiceParameterTypes.length == 1)
|
|
{
|
|
alias CastClass = choiceParameterTypes[0];
|
|
static assert(is(CastClass == class) || is(CastClass == interface),
|
|
"A choice handler can have only class or interface typed argument.");
|
|
|
|
// Check for overshadowing:
|
|
immutable indexOfOvershadowingChoice =
|
|
indexOfFirstOvershadowingChoiceOnLast!(choices[0..index + 1]);
|
|
static assert(indexOfOvershadowingChoice == index,
|
|
"choice number %d(type %s) is overshadowed by choice number %d(type %s)".format(
|
|
index + 1, CastClass.stringof, indexOfOvershadowingChoice + 1,
|
|
ParameterTypeTuple!(choices[indexOfOvershadowingChoice])[0].stringof));
|
|
|
|
if (classInfo == typeid(CastClass))
|
|
{
|
|
static if(is(ReturnType!(choice) == void))
|
|
{
|
|
choice(cast(CastClass) switchObject);
|
|
static if (areAllHandlersVoidResult)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
throw new SwitchError("Handlers that return void should throw");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return choice(cast(CastClass) switchObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checking for derived matches:
|
|
foreach (choice; choices)
|
|
{
|
|
alias choiceParameterTypes = ParameterTypeTuple!choice;
|
|
static if (choiceParameterTypes.length == 1)
|
|
{
|
|
if (auto castedObject = cast(choiceParameterTypes[0]) switchObject)
|
|
{
|
|
static if(is(ReturnType!(choice) == void))
|
|
{
|
|
choice(castedObject);
|
|
static if (areAllHandlersVoidResult)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
throw new SwitchError("Handlers that return void should throw");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return choice(castedObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // If switchObject is null:
|
|
{
|
|
// Checking for null matches:
|
|
foreach (index, choice; choices)
|
|
{
|
|
static if (ParameterTypeTuple!(choice).length == 0)
|
|
{
|
|
immutable indexOfOvershadowingChoice =
|
|
indexOfFirstOvershadowingChoiceOnLast!(choices[0..index + 1]);
|
|
|
|
// Check for overshadowing:
|
|
static assert(indexOfOvershadowingChoice == index,
|
|
"choice number %d(null reference) is overshadowed by choice number %d(null reference)".format(
|
|
index + 1, indexOfOvershadowingChoice + 1));
|
|
|
|
if (switchObject is null)
|
|
{
|
|
static if(is(ReturnType!(choice) == void))
|
|
{
|
|
choice();
|
|
static if (areAllHandlersVoidResult)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
throw new SwitchError("Handlers that return void should throw");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return choice();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// In case nothing matched:
|
|
throw new SwitchError("Input not matched by any choice");
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
import std.string : format;
|
|
|
|
class A
|
|
{
|
|
int a;
|
|
this(int a) {this.a = a;}
|
|
@property int i() { return a; }
|
|
}
|
|
interface I { }
|
|
class B : I { }
|
|
|
|
Object[] arr = [new A(1), new B(), null];
|
|
|
|
auto results = arr.map!(castSwitch!(
|
|
(A a) => "A with a value of %d".format(a.a),
|
|
(I i) => "derived from I",
|
|
() => "null reference",
|
|
))();
|
|
|
|
// A is handled directly:
|
|
assert(results[0] == "A with a value of 1");
|
|
// B has no handler - it is handled by the handler of I:
|
|
assert(results[1] == "derived from I");
|
|
// null is handled by the null handler:
|
|
assert(results[2] == "null reference");
|
|
}
|
|
|
|
/// Using with void handlers:
|
|
unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
|
|
class A { }
|
|
class B { }
|
|
// Void handlers are allowed if they throw:
|
|
assertThrown!Exception(
|
|
new B().castSwitch!(
|
|
(A a) => 1,
|
|
(B d) { throw new Exception("B is not allowed!"); }
|
|
)()
|
|
);
|
|
|
|
// Void handlers are also allowed if all the handlers are void:
|
|
new A().castSwitch!(
|
|
(A a) { assert(true); },
|
|
(B b) { assert(false); },
|
|
)();
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import core.exception : SwitchError;
|
|
import std.exception : assertThrown;
|
|
|
|
interface I { }
|
|
class A : I { }
|
|
class B { }
|
|
|
|
// Nothing matches:
|
|
assertThrown!SwitchError((new A()).castSwitch!(
|
|
(B b) => 1,
|
|
() => 2,
|
|
)());
|
|
|
|
// Choices with multiple arguments are not allowed:
|
|
static assert(!__traits(compiles,
|
|
(new A()).castSwitch!(
|
|
(A a, B b) => 0,
|
|
)()));
|
|
|
|
// Only callable handlers allowed:
|
|
static assert(!__traits(compiles,
|
|
(new A()).castSwitch!(
|
|
1234,
|
|
)()));
|
|
|
|
// Only object arguments allowed:
|
|
static assert(!__traits(compiles,
|
|
(new A()).castSwitch!(
|
|
(int x) => 0,
|
|
)()));
|
|
|
|
// Object overshadows regular classes:
|
|
static assert(!__traits(compiles,
|
|
(new A()).castSwitch!(
|
|
(Object o) => 0,
|
|
(A a) => 1,
|
|
)()));
|
|
|
|
// Object overshadows interfaces:
|
|
static assert(!__traits(compiles,
|
|
(new A()).castSwitch!(
|
|
(Object o) => 0,
|
|
(I i) => 1,
|
|
)()));
|
|
|
|
// No multiple null handlers allowed:
|
|
static assert(!__traits(compiles,
|
|
(new A()).castSwitch!(
|
|
() => 0,
|
|
() => 1,
|
|
)()));
|
|
|
|
// No non-throwing void handlers allowed(when there are non-void handlers):
|
|
assertThrown!SwitchError((new A()).castSwitch!(
|
|
(A a) {},
|
|
(B b) => 2,
|
|
)());
|
|
|
|
// All-void handlers work for the null case:
|
|
null.castSwitch!(
|
|
(Object o) { assert(false); },
|
|
() { assert(true); },
|
|
)();
|
|
|
|
// Throwing void handlers work for the null case:
|
|
assertThrown!Exception(null.castSwitch!(
|
|
(Object o) => 1,
|
|
() { throw new Exception("null"); },
|
|
)());
|
|
}
|
|
|
|
// cartesianProduct
|
|
/**
|
|
Lazily computes the Cartesian product of two or more ranges. The product is a
|
|
_range of tuples of elements from each respective range.
|
|
|
|
The conditions for the two-range case are as follows:
|
|
|
|
If both ranges are finite, then one must be (at least) a forward range and the
|
|
other an input range.
|
|
|
|
If one _range is infinite and the other finite, then the finite _range must
|
|
be a forward _range, and the infinite range can be an input _range.
|
|
|
|
If both ranges are infinite, then both must be forward ranges.
|
|
|
|
When there are more than two ranges, the above conditions apply to each
|
|
adjacent pair of ranges.
|
|
*/
|
|
auto cartesianProduct(R1, R2)(R1 range1, R2 range2)
|
|
if (!allSatisfy!(isForwardRange, R1, R2) ||
|
|
anySatisfy!(isInfinite, R1, R2))
|
|
{
|
|
static if (isInfinite!R1 && isInfinite!R2)
|
|
{
|
|
static if (isForwardRange!R1 && isForwardRange!R2)
|
|
{
|
|
// This algorithm traverses the cartesian product by alternately
|
|
// covering the right and bottom edges of an increasing square area
|
|
// over the infinite table of combinations. This schedule allows us
|
|
// to require only forward ranges.
|
|
return zip(sequence!"n"(cast(size_t)0), range1.save, range2.save,
|
|
repeat(range1), repeat(range2))
|
|
.map!(function(a) => chain(
|
|
zip(repeat(a[1]), take(a[4].save, a[0])),
|
|
zip(take(a[3].save, a[0]+1), repeat(a[2]))
|
|
))()
|
|
.joiner();
|
|
}
|
|
else static assert(0, "cartesianProduct of infinite ranges requires "~
|
|
"forward ranges");
|
|
}
|
|
else static if (isInputRange!R1 && isForwardRange!R2 && !isInfinite!R2)
|
|
{
|
|
return joiner(map!((ElementType!R1 a) => zip(repeat(a), range2.save))
|
|
(range1));
|
|
}
|
|
else static if (isInputRange!R2 && isForwardRange!R1 && !isInfinite!R1)
|
|
{
|
|
return joiner(map!((ElementType!R2 a) => zip(range1.save, repeat(a)))
|
|
(range2));
|
|
}
|
|
else static assert(0, "cartesianProduct involving finite ranges must "~
|
|
"have at least one finite forward range");
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto N = sequence!"n"(0); // the range of natural numbers
|
|
auto N2 = cartesianProduct(N, N); // the range of all pairs of natural numbers
|
|
|
|
// Various arbitrary number pairs can be found in the range in finite time.
|
|
assert(canFind(N2, tuple(0, 0)));
|
|
assert(canFind(N2, tuple(123, 321)));
|
|
assert(canFind(N2, tuple(11, 35)));
|
|
assert(canFind(N2, tuple(279, 172)));
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto B = [ 1, 2, 3 ];
|
|
auto C = [ 4, 5, 6 ];
|
|
auto BC = cartesianProduct(B, C);
|
|
|
|
foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6],
|
|
[2, 6], [3, 6]])
|
|
{
|
|
assert(canFind(BC, tuple(n[0], n[1])));
|
|
}
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Test cartesian product of two infinite ranges
|
|
auto Even = sequence!"2*n"(0);
|
|
auto Odd = sequence!"2*n+1"(0);
|
|
auto EvenOdd = cartesianProduct(Even, Odd);
|
|
|
|
foreach (pair; [[0, 1], [2, 1], [0, 3], [2, 3], [4, 1], [4, 3], [0, 5],
|
|
[2, 5], [4, 5], [6, 1], [6, 3], [6, 5]])
|
|
{
|
|
assert(canFind(EvenOdd, tuple(pair[0], pair[1])));
|
|
}
|
|
|
|
// This should terminate in finite time
|
|
assert(canFind(EvenOdd, tuple(124, 73)));
|
|
assert(canFind(EvenOdd, tuple(0, 97)));
|
|
assert(canFind(EvenOdd, tuple(42, 1)));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Test cartesian product of an infinite input range and a finite forward
|
|
// range.
|
|
auto N = sequence!"n"(0);
|
|
auto M = [100, 200, 300];
|
|
auto NM = cartesianProduct(N,M);
|
|
|
|
foreach (pair; [[0, 100], [0, 200], [0, 300], [1, 100], [1, 200], [1, 300],
|
|
[2, 100], [2, 200], [2, 300], [3, 100], [3, 200],
|
|
[3, 300]])
|
|
{
|
|
assert(canFind(NM, tuple(pair[0], pair[1])));
|
|
}
|
|
|
|
// We can't solve the halting problem, so we can only check a finite
|
|
// initial segment here.
|
|
assert(!canFind(NM.take(100), tuple(100, 0)));
|
|
assert(!canFind(NM.take(100), tuple(1, 1)));
|
|
assert(!canFind(NM.take(100), tuple(100, 200)));
|
|
|
|
auto MN = cartesianProduct(M,N);
|
|
foreach (pair; [[100, 0], [200, 0], [300, 0], [100, 1], [200, 1], [300, 1],
|
|
[100, 2], [200, 2], [300, 2], [100, 3], [200, 3],
|
|
[300, 3]])
|
|
{
|
|
assert(canFind(MN, tuple(pair[0], pair[1])));
|
|
}
|
|
|
|
// We can't solve the halting problem, so we can only check a finite
|
|
// initial segment here.
|
|
assert(!canFind(MN.take(100), tuple(0, 100)));
|
|
assert(!canFind(MN.take(100), tuple(0, 1)));
|
|
assert(!canFind(MN.take(100), tuple(100, 200)));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Test cartesian product of two finite ranges.
|
|
auto X = [1, 2, 3];
|
|
auto Y = [4, 5, 6];
|
|
auto XY = cartesianProduct(X, Y);
|
|
auto Expected = [[1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4],
|
|
[3, 5], [3, 6]];
|
|
|
|
// Verify Expected ⊆ XY
|
|
foreach (pair; Expected)
|
|
{
|
|
assert(canFind(XY, tuple(pair[0], pair[1])));
|
|
}
|
|
|
|
// Verify XY ⊆ Expected
|
|
foreach (pair; XY)
|
|
{
|
|
assert(canFind(Expected, [pair[0], pair[1]]));
|
|
}
|
|
|
|
// And therefore, by set comprehension, XY == Expected
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto N = sequence!"n"(0);
|
|
|
|
// To force the template to fall to the second case, we wrap N in a struct
|
|
// that doesn't allow bidirectional access.
|
|
struct FwdRangeWrapper(R)
|
|
{
|
|
R impl;
|
|
|
|
// Input range API
|
|
@property auto front() { return impl.front; }
|
|
void popFront() { impl.popFront(); }
|
|
static if (isInfinite!R)
|
|
enum empty = false;
|
|
else
|
|
@property bool empty() { return impl.empty; }
|
|
|
|
// Forward range API
|
|
@property auto save() { return typeof(this)(impl.save); }
|
|
}
|
|
auto fwdWrap(R)(R range) { return FwdRangeWrapper!R(range); }
|
|
|
|
// General test: two infinite bidirectional ranges
|
|
auto N2 = cartesianProduct(N, N);
|
|
|
|
assert(canFind(N2, tuple(0, 0)));
|
|
assert(canFind(N2, tuple(123, 321)));
|
|
assert(canFind(N2, tuple(11, 35)));
|
|
assert(canFind(N2, tuple(279, 172)));
|
|
|
|
// Test first case: forward range with bidirectional range
|
|
auto fwdN = fwdWrap(N);
|
|
auto N2_a = cartesianProduct(fwdN, N);
|
|
|
|
assert(canFind(N2_a, tuple(0, 0)));
|
|
assert(canFind(N2_a, tuple(123, 321)));
|
|
assert(canFind(N2_a, tuple(11, 35)));
|
|
assert(canFind(N2_a, tuple(279, 172)));
|
|
|
|
// Test second case: bidirectional range with forward range
|
|
auto N2_b = cartesianProduct(N, fwdN);
|
|
|
|
assert(canFind(N2_b, tuple(0, 0)));
|
|
assert(canFind(N2_b, tuple(123, 321)));
|
|
assert(canFind(N2_b, tuple(11, 35)));
|
|
assert(canFind(N2_b, tuple(279, 172)));
|
|
|
|
// Test third case: finite forward range with (infinite) input range
|
|
static struct InpRangeWrapper(R)
|
|
{
|
|
R impl;
|
|
|
|
// Input range API
|
|
@property auto front() { return impl.front; }
|
|
void popFront() { impl.popFront(); }
|
|
static if (isInfinite!R)
|
|
enum empty = false;
|
|
else
|
|
@property bool empty() { return impl.empty; }
|
|
}
|
|
auto inpWrap(R)(R r) { return InpRangeWrapper!R(r); }
|
|
|
|
auto inpN = inpWrap(N);
|
|
auto B = [ 1, 2, 3 ];
|
|
auto fwdB = fwdWrap(B);
|
|
auto BN = cartesianProduct(fwdB, inpN);
|
|
|
|
assert(equal(map!"[a[0],a[1]]"(BN.take(10)), [[1, 0], [2, 0], [3, 0],
|
|
[1, 1], [2, 1], [3, 1], [1, 2], [2, 2], [3, 2], [1, 3]]));
|
|
|
|
// Test fourth case: (infinite) input range with finite forward range
|
|
auto NB = cartesianProduct(inpN, fwdB);
|
|
|
|
assert(equal(map!"[a[0],a[1]]"(NB.take(10)), [[0, 1], [0, 2], [0, 3],
|
|
[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1]]));
|
|
|
|
// General finite range case
|
|
auto C = [ 4, 5, 6 ];
|
|
auto BC = cartesianProduct(B, C);
|
|
|
|
foreach (n; [[1, 4], [2, 4], [3, 4], [1, 5], [2, 5], [3, 5], [1, 6],
|
|
[2, 6], [3, 6]])
|
|
{
|
|
assert(canFind(BC, tuple(n[0], n[1])));
|
|
}
|
|
}
|
|
|
|
// Issue 13091
|
|
pure nothrow @safe @nogc unittest
|
|
{
|
|
import std.algorithm: cartesianProduct;
|
|
int[1] a = [1];
|
|
foreach (t; cartesianProduct(a[], a[])) {}
|
|
}
|
|
|
|
/// ditto
|
|
auto cartesianProduct(RR...)(RR ranges)
|
|
if (ranges.length >= 2 &&
|
|
allSatisfy!(isForwardRange, RR) &&
|
|
!anySatisfy!(isInfinite, RR))
|
|
{
|
|
// This overload uses a much less template-heavy implementation when
|
|
// all ranges are finite forward ranges, which is the most common use
|
|
// case, so that we don't run out of resources too quickly.
|
|
//
|
|
// For infinite ranges or non-forward ranges, we fall back to the old
|
|
// implementation which expands an exponential number of templates.
|
|
import std.typecons : tuple;
|
|
|
|
static struct Result
|
|
{
|
|
RR ranges;
|
|
RR current;
|
|
bool empty = true;
|
|
|
|
this(RR _ranges)
|
|
{
|
|
ranges = _ranges;
|
|
empty = false;
|
|
foreach (i, r; ranges)
|
|
{
|
|
current[i] = r.save;
|
|
if (current[i].empty)
|
|
empty = true;
|
|
}
|
|
}
|
|
@property auto front()
|
|
{
|
|
return mixin(algoFormat("tuple(%(current[%d].front%|,%))",
|
|
iota(0, current.length)));
|
|
}
|
|
void popFront()
|
|
{
|
|
foreach_reverse (i, ref r; current)
|
|
{
|
|
r.popFront();
|
|
if (!r.empty) break;
|
|
|
|
static if (i==0)
|
|
empty = true;
|
|
else
|
|
r = ranges[i].save; // rollover
|
|
}
|
|
}
|
|
@property Result save()
|
|
{
|
|
Result copy;
|
|
foreach (i, r; ranges)
|
|
{
|
|
copy.ranges[i] = r.save;
|
|
copy.current[i] = current[i].save;
|
|
}
|
|
copy.empty = this.empty;
|
|
return copy;
|
|
}
|
|
}
|
|
static assert(isForwardRange!Result);
|
|
|
|
return Result(ranges);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Issue 10693: cartesian product of empty ranges should be empty.
|
|
int[] a, b, c, d, e;
|
|
auto cprod = cartesianProduct(a,b,c,d,e);
|
|
assert(cprod.empty);
|
|
foreach (_; cprod) {} // should not crash
|
|
|
|
// Test case where only one of the ranges is empty: the result should still
|
|
// be empty.
|
|
int[] p=[1], q=[];
|
|
auto cprod2 = cartesianProduct(p,p,p,q,p);
|
|
assert(cprod2.empty);
|
|
foreach (_; cprod2) {} // should not crash
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// .init value of cartesianProduct should be empty
|
|
auto cprod = cartesianProduct([0,0], [1,1], [2,2]);
|
|
assert(!cprod.empty);
|
|
assert(cprod.init.empty);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
// Issue 13393
|
|
assert(!cartesianProduct([0],[0],[0]).save.empty);
|
|
}
|
|
|
|
/// ditto
|
|
auto cartesianProduct(R1, R2, RR...)(R1 range1, R2 range2, RR otherRanges)
|
|
if (!allSatisfy!(isForwardRange, R1, R2, RR) ||
|
|
anySatisfy!(isInfinite, R1, R2, RR))
|
|
{
|
|
/* We implement the n-ary cartesian product by recursively invoking the
|
|
* binary cartesian product. To make the resulting range nicer, we denest
|
|
* one level of tuples so that a ternary cartesian product, for example,
|
|
* returns 3-element tuples instead of nested 2-element tuples.
|
|
*/
|
|
enum string denest = algoFormat("tuple(a[0], %(a[1][%d]%|,%))",
|
|
iota(0, otherRanges.length+1));
|
|
return map!denest(
|
|
cartesianProduct(range1, cartesianProduct(range2, otherRanges))
|
|
);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto N = sequence!"n"(0);
|
|
auto N3 = cartesianProduct(N, N, N);
|
|
|
|
// Check that tuples are properly denested
|
|
assert(is(ElementType!(typeof(N3)) == Tuple!(size_t,size_t,size_t)));
|
|
|
|
assert(canFind(N3, tuple(0, 27, 7)));
|
|
assert(canFind(N3, tuple(50, 23, 71)));
|
|
assert(canFind(N3, tuple(9, 3, 0)));
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto N = sequence!"n"(0);
|
|
auto N4 = cartesianProduct(N, N, N, N);
|
|
|
|
// Check that tuples are properly denested
|
|
assert(is(ElementType!(typeof(N4)) == Tuple!(size_t,size_t,size_t,size_t)));
|
|
|
|
assert(canFind(N4, tuple(1, 2, 3, 4)));
|
|
assert(canFind(N4, tuple(4, 3, 2, 1)));
|
|
assert(canFind(N4, tuple(10, 31, 7, 12)));
|
|
}
|
|
|
|
// Issue 9878
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto A = [ 1, 2, 3 ];
|
|
auto B = [ 'a', 'b', 'c' ];
|
|
auto C = [ "x", "y", "z" ];
|
|
auto ABC = cartesianProduct(A, B, C);
|
|
|
|
assert(ABC.equal([
|
|
tuple(1, 'a', "x"), tuple(1, 'a', "y"), tuple(1, 'a', "z"),
|
|
tuple(1, 'b', "x"), tuple(1, 'b', "y"), tuple(1, 'b', "z"),
|
|
tuple(1, 'c', "x"), tuple(1, 'c', "y"), tuple(1, 'c', "z"),
|
|
tuple(2, 'a', "x"), tuple(2, 'a', "y"), tuple(2, 'a', "z"),
|
|
tuple(2, 'b', "x"), tuple(2, 'b', "y"), tuple(2, 'b', "z"),
|
|
tuple(2, 'c', "x"), tuple(2, 'c', "y"), tuple(2, 'c', "z"),
|
|
tuple(3, 'a', "x"), tuple(3, 'a', "y"), tuple(3, 'a', "z"),
|
|
tuple(3, 'b', "x"), tuple(3, 'b', "y"), tuple(3, 'b', "z"),
|
|
tuple(3, 'c', "x"), tuple(3, 'c', "y"), tuple(3, 'c', "z")
|
|
]));
|
|
}
|
|
|
|
pure @safe nothrow @nogc unittest
|
|
{
|
|
int[2] A = [1,2];
|
|
auto C = cartesianProduct(A[], A[], A[]);
|
|
assert(isForwardRange!(typeof(C)));
|
|
|
|
C.popFront();
|
|
auto front1 = C.front;
|
|
auto D = C.save;
|
|
C.popFront();
|
|
assert(D.front == front1);
|
|
}
|
|
|
|
/**
|
|
Find $(D value) _among $(D values), returning the 1-based index
|
|
of the first matching value in $(D values), or $(D 0) if $(D value)
|
|
is not _among $(D values). The predicate $(D pred) is used to
|
|
compare values, and uses equality by default.
|
|
|
|
See_Also:
|
|
$(LREF find) and $(LREF canFind) for finding a value in a
|
|
range.
|
|
*/
|
|
uint among(alias pred = (a, b) => a == b, Value, Values...)
|
|
(Value value, Values values)
|
|
if (Values.length != 0)
|
|
{
|
|
foreach (uint i, ref v; values)
|
|
{
|
|
import std.functional : binaryFun;
|
|
if (binaryFun!pred(value, v)) return i + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// Ditto
|
|
template among(values...)
|
|
if (isExpressionTuple!values)
|
|
{
|
|
uint among(Value)(Value value)
|
|
if (!is(CommonType!(Value, values) == void))
|
|
{
|
|
switch (value)
|
|
{
|
|
foreach (uint i, v; values)
|
|
case v:
|
|
return i + 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
assert(3.among(1, 42, 24, 3, 2));
|
|
|
|
if (auto pos = "bar".among("foo", "bar", "baz"))
|
|
assert(pos == 2);
|
|
else
|
|
assert(false);
|
|
|
|
// 42 is larger than 24
|
|
assert(42.among!((lhs, rhs) => lhs > rhs)(43, 24, 100) == 2);
|
|
}
|
|
|
|
/**
|
|
Alternatively, $(D values) can be passed at compile-time, allowing for a more
|
|
efficient search, but one that only supports matching on equality:
|
|
*/
|
|
@safe unittest
|
|
{
|
|
assert(3.among!(2, 3, 4));
|
|
assert("bar".among!("foo", "bar", "baz") == 2);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
if (auto pos = 3.among(1, 2, 3))
|
|
assert(pos == 3);
|
|
else
|
|
assert(false);
|
|
assert(!4.among(1, 2, 3));
|
|
|
|
auto position = "hello".among("hello", "world");
|
|
assert(position);
|
|
assert(position == 1);
|
|
|
|
alias values = TypeTuple!("foo", "bar", "baz");
|
|
auto arr = [values];
|
|
assert(arr[0 .. "foo".among(values)] == ["foo"]);
|
|
assert(arr[0 .. "bar".among(values)] == ["foo", "bar"]);
|
|
assert(arr[0 .. "baz".among(values)] == arr);
|
|
assert("foobar".among(values) == 0);
|
|
|
|
if (auto pos = 3.among!(1, 2, 3))
|
|
assert(pos == 3);
|
|
else
|
|
assert(false);
|
|
assert(!4.among!(1, 2, 3));
|
|
|
|
position = "hello".among!("hello", "world");
|
|
assert(position);
|
|
assert(position == 1);
|
|
|
|
static assert(!__traits(compiles, "a".among!("a", 42)));
|
|
static assert(!__traits(compiles, (Object.init).among!(42, "a")));
|
|
}
|
|
|
|
/**
|
|
Returns one of a collection of expressions based on the value of the switch
|
|
expression.
|
|
|
|
$(D choices) needs to be composed of pairs of test expressions and return
|
|
expressions. Each test-expression is compared with $(D switchExpression) using
|
|
$(D pred)($(D switchExpression) is the first argument) and if that yields true
|
|
- the return expression is returned.
|
|
|
|
Both the test and the return expressions are lazily evaluated.
|
|
|
|
Params:
|
|
|
|
switchExpression = The first argument for the predicate.
|
|
|
|
choices = Pairs of test expressions and return expressions. The test
|
|
expressions will be the second argument for the predicate, and the return
|
|
expression will be returned if the predicate yields true with $(D
|
|
switchExpression) and the test expression as arguments. May also have a
|
|
default return expression, that needs to be the last expression without a test
|
|
expression before it. A return expression may be of void type only if it
|
|
always throws.
|
|
|
|
Returns: The return expression associated with the first test expression that
|
|
made the predicate yield true, or the default return expression if no test
|
|
expression matched.
|
|
|
|
Throws: If there is no default return expression and the predicate does not
|
|
yield true with any test expression - $(D SwitchError) is thrown. $(D
|
|
SwitchError) is also thrown if a void return expression was executed without
|
|
throwing anything.
|
|
*/
|
|
auto predSwitch(alias pred = "a == b", T, R ...)(T switchExpression, lazy R choices)
|
|
{
|
|
import core.exception : SwitchError;
|
|
alias predicate = binaryFun!(pred);
|
|
|
|
foreach (index, ChoiceType; R)
|
|
{
|
|
//The even places in `choices` are for the predicate.
|
|
static if (index % 2 == 1)
|
|
{
|
|
if (predicate(switchExpression, choices[index - 1]()))
|
|
{
|
|
static if (is(typeof(choices[index]()) == void))
|
|
{
|
|
choices[index]();
|
|
throw new SwitchError("Choices that return void should throw");
|
|
}
|
|
else
|
|
{
|
|
return choices[index]();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//In case nothing matched:
|
|
static if (R.length % 2 == 1) //If there is a default return expression:
|
|
{
|
|
static if (is(typeof(choices[$ - 1]()) == void))
|
|
{
|
|
choices[$ - 1]();
|
|
throw new SwitchError("Choices that return void should throw");
|
|
}
|
|
else
|
|
{
|
|
return choices[$ - 1]();
|
|
}
|
|
}
|
|
else //If there is no default return expression:
|
|
{
|
|
throw new SwitchError("Input not matched by any pattern");
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
string res = 2.predSwitch!"a < b"(
|
|
1, "less than 1",
|
|
5, "less than 5",
|
|
10, "less than 10",
|
|
"greater or equal to 10");
|
|
|
|
assert(res == "less than 5");
|
|
|
|
//The arguments are lazy, which allows us to use predSwitch to create
|
|
//recursive functions:
|
|
int factorial(int n)
|
|
{
|
|
return n.predSwitch!"a <= b"(
|
|
-1, {throw new Exception("Can not calculate n! for n < 0");}(),
|
|
0, 1, // 0! = 1
|
|
n * factorial(n - 1) // n! = n * (n - 1)! for n >= 0
|
|
);
|
|
}
|
|
assert(factorial(3) == 6);
|
|
|
|
//Void return expressions are allowed if they always throw:
|
|
import std.exception : assertThrown;
|
|
assertThrown!Exception(factorial(-9));
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import core.exception : SwitchError;
|
|
import std.exception : assertThrown;
|
|
|
|
//Nothing matches - with default return expression:
|
|
assert(20.predSwitch!"a < b"(
|
|
1, "less than 1",
|
|
5, "less than 5",
|
|
10, "less than 10",
|
|
"greater or equal to 10") == "greater or equal to 10");
|
|
|
|
//Nothing matches - without default return expression:
|
|
assertThrown!SwitchError(20.predSwitch!"a < b"(
|
|
1, "less than 1",
|
|
5, "less than 5",
|
|
10, "less than 10",
|
|
));
|
|
|
|
//Using the default predicate:
|
|
assert(2.predSwitch(
|
|
1, "one",
|
|
2, "two",
|
|
3, "three",
|
|
) == "two");
|
|
|
|
//Void return expressions must always throw:
|
|
assertThrown!SwitchError(1.predSwitch(
|
|
0, "zero",
|
|
1, {}(), //A void return expression that doesn't throw
|
|
2, "two",
|
|
));
|
|
}
|