diff options
author | Iain Buclaw <ibuclaw@gdcproject.org> | 2019-06-18 20:42:10 +0200 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gdcproject.org> | 2021-11-30 16:53:28 +0100 |
commit | 5fee5ec362f7a243f459e6378fd49dfc89dc9fb5 (patch) | |
tree | 61d1bdbca854a903c0860406f457f06b2040be7a /libphobos/src/std/algorithm | |
parent | b3f60112edcb85b459e60f66c44a55138b1cef49 (diff) | |
download | gcc-5fee5ec362f7a243f459e6378fd49dfc89dc9fb5.zip gcc-5fee5ec362f7a243f459e6378fd49dfc89dc9fb5.tar.gz gcc-5fee5ec362f7a243f459e6378fd49dfc89dc9fb5.tar.bz2 |
d: Import dmd b8384668f, druntime e6caaab9, phobos 5ab9ad256 (v2.098.0-beta.1)
The D front-end is now itself written in D, in order to build GDC, you
will need a working GDC compiler (GCC version 9.1 or later).
GCC changes:
- Add support for bootstrapping the D front-end.
These add the required components in order to have a D front-end written
in D itself. Because the compiler front-end only depends on the core
runtime modules, only libdruntime is built for the bootstrap stages.
D front-end changes:
- Import dmd v2.098.0-beta.1.
Druntime changes:
- Import druntime v2.098.0-beta.1.
Phobos changes:
- Import phobos v2.098.0-beta.1.
The jump from v2.076.1 to v2.098.0 covers nearly 4 years worth of
development on the D programming language and run-time libraries.
ChangeLog:
* Makefile.def: Add bootstrap to libbacktrace, libphobos, zlib, and
libatomic.
* Makefile.in: Regenerate.
* Makefile.tpl (POSTSTAGE1_HOST_EXPORTS): Fix command for GDC.
(STAGE1_CONFIGURE_FLAGS): Add --with-libphobos-druntime-only if
target-libphobos-bootstrap.
(STAGE2_CONFIGURE_FLAGS): Likewise.
* configure: Regenerate.
* configure.ac: Add support for bootstrapping D front-end.
config/ChangeLog:
* acx.m4 (ACX_PROG_GDC): New m4 function.
gcc/ChangeLog:
* Makefile.in (GDC): New variable.
(GDCFLAGS): New variable.
* configure: Regenerate.
* configure.ac: Add call to ACX_PROG_GDC. Substitute GDCFLAGS.
gcc/d/ChangeLog:
* dmd/MERGE: Merge upstream dmd b8384668f.
* Make-lang.in (d-warn): Use strict warnings.
(DMD_WARN_CXXFLAGS): Remove.
(DMD_COMPILE): Remove.
(CHECKING_DFLAGS): Define.
(WARN_DFLAGS): Define.
(ALL_DFLAGS): Define.
(DCOMPILE.base): Define.
(DCOMPILE): Define.
(DPOSTCOMPILE): Define.
(DLINKER): Define.
(DLLINKER): Define.
(D_FRONTEND_OBJS): Add new dmd front-end objects.
(D_GENERATED_SRCS): Remove.
(D_GENERATED_OBJS): Remove.
(D_ALL_OBJS): Remove D_GENERATED_OBJS.
(d21$(exeext)): Build using DLLINKER and -static-libphobos.
(d.tags): Remove dmd/*.c and dmd/root/*.c.
(d.mostlyclean): Remove D_GENERATED_SRCS, d/idgen$(build_exeext),
d/impcnvgen$(build_exeext).
(D_INCLUDES): Include $(srcdir)/d/dmd/res.
(CFLAGS-d/id.o): Remove.
(CFLAGS-d/impcnvtab.o): Remove.
(d/%.o): Build using DCOMPILE and DPOSTCOMPILE. Update dependencies
from d/dmd/%.c to d/dmd/%.d.
(d/idgen$(build_exeext)): Remove.
(d/impcnvgen$(build_exeext)): Remove.
(d/id.c): Remove.
(d/id.h): Remove.
(d/impcnvtab.c): Remove.
(d/%.dmdgen.o): Remove.
(D_SYSTEM_H): Remove.
(d/idgen.dmdgen.o): Remove.
(d/impcnvgen.dmdgen.o): Remove.
* config-lang.in (boot_language): New variable.
* d-attribs.cc: Include dmd/expression.h.
* d-builtins.cc: Include d-frontend.h.
(build_frontend_type): Update for new front-end interface.
(d_eval_constant_expression): Likewise.
(d_build_builtins_module): Likewise.
(maybe_set_builtin_1): Likewise.
(d_build_d_type_nodes): Likewise.
* d-codegen.cc (d_decl_context): Likewise.
(declaration_reference_p): Likewise.
(declaration_type): Likewise.
(parameter_reference_p): Likewise.
(parameter_type): Likewise.
(get_array_length): Likewise.
(build_delegate_cst): Likewise.
(build_typeof_null_value): Likewise.
(identity_compare_p): Likewise.
(lower_struct_comparison): Likewise.
(build_filename_from_loc): Likewise.
(build_assert_call): Remove LIBCALL_SWITCH_ERROR.
(build_bounds_index_condition): Call LIBCALL_ARRAYBOUNDS_INDEXP on
bounds error.
(build_bounds_slice_condition): Call LIBCALL_ARRAYBOUNDS_SLICEP on
bounds error.
(array_bounds_check): Update for new front-end interface.
(checkaction_trap_p): Handle CHECKACTION_context.
(get_function_type): Update for new front-end interface.
(d_build_call): Likewise.
* d-compiler.cc: Remove include of dmd/scope.h.
(Compiler::genCmain): Remove.
(Compiler::paintAsType): Update for new front-end interface.
(Compiler::onParseModule): Likewise.
* d-convert.cc (convert_expr): Remove call to LIBCALL_ARRAYCAST.
(convert_for_rvalue): Update for new front-end interface.
(convert_for_assignment): Likewise.
(convert_for_condition): Likewise.
(d_array_convert): Likewise.
* d-diagnostic.cc (error): Remove.
(errorSupplemental): Remove.
(warning): Remove.
(warningSupplemental): Remove.
(deprecation): Remove.
(deprecationSupplemental): Remove.
(message): Remove.
(vtip): New.
* d-frontend.cc (global): Remove.
(Global::_init): Remove.
(Global::startGagging): Remove.
(Global::endGagging): Remove.
(Global::increaseErrorCount): Remove.
(Loc::Loc): Remove.
(Loc::toChars): Remove.
(Loc::equals): Remove.
(isBuiltin): Update for new front-end interface.
(eval_builtin): Likewise.
(getTypeInfoType): Likewise.
(inlineCopy): Remove.
* d-incpath.cc: Include d-frontend.h.
(add_globalpaths): Call d_gc_malloc to allocate Strings.
(add_filepaths): Likewise.
* d-lang.cc: Include dmd/id.h, dmd/root/file.h, d-frontend.h. Remove
include of dmd/mars.h, id.h.
(entrypoint_module): Remove.
(entrypoint_root_module): Remove.
(deps_write_string): Update for new front-end interface.
(deps_write): Likewise.
(d_init_options): Call rt_init. Remove setting global params that are
default initialized by the front-end.
(d_handle_option): Handle OPT_fcheckaction_, OPT_fdump_c___spec_,
OPT_fdump_c___spec_verbose, OPT_fextern_std_, OPT_fpreview,
OPT_revert, OPT_fsave_mixins_, and OPT_ftransition.
(d_post_options): Propagate dip1021 and dip1000 preview flags to
dip25, and flag_diagnostics_show_caret to printErrorContext.
(d_add_entrypoint_module): Remove.
(d_parse_file): Update for new front-end interface.
(d_type_promotes_to): Likewise.
(d_types_compatible_p): Likewise.
* d-longdouble.cc (CTFloat::zero): Remove.
(CTFloat::one): Remove.
(CTFloat::minusone): Remove.
(CTFloat::half): Remove.
* d-system.h (POSIX): Remove.
(realpath): Remove.
(isalpha): Remove.
(isalnum): Remove.
(isdigit): Remove.
(islower): Remove.
(isprint): Remove.
(isspace): Remove.
(isupper): Remove.
(isxdigit): Remove.
(tolower): Remove.
(_mkdir): Remove.
(INT32_MAX): Remove.
(INT32_MIN): Remove.
(INT64_MIN): Remove.
(UINT32_MAX): Remove.
(UINT64_MAX): Remove.
* d-target.cc: Include calls.h.
(target): Remove.
(define_float_constants): Remove initialization of snan.
(Target::_init): Update for new front-end interface.
(Target::isVectorTypeSupported): Likewise.
(Target::isVectorOpSupported): Remove cases for unordered operators.
(TargetCPP::typeMangle): Update for new front-end interface.
(TargetCPP::parameterType): Likewise.
(Target::systemLinkage): Likewise.
(Target::isReturnOnStack): Likewise.
(Target::isCalleeDestroyingArgs): Define.
(Target::preferPassByRef): Define.
* d-tree.h (d_add_entrypoint_module): Remove.
* decl.cc (gcc_attribute_p): Update for new front-end interface.
(apply_pragma_crt): Define.
(DeclVisitor::visit(PragmaDeclaration *)): Handle pragmas
crt_constructor and crt_destructor.
(DeclVisitor::visit(TemplateDeclaration *)): Update for new front-end
interface.
(DeclVisitor::visit): Likewise.
(DeclVisitor::finish_vtable): Likewise.
(get_symbol_decl): Error if template has more than one nesting
context. Update for new front-end interface.
(make_thunk): Update for new front-end interface.
(get_vtable_decl): Likewise.
* expr.cc (ExprVisitor::visit): Likewise.
(build_return_dtor): Likewise.
* imports.cc (ImportVisitor::visit): Likewise.
* intrinsics.cc: Include dmd/expression.h. Remove include of
dmd/mangle.h.
(maybe_set_intrinsic): Update for new front-end interface.
* intrinsics.def (INTRINSIC_ROL): Update intrinsic signature.
(INTRINSIC_ROR): Likewise.
(INTRINSIC_ROR_TIARG): Likewise.
(INTRINSIC_TOPREC): Likewise.
(INTRINSIC_TOPRECL): Likewise.
(INTRINSIC_TAN): Update intrinsic module and signature.
(INTRINSIC_ISNAN): Likewise.
(INTRINSIC_ISFINITE): Likewise.
(INTRINSIC_COPYSIGN): Define intrinsic.
(INTRINSIC_COPYSIGNI): Define intrinsic.
(INTRINSIC_EXP): Update intrinsic module.
(INTRINSIC_EXPM1): Likewise.
(INTRINSIC_EXP2): Likewise.
(INTRINSIC_LOG): Likewise.
(INTRINSIC_LOG2): Likewise.
(INTRINSIC_LOG10): Likewise.
(INTRINSIC_POW): Likewise.
(INTRINSIC_ROUND): Likewise.
(INTRINSIC_FLOORF): Likewise.
(INTRINSIC_FLOOR): Likewise.
(INTRINSIC_FLOORL): Likewise.
(INTRINSIC_CEILF): Likewise.
(INTRINSIC_CEIL): Likewise.
(INTRINSIC_CEILL): Likewise.
(INTRINSIC_TRUNC): Likewise.
(INTRINSIC_FMIN): Likewise.
(INTRINSIC_FMAX): Likewise.
(INTRINSIC_FMA): Likewise.
(INTRINSIC_VA_ARG): Update intrinsic signature.
(INTRINSIC_VASTART): Likewise.
* lang.opt (fcheck=): Add alternate aliases for contract switches.
(fcheckaction=): New option.
(check_action): New Enum and EnumValue entries.
(fdump-c++-spec-verbose): New option.
(fdump-c++-spec=): New option.
(fextern-std=): New option.
(extern_stdcpp): New Enum and EnumValue entries
(fpreview=): New options.
(frevert=): New options.
(fsave-mixins): New option.
(ftransition=): Update options.
* modules.cc (get_internal_fn): Replace Prot with Visibility.
(build_internal_fn): Likewise.
(build_dso_cdtor_fn): Likewise.
(build_module_tree): Remove check for __entrypoint module.
* runtime.def (P5): Define.
(ARRAYBOUNDS_SLICEP): Define.
(ARRAYBOUNDS_INDEXP): Define.
(NEWTHROW): Define.
(ADCMP2): Remove.
(ARRAYCAST): Remove.
(SWITCH_STRING): Remove.
(SWITCH_USTRING): Remove.
(SWITCH_DSTRING): Remove.
(SWITCH_ERROR): Remove.
* toir.cc (IRVisitor::visit): Update for new front-end interface.
(IRVisitor::check_previous_goto): Remove checks for case and default
statements.
(IRVisitor::visit(SwitchStatement *)): Remove handling of string
switch conditions.
* typeinfo.cc: Include d-frontend.h.
(get_typeinfo_kind): Update for new front-end interface.
(make_frontend_typeinfo): Likewise.
(TypeInfoVisitor::visit): Likewise.
(builtin_typeinfo_p): Likewise.
(get_typeinfo_decl): Likewise.
(build_typeinfo): Likewise.
* types.cc (valist_array_p): Likewise.
(make_array_type): Likewise.
(merge_aggregate_types): Likewise.
(TypeVisitor::visit(TypeBasic *)): Likewise.
(TypeVisitor::visit(TypeFunction *)): Likewise.
(TypeVisitor::visit(TypeStruct *)): Update comment.
* verstr.h: Removed.
* d-frontend.h: New file.
gcc/po/ChangeLog:
* EXCLUDES: Remove d/dmd sources from list.
gcc/testsuite/ChangeLog:
* gdc.dg/Wcastresult2.d: Update test.
* gdc.dg/asm1.d: Likewise.
* gdc.dg/asm2.d: Likewise.
* gdc.dg/asm3.d: Likewise.
* gdc.dg/gdc282.d: Likewise.
* gdc.dg/imports/gdc170.d: Likewise.
* gdc.dg/intrinsics.d: Likewise.
* gdc.dg/pr101672.d: Likewise.
* gdc.dg/pr90650a.d: Likewise.
* gdc.dg/pr90650b.d: Likewise.
* gdc.dg/pr94777a.d: Likewise.
* gdc.dg/pr95250.d: Likewise.
* gdc.dg/pr96869.d: Likewise.
* gdc.dg/pr98277.d: Likewise.
* gdc.dg/pr98457.d: Likewise.
* gdc.dg/simd1.d: Likewise.
* gdc.dg/simd2a.d: Likewise.
* gdc.dg/simd2b.d: Likewise.
* gdc.dg/simd2c.d: Likewise.
* gdc.dg/simd2d.d: Likewise.
* gdc.dg/simd2e.d: Likewise.
* gdc.dg/simd2f.d: Likewise.
* gdc.dg/simd2g.d: Likewise.
* gdc.dg/simd2h.d: Likewise.
* gdc.dg/simd2i.d: Likewise.
* gdc.dg/simd2j.d: Likewise.
* gdc.dg/simd7951.d: Likewise.
* gdc.dg/torture/gdc309.d: Likewise.
* gdc.dg/torture/pr94424.d: Likewise.
* gdc.dg/torture/pr94777b.d: Likewise.
* lib/gdc-utils.exp (gdc-convert-args): Handle new compiler options.
(gdc-convert-test): Handle CXXFLAGS, EXTRA_OBJC_SOURCES, and ARG_SETS
test directives.
(gdc-do-test): Only import modules in the test run directory.
* gdc.dg/pr94777c.d: New test.
* gdc.dg/pr96156b.d: New test.
* gdc.dg/pr96157c.d: New test.
* gdc.dg/simd_ctfe.d: New test.
* gdc.dg/torture/simd17344.d: New test.
* gdc.dg/torture/simd20052.d: New test.
* gdc.dg/torture/simd6.d: New test.
* gdc.dg/torture/simd7.d: New test.
libphobos/ChangeLog:
* libdruntime/MERGE: Merge upstream druntime e6caaab9.
* libdruntime/Makefile.am (D_EXTRA_FLAGS): Build libdruntime with
-fpreview=dip1000, -fpreview=fieldwise, and -fpreview=dtorfields.
(ALL_DRUNTIME_SOURCES): Add DRUNTIME_DSOURCES_STDCXX.
(DRUNTIME_DSOURCES): Update list of C binding modules.
(DRUNTIME_DSOURCES_STDCXX): Likewise.
(DRUNTIME_DSOURCES_LINUX): Likewise.
(DRUNTIME_DSOURCES_OPENBSD): Likewise.
(DRUNTIME_DISOURCES): Remove __entrypoint.di.
* libdruntime/Makefile.in: Regenerated.
* libdruntime/__entrypoint.di: Removed.
* libdruntime/gcc/deh.d (_d_isbaseof): Update signature.
(_d_createTrace): Likewise.
(__gdc_begin_catch): Remove reference to the exception.
(_d_throw): Increment reference count of thrown object before unwind.
(__gdc_personality): Chain exceptions with Throwable.chainTogether.
* libdruntime/gcc/emutls.d: Update imports.
* libdruntime/gcc/sections/elf.d: Update imports.
(DSO.moduleGroup): Update signature.
* libdruntime/gcc/sections/macho.d: Update imports.
(DSO.moduleGroup): Update signature.
* libdruntime/gcc/sections/pecoff.d: Update imports.
(DSO.moduleGroup): Update signature.
* src/MERGE: Merge upstream phobos 5ab9ad256.
* src/Makefile.am (D_EXTRA_DFLAGS): Add -fpreview=dip1000 and
-fpreview=dtorfields flags.
(PHOBOS_DSOURCES): Update list of std modules.
* src/Makefile.in: Regenerate.
* testsuite/lib/libphobos.exp (libphobos-dg-test): Handle assembly
compile types.
(dg-test): Override.
(additional_prunes): Define.
(libphobos-dg-prune): Filter any additional_prunes set by tests.
* testsuite/libphobos.aa/test_aa.d: Update test.
* testsuite/libphobos.druntime/druntime.exp (version_flags): Add
-fversion=CoreUnittest.
* testsuite/libphobos.druntime_shared/druntime_shared.exp
(version_flags): Add -fversion=CoreUnittest -fversion=Shared.
* testsuite/libphobos.exceptions/unknown_gc.d: Update test.
* testsuite/libphobos.hash/test_hash.d: Update test.
* testsuite/libphobos.phobos/phobos.exp (version_flags): Add
-fversion=StdUnittest
* testsuite/libphobos.phobos_shared/phobos_shared.exp (version_flags):
Likewise.
* testsuite/libphobos.shared/host.c: Update test.
* testsuite/libphobos.shared/load.d: Update test.
* testsuite/libphobos.shared/load_13414.d: Update test.
* testsuite/libphobos.thread/fiber_guard_page.d: Update test.
* testsuite/libphobos.thread/tlsgc_sections.d: Update test.
* testsuite/testsuite_flags.in: Add -fpreview=dip1000 to --gdcflags.
* testsuite/libphobos.shared/link_mod_collision.d: Removed.
* testsuite/libphobos.shared/load_mod_collision.d: Removed.
* testsuite/libphobos.betterc/betterc.exp: New test.
* testsuite/libphobos.config/config.exp: New test.
* testsuite/libphobos.gc/gc.exp: New test.
* testsuite/libphobos.imports/imports.exp: New test.
* testsuite/libphobos.lifetime/lifetime.exp: New test.
* testsuite/libphobos.unittest/unittest.exp: New test.
Diffstat (limited to 'libphobos/src/std/algorithm')
-rw-r--r-- | libphobos/src/std/algorithm/comparison.d | 950 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/internal.d | 22 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/iteration.d | 4235 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/mutation.d | 1408 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/package.d | 13 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/searching.d | 1884 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/setops.d | 198 | ||||
-rw-r--r-- | libphobos/src/std/algorithm/sorting.d | 1273 |
8 files changed, 7203 insertions, 2780 deletions
diff --git a/libphobos/src/std/algorithm/comparison.d b/libphobos/src/std/algorithm/comparison.d index faa4d44..c952544 100644 --- a/libphobos/src/std/algorithm/comparison.d +++ b/libphobos/src/std/algorithm/comparison.d @@ -1,48 +1,48 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _comparison algorithms. +It contains generic comparison algorithms. $(SCRIPT inhibitQuickIndex = 1;) $(BOOKTABLE Cheat Sheet, $(TR $(TH Function Name) $(TH Description)) $(T2 among, Checks if a value is among a set of values, e.g. - $(D if (v.among(1, 2, 3)) // `v` is 1, 2 or 3)) + `if (v.among(1, 2, 3)) // `v` is 1, 2 or 3`) $(T2 castSwitch, - $(D (new A()).castSwitch((A a)=>1,(B b)=>2)) returns $(D 1).) + `(new A()).castSwitch((A a)=>1,(B b)=>2)` returns `1`.) $(T2 clamp, - $(D clamp(1, 3, 6)) returns $(D 3). $(D clamp(4, 3, 6)) returns $(D 4).) + `clamp(1, 3, 6)` returns `3`. `clamp(4, 3, 6)` returns `4`.) $(T2 cmp, - $(D cmp("abc", "abcd")) is $(D -1), $(D cmp("abc", "aba")) is $(D 1), - and $(D cmp("abc", "abc")) is $(D 0).) + `cmp("abc", "abcd")` is `-1`, `cmp("abc", "aba")` is `1`, + and `cmp("abc", "abc")` is `0`.) $(T2 either, - Return first parameter $(D p) that passes an $(D if (p)) test, e.g. - $(D either(0, 42, 43)) returns $(D 42).) + Return first parameter `p` that passes an `if (p)` test, e.g. + `either(0, 42, 43)` returns `42`.) $(T2 equal, Compares ranges for element-by-element equality, e.g. - $(D equal([1, 2, 3], [1.0, 2.0, 3.0])) returns $(D true).) + `equal([1, 2, 3], [1.0, 2.0, 3.0])` returns `true`.) $(T2 isPermutation, - $(D isPermutation([1, 2], [2, 1])) returns $(D true).) + `isPermutation([1, 2], [2, 1])` returns `true`.) $(T2 isSameLength, - $(D isSameLength([1, 2, 3], [4, 5, 6])) returns $(D true).) + `isSameLength([1, 2, 3], [4, 5, 6])` returns `true`.) $(T2 levenshteinDistance, - $(D levenshteinDistance("kitten", "sitting")) returns $(D 3) by using + `levenshteinDistance("kitten", "sitting")` returns `3` by using the $(LINK2 https://en.wikipedia.org/wiki/Levenshtein_distance, - Levenshtein distance _algorithm).) + Levenshtein distance algorithm).) $(T2 levenshteinDistanceAndPath, - $(D levenshteinDistanceAndPath("kitten", "sitting")) returns - $(D tuple(3, "snnnsni")) by using the + `levenshteinDistanceAndPath("kitten", "sitting")` returns + `tuple(3, "snnnsni")` by using the $(LINK2 https://en.wikipedia.org/wiki/Levenshtein_distance, - Levenshtein distance _algorithm).) + Levenshtein distance algorithm).) $(T2 max, - $(D max(3, 4, 2)) returns $(D 4).) + `max(3, 4, 2)` returns `4`.) $(T2 min, - $(D min(3, 4, 2)) returns $(D 2).) + `min(3, 4, 2)` returns `2`.) $(T2 mismatch, - $(D mismatch("oh hi", "ohayo")) returns $(D tuple(" hi", "ayo")).) + `mismatch("oh hi", "ohayo")` returns `tuple(" hi", "ayo")`.) $(T2 predSwitch, - $(D 2.predSwitch(1, "one", 2, "two", 3, "three")) returns $(D "two").) + `2.predSwitch(1, "one", 2, "two", 3, "three")` returns `"two"`.) ) Copyright: Andrei Alexandrescu 2008-. @@ -51,25 +51,25 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_comparison.d) +Source: $(PHOBOSSRC std/algorithm/comparison.d) Macros: T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) */ module std.algorithm.comparison; -// FIXME -import std.functional; // : unaryFun, binaryFun; +import std.functional : unaryFun, binaryFun; import std.range.primitives; import std.traits; -// FIXME import std.meta : allSatisfy; -import std.typecons; // : tuple, Tuple, Flag, Yes; +import std.typecons : tuple, Tuple, Flag, Yes; + +import std.internal.attributes : betterC; /** -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 +Find `value` _among `values`, returning the 1-based index +of the first matching value in `values`, or `0` if `value` +is not _among `values`. The predicate `pred` is used to compare values, and uses equality by default. Params: @@ -116,7 +116,7 @@ if (isExpressionTuple!values) } /// -@safe unittest +@safe @nogc @betterC unittest { assert(3.among(1, 42, 24, 3, 2)); @@ -130,10 +130,10 @@ if (isExpressionTuple!values) } /** -Alternatively, $(D values) can be passed at compile-time, allowing for a more +Alternatively, `values` can be passed at compile-time, allowing for a more efficient search, but one that only supports matching on equality: */ -@safe unittest +@safe @nogc @betterC unittest { assert(3.among!(2, 3, 4)); assert("bar".among!("foo", "bar", "baz") == 2); @@ -214,19 +214,19 @@ private template indexOfFirstOvershadowingChoiceOnLast(choices...) Executes and returns one of a collection of handlers based on the type of the switch object. -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). +The first choice that `switchObject` can be casted to the type +of argument it accepts will be called with `switchObject` casted to that +type, and the value it'll return will be returned by `castSwitch`. 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 +Throws: If none of the choice matches, a `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. Params: - choices = The $(D choices) needs to be composed of function or delegate + choices = The `choices` needs to be composed of function or delegate handlers that accept one argument. There can also be a choice that accepts zero arguments. That choice will be invoked if the $(D switchObject) is null. @@ -235,7 +235,7 @@ Params: Returns: The value of the selected choice. -Note: $(D castSwitch) can only be used with object types. +Note: `castSwitch` can only be used with object types. */ auto castSwitch(choices...)(Object switchObject) { @@ -254,7 +254,6 @@ auto castSwitch(choices...)(Object switchObject) if (switchObject !is null) { - // Checking for exact matches: const classInfo = typeid(switchObject); foreach (index, choice; choices) @@ -517,7 +516,7 @@ auto castSwitch(choices...)(Object switchObject) /** Clamps a value into the given bounds. -This functions is equivalent to $(D max(lower, min(upper,val))). +This function is equivalent to `max(lower, min(upper, val))`. Params: val = The value to _clamp. @@ -525,23 +524,24 @@ Params: upper = The _upper bound of the _clamp. Returns: - Returns $(D val), if it is between $(D lower) and $(D upper). + Returns `val`, if it is between `lower` and `upper`. Otherwise returns the nearest of the two. */ auto clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper) +if (is(typeof(max(min(val, upper), lower)))) in { import std.functional : greaterThan; - assert(!lower.greaterThan(upper)); + assert(!lower.greaterThan(upper), "Lower can't be greater than upper."); } -body +do { - return max(lower, min(upper, val)); + return max(min(val, upper), lower); } /// -@safe unittest +@safe @nogc @betterC unittest { assert(clamp(2, 1, 3) == 2); assert(clamp(0, 1, 3) == 1); @@ -576,121 +576,160 @@ body // UFCS style assert(Date(1982, 1, 4).clamp(Date.min, Date.max) == Date(1982, 1, 4)); + // Stability + struct A { + int x, y; + int opCmp(ref const A rhs) const { return (x > rhs.x) - (x < rhs.x); } + } + A x, lo, hi; + x.y = 42; + assert(x.clamp(lo, hi).y == 42); } // cmp /********************************** -Performs three-way lexicographical comparison on two -$(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) -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 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 +Performs a lexicographical comparison on two +$(REF_ALTTEXT input ranges, isInputRange, std,range,primitives). +Iterating `r1` and `r2` in lockstep, `cmp` compares each element +`e1` of `r1` with the corresponding element `e2` in `r2`. If one +of the ranges has been finished, `cmp` returns a negative value +if `r1` has fewer elements than `r2`, a positive value if `r1` +has more elements than `r2`, and `0` if the ranges have the same +number of elements. + +If the ranges are strings, `cmp` performs UTF decoding appropriately and compares the ranges one code point at a time. +A custom predicate may be specified, in which case `cmp` performs +a three-way lexicographical comparison using `pred`. Otherwise +the elements are compared using `opCmp`. + Params: - pred = The predicate used for comparison. + pred = Predicate used for comparison. Without a predicate + specified the ordering implied by `opCmp` is used. r1 = The first range. r2 = The second range. Returns: - 0 if both ranges compare equal. -1 if the first differing element of $(D - r1) is less than the corresponding element of $(D r2) according to $(D - pred). 1 if the first differing element of $(D r2) is less than the - corresponding element of $(D r1) according to $(D pred). - + `0` if the ranges compare equal. A negative value if `r1` is a prefix of `r2` or + the first differing element of `r1` is less than the corresponding element of `r2` + according to `pred`. A positive value if `r2` is a prefix of `r1` or the first + differing element of `r2` is less than the corresponding element of `r1` + according to `pred`. + +Note: + An earlier version of the documentation incorrectly stated that `-1` is the + only negative value returned and `1` is the only positive value returned. + Whether that is true depends on the types being compared. */ -int cmp(alias pred = "a < b", R1, R2)(R1 r1, R2 r2) -if (isInputRange!R1 && isInputRange!R2 && !(isSomeString!R1 && isSomeString!R2)) +auto cmp(R1, R2)(R1 r1, R2 r2) +if (isInputRange!R1 && isInputRange!R2) { - for (;; r1.popFront(), r2.popFront()) + alias E1 = ElementEncodingType!R1; + alias E2 = ElementEncodingType!R2; + + static if (isDynamicArray!R1 && isDynamicArray!R2 + && __traits(isUnsigned, E1) && __traits(isUnsigned, E2) + && E1.sizeof == 1 && E2.sizeof == 1 + // Both or neither must auto-decode. + && (is(immutable E1 == immutable char) == is(immutable E2 == immutable char))) { - 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; + // dstrcmp algorithm is correct for both ubyte[] and for char[]. + import core.internal.string : dstrcmp; + return dstrcmp(cast(const char[]) r1, cast(const char[]) r2); } -} - -/// ditto -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) + else static if (!(isSomeString!R1 && isSomeString!R2)) { - 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; + for (;; r1.popFront(), r2.popFront()) + { + static if (is(typeof(r1.front.opCmp(r2.front)) R)) + alias Result = R; + else + alias Result = int; + if (r2.empty) return Result(!r1.empty); + if (r1.empty) return Result(-1); + static if (is(typeof(r1.front.opCmp(r2.front)))) + { + auto c = r1.front.opCmp(r2.front); + if (c != 0) return c; + } + else + { + auto a = r1.front, b = r2.front; + if (auto result = (b < a) - (a < b)) return result; + } + } } - - static if (typeof(r1[0]).sizeof == typeof(r2[0]).sizeof && isLessThan) + else { - static if (typeof(r1[0]).sizeof == 1) + static if (typeof(r1[0]).sizeof == typeof(r2[0]).sizeof) { - immutable len = min(r1.length, r2.length); - immutable result = __ctfe ? + return () @trusted + { + auto p1 = r1.ptr, p2 = r2.ptr, + pEnd = p1 + min(r1.length, r2.length); + for (; p1 != pEnd; ++p1, ++p2) { - foreach (i; 0 .. len) - { - if (r1[i] != r2[i]) - return threeWayInt(r1[i], r2[i]); - } - return 0; - }() - : () @trusted { return memcmp(r1.ptr, r2.ptr, len); }(); - if (result) return result; + if (*p1 != *p2) return cast(int) *p1 - cast(int) *p2; + } + static if (typeof(r1[0]).sizeof >= 2 && size_t.sizeof <= uint.sizeof) + return cast(int) r1.length - cast(int) r2.length; + else + return int(r1.length > r2.length) - int(r1.length < r2.length); + }(); } else { - auto p1 = r1.ptr, p2 = r2.ptr, - pEnd = p1 + min(r1.length, r2.length); - for (; p1 != pEnd; ++p1, ++p2) + import std.utf : decode; + + for (size_t i1, i2;;) { - if (*p1 != *p2) return threeWayInt(cast(int) *p1, cast(int) *p2); + if (i1 == r1.length) return -int(i2 < r2.length); + if (i2 == r2.length) return int(1); + immutable c1 = decode(r1, i1), + c2 = decode(r2, i2); + if (c1 != c2) return cast(int) c1 - cast(int) c2; } } - return threeWay(r1.length, r2.length); + } +} + +/// ditto +int cmp(alias pred, R1, R2)(R1 r1, R2 r2) +if (isInputRange!R1 && isInputRange!R2) +{ + static if (!(isSomeString!R1 && isSomeString!R2)) + { + for (;; r1.popFront(), r2.popFront()) + { + if (r2.empty) return !r1.empty; + if (r1.empty) return -1; + auto a = r1.front, b = r2.front; + if (binaryFun!pred(a, b)) return -1; + if (binaryFun!pred(b, a)) return 1; + } } else { + import std.utf : decode; + for (size_t i1, i2;;) { - if (i1 == r1.length) return threeWay(i2, r2.length); - if (i2 == r2.length) return threeWay(r1.length, i1); + if (i1 == r1.length) return -int(i2 < r2.length); + if (i2 == r2.length) return 1; immutable c1 = decode(r1, i1), c2 = decode(r2, i2); - if (c1 != c2) return threeWayInt(cast(int) c1, cast(int) c2); + if (c1 != c2) + { + if (binaryFun!pred(c2, c1)) return 1; + if (binaryFun!pred(c1, c2)) return -1; + } } } } /// -@safe unittest +pure @safe unittest { int result; @@ -712,6 +751,8 @@ if (isSomeString!R1 && isSomeString!R2) assert(result > 0); result = cmp("aaa", "aaa"d); assert(result == 0); + result = cmp("aaa"d, "aaa"d); + assert(result == 0); result = cmp(cast(int[])[], cast(int[])[]); assert(result == 0); result = cmp([1, 2, 3], [1, 2, 3]); @@ -724,129 +765,237 @@ if (isSomeString!R1 && isSomeString!R2) assert(result > 0); } +/// Example predicate that compares individual elements in reverse lexical order +pure @safe unittest +{ + int result; + + result = cmp!"a > b"("abc", "abc"); + assert(result == 0); + result = cmp!"a > b"("", ""); + assert(result == 0); + result = cmp!"a > b"("abc", "abcd"); + assert(result < 0); + result = cmp!"a > b"("abcd", "abc"); + assert(result > 0); + result = cmp!"a > b"("abc"d, "abd"); + assert(result > 0); + result = cmp!"a > b"("bbc", "abc"w); + assert(result < 0); + result = cmp!"a > b"("aaa", "aaaa"d); + assert(result < 0); + result = cmp!"a > b"("aaaa", "aaa"d); + assert(result > 0); + result = cmp!"a > b"("aaa", "aaa"d); + assert(result == 0); + result = cmp("aaa"d, "aaa"d); + assert(result == 0); + result = cmp!"a > b"(cast(int[])[], cast(int[])[]); + assert(result == 0); + result = cmp!"a > b"([1, 2, 3], [1, 2, 3]); + assert(result == 0); + result = cmp!"a > b"([1, 3, 2], [1, 2, 3]); + assert(result < 0); + result = cmp!"a > b"([1, 2, 3], [1L, 2, 3, 4]); + assert(result < 0); + result = cmp!"a > b"([1L, 2, 3], [1, 2]); + assert(result > 0); +} + +// cmp for string with custom predicate fails if distinct chars can compare equal +// https://issues.dlang.org/show_bug.cgi?id=18286 +@nogc nothrow pure @safe unittest +{ + static bool ltCi(dchar a, dchar b)// less than, case insensitive + { + import std.ascii : toUpper; + return toUpper(a) < toUpper(b); + } + static assert(cmp!ltCi("apple2", "APPLE1") > 0); + static assert(cmp!ltCi("apple1", "APPLE2") < 0); + static assert(cmp!ltCi("apple", "APPLE1") < 0); + static assert(cmp!ltCi("APPLE", "apple1") < 0); + static assert(cmp!ltCi("apple", "APPLE") == 0); +} + +// for non-string ranges check that opCmp is evaluated only once per pair. +// https://issues.dlang.org/show_bug.cgi?id=18280 +@nogc nothrow @safe unittest +{ + static int ctr = 0; + struct S + { + int opCmp(ref const S rhs) const + { + ++ctr; + return 0; + } + bool opEquals(T)(T o) const { return false; } + size_t toHash() const { return 0; } + } + immutable S[4] a; + immutable S[4] b; + immutable result = cmp(a[], b[]); + assert(result == 0, "neither should compare greater than the other!"); + assert(ctr == a.length, "opCmp should be called exactly once per pair of items!"); +} + +nothrow pure @safe @nogc unittest +{ + import std.array : staticArray; + // Test cmp when opCmp returns float. + struct F + { + float value; + float opCmp(const ref F rhs) const + { + return value - rhs.value; + } + bool opEquals(T)(T o) const { return false; } + size_t toHash() const { return 0; } + } + auto result = cmp([F(1), F(2), F(3)].staticArray[], [F(1), F(2), F(3)].staticArray[]); + assert(result == 0); + assert(is(typeof(result) == float)); + result = cmp([F(1), F(3), F(2)].staticArray[], [F(1), F(2), F(3)].staticArray[]); + assert(result > 0); + result = cmp([F(1), F(2), F(3)].staticArray[], [F(1), F(2), F(3), F(4)].staticArray[]); + assert(result < 0); + result = cmp([F(1), F(2), F(3)].staticArray[], [F(1), F(2)].staticArray[]); + assert(result > 0); +} + +nothrow pure @safe unittest +{ + // Parallelism (was broken by inferred return type "immutable int") + import std.parallelism : task; + auto t = task!cmp("foo", "bar"); +} + // equal /** -Compares two ranges for equality, as defined by predicate $(D pred) -(which is $(D ==) by default). +Compares two ranges for equality, as defined by predicate `pred` +(which is `==` by default). */ template equal(alias pred = "a == b") { - enum isEmptyRange(R) = - isInputRange!R && __traits(compiles, {static assert(R.empty);}); - - enum hasFixedLength(T) = hasLength!T || isNarrowString!T; - /++ Compares two ranges for equality. The ranges may have - different element types, as long as $(D pred(r1.front, r2.front)) - evaluates to $(D bool). - Performs $(BIGOH min(r1.length, r2.length)) evaluations of $(D pred). + different element types, as long as `pred(r1.front, r2.front)` + evaluates to `bool`. + Performs $(BIGOH min(r1.length, r2.length)) evaluations of `pred`. + + At least one of the ranges must be finite. If one range involved is infinite, the result is + (statically known to be) `false`. + + If the two ranges are different kinds of UTF code unit (`char`, `wchar`, or + `dchar`), then the arrays are compared using UTF decoding to avoid + accidentally integer-promoting units. Params: r1 = The first range to be compared. r2 = The second range to be compared. Returns: - $(D true) if and only if the two ranges compare _equal element - for element, according to binary predicate $(D pred). - - See_Also: - $(HTTP sgi.com/tech/stl/_equal.html, STL's _equal) + `true` if and only if the two ranges compare _equal element + for element, according to binary predicate `pred`. +/ bool equal(Range1, Range2)(Range1 r1, Range2 r2) if (isInputRange!Range1 && isInputRange!Range2 && + !(isInfinite!Range1 && isInfinite!Range2) && is(typeof(binaryFun!pred(r1.front, r2.front)))) { - static assert(!(isInfinite!Range1 && isInfinite!Range2), - "Both ranges are known to be infinite"); - - //No pred calls necessary - static if (isEmptyRange!Range1 || isEmptyRange!Range2) + // Use code points when comparing two ranges of UTF code units that aren't + // the same type. This is for backwards compatibility with autodecode + // strings. + enum useCodePoint = + isSomeChar!(ElementEncodingType!Range1) && isSomeChar!(ElementEncodingType!Range2) && + ElementEncodingType!Range1.sizeof != ElementEncodingType!Range2.sizeof; + + static if (useCodePoint) { - return r1.empty && r2.empty; + import std.utf : byDchar; + return equal(r1.byDchar, r2.byDchar); } - else static if ((isInfinite!Range1 && hasFixedLength!Range2) || - (hasFixedLength!Range1 && isInfinite!Range2)) - { - return false; - } - //Detect default pred and compatible dynamic array - else static if (is(typeof(pred) == string) && pred == "a == b" && - isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2))) - { - return r1 == r2; - } - // if one of the arguments is a string and the other isn't, then auto-decoding - // can be avoided if they have the same ElementEncodingType - else static if (is(typeof(pred) == string) && pred == "a == b" && - isAutodecodableString!Range1 != isAutodecodableString!Range2 && - is(ElementEncodingType!Range1 == ElementEncodingType!Range2)) + else { - import std.utf : byCodeUnit; - - static if (isAutodecodableString!Range1) + static if (isInfinite!Range1 || isInfinite!Range2) { - return equal(r1.byCodeUnit, r2); + // No finite range can be ever equal to an infinite range. + return false; } - else + // Detect default pred and compatible dynamic arrays. + else static if (is(typeof(pred) == string) && pred == "a == b" && + isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2))) { - return equal(r2.byCodeUnit, r1); + 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))) - { - immutable len1 = r1.length; - immutable 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 squeeze out a bit of performance by not checking if r2 is empty - for (; !r1.empty; r1.popFront(), r2.popFront()) + // If one of the arguments is a string and the other isn't, then auto-decoding + // can be avoided if they have the same ElementEncodingType. + else static if (is(typeof(pred) == string) && pred == "a == b" && + isAutodecodableString!Range1 != isAutodecodableString!Range2 && + is(immutable ElementEncodingType!Range1 == immutable ElementEncodingType!Range2)) { - if (!binaryFun!(pred)(r1.front, r2.front)) return false; + import std.utf : byCodeUnit; + + static if (isAutodecodableString!Range1) + return equal(r1.byCodeUnit, r2); + else + return equal(r2.byCodeUnit, r1); } - return true; - } - else - { - //Generic case, we have to walk both ranges making sure neither is empty - for (; !r1.empty; r1.popFront(), r2.popFront()) + // Try a fast implementation when the ranges have comparable lengths. + else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length))) { - if (r2.empty) return false; - if (!binaryFun!(pred)(r1.front, r2.front)) return false; + immutable len1 = r1.length; + immutable 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 squeeze 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; } - static if (!isInfinite!Range1) + else + { + //Generic case, we have to walk both ranges making sure neither is empty + for (; !r1.empty; r1.popFront(), r2.popFront()) + { + if (r2.empty || !binaryFun!(pred)(r1.front, r2.front)) return false; + } return r2.empty; + } } } } /// -@safe unittest +@safe @nogc unittest { import std.algorithm.comparison : equal; - import std.math : approxEqual; + import std.math.operations : isClose; - int[] a = [ 1, 2, 4, 3 ]; - assert(!equal(a, a[1..$])); - assert(equal(a, a)); - assert(equal!((a, b) => a == b)(a, a)); + int[4] a = [ 1, 2, 4, 3 ]; + assert(!equal(a[], a[1..$])); + assert(equal(a[], a[])); + assert(equal!((a, b) => a == b)(a[], a[])); // different types - double[] b = [ 1.0, 2, 4, 3]; - assert(!equal(a, b[1..$])); - assert(equal(a, b)); + double[4] 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)); + double[4] c = [ 1.0000000005, 2, 4, 3]; + assert(equal!isClose(b[], c[])); } /++ -Tip: $(D equal) can itself be used as a predicate to other functions. +Tip: `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. In particular, `equal` can be its own predicate, allowing range of range (of range...) comparisons. +/ @safe unittest @@ -864,7 +1013,7 @@ range of range (of range...) comparisons. import std.algorithm.iteration : map; import std.internal.test.dummyrange : ReferenceForwardRange, ReferenceInputRange, ReferenceInfiniteForwardRange; - import std.math : approxEqual; + import std.math.operations : isClose; // various strings assert(equal("æøå", "æøå")); //UTF8 vs UTF8 @@ -898,11 +1047,11 @@ range of range (of range...) comparisons. 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))); + double[] c = [ 1.0000000005, 2, 4, 3]; + assert(equal!isClose(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))); + assert(!equal!isClose(map!"a*3"(b), map!"a*2"(c))); //Tests with some fancy reference ranges. ReferenceInputRange!int cir = new ReferenceInputRange!int([1, 2, 4, 3]); @@ -923,19 +1072,33 @@ range of range (of range...) comparisons. assert(!equal(cir, ifr)); } -@safe pure unittest +@safe @nogc pure unittest { - import std.utf : byChar, byWchar, byDchar; + import std.utf : byChar, byDchar, byWchar; assert(equal("æøå".byChar, "æøå")); + assert(equal("æøå".byChar, "æøå"w)); + assert(equal("æøå".byChar, "æøå"d)); assert(equal("æøå", "æøå".byChar)); + assert(equal("æøå"w, "æøå".byChar)); + assert(equal("æøå"d, "æøå".byChar)); + + assert(equal("æøå".byWchar, "æøå")); assert(equal("æøå".byWchar, "æøå"w)); + assert(equal("æøå".byWchar, "æøå"d)); + assert(equal("æøå", "æøå".byWchar)); assert(equal("æøå"w, "æøå".byWchar)); + assert(equal("æøå"d, "æøå".byWchar)); + + assert(equal("æøå".byDchar, "æøå")); + assert(equal("æøå".byDchar, "æøå"w)); assert(equal("æøå".byDchar, "æøå"d)); + assert(equal("æøå", "æøå".byDchar)); + assert(equal("æøå"w, "æøå".byDchar)); assert(equal("æøå"d, "æøå".byDchar)); } -@safe pure unittest +@safe @nogc pure unittest { struct R(bool _empty) { enum empty = _empty; @@ -956,37 +1119,14 @@ range of range (of range...) comparisons. assert(!"bar".equal(E())); } -// 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 .. $])); - } -} - // levenshteinDistance /** Encodes $(HTTP 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: +another. Given sequences `s` (source) and `t` (target), a +sequence of `EditOp` encodes the steps that need to be taken to +convert `s` into `t`. For example, if `s = "cat"` and $(D +"cars"), the minimal sequence that transforms `s` into `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 @@ -1074,7 +1214,8 @@ private: import core.checkedint : mulu; bool overflow; const rc = mulu(r, c, overflow); - if (overflow) assert(0); + assert(!overflow, "Overflow during multiplication to determine number " + ~ " of matrix elements"); rows = r; cols = c; if (_matrix.length < rc) @@ -1082,7 +1223,8 @@ private: import core.exception : onOutOfMemoryError; import core.stdc.stdlib : realloc; const nbytes = mulu(rc, _matrix[0].sizeof, overflow); - if (overflow) assert(0); + assert(!overflow, "Overflow during multiplication to determine " + ~ " number of bytes of matrix"); auto m = cast(CostType *) realloc(_matrix.ptr, nbytes); if (!m) onOutOfMemoryError(); @@ -1193,10 +1335,10 @@ private: /** Returns the $(HTTP 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. +distance) between `s` and `t`. The Levenshtein distance computes +the minimal amount of edit operations necessary to transform `s` +into `t`. Performs $(BIGOH s.length * t.length) evaluations of $(D +equals) and occupies $(BIGOH min(s.length, t.length)) storage. Params: equals = The binary predicate to compare the elements of the two ranges. @@ -1244,7 +1386,7 @@ if (isForwardRange!(Range1) && isForwardRange!(Range2)) return eq(s.front, t.front) ? 0 : 1; } - if (slen > tlen) + if (slen < tlen) { Levenshtein!(Range1, eq, size_t) lev; return lev.distanceLowMem(s, t, slen, tlen); @@ -1305,8 +1447,8 @@ if (isConvertibleToString!Range1 || isConvertibleToString!Range2) } /** -Returns the Levenshtein distance and the edit path between $(D s) and -$(D t). +Returns the Levenshtein distance and the edit path between `s` and +`t`. Params: equals = The binary predicate to compare the elements of the two ranges. @@ -1367,50 +1509,73 @@ if (isConvertibleToString!Range1 || isConvertibleToString!Range2) assert(levenshteinDistanceAndPath(S("cat"), "rat")[0] == 1); } + // max /** -Iterates the passed arguments and return the maximum value. +Iterates the passed arguments and returns the maximum value. Params: args = The values to select the maximum from. At least two arguments must - be passed. + be passed, and they must be comparable with `<`. Returns: - The maximum of the passed-in args. The type of the returned value is + The maximum of the passed-in values. The type of the returned value is the type among the passed arguments that is able to store the largest value. + If at least one of the arguments is NaN, the result is an unspecified value. + See $(REF maxElement, std,algorithm,searching) for examples on how to cope + with NaNs. See_Also: $(REF maxElement, std,algorithm,searching) */ -MaxType!T max(T...)(T args) -if (T.length >= 2) +auto max(T...)(T args) +if (T.length >= 2 && !is(CommonType!T == void)) { - //Get "a" - static if (T.length <= 2) + // Get left-hand side of the comparison. + static if (T.length == 2) alias a = args[0]; else - auto a = max(args[0 .. ($+1)/2]); + auto a = max(args[0 .. ($ + 1) / 2]); alias T0 = typeof(a); - //Get "b" + // Get right-hand side. static if (T.length <= 3) - alias b = args[$-1]; + alias b = args[$ - 1]; else - auto b = max(args[($+1)/2 .. $]); + auto b = max(args[($ + 1) / 2 .. $]); alias T1 = typeof(b); - import std.algorithm.internal : algoFormat; static assert(is(typeof(a < b)), - algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof)); + "Invalid arguments: Cannot compare types " ~ T0.stringof ~ + " and " ~ T1.stringof ~ " for ordering."); + + // Compute the returned type. + static if (is(typeof(mostNegative!T0 < mostNegative!T1))) + // Both are numeric (or character or Boolean), so we choose the one with the highest maximum. + // (We use mostNegative for num/bool/char testing purposes even if it's not used otherwise.) + alias Result = Select!(T1.max > T0.max, T1, T0); + else + // At least one is non-numeric, so just go with the common type. + alias Result = CommonType!(T0, T1); - //Do the "max" proper with a and b + // Perform the computation. import std.functional : lessThan; immutable chooseB = lessThan!(T0, T1)(a, b); - return cast(typeof(return)) (chooseB ? b : a); + return cast(Result) (chooseB ? b : a); +} + +/// ditto +T max(T, U)(T a, U b) +if (is(T == U) && is(typeof(a < b))) +{ + /* Handle the common case without all the template expansions + * of the general case + */ + return a < b ? b : a; } /// -@safe unittest +@safe @betterC @nogc unittest { int a = 5; short b = 6; @@ -1423,7 +1588,7 @@ if (T.length >= 2) assert(e == 2); } -@safe unittest +@safe unittest // not @nogc due to `Date` { int a = 5; short b = 6; @@ -1452,77 +1617,76 @@ if (T.length >= 2) assert(max(Date.max, Date.min) == Date.max); } -// 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 /** Iterates the passed arguments and returns the minimum value. -Params: args = The values to select the minimum from. At least two arguments - must be passed, and they must be comparable with `<`. -Returns: The minimum of the passed-in values. +Params: + args = The values to select the minimum from. At least two arguments must + be passed, and they must be comparable with `<`. + +Returns: + The minimum of the passed-in values. The type of the returned value is + the type among the passed arguments that is able to store the smallest value. + If at least one of the arguments is NaN, the result is an unspecified value. + See $(REF minElement, std,algorithm,searching) for examples on how to cope + with NaNs. + See_Also: $(REF minElement, std,algorithm,searching) */ -MinType!T min(T...)(T args) -if (T.length >= 2) +auto min(T...)(T args) +if (T.length >= 2 && !is(CommonType!T == void)) { - //Get "a" + // Get the left-hand side of the comparison. static if (T.length <= 2) alias a = args[0]; else - auto a = min(args[0 .. ($+1)/2]); + auto a = min(args[0 .. ($ + 1) / 2]); alias T0 = typeof(a); - //Get "b" + // Get the right-hand side. static if (T.length <= 3) - alias b = args[$-1]; + alias b = args[$ - 1]; else - auto b = min(args[($+1)/2 .. $]); + auto b = min(args[($ + 1) / 2 .. $]); alias T1 = typeof(b); - import std.algorithm.internal : algoFormat; static assert(is(typeof(a < b)), - algoFormat("Invalid arguments: Cannot compare types %s and %s.", T0.stringof, T1.stringof)); + "Invalid arguments: Cannot compare types " ~ T0.stringof ~ + " and " ~ T1.stringof ~ " for ordering."); + + // Compute the returned type. + static if (is(typeof(mostNegative!T0 < mostNegative!T1))) + // Both are numeric (or character or Boolean), so we choose the one with the lowest minimum. + // If they have the same minimum, choose the one with the smallest size. + // If both mostNegative and sizeof are equal, go for stability: pick the type of the first one. + alias Result = Select!(mostNegative!T1 < mostNegative!T0 || + mostNegative!T1 == mostNegative!T0 && T1.sizeof < T0.sizeof, + T1, T0); + else + // At least one is non-numeric, so just go with the common type. + alias Result = CommonType!(T0, T1); - //Do the "min" proper with a and b + // Engage! import std.functional : lessThan; - immutable chooseA = lessThan!(T0, T1)(a, b); - return cast(typeof(return)) (chooseA ? a : b); + immutable chooseB = lessThan!(T1, T0)(b, a); + return cast(Result) (chooseB ? b : a); +} + +/// ditto +T min(T, U)(T a, U b) +if (is(T == U) && is(typeof(a < b))) +{ + /* Handle the common case without all the template expansions + * of the general case + */ + return b < a ? b : a; } + /// -@safe unittest +@safe @nogc @betterC unittest { int a = 5; short b = 6; @@ -1533,15 +1697,31 @@ if (T.length >= 2) auto e = min(a, b, c); static assert(is(typeof(e) == double)); assert(e == 2); + ulong f = 0xffff_ffff_ffff; + const uint g = min(f, 0xffff_0000); + assert(g == 0xffff_0000); + dchar h = 100; + uint i = 101; + static assert(is(typeof(min(h, i)) == dchar)); + static assert(is(typeof(min(i, h)) == uint)); + assert(min(h, i) == 100); +} - // With arguments of mixed signedness, the return type is the one that can - // store the lowest values. - a = -10; +/** +With arguments of mixed signedness, the return type is the one that can +store the lowest values. +*/ +@safe @nogc @betterC unittest +{ + int a = -10; uint f = 10; static assert(is(typeof(min(a, f)) == int)); assert(min(a, f) == -10); +} - // User-defined types that support comparison with < are supported. +/// User-defined types that support comparison with < are supported. +@safe unittest // not @nogc due to `Date` +{ 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)); @@ -1553,16 +1733,26 @@ if (T.length >= 2) assert(min(Date.max, Date.min) == Date.min); } +// min must be stable: when in doubt, return the first argument. +@safe unittest +{ + assert(min(1.0, double.nan) == 1.0); + assert(min(double.nan, 1.0) is double.nan); + static struct A { + int x; + string y; + int opCmp(const A a) const { return int(x > a.x) - int(x < a.x); } + } + assert(min(A(1, "first"), A(1, "second")) == A(1, "first")); +} + // mismatch /** -Sequentially compares elements in $(D r1) and $(D r2) in lockstep, and -stops at the first mismatch (according to $(D pred), by default +Sequentially compares elements in `r1` and `r2` in lockstep, and +stops at the first mismatch (according to `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: - $(HTTP sgi.com/tech/stl/_mismatch.html, STL's _mismatch) +evaluations of `pred`. */ Tuple!(Range1, Range2) mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2) @@ -1576,32 +1766,34 @@ if (isInputRange!(Range1) && isInputRange!(Range2)) } /// -@safe unittest +@safe @nogc unittest { - int[] x = [ 1, 5, 2, 7, 4, 3 ]; - double[] y = [ 1.0, 5, 2, 7.3, 4, 8 ]; - auto m = mismatch(x, y); + int[6] x = [ 1, 5, 2, 7, 4, 3 ]; + double[6] 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 +@safe @nogc unittest { - 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]); + import std.range : only; + + int[3] a = [ 1, 2, 3 ]; + int[4] b = [ 1, 2, 4, 5 ]; + auto mm = mismatch(a[], b[]); + assert(equal(mm[0], only(3))); + assert(equal(mm[1], only(4, 5))); } /** 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. +`choices` needs to be composed of pairs of test expressions and return +expressions. Each test-expression is compared with `switchExpression` using +`pred`(`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. @@ -1622,7 +1814,7 @@ 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 +yield true with any test expression - `SwitchError` is thrown. $(D SwitchError) is also thrown if a void return expression was executed without throwing anything. */ @@ -1734,74 +1926,48 @@ auto predSwitch(alias pred = "a == b", T, R ...)(T switchExpression, lazy R choi /** Checks if the two ranges have the same number of elements. This function is -optimized to always take advantage of the $(D length) member of either range +optimized to always take advantage of the `length` member of either range if it exists. If both ranges have a length member, this function is $(BIGOH 1). Otherwise, this function is $(BIGOH min(r1.length, r2.length)). +Infinite ranges are considered of the same length. An infinite range has never the same length as a +finite range. + Params: r1 = a finite $(REF_ALTTEXT input range, isInputRange, std,range,primitives) r2 = a finite $(REF_ALTTEXT input range, isInputRange, std,range,primitives) Returns: - $(D true) if both ranges have the same length, $(D false) otherwise. + `true` if both ranges have the same length, `false` otherwise. */ bool isSameLength(Range1, Range2)(Range1 r1, Range2 r2) -if (isInputRange!Range1 && - isInputRange!Range2 && - !isInfinite!Range1 && - !isInfinite!Range2) +if (isInputRange!Range1 && isInputRange!Range2) { - static if (hasLength!(Range1) && hasLength!(Range2)) + static if (isInfinite!Range1 || isInfinite!Range2) + { + return isInfinite!Range1 && isInfinite!Range2; + } + else static if (hasLength!(Range1) && hasLength!(Range2)) { return r1.length == r2.length; } else static if (hasLength!(Range1) && !hasLength!(Range2)) { - size_t length; - - while (!r2.empty) - { - r2.popFront; - - if (++length > r1.length) - { - return false; - } - } - - return !(length < r1.length); + return r2.walkLength(r1.length + 1) == r1.length; } else static if (!hasLength!(Range1) && hasLength!(Range2)) { - size_t length; - - while (!r1.empty) - { - r1.popFront; - - if (++length > r2.length) - { - return false; - } - } - - return !(length < r2.length); + return r1.walkLength(r2.length + 1) == r2.length; } else { - while (!r1.empty) + for (; !r1.empty; r1.popFront, r2.popFront) { if (r2.empty) - { return false; - } - - r1.popFront; - r2.popFront; } - return r2.empty; } } @@ -1823,7 +1989,7 @@ if (isInputRange!Range1 && } // Test CTFE -@safe pure unittest +@safe @nogc pure @betterC unittest { enum result1 = isSameLength([1, 2, 3], [4, 5, 6]); static assert(result1); @@ -1832,6 +1998,14 @@ if (isInputRange!Range1 && static assert(!result2); } +@safe @nogc pure unittest +{ + import std.range : only; + assert(isSameLength(only(1, 2, 3), only(4, 5, 6))); + assert(isSameLength(only(0.3, 90.4, 23.7, 119.2), only(42.6, 23.6, 95.5, 6.3))); + assert(!isSameLength(only(1, 3, 3), only(4, 5))); +} + @safe nothrow pure unittest { import std.internal.test.dummyrange; @@ -1861,38 +2035,38 @@ if (isInputRange!Range1 && assert(!isSameLength(r11, r12)); } -/// For convenience +// Still functional but not documented anymore. alias AllocateGC = Flag!"allocateGC"; /** Checks if both ranges are permutations of each other. -This function can allocate if the $(D Yes.allocateGC) flag is passed. This has -the benefit of have better complexity than the $(D Yes.allocateGC) option. However, +This function can allocate if the `Yes.allocateGC` flag is passed. This has +the benefit of have better complexity than the `Yes.allocateGC` option. However, this option is only available for ranges whose equality can be determined via each -element's $(D toHash) method. If customized equality is needed, then the $(D pred) +element's `toHash` method. If customized equality is needed, then the `pred` template parameter can be passed, and the function will automatically switch to the non-allocating algorithm. See $(REF binaryFun, std,functional) for more details on -how to define $(D pred). +how to define `pred`. Non-allocating forward range option: $(BIGOH n^2) -Non-allocating forward range option with custom $(D pred): $(BIGOH n^2) +Non-allocating forward range option with custom `pred`: $(BIGOH n^2) Allocating forward range option: amortized $(BIGOH r1.length) + $(BIGOH r2.length) Params: pred = an optional parameter to change how equality is defined - allocate_gc = $(D Yes.allocateGC)/$(D No.allocateGC) + allocateGC = `Yes.allocateGC`/`No.allocateGC` r1 = A finite $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) r2 = A finite $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) Returns: - $(D true) if all of the elements in $(D r1) appear the same number of times in $(D r2). - Otherwise, returns $(D false). + `true` if all of the elements in `r1` appear the same number of times in `r2`. + Otherwise, returns `false`. */ -bool isPermutation(AllocateGC allocate_gc, Range1, Range2) +bool isPermutation(Flag!"allocateGC" allocateGC, Range1, Range2) (Range1 r1, Range2 r2) -if (allocate_gc == Yes.allocateGC && +if (allocateGC == Yes.allocateGC && isForwardRange!Range1 && isForwardRange!Range2 && !isInfinite!Range1 && @@ -2111,7 +2285,7 @@ if (alternatives.length >= 1 && } /// -@safe pure unittest +@safe pure @betterC unittest { const a = 1; const b = 2; @@ -2130,7 +2304,11 @@ if (alternatives.length >= 1 && auto ef = either(e, f); static assert(is(typeof(ef) == int)); assert(ef == f); +} +/// +@safe pure unittest +{ immutable p = 1; immutable q = 2; auto pq = either(p, q); @@ -2141,7 +2319,11 @@ if (alternatives.length >= 1 && assert(either(0, 4) == 4); assert(either(0, 0) == 0); assert(either("", "a") == ""); +} +/// +@safe pure unittest +{ string r = null; assert(either(r, "a") == "a"); assert(either("a", "") == "a"); diff --git a/libphobos/src/std/algorithm/internal.d b/libphobos/src/std/algorithm/internal.d index ca03fd7..3caeefeb 100644 --- a/libphobos/src/std/algorithm/internal.d +++ b/libphobos/src/std/algorithm/internal.d @@ -14,22 +14,16 @@ package template algoFormat() } // Internal random array generators -version (unittest) +version (StdUnittest) { package enum size_t maxArraySize = 50; package enum size_t minArraySize = maxArraySize - 1; package string[] rndstuff(T : string)() { - import std.random : Random, unpredictableSeed, uniform; + import std.random : Xorshift, uniform; - static Random rnd; - static bool first = true; - if (first) - { - rnd = Random(unpredictableSeed); - first = false; - } + static rnd = Xorshift(234_567_891); string[] result = new string[uniform(minArraySize, maxArraySize, rnd)]; string alpha = "abcdefghijABCDEFGHIJ"; @@ -46,15 +40,9 @@ version (unittest) package int[] rndstuff(T : int)() { - import std.random : Random, unpredictableSeed, uniform; + import std.random : Xorshift, uniform; - static Random rnd; - static bool first = true; - if (first) - { - rnd = Random(unpredictableSeed); - first = false; - } + static rnd = Xorshift(345_678_912); int[] result = new int[uniform(minArraySize, maxArraySize, rnd)]; foreach (ref i; result) { diff --git a/libphobos/src/std/algorithm/iteration.d b/libphobos/src/std/algorithm/iteration.d index 19cfb77..c3e8487 100644 --- a/libphobos/src/std/algorithm/iteration.d +++ b/libphobos/src/std/algorithm/iteration.d @@ -1,54 +1,60 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _iteration algorithms. +It contains generic iteration algorithms. $(SCRIPT inhibitQuickIndex = 1;) $(BOOKTABLE Cheat Sheet, $(TR $(TH Function Name) $(TH Description)) $(T2 cache, - Eagerly evaluates and caches another range's $(D front).) + Eagerly evaluates and caches another range's `front`.) $(T2 cacheBidirectional, - As above, but also provides $(D back) and $(D popBack).) + As above, but also provides `back` and `popBack`.) $(T2 chunkBy, - $(D chunkBy!((a,b) => a[1] == b[1])([[1, 1], [1, 2], [2, 2], [2, 1]])) + `chunkBy!((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]).) + `[1, 1]`; the second with the elements `[1, 2]` and `[2, 2]`; + and the third with just `[2, 1]`.) $(T2 cumulativeFold, - $(D cumulativeFold!((a, b) => a + b)([1, 2, 3, 4])) returns a + `cumulativeFold!((a, b) => a + b)([1, 2, 3, 4])` returns a lazily-evaluated range containing the successive reduced values `1`, `3`, `6`, `10`.) $(T2 each, - $(D each!writeln([1, 2, 3])) eagerly prints the numbers $(D 1), $(D 2) - and $(D 3) on their own lines.) + `each!writeln([1, 2, 3])` eagerly prints the numbers `1`, `2` + and `3` on their own lines.) $(T2 filter, - $(D filter!(a => a > 0)([1, -1, 2, 0, -3])) iterates over elements $(D 1) - and $(D 2).) + `filter!(a => a > 0)([1, -1, 2, 0, -3])` iterates over elements `1` + and `2`.) $(T2 filterBidirectional, - Similar to $(D filter), but also provides $(D back) and $(D popBack) at + Similar to `filter`, but also provides `back` and `popBack` at a small increase in cost.) $(T2 fold, - $(D fold!((a, b) => a + b)([1, 2, 3, 4])) returns $(D 10).) + `fold!((a, b) => a + b)([1, 2, 3, 4])` returns `10`.) $(T2 group, - $(D group([5, 2, 2, 3, 3])) returns a range containing the tuples - $(D tuple(5, 1)), $(D tuple(2, 2)), and $(D tuple(3, 2)).) + `group([5, 2, 2, 3, 3])` returns a range containing the tuples + `tuple(5, 1)`, `tuple(2, 2)`, and `tuple(3, 2)`.) $(T2 joiner, - $(D joiner(["hello", "world!"], "; ")) returns a range that iterates - over the characters $(D "hello; world!"). No new string is created - + `joiner(["hello", "world!"], "; ")` returns a range that iterates + over the characters `"hello; world!"`. No new string is created - the existing inputs are iterated.) $(T2 map, - $(D map!(a => a * 2)([1, 2, 3])) lazily returns a range with the numbers - $(D 2), $(D 4), $(D 6).) + `map!(a => a * 2)([1, 2, 3])` lazily returns a range with the numbers + `2`, `4`, `6`.) +$(T2 mean, + Colloquially known as the average, `mean([1, 2, 3])` returns `2`.) $(T2 permutations, Lazily computes all permutations using Heap's algorithm.) $(T2 reduce, - $(D reduce!((a, b) => a + b)([1, 2, 3, 4])) returns $(D 10). + `reduce!((a, b) => a + b)([1, 2, 3, 4])` returns `10`. This is the old implementation of `fold`.) +$(T2 splitWhen, + Lazily splits a range by comparing adjacent elements.) $(T2 splitter, Lazily splits a range by a separator.) +$(T2 substitute, + `[1, 2].substitute(1, 0.1)` returns `[0.1, 2]`.) $(T2 sum, - Same as $(D fold), but specialized for accurate summation.) + Same as `fold`, but specialized for accurate summation.) $(T2 uniq, Iterates over the unique elements in a range, which is assumed sorted.) ) @@ -59,17 +65,17 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_iteration.d) +Source: $(PHOBOSSRC std/algorithm/iteration.d) Macros: T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) */ module std.algorithm.iteration; -// FIXME -import std.functional; // : unaryFun, binaryFun; +import std.functional : unaryFun, binaryFun; import std.range.primitives; import std.traits; +import std.typecons : Flag, Yes, No; private template aggregate(fun...) if (fun.length >= 1) @@ -117,36 +123,36 @@ if (fun.length >= 1) } /++ -$(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, +`cache` eagerly evaluates $(REF_ALTTEXT front, front, std,range,primitives) of `range` +on each construction or call to $(REF_ALTTEXT popFront, popFront, std,range,primitives), +to store the result in a _cache. +The result is then directly returned when $(REF_ALTTEXT front, front, std,range,primitives) 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 $(REF array, std,array). -In particular, it can be placed after a call to $(D map), or before a call -to $(D filter). +In particular, it can be placed after a call to $(LREF map), or before a call +$(REF filter, std,range) or $(REF tee, std,range) -$(D cache) may provide +`cache` may provide $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) 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 +call to `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 $(REF hasSlicing, std,_range,primitives) +`cache` does not provide random access primitives, +as `cache` would be unable to _cache the random accesses. +If `Range` provides slicing primitives, +then `cache` will provide the same slicing primitives, +but `hasSlicing!Cache` will not yield true (as the $(REF hasSlicing, std,range,primitives) trait also checks for random access). Params: range = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) Returns: - an input range with the cached values of range + An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with the cached values of range +/ auto cache(Range)(Range range) if (isInputRange!Range) @@ -203,16 +209,22 @@ if (isBidirectionalRange!Range) assert(counter == iota(-4, 5).length); } +// https://issues.dlang.org/show_bug.cgi?id=15891 +@safe pure unittest +{ + assert([1].map!(x=>[x].map!(y=>y)).cache.front.front == 1); +} + /++ -Tip: $(D cache) is eager when evaluating elements. If calling front on the -underlying _range has a side effect, it will be observable before calling -front on the actual cached _range. +Tip: `cache` is eager when evaluating elements. If calling front on the +underlying range has a side effect, it will be observable before calling +front on the actual cached range. -Furthermore, care should be taken composing $(D cache) with $(REF take, std,_range). -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. +Furthermore, care should be taken composing `cache` with $(REF take, std,range). +By placing `take` before `cache`, then `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 `take` after `cache` +may yield a faster range. Either way, the resulting ranges will be equivalent, but maybe not at the same cost or side effects. @@ -339,9 +351,17 @@ private struct _Cache(R, bool bidir) source = range; if (!range.empty) { - caches[0] = source.front; - static if (bidir) - caches[1] = source.back; + caches[0] = source.front; + static if (bidir) + caches[1] = source.back; + } + else + { + // needed, because the compiler cannot deduce, that 'caches' is initialized + // see https://issues.dlang.org/show_bug.cgi?id=15891 + caches[0] = UE.init; + static if (bidir) + caches[1] = UE.init; } } @@ -353,10 +373,7 @@ private struct _Cache(R, bool bidir) return source.empty; } - static if (hasLength!R) auto length() @property - { - return source.length; - } + mixin ImplementLength!source; E front() @property { @@ -376,7 +393,12 @@ private struct _Cache(R, bool bidir) if (!source.empty) caches[0] = source.front; else - caches = CacheTypes.init; + { + // see https://issues.dlang.org/show_bug.cgi?id=15891 + caches[0] = UE.init; + static if (bidir) + caches[1] = UE.init; + } } static if (bidir) void popBack() { @@ -385,7 +407,11 @@ private struct _Cache(R, bool bidir) if (!source.empty) caches[1] = source.back; else - caches = CacheTypes.init; + { + // see https://issues.dlang.org/show_bug.cgi?id=15891 + caches[0] = UE.init; + caches[1] = UE.init; + } } static if (isForwardRange!R) @@ -430,7 +456,7 @@ private struct _Cache(R, bool bidir) { assert(low <= high, "Bounds error when slicing cache."); } - body + do { import std.range : takeExactly; return this[low .. $].takeExactly(high - low); @@ -440,21 +466,14 @@ private struct _Cache(R, bool bidir) } /** -$(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(a)) -left to right for all elements $(D a) in $(D range). The original ranges are +Implements the homonym function (also known as `transform`) present +in many languages of functional flavor. The call `map!(fun)(range)` +returns a range of which elements are obtained by applying `fun(a)` +left to right for all elements `a` in `range`. The original ranges are not changed. Evaluation is done lazily. Params: fun = one or more transformation functions - r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) - -Returns: - a range with each fun applied to all the elements. If there is more than one - fun, the element type will be $(D Tuple) containing one element for each fun. See_Also: $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) @@ -462,6 +481,13 @@ See_Also: template map(fun...) if (fun.length >= 1) { + /** + Params: + r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + Returns: + A range with each fun applied to all the elements. If there is more than one + fun, the element type will be `Tuple` containing one element for each fun. + */ auto map(Range)(Range r) if (isInputRange!(Unqual!Range)) { import std.meta : AliasSeq, staticMap; @@ -475,7 +501,9 @@ if (fun.length >= 1) alias _funs = staticMap!(unaryFun, fun); alias _fun = adjoin!_funs; - // Once DMD issue #5710 is fixed, this validation loop can be moved into a template. + // Once https://issues.dlang.org/show_bug.cgi?id=5710 is fixed + // accross all compilers (as of 2020-04, it wasn't fixed in LDC and GDC), + // this validation loop can be moved into a template. foreach (f; _funs) { static assert(!is(typeof(f(RE.init)) == void), @@ -487,7 +515,8 @@ if (fun.length >= 1) alias _fun = unaryFun!fun; alias _funs = AliasSeq!(_fun); - // Do the validation separately for single parameters due to DMD issue #15777. + // Do the validation separately for single parameters due to + // https://issues.dlang.org/show_bug.cgi?id=15777. static assert(!is(typeof(_fun(RE.init)) == void), "Mapping function(s) must not return void: " ~ _funs.stringof); } @@ -497,19 +526,18 @@ if (fun.length >= 1) } /// -@safe unittest +@safe @nogc unittest { import std.algorithm.comparison : equal; - import std.range : chain; - 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 ])); + import std.range : chain, only; + auto squares = + chain(only(1, 2, 3, 4), only(5, 6)).map!(a => a * a); + assert(equal(squares, only(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 +Multiple functions can be passed to `map`. In that case, the +element type of `map` is a tuple containing one element for each function. */ @safe unittest @@ -527,7 +555,7 @@ function. } /** -You may alias $(D map) with some function(s) to a symbol and use +You may alias `map` with some function(s) to a symbol and use it separately: */ @safe unittest @@ -539,10 +567,9 @@ it separately: assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ])); } +// Verify workaround for https://issues.dlang.org/show_bug.cgi?id=15777 @safe unittest { - // Verify workaround for DMD #15777 - import std.algorithm.mutation, std.string; auto foo(string[] args) { @@ -602,7 +629,7 @@ private struct MapResult(alias fun, Range) static if (isRandomAccessRange!R) { - static if (is(typeof(_input[ulong.max]))) + static if (is(typeof(Range.init[ulong.max]))) private alias opIndex_t = ulong; else private alias opIndex_t = uint; @@ -613,15 +640,7 @@ private struct MapResult(alias fun, Range) } } - static if (hasLength!R) - { - @property auto length() - { - return _input.length; - } - - alias opDollar = length; - } + mixin ImplementLength!_input; static if (hasSlicing!R) { @@ -689,7 +708,7 @@ private struct MapResult(alias fun, Range) import std.internal.test.dummyrange; import std.range; import std.typecons : tuple; - import std.random : unpredictableSeed, uniform, Random; + import std.random : uniform, Random = Xorshift; int[] arr1 = [ 1, 2, 3, 4 ]; const int[] arr1Const = arr1; @@ -747,7 +766,7 @@ private struct MapResult(alias fun, Range) assert(fibsSquares.front == 9); auto repeatMap = map!"a"(repeat(1)); - auto gen = Random(unpredictableSeed); + auto gen = Random(123_456_789); auto index = uniform(0, 1024, gen); static assert(isInfinite!(typeof(repeatMap))); assert(repeatMap[index] == 1); @@ -779,7 +798,7 @@ private struct MapResult(alias fun, Range) assert(equal(ms2[0 .. 2], "日本"w)); assert(equal(ms3[0 .. 2], "HE")); - // Issue 5753 + // https://issues.dlang.org/show_bug.cgi?id=5753 static void voidFun(int) {} static int nonvoidFun(int) { return 0; } static assert(!__traits(compiles, map!voidFun([1]))); @@ -788,7 +807,7 @@ private struct MapResult(alias fun, Range) static assert(!__traits(compiles, map!(voidFun, nonvoidFun)([1]))); static assert(!__traits(compiles, map!(a => voidFun(a))([1]))); - // Phobos issue #15480 + // https://issues.dlang.org/show_bug.cgi?id=15480 auto dd = map!(z => z * z, c => c * c * c)([ 1, 2, 3, 4 ]); assert(dd[0] == tuple(1, 1)); assert(dd[1] == tuple(4, 8)); @@ -810,7 +829,7 @@ private struct MapResult(alias fun, Range) { import std.range : iota; - // Issue #10130 - map of iota with const step. + // https://issues.dlang.org/show_bug.cgi?id=10130 - map of iota with const step. const step = 2; assert(map!(i => i)(iota(0, 10, step)).walkLength == 5); @@ -842,35 +861,59 @@ private struct MapResult(alias fun, Range) assert(m.front == immutable(S)(null)); } +// Issue 20928 +@safe unittest +{ + struct Always3 + { + enum empty = false; + auto save() { return this; } + long front() { return 3; } + void popFront() {} + long opIndex(ulong i) { return 3; } + long opIndex(ulong i) immutable { return 3; } + } + + import std.algorithm.iteration : map; + Always3.init.map!(e => e)[ulong.max]; +} + // each /** -Eagerly iterates over $(D r) and calls $(D pred) over _each element. +Eagerly iterates over `r` and calls `fun` with _each element. + +If no function to call is specified, `each` defaults to doing nothing but +consuming the entire range. `r.front` will be evaluated, but that can be avoided +by specifying a lambda with a `lazy` parameter. -If no predicate is specified, $(D each) will default to doing nothing -but consuming the entire range. $(D .front) will be evaluated, but this -can be avoided by explicitly specifying a predicate lambda with a -$(D lazy) parameter. +`each` also supports `opApply`-based types, so it works with e.g. $(REF +parallel, std,parallelism). -$(D each) also supports $(D opApply)-based iterators, so it will work -with e.g. $(REF parallel, std,parallelism). +Normally the entire range is iterated. If partial iteration (early stopping) is +desired, `fun` needs to return a value of type $(REF Flag, +std,typecons)`!"each"` (`Yes.each` to continue iteration, or `No.each` to stop +iteration). Params: - pred = predicate to apply to each element of the range - r = range or iterable over which each iterates + fun = function to apply to _each element of the range + r = range or iterable over which `each` iterates -See_Also: $(REF tee, std,range) +Returns: `Yes.each` if the entire range was iterated, `No.each` in case of early +stopping. +See_Also: $(REF tee, std,range) */ -template each(alias pred = "a") +template each(alias fun = "a") { import std.meta : AliasSeq; import std.traits : Parameters; + import std.typecons : Flag, Yes, No; private: - alias BinaryArgs = AliasSeq!(pred, "i", "a"); + alias BinaryArgs = AliasSeq!(fun, "i", "a"); enum isRangeUnaryIterable(R) = - is(typeof(unaryFun!pred(R.init.front))); + is(typeof(unaryFun!fun(R.init.front))); enum isRangeBinaryIterable(R) = is(typeof(binaryFun!BinaryArgs(0, R.init.front))); @@ -882,21 +925,32 @@ private: enum isForeachUnaryIterable(R) = is(typeof((R r) { foreach (ref a; r) - cast(void) unaryFun!pred(a); + cast(void) unaryFun!fun(a); })); - enum isForeachBinaryIterable(R) = + enum isForeachUnaryWithIndexIterable(R) = is(typeof((R r) { - foreach (ref i, ref a; r) + foreach (i, ref a; r) cast(void) binaryFun!BinaryArgs(i, a); })); + enum isForeachBinaryIterable(R) = + is(typeof((R r) { + foreach (ref a, ref b; r) + cast(void) binaryFun!fun(a, b); + })); + enum isForeachIterable(R) = (!isForwardRange!R || isDynamicArray!R) && - (isForeachUnaryIterable!R || isForeachBinaryIterable!R); + (isForeachUnaryIterable!R || isForeachBinaryIterable!R || + isForeachUnaryWithIndexIterable!R); public: - void each(Range)(Range r) + /** + Params: + r = range or iterable over which each iterates + */ + Flag!"each" each(Range)(Range r) if (!isForeachIterable!Range && ( isRangeIterable!Range || __traits(compiles, typeof(r.front).length))) @@ -908,7 +962,15 @@ public: { while (!r.empty) { - cast(void) unaryFun!pred(r.front); + static if (!is(typeof(unaryFun!fun(r.front)) == Flag!"each")) + { + cast(void) unaryFun!fun(r.front); + } + else + { + if (unaryFun!fun(r.front) == No.each) return No.each; + } + r.popFront(); } } @@ -917,7 +979,14 @@ public: size_t i = 0; while (!r.empty) { - cast(void) binaryFun!BinaryArgs(i, r.front); + static if (!is(typeof(binaryFun!BinaryArgs(i, r.front)) == Flag!"each")) + { + cast(void) binaryFun!BinaryArgs(i, r.front); + } + else + { + if (binaryFun!BinaryArgs(i, r.front) == No.each) return No.each; + } r.popFront(); i++; } @@ -927,71 +996,149 @@ public: { // range interface with >2 parameters. for (auto range = r; !range.empty; range.popFront()) - pred(range.front.expand); + { + static if (!is(typeof(fun(r.front.expand)) == Flag!"each")) + { + cast(void) fun(range.front.expand); + } + else + { + if (fun(range.front.expand)) return No.each; + } + } } + return Yes.each; } - void each(Iterable)(auto ref Iterable r) + /// ditto + Flag!"each" each(Iterable)(auto ref Iterable r) if (isForeachIterable!Iterable || __traits(compiles, Parameters!(Parameters!(r.opApply)))) { static if (isForeachIterable!Iterable) { - debug(each) pragma(msg, "Using foreach for ", Iterable.stringof); static if (isForeachUnaryIterable!Iterable) { - foreach (ref e; r) - cast(void) unaryFun!pred(e); + debug(each) pragma(msg, "Using foreach UNARY for ", Iterable.stringof); + { + foreach (ref e; r) + { + static if (!is(typeof(unaryFun!fun(e)) == Flag!"each")) + { + cast(void) unaryFun!fun(e); + } + else + { + if (unaryFun!fun(e) == No.each) return No.each; + } + } + } + } + else static if (isForeachBinaryIterable!Iterable) + { + debug(each) pragma(msg, "Using foreach BINARY for ", Iterable.stringof); + foreach (ref a, ref b; r) + { + static if (!is(typeof(binaryFun!fun(a, b)) == Flag!"each")) + { + cast(void) binaryFun!fun(a, b); + } + else + { + if (binaryFun!fun(a, b) == No.each) return No.each; + } + } } - else // if (isForeachBinaryIterable!Iterable) + else static if (isForeachUnaryWithIndexIterable!Iterable) { - foreach (ref i, ref e; r) - cast(void) binaryFun!BinaryArgs(i, e); + debug(each) pragma(msg, "Using foreach INDEX for ", Iterable.stringof); + foreach (i, ref e; r) + { + static if (!is(typeof(binaryFun!BinaryArgs(i, e)) == Flag!"each")) + { + cast(void) binaryFun!BinaryArgs(i, e); + } + else + { + if (binaryFun!BinaryArgs(i, e) == No.each) return No.each; + } + } + } + else + { + static assert(0, "Invalid foreach iteratable type " ~ Iterable.stringof ~ " met."); } + return Yes.each; } else { // opApply with >2 parameters. count the delegate args. // only works if it is not templated (otherwise we cannot count the args) - auto dg(Parameters!(Parameters!(r.opApply)) params) { - pred(params); - return 0; // tells opApply to continue iteration + auto result = Yes.each; + auto dg(Parameters!(Parameters!(r.opApply)) params) + { + static if (!is(typeof(binaryFun!BinaryArgs(i, e)) == Flag!"each")) + { + fun(params); + return 0; // tells opApply to continue iteration + } + else + { + result = fun(params); + return result == Yes.each ? 0 : -1; + } } r.opApply(&dg); + return result; } } } /// -@system unittest +@safe unittest { import std.range : iota; + import std.typecons : No; - long[] arr; + int[] arr; iota(5).each!(n => arr ~= n); assert(arr == [0, 1, 2, 3, 4]); + // stop iterating early + iota(5).each!((n) { arr ~= n; return No.each; }); + assert(arr == [0, 1, 2, 3, 4, 0]); + // If the range supports it, the value can be mutated in place arr.each!((ref n) => n++); - assert(arr == [1, 2, 3, 4, 5]); + assert(arr == [1, 2, 3, 4, 5, 1]); arr.each!"a++"; - assert(arr == [2, 3, 4, 5, 6]); + assert(arr == [2, 3, 4, 5, 6, 2]); + auto m = arr.map!(n => n); // by-ref lambdas are not allowed for non-ref ranges - static assert(!is(typeof(arr.map!(n => n).each!((ref n) => n++)))); + static assert(!__traits(compiles, m.each!((ref n) => n++))); // The default predicate consumes the range - auto m = arr.map!(n => n); (&m).each(); assert(m.empty); +} + +/// `each` can pass an index variable for iterable objects which support this +@safe unittest +{ + auto arr = new size_t[4]; - // Indexes are also available for in-place mutations - arr[] = 0; arr.each!"a=i"(); - assert(arr == [0, 1, 2, 3, 4]); + assert(arr == [0, 1, 2, 3]); + + arr.each!((i, ref e) => e = i * 2); + assert(arr == [0, 2, 4, 6]); +} - // opApply iterators work as well +/// opApply iterators work as well +@system unittest +{ static class S { int x; @@ -1017,7 +1164,8 @@ public: assert(b == [ 3, 4, 5 ]); } -// #15358: application of `each` with >2 args (opApply) +// https://issues.dlang.org/show_bug.cgi?id=15358 +// application of `each` with >2 args (opApply) @system unittest { import std.range : lockstep; @@ -1032,7 +1180,8 @@ public: assert(c == [7,8,9]); } -// #15358: application of `each` with >2 args (range interface) +// https://issues.dlang.org/show_bug.cgi?id=15358 +// application of `each` with >2 args (range interface) @safe unittest { import std.range : zip; @@ -1047,7 +1196,8 @@ public: assert(res == [9, 12, 15]); } -// #16255: `each` on opApply doesn't support ref +// https://issues.dlang.org/show_bug.cgi?id=16255 +// `each` on opApply doesn't support ref @safe unittest { int[] dynamicArray = [1, 2, 3, 4, 5]; @@ -1063,7 +1213,8 @@ public: assert(staticArray == [3, 4, 5, 6, 7]); } -// #16255: `each` on opApply doesn't support ref +// https://issues.dlang.org/show_bug.cgi?id=16255 +// `each` on opApply doesn't support ref @system unittest { struct S @@ -1079,21 +1230,94 @@ public: assert(s.x == 2); } +// https://issues.dlang.org/show_bug.cgi?id=15357 +// `each` should behave similar to foreach +@safe unittest +{ + import std.range : iota; + + auto arr = [1, 2, 3, 4]; + + // 1 ref parameter + arr.each!((ref e) => e = 0); + assert(arr.sum == 0); + + // 1 ref parameter and index + arr.each!((i, ref e) => e = cast(int) i); + assert(arr.sum == 4.iota.sum); +} + +// https://issues.dlang.org/show_bug.cgi?id=15357 +// `each` should behave similar to foreach +@system unittest +{ + import std.range : iota, lockstep; + + // 2 ref parameters and index + auto arrA = [1, 2, 3, 4]; + auto arrB = [5, 6, 7, 8]; + lockstep(arrA, arrB).each!((ref a, ref b) { + a = 0; + b = 1; + }); + assert(arrA.sum == 0); + assert(arrB.sum == 4); + + // 3 ref parameters + auto arrC = [3, 3, 3, 3]; + lockstep(arrA, arrB, arrC).each!((ref a, ref b, ref c) { + a = 1; + b = 2; + c = 3; + }); + assert(arrA.sum == 4); + assert(arrB.sum == 8); + assert(arrC.sum == 12); +} + +// https://issues.dlang.org/show_bug.cgi?id=15357 +// `each` should behave similar to foreach +@system unittest +{ + import std.range : lockstep; + import std.typecons : Tuple; + + auto a = "abc"; + auto b = "def"; + + // print each character with an index + { + alias Element = Tuple!(size_t, "index", dchar, "value"); + Element[] rForeach, rEach; + foreach (i, c ; a) rForeach ~= Element(i, c); + a.each!((i, c) => rEach ~= Element(i, c)); + assert(rForeach == rEach); + assert(rForeach == [Element(0, 'a'), Element(1, 'b'), Element(2, 'c')]); + } + + // print pairs of characters + { + alias Element = Tuple!(dchar, "a", dchar, "b"); + Element[] rForeach, rEach; + foreach (c1, c2 ; a.lockstep(b)) rForeach ~= Element(c1, c2); + a.lockstep(b).each!((c1, c2) => rEach ~= Element(c1, c2)); + assert(rForeach == rEach); + assert(rForeach == [Element('a', 'd'), Element('b', 'e'), Element('c', 'f')]); + } +} + // filter /** -$(D auto filter(Range)(Range rs) if (isInputRange!(Unqual!Range));) - -Implements the higher order _filter function. The predicate is passed to +Implements the higher order filter function. The predicate is passed to $(REF unaryFun, std,functional), and can either accept a string, or any callable -that can be executed via $(D pred(element)). +that can be executed via `pred(element)`. Params: predicate = Function to apply to each element of range - range = Input range of elements Returns: - $(D filter!(predicate)(range)) returns a new range containing only elements $(D x) in $(D range) for - which $(D predicate(x)) returns $(D true). + `filter!(predicate)(range)` returns a new range containing only elements `x` in `range` for + which `predicate(x)` returns `true`. See_Also: $(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) @@ -1101,6 +1325,14 @@ See_Also: template filter(alias predicate) if (is(typeof(unaryFun!predicate))) { + /** + Params: + range = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + of elements + Returns: + A range containing only elements `x` in `range` for + which `predicate(x)` returns `true`. + */ auto filter(Range)(Range range) if (isInputRange!(Unqual!Range)) { return FilterResult!(unaryFun!predicate, Range)(range); @@ -1111,16 +1343,16 @@ if (is(typeof(unaryFun!predicate))) @safe unittest { import std.algorithm.comparison : equal; - import std.math : approxEqual; + import std.math.operations : isClose; import std.range; int[] arr = [ 1, 2, 3, 4, 5 ]; - // Sum all elements + // Filter below 3 auto small = filter!(a => a < 3)(arr); assert(equal(small, [ 1, 2 ])); - // Sum again, but with Uniform Function Call Syntax (UFCS) + // Filter again, but with Uniform Function Call Syntax (UFCS) auto sum = arr.filter!(a => a < 3); assert(equal(sum, [ 1, 2 ])); @@ -1133,7 +1365,7 @@ if (is(typeof(unaryFun!predicate))) // 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 ])); + assert(isClose(r1, [ 2.5 ])); } private struct FilterResult(alias pred, Range) @@ -1176,11 +1408,11 @@ private struct FilterResult(alias pred, Range) void popFront() { + prime; do { _input.popFront(); } while (!_input.empty && !pred(_input.front)); - _primed = true; } @property auto ref front() @@ -1298,10 +1530,25 @@ private struct FilterResult(alias pred, Range) assert(equal(filter!underX(list), [ 1, 2, 3, 4 ])); } +// https://issues.dlang.org/show_bug.cgi?id=19823 +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : dropOne; + + auto a = [1, 2, 3, 4]; + assert(a.filter!(a => a != 1).dropOne.equal([3, 4])); + assert(a.filter!(a => a != 2).dropOne.equal([3, 4])); + assert(a.filter!(a => a != 3).dropOne.equal([2, 4])); + assert(a.filter!(a => a != 4).dropOne.equal([2, 3])); + assert(a.filter!(a => a == 1).dropOne.empty); + assert(a.filter!(a => a == 2).dropOne.empty); + assert(a.filter!(a => a == 3).dropOne.empty); + assert(a.filter!(a => a == 4).dropOne.empty); +} + /** - * $(D auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range));) - * - * Similar to $(D filter), except it defines a + * Similar to `filter`, except it defines a * $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives). * There is a speed disadvantage - the constructor spends time * finding the last element in the range that satisfies the filtering @@ -1310,17 +1557,19 @@ private struct FilterResult(alias pred, Range) * $(REF retro, std,range) can be applied against the filtered range. * * The predicate is passed to $(REF unaryFun, std,functional), and can either - * accept a string, or any callable that can be executed via $(D pred(element)). + * accept a string, or any callable that can be executed via `pred(element)`. * * Params: * pred = Function to apply to each element of range - * r = Bidirectional range of elements - * - * Returns: - * a new range containing only the elements in r for which pred returns $(D true). */ template filterBidirectional(alias pred) { + /** + Params: + r = Bidirectional range of elements + Returns: + A range containing only the elements in `r` for which `pred` returns `true`. + */ auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range)) { return FilterBidiResult!(unaryFun!pred, Range)(r); @@ -1398,22 +1647,23 @@ private struct FilterBidiResult(alias pred, Range) Groups consecutively equivalent elements into a single tuple of the element and the number of its repetitions. -Similarly to $(D uniq), $(D group) produces a range that iterates over unique +Similarly to `uniq`, `group` produces a range that iterates over unique consecutive elements of the given range. Each element of this range is a tuple of the element and the number of times it is repeated in the original range. -Equivalence of elements is assessed by using the predicate $(D pred), which -defaults to $(D "a == b"). The predicate is passed to $(REF binaryFun, std,functional), +Equivalence of elements is assessed by using the predicate `pred`, which +defaults to `"a == b"`. The predicate is passed to $(REF binaryFun, std,functional), and can either accept a string, or any callable that can be executed via -$(D pred(element, element)). +`pred(element, element)`. Params: pred = Binary predicate for determining equivalence of two elements. + R = The range type r = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to iterate over. -Returns: A range of elements of type $(D Tuple!(ElementType!R, uint)), +Returns: A range of elements of type `Tuple!(ElementType!R, uint)`, representing each consecutively unique element and its respective number of -occurrences in that run. This will be an input range if $(D R) is an input +occurrences in that run. This will be an input range if `R` is an input range, and a forward range in all other cases. See_Also: $(LREF chunkBy), which chunks an input range into subranges @@ -1457,6 +1707,12 @@ if (isInputRange!R) if (!_input.empty) popFront(); } + private this(R input, Tuple!(MutableE, uint) current) + { + _input = input; + _current = current; + } + /// void popFront() { @@ -1490,7 +1746,7 @@ if (isInputRange!R) } } - /// + /// Returns: the front of the range @property auto ref front() { assert(!empty, "Attempting to fetch the front of an empty Group."); @@ -1500,11 +1756,9 @@ if (isInputRange!R) static if (isForwardRange!R) { /// - @property typeof(this) save() { - typeof(this) ret = this; - ret._input = this._input.save; - ret._current = this._current; - return ret; + @property typeof(this) save() + { + return Group(_input.save, _current); } } } @@ -1567,7 +1821,7 @@ if (isInputRange!R) import std.algorithm.comparison : equal; import std.typecons : tuple; - // Issue 13857 + // https://issues.dlang.org/show_bug.cgi?id=13857 immutable(int)[] a1 = [1,1,2,2,2,3,4,4,5,6,6,7,8,9,9,9]; auto g1 = group(a1); assert(equal(g1, [ tuple(1, 2u), tuple(2, 3u), tuple(3, 1u), @@ -1575,18 +1829,18 @@ if (isInputRange!R) tuple(7, 1u), tuple(8, 1u), tuple(9, 3u) ])); - // Issue 13162 + // https://issues.dlang.org/show_bug.cgi?id=13162 immutable(ubyte)[] a2 = [1, 1, 1, 0, 0, 0]; auto g2 = a2.group; assert(equal(g2, [ tuple(1, 3u), tuple(0, 3u) ])); - // Issue 10104 + // https://issues.dlang.org/show_bug.cgi?id=10104 const a3 = [1, 1, 2, 2]; auto g3 = a3.group; assert(equal(g3, [ tuple(1, 2u), tuple(2, 2u) ])); interface I {} - class C : I {} + class C : I { override size_t toHash() const nothrow @safe { return 0; } } const C[] a4 = [new const C()]; auto g4 = a4.group!"a is b"; assert(g4.front[1] == 1); @@ -1600,18 +1854,52 @@ if (isInputRange!R) assert(equal(g6.front[0], [1])); } +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; + auto r = arr.group; + assert(r.equal([ tuple(1,1u), tuple(2, 4u), tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ])); + r.popFront; + assert(r.equal([ tuple(2, 4u), tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ])); + auto s = r.save; + r.popFrontN(2); + assert(r.equal([ tuple(4, 3u), tuple(5, 1u) ])); + assert(s.equal([ tuple(2, 4u), tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ])); + s.popFront; + auto t = s.save; + r.popFront; + s.popFront; + assert(r.equal([ tuple(5, 1u) ])); + assert(s.equal([ tuple(4, 3u), tuple(5, 1u) ])); + assert(t.equal([ tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ])); +} + +// https://issues.dlang.org/show_bug.cgi?id=18657 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : refRange; + string s = "foo"; + auto r = refRange(&s).group; + assert(equal(r.save, "foo".group)); + assert(equal(r, "foo".group)); +} + // Used by implementation of chunkBy for non-forward input ranges. private struct ChunkByChunkImpl(alias pred, Range) if (isInputRange!Range && !isForwardRange!Range) { alias fun = binaryFun!pred; - private Range r; + private Range *r; private ElementType!Range prev; - this(Range range, ElementType!Range _prev) + this(ref Range range, ElementType!Range _prev) { - r = range; + r = ⦥ prev = _prev; } @@ -1620,18 +1908,26 @@ if (isInputRange!Range && !isForwardRange!Range) return r.empty || !fun(prev, r.front); } - @property ElementType!Range front() { return r.front; } - void popFront() { r.popFront(); } + @property ElementType!Range front() + { + assert(!empty, "Attempting to fetch the front of an empty chunkBy chunk."); + return r.front; + } + + void popFront() + { + assert(!empty, "Attempting to popFront an empty chunkBy chunk."); + r.popFront(); + } } private template ChunkByImplIsUnary(alias pred, Range) { - static if (is(typeof(binaryFun!pred(ElementType!Range.init, - ElementType!Range.init)) : bool)) + alias e = lvalueOf!(ElementType!Range); + + static if (is(typeof(binaryFun!pred(e, e)) : bool)) enum ChunkByImplIsUnary = false; - else static if (is(typeof( - unaryFun!pred(ElementType!Range.init) == - unaryFun!pred(ElementType!Range.init)))) + else static if (is(typeof(unaryFun!pred(e) == unaryFun!pred(e)) : bool)) enum ChunkByImplIsUnary = true; else static assert(0, "chunkBy expects either a binary predicate or "~ @@ -1652,6 +1948,7 @@ if (isInputRange!Range && !isForwardRange!Range) private Range r; private ElementType!Range _prev; + private bool openChunk = false; this(Range _r) { @@ -1673,10 +1970,12 @@ if (isInputRange!Range && !isForwardRange!Range) _prev = typeof(_prev).init; } } - @property bool empty() { return r.empty; } + @property bool empty() { return r.empty && !openChunk; } @property auto front() { + assert(!empty, "Attempting to fetch the front of an empty chunkBy."); + openChunk = true; static if (isUnary) { import std.typecons : tuple; @@ -1691,6 +1990,8 @@ if (isInputRange!Range && !isForwardRange!Range) void popFront() { + assert(!empty, "Attempting to popFront an empty chunkBy."); + openChunk = false; while (!r.empty) { if (!eq(_prev, r.front)) @@ -1702,105 +2003,137 @@ if (isInputRange!Range && !isForwardRange!Range) } } } +// Outer range for forward range version of chunkBy +private struct ChunkByOuter(Range, bool eqEquivalenceAssured) +{ + size_t groupNum; + Range current; + Range next; + static if (!eqEquivalenceAssured) + { + bool nextUpdated; + } +} -// Single-pass implementation of chunkBy for forward ranges. -private struct ChunkByImpl(alias pred, Range) -if (isForwardRange!Range) +// Inner range for forward range version of chunkBy +private struct ChunkByGroup(alias eq, Range, bool eqEquivalenceAssured) { import std.typecons : RefCounted; - enum bool isUnary = ChunkByImplIsUnary!(pred, Range); - - static if (isUnary) - alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b)); - else - alias eq = binaryFun!pred; - - // Outer range - static struct Impl - { - size_t groupNum; - Range current; - Range next; - } + alias OuterRange = ChunkByOuter!(Range, eqEquivalenceAssured); - // Inner range - static struct Group + private size_t groupNum; + static if (eqEquivalenceAssured) { - private size_t groupNum; private Range start; - private Range current; + } + private Range current; - private RefCounted!Impl mothership; + private RefCounted!(OuterRange) mothership; - this(RefCounted!Impl origin) + this(RefCounted!(OuterRange) origin) + { + groupNum = origin.groupNum; + current = origin.current.save; + assert(!current.empty, "Passed range 'r' must not be empty"); + static if (eqEquivalenceAssured) { - groupNum = origin.groupNum; - start = origin.current.save; - current = origin.current.save; - assert(!start.empty); - - mothership = origin; - // Note: this requires reflexivity. + // Check for reflexivity. assert(eq(start.front, current.front), - "predicate is not reflexive"); + "predicate is not reflexive"); } - @property bool empty() { return groupNum == size_t.max; } - @property auto ref front() { return current.front; } + mothership = origin; + } - void popFront() + @property bool empty() { return groupNum == size_t.max; } + @property auto ref front() { return current.front; } + + void popFront() + { + static if (!eqEquivalenceAssured) { - current.popFront(); + auto prevElement = current.front; + } + + current.popFront(); - // Note: this requires transitivity. - if (current.empty || !eq(start.front, current.front)) + static if (eqEquivalenceAssured) + { + //this requires transitivity from the predicate. + immutable nowEmpty = current.empty || !eq(start.front, current.front); + } + else + { + immutable nowEmpty = current.empty || !eq(prevElement, current.front); + } + + + if (nowEmpty) + { + if (groupNum == mothership.groupNum) { - if (groupNum == mothership.groupNum) + // If parent range hasn't moved on yet, help it along by + // saving location of start of next Group. + mothership.next = current.save; + static if (!eqEquivalenceAssured) { - // If parent range hasn't moved on yet, help it along by - // saving location of start of next Group. - mothership.next = current.save; + mothership.nextUpdated = true; } - - groupNum = size_t.max; } - } - @property auto save() - { - auto copy = this; - copy.current = current.save; - return copy; + groupNum = size_t.max; } } - static assert(isForwardRange!Group); - private RefCounted!Impl impl; + @property auto save() + { + auto copy = this; + copy.current = current.save; + return copy; + } +} + +private enum GroupingOpType{binaryEquivalent, binaryAny, unary} + +// Single-pass implementation of chunkBy for forward ranges. +private struct ChunkByImpl(alias pred, alias eq, GroupingOpType opType, Range) +if (isForwardRange!Range) +{ + import std.typecons : RefCounted; + + enum bool eqEquivalenceAssured = opType != GroupingOpType.binaryAny; + alias OuterRange = ChunkByOuter!(Range, eqEquivalenceAssured); + alias InnerRange = ChunkByGroup!(eq, Range, eqEquivalenceAssured); + + static assert(isForwardRange!InnerRange); + + private RefCounted!OuterRange impl; this(Range r) { - impl = RefCounted!Impl(0, r, r.save); + static if (eqEquivalenceAssured) + { + impl = RefCounted!OuterRange(0, r, r.save); + } + else impl = RefCounted!OuterRange(0, r, r.save, false); } @property bool empty() { return impl.current.empty; } - @property auto front() + static if (opType == GroupingOpType.unary) @property auto front() { - static if (isUnary) - { - import std.typecons : tuple; - return tuple(unaryFun!pred(impl.current.front), Group(impl)); - } - else - { - return Group(impl); - } + import std.typecons : tuple; + return tuple(unaryFun!pred(impl.current.front), InnerRange(impl)); + } + else @property auto front() + { + return InnerRange(impl); } - void popFront() + static if (eqEquivalenceAssured) void popFront() { // Scan for next group. If we're lucky, one of our Groups would have // already set .next to the start of the next group, in which case the @@ -1815,6 +2148,24 @@ if (isForwardRange!Range) // Indicate to any remaining Groups that we have moved on. impl.groupNum++; } + else void popFront() + { + if (impl.nextUpdated) + { + impl.current = impl.next.save; + } + else while (true) + { + auto prevElement = impl.current.front; + impl.current.popFront(); + if (impl.current.empty) break; + if (!eq(prevElement, impl.current.front)) break; + } + + impl.nextUpdated = false; + // Indicate to any remaining Groups that we have moved on. + impl.groupNum++; + } @property auto save() { @@ -1823,7 +2174,43 @@ if (isForwardRange!Range) return typeof(this)(impl.current.save); } - static assert(isForwardRange!(typeof(this))); + static assert(isForwardRange!(typeof(this)), typeof(this).stringof + ~ " must be a forward range"); +} + +//Test for https://issues.dlang.org/show_bug.cgi?id=14909 +@system unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + import std.stdio; + auto n = 3; + auto s = [1,2,3].chunkBy!(a => a+n); + auto t = s.save.map!(x=>x[0]); + auto u = s.map!(x=>x[1]); + assert(t.equal([4,5,6])); + assert(u.equal!equal([[1],[2],[3]])); +} + +//Test for https://issues.dlang.org/show_bug.cgi?id=18751 +@system unittest +{ + import std.algorithm.comparison : equal; + + string[] data = [ "abc", "abc", "def" ]; + int[] indices = [ 0, 1, 2 ]; + + auto chunks = indices.chunkBy!((i, j) => data[i] == data[j]); + assert(chunks.equal!equal([ [ 0, 1 ], [ 2 ] ])); +} + +//Additional test for fix for issues 14909 and 18751 +@system unittest +{ + import std.algorithm.comparison : equal; + auto v = [2,4,8,3,6,9,1,5,7]; + auto i = 2; + assert(v.chunkBy!((a,b) => a % i == b % i).equal!equal([[2,4,8],[3],[6],[9,1,5,7]])); } @system unittest @@ -1886,18 +2273,19 @@ if (isForwardRange!Range) * In other languages this is often called `partitionBy`, `groupBy` * or `sliceWhen`. * - * Equivalence is defined by the predicate $(D pred), which can be either + * Equivalence is defined by the predicate `pred`, which can be either * binary, which is passed to $(REF binaryFun, std,functional), or unary, which is - * passed to $(REF unaryFun, std,functional). In the binary form, two _range elements - * $(D a) and $(D b) are considered equivalent if $(D pred(a,b)) is true. In - * unary form, two elements are considered equivalent if $(D pred(a) == pred(b)) + * passed to $(REF unaryFun, std,functional). In the binary form, two range elements + * `a` and `b` are considered equivalent if `pred(a,b)` is true. In + * unary form, two elements are considered equivalent if `pred(a) == pred(b)` * is true. * * This predicate must be an equivalence relation, that is, it must be - * reflexive ($(D pred(x,x)) is always true), symmetric - * ($(D pred(x,y) == pred(y,x))), and transitive ($(D pred(x,y) && pred(y,z)) - * implies $(D pred(x,z))). If this is not the case, the range returned by - * chunkBy may assert at runtime or behave erratically. + * reflexive (`pred(x,x)` is always true), symmetric + * (`pred(x,y) == pred(y,x)`), and transitive (`pred(x,y) && pred(y,z)` + * implies `pred(x,z)`). If this is not the case, the range returned by + * chunkBy may assert at runtime or behave erratically. Use $(LREF splitWhen) + * if you want to chunk by a predicate that is not an equivalence relation. * * Params: * pred = Predicate for determining equivalence. @@ -1907,7 +2295,8 @@ if (isForwardRange!Range) * all elements in a given subrange are equivalent under the given predicate. * With a unary predicate, a range of tuples is returned, with the tuple * consisting of the result of the unary predicate for each subrange, and the - * subrange itself. + * subrange itself. Copying the range currently has reference semantics, but this may + * change in the future. * * Notes: * @@ -1923,7 +2312,20 @@ if (isForwardRange!Range) auto chunkBy(alias pred, Range)(Range r) if (isInputRange!Range) { - return ChunkByImpl!(pred, Range)(r); + static if (ChunkByImplIsUnary!(pred, Range)) + { + enum opType = GroupingOpType.unary; + alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b)); + } + else + { + enum opType = GroupingOpType.binaryEquivalent; + alias eq = binaryFun!pred; + } + static if (isForwardRange!Range) + return ChunkByImpl!(pred, eq, opType, Range)(r); + else + return ChunkByImpl!(pred, Range)(r); } /// Showing usage with binary predicate: @@ -1953,20 +2355,6 @@ if (isInputRange!Range) ])); } -version (none) // this example requires support for non-equivalence relations -@safe unittest -{ - // Grouping by maximum adjacent difference: - import std.math : abs; - auto r3 = [1, 3, 2, 5, 4, 9, 10].chunkBy!((a, b) => abs(a-b) < 3); - assert(r3.equal!equal([ - [1, 3, 2], - [5, 4], - [9, 10] - ])); - -} - /// Showing usage with unary predicate: /* FIXME: pure @safe nothrow*/ @system unittest { @@ -2032,11 +2420,22 @@ version (none) // this example requires support for non-equivalence relations 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(); } + @property auto front() pure @safe nothrow { assert(!empty); return data.front; } + void popFront() pure @safe nothrow { assert(!empty); data.popFront(); } } auto refInputRange(R)(R range) { return new RefInputRange!R(range); } + // An input range API with value semantics. + struct ValInputRange(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 { assert(!empty); return data.front; } + void popFront() pure @safe nothrow { assert(!empty); data.popFront(); } + } + auto valInputRange(R)(R range) { return ValInputRange!R(range); } + { auto arr = [ Item(1,2), Item(1,3), Item(2,3) ]; static assert(isForwardRange!(typeof(arr))); @@ -2067,7 +2466,7 @@ version (none) // this example requires support for non-equivalence relations assert(byY2.front[1].equal([ Item(1,2) ])); } - // Test non-forward input ranges. + // Test non-forward input ranges with reference semantics. { auto range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]); auto byX = chunkBy!(a => a.x)(range); @@ -2091,14 +2490,371 @@ version (none) // this example requires support for non-equivalence relations assert(byY.empty); assert(range.empty); } + + // Test non-forward input ranges with value semantics. + { + auto range = valInputRange([ Item(1,1), Item(1,2), Item(2,2) ]); + auto byX = chunkBy!(a => a.x)(range); + assert(byX.front[0] == 1); + assert(byX.front[1].equal([ Item(1,1), Item(1,2) ])); + byX.popFront(); + assert(byX.front[0] == 2); + assert(byX.front[1].equal([ Item(2,2) ])); + byX.popFront(); + assert(byX.empty); + assert(!range.empty); // Opposite of refInputRange test + + range = valInputRange([ Item(1,1), Item(1,2), Item(2,2) ]); + auto byY = chunkBy!(a => a.y)(range); + assert(byY.front[0] == 1); + assert(byY.front[1].equal([ Item(1,1) ])); + byY.popFront(); + assert(byY.front[0] == 2); + assert(byY.front[1].equal([ Item(1,2), Item(2,2) ])); + byY.popFront(); + assert(byY.empty); + assert(!range.empty); // Opposite of refInputRange test + } + + /* https://issues.dlang.org/show_bug.cgi?id=19532 + * General behavior of non-forward input ranges. + * + * - If the same chunk is retrieved multiple times via front, the separate chunk + * instances refer to a shared range segment that advances as a single range. + * - Emptying a chunk via popFront does not implicitly popFront the chunk off + * main range. The chunk is still available via front, it is just empty. + */ + { + import std.algorithm.comparison : equal; + import core.exception : AssertError; + import std.exception : assertThrown; + + auto a = [[0, 0], [0, 1], + [1, 2], [1, 3], [1, 4], + [2, 5], [2, 6], + [3, 7], + [4, 8]]; + + // Value input range + { + auto r = valInputRange(a).chunkBy!((a, b) => a[0] == b[0]); + + size_t numChunks = 0; + while (!r.empty) + { + ++numChunks; + auto chunk = r.front; + while (!chunk.empty) + { + assert(r.front.front[1] == chunk.front[1]); + chunk.popFront; + } + assert(!r.empty); + assert(r.front.empty); + r.popFront; + } + + assert(numChunks == 5); + + // Now front and popFront should assert. + bool thrown = false; + try r.front; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r.popFront; + catch (AssertError) thrown = true; + assert(thrown); + } + + // Reference input range + { + auto r = refInputRange(a).chunkBy!((a, b) => a[0] == b[0]); + + size_t numChunks = 0; + while (!r.empty) + { + ++numChunks; + auto chunk = r.front; + while (!chunk.empty) + { + assert(r.front.front[1] == chunk.front[1]); + chunk.popFront; + } + assert(!r.empty); + assert(r.front.empty); + r.popFront; + } + + assert(numChunks == 5); + + // Now front and popFront should assert. + bool thrown = false; + try r.front; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r.popFront; + catch (AssertError) thrown = true; + assert(thrown); + } + + // Ensure that starting with an empty range doesn't create an empty chunk. + { + int[] emptyRange = []; + + auto r1 = valInputRange(emptyRange).chunkBy!((a, b) => a == b); + auto r2 = refInputRange(emptyRange).chunkBy!((a, b) => a == b); + + assert(r1.empty); + assert(r2.empty); + + bool thrown = false; + try r1.front; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r1.popFront; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r2.front; + catch (AssertError) thrown = true; + assert(thrown); + + thrown = false; + try r2.popFront; + catch (AssertError) thrown = true; + assert(thrown); + } + } + + // https://issues.dlang.org/show_bug.cgi?id=19532 - Using roundRobin/chunkBy + { + import std.algorithm.comparison : equal; + import std.range : roundRobin; + + auto a0 = [0, 1, 3, 6]; + auto a1 = [0, 2, 4, 6, 7]; + auto a2 = [1, 2, 4, 6, 8, 8, 9]; + + auto expected = + [[0, 0], [1, 1], [2, 2], [3], [4, 4], [6, 6, 6], [7], [8, 8], [9]]; + + auto r1 = roundRobin(valInputRange(a0), valInputRange(a1), valInputRange(a2)) + .chunkBy!((a, b) => a == b); + assert(r1.equal!equal(expected)); + + auto r2 = roundRobin(refInputRange(a0), refInputRange(a1), refInputRange(a2)) + .chunkBy!((a, b) => a == b); + assert(r2.equal!equal(expected)); + + auto r3 = roundRobin(a0, a1, a2).chunkBy!((a, b) => a == b); + assert(r3.equal!equal(expected)); + } + + // https://issues.dlang.org/show_bug.cgi?id=19532 - Using merge/chunkBy + { + import std.algorithm.comparison : equal; + import std.algorithm.sorting : merge; + + auto a0 = [2, 3, 5]; + auto a1 = [2, 4, 5]; + auto a2 = [1, 2, 4, 5]; + + auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]]; + + auto r1 = merge(valInputRange(a0), valInputRange(a1), valInputRange(a2)) + .chunkBy!((a, b) => a == b); + assert(r1.equal!equal(expected)); + + auto r2 = merge(refInputRange(a0), refInputRange(a1), refInputRange(a2)) + .chunkBy!((a, b) => a == b); + assert(r2.equal!equal(expected)); + + auto r3 = merge(a0, a1, a2).chunkBy!((a, b) => a == b); + assert(r3.equal!equal(expected)); + } + + // https://issues.dlang.org/show_bug.cgi?id=19532 - Using chunkBy/map-fold + { + import std.algorithm.comparison : equal; + import std.algorithm.iteration : fold, map; + + auto a = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8, 9]; + auto expected = [0, 3, 4, 6, 8, 5, 18, 7, 16, 9]; + + auto r1 = a + .chunkBy!((a, b) => a == b) + .map!(c => c.fold!((a, b) => a + b)); + assert(r1.equal(expected)); + + auto r2 = valInputRange(a) + .chunkBy!((a, b) => a == b) + .map!(c => c.fold!((a, b) => a + b)); + assert(r2.equal(expected)); + + auto r3 = refInputRange(a) + .chunkBy!((a, b) => a == b) + .map!(c => c.fold!((a, b) => a + b)); + assert(r3.equal(expected)); + } + + // https://issues.dlang.org/show_bug.cgi?id=16169 + // https://issues.dlang.org/show_bug.cgi?id=17966 + // https://issues.dlang.org/show_bug.cgi?id=19532 + // Using multiwayMerge/chunkBy + { + import std.algorithm.comparison : equal; + import std.algorithm.setops : multiwayMerge; + + { + auto a0 = [2, 3, 5]; + auto a1 = [2, 4, 5]; + auto a2 = [1, 2, 4, 5]; + + auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]]; + auto r = multiwayMerge([a0, a1, a2]).chunkBy!((a, b) => a == b); + assert(r.equal!equal(expected)); + } + { + auto a0 = [2, 3, 5]; + auto a1 = [2, 4, 5]; + auto a2 = [1, 2, 4, 5]; + + auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]]; + auto r = + multiwayMerge([valInputRange(a0), valInputRange(a1), valInputRange(a2)]) + .chunkBy!((a, b) => a == b); + assert(r.equal!equal(expected)); + } + { + auto a0 = [2, 3, 5]; + auto a1 = [2, 4, 5]; + auto a2 = [1, 2, 4, 5]; + + auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]]; + auto r = + multiwayMerge([refInputRange(a0), refInputRange(a1), refInputRange(a2)]) + .chunkBy!((a, b) => a == b); + assert(r.equal!equal(expected)); + } + } + + // https://issues.dlang.org/show_bug.cgi?id=20496 + { + auto r = [1,1,1,2,2,2,3,3,3]; + r.chunkBy!((ref e1, ref e2) => e1 == e2); + } +} + + + +// https://issues.dlang.org/show_bug.cgi?id=13805 +@system unittest +{ + [""].map!((s) => s).chunkBy!((x, y) => true); +} + +/** +Splits a forward range into subranges in places determined by a binary +predicate. + +When iterating, one element of `r` is compared with `pred` to the next +element. If `pred` return true, a new subrange is started for the next element. +Otherwise, they are part of the same subrange. + +If the elements are compared with an inequality (!=) operator, consider +$(LREF chunkBy) instead, as it's likely faster to execute. + +Params: +pred = Predicate for determining where to split. The earlier element in the +source range is always given as the first argument. +r = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to be split. +Returns: a range of subranges of `r`, split such that within a given subrange, +calling `pred` with any pair of adjacent elements as arguments returns `false`. +Copying the range currently has reference semantics, but this may change in the future. + +See_also: +$(LREF splitter), which uses elements as splitters instead of element-to-element +relations. +*/ + +auto splitWhen(alias pred, Range)(Range r) +if (isForwardRange!Range) +{ import std.functional : not; + return ChunkByImpl!(not!pred, not!pred, GroupingOpType.binaryAny, Range)(r); +} + +//FIXME: these should be @safe +/// +nothrow pure @system unittest +{ + import std.algorithm.comparison : equal; + import std.range : dropExactly; + auto source = [4, 3, 2, 11, 0, -3, -3, 5, 3, 0]; + + auto result1 = source.splitWhen!((a,b) => a <= b); + assert(result1.save.equal!equal([ + [4, 3, 2], + [11, 0, -3], + [-3], + [5, 3, 0] + ])); + + //splitWhen, like chunkBy, is currently a reference range (this may change + //in future). Remember to call `save` when appropriate. + auto result2 = result1.dropExactly(2); + assert(result1.save.equal!equal([ + [-3], + [5, 3, 0] + ])); +} + +//ensure we don't iterate the underlying range twice +nothrow @system unittest +{ + import std.algorithm.comparison : equal; + import std.math.algebraic : abs; + + struct SomeRange + { + int[] elements; + static int popfrontsSoFar; + + auto front(){return elements[0];} + nothrow void popFront() + { popfrontsSoFar++; + elements = elements[1 .. $]; + } + auto empty(){return elements.length == 0;} + auto save(){return this;} + } + + auto result = SomeRange([10, 9, 8, 5, 0, 1, 0, 8, 11, 10, 8, 12]) + .splitWhen!((a, b) => abs(a - b) >= 3); + + assert(result.equal!equal([ + [10, 9, 8], + [5], + [0, 1, 0], + [8], + [11, 10, 8], + [12] + ])); + + assert(SomeRange.popfrontsSoFar == 12); } // Issue 13595 -version (none) // This requires support for non-equivalence relations @system unittest { import std.algorithm.comparison : equal; - auto r = [1, 2, 3, 4, 5, 6, 7, 8, 9].chunkBy!((x, y) => ((x*y) % 3) == 0); + auto r = [1, 2, 3, 4, 5, 6, 7, 8, 9].splitWhen!((x, y) => ((x*y) % 3) > 0); assert(r.equal!equal([ [1], [2, 3, 4], @@ -2107,10 +2863,25 @@ version (none) // This requires support for non-equivalence relations ])); } -// Issue 13805 -@system unittest +nothrow pure @system unittest { - [""].map!((s) => s).chunkBy!((x, y) => true); + // Grouping by maximum adjacent difference: + import std.math.algebraic : abs; + import std.algorithm.comparison : equal; + auto r3 = [1, 3, 2, 5, 4, 9, 10].splitWhen!((a, b) => abs(a-b) >= 3); + assert(r3.equal!equal([ + [1, 3, 2], + [5, 4], + [9, 10] + ])); +} + +// empty range splitWhen +@nogc nothrow pure @system unittest +{ + int[1] sliceable; + auto result = sliceable[0 .. 0].splitWhen!((a,b) => a+b > 10); + assert(result.empty); } // joiner @@ -2127,13 +2898,21 @@ Params: element(s) to serve as separators in the joined range. Returns: -A range of elements in the joined range. This will be a forward range if -both outer and inner ranges of $(D RoR) are forward ranges; otherwise it will -be only an input range. +A range of elements in the joined range. This will be a bidirectional range if +both outer and inner ranges of `RoR` are at least bidirectional ranges. Else if +both outer and inner ranges of `RoR` are forward ranges, the returned range will +be likewise. Otherwise it will be only an input range. The +$(REF_ALTTEXT range bidirectionality, isBidirectionalRange, std,range,primitives) +is propagated if no separator is specified. See_also: $(REF chain, std,range), which chains a sequence of ranges with compatible elements into a single range. + +Note: +When both outer and inner ranges of `RoR` are bidirectional and the joiner is +iterated from the back to the front, the separator will still be consumed from +front to back, even if it is a bidirectional range too. */ auto joiner(RoR, Separator)(RoR r, Separator sep) if (isInputRange!RoR && isInputRange!(ElementType!RoR) @@ -2144,18 +2923,78 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) { 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{ + bool inputStartsWithEmpty = false; + static if (isBidirectional) + { + private ElementType!RoR _currentBack; + bool inputEndsWithEmpty = false; + } + enum isBidirectional = isBidirectionalRange!RoR && + isBidirectionalRange!(ElementType!RoR); + static if (isRandomAccessRange!Separator) + { + static struct CurrentSep + { + private Separator _sep; + private size_t sepIndex; + private size_t sepLength; // cache the length for performance + auto front() { return _sep[sepIndex]; } + void popFront() { sepIndex++; } + auto empty() { return sepIndex >= sepLength; } + auto save() + { + auto copy = this; + copy._sep = _sep; + return copy; + } + void reset() + { + sepIndex = 0; + } + + void initialize(Separator sep) + { + _sep = sep; + sepIndex = sepLength = _sep.length; + } + } + } + else + { + static struct CurrentSep + { + private Separator _sep; + Separator payload; + + alias payload this; + + auto save() + { + auto copy = this; + copy._sep = _sep; + return copy; + } + + void reset() + { + payload = _sep.save; + } + + void initialize(Separator sep) + { + _sep = sep; + } + } + } + + private CurrentSep _currentSep; + static if (isBidirectional) + { + private CurrentSep _currentBackSep; + } + + private void setItem() + { if (!_items.empty) { // If we're exporting .save, we must not consume any of the @@ -2167,20 +3006,22 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) else _current = _items.front; } - }; + } private void useSeparator() { // Separator must always come after an item. - assert(_currentSep.empty && !_items.empty, - "joiner: internal error"); + assert(_currentSep.empty, + "Attempting to reset a non-empty separator"); + assert(!_items.empty, + "Attempting to use a separator in an empty joiner"); _items.popFront(); // If there are no more items, we're done, since separators are not // terminators. if (_items.empty) return; - if (_sep.empty) + if (_currentSep._sep.empty) { // Advance to the next range in the // input @@ -2189,41 +3030,29 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) _items.popFront(); if (_items.empty) return; } - mixin(setItem); + setItem; } else { - _currentSep = _sep.save; - assert(!_currentSep.empty); + _currentSep.reset; + assert(!_currentSep.empty, "separator must not be 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; + _currentSep.initialize(sep); + static if (isBidirectional) + _currentBackSep.initialize(sep); //mixin(useItem); // _current should be initialized in place if (_items.empty) + { _current = _current.init; // set invalid state + static if (isBidirectional) + _currentBack = _currentBack.init; + } else { // If we're exporting .save, we must not consume any of the @@ -2235,10 +3064,42 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) else _current = _items.front; + static if (isBidirectional) + { + _currentBack = _items.back.save; + + if (_currentBack.empty) + { + // No data in the currentBack item - toggle to use + // the separator + inputEndsWithEmpty = true; + } + } + if (_current.empty) { // No data in the current item - toggle to use the separator - useSeparator(); + inputStartsWithEmpty = true; + + // If RoR contains a single empty element, + // the returned Result will always be empty + import std.range : dropOne; + static if (hasLength!RoR) + { + if (_items.length == 1) + _items.popFront; + } + else static if (isForwardRange!RoR) + { + if (_items.save.dropOne.empty) + _items.popFront; + } + else + { + auto _itemsCopy = _items; + if (_itemsCopy.dropOne.empty) + _items.popFront; + } } } } @@ -2248,8 +3109,18 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) return _items.empty; } + //no data in the first item of the initial range - use the separator + private enum useSepIfFrontIsEmpty = q{ + if (inputStartsWithEmpty) + { + useSeparator(); + inputStartsWithEmpty = false; + } + }; + @property ElementType!(ElementType!RoR) front() { + mixin(useSepIfFrontIsEmpty); if (!_currentSep.empty) return _currentSep.front; assert(!_current.empty, "Attempting to fetch the front of an empty joiner."); return _current.front; @@ -2259,18 +3130,27 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) { assert(!_items.empty, "Attempting to popFront an empty joiner."); // Using separator? + mixin(useSepIfFrontIsEmpty); + if (!_currentSep.empty) { _currentSep.popFront(); - if (!_currentSep.empty) return; - mixin(useItem); + if (_currentSep.empty && !_items.empty) + { + setItem; + if (_current.empty) + { + // No data in the current item - toggle to use the separator + useSeparator(); + } + } } else { // we're using the range _current.popFront(); - if (!_current.empty) return; - useSeparator(); + if (_current.empty) + useSeparator(); } } @@ -2281,11 +3161,103 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) Result copy = this; copy._items = _items.save; copy._current = _current.save; - copy._sep = _sep.save; copy._currentSep = _currentSep.save; + static if (isBidirectional) + { + copy._currentBack = _currentBack; + copy._currentBackSep = _currentBackSep; + } return copy; } } + + static if (isBidirectional) + { + //no data in the last item of the initial range - use the separator + private enum useSepIfBackIsEmpty = q{ + if (inputEndsWithEmpty) + { + useBackSeparator; + inputEndsWithEmpty = false; + } + }; + + private void setBackItem() + { + if (!_items.empty) + { + _currentBack = _items.back.save; + } + } + + private void useBackSeparator() + { + // Separator must always come after an item. + assert(_currentBackSep.empty, + "Attempting to reset a non-empty separator"); + assert(!_items.empty, + "Attempting to use a separator in an empty joiner"); + _items.popBack(); + + // If there are no more items, we're done, since separators are not + // terminators. + if (_items.empty) return; + + if (_currentBackSep._sep.empty) + { + // Advance to the next range in the + // input + while (_items.back.empty) + { + _items.popBack(); + if (_items.empty) return; + } + setBackItem; + } + else + { + _currentBackSep.reset; + assert(!_currentBackSep.empty, "separator must not be empty"); + } + } + + @property ElementType!(ElementType!RoR) back() + { + mixin(useSepIfBackIsEmpty); + + if (!_currentBackSep.empty) return _currentBackSep.front; + assert(!_currentBack.empty, "Attempting to fetch the back of an empty joiner."); + return _currentBack.back; + } + + void popBack() + { + assert(!_items.empty, "Attempting to popBack an empty joiner."); + + mixin(useSepIfBackIsEmpty); + + if (!_currentBackSep.empty) + { + _currentBackSep.popFront(); + if (_currentBackSep.empty && !_items.empty) + { + setBackItem; + if (_currentBack.empty) + { + // No data in the current item - toggle to use the separator + useBackSeparator(); + } + } + } + else + { + // we're using the range + _currentBack.popBack(); + if (_currentBack.empty) + useBackSeparator(); + } + } + } } return Result(r, sep); } @@ -2305,6 +3277,12 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) assert(["", ""].joiner("xyz").equal("xyz")); } +@safe pure nothrow unittest +{ + //joiner with separator can return a bidirectional range + assert(isBidirectionalRange!(typeof(["abc", "def"].joiner("...")))); +} + @system unittest { import std.algorithm.comparison : equal; @@ -2320,7 +3298,7 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) import std.algorithm.comparison : equal; import std.range; - // Related to issue 8061 + // Related to https://issues.dlang.org/show_bug.cgi?id=8061 auto r = joiner([ inputRangeObject("abc"), inputRangeObject("def"), @@ -2357,7 +3335,7 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) assert(equal(u, "+-abc+-+-def+-")); - // Issue 13441: only(x) as separator + // https://issues.dlang.org/show_bug.cgi?id=13441: only(x) as separator string[][] lines = [null]; lines .joiner(only("b")) @@ -2417,6 +3395,174 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR) static assert(isForwardRange!(typeof(joiner([""], "")))); } +@safe pure unittest +{ + { + import std.algorithm.comparison : equal; + auto r = joiner(["abc", "def", "ghi"], "?!"); + char[] res; + while (!r.empty) + { + res ~= r.back; + r.popBack; + } + assert(res.equal("ihg?!fed?!cba")); + } + + { + wchar[] sep = ['Ș', 'Ț']; + auto r = joiner(["","abc",""],sep); + wchar[] resFront; + wchar[] resBack; + + auto rCopy = r.save; + while (!r.empty) + { + resFront ~= r.front; + r.popFront; + } + + while (!rCopy.empty) + { + resBack ~= rCopy.back; + rCopy.popBack; + } + + import std.algorithm.comparison : equal; + + assert(resFront.equal("ȘȚabcȘȚ")); + assert(resBack.equal("ȘȚcbaȘȚ")); + } + + { + import std.algorithm.comparison : equal; + auto r = [""]; + r.popBack; + assert(r.joiner("AB").equal("")); + } + + { + auto r = ["", "", "", "abc", ""].joiner("../"); + auto rCopy = r.save; + + char[] resFront; + char[] resBack; + + while (!r.empty) + { + resFront ~= r.front; + r.popFront; + } + + while (!rCopy.empty) + { + resBack ~= rCopy.back; + rCopy.popBack; + } + + import std.algorithm.comparison : equal; + + assert(resFront.equal("../../../abc../")); + assert(resBack.equal("../cba../../../")); + } + + { + auto r = ["", "abc", ""].joiner("./"); + auto rCopy = r.save; + r.popBack; + rCopy.popFront; + + auto rRev = r.save; + auto rCopyRev = rCopy.save; + + char[] r1, r2, r3, r4; + + while (!r.empty) + { + r1 ~= r.back; + r.popBack; + } + + while (!rCopy.empty) + { + r2 ~= rCopy.front; + rCopy.popFront; + } + + while (!rRev.empty) + { + r3 ~= rRev.front; + rRev.popFront; + } + + while (!rCopyRev.empty) + { + r4 ~= rCopyRev.back; + rCopyRev.popBack; + } + + import std.algorithm.comparison : equal; + + assert(r1.equal("/cba./")); + assert(r2.equal("/abc./")); + assert(r3.equal("./abc")); + assert(r4.equal("./cba")); + } +} + +@system unittest +{ + import std.range; + import std.algorithm.comparison : equal; + + assert(inputRangeObject([""]).joiner("lz").equal("")); +} + +@safe pure unittest +{ + struct inputRangeStrings + { + private string[] strings; + + string front() + { + return strings[0]; + } + + void popFront() + { + strings = strings[1..$]; + } + + bool empty() const + { + return strings.length == 0; + } + } + + auto arr = inputRangeStrings([""]); + + import std.algorithm.comparison : equal; + + assert(arr.joiner("./").equal("")); +} + +@safe pure unittest +{ + auto r = joiner(["", "", "abc", "", ""], ""); + char[] res; + while (!r.empty) + { + res ~= r.back; + r.popBack; + } + + import std.algorithm.comparison : equal; + + assert(res.equal("cba")); +} + + /// Ditto auto joiner(RoR)(RoR r) if (isInputRange!RoR && isInputRange!(ElementType!RoR)) @@ -2426,50 +3572,33 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) 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; - }; + enum isBidirectional = isForwardRange!RoR && isForwardRange!(ElementType!RoR) && + isBidirectionalRange!RoR && isBidirectionalRange!(ElementType!RoR); + static if (isBidirectional) + { + ElementType!RoR _currentBack; + bool reachedFinalElement; + } + this(RoR items, ElementType!RoR current) { _items = items; _current = current; + static if (isBidirectional && hasNested!Result) + _currentBack = typeof(_currentBack).init; } + 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 (isBidirectional && hasNested!Result) + _currentBack = typeof(_currentBack).init; + // field _current must be initialized in constructor, because it is nested struct + mixin(popFrontEmptyElements); + static if (isBidirectional) + mixin(popBackEmptyElements); } static if (isInfinite!RoR) { @@ -2493,16 +3622,45 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) _current.popFront(); if (_current.empty) { - assert(!_items.empty); + assert(!_items.empty, "Attempting to popFront an empty joiner."); _items.popFront(); - mixin(prepare); + mixin(popFrontEmptyElements); } } + + private enum popFrontEmptyElements = q{ + // Skip over empty subranges. + while (!_items.empty && _items.front.empty) + { + _items.popFront(); + } + if (!_items.empty) + { + // 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; + } + else + { + _current = typeof(_current).init; + } + }; + static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR)) { @property auto save() { - return Result(_items.save, _current.save); + auto r = Result(_items.save, _current.save); + static if (isBidirectional) + { + r._currentBack = _currentBack.save; + r.reachedFinalElement = reachedFinalElement; + } + return r; } } @@ -2520,28 +3678,134 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) _current.front = element; } } + + static if (isBidirectional) + { + bool checkFinalElement() + { + import std.range : dropOne; + + if (reachedFinalElement) + return true; + + static if (hasLength!(typeof(_items))) + { + if (_items.length == 1) + reachedFinalElement = true; + } + else + { + if (_items.save.dropOne.empty) + reachedFinalElement = true; + } + + return false; + } + + @property auto ref back() + { + assert(!empty, "Attempting to fetch the back of an empty joiner."); + if (reachedFinalElement) + return _current.back; + else + return _currentBack.back; + } + + void popBack() + { + assert(!_current.empty, "Attempting to popBack an empty joiner."); + if (checkFinalElement) + _current.popBack(); + else + _currentBack.popBack(); + + bool isEmpty = reachedFinalElement ? _current.empty : _currentBack.empty; + if (isEmpty) + { + assert(!_items.empty, "Attempting to popBack an empty joiner."); + _items.popBack(); + mixin(popBackEmptyElements); + } + } + + private enum popBackEmptyElements = q{ + // Skip over empty subranges. + while (!_items.empty && _items.back.empty) + { + _items.popBack(); + } + if (!_items.empty) + { + checkFinalElement; + // 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)) + { + if (reachedFinalElement) + _current = _items.back.save; + else + _currentBack = _items.back.save; + } + else + { + if (reachedFinalElement) + _current = _items.back; + else + _currentBack = _items.back; + } + } + else + { + _current = typeof(_current).init; + _currentBack = typeof(_currentBack).init; + } + }; + + static if (hasAssignableElements!(ElementType!RoR)) + { + @property void back(ElementType!(ElementType!RoR) element) + { + assert(!empty, "Attempting to assign to back of an empty joiner."); + if (reachedFinalElement) + _current.back = element; + else + _currentBack.back = element; + } + + @property void back(ref ElementType!(ElementType!RoR) element) + { + assert(!empty, "Attempting to assign to back of an empty joiner."); + if (reachedFinalElement) + _current.back = element; + else + _currentBack.back = element; + } + } + } } return Result(r); } +/// @safe unittest { import std.algorithm.comparison : equal; - import std.range.interfaces : inputRangeObject; import std.range : repeat; - 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(repeat("abc", 3)), "abcabcabc")); - - // joiner allows in-place mutation! + assert([""].joiner.equal("")); + assert(["", ""].joiner.equal("")); + assert(["", "abc"].joiner.equal("abc")); + assert(["abc", ""].joiner.equal("abc")); + assert(["abc", "def"].joiner.equal("abcdef")); + assert(["Mary", "has", "a", "little", "lamb"].joiner.equal("Maryhasalittlelamb")); + assert("abc".repeat(3).joiner.equal("abcabcabc")); +} + +/// joiner allows in-place mutation! +@safe unittest +{ + import std.algorithm.comparison : equal; auto a = [ [1, 2, 3], [42, 43] ]; auto j = joiner(a); j.front = 44; @@ -2549,16 +3813,61 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) assert(equal(j, [44, 2, 3, 42, 43])); } +/// insert characters fully lazily into a string +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.range : chain, cycle, iota, only, retro, take, zip; + import std.format : format; + + static immutable number = "12345678"; + static immutable delimiter = ","; + auto formatted = number.retro + .zip(3.iota.cycle.take(number.length)) + .map!(z => chain(z[0].only, z[1] == 2 ? delimiter : null)) + .joiner + .retro; + static immutable expected = "12,345,678"; + assert(formatted.equal(expected)); +} + +@safe unittest +{ + import std.range.interfaces : inputRangeObject; + static assert(isInputRange!(typeof(joiner([""])))); + static assert(isForwardRange!(typeof(joiner([""])))); +} + +@safe unittest +{ + // Initial version of PR #6115 caused a compilation failure for + // https://github.com/BlackEdder/ggplotd/blob/d4428c08db5ffdc05dfd29690bf7da9073ea1dc5/source/ggplotd/stat.d#L562-L583 + import std.range : zip; + int[] xCoords = [1, 2, 3]; + int[] yCoords = [4, 5, 6]; + auto coords = zip(xCoords, xCoords[1..$]).map!( (xr) { + return zip(yCoords, yCoords[1..$]).map!( (yr) { + return [ + [[xr[0], xr[0], xr[1]], + [yr[0], yr[1], yr[1]]], + [[xr[0], xr[1], xr[1]], + [yr[0], yr[0], yr[1]]] + ]; + }).joiner; + }).joiner; +} @system unittest { import std.algorithm.comparison : equal; import std.range.interfaces : inputRangeObject; + import std.range : retro; - // bugzilla 8240 + // https://issues.dlang.org/show_bug.cgi?id=8240 assert(equal(joiner([inputRangeObject("")]), "")); + assert(equal(joiner([inputRangeObject("")]).retro, "")); - // issue 8792 + // https://issues.dlang.org/show_bug.cgi?id=8792 auto b = [[1], [2], [3]]; auto jb = joiner(b); auto js = jb.save; @@ -2573,6 +3882,118 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) assert(!equal(js2, js)); } +// https://issues.dlang.org/show_bug.cgi?id=19213 +@system unittest +{ + auto results = [[1,2], [3,4]].map!(q => q.chunkBy!"a").joiner; + int i = 1; + foreach (ref e; results) + assert(e[0] == i++); +} + +/// joiner can be bidirectional +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + + auto a = [[1, 2, 3], [4, 5]]; + auto j = a.joiner; + j.back = 44; + assert(a == [[1, 2, 3], [4, 44]]); + assert(equal(j.retro, [44, 4, 3, 2, 1])); +} + +// bidirectional joiner: test for filtering empty elements +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + + alias El = (e) => new int(e); + auto a = [null, [null, El(1), null, El(2), null, El(3), null], null, [null, El(4), null, El(5), null]]; + auto j = a.joiner; + + alias deref = a => a is null ? -1 : *a; + auto expected = [-1, 5, -1, 4, -1, -1, 3, -1, 2, -1, 1, -1]; + // works with .save. + assert(j.save.retro.map!deref.equal(expected)); + // and without .save + assert(j.retro.map!deref.equal(expected)); + assert(j.retro.map!deref.equal(expected)); +} + +// bidirectional joiner is @nogc +@safe @nogc unittest +{ + import std.algorithm.comparison : equal; + import std.range : iota, only, retro; + + auto a = only(iota(1, 4), iota(4, 6)); + auto j = a.joiner; + static immutable expected = [5 , 4, 3, 2, 1]; + assert(equal(j.retro, expected)); +} + +// bidirectional joiner supports assignment to the back +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : popBackN; + + auto a = [[1, 2, 3], [4, 5]]; + auto j = a.joiner; + j.back = 55; + assert(a == [[1, 2, 3], [4, 55]]); + j.popBackN(2); + j.back = 33; + assert(a == [[1, 2, 33], [4, 55]]); +} + +// bidirectional joiner works with auto-decoding +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + + auto a = ["😀😐", "😠"]; + auto j = a.joiner; + assert(j.retro.equal("😠😐😀")); +} + +// test two-side iteration +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : popBackN; + + auto arrs = [ + [[1], [2], [3], [4], [5]], + [[1], [2, 3, 4], [5]], + [[1, 2, 3, 4, 5]], + ]; + foreach (arr; arrs) + { + auto a = arr.joiner; + assert(a.front == 1); + assert(a.back == 5); + a.popFront; + assert(a.front == 2); + assert(a.back == 5); + a.popBack; + assert(a.front == 2); + assert(a.back == 4); + a.popFront; + assert(a.front == 3); + assert(a.back == 4); + a.popBack; + assert(a.front == 3); + assert(a.back == 3); + a.popBack; + assert(a.empty); + } +} + @safe unittest { import std.algorithm.comparison : equal; @@ -2667,7 +4088,7 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) to!string("Unexpected result: '%s'"d.algoFormat(result))); } -// Issue 8061 +// https://issues.dlang.org/show_bug.cgi?id=8061 @system unittest { import std.conv : to; @@ -2692,17 +4113,23 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) { return element; } + alias back = front; enum empty = false; - void popFront() + auto save() { + return this; } + void popFront() {} + alias popBack = popFront; + @property void front(int newValue) { element = newValue; } + alias back = front; } static assert(isInputRange!AssignableRange); @@ -2712,34 +4139,53 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)) auto range = new AssignableRange(); assert(range.element == 0); + { + auto joined = joiner(repeat(range)); + joined.front = 5; + assert(range.element == 5); + assert(joined.front == 5); + + joined.popFront; + int byRef = 7; + joined.front = byRef; + assert(range.element == byRef); + assert(joined.front == byRef); + } + { + auto joined = joiner(repeat(range)); + joined.back = 5; + assert(range.element == 5); + assert(joined.back == 5); - auto joined = joiner(repeat(range)); - joined.front = 5; - assert(range.element == 5); - assert(joined.front == 5); + joined.popBack; + int byRef = 7; + joined.back = byRef; + assert(range.element == byRef); + assert(joined.back == byRef); + } +} - joined.popFront; - int byRef = 7; - joined.front = byRef; - assert(range.element == byRef); - assert(joined.front == byRef); +// https://issues.dlang.org/show_bug.cgi?id=19850 +@safe pure unittest +{ + assert([[0]].joiner.save.back == 0); } /++ -Implements the homonym function (also known as $(D accumulate), $(D -compress), $(D inject), or $(D foldl)) present in various programming +Implements the homonym function (also known as `accumulate`, $(D +compress), `inject`, or `foldl`) present in various programming languages of functional flavor. There is also $(LREF fold) which does the same thing but with the opposite parameter order. -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)) +The call `reduce!(fun)(seed, range)` first assigns `seed` to +an internal variable `result`, also called the accumulator. +Then, for each element `x` in `range`, `result = fun(result, x)` +gets evaluated. Finally, `result` is returned. +The one-argument version `reduce!(fun)(range)` works similarly, but it uses the first element of the range as the seed (the range must be non-empty). Returns: - the accumulated $(D result) + the accumulated `result` Params: fun = one or more functions @@ -2747,11 +4193,11 @@ Params: See_Also: $(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) - $(LREF fold) is functionally equivalent to $(LREF reduce) with the argument order reversed, - and without the need to use $(LREF tuple) for multiple seeds. This makes it easier - to use in UFCS chains. + $(LREF fold) is functionally equivalent to $(LREF _reduce) with the argument + order reversed, and without the need to use $(REF_ALTTEXT `tuple`,tuple,std,typecons) + for multiple seeds. This makes it easier to use in UFCS chains. - $(LREF sum) is similar to $(D reduce!((a, b) => a + b)) that offers + $(LREF sum) is similar to `reduce!((a, b) => a + b)` that offers pairwise summing of floating point numbers. +/ template reduce(fun...) @@ -2764,23 +4210,23 @@ 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. + No-seed version. The first element of `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. + For each function `f` in `fun`, the corresponding + seed type `S` is `Unqual!(typeof(f(e, e)))`, where `e` is an + element of `r`: `ElementType!R` for ranges, + and `ForeachType!R` otherwise. - Once S has been determined, then $(D S s = e;) and $(D s = f(s, e);) + Once S has been determined, then `S s = e;` and `s = f(s, e);` must both be legal. - If $(D r) is empty, an $(D Exception) is thrown. - Params: - r = an iterable value as defined by $(D isIterable) + r = an iterable value as defined by `isIterable` Returns: the final result of the accumulator applied to the iterable + + Throws: `Exception` if `r` is empty +/ auto reduce(R)(R r) if (isIterable!R) @@ -2791,7 +4237,13 @@ if (fun.length >= 1) static if (isInputRange!R) { - enforce(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); + // no need to throw if range is statically known to be non-empty + static if (!__traits(compiles, + { + static assert(r.length > 0); + })) + enforce(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); + Args result = r.front; r.popFront(); return reduceImpl!false(r, result); @@ -2804,19 +4256,19 @@ if (fun.length >= 1) } /++ - 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 $(REF Tuple, std,typecons), with one field per function in $(D f). + Seed version. The seed should be a single value if `fun` is a + single function. If `fun` is multiple functions, then `seed` + should be a $(REF Tuple, std,typecons), with one field per function in `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). + `reduce` will operate on an unqualified copy. If this happens + then the returned type will not perfectly match `S`. - Use $(D fold) instead of $(D reduce) to use the seed version in a UFCS chain. + Use `fold` instead of `reduce` to use the seed version in a UFCS chain. Params: seed = the initial value of the accumulator - r = an iterable value as defined by $(D isIterable) + r = an iterable value as defined by `isIterable` Returns: the final result of the accumulator applied to the iterable @@ -2853,7 +4305,7 @@ if (fun.length >= 1) alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); static if (mustInitialize) bool initialized = false; - foreach (/+auto ref+/ E e; r) // @@@4707@@@ + foreach (/+auto ref+/ E e; r) // https://issues.dlang.org/show_bug.cgi?id=4707 { foreach (i, f; binfuns) { @@ -2869,7 +4321,7 @@ if (fun.length >= 1) static if (mustInitialize) if (initialized == false) { - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; foreach (i, f; binfuns) emplaceRef!(Args[i])(args[i], e); initialized = true; @@ -2880,8 +4332,15 @@ if (fun.length >= 1) 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."); + // no need to throw if range is statically known to be non-empty + static if (!__traits(compiles, + { + static assert(r.length > 0); + })) + { + if (!initialized) + throw new Exception("Cannot reduce an empty iterable w/o an explicit seed value."); + } static if (Args.length == 1) return args[0]; @@ -2891,14 +4350,14 @@ if (fun.length >= 1) } /** -Many aggregate range operations turn out to be solved with $(D reduce) -quickly and easily. The example below illustrates $(D reduce)'s +Many aggregate range operations turn out to be solved with `reduce` +quickly and easily. The example below illustrates `reduce`'s remarkable power and flexibility. */ @safe unittest { import std.algorithm.comparison : max, min; - import std.math : approxEqual; + import std.math.operations : isClose; import std.range; int[] arr = [ 1, 2, 3, 4, 5 ]; @@ -2935,38 +4394,39 @@ remarkable power and flexibility. // 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)); + assert(isClose(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)); + assert(isClose(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 +is shared. That's why `reduce` accepts multiple functions. +If two or more functions are passed, `reduce` returns a $(REF Tuple, std,typecons) object with one member per passed-in function. The number of seeds must be correspondingly increased. */ @safe unittest { import std.algorithm.comparison : max, min; - import std.math : approxEqual, sqrt; + import std.math.operations : isClose; + import std.math.algebraic : sqrt; import std.typecons : tuple, Tuple; 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 + assert(isClose(r[0], 2)); // minimum + assert(isClose(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 + assert(isClose(r[0], 35)); // sum + assert(isClose(r[1], 233)); // sum of squares // Compute average and standard deviation from the above auto avg = r[0] / a.length; assert(avg == 5); @@ -3003,7 +4463,7 @@ The number of seeds must be correspondingly increased. assert(rep[2 .. $] == "1, 2, 3, 4, 5", "["~rep[2 .. $]~"]"); } -@system unittest +@safe unittest { import std.algorithm.comparison : max, min; import std.exception : assertThrown; @@ -3015,7 +4475,7 @@ The number of seeds must be correspondingly increased. { bool actEmpty; - int opApply(scope int delegate(ref int) dg) + int opApply(scope int delegate(ref int) @safe dg) { int res; if (actEmpty) return res; @@ -3055,7 +4515,8 @@ The number of seeds must be correspondingly increased. @safe unittest { - // Issue #10408 - Two-function reduce of a const array. + // https://issues.dlang.org/show_bug.cgi?id=10408 + // Two-function reduce of a const array. import std.algorithm.comparison : max, min; import std.typecons : tuple, Tuple; @@ -3068,7 +4529,7 @@ The number of seeds must be correspondingly increased. @safe unittest { - //10709 + // https://issues.dlang.org/show_bug.cgi?id=10709 import std.typecons : tuple, Tuple; enum foo = "a + 0.5 * b"; @@ -3079,11 +4540,11 @@ The number of seeds must be correspondingly increased. assert(r2 == tuple(3, 3)); } -@system unittest +@safe unittest { static struct OpApply { - int opApply(int delegate(ref int) dg) + int opApply(int delegate(ref int) @safe dg) { int[] a = [1, 2, 3]; @@ -3135,7 +4596,8 @@ The number of seeds must be correspondingly increased. assert(minmaxElement([1, 2, 3]) == tuple(1, 3)); } -@safe unittest //12569 +// https://issues.dlang.org/show_bug.cgi?id=12569 +@safe unittest { import std.algorithm.comparison : max, min; import std.typecons : tuple; @@ -3156,13 +4618,27 @@ The number of seeds must be correspondingly increased. static assert(!is(typeof(reduce!(all, all)(tuple(1, 1), "hello")))); } -@safe unittest //13304 +// https://issues.dlang.org/show_bug.cgi?id=13304 +@safe unittest { int[] data; static assert(is(typeof(reduce!((a, b) => a + b)(data)))); assert(data.length == 0); } +// https://issues.dlang.org/show_bug.cgi?id=13880 +// reduce shouldn't throw if the length is statically known +pure nothrow @safe @nogc unittest +{ + import std.algorithm.comparison : min; + int[5] arr; + arr[2] = -1; + assert(arr.reduce!min == -1); + + int[0] arr0; + assert(reduce!min(42, arr0) == 42); +} + //Helper for Reduce private template ReduceSeedType(E) { @@ -3187,31 +4663,39 @@ private template ReduceSeedType(E) /++ -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 fold!(fun)(range, seed)) -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 fold!(fun)(range)) +Implements the homonym function (also known as `accumulate`, $(D +compress), `inject`, or `foldl`) present in various programming +languages of functional flavor. The call `fold!(fun)(range, seed)` +first assigns `seed` to an internal variable `result`, +also called the accumulator. Then, for each element `x` in $(D +range), `result = fun(result, x)` gets evaluated. Finally, $(D +result) is returned. The one-argument version `fold!(fun)(range)` works similarly, but it uses the first element of the range as the seed (the range must be non-empty). -Returns: - the accumulated $(D result) +Params: + fun = the predicate function(s) to apply to the elements See_Also: $(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) - $(LREF sum) is similar to $(D fold!((a, b) => a + b)) that offers + $(LREF sum) is similar to `fold!((a, b) => a + b)` that offers precise summing of floating point numbers. - This is functionally equivalent to $(LREF reduce) with the argument order reversed, - and without the need to use $(LREF tuple) for multiple seeds. + This is functionally equivalent to $(LREF reduce) with the argument order + reversed, and without the need to use $(REF_ALTTEXT `tuple`,tuple,std,typecons) + for multiple seeds. +/ template fold(fun...) if (fun.length >= 1) { + /** + Params: + r = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to fold + seed = the initial value of the accumulator + Returns: + the accumulated `result` + */ auto fold(R, S...)(R r, S seed) { static if (S.length < 2) @@ -3265,12 +4749,12 @@ if (fun.length >= 1) /++ Similar to `fold`, but returns a range containing the successive reduced values. -The call $(D cumulativeFold!(fun)(range, seed)) first assigns `seed` to an +The call `cumulativeFold!(fun)(range, seed)` first assigns `seed` to an internal variable `result`, also called the accumulator. -The returned range contains the values $(D result = fun(result, x)) lazily +The returned range contains the values `result = fun(result, x)` lazily evaluated for each element `x` in `range`. Finally, the last element has the -same value as $(D fold!(fun)(seed, range)). -The one-argument version $(D cumulativeFold!(fun)(range)) works similarly, but +same value as `fold!(fun)(seed, range)`. +The one-argument version `cumulativeFold!(fun)(range)` works similarly, but it returns the first element unchanged and uses it as seed for the next elements. This function is also known as @@ -3289,6 +4773,11 @@ Returns: See_Also: $(HTTP en.wikipedia.org/wiki/Prefix_sum, Prefix Sum) + +Note: + + In functional programming languages this is typically called `scan`, `scanl`, + `scanLeft` or `reductions`. +/ template cumulativeFold(fun...) if (fun.length >= 1) @@ -3299,9 +4788,9 @@ if (fun.length >= 1) /++ No-seed version. The first element of `r` is used as the seed's value. For each function `f` in `fun`, the corresponding seed type `S` is - $(D Unqual!(typeof(f(e, e)))), where `e` is an element of `r`: + `Unqual!(typeof(f(e, e)))`, where `e` is an element of `r`: `ElementType!R`. - Once `S` has been determined, then $(D S s = e;) and $(D s = f(s, e);) must + Once `S` has been determined, then `S s = e;` and `s = f(s, e);` must both be legal. Params: @@ -3422,13 +4911,7 @@ if (fun.length >= 1) } } - static if (hasLength!R) - { - @property size_t length() - { - return source.length; - } - } + mixin ImplementLength!source; } return Result(range, args); @@ -3440,7 +4923,7 @@ if (fun.length >= 1) { import std.algorithm.comparison : max, min; import std.array : array; - import std.math : approxEqual; + import std.math.operations : isClose; import std.range : chain; int[] arr = [1, 2, 3, 4, 5]; @@ -3477,11 +4960,11 @@ if (fun.length >= 1) // Mixing convertible types is fair game, too double[] c = [2.5, 3.0]; auto r1 = cumulativeFold!"a + b"(chain(a, b, c)); - assert(approxEqual(r1, [3, 7, 107, 109.5, 112.5])); + assert(isClose(r1, [3, 7, 107, 109.5, 112.5])); // To minimize nesting of parentheses, Uniform Function Call Syntax can be used auto r2 = chain(a, b, c).cumulativeFold!"a + b"; - assert(approxEqual(r2, [3, 7, 107, 109.5, 112.5])); + assert(isClose(r2, [3, 7, 107, 109.5, 112.5])); } /** @@ -3496,20 +4979,20 @@ The number of seeds must be correspondingly increased. { import std.algorithm.comparison : max, min; import std.algorithm.iteration : map; - import std.math : approxEqual; + import std.math.operations : isClose; import std.typecons : tuple; double[] a = [3.0, 4, 7, 11, 3, 2, 5]; // Compute minimum and maximum in one pass auto r = a.cumulativeFold!(min, max); // The type of r is Tuple!(int, int) - assert(approxEqual(r.map!"a[0]", [3, 3, 3, 3, 3, 2, 2])); // minimum - assert(approxEqual(r.map!"a[1]", [3, 4, 7, 11, 11, 11, 11])); // maximum + assert(isClose(r.map!"a[0]", [3, 3, 3, 3, 3, 2, 2])); // minimum + assert(isClose(r.map!"a[1]", [3, 4, 7, 11, 11, 11, 11])); // maximum // Compute sum and sum of squares in one pass auto r2 = a.cumulativeFold!("a + b", "a + b * b")(tuple(0.0, 0.0)); - assert(approxEqual(r2.map!"a[0]", [3, 7, 14, 25, 28, 30, 35])); // sum - assert(approxEqual(r2.map!"a[1]", [9, 25, 74, 195, 204, 208, 233])); // sum of squares + assert(isClose(r2.map!"a[0]", [3, 7, 14, 25, 28, 30, 35])); // sum + assert(isClose(r2.map!"a[1]", [9, 25, 74, 195, 204, 208, 233])); // sum of squares } @safe unittest @@ -3551,7 +5034,7 @@ The number of seeds must be correspondingly increased. { import std.algorithm.comparison : max, min; import std.array : array; - import std.math : approxEqual; + import std.math.operations : isClose; import std.typecons : tuple; const float a = 0.0; @@ -3559,10 +5042,10 @@ The number of seeds must be correspondingly increased. float[] c = [1.2, 3, 3.3]; auto r = cumulativeFold!"a + b"(b, a); - assert(approxEqual(r, [1.2, 4.2, 7.5])); + assert(isClose(r, [1.2, 4.2, 7.5])); auto r2 = cumulativeFold!"a + b"(c, a); - assert(approxEqual(r2, [1.2, 4.2, 7.5])); + assert(isClose(r2, [1.2, 4.2, 7.5])); const numbers = [10, 30, 20]; enum m = numbers.cumulativeFold!(min).array; @@ -3573,16 +5056,16 @@ The number of seeds must be correspondingly increased. @safe unittest { - import std.math : approxEqual; + import std.math.operations : isClose; import std.typecons : tuple; enum foo = "a + 0.5 * b"; auto r = [0, 1, 2, 3]; auto r1 = r.cumulativeFold!foo; auto r2 = r.cumulativeFold!(foo, foo); - assert(approxEqual(r1, [0, 0.5, 1.5, 3])); - assert(approxEqual(r2.map!"a[0]", [0, 0.5, 1.5, 3])); - assert(approxEqual(r2.map!"a[1]", [0, 0.5, 1.5, 3])); + assert(isClose(r1, [0, 0.5, 1.5, 3])); + assert(isClose(r2.map!"a[0]", [0, 0.5, 1.5, 3])); + assert(isClose(r2.map!"a[1]", [0, 0.5, 1.5, 3])); } @safe unittest @@ -3602,7 +5085,8 @@ The number of seeds must be correspondingly increased. assert(minmaxElement([1, 2, 3]).equal([tuple(1, 1), tuple(1, 2), tuple(1, 3)])); } -@safe unittest //12569 +// https://issues.dlang.org/show_bug.cgi?id=12569 +@safe unittest { import std.algorithm.comparison : equal, max, min; import std.typecons : tuple; @@ -3625,7 +5109,8 @@ The number of seeds must be correspondingly increased. static assert(!__traits(compiles, cumulativeFold!(all, all)("hello", tuple(1, 1)))); } -@safe unittest //13304 +// https://issues.dlang.org/show_bug.cgi?id=13304 +@safe unittest { int[] data; assert(data.cumulativeFold!((a, b) => a + b).empty); @@ -3652,55 +5137,66 @@ The number of seeds must be correspondingly increased. // splitter /** -Lazily 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. +Lazily splits a range using an element or range as a separator. +Separator ranges can be any narrow string type or sliceable range type. 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 +the split range. Use `filter!(a => !a.empty)` on the result to compress empty elements. -The predicate is passed to $(REF binaryFun, std,functional), and can either accept -a string, or any callable that can be executed via $(D pred(element, s)). +The predicate is passed to $(REF binaryFun, std,functional) and accepts +any callable function that can be executed via `pred(element, s)`. -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. +Notes: + If splitting a string on whitespace and token compression is desired, + consider using `splitter` without specifying a separator. -If splitting a string on whitespace and token compression is desired, -consider using $(D splitter) without specifying a separator (see fourth overload -below). + If no separator is passed, the $(REF_ALTTEXT, unary, unaryFun, std,functional) + predicate `isTerminator` decides whether to accept an element of `r`. Params: pred = The predicate for comparing each element with the separator, - defaulting to $(D "a == b"). + defaulting to `"a == b"`. r = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to be - split. Must support slicing and $(D .length). - s = The element to be treated as the separator between range segments to be - split. + split. Must support slicing and `.length` or be a narrow string type. + s = The element (or range) to be treated as the separator + between range segments to be split. + isTerminator = The predicate for deciding where to split the range when no separator is passed + keepSeparators = The flag for deciding if the separators are kept Constraints: - The predicate $(D pred) needs to accept an element of $(D r) and the - separator $(D s). + The predicate `pred` needs to accept an element of `r` and the + separator `s`. Returns: - An input range of the subranges of elements between separators. If $(D r) + An input range of the subranges of elements between separators. If `r` is a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) or $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives), the returned range will be likewise. + When a range is used a separator, bidirectionality isn't possible. + + If keepSeparators is equal to Yes.keepSeparators the output will also contain the + separators. + + If an empty range is given, the result is an empty range. If a range with + one separator is given, the result is a range with two empty elements. See_Also: - $(REF _splitter, std,regex) for a version that splits using a regular -expression defined separator. + $(REF _splitter, std,regex) for a version that splits using a regular expression defined separator, + $(REF _split, std,array) for a version that splits eagerly and + $(LREF splitWhen), which compares adjacent elements instead of element against separator. */ -auto splitter(alias pred = "a == b", Range, Separator)(Range r, Separator s) +auto splitter(alias pred = "a == b", + Flag!"keepSeparators" keepSeparators = No.keepSeparators, + Range, + Separator)(Range r, Separator s) if (is(typeof(binaryFun!pred(r.front, s)) : bool) && ((hasSlicing!Range && hasLength!Range) || isNarrowString!Range)) { import std.algorithm.searching : find; import std.conv : unsigned; - static struct Result + struct Result { private: Range _input; @@ -3719,9 +5215,14 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) enum _separatorLength = 1; } + static if (keepSeparators) + { + bool _wasSeparator = true; + } + static if (isBidirectionalRange!Range) { - static size_t lastIndexOf(Range haystack, Separator needle) + size_t lastIndexOf(Range haystack, Separator needle) { import std.range : retro; auto r = haystack.retro().find!pred(needle); @@ -3760,10 +5261,27 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) @property Range front() { assert(!empty, "Attempting to fetch the front of an empty splitter."); - if (_frontLength == _unComputed) + static if (keepSeparators) { - auto r = _input.find!pred(_separator); - _frontLength = _input.length - r.length; + if (!_wasSeparator) + { + _frontLength = _separatorLength; + _wasSeparator = true; + } + else if (_frontLength == _unComputed) + { + auto r = _input.find!pred(_separator); + _frontLength = _input.length - r.length; + _wasSeparator = false; + } + } + else + { + if (_frontLength == _unComputed) + { + auto r = _input.find!pred(_separator); + _frontLength = _input.length - r.length; + } } return _input[0 .. _frontLength]; } @@ -3775,19 +5293,37 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) { front; } - assert(_frontLength <= _input.length); - if (_frontLength == _input.length) + assert(_frontLength <= _input.length, "The front position must" + ~ " not exceed the input.length"); + static if (keepSeparators) { - // no more input and need to fetch => done - _frontLength = _atEnd; + if (_frontLength == _input.length && !_wasSeparator) + { + _frontLength = _atEnd; - // Probably don't need this, but just for consistency: - _backLength = _atEnd; + _backLength = _atEnd; + } + else + { + _input = _input[_frontLength .. _input.length]; + _frontLength = _unComputed; + } } else { - _input = _input[_frontLength + _separatorLength .. _input.length]; - _frontLength = _unComputed; + 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; + } } } @@ -3806,16 +5342,40 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) @property Range back() { assert(!empty, "Attempting to fetch the back of an empty splitter."); - if (_backLength == _unComputed) + static if (keepSeparators) { - immutable lastIndex = lastIndexOf(_input, _separator); - if (lastIndex == -1) + if (!_wasSeparator) { - _backLength = _input.length; + _backLength = _separatorLength; + _wasSeparator = true; } - else + else if (_backLength == _unComputed) + { + immutable lastIndex = lastIndexOf(_input, _separator); + if (lastIndex == -1) + { + _backLength = _input.length; + } + else + { + _backLength = _input.length - lastIndex - 1; + } + _wasSeparator = false; + } + } + else + { + if (_backLength == _unComputed) { - _backLength = _input.length - lastIndex - 1; + immutable lastIndex = lastIndexOf(_input, _separator); + if (lastIndex == -1) + { + _backLength = _input.length; + } + else + { + _backLength = _input.length - lastIndex - 1; + } } } return _input[_input.length - _backLength .. _input.length]; @@ -3829,17 +5389,34 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) // evaluate back to make sure it's computed back; } - assert(_backLength <= _input.length); - if (_backLength == _input.length) + assert(_backLength <= _input.length, "The end index must not" + ~ " exceed the length of the input"); + static if (keepSeparators) { - // no more input and need to fetch => done - _frontLength = _atEnd; - _backLength = _atEnd; + if (_backLength == _input.length && !_wasSeparator) + { + _frontLength = _atEnd; + _backLength = _atEnd; + } + else + { + _input = _input[0 .. _input.length - _backLength]; + _backLength = _unComputed; + } } else { - _input = _input[0 .. _input.length - _backLength - _separatorLength]; - _backLength = _unComputed; + 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; + } } } } @@ -3848,21 +5425,239 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) return Result(r, s); } -/// +/// Basic splitting with characters and numbers. @safe unittest { import std.algorithm.comparison : equal; - assert(equal(splitter("hello world", ' '), [ "hello", "", "world" ])); + assert("a|bc|def".splitter('|').equal([ "a", "bc", "def" ])); + + int[] a = [1, 0, 2, 3, 0, 4, 5, 6]; + int[][] w = [ [1], [2, 3], [4, 5, 6] ]; + assert(a.splitter(0).equal(w)); +} + +/// Basic splitting with characters and numbers and keeping sentinels. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + + assert("a|bc|def".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "a", "|", "bc", "|", "def" ])); + + int[] a = [1, 0, 2, 3, 0, 4, 5, 6]; + int[][] w = [ [1], [0], [2, 3], [0], [4, 5, 6] ]; + assert(a.splitter!("a == b", Yes.keepSeparators)(0).equal(w)); +} + +/// Adjacent separators. +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert("|ab|".splitter('|').equal([ "", "ab", "" ])); + assert("ab".splitter('|').equal([ "ab" ])); + + assert("a|b||c".splitter('|').equal([ "a", "b", "", "c" ])); + assert("hello world".splitter(' ').equal([ "hello", "", "world" ])); + + auto a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + auto w = [ [1, 2], [], [3], [4, 5], [] ]; + assert(a.splitter(0).equal(w)); +} + +/// Adjacent separators and keeping sentinels. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + + assert("|ab|".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "", "|", "ab", "|", "" ])); + assert("ab".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "ab" ])); + + assert("a|b||c".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "a", "|", "b", "|", "", "|", "c" ])); + assert("hello world".splitter!("a == b", Yes.keepSeparators)(' ') + .equal([ "hello", " ", "", " ", "world" ])); + + auto a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + auto w = [ [1, 2], [0], [], [0], [3], [0], [4, 5], [0], [] ]; + assert(a.splitter!("a == b", Yes.keepSeparators)(0).equal(w)); +} + +/// Empty and separator-only ranges. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : empty; + + assert("".splitter('|').empty); + assert("|".splitter('|').equal([ "", "" ])); + assert("||".splitter('|').equal([ "", "", "" ])); +} + +/// Empty and separator-only ranges and keeping sentinels. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + import std.range : empty; + + assert("".splitter!("a == b", Yes.keepSeparators)('|').empty); + assert("|".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "", "|", "" ])); + assert("||".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "", "|", "", "|", "" ])); +} + +/// Use a range for splitting +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert("a=>bc=>def".splitter("=>").equal([ "a", "bc", "def" ])); + assert("a|b||c".splitter("||").equal([ "a|b", "c" ])); + assert("hello world".splitter(" ").equal([ "hello", "world" ])); + + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [3, 0, 4, 5, 0] ]; + assert(a.splitter([0, 0]).equal(w)); + + a = [ 0, 0 ]; + assert(a.splitter([0, 0]).equal([ (int[]).init, (int[]).init ])); + + a = [ 0, 0, 1 ]; + assert(a.splitter([0, 0]).equal([ [], [1] ])); +} + +/// Use a range for splitting +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + + assert("a=>bc=>def".splitter!("a == b", Yes.keepSeparators)("=>") + .equal([ "a", "=>", "bc", "=>", "def" ])); + assert("a|b||c".splitter!("a == b", Yes.keepSeparators)("||") + .equal([ "a|b", "||", "c" ])); + assert("hello world".splitter!("a == b", Yes.keepSeparators)(" ") + .equal([ "hello", " ", "world" ])); + + int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; + int[][] w = [ [1, 2], [0, 0], [3, 0, 4, 5, 0] ]; + assert(a.splitter!("a == b", Yes.keepSeparators)([0, 0]).equal(w)); + + a = [ 0, 0 ]; + assert(a.splitter!("a == b", Yes.keepSeparators)([0, 0]) + .equal([ (int[]).init, [0, 0], (int[]).init ])); + + a = [ 0, 0, 1 ]; + assert(a.splitter!("a == b", Yes.keepSeparators)([0, 0]) + .equal([ [], [0, 0], [1] ])); +} + +/// Custom predicate functions. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.ascii : toLower; + + assert("abXcdxef".splitter!"a.toLower == b"('x').equal( + [ "ab", "cd", "ef" ])); + + auto w = [ [0], [1], [2] ]; + assert(w.splitter!"a.front == b"(1).equal([ [[0]], [[2]] ])); +} + +/// Custom predicate functions. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + import std.ascii : toLower; + + assert("abXcdxef".splitter!("a.toLower == b", Yes.keepSeparators)('x') + .equal([ "ab", "X", "cd", "x", "ef" ])); + + auto w = [ [0], [1], [2] ]; + assert(w.splitter!("a.front == b", Yes.keepSeparators)(1) + .equal([ [[0]], [[1]], [[2]] ])); +} + +/// Use splitter without a separator +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : front; + + assert(equal(splitter!(a => a == '|')("a|bc|def"), [ "a", "bc", "def" ])); + assert(equal(splitter!(a => a == ' ')("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)); + assert(equal(splitter!(a => a == 0)(a), w)); + a = [ 0 ]; - assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ])); + assert(equal(splitter!(a => a == 0)(a), [ (int[]).init, (int[]).init ])); + a = [ 0, 1 ]; - assert(equal(splitter(a, 0), [ [], [1] ])); + assert(equal(splitter!(a => a == 0)(a), [ [], [1] ])); + w = [ [0], [1], [2] ]; - assert(equal(splitter!"a.front == b"(w, 1), [ [[0]], [[2]] ])); + assert(equal(splitter!(a => a.front == 1)(w), [ [[0]], [[2]] ])); +} + +/// Leading separators, trailing separators, or no separators. +@safe unittest +{ + import std.algorithm.comparison : equal; + + assert("|ab|".splitter('|').equal([ "", "ab", "" ])); + assert("ab".splitter('|').equal([ "ab" ])); +} + +/// Leading separators, trailing separators, or no separators. +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + + assert("|ab|".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "", "|", "ab", "|", "" ])); + assert("ab".splitter!("a == b", Yes.keepSeparators)('|') + .equal([ "ab" ])); +} + +/// Splitter returns bidirectional ranges if the delimiter is a single element +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : retro; + assert("a|bc|def".splitter('|').retro.equal([ "def", "bc", "a" ])); +} + +/// Splitter returns bidirectional ranges if the delimiter is a single element +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : Yes; + import std.range : retro; + assert("a|bc|def".splitter!("a == b", Yes.keepSeparators)('|') + .retro.equal([ "def", "|", "bc", "|", "a" ])); +} + +/// Splitting by word lazily +@safe unittest +{ + import std.ascii : isWhite; + import std.algorithm.comparison : equal; + import std.algorithm.iteration : splitter; + + string str = "Hello World!"; + assert(str.splitter!(isWhite).equal(["Hello", "World!"])); } @safe unittest @@ -3884,6 +5679,7 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) a = [ 0 ]; assert(equal(splitter(a, 0), [ (int[]).init, (int[]).init ][])); a = [ 0, 1 ]; + assert(equal(splitter(a, 0), [ [], [1] ])); assert(equal(splitter(a, 0), [ [], [1] ][])); // Thoroughly exercise the bidirectional stuff. @@ -3912,7 +5708,9 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) assert(split.front == "b "); assert(split.back == "r "); - foreach (DummyType; AllDummyRanges) { // Bug 4408 + // https://issues.dlang.org/show_bug.cgi?id=4408 + foreach (DummyType; AllDummyRanges) + { static if (isRandomAccessRange!DummyType) { static assert(isBidirectionalRange!DummyType); @@ -3939,38 +5737,30 @@ if (is(typeof(binaryFun!pred(r.front, s)) : bool) assert(s.empty); } -/** -Similar to the previous overload of $(D splitter), except this one uses 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. The predicate is passed to -$(REF binaryFun, std,functional), and can either accept a string, or any callable -that can be executed via $(D pred(r.front, s.front)). - -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. +// https://issues.dlang.org/show_bug.cgi?id=18470 +@safe unittest +{ + import std.algorithm.comparison : equal; -Params: - pred = The predicate for comparing each element with the separator, - defaulting to $(D "a == b"). - r = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to be - split. - s = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to - be treated as the separator between segments of $(D r) to be split. + const w = [[0], [1], [2]]; + assert(w.splitter!((a, b) => a.front() == b)(1).equal([[[0]], [[2]]])); +} -Constraints: - The predicate $(D pred) needs to accept an element of $(D r) and an - element of $(D s). +// https://issues.dlang.org/show_bug.cgi?id=18470 +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.ascii : toLower; -Returns: - An input range of the subranges of elements between separators. If $(D r) - is a forward range or $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives), - the returned range will be likewise. + assert("abXcdxef".splitter!"a.toLower == b"('x').equal(["ab", "cd", "ef"])); + assert("abXcdxef".splitter!((a, b) => a.toLower == b)('x').equal(["ab", "cd", "ef"])); +} -See_Also: $(REF _splitter, std,regex) for a version that splits using a regular -expression defined separator. - */ -auto splitter(alias pred = "a == b", Range, Separator)(Range r, Separator s) +/// ditto +auto splitter(alias pred = "a == b", + Flag!"keepSeparators" keepSeparators = No.keepSeparators, + Range, + Separator)(Range r, Separator s) if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) && (hasSlicing!Range || isNarrowString!Range) && isForwardRange!Separator @@ -3986,33 +5776,38 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) Separator _separator; // _frontLength == size_t.max means empty size_t _frontLength = size_t.max; - static if (isBidirectionalRange!Range) - size_t _backLength = size_t.max; + + static if (keepSeparators) + { + bool _wasSeparator = true; + } @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!pred(_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) + static if (keepSeparators) { - import std.range : retro; - _backLength = _input.length - - find!pred(retro(_input), retro(_separator)).source.length; + assert(!_input.empty || _wasSeparator, "The input must not be empty"); + if (_wasSeparator) + { + _frontLength = _input.length - + find!pred(_input, _separator).length; + _wasSeparator = false; + } + else + { + _frontLength = separatorLength(); + _wasSeparator = true; + } + } + else + { + assert(!_input.empty, "The input must not be empty"); + // compute front length + _frontLength = (_separator.empty) ? 1 : + _input.length - find!pred(_input, _separator).length; } } @@ -4038,7 +5833,14 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) { @property bool empty() { - return _frontLength == size_t.max && _input.empty; + static if (keepSeparators) + { + return _frontLength == size_t.max && _input.empty && !_wasSeparator; + } + else + { + return _frontLength == size_t.max && _input.empty; + } } } @@ -4046,28 +5848,32 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) { assert(!empty, "Attempting to popFront an empty splitter."); ensureFrontLength(); - if (_frontLength == _input.length) + + static if (keepSeparators) { - // done, there's no separator in sight - _input = _input[_frontLength .. _frontLength]; - _frontLength = _frontLength.max; - static if (isBidirectionalRange!Range) - _backLength = _backLength.max; - return; + _input = _input[_frontLength .. _input.length]; } - if (_frontLength + separatorLength == _input.length) + else { - // 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; + if (_frontLength == _input.length) + { + // done, there's no separator in sight + _input = _input[_frontLength .. _frontLength]; + _frontLength = _frontLength.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; + return; + } + // Normal case, pop one item and the separator, get ready for + // reading the next item + _input = _input[_frontLength + separatorLength .. _input.length]; } - // 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; } @@ -4086,21 +5892,6 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) return Result(r, s); } -/// -@safe unittest -{ - import std.algorithm.comparison : equal; - - assert(equal(splitter("hello world", " "), [ "hello", "world" ])); - int[] a = [ 1, 2, 0, 0, 3, 0, 4, 5, 0 ]; - int[][] w = [ [1, 2], [3, 0, 4, 5, 0] ]; - assert(equal(splitter(a, [0, 0]), w)); - a = [ 0, 0 ]; - assert(equal(splitter(a, [0, 0]), [ (int[]).init, (int[]).init ])); - a = [ 0, 0, 1 ]; - assert(equal(splitter(a, [0, 0]), [ [], [1] ])); -} - @safe unittest { import std.algorithm.comparison : equal; @@ -4135,15 +5926,6 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) 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, ","); @@ -4172,11 +5954,11 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) assert(equal(sp6, ["", ""][])); } +// https://issues.dlang.org/show_bug.cgi?id=10773 @safe unittest { import std.algorithm.comparison : equal; - // Issue 10773 auto s = splitter("abc", ""); assert(s.equal(["a", "b", "c"])); } @@ -4193,7 +5975,7 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) @property empty() { return _impl.empty; } @property auto front() { return _impl.front; } void popFront() { _impl = _impl[1..$]; } - @property RefSep save() { return new RefSep(_impl); } + @property RefSep save() scope { return new RefSep(_impl); } @property auto length() { return _impl.length; } } auto sep = new RefSep("->"); @@ -4202,55 +5984,11 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) assert(words.equal([ "i", "am", "pointing" ])); } -/** - -Similar to the previous overload of $(D splitter), except this one does not use a separator. -Instead, the predicate is an unary function on the input range's element type. -The $(D isTerminator) predicate is passed to $(REF unaryFun, std,functional) and can -either accept a string, or any callable that can be executed via $(D pred(element, s)). - -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. - -Params: - isTerminator = The predicate for deciding where to split the range. - input = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to - be split. - -Constraints: - The predicate $(D isTerminator) needs to accept an element of $(D input). - -Returns: - An input range of the subranges of elements between separators. If $(D input) - is a forward range or $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives), - the returned range will be likewise. - -See_Also: $(REF _splitter, std,regex) for a version that splits using a regular -expression defined separator. - */ -auto splitter(alias isTerminator, Range)(Range input) -if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(input.front)))) +/// ditto +auto splitter(alias isTerminator, Range)(Range r) +if (isForwardRange!Range && is(typeof(unaryFun!isTerminator(r.front)))) { - return SplitterResult!(unaryFun!isTerminator, Range)(input); -} - -/// -@safe unittest -{ - import std.algorithm.comparison : equal; - import std.range.primitives : front; - - assert(equal(splitter!(a => a == ' ')("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 => a == 0)(a), w)); - a = [ 0 ]; - assert(equal(splitter!(a => a == 0)(a), [ (int[]).init, (int[]).init ])); - a = [ 0, 1 ]; - assert(equal(splitter!(a => a == 0)(a), [ [], [1] ])); - w = [ [0], [1], [2] ]; - assert(equal(splitter!(a => a.front == 1)(w), [ [[0]], [[2]] ])); + return SplitterResult!(unaryFun!isTerminator, Range)(r); } private struct SplitterResult(alias isTerminator, Range) @@ -4291,6 +6029,24 @@ private struct SplitterResult(alias isTerminator, Range) _end = size_t.max; } + static if (fullSlicing) + { + private this(Range input, size_t end) + { + _input = input; + _end = end; + } + } + else + { + private this(Range input, size_t end, Range next) + { + _input = input; + _end = end; + _next = next; + } + } + static if (isInfinite!Range) { enum bool empty = false; // Propagate infiniteness. @@ -4355,11 +6111,10 @@ private struct SplitterResult(alias isTerminator, Range) @property typeof(this) save() { - auto ret = this; - ret._input = _input.save; - static if (!fullSlicing) - ret._next = _next.save; - return ret; + static if (fullSlicing) + return SplitterResult(_input.save, _end); + else + return SplitterResult(_input.save, _end, _next.save); } } @@ -4444,7 +6199,7 @@ private struct SplitterResult(alias isTerminator, Range) import std.algorithm.comparison : equal; import std.uni : isWhite; - //@@@6791@@@ + // https://issues.dlang.org/show_bug.cgi?id=6791 assert(equal( splitter("là dove terminava quella valle"), ["là", "dove", "terminava", "quella", "valle"] @@ -4456,54 +6211,126 @@ private struct SplitterResult(alias isTerminator, Range) assert(equal(splitter!"a=='本'"("日本語"), ["日", "語"])); } +// https://issues.dlang.org/show_bug.cgi?id=18657 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : refRange; + string s = "foobar"; + auto r = refRange(&s).splitter!(c => c == 'b'); + assert(equal!equal(r.save, ["foo", "ar"])); + assert(equal!equal(r.save, ["foo", "ar"])); +} + /++ -Lazily splits the string $(D s) into words, using whitespace as the delimiter. +Lazily splits the character-based range `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 +This function is character-range specific and, contrary to +`splitter!(std.uni.isWhite)`, runs of whitespace will be merged together (no empty tokens will be produced). Params: - s = The string to be split. + s = The character-based range to be split. Must be a string, or a + random-access range of character types. Returns: An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of slices of - the original string split by whitespace. + the original range split by whitespace. +/ -auto splitter(C)(C[] s) -if (isSomeChar!C) +auto splitter(Range)(Range s) +if (isSomeString!Range || + isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && + !isConvertibleToString!Range && + isSomeChar!(ElementEncodingType!Range)) { import std.algorithm.searching : find; static struct Result { private: import core.exception : RangeError; - C[] _s; + Range _s; size_t _frontLength; - void getFirst() pure @safe + void getFirst() { import std.uni : isWhite; + import std.traits : Unqual; + + static if (is(immutable ElementEncodingType!Range == immutable wchar) && + is(immutable ElementType!Range == immutable dchar)) + { + // all unicode whitespace characters fit into a wchar. However, + // this range is a wchar array, so we will treat it like a + // wchar array instead of decoding each code point. + _frontLength = _s.length; // default condition, no spaces + foreach (i; 0 .. _s.length) + if (isWhite(_s[i])) + { + _frontLength = i; + break; + } + } + else static if (is(immutable ElementType!Range == immutable dchar) || + is(immutable ElementType!Range == immutable wchar)) + { + // dchar or wchar range, we can just use find. + auto r = find!(isWhite)(_s.save); + _frontLength = _s.length - r.length; + } + else + { + // need to decode the characters until we find a space. This is + // ported from std.string.stripLeft. + static import std.ascii; + static import std.uni; + import std.utf : decodeFront; + + auto input = _s.save; + size_t iLength = input.length; + + while (!input.empty) + { + auto c = input.front; + if (std.ascii.isASCII(c)) + { + if (std.ascii.isWhite(c)) + break; + input.popFront(); + --iLength; + } + else + { + auto dc = decodeFront(input); + if (std.uni.isWhite(dc)) + break; + iLength = input.length; + } + } + + // sanity check + assert(iLength <= _s.length, "The current index must not" + ~ " exceed the length of the input"); - auto r = find!(isWhite)(_s); - _frontLength = _s.length - r.length; + _frontLength = _s.length - iLength; + } } public: - this(C[] s) pure @safe + this(Range s) { - import std.string : strip; - _s = s.strip(); + import std.string : stripLeft; + _s = s.stripLeft(); getFirst(); } - @property C[] front() pure @safe + @property auto front() { version (assert) if (empty) throw new RangeError(); return _s[0 .. _frontLength]; } - void popFront() pure @safe + void popFront() { import std.string : stripLeft; version (assert) if (empty) throw new RangeError(); @@ -4511,7 +6338,7 @@ if (isSomeChar!C) getFirst(); } - @property bool empty() const @safe pure nothrow + @property bool empty() const { return _s.empty; } @@ -4536,14 +6363,14 @@ if (isSomeChar!C) { import std.algorithm.comparison : equal; import std.meta : AliasSeq; - foreach (S; AliasSeq!(string, wstring, dstring)) - { + static foreach (S; AliasSeq!(string, wstring, dstring)) + {{ import std.conv : to; - S a = " a bcd ef gh "; + S a = " a \u2028 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"][])); @@ -4575,6 +6402,56 @@ if (isSomeChar!C) assert(dictionary["two"]== 2); assert(dictionary["yah"]== 3); assert(dictionary["last"]== 4); + +} + +@safe unittest +{ + // do it with byCodeUnit + import std.conv : to; + import std.string : strip; + import std.utf : byCodeUnit; + + alias BCU = typeof("abc".byCodeUnit()); + + // TDPL example, page 8 + uint[BCU] dictionary; + BCU[3] lines; + lines[0] = "line one".byCodeUnit; + lines[1] = "line \ttwo".byCodeUnit; + lines[2] = "yah last line\ryah".byCodeUnit; + foreach (line; lines) + { + foreach (word; splitter(strip(line))) + { + static assert(is(typeof(word) == BCU)); + if (word in dictionary) continue; // Nothing to do + auto newID = dictionary.length; + dictionary[word] = cast(uint) newID; + } + } + assert(dictionary.length == 5); + assert(dictionary["line".byCodeUnit]== 0); + assert(dictionary["one".byCodeUnit]== 1); + assert(dictionary["two".byCodeUnit]== 2); + assert(dictionary["yah".byCodeUnit]== 3); + assert(dictionary["last".byCodeUnit]== 4); +} + +// https://issues.dlang.org/show_bug.cgi?id=19238 +@safe pure unittest +{ + import std.utf : byCodeUnit; + import std.algorithm.comparison : equal; + auto range = "hello world".byCodeUnit.splitter; + static assert(is(typeof(range.front()) == typeof("hello".byCodeUnit()))); + assert(range.equal(["hello".byCodeUnit, "world".byCodeUnit])); + + // test other space types, including unicode + auto u = " a\t\v\r bcd\u3000 \u2028\t\nef\U00010001 gh"; + assert(equal(splitter(u), ["a", "bcd", "ef\U00010001", "gh"][])); + assert(equal(splitter(u.byCodeUnit), ["a".byCodeUnit, "bcd".byCodeUnit, + "ef\U00010001".byCodeUnit, "gh".byCodeUnit][])); } @safe unittest @@ -4649,45 +6526,702 @@ if (isSomeChar!C) } } +// In same combinations substitute needs to calculate the auto-decoded length +// of its needles +private template hasDifferentAutodecoding(Range, Needles...) +{ + import std.meta : anySatisfy; + /* iff + - the needles needs auto-decoding, but the incoming range doesn't (or vice versa) + - both (range, needle) need auto-decoding and don't share the same common type + */ + enum needlesAreNarrow = anySatisfy!(isNarrowString, Needles); + enum sourceIsNarrow = isNarrowString!Range; + enum hasDifferentAutodecoding = sourceIsNarrow != needlesAreNarrow || + (sourceIsNarrow && needlesAreNarrow && + is(CommonType!(Range, Needles) == void)); +} + +@safe nothrow @nogc pure unittest +{ + import std.meta : AliasSeq; // used for better clarity + + static assert(!hasDifferentAutodecoding!(string, AliasSeq!(string, string))); + static assert(!hasDifferentAutodecoding!(wstring, AliasSeq!(wstring, wstring))); + static assert(!hasDifferentAutodecoding!(dstring, AliasSeq!(dstring, dstring))); + + // the needles needs auto-decoding, but the incoming range doesn't (or vice versa) + static assert(hasDifferentAutodecoding!(string, AliasSeq!(wstring, wstring))); + static assert(hasDifferentAutodecoding!(string, AliasSeq!(dstring, dstring))); + static assert(hasDifferentAutodecoding!(wstring, AliasSeq!(string, string))); + static assert(hasDifferentAutodecoding!(wstring, AliasSeq!(dstring, dstring))); + static assert(hasDifferentAutodecoding!(dstring, AliasSeq!(string, string))); + static assert(hasDifferentAutodecoding!(dstring, AliasSeq!(wstring, wstring))); + + // both (range, needle) need auto-decoding and don't share the same common type + static foreach (T; AliasSeq!(string, wstring, dstring)) + { + static assert(hasDifferentAutodecoding!(T, AliasSeq!(wstring, string))); + static assert(hasDifferentAutodecoding!(T, AliasSeq!(dstring, string))); + static assert(hasDifferentAutodecoding!(T, AliasSeq!(wstring, dstring))); + } +} + +// substitute +/** +Returns a range with all occurrences of `substs` in `r`. +replaced with their substitution. + +Single value replacements (`'ö'.substitute!('ä', 'a', 'ö', 'o', 'ü', 'u)`) are +supported as well and in $(BIGOH 1). + +Params: + r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + value = a single value which can be substituted in $(BIGOH 1) + substs = a set of replacements/substitutions + pred = the equality function to test if element(s) are equal to + a substitution + +Returns: a range with the substitutions replaced. + +See_Also: +$(REF replace, std, array) for an eager replace algorithm or +$(REF translate, std, string), and $(REF tr, std, string) +for string algorithms with translation tables. +*/ +template substitute(substs...) +if (substs.length >= 2 && isExpressions!substs) +{ + import std.range.primitives : ElementType; + import std.traits : CommonType; + + static assert(!(substs.length & 1), "The number of substitution parameters must be even"); + + /** + Substitute single values with compile-time substitution mappings. + Complexity: $(BIGOH 1) due to D's `switch` guaranteeing $(BIGOH 1); + */ + auto substitute(Value)(Value value) + if (isInputRange!Value || !is(CommonType!(Value, typeof(substs[0])) == void)) + { + static if (isInputRange!Value) + { + static if (!is(CommonType!(ElementType!Value, typeof(substs[0])) == void)) + { + // Substitute single range elements with compile-time substitution mappings + return value.map!(a => substitute(a)); + } + else static if (isInputRange!Value && + !is(CommonType!(ElementType!Value, ElementType!(typeof(substs[0]))) == void)) + { + // not implemented yet, fallback to runtime variant for now + return .substitute(value, substs); + } + else + { + static assert(0, `Compile-time substitutions must be elements or ranges of the same type of ` ~ + Value.stringof ~ `.`); + } + } + // Substitute single values with compile-time substitution mappings. + else // static if (!is(CommonType!(Value, typeof(substs[0])) == void)) + { + switch (value) + { + static foreach (i; 0 .. substs.length / 2) + case substs[2 * i]: + return substs[2 * i + 1]; + + default: return value; + } + } + } +} + +/// ditto +auto substitute(alias pred = (a, b) => a == b, R, Substs...)(R r, Substs substs) +if (isInputRange!R && Substs.length >= 2 && !is(CommonType!(Substs) == void)) +{ + import std.range.primitives : ElementType; + import std.meta : allSatisfy; + import std.traits : CommonType; + + static assert(!(Substs.length & 1), "The number of substitution parameters must be even"); + + enum n = Substs.length / 2; + + // Substitute individual elements + static if (!is(CommonType!(ElementType!R, Substs) == void)) + { + import std.functional : binaryFun; + + // Imitate a value closure to be @nogc + static struct ReplaceElement + { + private Substs substs; + + this(Substs substs) + { + this.substs = substs; + } + + auto opCall(E)(E e) + { + static foreach (i; 0 .. n) + if (binaryFun!pred(e, substs[2 * i])) + return substs[2 * i + 1]; + + return e; + } + } + auto er = ReplaceElement(substs); + return r.map!er; + } + // Substitute subranges + else static if (!is(CommonType!(ElementType!R, ElementType!(Substs[0])) == void) && + allSatisfy!(isForwardRange, Substs)) + { + import std.range : choose, take; + import std.meta : Stride; + + auto replaceElement(E)(E e) + { + alias ReturnA = typeof(e[0]); + alias ReturnB = typeof(substs[0 .. 1].take(1)); + + // 1-based index + const auto hitNr = e[1]; + switch (hitNr) + { + // no hit + case 0: + // use choose trick for non-common range + static if (is(CommonType!(ReturnA, ReturnB) == void)) + return choose(1, e[0], ReturnB.init); + else + return e[0]; + + // all replacements + static foreach (i; 0 .. n) + case i + 1: + // use choose trick for non-common ranges + static if (is(CommonType!(ReturnA, ReturnB) == void)) + return choose(0, e[0], substs[2 * i + 1].take(size_t.max)); + else + return substs[2 * i + 1].take(size_t.max); + default: + assert(0, "hitNr should always be found."); + } + } + + alias Ins = Stride!(2, Substs); + + static struct SubstituteSplitter + { + import std.range : drop; + import std.typecons : Tuple; + + private + { + typeof(R.init.drop(0)) rest; + Ins needles; + + typeof(R.init.take(0)) skip; // skip before next hit + alias Hit = size_t; // 0 iff no hit, otherwise hit in needles[index-1] + alias E = Tuple!(typeof(skip), Hit); + Hit hitNr; // hit number: 0 means no hit, otherwise index+1 to needles that matched + bool hasHit; // is there a replacement hit which should be printed? + + enum hasDifferentAutodecoding = .hasDifferentAutodecoding!(typeof(rest), Ins); + + // calculating the needle length for narrow strings might be expensive -> cache it + static if (hasDifferentAutodecoding) + ptrdiff_t[n] needleLengths = -1; + } + + this(R haystack, Ins needles) + { + this.rest = haystack.drop(0); + this.needles = needles; + if (!haystack.empty) + { + hasHit = true; + popFront; + } + static if (hasNested!(typeof(skip))) + skip = rest.take(0); + } + + /* If `skip` is non-empty, it's returned as (skip, 0) tuple + otherwise a similar (<empty>, hitNr) tuple is returned. + `replaceElement` maps based on the second item (`hitNr`). + */ + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty substitute."); + return !skip.empty ? E(skip, 0) : E(typeof(skip).init, hitNr); + } + + static if (isInfinite!R) + enum empty = false; // propagate infiniteness + else + @property bool empty() + { + return skip.empty && !hasHit; + } + + /* If currently in a skipping phase => reset. + Otherwise try to find the next occurrence of `needles` + If valid match + - if there are elements before the match, set skip with these elements + (on the next popFront, the range will be in the skip state once) + - `rest`: advance to the end of the match + - set hasHit + Otherwise skip to the end + */ + void popFront() + { + assert(!empty, "Attempting to popFront an empty substitute."); + if (!skip.empty) + { + skip = typeof(skip).init; // jump over skip + } + else + { + import std.algorithm.searching : countUntil, find; + + auto match = rest.find!pred(needles); + + static if (needles.length >= 2) // variadic version of find (returns a tuple) + { + // find with variadic needles returns a (range, needleNr) tuple + // needleNr is a 1-based index + auto hitValue = match[0]; + hitNr = match[1]; + } + else + { + // find with one needle returns the range + auto hitValue = match; + hitNr = match.empty ? 0 : 1; + } + + if (hitNr == 0) // no more hits + { + skip = rest.take(size_t.max); + hasHit = false; + rest = typeof(rest).init; + } + else + { + auto hitLength = size_t.max; + switchL: switch (hitNr - 1) + { + static foreach (i; 0 .. n) + { + case i: + static if (hasDifferentAutodecoding) + { + import std.utf : codeLength; + + // cache calculated needle length + if (needleLengths[i] != -1) + hitLength = needleLengths[i]; + else + hitLength = needleLengths[i] = codeLength!dchar(needles[i]); + } + else + { + hitLength = needles[i].length; + } + break switchL; + } + default: + assert(0, "hitNr should always be found"); + } + + const pos = rest.countUntil(hitValue); + if (pos > 0) // match not at start of rest + skip = rest.take(pos); + + hasHit = true; + + // iff the source range and the substitutions are narrow strings, + // we can avoid calling the auto-decoding `popFront` (via drop) + static if (isNarrowString!(typeof(hitValue)) && !hasDifferentAutodecoding) + rest = hitValue[hitLength .. $]; + else + rest = hitValue.drop(hitLength); + } + } + } + } + + // extract inputs + Ins ins; + static foreach (i; 0 .. n) + ins[i] = substs[2 * i]; + + return SubstituteSplitter(r, ins) + .map!(a => replaceElement(a)) + .joiner; + } + else + { + static assert(0, "The substitutions must either substitute a single element or a save-able subrange."); + } +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // substitute single elements + assert("do_it".substitute('_', ' ').equal("do it")); + + // substitute multiple, single elements + assert("do_it".substitute('_', ' ', + 'd', 'g', + 'i', 't', + 't', 'o') + .equal("go to")); + + // substitute subranges + assert("do_it".substitute("_", " ", + "do", "done") + .equal("done it")); + + // substitution works for any ElementType + int[] x = [1, 2, 3]; + auto y = x.substitute(1, 0.1); + assert(y.equal([0.1, 2, 3])); + static assert(is(typeof(y.front) == double)); + + import std.range : retro; + assert([1, 2, 3].substitute(1, 0.1).retro.equal([3, 2, 0.1])); +} + +/// Use the faster compile-time overload +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // substitute subranges of a range + assert("apple_tree".substitute!("apple", "banana", + "tree", "shrub").equal("banana_shrub")); + + // substitute subranges of a range + assert("apple_tree".substitute!('a', 'b', + 't', 'f').equal("bpple_free")); + + // substitute values + assert('a'.substitute!('a', 'b', 't', 'f') == 'b'); +} + +/// Multiple substitutes +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : ElementType; + + int[3] x = [1, 2, 3]; + auto y = x[].substitute(1, 0.1) + .substitute(0.1, 0.2); + static assert(is(typeof(y.front) == double)); + assert(y.equal([0.2, 2, 3])); + + auto z = "42".substitute('2', '3') + .substitute('3', '1'); + static assert(is(ElementType!(typeof(z)) == dchar)); + assert(equal(z, "41")); +} + +// Test the first example with compile-time overloads +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // substitute single elements + assert("do_it".substitute!('_', ' ').equal("do it")); + + // substitute multiple, single elements + assert("do_it".substitute!('_', ' ', + 'd', 'g', + 'i', 't', + 't', 'o') + .equal(`go to`)); + + // substitute subranges + assert("do_it".substitute!("_", " ", + "do", "done") + .equal("done it")); + + // substitution works for any ElementType + int[3] x = [1, 2, 3]; + auto y = x[].substitute!(1, 0.1); + assert(y.equal([0.1, 2, 3])); + static assert(is(typeof(y.front) == double)); + + import std.range : retro; + assert([1, 2, 3].substitute!(1, 0.1).retro.equal([3, 2, 0.1])); +} + +// test infinite ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.range : cycle, take; + + int[] x = [1, 2, 3]; + assert(x.cycle.substitute!(1, 0.1).take(4).equal([0.1, 2, 3, 0.1])); + assert(x.cycle.substitute(1, 0.1).take(4).equal([0.1, 2, 3, 0.1])); +} + +// test infinite ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + foreach (R; AllDummyRanges) + { + assert(R.init + .substitute!(2, 22, 3, 33, 5, 55, 9, 99) + .equal([1, 22, 33, 4, 55, 6, 7, 8, 99, 10])); + + assert(R.init + .substitute(2, 22, 3, 33, 5, 55, 9, 99) + .equal([1, 22, 33, 4, 55, 6, 7, 8, 99, 10])); + } +} + +// test multiple replacements +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("alpha.beta.gamma" + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("1.2.3")); + + assert("alpha.beta.gamma." + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("1.2.3.")); + + assert("beta.beta.beta" + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("2.2.2")); + + assert("alpha.alpha.alpha" + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("1.1.1")); +} + +// test combination of subrange + element replacement +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert(("abcDe".substitute("a", "AA", + "b", "DD") + .substitute('A', 'y', + 'D', 'x', + 'e', '1')) + .equal("yyxxcx1")); +} + +// test const + immutable storage groups +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + auto xyz_abc(T)(T value) + { + immutable a = "a"; + const b = "b"; + auto c = "c"; + return value.substitute!("x", a, + "y", b, + "z", c); + } + assert(xyz_abc("_x").equal("_a")); + assert(xyz_abc(".y.").equal(".b.")); + assert(xyz_abc("z").equal("c")); + assert(xyz_abc("w").equal("w")); +} + +// test with narrow strings (auto-decoding) and subranges +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("äöü€".substitute("ä", "b", "ü", "u").equal("böu€")); + assert("äöü€".substitute!("ä", "b", "ü", "u").equal("böu€")); + assert("ä...öü€".substitute("ä", "b", "ü", "u").equal("b...öu€")); + + auto expected = "emoticons😄😅.😇😈Rock"; + assert("emoticons😄😅😆😇😈rock" + .substitute("r", "R", "😆", ".").equal(expected)); + assert("emoticons😄😅😆😇😈rock" + .substitute!("r", "R", "😆", ".").equal(expected)); +} + +// test with narrow strings (auto-decoding) and single elements +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("äöü€".substitute('ä', 'b', 'ü', 'u').equal("böu€")); + assert("äöü€".substitute!('ä', 'b', 'ü', 'u').equal("böu€")); + + auto expected = "emoticons😄😅.😇😈Rock"; + assert("emoticons😄😅😆😇😈rock" + .substitute('r', 'R', '😆', '.').equal(expected)); + assert("emoticons😄😅😆😇😈rock" + .substitute!('r', 'R', '😆', '.').equal(expected)); +} + +// test auto-decoding {n,w,d} strings X {n,w,d} strings +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("ääöü€".substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€".substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€".substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); + + assert("ääöü€"w.substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€"w.substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€"w.substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); + + assert("ääöü€"d.substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€"d.substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€"d.substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); + + // auto-decoding is done before by a different range + assert("ääöü€".filter!(a => true).substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€".filter!(a => true).substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€".filter!(a => true).substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); +} + +// test repeated replacement +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + assert([1, 2, 3, 1, 1, 2].substitute(1, 0).equal([0, 2, 3, 0, 0, 2])); + assert([1, 2, 3, 1, 1, 2].substitute!(1, 0).equal([0, 2, 3, 0, 0, 2])); + assert([1, 2, 3, 1, 1, 2].substitute(1, 2, 2, 9).equal([2, 9, 3, 2, 2, 9])); +} + +// test @nogc for single element replacements +@safe @nogc unittest +{ + import std.algorithm.comparison : equal; + + static immutable arr = [1, 2, 3, 1, 1, 2]; + static immutable expected = [0, 2, 3, 0, 0, 2]; + + assert(arr.substitute!(1, 0).equal(expected)); + assert(arr.substitute(1, 0).equal(expected)); +} + +// test different range types +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + static foreach (DummyType; AllDummyRanges) + {{ + DummyType dummyRange; + + // single substitution + dummyRange.substitute (2, 22).equal([1, 22, 3, 4, 5, 6, 7, 8, 9, 10]); + dummyRange.substitute!(2, 22).equal([1, 22, 3, 4, 5, 6, 7, 8, 9, 10]); + + // multiple substitution + dummyRange.substitute (2, 22, 5, 55, 7, 77).equal([1, 22, 3, 4, 55, 6, 77, 8, 9, 10]); + dummyRange.substitute!(2, 22, 5, 55, 7, 77).equal([1, 22, 3, 4, 55, 6, 77, 8, 9, 10]); + }} +} + +// https://issues.dlang.org/show_bug.cgi?id=19207 +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + assert([1, 2, 3, 4].substitute([1], [7]).equal([7, 2, 3, 4])); + assert([1, 2, 3, 4].substitute([2], [7]).equal([1, 7, 3, 4])); + assert([1, 2, 3, 4].substitute([4], [7]).equal([1, 2, 3, 7])); + assert([1, 2, 3, 4].substitute([2, 3], [7]).equal([1, 7, 4])); + assert([1, 2, 3, 4].substitute([3, 4], [7, 8]).equal([1, 2, 7, 8])); +} + +// tests recognizing empty base ranges +nothrow pure @safe unittest +{ + import std.utf : byCodeUnit; + import std.algorithm.comparison : equal; + + assert("".byCodeUnit.substitute('4', 'A').empty); + assert("".byCodeUnit.substitute('0', 'O', '5', 'S', '1', 'l').empty); + assert("".byCodeUnit.substitute("PKM".byCodeUnit, "PoKeMon".byCodeUnit).empty); + assert("".byCodeUnit.substitute + ( "ding".byCodeUnit, + "dong".byCodeUnit, + "click".byCodeUnit, + "clack".byCodeUnit, + "ping".byCodeUnit, + "latency".byCodeUnit + ).empty); +} + // sum /** -Sums elements of $(D r), which must be a finite +Sums elements of `r`, which must be a finite $(REF_ALTTEXT input range, isInputRange, std,range,primitives). Although -conceptually $(D sum(r)) is equivalent to $(LREF fold)!((a, b) => a + -b)(r, 0), $(D sum) uses specialized algorithms to maximize accuracy, +conceptually `sum(r)` is equivalent to $(LREF fold)!((a, b) => a + +b)(r, 0), `sum` uses specialized algorithms to maximize accuracy, as follows. $(UL -$(LI If $(D $(REF ElementType, std,range,primitives)!R) is a floating-point -type and $(D R) is a +$(LI If $(REF ElementType, std,range,primitives)!R is a floating-point +type and `R` is a $(REF_ALTTEXT random-access range, isRandomAccessRange, std,range,primitives) with -length and slicing, then $(D sum) uses the +length and slicing, then `sum` uses the $(HTTP en.wikipedia.org/wiki/Pairwise_summation, pairwise summation) algorithm.) -$(LI If $(D ElementType!R) is a floating-point type and $(D R) is a +$(LI If `ElementType!R` is a floating-point type and `R` is a finite input range (but not a random-access range with slicing), then -$(D sum) uses the $(HTTP en.wikipedia.org/wiki/Kahan_summation, +`sum` uses the $(HTTP 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 -$(DDLINK spec/type, Types, $(D real)) -precision for $(D real) inputs and in $(D double) precision otherwise -(Note this is a special case that deviates from $(D fold)'s behavior, -which would have kept $(D float) precision for a $(D float) range). +$(DDLINK spec/type, Types, `real`) +precision for `real` inputs and in `double` precision otherwise +(Note this is a special case that deviates from `fold`'s behavior, +which would have kept `float` precision for a `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 $(DDSUBLINK spec/type,integer-promotions, integral promotion)). -A seed may be passed to $(D sum). Not only will this seed be used as an initial +A seed may be passed to `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 summation. +and precision used for summation. If a seed is not passed, one is created with +the value of `typeof(r.front + r.front)(0)`, or `typeof(r.front + r.front).zero` +if no constructor exists that takes an int. 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 fold!((a, b) => a + b)(r, 0)), which +at expense of precision, one can use `fold!((a, b) => a + b)(r, 0)`, which is not specialized for summation. Params: @@ -4705,7 +7239,15 @@ if (isInputRange!R && !isInfinite!R && is(typeof(r.front + r.front))) 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)); + static if (is(typeof(Unqual!Seed(0)))) + enum seedValue = Unqual!Seed(0); + else static if (is(typeof({ Unqual!Seed tmp = Seed.zero; }))) + enum Unqual!Seed seedValue = Seed.zero; + else + static assert(false, + "Could not initiate an initial value for " ~ (Unqual!Seed).stringof + ~ ". Please supply an initial value manually."); + return sum(r, seedValue); } /// ditto auto sum(R, E)(R r, E seed) @@ -4727,6 +7269,36 @@ if (isInputRange!R && !isInfinite!R && is(typeof(seed = seed + r.front))) } } +/// Ditto +@safe pure nothrow unittest +{ + import std.range; + + //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.operations : isClose; + assert(iota(ulong.max / 2, ulong.max / 2 + 4096).sum(0.0) + .isClose((ulong.max / 2) * 4096.0 + 4096^^2 / 2)); +} + // Pairwise summation http://en.wikipedia.org/wiki/Pairwise_summation private auto sumPairwise(F, R)(R data) if (isInputRange!R && !isInfinite!R) @@ -4815,8 +7387,8 @@ if (isForwardRange!R && !isRandomAccessRange!R) private auto sumPairwiseN(size_t N, bool needEmptyChecks, F, R)(ref R r) if (isForwardRange!R && !isRandomAccessRange!R) { - import std.math : isPowerOf2; - static assert(isPowerOf2(N)); + import std.math.traits : isPowerOf2; + static assert(isPowerOf2(N), "N must be a power of 2"); static if (N == 2) return sumPair!(needEmptyChecks, F)(r); else return sumPairwiseN!(N/2, needEmptyChecks, F)(r) + sumPairwiseN!(N/2, needEmptyChecks, F)(r); @@ -4825,7 +7397,9 @@ if (isForwardRange!R && !isRandomAccessRange!R) // 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); + static assert(isFloatingPoint!Result && isMutable!Result, "The type of" + ~ " Result must be a mutable floating point, not " + ~ Result.stringof); Result c = 0; for (; !r.empty; r.popFront()) { @@ -4837,36 +7411,6 @@ private auto sumKahan(Result, R)(Result result, R r) return result; } -/// Ditto -@safe pure nothrow unittest -{ - import std.range; - - //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)); @@ -4914,7 +7458,8 @@ private auto sumKahan(Result, R)(Result result, R r) assert(sum(SList!double(1, 2, 3, 4)[]) == 10); } -@safe pure nothrow unittest // 12434 +// https://issues.dlang.org/show_bug.cgi?id=12434 +@safe pure nothrow unittest { immutable a = [10, 20]; auto s1 = sum(a); @@ -4943,15 +7488,154 @@ private auto sumKahan(Result, R)(Result result, R r) assert(repeat(1.0, n).sum == n); } +// Issue 19525 +@safe unittest +{ + import std.datetime : Duration, minutes; + assert([1.minutes].sum() == 1.minutes); +} + +/** +Finds the mean (colloquially known as the average) of a range. + +For built-in numerical types, accurate Knuth & Welford mean calculation +is used. For user-defined types, element by element summation is used. +Additionally an extra parameter `seed` is needed in order to correctly +seed the summation with the equivalent to `0`. + +The first overload of this function will return `T.init` if the range +is empty. However, the second overload will return `seed` on empty ranges. + +This function is $(BIGOH r.length). + +Params: + T = The type of the return value. + r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + seed = For user defined types. Should be equivalent to `0`. + +Returns: + The mean of `r` when `r` is non-empty. +*/ +T mean(T = double, R)(R r) +if (isInputRange!R && + isNumeric!(ElementType!R) && + !isInfinite!R) +{ + if (r.empty) + return T.init; + + Unqual!T meanRes = 0; + size_t i = 1; + + // Knuth & Welford mean calculation + // division per element is slower, but more accurate + for (; !r.empty; r.popFront()) + { + T delta = r.front - meanRes; + meanRes += delta / i++; + } + + return meanRes; +} + +/// ditto +auto mean(R, T)(R r, T seed) +if (isInputRange!R && + !isNumeric!(ElementType!R) && + is(typeof(r.front + seed)) && + is(typeof(r.front / size_t(1))) && + !isInfinite!R) +{ + import std.algorithm.iteration : sum, reduce; + + // per item division vis-a-vis the previous overload is too + // inaccurate for integer division, which the user defined + // types might be representing + static if (hasLength!R) + { + if (r.length == 0) + return seed; + + return sum(r, seed) / r.length; + } + else + { + import std.typecons : tuple; + + if (r.empty) + return seed; + + auto pair = reduce!((a, b) => tuple(a[0] + 1, a[1] + b)) + (tuple(size_t(0), seed), r); + return pair[1] / pair[0]; + } +} + +/// +@safe @nogc pure nothrow unittest +{ + import std.math.operations : isClose; + import std.math.traits : isNaN; + + static immutable arr1 = [1, 2, 3]; + static immutable arr2 = [1.5, 2.5, 12.5]; + + assert(arr1.mean.isClose(2)); + assert(arr2.mean.isClose(5.5)); + + assert(arr1[0 .. 0].mean.isNaN); +} + +@safe pure nothrow unittest +{ + import std.internal.test.dummyrange : ReferenceInputRange; + import std.math.operations : isClose; + + auto r1 = new ReferenceInputRange!int([1, 2, 3]); + assert(r1.mean.isClose(2)); + + auto r2 = new ReferenceInputRange!double([1.5, 2.5, 12.5]); + assert(r2.mean.isClose(5.5)); +} + +// Test user defined types +@system pure unittest +{ + import std.bigint : BigInt; + import std.internal.test.dummyrange : ReferenceInputRange; + import std.math.operations : isClose; + + auto bigint_arr = [BigInt("1"), BigInt("2"), BigInt("3"), BigInt("6")]; + auto bigint_arr2 = new ReferenceInputRange!BigInt([ + BigInt("1"), BigInt("2"), BigInt("3"), BigInt("6") + ]); + assert(bigint_arr.mean(BigInt(0)) == BigInt("3")); + assert(bigint_arr2.mean(BigInt(0)) == BigInt("3")); + + BigInt[] bigint_arr3 = []; + assert(bigint_arr3.mean(BigInt(0)) == BigInt(0)); + + struct MyFancyDouble + { + double v; + alias v this; + } + + // both overloads + auto d_arr = [MyFancyDouble(10), MyFancyDouble(15), MyFancyDouble(30)]; + assert(mean!(double)(cast(double[]) d_arr).isClose(18.33333333)); + assert(mean(d_arr, MyFancyDouble(0)).isClose(18.33333333)); +} + // uniq /** Lazily iterates unique consecutive elements of the given range (functionality akin to the $(HTTP wikipedia.org/wiki/_Uniq, _uniq) system utility). Equivalence of elements is assessed by using the predicate -$(D pred), by default $(D "a == b"). The predicate is passed to +`pred`, by default `"a == b"`. The predicate is passed to $(REF binaryFun, std,functional), and can either accept a string, or any callable -that can be executed via $(D pred(element, element)). If the given range is -bidirectional, $(D uniq) also yields a +that can be executed via `pred(element, element)`. If the given range is +bidirectional, `uniq` also yields a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives). Params: @@ -4961,7 +7645,7 @@ Params: Returns: An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of - consecutively unique elements in the original range. If $(D r) is also a + consecutively unique elements in the original range. If `r` is also a forward range or bidirectional range, the returned range will be likewise. */ auto uniq(alias pred = "a == b", Range)(Range r) @@ -5085,7 +7769,8 @@ private struct UniqResult(alias pred, Range) } } -@safe unittest // https://issues.dlang.org/show_bug.cgi?id=17264 +// https://issues.dlang.org/show_bug.cgi?id=17264 +@safe unittest { import std.algorithm.comparison : equal; @@ -5094,12 +7779,16 @@ private struct UniqResult(alias pred, Range) } /** -Lazily computes all _permutations of $(D r) using $(HTTP +Lazily computes all _permutations of `r` using $(HTTP en.wikipedia.org/wiki/Heap%27s_algorithm, Heap's algorithm). +Params: + Range = the range type + r = the $(REF_ALTTEXT random access range, isRandomAccessRange, std,range,primitives) + to find the permutations for. Returns: -A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) -the elements of which are an $(REF indexed, std,range) view into $(D r). + A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + of elements of which are an $(REF indexed, std,range) view into `r`. See_Also: $(REF nextPermutation, std,algorithm,sorting). @@ -5130,13 +7819,13 @@ if (isRandomAccessRange!Range && hasLength!Range) _empty = r.length == 0; } - /// + /// Returns: `true` if the range is empty, `false` otherwise. @property bool empty() const pure nothrow @safe @nogc { return _empty; } - /// + /// Returns: the front of the range @property auto front() { import std.range : indexed; diff --git a/libphobos/src/std/algorithm/mutation.d b/libphobos/src/std/algorithm/mutation.d index 2b708ad..88191bb 100644 --- a/libphobos/src/std/algorithm/mutation.d +++ b/libphobos/src/std/algorithm/mutation.d @@ -1,59 +1,59 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _mutation algorithms. +It contains generic mutation algorithms. $(SCRIPT inhibitQuickIndex = 1;) $(BOOKTABLE Cheat Sheet, $(TR $(TH Function Name) $(TH Description)) $(T2 bringToFront, - If $(D a = [1, 2, 3]) and $(D b = [4, 5, 6, 7]), - $(D bringToFront(a, b)) leaves $(D a = [4, 5, 6]) and - $(D b = [7, 1, 2, 3]).) + If `a = [1, 2, 3]` and `b = [4, 5, 6, 7]`, + `bringToFront(a, b)` leaves `a = [4, 5, 6]` and + `b = [7, 1, 2, 3]`.) $(T2 copy, Copies a range to another. If - $(D a = [1, 2, 3]) and $(D b = new int[5]), then $(D copy(a, b)) - leaves $(D b = [1, 2, 3, 0, 0]) and returns $(D b[3 .. $]).) + `a = [1, 2, 3]` and `b = new int[5]`, then `copy(a, b)` + leaves `b = [1, 2, 3, 0, 0]` and returns `b[3 .. $]`.) $(T2 fill, Fills a range with a pattern, - e.g., if $(D a = new int[3]), then $(D fill(a, 4)) - leaves $(D a = [4, 4, 4]) and $(D fill(a, [3, 4])) leaves - $(D a = [3, 4, 3]).) + e.g., if `a = new int[3]`, then `fill(a, 4)` + leaves `a = [4, 4, 4]` and `fill(a, [3, 4])` leaves + `a = [3, 4, 3]`.) $(T2 initializeAll, - If $(D a = [1.2, 3.4]), then $(D initializeAll(a)) leaves - $(D a = [double.init, double.init]).) + If `a = [1.2, 3.4]`, then `initializeAll(a)` leaves + `a = [double.init, double.init]`.) $(T2 move, - $(D move(a, b)) moves $(D a) into $(D b). $(D move(a)) reads $(D a) + `move(a, b)` moves `a` into `b`. `move(a)` reads `a` destructively when necessary.) $(T2 moveEmplace, - Similar to $(D move) but assumes `target` is uninitialized.) + Similar to `move` but assumes `target` is uninitialized.) $(T2 moveAll, Moves all elements from one range to another.) $(T2 moveEmplaceAll, - Similar to $(D moveAll) but assumes all elements in `target` are uninitialized.) + Similar to `moveAll` but assumes all elements in `target` are uninitialized.) $(T2 moveSome, Moves as many elements as possible from one range to another.) $(T2 moveEmplaceSome, - Similar to $(D moveSome) but assumes all elements in `target` are uninitialized.) + Similar to `moveSome` but assumes all elements in `target` are uninitialized.) $(T2 remove, Removes elements from a range in-place, and returns the shortened range.) $(T2 reverse, - If $(D a = [1, 2, 3]), $(D reverse(a)) changes it to $(D [3, 2, 1]).) + If `a = [1, 2, 3]`, `reverse(a)` changes it to `[3, 2, 1]`.) $(T2 strip, Strips all leading and trailing elements equal to a value, or that satisfy a predicate. - If $(D a = [1, 1, 0, 1, 1]), then $(D strip(a, 1)) and - $(D strip!(e => e == 1)(a)) returns $(D [0]).) + If `a = [1, 1, 0, 1, 1]`, then `strip(a, 1)` and + `strip!(e => e == 1)(a)` returns `[0]`.) $(T2 stripLeft, Strips all leading elements equal to a value, or that satisfy a - predicate. If $(D a = [1, 1, 0, 1, 1]), then $(D stripLeft(a, 1)) and - $(D stripLeft!(e => e == 1)(a)) returns $(D [0, 1, 1]).) + predicate. If `a = [1, 1, 0, 1, 1]`, then `stripLeft(a, 1)` and + `stripLeft!(e => e == 1)(a)` returns `[0, 1, 1]`.) $(T2 stripRight, Strips all trailing elements equal to a value, or that satisfy a predicate. - If $(D a = [1, 1, 0, 1, 1]), then $(D stripRight(a, 1)) and - $(D stripRight!(e => e == 1)(a)) returns $(D [1, 1, 0]).) + If `a = [1, 1, 0, 1, 1]`, then `stripRight(a, 1)` and + `stripRight!(e => e == 1)(a)` returns `[1, 1, 0]`.) $(T2 swap, Swaps two values.) $(T2 swapAt, @@ -70,7 +70,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_mutation.d) +Source: $(PHOBOSSRC std/algorithm/mutation.d) Macros: T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) @@ -78,36 +78,36 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) module std.algorithm.mutation; import std.range.primitives; -import std.traits : isArray, isBlitAssignable, isNarrowString, Unqual, isSomeChar; -// FIXME -import std.typecons; // : tuple, Tuple; +import std.traits : isArray, isAssignable, isBlitAssignable, isNarrowString, + Unqual, isSomeChar, isMutable; +import std.meta : allSatisfy; +import std.typecons : tuple, Tuple; // 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. - -The $(D bringToFront) function treats strings at the code unit +`bringToFront` takes two ranges `front` and `back`, which may +be of different types. Considering the concatenation of `front` and +`back` one unified range, `bringToFront` rotates that unified +range such that all elements in `back` are brought to the beginning +of the unified range. The relative ordering of elements in `front` +and `back`, respectively, remains unchanged. + +The `bringToFront` function treats strings at the code unit level and it is not concerned with Unicode character integrity. -$(D bringToFront) is designed as a function for moving elements +`bringToFront` is designed as a function for moving elements in ranges, not as a string function. Performs $(BIGOH max(front.length, back.length)) evaluations of $(D swap). +The `bringToFront` function 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. + 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 +Either `front` and `back` are disjoint, or `back` is +reachable from `front` and `front` is not reachable from $(D back). Params: @@ -115,10 +115,10 @@ Params: back = a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) Returns: - The number of elements brought to the front, i.e., the length of $(D back). + The number of elements brought to the front, i.e., the length of `back`. See_Also: - $(HTTP sgi.com/tech/stl/_rotate.html, STL's rotate) + $(LINK2 http://en.cppreference.com/w/cpp/algorithm/rotate, STL's `rotate`) */ size_t bringToFront(InputRange, ForwardRange)(InputRange front, ForwardRange back) if (isInputRange!InputRange && isForwardRange!ForwardRange) @@ -145,71 +145,8 @@ if (isInputRange!InputRange && isForwardRange!ForwardRange) return bringToFrontImpl(frontW, backW); } -private size_t bringToFrontImpl(InputRange, ForwardRange)(InputRange front, ForwardRange back) -if (isInputRange!InputRange && isForwardRange!ForwardRange) -{ - import std.array : sameHead; - import std.range : take, Take; - 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(InputRange == Take!ForwardRange)) - { - 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 +The simplest use of `bringToFront` is for rotating elements in a buffer. For example: */ @safe unittest @@ -221,10 +158,10 @@ buffer. For example: } /** -The $(D front) range may actually "step over" the $(D back) +The `front` range may actually "step over" the `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). +comfortably right-bounded subranges like `arr[0 .. 4]` above. In +the example below, `r2` is a right subrange of `r1`. */ @safe unittest { @@ -275,15 +212,78 @@ Unicode integrity is not preserved: assert(b == "\247a"); } +private size_t bringToFrontImpl(InputRange, ForwardRange)(InputRange front, ForwardRange back) +if (isInputRange!InputRange && isForwardRange!ForwardRange) +{ + import std.array : sameHead; + import std.range : take, Take; + 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, "Expected front to be empty"); + // Left side was shorter. Let's step into the back. + static if (is(InputRange == Take!ForwardRange)) + { + front = take(back0, nswaps); + } + else + { + immutable subresult = bringToFront(take(back0, nswaps), + back); + if (!semidone) result += subresult; + break; // done + } + } + } + return result; +} + @safe unittest { import std.algorithm.comparison : equal; import std.conv : text; - import std.random : Random, unpredictableSeed, uniform; + import std.random : Random = Xorshift, uniform; // a more elaborate test { - auto rnd = Random(unpredictableSeed); + auto rnd = Random(123_456_789); 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); @@ -337,7 +337,7 @@ Unicode integrity is not preserved: assert(equal(arr, [1, 2, 3, 4, 5, 6, 7])); } - // Bugzilla 16959 + // https://issues.dlang.org/show_bug.cgi?id=16959 auto arr = ['4', '5', '6', '7', '1', '2', '3']; auto p = bringToFront(arr[0 .. 4], arr[4 .. $]); @@ -351,11 +351,11 @@ private enum bool areCopyCompatibleArrays(T1, T2) = // copy /** -Copies the content of $(D source) into $(D target) and returns the -remaining (unfilled) part of $(D target). +Copies the content of `source` into `target` and returns the +remaining (unfilled) part of `target`. -Preconditions: $(D target) shall have enough room to accommodate -the entirety of $(D source). +Preconditions: `target` shall have enough room to accommodate +the entirety of `source`. Params: source = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) @@ -363,62 +363,66 @@ Params: Returns: The unfilled part of target - -See_Also: - $(HTTP sgi.com/tech/stl/_copy.html, STL's _copy) */ TargetRange copy(SourceRange, TargetRange)(SourceRange source, TargetRange target) -if (areCopyCompatibleArrays!(SourceRange, TargetRange)) +if (isInputRange!SourceRange && isOutputRange!(TargetRange, ElementType!SourceRange)) { - const tlen = target.length; - const slen = source.length; - assert(tlen >= slen, - "Cannot copy a source range into a smaller target range."); - - immutable overlaps = __ctfe || () @trusted { - return source.ptr < target.ptr + tlen && - target.ptr < source.ptr + slen; }(); - - if (overlaps) + static if (areCopyCompatibleArrays!(SourceRange, TargetRange)) { - foreach (idx; 0 .. slen) - target[idx] = source[idx]; - return target[slen .. tlen]; - } - else - { - // Array specialization. This uses optimized memory copying - // routines under the hood and is about 10-20x faster than the - // generic implementation. - target[0 .. slen] = source[]; - return target[slen .. $]; - } -} + const tlen = target.length; + const slen = source.length; + assert(tlen >= slen, + "Cannot copy a source range into a smaller target range."); -/// ditto -TargetRange copy(SourceRange, TargetRange)(SourceRange source, TargetRange target) -if (!areCopyCompatibleArrays!(SourceRange, TargetRange) && - isInputRange!SourceRange && - isOutputRange!(TargetRange, ElementType!SourceRange)) -{ - // Specialize for 2 random access ranges. - // Typically 2 random access ranges are faster iterated by common - // index than by x.popFront(), y.popFront() pair - static if (isRandomAccessRange!SourceRange && - hasLength!SourceRange && - hasSlicing!TargetRange && - isRandomAccessRange!TargetRange && - hasLength!TargetRange) - { - auto len = source.length; - foreach (idx; 0 .. len) - target[idx] = source[idx]; - return target[len .. target.length]; + immutable overlaps = () @trusted { + return source.ptr < target.ptr + tlen && + target.ptr < source.ptr + slen; }(); + + if (overlaps) + { + if (source.ptr < target.ptr) + { + foreach_reverse (idx; 0 .. slen) + target[idx] = source[idx]; + } + else + { + foreach (idx; 0 .. slen) + target[idx] = source[idx]; + } + return target[slen .. tlen]; + } + else + { + // Array specialization. This uses optimized memory copying + // routines under the hood and is about 10-20x faster than the + // generic implementation. + target[0 .. slen] = source[]; + return target[slen .. $]; + } } else { - put(target, source); - return target; + // Specialize for 2 random access ranges. + // Typically 2 random access ranges are faster iterated by common + // index than by x.popFront(), y.popFront() pair + static if (isRandomAccessRange!SourceRange && + hasLength!SourceRange && + hasSlicing!TargetRange && + isRandomAccessRange!TargetRange && + hasLength!TargetRange) + { + auto len = source.length; + foreach (idx; 0 .. len) + target[idx] = source[idx]; + return target[len .. target.length]; + } + else + { + foreach (element; source) + put(target, element); + return target; + } } } @@ -446,7 +450,7 @@ range elements, different types of ranges are accepted: } /** -To _copy at most $(D n) elements from a range, you may want to use +To _copy at most `n` elements from a range, you may want to use $(REF take, std,range): */ @safe unittest @@ -475,7 +479,7 @@ use $(LREF filter): /** $(REF retro, std,range) can be used to achieve behavior similar to -$(HTTP sgi.com/tech/stl/copy_backward.html, STL's copy_backward'): +$(LINK2 http://en.cppreference.com/w/cpp/algorithm/copy_backward, STL's `copy_backward`'): */ @safe unittest { @@ -511,7 +515,15 @@ $(HTTP sgi.com/tech/stl/copy_backward.html, STL's copy_backward'): assert(a[4 .. 9] == [6, 7, 8, 9, 10]); } - { // Test for bug 7898 + // https://issues.dlang.org/show_bug.cgi?id=21724 + { + int[] a = [1, 2, 3, 4]; + copy(a[0 .. 2], a[1 .. 3]); + assert(a == [1, 1, 2, 4]); + } + + // https://issues.dlang.org/show_bug.cgi?id=7898 + { enum v = { import std.algorithm; @@ -524,29 +536,58 @@ $(HTTP sgi.com/tech/stl/copy_backward.html, STL's copy_backward'): } } +// https://issues.dlang.org/show_bug.cgi?id=13650 @safe unittest { - // Issue 13650 import std.meta : AliasSeq; - foreach (Char; AliasSeq!(char, wchar, dchar)) - { + static foreach (Char; AliasSeq!(char, wchar, dchar)) + {{ Char[3] a1 = "123"; Char[6] a2 = "456789"; assert(copy(a1[], a2[]) is a2[3..$]); assert(a1[] == "123"); assert(a2[] == "123789"); + }} +} + +// https://issues.dlang.org/show_bug.cgi?id=18804 +@safe unittest +{ + static struct NullSink + { + void put(E)(E) {} + } + int line = 0; + struct R + { + int front; + @property bool empty() { return line == 1; } + void popFront() { line = 1; } } + R r; + copy(r, NullSink()); + assert(line == 1); } /** -Assigns $(D value) to each element of input _range $(D range). +Assigns `value` to each element of input range `range`. + +Alternatively, instead of using a single `value` to fill the `range`, +a `filter` $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) +can be provided. The length of `filler` and `range` do not need to match, but +`filler` must not be empty. Params: range = An - $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that exposes references to its elements and has assignable elements value = Assigned to each element of range + filler = A + $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) + representing the _fill pattern. + +Throws: If `filler` is empty. See_Also: $(LREF uninitializedFill) @@ -583,7 +624,8 @@ if ((isInputRange!Range && is(typeof(range.front = value)) || assert(a == [ 5, 5, 5, 5 ]); } -// issue 16342, test fallback on mutable narrow strings +// test fallback on mutable narrow strings +// https://issues.dlang.org/show_bug.cgi?id=16342 @safe unittest { char[] chars = ['a', 'b']; @@ -646,7 +688,7 @@ if ((isInputRange!Range && is(typeof(range.front = value)) || chars[1 .. 3].fill(':'); assert(chars == "a::d"); } -// end issue 16342 +// end https://issues.dlang.org/show_bug.cgi?id=16342 @safe unittest { @@ -712,18 +754,7 @@ if ((isInputRange!Range && is(typeof(range.front = value)) || } } -/** -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. - -Params: - range = An $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) - that exposes references to its elements and has assignable elements. - filler = The - $(REF_ALTTEXT forward _range, isForwardRange, std,_range,primitives) - representing the _fill pattern. - */ +/// ditto void fill(InputRange, ForwardRange)(InputRange range, ForwardRange filler) if (isInputRange!InputRange && (isForwardRange!ForwardRange @@ -832,12 +863,12 @@ if (isInputRange!InputRange } /** -Initializes all elements of $(D range) with their $(D .init) value. +Initializes all elements of `range` with their `.init` value. Assumes that the elements of the range are uninitialized. Params: range = An - $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that exposes references to its elements and has assignable elements @@ -859,9 +890,9 @@ if (isInputRange!Range && hasLvalueElements!Range && hasAssignableElements!Range //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).initializer(); - if (p.ptr) + static if (!__traits(isZeroInit, T)) { + auto p = typeid(T).initializer(); for ( ; !range.empty ; range.popFront() ) { static if (__traits(isStaticArray, T)) @@ -972,7 +1003,7 @@ if (is(Range == char[]) || is(Range == wchar[])) assert(!typeid(S3).initializer().ptr); assert( typeid(S4).initializer().ptr); - foreach (S; AliasSeq!(S1, S2, S3, S4)) + static foreach (S; AliasSeq!(S1, S2, S3, S4)) { //initializeAll { @@ -1033,8 +1064,8 @@ to its `.init` value after it is moved into target, otherwise it is left unchanged. Preconditions: -If source has internal pointers that point to itself, it cannot be moved, and -will trigger an assertion failure. +If source has internal pointers that point to itself and doesn't define +opPostMove, it cannot be moved, and will trigger an assertion failure. Params: source = Data to copy. @@ -1043,11 +1074,7 @@ Params: */ void move(T)(ref T source, ref T target) { - // test @safe destructible - static if (__traits(compiles, (T t) @safe {})) - trustedMoveImpl(source, target); - else - moveImpl(source, target); + moveImpl(target, source); } /// For non-struct types, `move` just performs `target = source`: @@ -1123,7 +1150,7 @@ pure nothrow @safe @nogc unittest move(s21, s22); assert(s21 == s22); }); - // Issue 5661 test(1) + // https://issues.dlang.org/show_bug.cgi?id=5661 test(1) static struct S3 { static struct X { int n = 0; ~this(){n = 0;} } @@ -1136,7 +1163,7 @@ pure nothrow @safe @nogc unittest assert(s31.x.n == 0); assert(s32.x.n == 1); - // Issue 5661 test(2) + // https://issues.dlang.org/show_bug.cgi?id=5661 test(2) static struct S4 { static struct X { int n = 0; this(this){n = 0;} } @@ -1149,7 +1176,7 @@ pure nothrow @safe @nogc unittest assert(s41.x.n == 0); assert(s42.x.n == 1); - // Issue 13990 test + // https://issues.dlang.org/show_bug.cgi?id=13990 test class S5; S5 s51; @@ -1160,13 +1187,9 @@ pure nothrow @safe @nogc unittest } /// Ditto -T move(T)(ref T source) +T move(T)(return scope ref T source) { - // test @safe destructible - static if (__traits(compiles, (T t) @safe {})) - return trustedMoveImpl(source); - else - return moveImpl(source); + return moveImpl(source); } /// Non-copyable structs can still be moved: @@ -1185,34 +1208,71 @@ pure nothrow @safe @nogc unittest assert(s2.a == 2); } -private void trustedMoveImpl(T)(ref T source, ref T target) @trusted +/// `opPostMove` will be called if defined: +pure nothrow @safe @nogc unittest { - moveImpl(source, target); + struct S + { + int a; + void opPostMove(const ref S old) + { + assert(a == old.a); + a++; + } + } + S s1; + s1.a = 41; + S s2 = move(s1); + assert(s2.a == 42); } -private void moveImpl(T)(ref T source, ref T target) +// https://issues.dlang.org/show_bug.cgi?id=20869 +// `move` should propagate the attributes of `opPostMove` +@system unittest +{ + static struct S + { + void opPostMove(const ref S old) nothrow @system + { + __gshared int i; + new int(i++); // Force @gc impure @system + } + } + + alias T = void function() @system nothrow; + static assert(is(typeof({ S s; move(s); }) == T)); + static assert(is(typeof({ S s; move(s, s); }) == T)); +} + +private void moveImpl(T)(ref scope T target, ref return scope T source) { import std.traits : hasElaborateDestructor; static if (is(T == struct)) { - if (&source == &target) return; + // Unsafe when compiling without -dip1000 + if ((() @trusted => &source == &target)()) return; + // Destroy target before overwriting it static if (hasElaborateDestructor!T) target.__xdtor(); } // move and emplace source into target - moveEmplace(source, target); + moveEmplaceImpl(target, source); } -private T trustedMoveImpl(T)(ref T source) @trusted +private T moveImpl(T)(ref return scope T source) { - return moveImpl(source); + // Properly infer safety from moveEmplaceImpl as the implementation below + // might void-initialize pointers in result and hence needs to be @trusted + if (false) moveEmplaceImpl(source, source); + + return trustedMoveImpl(source); } -private T moveImpl(T)(ref T source) +private T trustedMoveImpl(T)(ref return scope T source) @trusted { T result = void; - moveEmplace(source, result); + moveEmplaceImpl(result, source); return result; } @@ -1239,7 +1299,7 @@ private T moveImpl(T)(ref T source) assert(s21 == s22); }); - // Issue 5661 test(1) + // https://issues.dlang.org/show_bug.cgi?id=5661 test(1) static struct S3 { static struct X { int n = 0; ~this(){n = 0;} } @@ -1252,7 +1312,7 @@ private T moveImpl(T)(ref T source) assert(s31.x.n == 0); assert(s32.x.n == 1); - // Issue 5661 test(2) + // https://issues.dlang.org/show_bug.cgi?id=5661 test(2) static struct S4 { static struct X { int n = 0; this(this){n = 0;} } @@ -1265,7 +1325,7 @@ private T moveImpl(T)(ref T source) assert(s41.x.n == 0); assert(s42.x.n == 1); - // Issue 13990 test + // https://issues.dlang.org/show_bug.cgi?id=13990 test class S5; S5 s51; @@ -1289,14 +1349,16 @@ private T moveImpl(T)(ref T source) assert(a.n == 0); } -@safe unittest//Issue 6217 +// https://issues.dlang.org/show_bug.cgi?id=6217 +@safe unittest { import std.algorithm.iteration : map; auto x = map!"a"([1,2,3]); x = move(x); } -@safe unittest// Issue 8055 +// https://issues.dlang.org/show_bug.cgi?id=8055 +@safe unittest { static struct S { @@ -1316,7 +1378,8 @@ private T moveImpl(T)(ref T source) assert(b.x == 0); } -@system unittest// Issue 8057 +// https://issues.dlang.org/show_bug.cgi?id=8057 +@system unittest { int n = 10; struct S @@ -1338,7 +1401,7 @@ private T moveImpl(T)(ref T source) auto b = foo(a); assert(b.x == 1); - // Regression 8171 + // Regression https://issues.dlang.org/show_bug.cgi?id=8171 static struct Array(T) { // nested struct has no member @@ -1352,37 +1415,34 @@ private T moveImpl(T)(ref T source) move(x, x); } -/** - * Similar to $(LREF move) but assumes `target` is uninitialized. This - * is more efficient because `source` can be blitted over `target` - * without destroying or initializing it first. - * - * Params: - * source = value to be moved into target - * target = uninitialized value to be filled by source - */ -void moveEmplace(T)(ref T source, ref T target) @system +private void moveEmplaceImpl(T)(ref scope T target, ref return scope T source) { import core.stdc.string : memcpy, memset; import std.traits : hasAliasing, hasElaborateAssign, hasElaborateCopyConstructor, hasElaborateDestructor, - isAssignable; + hasElaborateMove, + isAssignable, isStaticArray; static if (!is(T == class) && hasAliasing!T) if (!__ctfe) { import std.exception : doesPointTo; - assert(!doesPointTo(source, source), "Cannot move object with internal pointer."); + assert(!(doesPointTo(source, source) && !hasElaborateMove!T), + "Cannot move object with internal pointer unless `opPostMove` is defined."); } static if (is(T == struct)) { - assert(&source !is &target, "source and target must not be identical"); + // Unsafe when compiling without -dip1000 + assert((() @trusted => &source !is &target)(), "source and target must not be identical"); static if (hasElaborateAssign!T || !isAssignable!T) - memcpy(&target, &source, T.sizeof); + () @trusted { memcpy(&target, &source, T.sizeof); }(); else target = source; + static if (hasElaborateMove!T) + __move_post_blt(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) @@ -1393,13 +1453,20 @@ void moveEmplace(T)(ref T source, ref T target) @system else enum sz = T.sizeof; - auto init = typeid(T).initializer(); - if (init.ptr is null) // null ptr means initialize to 0s - memset(&source, 0, sz); + static if (__traits(isZeroInit, T)) + () @trusted { memset(&source, 0, sz); }(); else - memcpy(&source, init.ptr, sz); + { + auto init = typeid(T).initializer(); + () @trusted { memcpy(&source, init.ptr, sz); }(); + } } } + else static if (isStaticArray!T) + { + for (size_t i = 0; i < source.length; ++i) + move(source[i], target[i]); + } else { // Primitive data (including pointers and arrays) or class - @@ -1408,6 +1475,20 @@ void moveEmplace(T)(ref T source, ref T target) @system } } +/** + * Similar to $(LREF move) but assumes `target` is uninitialized. This + * is more efficient because `source` can be blitted over `target` + * without destroying or initializing it first. + * + * Params: + * source = value to be moved into target + * target = uninitialized value to be filled by source + */ +void moveEmplace(T)(ref T source, ref T target) pure @system +{ + moveEmplaceImpl(target, source); +} + /// pure nothrow @nogc @system unittest { @@ -1433,6 +1514,24 @@ pure nothrow @nogc @system unittest assert(val == 0); } +// https://issues.dlang.org/show_bug.cgi?id=18913 +@safe unittest +{ + static struct NoCopy + { + int payload; + ~this() { } + @disable this(this); + } + + static void f(NoCopy[2]) { } + + NoCopy[2] ncarray = [ NoCopy(1), NoCopy(2) ]; + + static assert(!__traits(compiles, f(ncarray))); + f(move(ncarray)); +} + // moveAll /** Calls `move(a, b)` for each element `a` in `src` and the corresponding @@ -1447,9 +1546,9 @@ Params: src = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with movable elements. tgt = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with - elements that elements from $(D src) can be moved into. + elements that elements from `src` can be moved into. -Returns: The leftover portion of $(D tgt) after all elements from $(D src) have +Returns: The leftover portion of `tgt` after all elements from `src` have been moved. */ InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt) @@ -1528,7 +1627,7 @@ private InputRange2 moveAllImpl(alias moveOp, InputRange1, InputRange2)( && hasSlicing!InputRange2 && isRandomAccessRange!InputRange2) { auto toMove = src.length; - assert(toMove <= tgt.length); + assert(toMove <= tgt.length, "Source buffer needs to be smaller or equal to the target buffer."); foreach (idx; 0 .. toMove) moveOp(src[idx], tgt[idx]); return tgt[toMove .. tgt.length]; @@ -1537,7 +1636,7 @@ private InputRange2 moveAllImpl(alias moveOp, InputRange1, InputRange2)( { for (; !src.empty; src.popFront(), tgt.popFront()) { - assert(!tgt.empty); + assert(!tgt.empty, "Source buffer needs to be smaller or equal to the target buffer."); moveOp(src.front, tgt.front); } return tgt; @@ -1554,7 +1653,7 @@ Params: src = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with movable elements. tgt = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with - elements that elements from $(D src) can be moved into. + elements that elements from `src` can be moved into. Returns: The leftover portions of the two ranges after one or the other of the ranges have been exhausted. @@ -1624,15 +1723,15 @@ 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 +"b", "aBc" ]) according to `toUpper(a) < toUpper(b)`. That +algorithm might choose to swap the two equivalent strings `"abc"` +and `"aBc"`. That does not affect the sorting since both +`["abc", "aBc", "b" ]` and `[ "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 +`[ "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). @@ -1642,12 +1741,12 @@ 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 +Generally, the `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 +constraints. Similarly, `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. +algorithms in this module parameterized by `SwapStrategy` all +choose `SwapStrategy.unstable` as the default. */ enum SwapStrategy @@ -1672,8 +1771,6 @@ enum SwapStrategy /// @safe unittest { - import std.stdio; - import std.algorithm.sorting : partition; int[] a = [0, 1, 2, 3]; assert(remove!(SwapStrategy.stable)(a, 1) == [0, 2, 3]); a = [0, 1, 2, 3]; @@ -1699,11 +1796,27 @@ enum SwapStrategy assert(arr == [10, 9, 8, 4, 5, 6, 7, 3, 2, 1]); } +private template isValidIntegralTuple(T) +{ + import std.traits : isIntegral; + import std.typecons : isTuple; + static if (isTuple!T) + { + enum isValidIntegralTuple = T.length == 2 && + isIntegral!(typeof(T.init[0])) && isIntegral!(typeof(T.init[0])); + } + else + { + enum isValidIntegralTuple = isIntegral!T; + } +} + + /** Eliminates elements at given offsets from `range` and returns the shortened range. -For example, here is how to _remove a single element from an array: +For example, here is how to remove a single element from an array: ---- string[] a = [ "a", "b", "c", "d" ]; @@ -1711,9 +1824,9 @@ a = a.remove(1); // remove element at offset 1 assert(a == [ "a", "c", "d"]); ---- -Note that `remove` does not change the length of the original _range directly; -instead, it returns the shortened _range. If its return value is not assigned to -the original _range, the original _range will retain its original length, though +Note that `remove` does not change the length of the original range directly; +instead, it returns the shortened range. If its return value is not assigned to +the original range, the original range will retain its original length, though its contents will have changed: ---- @@ -1722,7 +1835,7 @@ assert(remove(a, 1) == [ 3, 7, 8 ]); assert(a == [ 3, 7, 8, 8 ]); ---- -The element at _offset `1` has been removed and the rest of the elements have +The element at offset `1` has been removed and the rest of the elements have shifted up to fill its place, however, the original array remains of the same length. This is because all functions in `std.algorithm` only change $(I content), not $(I topology). The value `8` is repeated because $(LREF move) was @@ -1730,7 +1843,7 @@ invoked to rearrange elements, and on integers `move` simply copies the source to the destination. To replace `a` with the effect of the removal, simply assign the slice returned by `remove` to it, as shown in the first example. -Multiple indices can be passed into $(D remove). In that case, +Multiple indices can be passed into `remove`. In that case, elements at the respective indices are all removed. The indices must be passed in increasing order, otherwise an exception occurs. @@ -1741,9 +1854,21 @@ assert(remove(a, 1, 3, 5) == ---- (Note that 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. +in the array as it is being progressively shortened.) + +Tuples of two integral offsets can be used to remove an indices range: + +---- +int[] a = [ 3, 4, 5, 6, 7]; +assert(remove(a, 1, tuple(1, 3), 9) == [ 3, 6, 7 ]); +---- + +The tuple passes in a range closed to the left and open to +the right (consistent with built-in slices), e.g. `tuple(1, 3)` +means indices `1` and `2` but not `3`. + +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 ]; @@ -1751,56 +1876,247 @@ assert(remove(a, 1, tuple(3, 5), 9) == [ 0, 2, 5, 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). +the array. 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). +pass `SwapStrategy.unstable` to `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 +In the case above, the element at slot `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 stability requirement, `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 bidirectional ranges that have assignable +The function `remove` works on bidirectional ranges that have assignable lvalue elements. The moving strategy is (listed from fastest to slowest): -$(UL $(LI If $(D s == SwapStrategy.unstable && isRandomAccessRange!Range && + +$(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 == +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 +calls to `range.popFront`.) + $(LI Otherwise, elements are moved +incrementally towards the front of `range`; a given element is never moved several times, but more elements are moved than in the previous -cases.)) +cases.) +) Params: s = a SwapStrategy to determine if the original order needs to be preserved - range = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,_range,primitives) + range = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) with a length member offset = which element(s) to remove Returns: - a range containing all of the elements of range with offset removed - */ + A range containing all of the elements of range with offset removed. +*/ +Range remove +(SwapStrategy s = SwapStrategy.stable, Range, Offset ...) +(Range range, Offset offset) +if (Offset.length >= 1 && allSatisfy!(isValidIntegralTuple, Offset)) +{ + // Activate this check when the deprecation of non-integral tuples is over + //import std.traits : isIntegral; + //import std.typecons : isTuple; + //static foreach (T; Offset) + //{ + //static if (isTuple!T) + //{ + //static assert(T.length == 2 && + //isIntegral!(typeof(T.init[0])) && isIntegral!(typeof(T.init[0])), + //"Each offset must be an integral or a tuple of two integrals." ~ + //"Use `arr.remove(pos1, pos2)` or `arr.remove(tuple(start, begin))`"); + //} + //else + //{ + //static assert(isIntegral!T, + //"Each offset must be an integral or a tuple of two integrals." ~ + //"Use `arr.remove(pos1, pos2)` or `arr.remove(tuple(start, begin))`"); + //} + //} + return removeImpl!s(range, offset); +} + +deprecated("Use of non-integral tuples is deprecated. Use remove(tuple(start, end).") Range remove -(SwapStrategy s = SwapStrategy.stable, Range, Offset...) +(SwapStrategy s = SwapStrategy.stable, Range, Offset ...) (Range range, Offset offset) -if (s != SwapStrategy.stable - && isBidirectionalRange!Range - && hasLvalueElements!Range - && hasLength!Range - && Offset.length >= 1) +if (Offset.length >= 1 && !allSatisfy!(isValidIntegralTuple, Offset)) +{ + return removeImpl!s(range, offset); +} + +/// +@safe pure unittest +{ + import std.typecons : tuple; + + auto a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.stable)(a, 1) == [ 0, 2, 3, 4, 5 ]); + a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.stable)(a, 1, 3) == [ 0, 2, 4, 5] ); + a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 6)) == [ 0, 2 ]); + + a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.unstable)(a, 1) == [0, 5, 2, 3, 4]); + a = [ 0, 1, 2, 3, 4, 5 ]; + assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4)) == [0, 5, 4]); +} + +/// +@safe pure unittest +{ + import std.typecons : tuple; + + // Delete an index + assert([4, 5, 6].remove(1) == [4, 6]); + + // Delete multiple indices + assert([4, 5, 6, 7, 8].remove(1, 3) == [4, 6, 8]); + + // Use an indices range + assert([4, 5, 6, 7, 8].remove(tuple(1, 3)) == [4, 7, 8]); + + // Use an indices range and individual indices + assert([4, 5, 6, 7, 8].remove(0, tuple(1, 3), 4) == [7]); +} + +/// `SwapStrategy.unstable` is faster, but doesn't guarantee the same order of the original array +@safe pure unittest +{ + assert([5, 6, 7, 8].remove!(SwapStrategy.stable)(1) == [5, 7, 8]); + assert([5, 6, 7, 8].remove!(SwapStrategy.unstable)(1) == [5, 8, 7]); +} + +private auto removeImpl(SwapStrategy s, Range, Offset...)(Range range, Offset offset) +{ + static if (isNarrowString!Range) + { + static assert(isMutable!(typeof(range[0])), + "Elements must be mutable to remove"); + static assert(s == SwapStrategy.stable, + "Only stable removing can be done for character arrays"); + return removeStableString(range, offset); + } + else + { + static assert(isBidirectionalRange!Range, + "Range must be bidirectional"); + static assert(hasLvalueElements!Range, + "Range must have Lvalue elements (see std.range.hasLvalueElements)"); + + static if (s == SwapStrategy.unstable) + { + static assert(hasLength!Range, + "Range must have `length` for unstable remove"); + return removeUnstable(range, offset); + } + else static if (s == SwapStrategy.stable) + return removeStable(range, offset); + else + static assert(false, + "Only SwapStrategy.stable and SwapStrategy.unstable are supported"); + } +} + +@safe unittest +{ + import std.exception : assertThrown; + import std.range; + + // https://issues.dlang.org/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 +{ + import std.range; + int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; + 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 ]); + // https://issues.dlang.org/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 ]; + 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 ]; + 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 ]; + 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]); +} + +// https://issues.dlang.org/show_bug.cgi?id=11576 +@safe unittest +{ + auto arr = [1,2,3]; + arr = arr.remove!(SwapStrategy.unstable)(2); + assert(arr == [1,2]); + +} + +// https://issues.dlang.org/show_bug.cgi?id=12889 +@safe unittest +{ + import std.range; + 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))); + } +} + +@safe unittest +{ + char[] chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + remove(chars, 4); + assert(chars == ['a', 'b', 'c', 'd', 'f', 'g', 'h', 'h']); + + char[] bigChars = "∑œ∆¬é˚˙ƒé∂ß¡¡".dup; + assert(remove(bigChars, tuple(4, 6), 8) == ("∑œ∆¬˙ƒ∂ß¡¡")); + + import std.exception : assertThrown; + assertThrown(remove(bigChars.dup, 1, 0)); + assertThrown(remove(bigChars.dup, tuple(4, 3))); +} + +private Range removeUnstable(Range, Offset...)(Range range, Offset offset) { Tuple!(size_t, "pos", size_t, "len")[offset.length] blackouts; foreach (i, v; offset) @@ -1849,7 +2165,7 @@ if (s != SwapStrategy.stable break; } // Advance to next blackout on the left - assert(blackouts[left].pos >= tgtPos); + assert(blackouts[left].pos >= tgtPos, "Next blackout on the left shouldn't appear before the target."); tgt.popFrontExactly(blackouts[left].pos - tgtPos); tgtPos = blackouts[left].pos; @@ -1879,14 +2195,7 @@ if (s != SwapStrategy.stable 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) +private Range removeStable(Range, Offset...)(Range range, Offset offset) { auto result = range; auto src = range, tgt = range; @@ -1930,149 +2239,121 @@ if (s == SwapStrategy.stable return result; } -/// -@safe pure unittest +private Range removeStableString(Range, Offset...)(Range range, Offset offsets) { - import std.typecons : tuple; - - auto a = [ 0, 1, 2, 3, 4, 5 ]; - assert(remove!(SwapStrategy.stable)(a, 1) == [ 0, 2, 3, 4, 5 ]); - a = [ 0, 1, 2, 3, 4, 5 ]; - assert(remove!(SwapStrategy.stable)(a, 1, 3) == [ 0, 2, 4, 5] ); - a = [ 0, 1, 2, 3, 4, 5 ]; - assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 6)) == [ 0, 2 ]); - - a = [ 0, 1, 2, 3, 4, 5 ]; - assert(remove!(SwapStrategy.unstable)(a, 1) == [0, 5, 2, 3, 4]); - a = [ 0, 1, 2, 3, 4, 5 ]; - assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4)) == [0, 5, 4]); -} + import std.utf : stride; + size_t charIdx = 0; + size_t dcharIdx = 0; + size_t charShift = 0; -@safe unittest -{ - import std.exception : assertThrown; - import std.range; + void skipOne() + { + charIdx += stride(range[charIdx .. $]); + ++dcharIdx; + } - // 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)); -} + void copyBackOne() + { + auto encodedLen = stride(range[charIdx .. $]); + foreach (j; charIdx .. charIdx + encodedLen) + range[j - charShift] = range[j]; + charIdx += encodedLen; + ++dcharIdx; + } -@safe unittest -{ - import std.range; - int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; - 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 ]); + foreach (pass, i; offsets) + { + static if (is(typeof(i[0])) && is(typeof(i[1]))) + { + auto from = i[0]; + auto delta = i[1] - i[0]; + } + else + { + auto from = i; + enum delta = 1; + } - 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 ]); + import std.exception : enforce; + enforce(dcharIdx <= from && delta >= 0, + "remove(): incorrect ordering of elements to remove"); - 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 ]); + while (dcharIdx < from) + static if (pass == 0) + skipOne(); + else + copyBackOne(); - 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 ]); + auto mark = charIdx; + while (dcharIdx < from + delta) + skipOne(); + charShift += charIdx - mark; + } - 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 ]; - assert(remove!(SwapStrategy.stable)(a, 1, tuple(3, 5)) - == [ 0, 2, 5, 6, 7, 8, 9, 10]); + foreach (i; charIdx .. range.length) + range[i - charShift] = range[i]; - a = iota(0, 10).array(); - assert(remove!(SwapStrategy.unstable)(a, tuple(1, 4), tuple(6, 7)) - == [0, 9, 8, 7, 4, 5]); + return range[0 .. $ - charShift]; } +// Use of dynamic arrays as offsets is too error-prone +// https://issues.dlang.org/show_bug.cgi?id=12086 +// Activate these tests once the deprecation period of remove with non-integral tuples is over @safe unittest { - // Issue 11576 - auto arr = [1,2,3]; - arr = arr.remove!(SwapStrategy.unstable)(2); - assert(arr == [1,2]); + //static assert(!__traits(compiles, [0, 1, 2, 3, 4].remove([1, 3]) == [0, 3, 4])); + static assert(__traits(compiles, [0, 1, 2, 3, 4].remove(1, 3) == [0, 2, 4])); + //static assert(!__traits(compiles, assert([0, 1, 2, 3, 4].remove([1, 3, 4]) == [0, 3, 4]))); + //static assert(!__traits(compiles, assert([0, 1, 2, 3, 4].remove(tuple(1, 3, 4)) == [0, 3, 4]))); -} - -@safe unittest -{ - import std.range; - // 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))); - } + import std.range : only; + //static assert(!__traits(compiles, assert([0, 1, 2, 3, 4].remove(only(1, 3)) == [0, 3, 4]))); + static assert(__traits(compiles, assert([0, 1, 2, 3, 4].remove(1, 3) == [0, 2, 4]))); } /** Reduces the length of the -$(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,_range,primitives) $(D range) by removing -elements that satisfy $(D pred). If $(D s = SwapStrategy.unstable), +$(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) `range` by removing +elements that satisfy `pred`. If `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), +to eliminate. If `s = SwapStrategy.stable` (the default), elements are moved progressively to front such that their relative order is preserved. Returns the filtered range. Params: range = a bidirectional ranges with lvalue elements + or mutable character arrays Returns: - the range with all of the elements where $(D pred) is $(D true) + the range with all of the elements where `pred` is `true` removed */ -Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range) -(Range range) -if (isBidirectionalRange!Range - && hasLvalueElements!Range) +Range remove(alias pred, SwapStrategy s = SwapStrategy.stable, Range)(Range range) { import std.functional : unaryFun; - auto result = range; - static if (s != SwapStrategy.stable) + alias pred_ = unaryFun!pred; + static if (isNarrowString!Range) { - for (;!range.empty;) - { - if (!unaryFun!pred(range.front)) - { - range.popFront(); - continue; - } - move(range.back, range.front); - range.popBack(); - result.popBack(); - } + static assert(isMutable!(typeof(range[0])), + "Elements must be mutable to remove"); + static assert(s == SwapStrategy.stable, + "Only stable removing can be done for character arrays"); + return removePredString!pred_(range); } 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(); - } + static assert(isBidirectionalRange!Range, + "Range must be bidirectional"); + static assert(hasLvalueElements!Range, + "Range must have Lvalue elements (see std.range.hasLvalueElements)"); + static if (s == SwapStrategy.unstable) + return removePredUnstable!pred_(range); + else static if (s == SwapStrategy.stable) + return removePredStable!pred_(range); + else + static assert(false, + "Only SwapStrategy.stable and SwapStrategy.unstable are supported"); } - return result; } /// @@ -2104,10 +2385,10 @@ if (isBidirectionalRange!Range [ 1, 3, 3, 4, 5, 5, 6 ]); } -@nogc @system unittest +@nogc @safe unittest { // @nogc test - int[10] arr = [0,1,2,3,4,5,6,7,8,9]; + static int[] arr = [0,1,2,3,4,5,6,7,8,9]; alias pred = e => e < 5; auto r = arr[].remove!(SwapStrategy.unstable)(0); @@ -2125,19 +2406,20 @@ if (isBidirectionalRange!Range import std.meta : AliasSeq; import std.range : iota, only; import std.typecons : Tuple; - alias S = Tuple!(int[2]); + alias E = Tuple!(int, int); + alias S = Tuple!(E); S[] soffsets; foreach (start; 0 .. 5) foreach (end; min(start+1,5) .. 5) - soffsets ~= S([start,end]); - alias D = Tuple!(int[2],int[2]); + soffsets ~= S(E(start,end)); + alias D = Tuple!(E, E); D[] doffsets; foreach (start1; 0 .. 10) foreach (end1; min(start1+1,10) .. 10) foreach (start2; end1 .. 10) foreach (end2; min(start2+1,10) .. 10) - doffsets ~= D([start1,end1],[start2,end2]); - alias T = Tuple!(int[2],int[2],int[2]); + doffsets ~= D(E(start1,end1),E(start2,end2)); + alias T = Tuple!(E, E, E); T[] toffsets; foreach (start1; 0 .. 15) foreach (end1; min(start1+1,15) .. 15) @@ -2145,7 +2427,7 @@ if (isBidirectionalRange!Range foreach (end2; min(start2+1,15) .. 15) foreach (start3; end2 .. 15) foreach (end3; min(start3+1,15) .. 15) - toffsets ~= T([start1,end1],[start2,end2],[start3,end3]); + toffsets ~= T(E(start1,end1),E(start2,end2),E(start3,end3)); static void verify(O...)(int[] r, int len, int removed, bool stable, O offsets) { @@ -2154,7 +2436,7 @@ if (isBidirectionalRange!Range assert(r.all!(e => all!(o => e < o[0] || e >= o[1])(offsets.only))); } - foreach (offsets; AliasSeq!(soffsets,doffsets,toffsets)) + static foreach (offsets; AliasSeq!(soffsets,doffsets,toffsets)) foreach (os; offsets) { int len = 5*os.length; @@ -2179,109 +2461,186 @@ if (isBidirectionalRange!Range } } -// reverse -/** -Reverses $(D r) in-place. Performs $(D r.length / 2) evaluations of $(D -swap). -Params: - r = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) - with swappable elements or a random access range with a length member +@safe unittest +{ + char[] chars = "abcdefg".dup; + assert(chars.remove!(dc => dc == 'c' || dc == 'f') == "abdeg"); + assert(chars == "abdegfg"); -See_Also: - $(HTTP sgi.com/tech/stl/_reverse.html, STL's _reverse), $(REF retro, std,range) for a lazy reversed range view -*/ -void reverse(Range)(Range r) -if (isBidirectionalRange!Range && !isRandomAccessRange!Range - && hasSwappableElements!Range) + assert(chars.remove!"a == 'd'" == "abegfg"); + + char[] bigChars = "¥^¨^©é√∆π".dup; + assert(bigChars.remove!(dc => dc == "¨"d[0] || dc == "é"d[0]) == "¥^^©√∆π"); +} + +private Range removePredUnstable(alias pred, Range)(Range range) { - while (!r.empty) + auto result = range; + for (;!range.empty;) { - swap(r.front, r.back); - r.popFront(); - if (r.empty) break; - r.popBack(); + if (!pred(range.front)) + { + range.popFront(); + continue; + } + move(range.back, range.front); + range.popBack(); + result.popBack(); } + return result; } -/// -@safe unittest +private Range removePredStable(alias pred, Range)(Range range) { - int[] arr = [ 1, 2, 3 ]; - reverse(arr); - assert(arr == [ 3, 2, 1 ]); + auto result = range; + auto tgt = range; + for (; !range.empty; range.popFront()) + { + if (pred(range.front)) + { + // yank this guy + result.popBack(); + continue; + } + // keep this guy + move(range.front, tgt.front); + tgt.popFront(); + } + return result; } -///ditto -void reverse(Range)(Range r) -if (isRandomAccessRange!Range && hasLength!Range) +private Range removePredString(alias pred, SwapStrategy s = SwapStrategy.stable, Range) +(Range 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++) + import std.utf : decode; + import std.functional : unaryFun; + + alias pred_ = unaryFun!pred; + + size_t charIdx = 0; + size_t charShift = 0; + while (charIdx < range.length) + { + size_t start = charIdx; + if (pred_(decode(range, charIdx))) + { + charShift += charIdx - start; + break; + } + } + while (charIdx < range.length) { - r.swapAt(i, last-i); + size_t start = charIdx; + auto doRemove = pred_(decode(range, charIdx)); + auto encodedLen = charIdx - start; + if (doRemove) + charShift += encodedLen; + else + foreach (i; start .. charIdx) + range[i - charShift] = range[i]; } -} -@safe unittest -{ - 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]); + return range[0 .. $ - charShift]; } +// reverse /** -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. +Reverses `r` in-place. Performs `r.length / 2` evaluations of `swap`. +UTF sequences consisting of multiple code units are preserved properly. Params: - s = a narrow string + r = a $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) + with either swappable elements, a random access range with a length member, + or a narrow string -Bugs: - When passing a sting with unicode modifiers on characters, such as $(D \u0301), +Returns: `r` + +Note: + When passing a string with unicode modifiers on characters, such as `\u0301`, this function will not properly keep the position of the modifier. For example, - reversing $(D ba\u0301d) ("bád") will result in d\u0301ab ("d́ab") instead of - $(D da\u0301b) ("dáb"). -*/ -void reverse(Char)(Char[] s) -if (isNarrowString!(Char[]) && !is(Char == const) && !is(Char == immutable)) -{ - import std.string : representation; - import std.utf : stride; + reversing `ba\u0301d` ("bád") will result in d\u0301ab ("d́ab") instead of + `da\u0301b` ("dáb"). - auto r = representation(s); - for (size_t i = 0; i < s.length; ) +See_Also: $(REF retro, std,range) for a lazy reverse without changing `r` +*/ +Range reverse(Range)(Range r) +if (isBidirectionalRange!Range && + (hasSwappableElements!Range || + (hasAssignableElements!Range && hasLength!Range && isRandomAccessRange!Range) || + (isNarrowString!Range && isAssignable!(ElementType!Range)))) +{ + static 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++) + { + r.swapAt(i, last - i); + } + return r; + } + else static if (isNarrowString!Range && isAssignable!(ElementType!Range)) { - immutable step = stride(s, i); - if (step > 1) + import std.string : representation; + import std.utf : stride; + + auto raw = representation(r); + for (size_t i = 0; i < r.length;) { - .reverse(r[i .. i + step]); - i += step; + immutable step = stride(r, i); + if (step > 1) + { + .reverse(raw[i .. i + step]); + i += step; + } + else + { + ++i; + } } - else + reverse(raw); + return r; + } + else + { + while (!r.empty) { - ++i; + swap(r.front, r.back); + r.popFront(); + if (r.empty) break; + r.popBack(); } + return r; } - reverse(r); +} + +/// +@safe unittest +{ + int[] arr = [ 1, 2, 3 ]; + assert(arr.reverse == [ 3, 2, 1 ]); +} + +@safe unittest +{ + 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]; + assert(range.reverse == [3, 2, 1]); } /// @safe unittest { char[] arr = "hello\U00010143\u0100\U00010143".dup; - reverse(arr); - assert(arr == "\U00010143\u0100\U00010143olleh"); + assert(arr.reverse == "\U00010143\u0100\U00010143olleh"); } @safe unittest @@ -2307,12 +2666,12 @@ if (isNarrowString!(Char[]) && !is(Char == const) && !is(Char == immutable)) 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) + The `stripLeft` function will strip the `front` of the range, + the `stripRight` function will strip the `back` of the range, + while the `strip` function will strip both the `front` and `back` of the range. - Note that the $(D strip) and $(D stripRight) functions require the range to + Note that the `strip` and `stripRight` functions require the range to be a $(LREF BidirectionalRange) range. All of these functions come in two varieties: one takes a target element, @@ -2445,18 +2804,18 @@ if (isBidirectionalRange!Range && is(typeof(pred(range.back)) : bool)) // 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) +Swaps `lhs` and `rhs`. The instances `lhs` and `rhs` are moved in +memory, without ever calling `opAssign`, nor any other function. `T` need not be assignable at all to be swapped. -If $(D lhs) and $(D rhs) reference the same instance, then nothing is done. +If `lhs` and `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 +`lhs` and `rhs` must be mutable. If `T` is a struct or union, then its fields must also all be (recursively) mutable. Params: - lhs = Data to be swapped with $(D rhs). - rhs = Data to be swapped with $(D lhs). + lhs = Data to be swapped with `rhs`. + rhs = Data to be swapped with `lhs`. */ void swap(T)(ref T lhs, ref T rhs) @trusted pure nothrow @nogc if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) @@ -2499,8 +2858,15 @@ if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) return; } - // For non-struct types, suffice to do the classic swap - auto tmp = lhs; + // For non-elaborate-assign types, suffice to do the classic swap + static if (__traits(hasCopyConstructor, T)) + { + // don't invoke any elaborate constructors either + T tmp = void; + tmp = lhs; + } + else + auto tmp = lhs; lhs = rhs; rhs = tmp; } @@ -2585,9 +2951,9 @@ if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) static assert(!__traits(compiles, swap(const1, const2))); } +// https://issues.dlang.org/show_bug.cgi?id=4789 @safe unittest { - //Bug# 4789 int[1] s = [1]; swap(s, s); @@ -2625,23 +2991,24 @@ if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) assert(s.i3 == 2); } +// https://issues.dlang.org/show_bug.cgi?id=11853 @safe unittest { - //11853 import std.traits : isAssignable; alias T = Tuple!(int, double); static assert(isAssignable!T); } +// https://issues.dlang.org/show_bug.cgi?id=12024 @safe unittest { - // 12024 import std.datetime; SysTime a, b; swap(a, b); } -@system unittest // 9975 +// https://issues.dlang.org/show_bug.cgi?id=9975 +@system unittest { import std.exception : doesPointTo, mayPointTo; static struct S2 @@ -2686,6 +3053,31 @@ if (isBlitAssignable!T && !is(typeof(lhs.proxySwap(rhs)))) swap(b1, b2); } +// issue 20732 +@safe unittest +{ + static struct A + { + int x; + this(scope ref return const A other) + { + import std.stdio; + x = other.x; + // note, struct functions inside @safe functions infer ALL + // attributes, so the following 3 lines are meant to prevent this. + new int; // prevent @nogc inference + writeln("impure"); // prevent pure inference + throw new Exception(""); // prevent nothrow inference + } + } + + A a1, a2; + swap(a1, a2); + + A[1] a3, a4; + swap(a3, a4); +} + /// ditto void swap(T)(ref T lhs, ref T rhs) if (is(typeof(lhs.proxySwap(rhs)))) @@ -2820,16 +3212,16 @@ if (isInputRange!R1 && isInputRange!R2) // 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 +Swaps all elements of `r1` with successive elements in `r2`. +Returns a tuple containing the remainder portions of `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. Params: - r1 = an $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + r1 = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with swappable elements - r2 = an $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + r2 = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with swappable elements Returns: @@ -2860,7 +3252,7 @@ if (hasSwappableElements!InputRange1 && hasSwappableElements!InputRange2 } /** -Initializes each element of $(D range) with $(D value). +Initializes each element of `range` with `value`. Assumes that the elements of the range are uninitialized. This is of interest for structs that define copy constructors (for all other types, $(LREF fill) and @@ -2868,7 +3260,7 @@ uninitializedFill are equivalent). Params: range = An - $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that exposes references to its elements and has assignable elements value = Assigned to each element of range @@ -2885,7 +3277,7 @@ if (isInputRange!Range && hasLvalueElements!Range && is(typeof(range.front = val alias T = ElementType!Range; static if (hasElaborateAssign!T) { - import std.conv : emplaceRef; + import core.internal.lifetime : emplaceRef; // Must construct stuff by the book for (; !range.empty; range.popFront()) diff --git a/libphobos/src/std/algorithm/package.d b/libphobos/src/std/algorithm/package.d index 4c9a72f..6aacd51 100644 --- a/libphobos/src/std/algorithm/package.d +++ b/libphobos/src/std/algorithm/package.d @@ -79,9 +79,12 @@ $(TR $(SUBREF iteration, group) $(SUBREF iteration, joiner) $(SUBREF iteration, map) + $(SUBREF iteration, mean) $(SUBREF iteration, permutations) $(SUBREF iteration, reduce) + $(SUBREF iteration, splitWhen) $(SUBREF iteration, splitter) + $(SUBREF iteration, substitute) $(SUBREF iteration, sum) $(SUBREF iteration, uniq) ) @@ -152,12 +155,12 @@ Many functions in this package are parameterized with a $(GLOSSARY predicate). The predicate may be any suitable callable type (a function, a delegate, a $(GLOSSARY functor), or a lambda), 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 +expression that uses the symbol `a` (for unary functions) or the +symbols `a` and `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. +comparison predicates is `"a == b"` for unordered operations and +`"a < b"` for ordered operations. Example: @@ -184,7 +187,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/_algorithm/package.d) +Source: $(PHOBOSSRC std/algorithm/package.d) */ module std.algorithm; diff --git a/libphobos/src/std/algorithm/searching.d b/libphobos/src/std/algorithm/searching.d index 09073f6..d6d02ba 100644 --- a/libphobos/src/std/algorithm/searching.d +++ b/libphobos/src/std/algorithm/searching.d @@ -1,64 +1,64 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _searching algorithms. +It contains generic searching algorithms. $(SCRIPT inhibitQuickIndex = 1;) $(BOOKTABLE Cheat Sheet, $(TR $(TH Function Name) $(TH Description)) $(T2 all, - $(D all!"a > 0"([1, 2, 3, 4])) returns $(D true) because all elements + `all!"a > 0"([1, 2, 3, 4])` returns `true` because all elements are positive) $(T2 any, - $(D any!"a > 0"([1, 2, -3, -4])) returns $(D true) because at least one + `any!"a > 0"([1, 2, -3, -4])` returns `true` because at least one element is positive) $(T2 balancedParens, - $(D balancedParens("((1 + 1) / 2)")) returns $(D true) because the + `balancedParens("((1 + 1) / 2)")` returns `true` because the string has balanced parentheses.) $(T2 boyerMooreFinder, - $(D find("hello world", boyerMooreFinder("or"))) returns $(D "orld") + `find("hello world", boyerMooreFinder("or"))` returns `"orld"` using the $(LINK2 https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm, Boyer-Moore _algorithm).) $(T2 canFind, - $(D canFind("hello world", "or")) returns $(D true).) + `canFind("hello world", "or")` returns `true`.) $(T2 count, Counts elements that are equal to a specified value or satisfy a - predicate. $(D count([1, 2, 1], 1)) returns $(D 2) and - $(D count!"a < 0"([1, -3, 0])) returns $(D 1).) + predicate. `count([1, 2, 1], 1)` returns `2` and + `count!"a < 0"([1, -3, 0])` returns `1`.) $(T2 countUntil, - $(D countUntil(a, b)) returns the number of steps taken in $(D a) to - reach $(D b); for example, $(D countUntil("hello!", "o")) returns - $(D 4).) + `countUntil(a, b)` returns the number of steps taken in `a` to + reach `b`; for example, `countUntil("hello!", "o")` returns + `4`.) $(T2 commonPrefix, - $(D commonPrefix("parakeet", "parachute")) returns $(D "para").) + `commonPrefix("parakeet", "parachute")` returns `"para"`.) $(T2 endsWith, - $(D endsWith("rocks", "ks")) returns $(D true).) + `endsWith("rocks", "ks")` returns `true`.) $(T2 find, - $(D find("hello world", "or")) returns $(D "orld") using linear search. - (For binary search refer to $(REF sortedRange, std,range).)) + `find("hello world", "or")` returns `"orld"` using linear search. + (For binary search refer to $(REF SortedRange, std,range).)) $(T2 findAdjacent, - $(D findAdjacent([1, 2, 3, 3, 4])) returns the subrange starting with - two equal adjacent elements, i.e. $(D [3, 3, 4]).) + `findAdjacent([1, 2, 3, 3, 4])` returns the subrange starting with + two equal adjacent elements, i.e. `[3, 3, 4]`.) $(T2 findAmong, - $(D findAmong("abcd", "qcx")) returns $(D "cd") because $(D 'c') is - among $(D "qcx").) + `findAmong("abcd", "qcx")` returns `"cd"` because `'c'` is + among `"qcx"`.) $(T2 findSkip, - If $(D a = "abcde"), then $(D findSkip(a, "x")) returns $(D false) and - leaves $(D a) unchanged, whereas $(D findSkip(a, "c")) advances $(D a) - to $(D "de") and returns $(D true).) + If `a = "abcde"`, then `findSkip(a, "x")` returns `false` and + leaves `a` unchanged, whereas `findSkip(a, "c")` advances `a` + to `"de"` and returns `true`.) $(T2 findSplit, - $(D findSplit("abcdefg", "de")) returns the three ranges $(D "abc"), - $(D "de"), and $(D "fg").) + `findSplit("abcdefg", "de")` returns a tuple of three ranges `"abc"`, + `"de"`, and `"fg"`.) $(T2 findSplitAfter, - $(D findSplitAfter("abcdefg", "de")) returns the two ranges - $(D "abcde") and $(D "fg").) +`findSplitAfter("abcdefg", "de")` returns a tuple of two ranges `"abcde"` + and `"fg"`.) $(T2 findSplitBefore, - $(D findSplitBefore("abcdefg", "de")) returns the two ranges $(D "abc") - and $(D "defg").) + `findSplitBefore("abcdefg", "de")` returns a tuple of two ranges `"abc"` + and `"defg"`.) $(T2 minCount, - $(D minCount([2, 1, 1, 4, 1])) returns $(D tuple(1, 3)).) + `minCount([2, 1, 1, 4, 1])` returns `tuple(1, 3)`.) $(T2 maxCount, - $(D maxCount([2, 4, 1, 4, 1])) returns $(D tuple(4, 2)).) + `maxCount([2, 4, 1, 4, 1])` returns `tuple(4, 2)`.) $(T2 minElement, Selects the minimal element of a range. `minElement([3, 4, 1, 2])` returns `1`.) @@ -67,27 +67,24 @@ $(T2 maxElement, `maxElement([3, 4, 1, 2])` returns `4`.) $(T2 minIndex, Index of the minimal element of a range. - `minElement([3, 4, 1, 2])` returns `2`.) + `minIndex([3, 4, 1, 2])` returns `2`.) $(T2 maxIndex, Index of the maximal element of a range. - `maxElement([3, 4, 1, 2])` returns `1`.) + `maxIndex([3, 4, 1, 2])` returns `1`.) $(T2 minPos, - $(D minPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [1, 3, 4, 1]), + `minPos([2, 3, 1, 3, 4, 1])` returns the subrange `[1, 3, 4, 1]`, i.e., positions the range at the first occurrence of its minimal element.) $(T2 maxPos, - $(D maxPos([2, 3, 1, 3, 4, 1])) returns the subrange $(D [4, 1]), + `maxPos([2, 3, 1, 3, 4, 1])` returns the subrange `[4, 1]`, i.e., positions the range at the first occurrence of its maximal element.) -$(T2 mismatch, - $(D mismatch("parakeet", "parachute")) returns the two ranges - $(D "keet") and $(D "chute").) $(T2 skipOver, - Assume $(D a = "blah"). Then $(D skipOver(a, "bi")) leaves $(D a) - unchanged and returns $(D false), whereas $(D skipOver(a, "bl")) - advances $(D a) to refer to $(D "ah") and returns $(D true).) + Assume `a = "blah"`. Then `skipOver(a, "bi")` leaves `a` + unchanged and returns `false`, whereas `skipOver(a, "bl")` + advances `a` to refer to `"ah"` and returns `true`.) $(T2 startsWith, - $(D startsWith("hello, world", "hello")) returns $(D true).) + `startsWith("hello, world", "hello")` returns `true`.) $(T2 until, Lazily iterates a range until a specific value is found.) ) @@ -98,33 +95,34 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_searching.d) +Source: $(PHOBOSSRC std/algorithm/searching.d) Macros: T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) */ module std.algorithm.searching; -// FIXME -import std.functional; // : unaryFun, binaryFun; +import std.functional : unaryFun, binaryFun; +import std.meta : allSatisfy; import std.range.primitives; import std.traits; -// FIXME -import std.typecons; // : Tuple, Flag, Yes, No; +import std.typecons : Tuple, Flag, Yes, No, tuple; /++ -Checks if $(I _all) of the elements verify $(D pred). +Checks if $(I _all) of the elements satisfy `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). + Returns `true` if and only if the input range `range` is empty + or $(I _all) values found in `range` satisfy the predicate `pred`. + Performs (at most) $(BIGOH range.length) evaluations of `pred`. +/ bool all(Range)(Range range) - if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) + if (isInputRange!Range) { + static assert(is(typeof(unaryFun!pred(range.front))), + "`" ~ pred.stringof[1..$-1] ~ "` isn't a unary predicate function for range.front"); import std.functional : not; return find!(not!(unaryFun!pred))(range).empty; @@ -139,7 +137,7 @@ template all(alias pred = "a") } /++ -$(D all) can also be used without a predicate, if its items can be +`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. @@ -154,20 +152,22 @@ are true. { int x = 1; assert(all!(a => a > x)([2, 3])); + assert(all!"a == 0x00c9"("\xc3\x89")); // Test that `all` auto-decodes. } /++ -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). +Checks if $(I _any) of the elements satisfies `pred`. +`!any` can be used to verify that $(I none) of the elements satisfy +`pred`. This is sometimes called `exists` in other languages. +/ 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). + Returns `true` if and only if the input range `range` is non-empty + and $(I _any) value found in `range` satisfies the predicate + `pred`. + Performs (at most) $(BIGOH range.length) evaluations of `pred`. +/ bool any(Range)(Range range) if (isInputRange!Range && is(typeof(unaryFun!pred(range.front)))) @@ -185,8 +185,8 @@ template any(alias pred = "a") } /++ -$(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 +`any` can also be used without a predicate, if its items can be +evaluated to true or false in a conditional statement. `!any` can be a convenient way to quickly test that $(I none) of the elements of a range evaluate to true. +/ @@ -208,14 +208,15 @@ evaluate to true. { auto a = [ 1, 2, 0, 4 ]; assert(any!"a == 2"(a)); + assert(any!"a == 0x3000"("\xe3\x80\x80")); // Test that `any` auto-decodes. } // 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 +Checks whether `r` has "balanced parentheses", i.e. all instances +of `lPar` are closed by corresponding instances of `rPar`. The +parameter `maxNestingLevel` controls the nesting level allowed. The +most common uses are the default or `0`. In the latter case, no nesting is allowed. Params: @@ -233,14 +234,25 @@ bool balancedParens(Range, E)(Range r, E lPar, E rPar, if (isInputRange!(Range) && is(typeof(r.front == lPar))) { size_t count; - for (; !r.empty; r.popFront()) + + static if (is(immutable ElementEncodingType!Range == immutable E) && isNarrowString!Range) + { + import std.utf : byCodeUnit; + auto rn = r.byCodeUnit; + } + else + { + alias rn = r; + } + + for (; !rn.empty; rn.popFront()) { - if (r.front == lPar) + if (rn.front == lPar) { if (count > maxNestingLevel) return false; ++count; } - else if (r.front == rPar) + else if (rn.front == rPar) { if (!count) return false; --count; @@ -250,7 +262,7 @@ if (isInputRange!(Range) && is(typeof(r.front == lPar))) } /// -@safe unittest +@safe pure unittest { auto s = "1 + (2 * (3 + 1 / 2)"; assert(!balancedParens(s, '(', ')')); @@ -260,21 +272,23 @@ if (isInputRange!(Range) && is(typeof(r.front == lPar))) assert(!balancedParens(s, '(', ')', 0)); s = "1 + (2 * 3 + 1) / (2 - 5)"; assert(balancedParens(s, '(', ')', 0)); + s = "f(x) = ⌈x⌉"; + assert(balancedParens(s, '⌈', '⌉')); } /** - * Sets up Boyer-Moore matching for use with $(D find) below. + * Sets up Boyer-Moore matching for use with `find` below. * By default, elements are compared for equality. * - * $(D BoyerMooreFinder) allocates GC memory. + * `BoyerMooreFinder` allocates GC memory. * * Params: * pred = Predicate used to compare elements. * needle = A random-access range with length and slicing. * * Returns: - * An instance of $(D BoyerMooreFinder) that can be used with $(D find()) to - * invoke the Boyer-Moore matching algorithm for finding of $(D needle) in a + * An instance of `BoyerMooreFinder` that can be used with `find()` to + * invoke the Boyer-Moore matching algorithm for finding of `needle` in a * given haystack. */ struct BoyerMooreFinder(alias pred, Range) @@ -284,7 +298,7 @@ private: ptrdiff_t[ElementType!(Range)] occ; // GC allocated Range needle; - ptrdiff_t occurrence(ElementType!(Range) c) + ptrdiff_t occurrence(ElementType!(Range) c) scope { auto p = c in occ; return p ? *p : -1; @@ -347,7 +361,7 @@ public: } /// - Range beFound(Range haystack) + Range beFound(Range haystack) scope { import std.algorithm.comparison : max; @@ -363,7 +377,7 @@ public: if (npos == 0) return haystack[hpos .. $]; --npos; } - hpos += max(skip[npos], cast(sizediff_t) npos - occurrence(haystack[npos+hpos])); + hpos += max(skip[npos], cast(ptrdiff_t) npos - occurrence(haystack[npos+hpos])); } return haystack[$ .. $]; } @@ -407,7 +421,7 @@ Returns the common prefix of two ranges. Params: pred = The predicate to use in comparing elements for commonality. Defaults - to equality $(D "a == b"). + to equality `"a == b"`. r1 = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) of elements. @@ -416,9 +430,9 @@ Params: elements. Returns: -A slice of $(D r1) which contains the characters that both ranges start with, +A slice of `r1` which contains the characters that both ranges start with, if the first argument is a string; otherwise, the same as the result of -$(D takeExactly(r1, n)), where $(D n) is the number of elements in the common +`takeExactly(r1, n)`, where `n` is the number of elements in the common prefix of both ranges. See_Also: @@ -542,12 +556,12 @@ if (isNarrowString!R1 && isNarrowString!R2) assert(commonPrefix(cast(int[]) null, [1, 2, 3]).empty); assert(commonPrefix(cast(int[]) null, cast(int[]) null).empty); - foreach (S; AliasSeq!(char[], const(char)[], string, + static foreach (S; AliasSeq!(char[], const(char)[], string, wchar[], const(wchar)[], wstring, dchar[], const(dchar)[], dstring)) { - foreach (T; AliasSeq!(string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(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); @@ -557,7 +571,7 @@ if (isNarrowString!R1 && isNarrowString!R2) 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 + // https://issues.dlang.org/show_bug.cgi?id=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("Пиво")); @@ -567,7 +581,7 @@ if (isNarrowString!R1 && isNarrowString!R2) 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("П"))); @@ -591,26 +605,26 @@ if (isNarrowString!R1 && isNarrowString!R2) // 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 first version counts the number of elements `x` in `r` for +which `pred(x, value)` is `true`. `pred` defaults to +equality. Performs $(BIGOH haystack.length) evaluations of `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 +The second version returns the number of times `needle` occurs in +`haystack`. Throws an exception if `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). +are not considered, for example `count("aaa", "aa")` is `1`, not +`2`. -The third version counts the elements for which $(D pred(x)) is $(D -true). Performs $(BIGOH haystack.length) evaluations of $(D pred). +The third version counts the elements for which `pred(x)` is $(D +true). Performs $(BIGOH haystack.length) evaluations of `pred`. The fourth version counts the number of elements in a range. It is an optimization for the third version: if the given range has the `length` property the count is returned right away, otherwise performs $(BIGOH haystack.length) to walk the range. -Note: Regardless of the overload, $(D count) will not accept -infinite ranges for $(D haystack). +Note: Regardless of the overload, `count` will not accept +infinite ranges for `haystack`. Params: pred = The predicate to evaluate. @@ -723,7 +737,7 @@ if (isInputRange!R && !isInfinite!R) assert(count("日本語") == 3); } -// Issue 11253 +// https://issues.dlang.org/show_bug.cgi?id=11253 @safe nothrow unittest { assert([1, 2, 3].count([2, 3]) == 1); @@ -732,7 +746,7 @@ if (isInputRange!R && !isInfinite!R) /++ Counts elements in the given $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) - until the given predicate is true for one of the given $(D needles). + until the given predicate is true for one of the given `needles`. Params: pred = The predicate for determining when to stop counting. @@ -742,13 +756,14 @@ if (isInputRange!R && !isInfinite!R) needles = Either a single element, or a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) of elements, to be evaluated in turn against each - element in $(D haystack) under the given predicate. + element in `haystack` under the given predicate. 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. + `haystack` before reaching an element for which + `startsWith!pred(haystack, needles)` is `true`. If + `startsWith!pred(haystack, needles)` is not `true` for any element in + `haystack`, then `-1` is returned. If only `pred` is provided, + `pred(haystack)` is tested for each element. See_Also: $(REF indexOf, std,string) +/ @@ -756,9 +771,7 @@ 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 .. $]))))) + && allSatisfy!(canTestStartsWith!(pred, R), Rs)) { typeof(return) result; @@ -834,8 +847,10 @@ if (isForwardRange!R } } - //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" - static if (isInfinite!R) assert(0); + // Because of https://issues.dlang.org/show_bug.cgi?id=8804 + // Avoids both "unreachable code" or "no return statement" + static if (isInfinite!R) assert(false, R.stringof ~ " must not be an" + ~ " infinite range"); else return -1; } @@ -898,18 +913,7 @@ if (isInputRange!R && assert(countUntil("hello world", "world", 'l') == 2); } -/++ - Similar to the previous overload of $(D countUntil), except that this one - evaluates only the predicate $(D pred). - - Params: - pred = Predicate to when to stop counting. - haystack = An - $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of - elements to be counted. - Returns: The number of elements which must be popped from $(D haystack) - before $(D pred(haystack.front)) is $(D true). - +/ +/// ditto ptrdiff_t countUntil(alias pred, R)(R haystack) if (isInputRange!R && is(typeof(unaryFun!pred(haystack.front)) : bool)) @@ -948,8 +952,10 @@ if (isInputRange!R && } } - //Because of @@@8804@@@: Avoids both "unreachable code" or "no return statement" - static if (isInfinite!R) assert(0); + // Because of https://issues.dlang.org/show_bug.cgi?id=8804 + // Avoids both "unreachable code" or "no return statement" + static if (isInfinite!R) assert(false, R.stringof ~ " must not be an" + ~ " inifite range"); else return -1; } @@ -995,7 +1001,7 @@ if (isInputRange!R && /** Checks if the given range ends with (one of) the given needle(s). -The reciprocal of $(D startsWith). +The reciprocal of `startsWith`. Params: pred = The predicate to use for comparing elements between the range and @@ -1013,16 +1019,15 @@ Params: Returns: 0 if the needle(s) do not occur at the end of the given range; otherwise the position of the matching needle, that is, 1 if the range ends -with $(D withOneOfThese[0]), 2 if it ends with $(D withOneOfThese[1]), and so +with `withOneOfThese[0]`, 2 if it ends with `withOneOfThese[1]`, and so on. -In the case when no needle parameters are given, return $(D true) iff back of -$(D doesThisStart) fulfils predicate $(D pred). +In the case when no needle parameters are given, return `true` iff back of +`doesThisStart` fulfils predicate `pred`. */ 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)) + allSatisfy!(canTestStartsWith!(pred, Range), Needles)) { alias haystack = doesThisEnd; alias needles = withOneOfThese; @@ -1100,7 +1105,7 @@ if (isBidirectionalRange!R1 && enum isDefaultPred = false; static if (isDefaultPred && isArray!R1 && isArray!R2 && - is(Unqual!(ElementEncodingType!R1) == Unqual!(ElementEncodingType!R2))) + is(immutable ElementEncodingType!R1 == immutable ElementEncodingType!R2)) { if (haystack.length < needle.length) return false; @@ -1121,16 +1126,27 @@ if (isBidirectionalRange!R && if (doesThisEnd.empty) return false; + static if (is(typeof(pred) : string)) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + alias predFunc = binaryFun!pred; // auto-decoding special case static if (isNarrowString!R) { + // statically determine decoding is unnecessary to evaluate pred + static if (isDefaultPred && isSomeChar!E && E.sizeof <= ElementEncodingType!R.sizeof) + return doesThisEnd[$ - 1] == withThis; // specialize for ASCII as to not change previous behavior - if (withThis <= 0x7F) - return predFunc(doesThisEnd[$ - 1], withThis); else - return predFunc(doesThisEnd.back, withThis); + { + if (withThis <= 0x7F) + return predFunc(doesThisEnd[$ - 1], withThis); + else + return predFunc(doesThisEnd.back, withThis); + } } else { @@ -1180,16 +1196,17 @@ if (isInputRange!R && import std.conv : to; import std.meta : AliasSeq; - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - { + static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 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; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { //Lots of strings assert(endsWith(to!S("abc"), to!T(""))); assert(!endsWith(to!S("abc"), to!T("a"))); @@ -1219,11 +1236,11 @@ if (isInputRange!R && 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; AliasSeq!(int, short)) - { + static foreach (T; AliasSeq!(int, short)) + {{ immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; //RA range @@ -1254,7 +1271,30 @@ if (isInputRange!R && //Non-default pred assert(endsWith!("a%10 == b%10")(arr, [14, 15])); assert(!endsWith!("a%10 == b%10")(arr, [15, 14])); - } + }} +} + +@safe pure unittest +{ + //example from issue 19727 + import std.path : asRelativePath; + string[] ext = ["abc", "def", "ghi"]; + string path = "/foo/file.def"; + assert(ext.any!(e => path.asRelativePath("/foo").endsWith(e)) == true); + assert(ext.any!(e => path.asRelativePath("/foo").startsWith(e)) == false); +} + +private enum bool hasConstEmptyMember(T) = is(typeof(((const T* a) => (*a).empty)(null)) : bool); + +// Rebindable doesn't work with structs +// see: https://github.com/dlang/phobos/pull/6136 +private template RebindableOrUnqual(T) +{ + import std.typecons : Rebindable; + static if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) + alias RebindableOrUnqual = Rebindable!T; + else + alias RebindableOrUnqual = Unqual!T; } /** @@ -1278,10 +1318,10 @@ in { assert(!r.empty, "r is an empty range"); } -body +do { alias Element = ElementType!Range; - Unqual!Element seed = r.front; + RebindableOrUnqual!Element seed = r.front; r.popFront(); return extremum!(map, selector)(r, seed); } @@ -1298,86 +1338,87 @@ if (isInputRange!Range && !isInfinite!Range && alias Element = ElementType!Range; alias CommonElement = CommonType!(Element, RangeElementType); - Unqual!CommonElement extremeElement = seedElement; + RebindableOrUnqual!CommonElement extremeElement = seedElement; - alias MapType = Unqual!(typeof(mapFun(CommonElement.init))); - MapType extremeElementMapped = mapFun(extremeElement); - // direct access via a random access range is faster - static if (isRandomAccessRange!Range) + // if we only have one statement in the loop, it can be optimized a lot better + static if (__traits(isSame, map, a => a)) { - foreach (const i; 0 .. r.length) + + // direct access via a random access range is faster + static if (isRandomAccessRange!Range) + { + foreach (const i; 0 .. r.length) + { + if (selectorFun(r[i], extremeElement)) + { + extremeElement = r[i]; + } + } + } + else { - MapType mapElement = mapFun(r[i]); - if (selectorFun(mapElement, extremeElementMapped)) + while (!r.empty) { - extremeElement = r[i]; - extremeElementMapped = mapElement; + if (selectorFun(r.front, extremeElement)) + { + extremeElement = r.front; + } + r.popFront(); } } } else { - while (!r.empty) + alias MapType = Unqual!(typeof(mapFun(CommonElement.init))); + MapType extremeElementMapped = mapFun(extremeElement); + + // direct access via a random access range is faster + static if (isRandomAccessRange!Range) { - MapType mapElement = mapFun(r.front); - if (selectorFun(mapElement, extremeElementMapped)) + foreach (const i; 0 .. r.length) { - extremeElement = r.front; - extremeElementMapped = mapElement; + MapType mapElement = mapFun(r[i]); + if (selectorFun(mapElement, extremeElementMapped)) + { + extremeElement = r[i]; + extremeElementMapped = mapElement; + } + } + } + else + { + while (!r.empty) + { + MapType mapElement = mapFun(r.front); + if (selectorFun(mapElement, extremeElementMapped)) + { + extremeElement = r.front; + extremeElementMapped = mapElement; + } + r.popFront(); } - r.popFront(); } } return extremeElement; } private auto extremum(alias selector = "a < b", Range)(Range r) - if (isInputRange!Range && !isInfinite!Range && - !is(typeof(unaryFun!selector(ElementType!(Range).init)))) +if (isInputRange!Range && !isInfinite!Range && + !is(typeof(unaryFun!selector(ElementType!(Range).init)))) { - alias Element = ElementType!Range; - Unqual!Element seed = r.front; - r.popFront(); - return extremum!selector(r, seed); + return extremum!(a => a, selector)(r); } // if we only have one statement in the loop it can be optimized a lot better private auto extremum(alias selector = "a < b", Range, RangeElementType = ElementType!Range) (Range r, RangeElementType seedElement) - if (isInputRange!Range && !isInfinite!Range && - !is(CommonType!(ElementType!Range, RangeElementType) == void) && - !is(typeof(unaryFun!selector(ElementType!(Range).init)))) +if (isInputRange!Range && !isInfinite!Range && + !is(CommonType!(ElementType!Range, RangeElementType) == void) && + !is(typeof(unaryFun!selector(ElementType!(Range).init)))) { - alias Element = ElementType!Range; - alias CommonElement = CommonType!(Element, RangeElementType); - Unqual!CommonElement extremeElement = seedElement; - alias selectorFun = binaryFun!selector; - - // direct access via a random access range is faster - static if (isRandomAccessRange!Range) - { - foreach (const i; 0 .. r.length) - { - if (selectorFun(r[i], extremeElement)) - { - extremeElement = r[i]; - } - } - } - else - { - while (!r.empty) - { - if (selectorFun(r.front, extremeElement)) - { - extremeElement = r.front; - } - r.popFront(); - } - } - return extremeElement; + return extremum!(a => a, selector)(r, seedElement); } @safe pure unittest @@ -1391,7 +1432,7 @@ private auto extremum(alias selector = "a < b", Range, assert([[0, 4], [1, 2]].extremum!("a[1]", "a > b") == [0, 4]); // use a custom comparator - import std.math : cmp; + import std.math.operations : cmp; assert([-2., 0, 5].extremum!cmp == 5.0); assert([-2., 0, 2].extremum!`cmp(a, b) < 0` == -2.0); @@ -1455,45 +1496,80 @@ private auto extremum(alias selector = "a < b", Range, assert(arr2d.extremum!"a[1]" == arr2d[1]); } +// https://issues.dlang.org/show_bug.cgi?id=17982 +@safe unittest +{ + class B + { + int val; + this(int val){ this.val = val; } + } + + const(B) doStuff(const(B)[] v) + { + return v.extremum!"a.val"; + } + assert(doStuff([new B(1), new B(0), new B(2)]).val == 0); + + const(B)[] arr = [new B(0), new B(1)]; + // can't compare directly - https://issues.dlang.org/show_bug.cgi?id=1824 + assert(arr.extremum!"a.val".val == 0); +} + // 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). +Finds an individual element in an $(REF_ALTTEXT input range, isInputRange, std,range,primitives). +Elements of `haystack` are compared with `needle` by using predicate +`pred` with `pred(haystack.front, needle)`. +`find` performs $(BIGOH walkLength(haystack)) evaluations of `pred`. -To _find the last occurrence of $(D needle) in $(D haystack), call $(D -find(retro(haystack), needle)). See $(REF retro, std,range). +The predicate is passed to $(REF binaryFun, std, functional), and can either accept a +string, or any callable that can be executed via `pred(element, element)`. -Params: +To _find the last occurrence of `needle` in a +$(REF_ALTTEXT bidirectional, isBidirectionalRange, std,range,primitives) `haystack`, +call `find(retro(haystack), needle)`. See $(REF retro, std,range). -pred = The predicate for comparing each element with the needle, defaulting to -$(D "a == b"). -The negated predicate $(D "a != b") can be used to search instead for the first -element $(I not) matching the needle. +If no `needle` is provided, `pred(haystack.front)` will be evaluated on each +element of the input range. -haystack = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) -searched in. +If `input` is a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives), +`needle` can be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) too. +In this case `startsWith!pred(haystack, needle)` is evaluated on each evaluation. -needle = The element searched for. +Note: + `find` behaves similar to `dropWhile` in other languages. -Constraints: +Complexity: + `find` performs $(BIGOH walkLength(haystack)) evaluations of `pred`. + There are specializations that improve performance by taking + advantage of $(REF_ALTTEXT bidirectional, isBidirectionalRange, std,range,primitives) + or $(REF_ALTTEXT random access, isRandomAccess, std,range,primitives) + ranges (where possible). -$(D isInputRange!InputRange && is(typeof(binaryFun!pred(haystack.front, needle) -: bool))) +Params: + + pred = The predicate for comparing each element with the needle, defaulting to equality `"a == b"`. + The negated predicate `"a != b"` can be used to search instead for the first + element $(I not) matching the needle. + + haystack = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + searched in. + + needle = The element searched for. Returns: -$(D haystack) advanced such that the front element is the one searched for; -that is, until $(D binaryFun!pred(haystack.front, needle)) is $(D true). If no -such position exists, returns an empty $(D haystack). + `haystack` advanced such that the front element is the one searched for; + that is, until `binaryFun!pred(haystack.front, needle)` is `true`. If no + such position exists, returns an empty `haystack`. -See_Also: - $(HTTP sgi.com/tech/stl/_find.html, STL's _find) - */ +See_ALso: $(LREF findAdjacent), $(LREF findAmong), $(LREF findSkip), $(LREF findSplit), $(LREF startsWith) +*/ InputRange find(alias pred = "a == b", InputRange, Element)(InputRange haystack, scope Element needle) if (isInputRange!InputRange && - is (typeof(binaryFun!pred(haystack.front, needle)) : bool)) + is (typeof(binaryFun!pred(haystack.front, needle)) : bool) && + !is (typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) { alias R = InputRange; alias E = Element; @@ -1508,7 +1584,7 @@ if (isInputRange!InputRange && // If the haystack is a SortedRange we can use binary search to find the needle. // Works only for the default find predicate and any SortedRange predicate. - // 8829 enhancement + // https://issues.dlang.org/show_bug.cgi?id=8829 import std.range : SortedRange; static if (is(InputRange : SortedRange!TT, TT) && isDefaultPred) { @@ -1535,7 +1611,8 @@ if (isInputRange!InputRange && { if (!__ctfe && canSearchInCodeUnits!char(needle)) { - static R trustedMemchr(ref R haystack, ref E needle) @trusted nothrow pure + static inout(R) trustedMemchr(ref return scope inout(R) haystack, + ref const scope E needle) @trusted nothrow pure { import core.stdc.string : memchr; auto ptr = memchr(haystack.ptr, needle, haystack.length); @@ -1562,7 +1639,7 @@ if (isInputRange!InputRange && } } - //Previous conditonal optimizations did not succeed. Fallback to + //Previous conditional optimizations did not succeed. Fallback to //unconditional implementations static if (isDefaultPred) { @@ -1594,12 +1671,12 @@ if (isInputRange!InputRange && } else static if (isArray!R) { - //10403 optimization + // https://issues.dlang.org/show_bug.cgi?id=10403 optimization static if (isDefaultPred && isIntegral!EType && EType.sizeof == 1 && isIntegralNeedle) { import std.algorithm.comparison : max, min; - R findHelper(ref R haystack, ref E needle) @trusted nothrow pure + R findHelper(return scope ref R haystack, ref E needle) @trusted nothrow pure { import core.stdc.string : memchr; @@ -1642,38 +1719,29 @@ if (isInputRange!InputRange && /// @safe unittest { - import std.algorithm.comparison : equal; - import std.container : SList; - import std.range; - import std.range.primitives : empty; + import std.range.primitives; - auto arr = assumeSorted!"a < b"([1, 2, 4, 4, 4, 4, 5, 6, 9]); - assert(find(arr, 4) == assumeSorted!"a < b"([4, 4, 4, 4, 5, 6, 9])); - assert(find(arr, 1) == arr); - assert(find(arr, 9) == assumeSorted!"a < b"([9])); - assert(find!"a > b"(arr, 4) == assumeSorted!"a < b"([5, 6, 9])); - assert(find!"a < b"(arr, 4) == arr); - assert(find(arr, 0).empty()); - assert(find(arr, 10).empty()); - assert(find(arr, 8).empty()); - - auto r = assumeSorted!"a > b"([10, 7, 3, 1, 0, 0]); - assert(find(r, 3) == assumeSorted!"a > b"([3, 1, 0, 0])); - assert(find!"a > b"(r, 8) == r); - assert(find!"a < b"(r, 5) == assumeSorted!"a > b"([3, 1, 0, 0])); + auto arr = [1, 2, 4, 4, 4, 4, 5, 6, 9]; + assert(arr.find(4) == [4, 4, 4, 4, 5, 6, 9]); + assert(arr.find(1) == arr); + assert(arr.find(9) == [9]); + assert(arr.find!((a, b) => a > b)(4) == [5, 6, 9]); + assert(arr.find!((a, b) => a < b)(4) == arr); + assert(arr.find(0).empty); + assert(arr.find(10).empty); + assert(arr.find(8).empty); 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 +@safe unittest +{ + import std.range.primitives; + import std.uni : toLower; - // Case-insensitive find of a string - string[] s = [ "Hello", "world", "!" ]; - assert(!find!("toLower(a) == b")(s, "hello").empty); + string[] s = ["Hello", "world", "!"]; + assert(s.find!((a, b) => toLower(a) == b)("hello") == s); } @safe unittest @@ -1700,9 +1768,9 @@ if (isInputRange!InputRange && @safe pure unittest { import std.meta : AliasSeq; - foreach (R; AliasSeq!(string, wstring, dstring)) + static foreach (R; AliasSeq!(string, wstring, dstring)) { - foreach (E; AliasSeq!(char, wchar, dchar)) + static foreach (E; AliasSeq!(char, wchar, dchar)) { assert(find ("hello world", 'w') == "world"); assert(find!((a,b)=>a == b)("hello world", 'w') == "world"); @@ -1741,9 +1809,9 @@ if (isInputRange!InputRange && { byte[] sarr = [1, 2, 3, 4]; ubyte[] uarr = [1, 2, 3, 4]; - foreach (arr; AliasSeq!(sarr, uarr)) + static foreach (arr; AliasSeq!(sarr, uarr)) { - foreach (T; AliasSeq!(byte, ubyte, int, uint)) + static foreach (T; AliasSeq!(byte, ubyte, int, uint)) { assert(find(arr, cast(T) 3) == arr[2 .. $]); assert(find(arr, cast(T) 9) == arr[$ .. $]); @@ -1755,9 +1823,9 @@ if (isInputRange!InputRange && assertCTFEable!dg; } +// https://issues.dlang.org/show_bug.cgi?id=11603 @safe unittest { - // Bugzilla 11603 enum Foo : ubyte { A } assert([Foo.A].find(Foo.A).empty == false); @@ -1765,35 +1833,7 @@ if (isInputRange!InputRange && assert([x].find(x).empty == false); } -/** -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 -$(REF_ALTTEXT bidirectional, isBidirectionalRange, std,range,primitives) $(D haystack) satisfying -$(D pred), call $(D find!(pred)(retro(haystack))). See $(REF retro, std,range). - -`find` behaves similar to `dropWhile` in other languages. - -Params: - -pred = The predicate for determining if a given element is the one being -searched for. - -haystack = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to -search in. - -Returns: - -$(D haystack) advanced such that the front element is the one searched for; -that is, until $(D binaryFun!pred(haystack.front, needle)) is $(D true). If no -such position exists, returns an empty $(D haystack). - -See_Also: - $(HTTP sgi.com/tech/stl/find_if.html, STL's find_if) -*/ +/// ditto InputRange find(alias pred, InputRange)(InputRange haystack) if (isInputRange!InputRange) { @@ -1847,31 +1887,7 @@ if (isInputRange!InputRange) assert(find!(a=>a%4 == 0)("日本語") == "本語"); } -/** -Finds the first occurrence of a forward range in another forward range. - -Performs $(BIGOH walkLength(haystack) * walkLength(needle)) comparisons in the -worst case. There are specializations that improve performance by taking -advantage of $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives) -or random access in the given ranges (where possible), depending on the statistics -of the two ranges' content. - -Params: - -pred = The predicate to use for comparing respective elements from the haystack -and the needle. Defaults to simple equality $(D "a == b"). - -haystack = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) -searched in. - -needle = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) -searched for. - -Returns: - -$(D haystack) advanced such that $(D needle) is a prefix of it (if no -such position exists, returns $(D haystack) advanced to termination). - */ +/// ditto R1 find(alias pred = "a == b", R1, R2)(R1 haystack, scope R2 needle) if (isForwardRange!R1 && isForwardRange!R2 && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) @@ -1887,7 +1903,7 @@ if (isForwardRange!R1 && isForwardRange!R2 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; } + static TO force(TO, T)(inout T r) @trusted { return cast(TO) r; } return force!R1(.find!(pred, Representation, Representation) (force!Representation(haystack), force!Representation(needle))); } @@ -1975,7 +1991,7 @@ if (isForwardRange!R1 && isForwardRange!R2 // Binary search can be used to find the first occurence // of the first element of the needle in haystack. // When it is found O(walklength(needle)) steps are performed. - // 8829 enhancement + // https://issues.dlang.org/show_bug.cgi?id=8829 enhancement import std.algorithm.comparison : mismatch; import std.range : SortedRange; static if (is(R1 == R2) @@ -2096,6 +2112,17 @@ if (isForwardRange!R1 && isForwardRange!R2 assert([C(1,0), C(2,0), C(3,1), C(4,0)].find!"a.x == b"(SList!int(2, 3)[]) == [C(2,0), C(3,1), C(4,0)]); } +// https://issues.dlang.org/show_bug.cgi?id=12470 +@safe unittest +{ + import std.array : replace; + inout(char)[] sanitize(inout(char)[] p) + { + return p.replace("\0", " "); + } + assert(sanitize("O\x00o") == "O o"); +} + @safe unittest { import std.algorithm.comparison : equal; @@ -2110,8 +2137,7 @@ if (isForwardRange!R1 && isForwardRange!R2 @safe unittest { - import std.range; - import std.stdio; + import std.range : assumeSorted; auto r1 = assumeSorted([1, 2, 3, 3, 3, 4, 5, 6, 7, 8, 8, 8, 10]); auto r2 = assumeSorted([3, 3, 4, 5, 6, 7, 8, 8]); @@ -2155,7 +2181,7 @@ if (isForwardRange!R1 && isForwardRange!R2 assert(find([ 1, 2, 1, 2, 3, 3 ], SList!int(2, 3)[]) == [ 2, 3, 3 ]); } -//Bug# 8334 +// https://issues.dlang.org/show_bug.cgi?id=8334 @safe unittest { import std.algorithm.iteration : filter; @@ -2171,6 +2197,12 @@ if (isForwardRange!R1 && isForwardRange!R2 assert(find(haystack, filter!"true"(needle)).empty); } +// https://issues.dlang.org/show_bug.cgi?id=11013 +@safe unittest +{ + assert(find!"a == a"("abc","abc") == "abc"); +} + // Internally used by some find() overloads above private R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, scope R2 needle) { @@ -2212,7 +2244,7 @@ private R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, scope R2 needle) } else { - assert(haystack.empty); + assert(haystack.empty, "Haystack must be empty by now"); return haystack; } } @@ -2269,7 +2301,7 @@ private R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, scope R2 needle) } /** -Finds two or more $(D needles) into a $(D haystack). The predicate $(D +Finds two or more `needles` into a `haystack`. The predicate $(D pred) is used throughout to compare elements. By default, elements are compared for equality. @@ -2278,41 +2310,41 @@ Params: pred = The predicate to use for comparing elements. haystack = The target of the search. Must be an input range. -If any of $(D needles) is a range with elements comparable to -elements in $(D haystack), then $(D haystack) must be a +If any of `needles` is a range with elements comparable to +elements in `haystack`, then `haystack` must be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) 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 +needles = One or more items to search for. Each of `needles` must +be either comparable to one element in `haystack`, or be itself a forward range with elements comparable with elements in -$(D haystack). +`haystack`. Returns: -A tuple containing $(D haystack) positioned to match one of the +A tuple containing `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 +needles) (0 if none of `needles` matched, 1 if `needles[0]` +matched, 2 if `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 +`"a"` and `'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 +The relationship between `haystack` and `needles` simply means +that one can e.g. search for individual `int`s or arrays of $(D +int)s in an array of `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 +as well: a `double[]` can be searched for an `int` or a $(D +short[]), and conversely a `long` can be searched for a `float` +or a `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 +subranges at once maximizes cache usage by moving in `haystack` as few times as possible. */ Tuple!(Range, size_t) find(alias pred = "a == b", Range, Ranges...) @@ -2418,7 +2450,7 @@ if (Ranges.length > 1 && is(typeof(startsWith!pred(haystack, needles)))) } /** - * Finds $(D needle) in $(D haystack) efficiently using the + * Finds `needle` in `haystack` efficiently using the * $(LINK2 https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm, * Boyer-Moore) method. * @@ -2427,8 +2459,8 @@ if (Ranges.length > 1 && is(typeof(startsWith!pred(haystack, needles)))) * needle = A $(LREF BoyerMooreFinder). * * Returns: - * $(D haystack) advanced such that $(D needle) is a prefix of it (if no - * such position exists, returns $(D haystack) advanced to termination). + * `haystack` advanced such that `needle` is a prefix of it (if no + * such position exists, returns `haystack` advanced to termination). */ RandomAccessRange find(RandomAccessRange, alias pred, InputRange)( RandomAccessRange haystack, scope BoyerMooreFinder!(pred, InputRange) needle) @@ -2473,16 +2505,14 @@ 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. +$(REF among, std,algorithm,comparison) for checking a value against multiple possibilities. +/ template canFind(alias pred="a == b") { - import std.meta : allSatisfy; - /++ - 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). + Returns `true` if and only if any value `v` found in the + input range `range` satisfies the predicate `pred`. + Performs (at most) $(BIGOH haystack.length) evaluations of `pred`. +/ bool canFind(Range)(Range haystack) if (is(typeof(find!pred(haystack)))) @@ -2491,8 +2521,8 @@ template canFind(alias pred="a == b") } /++ - Returns $(D true) if and only if $(D needle) can be found in $(D - range). Performs $(BIGOH haystack.length) evaluations of $(D pred). + Returns `true` if and only if `needle` can be found in $(D + range). Performs $(BIGOH haystack.length) evaluations of `pred`. +/ bool canFind(Range, Element)(Range haystack, scope Element needle) if (is(typeof(find!pred(haystack, needle)))) @@ -2501,14 +2531,14 @@ template canFind(alias pred="a == b") } /++ - Returns the 1-based index of the first needle found in $(D haystack). If no - needle is found, then $(D 0) is returned. + Returns the 1-based index of the first needle found in `haystack`. If no + needle is found, then `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 + will be `true` if one of the needles is found and `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 + `bool` for the same effect or used to get which needle was found first + without having to deal with the tuple that `LREF find` returns for the same operation. +/ size_t canFind(Range, Ranges...)(Range haystack, scope Ranges needles) @@ -2549,6 +2579,17 @@ template canFind(alias pred="a == b") assert( canFind!((string a, string b) => a.startsWith(b))(words, "bees")); } +/// Search for mutliple items in an array of items (search for needles in an array of hay stacks) +@safe unittest +{ + string s1 = "aaa111aaa"; + string s2 = "aaa222aaa"; + string s3 = "aaa333aaa"; + string s4 = "aaa444aaa"; + const hay = [s1, s2, s3, s4]; + assert(hay.canFind!(e => (e.canFind("111", "222")))); +} + @safe unittest { import std.algorithm.internal : rndstuff; @@ -2569,9 +2610,9 @@ template canFind(alias pred="a == b") // 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). +Advances `r` until it finds the first two adjacent elements `a`, +`b` that satisfy `pred(a, b)`. Performs $(BIGOH r.length) +evaluations of `pred`. Params: pred = The predicate to satisfy. @@ -2579,12 +2620,12 @@ Params: search in. Returns: -$(D r) advanced to the first occurrence of two adjacent elements that satisfy -the given predicate. If there are no such two elements, returns $(D r) advanced +`r` advanced to the first occurrence of two adjacent elements that satisfy +the given predicate. If there are no such two elements, returns `r` advanced until empty. See_Also: - $(HTTP sgi.com/tech/stl/adjacent_find.html, STL's adjacent_find) + $(LINK2 http://en.cppreference.com/w/cpp/algorithm/adjacent_find, STL's `adjacent_find`) */ Range findAdjacent(alias pred = "a == b", Range)(Range r) if (isForwardRange!(Range)) @@ -2599,6 +2640,7 @@ if (isForwardRange!(Range)) } static if (!isInfinite!Range) return ahead; + assert(0); } /// @@ -2636,7 +2678,7 @@ if (isForwardRange!(Range)) ReferenceForwardRange!int rfr = new ReferenceForwardRange!int([1, 2, 3, 2, 2, 3]); assert(equal(findAdjacent(rfr), [2, 2, 3])); - // Issue 9350 + // https://issues.dlang.org/show_bug.cgi?id=9350 assert(!repeat(1).findAdjacent().empty); } @@ -2644,9 +2686,9 @@ if (isForwardRange!(Range)) /** Searches the given range for an element that matches one of the given choices. -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). +Advances `seq` by calling `seq.popFront` until either +`find!(pred)(choices, seq.front)` is `true`, or `seq` becomes empty. +Performs $(BIGOH seq.length * choices.length) evaluations of `pred`. Params: pred = The predicate to use for determining a match. @@ -2656,17 +2698,16 @@ Params: of possible choices. Returns: -$(D seq) advanced to the first matching element, or until empty if there are no +`seq` advanced to the first matching element, or until empty if there are no matching elements. -See_Also: - $(HTTP sgi.com/tech/stl/find_first_of.html, STL's find_first_of) +See_Also: $(LREF find), $(REF std,algorithm,comparison,among) */ InputRange findAmong(alias pred = "a == b", InputRange, ForwardRange)( InputRange seq, ForwardRange choices) if (isInputRange!InputRange && isForwardRange!ForwardRange) { - for (; !seq.empty && find!pred(choices, seq.front).empty; seq.popFront()) + for (; !seq.empty && find!pred(choices.save, seq.front).empty; seq.popFront()) { } return seq; @@ -2690,10 +2731,24 @@ if (isInputRange!InputRange && isForwardRange!ForwardRange) assert(findAmong!("a == b")(b, [ 4, 6, 7 ][]).empty); } +// https://issues.dlang.org/show_bug.cgi?id=19765 +@system unittest +{ + import std.range.interfaces : inputRangeObject; + auto choices = inputRangeObject("b"); + auto f = "foobar".findAmong(choices); + assert(f == "bar"); +} + // findSkip /** - * Finds $(D needle) in $(D haystack) and positions $(D haystack) - * right after the first occurrence of $(D needle). + * Finds `needle` in `haystack` and positions `haystack` + * right after the first occurrence of `needle`. + * + * If no needle is provided, the `haystack` is advanced as long as `pred` + * evaluates to `true`. + * Similarly, the haystack is positioned so as `pred` evaluates to `false` for + * `haystack.front`. * * Params: * haystack = The @@ -2702,10 +2757,14 @@ if (isInputRange!InputRange && isForwardRange!ForwardRange) * needle = The * $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to search * for. + * pred = Custom predicate for comparison of haystack and needle * - * Returns: $(D true) if the needle was found, in which case $(D haystack) is - * positioned after the end of the first occurrence of $(D needle); otherwise - * $(D false), leaving $(D haystack) untouched. + * Returns: `true` if the needle was found, in which case `haystack` is + * positioned after the end of the first occurrence of `needle`; otherwise + * `false`, leaving `haystack` untouched. If no needle is provided, it returns + * the number of times `pred(haystack.front)` returned true. + * + * See_Also: $(LREF find) */ bool findSkip(alias pred = "a == b", R1, R2)(ref R1 haystack, R2 needle) if (isForwardRange!R1 && isForwardRange!R2 @@ -2736,6 +2795,55 @@ if (isForwardRange!R1 && isForwardRange!R2 assert(findSkip(s, "def") && s.empty); } +// https://issues.dlang.org/show_bug.cgi?id=19020 +@safe unittest +{ + static struct WrapperRange + { + string _r; + @property auto empty() { return _r.empty(); } + @property auto front() { return _r.front(); } + auto popFront() { return _r.popFront(); } + @property auto save() { return WrapperRange(_r.save); } + } + auto tmp = WrapperRange("there is a bug here: *"); + assert(!tmp.findSkip("*/")); + assert(tmp._r == "there is a bug here: *"); +} + +/// ditto +size_t findSkip(alias pred, R1)(ref R1 haystack) +if (isForwardRange!R1 && ifTestable!(typeof(haystack.front), unaryFun!pred)) +{ + size_t result; + while (!haystack.empty && unaryFun!pred(haystack.front)) + { + result++; + haystack.popFront; + } + return result; +} + +/// +@safe unittest +{ + import std.ascii : isWhite; + string s = " abc"; + assert(findSkip!isWhite(s) && s == "abc"); + assert(!findSkip!isWhite(s) && s == "abc"); + + s = " "; + assert(findSkip!isWhite(s) == 2); +} + +@safe unittest +{ + import std.ascii : isWhite; + + auto s = " "; + assert(findSkip!isWhite(s) == 2); +} + /** These functions find the first occurrence of `needle` in `haystack` and then split `haystack` as follows. @@ -2776,16 +2884,9 @@ Returns: A sub-type of `Tuple!()` of the split portions of `haystack` (see above for details). This sub-type of `Tuple!()` has `opCast` defined for `bool`. This `opCast` returns `true` when the separating `needle` was found -(`!result[1].empty`) and `false` otherwise. This enables the convenient idiom -shown in the following example. +and `false` otherwise. -Example: ---- -if (const split = haystack.findSplit(needle)) -{ - doSomethingWithSplit(split); -} ---- +See_Also: $(LREF find) */ auto findSplit(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) if (isForwardRange!R1 && isForwardRange!R2) @@ -2802,9 +2903,19 @@ if (isForwardRange!R1 && isForwardRange!R2) asTuple = rhs; } Tuple!(S1, S1, S2) asTuple; - bool opCast(T : bool)() + static if (hasConstEmptyMember!(typeof(asTuple[1]))) { - return !asTuple[1].empty; + bool opCast(T : bool)() const + { + return !asTuple[1].empty; + } + } + else + { + bool opCast(T : bool)() + { + return !asTuple[1].empty; + } } alias asTuple this; } @@ -2843,6 +2954,10 @@ if (isForwardRange!R1 && isForwardRange!R2) pos2 = ++pos1; } } + if (!n.empty) // incomplete match at the end of haystack + { + pos1 = pos2; + } return Result!(typeof(takeExactly(original, pos1)), typeof(h))(takeExactly(original, pos1), takeExactly(haystack, pos2 - pos1), @@ -2866,9 +2981,19 @@ if (isForwardRange!R1 && isForwardRange!R2) asTuple = rhs; } Tuple!(S1, S2) asTuple; - bool opCast(T : bool)() + static if (hasConstEmptyMember!(typeof(asTuple[1]))) { - return !asTuple[0].empty; + bool opCast(T : bool)() const + { + return !asTuple[1].empty; + } + } + else + { + bool opCast(T : bool)() + { + return !asTuple[1].empty; + } } alias asTuple this; } @@ -2888,24 +3013,30 @@ if (isForwardRange!R1 && isForwardRange!R2) auto original = haystack.save; auto h = haystack.save; auto n = needle.save; - size_t pos; + 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; - ++pos; + pos2 = ++pos1; } } - return Result!(typeof(takeExactly(original, pos)), - typeof(haystack))(takeExactly(original, pos), + if (!n.empty) // incomplete match at the end of haystack + { + pos1 = pos2; + haystack = h; + } + return Result!(typeof(takeExactly(original, pos1)), + typeof(haystack))(takeExactly(original, pos1), haystack); } } @@ -2926,9 +3057,19 @@ if (isForwardRange!R1 && isForwardRange!R2) asTuple = rhs; } Tuple!(S1, S2) asTuple; - bool opCast(T : bool)() + static if (hasConstEmptyMember!(typeof(asTuple[1]))) { - return !asTuple[1].empty; + bool opCast(T : bool)() const + { + return !asTuple[0].empty; + } + } + else + { + bool opCast(T : bool)() + { + return !asTuple[0].empty; + } } alias asTuple this; } @@ -2978,6 +3119,29 @@ if (isForwardRange!R1 && isForwardRange!R2) } } +/// Returning a subtype of $(REF Tuple, std,typecons) enables +/// the following convenient idiom: +@safe pure nothrow unittest +{ + // findSplit returns a triplet + if (auto split = "dlang-rocks".findSplit("-")) + { + assert(split[0] == "dlang"); + assert(split[1] == "-"); + assert(split[2] == "rocks"); + } + else assert(0); + + // works with const aswell + if (const split = "dlang-rocks".findSplit("-")) + { + assert(split[0] == "dlang"); + assert(split[1] == "-"); + assert(split[2] == "rocks"); + } + else assert(0); +} + /// @safe pure nothrow unittest { @@ -2996,14 +3160,18 @@ if (isForwardRange!R1 && isForwardRange!R2) assert(r[0] == "Carl"); assert(r[1] == " "); assert(r[2] == "Sagan Memorial Station"); - auto r1 = findSplitBefore(a, "Sagan"); - assert(r1); - assert(r1[0] == "Carl "); - assert(r1[1] == "Sagan Memorial Station"); - auto r2 = findSplitAfter(a, "Sagan"); - assert(r2); - assert(r2[0] == "Carl Sagan"); - assert(r2[1] == " Memorial Station"); + if (const r1 = findSplitBefore(a, "Sagan")) + { + assert(r1); + assert(r1[0] == "Carl "); + assert(r1[1] == "Sagan Memorial Station"); + } + if (const r2 = findSplitAfter(a, "Sagan")) + { + assert(r2); + assert(r2[0] == "Carl Sagan"); + assert(r2[1] == " Memorial Station"); + } } /// Use $(REF only, std,range) to find single elements: @@ -3017,7 +3185,7 @@ if (isForwardRange!R1 && isForwardRange!R2) { import std.range.primitives : empty; - auto a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; + immutable a = [ 1, 2, 3, 4, 5, 6, 7, 8 ]; auto r = findSplit(a, [9, 1]); assert(!r); assert(r[0] == a); @@ -3029,23 +3197,35 @@ if (isForwardRange!R1 && isForwardRange!R2) assert(r[1] == a[2 .. 3]); assert(r[2] == a[3 .. $]); - auto r1 = findSplitBefore(a, [9, 1]); - assert(r1); - assert(r1[0] == a); - assert(r1[1].empty); - r1 = findSplitBefore(a, [3, 4]); - assert(r1); - assert(r1[0] == a[0 .. 2]); - assert(r1[1] == a[2 .. $]); - - auto r2 = findSplitAfter(a, [9, 1]); - assert(r2); - assert(r2[0].empty); - assert(r2[1] == a); - r2 = findSplitAfter(a, [3, 4]); - assert(r2); - assert(r2[0] == a[0 .. 4]); - assert(r2[1] == a[4 .. $]); + { + const r1 = findSplitBefore(a, [9, 1]); + assert(!r1); + assert(r1[0] == a); + assert(r1[1].empty); + } + + if (immutable r1 = findSplitBefore(a, [3, 4])) + { + assert(r1); + assert(r1[0] == a[0 .. 2]); + assert(r1[1] == a[2 .. $]); + } + else assert(0); + + { + const r2 = findSplitAfter(a, [9, 1]); + assert(!r2); + assert(r2[0].empty); + assert(r2[1] == a); + } + + if (immutable r3 = findSplitAfter(a, [3, 4])) + { + assert(r3); + assert(r3[0] == a[0 .. 4]); + assert(r3[1] == a[4 .. $]); + } + else assert(0); } @safe pure nothrow unittest @@ -3065,24 +3245,56 @@ if (isForwardRange!R1 && isForwardRange!R2) assert(equal(r[0], a[0 .. 2])); assert(equal(r[1], a[2 .. 3])); assert(equal(r[2], a[3 .. $])); + r = findSplit(fwd, [8, 9]); + assert(!r); + assert(equal(r[0], a)); + assert(r[1].empty); + assert(r[2].empty); + + // auto variable `r2` cannot be `const` because `fwd.front` is mutable + { + auto r1 = findSplitBefore(fwd, [9, 1]); + assert(!r1); + assert(equal(r1[0], a)); + assert(r1[1].empty); + } + + if (auto r1 = findSplitBefore(fwd, [3, 4])) + { + assert(r1); + assert(equal(r1[0], a[0 .. 2])); + assert(equal(r1[1], a[2 .. $])); + } + else assert(0); + + { + auto r1 = findSplitBefore(fwd, [8, 9]); + assert(!r1); + assert(equal(r1[0], a)); + assert(r1[1].empty); + } + + { + auto r2 = findSplitAfter(fwd, [9, 1]); + assert(!r2); + assert(r2[0].empty); + assert(equal(r2[1], a)); + } + + if (auto r2 = findSplitAfter(fwd, [3, 4])) + { + assert(r2); + assert(equal(r2[0], a[0 .. 4])); + assert(equal(r2[1], a[4 .. $])); + } + else assert(0); - auto r1 = findSplitBefore(fwd, [9, 1]); - assert(r1); - assert(equal(r1[0], a)); - assert(r1[1].empty); - r1 = findSplitBefore(fwd, [3, 4]); - assert(r1); - assert(equal(r1[0], a[0 .. 2])); - assert(equal(r1[1], a[2 .. $])); - - auto r2 = findSplitAfter(fwd, [9, 1]); - assert(r2); - assert(r2[0].empty); - assert(equal(r2[1], a)); - r2 = findSplitAfter(fwd, [3, 4]); - assert(r2); - assert(equal(r2[0], a[0 .. 4])); - assert(equal(r2[1], a[4 .. $])); + { + auto r2 = findSplitAfter(fwd, [8, 9]); + assert(!r2); + assert(r2[0].empty); + assert(equal(r2[1], a)); + } } @safe pure nothrow @nogc unittest @@ -3116,22 +3328,31 @@ if (isForwardRange!R1 && isForwardRange!R2) assert(split[1] == "one"); } +// https://issues.dlang.org/show_bug.cgi?id=11013 +@safe pure unittest +{ + auto var = "abc"; + auto split = var.findSplitBefore!q{a == a}(var); + assert(split[0] == ""); + assert(split[1] == "abc"); +} + // minCount /** Computes the minimum (respectively maximum) of `range` along with its number of occurrences. Formally, the minimum is a value `x` in `range` such that $(D pred(a, x)) is `false` for all values `a` in `range`. Conversely, the maximum is -a value `x` in `range` such that $(D pred(x, a)) is `false` for all values `a` +a value `x` in `range` such that `pred(x, a)` is `false` for all values `a` in `range` (note the swapped arguments to `pred`). These functions may be used for computing arbitrary extrema by choosing `pred` appropriately. For corrrect functioning, `pred` must be a strict partial order, -i.e. transitive (if $(D pred(a, b) && pred(b, c)) then $(D pred(a, c))) and -irreflexive ($(D pred(a, a)) is `false`). The $(LUCKY trichotomy property of -inequality) is not required: these algoritms consider elements `a` and `b` equal +i.e. transitive (if `pred(a, b) && pred(b, c)` then `pred(a, c)`) and +irreflexive (`pred(a, a)` is `false`). The $(LUCKY trichotomy property of +inequality) is not required: these algorithms consider elements `a` and `b` equal (for the purpose of counting) if `pred` puts them in the same equivalence class, -i.e. $(D !pred(a, b) && !pred(b, a)). +i.e. `!pred(a, b) && !pred(b, a)`. Params: pred = The ordering predicate to use to determine the extremum (minimum @@ -3141,7 +3362,13 @@ Params: Returns: The minimum, respectively maximum element of a range together with the number it occurs in the range. +Limitations: If at least one of the arguments is NaN, the result is +an unspecified value. See $(REF maxElement, std,algorithm,searching) +for examples on how to cope with NaNs. + Throws: `Exception` if `range.empty`. + +See_Also: $(REF min, std,algorithm,comparison), $(LREF minIndex), $(LREF minElement), $(LREF minPos) */ Tuple!(ElementType!Range, size_t) minCount(alias pred = "a < b", Range)(Range range) @@ -3319,8 +3546,8 @@ if (isInputRange!Range && !isInfinite!Range && } static assert(!isAssignable!S3); - foreach (Type; AliasSeq!(S1, IS1, S2, IS2, S3)) - { + static foreach (Type; AliasSeq!(S1, IS1, S2, IS2, S3)) + {{ static if (is(Type == immutable)) alias V = immutable int; else alias V = int; V one = 1, two = 2; @@ -3329,7 +3556,7 @@ if (isInputRange!Range && !isInfinite!Range && 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); - } + }} } /** @@ -3347,24 +3574,37 @@ Params: Returns: The minimal element of the passed-in range. +Note: + If at least one of the arguments is NaN, the result is an unspecified value. + + If you want to ignore NaNs, you can use $(REF filter, std,algorithm,iteration) + and $(REF isNaN, std,math) to remove them, before applying minElement. + Add a suitable seed, to avoid error messages if all elements are NaNs: + + --- + <range>.filter!(a=>!a.isNaN).minElement(<seed>); + --- + + If you want to get NaN as a result if a NaN is present in the range, + you can use $(REF fold, std,algorithm,iteration) and $(REF isNaN, std,math): + + --- + <range>.fold!((a,b)=>a.isNaN || b.isNaN ? real.nan : a < b ? a : b); + --- + See_Also: - $(REF min, std,algorithm,comparison) + + $(LREF maxElement), $(REF min, std,algorithm,comparison), $(LREF minCount), + $(LREF minIndex), $(LREF minPos) */ -auto minElement(alias map, Range)(Range r) +auto minElement(alias map = (a => a), Range)(Range r) if (isInputRange!Range && !isInfinite!Range) { return extremum!map(r); } /// ditto -auto minElement(Range)(Range r) - if (isInputRange!Range && !isInfinite!Range) -{ - return extremum(r); -} - -/// ditto -auto minElement(alias map, Range, RangeElementType = ElementType!Range) +auto minElement(alias map = (a => a), Range, RangeElementType = ElementType!Range) (Range r, RangeElementType seed) if (isInputRange!Range && !isInfinite!Range && !is(CommonType!(ElementType!Range, RangeElementType) == void)) @@ -3372,22 +3612,13 @@ if (isInputRange!Range && !isInfinite!Range && return extremum!map(r, seed); } -/// ditto -auto minElement(Range, RangeElementType = ElementType!Range) - (Range r, RangeElementType seed) - if (isInputRange!Range && !isInfinite!Range && - !is(CommonType!(ElementType!Range, RangeElementType) == void)) -{ - return extremum(r, seed); -} - /// @safe pure unittest { import std.range : enumerate; import std.typecons : tuple; - assert([2, 1, 4, 3].minElement == 1); + assert([2, 7, 1, 3].minElement == 1); // allows to get the index of an element too assert([5, 3, 7, 9].enumerate.minElement!"a.value" == tuple(1, 3)); @@ -3429,6 +3660,7 @@ auto minElement(Range, RangeElementType = ElementType!Range) DummyType d; assert(d.minElement == 1); assert(d.minElement!(a => a) == 1); + assert(d.minElement!(a => -a) == 10); } // with empty, but seeded ranges @@ -3446,39 +3678,71 @@ auto minElement(Range, RangeElementType = ElementType!Range) assert(arr2d.minElement!"a[1]" == arr2d[1]); } +// https://issues.dlang.org/show_bug.cgi?id=17982 +@safe unittest +{ + struct A + { + int val; + } + + const(A)[] v = [A(0)]; + assert(v.minElement!"a.val" == A(0)); +} + +// https://issues.dlang.org/show_bug.cgi?id=17982 +@safe unittest +{ + class B + { + int val; + this(int val){ this.val = val; } + } + + const(B) doStuff(const(B)[] v) + { + return v.minElement!"a.val"; + } + assert(doStuff([new B(1), new B(0), new B(2)]).val == 0); + + const(B)[] arr = [new B(0), new B(1)]; + // can't compare directly - https://issues.dlang.org/show_bug.cgi?id=1824 + assert(arr.minElement!"a.val".val == 0); +} + /** Iterates the passed range and returns the maximal element. A custom mapping function can be passed to `map`. In other languages this is sometimes called `argmax`. -Complexity: +Complexity: O(n) Exactly `n - 1` comparisons are needed. Params: map = custom accessor for the comparison key - r = range from which the maximum will be selected + r = range from which the maximum element will be selected seed = custom seed to use as initial element Returns: The maximal element of the passed-in range. +Note: + If at least one of the arguments is NaN, the result is an unspecified value. + See $(REF minElement, std,algorithm,searching) for examples on how to cope + with NaNs. + See_Also: - $(REF max, std,algorithm,comparison) + + $(LREF minElement), $(REF max, std,algorithm,comparison), $(LREF maxCount), + $(LREF maxIndex), $(LREF maxPos) */ -auto maxElement(alias map, Range)(Range r) +auto maxElement(alias map = (a => a), Range)(Range r) if (isInputRange!Range && !isInfinite!Range) { return extremum!(map, "a > b")(r); } /// ditto -auto maxElement(Range)(Range r) -if (isInputRange!Range && !isInfinite!Range) -{ - return extremum!`a > b`(r); -} - -/// ditto -auto maxElement(alias map, Range, RangeElementType = ElementType!Range) +auto maxElement(alias map = (a => a), Range, RangeElementType = ElementType!Range) (Range r, RangeElementType seed) if (isInputRange!Range && !isInfinite!Range && !is(CommonType!(ElementType!Range, RangeElementType) == void)) @@ -3486,15 +3750,6 @@ if (isInputRange!Range && !isInfinite!Range && return extremum!(map, "a > b")(r, seed); } -/// ditto -auto maxElement(Range, RangeElementType = ElementType!Range) - (Range r, RangeElementType seed) -if (isInputRange!Range && !isInfinite!Range && - !is(CommonType!(ElementType!Range, RangeElementType) == void)) -{ - return extremum!`a > b`(r, seed); -} - /// @safe pure unittest { @@ -3544,6 +3799,7 @@ if (isInputRange!Range && !isInfinite!Range && DummyType d; assert(d.maxElement == 10); assert(d.maxElement!(a => a) == 10); + assert(d.maxElement!(a => -a) == 1); } // with empty, but seeded ranges @@ -3562,31 +3818,57 @@ if (isInputRange!Range && !isInfinite!Range && assert(arr2d.maxElement!"a[1]" == arr2d[1]); } +// https://issues.dlang.org/show_bug.cgi?id=17982 +@safe unittest +{ + class B + { + int val; + this(int val){ this.val = val; } + } + + const(B) doStuff(const(B)[] v) + { + return v.maxElement!"a.val"; + } + assert(doStuff([new B(1), new B(0), new B(2)]).val == 2); + + const(B)[] arr = [new B(0), new B(1)]; + // can't compare directly - https://issues.dlang.org/show_bug.cgi?id=1824 + assert(arr.maxElement!"a.val".val == 1); +} + // minPos /** Computes a subrange of `range` starting at the first occurrence of `range`'s minimum (respectively maximum) and with the same ending as `range`, or the empty range if `range` itself is empty. -Formally, the minimum is a value `x` in `range` such that $(D pred(a, x)) is +Formally, the minimum is a value `x` in `range` such that `pred(a, x)` is `false` for all values `a` in `range`. Conversely, the maximum is a value `x` in -`range` such that $(D pred(x, a)) is `false` for all values `a` in `range` (note +`range` such that `pred(x, a)` is `false` for all values `a` in `range` (note the swapped arguments to `pred`). These functions may be used for computing arbitrary extrema by choosing `pred` appropriately. For corrrect functioning, `pred` must be a strict partial order, -i.e. transitive (if $(D pred(a, b) && pred(b, c)) then $(D pred(a, c))) and -irreflexive ($(D pred(a, a)) is `false`). +i.e. transitive (if `pred(a, b) && pred(b, c)` then `pred(a, c)`) and +irreflexive (`pred(a, a)` is `false`). Params: pred = The ordering predicate to use to determine the extremum (minimum or maximum) element. - range = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to search. + range = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to search. Returns: The position of the minimum (respectively maximum) element of forward range `range`, i.e. a subrange of `range` starting at the position of its smallest (respectively largest) element and with the same ending as `range`. +Limitations: If at least one of the arguments is NaN, the result is +an unspecified value. See $(REF maxElement, std,algorithm,searching) +for examples on how to cope with NaNs. + +See_Also: + $(REF max, std,algorithm,comparison), $(LREF minCount), $(LREF minIndex), $(LREF minElement) */ Range minPos(alias pred = "a < b", Range)(Range range) if (isForwardRange!Range && !isInfinite!Range && @@ -3688,23 +3970,28 @@ Params: range = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to search. -Complexity: O(n) - Exactly `n - 1` comparisons are needed. +Complexity: $(BIGOH range.length) + Exactly `range.length - 1` comparisons are needed. Returns: The index of the first encounter of the minimum element in `range`. If the `range` is empty, -1 is returned. +Limitations: + If at least one of the arguments is NaN, the result is + an unspecified value. See $(REF maxElement, std,algorithm,searching) + for examples on how to cope with NaNs. + See_Also: - $(REF min, std,algorithm,comparison), $(LREF minCount), $(LREF minElement), $(LREF minPos) + $(LREF maxIndex), $(REF min, std,algorithm,comparison), $(LREF minCount), $(LREF minElement), $(LREF minPos) */ -sizediff_t minIndex(alias pred = "a < b", Range)(Range range) -if (isForwardRange!Range && !isInfinite!Range && +ptrdiff_t minIndex(alias pred = "a < b", Range)(Range range) +if (isInputRange!Range && !isInfinite!Range && is(typeof(binaryFun!pred(range.front, range.front)))) { if (range.empty) return -1; - sizediff_t minPos = 0; + ptrdiff_t minPos = 0; static if (isRandomAccessRange!Range && hasLength!Range) { @@ -3718,7 +4005,7 @@ if (isForwardRange!Range && !isInfinite!Range && } else { - sizediff_t curPos = 0; + ptrdiff_t curPos = 0; Unqual!(typeof(range.front)) min = range.front; for (range.popFront(); !range.empty; range.popFront()) { @@ -3799,11 +4086,45 @@ if (isForwardRange!Range && !isInfinite!Range && assert(arr2d.minIndex!"a[1] < b[1]" == 2); } +@safe nothrow pure unittest +{ + // InputRange test + + static struct InRange + { + @property int front() + { + return arr[index]; + } + + bool empty() const + { + return arr.length == index; + } + + void popFront() + { + index++; + } + + int[] arr; + size_t index = 0; + } + + static assert(isInputRange!InRange); + + auto arr1 = InRange([5, 2, 3, 4, 5, 3, 6]); + auto arr2 = InRange([7, 3, 8, 2, 1, 4]); + + assert(arr1.minIndex == 1); + assert(arr2.minIndex == 4); +} + /** Computes the index of the first occurrence of `range`'s maximum element. -Complexity: O(n) - Exactly `n - 1` comparisons are needed. +Complexity: $(BIGOH range) + Exactly `range.length - 1` comparisons are needed. Params: pred = The ordering predicate to use to determine the maximum element. @@ -3813,10 +4134,15 @@ Returns: The index of the first encounter of the maximum in `range`. If the `range` is empty, -1 is returned. +Limitations: + If at least one of the arguments is NaN, the result is + an unspecified value. See $(REF maxElement, std,algorithm,searching) + for examples on how to cope with NaNs. + See_Also: - $(REF max, std,algorithm,comparison), $(LREF maxCount), $(LREF maxElement), $(LREF maxPos) + $(LREF minIndex), $(REF max, std,algorithm,comparison), $(LREF maxCount), $(LREF maxElement), $(LREF maxPos) */ -sizediff_t maxIndex(alias pred = "a < b", Range)(Range range) +ptrdiff_t maxIndex(alias pred = "a < b", Range)(Range range) if (isInputRange!Range && !isInfinite!Range && is(typeof(binaryFun!pred(range.front, range.front)))) { @@ -3889,79 +4215,158 @@ if (isInputRange!Range && !isInfinite!Range && } /** -Skip over the initial portion of the first given range that matches the second -range, or if no second range is given skip over the elements that fullfil pred. +Skip over the initial portion of the first given range (`haystack`) that matches +any of the additionally given ranges (`needles`) fully, or +if no second range is given skip over the elements that fulfill pred. Do nothing if there is no match. Params: pred = The predicate that determines whether elements from each respective - range match. Defaults to equality $(D "a == b"). - r1 = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to - move forward. - r2 = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) - representing the initial segment of $(D r1) to skip over. - -Returns: -true if the initial segment of $(D r1) matches $(D r2) or $(D pred) evaluates to true, -and $(D r1) has been advanced to the point past this segment; otherwise false, and -$(D r1) is left in its original position. - */ -bool skipOver(R1, R2)(ref R1 r1, R2 r2) -if (isForwardRange!R1 && isInputRange!R2 - && is(typeof(r1.front == r2.front))) + range match. Defaults to equality `"a == b"`. +*/ +template skipOver(alias pred = (a, b) => a == b) { - static if (is(typeof(r1[0 .. $] == r2) : bool) - && is(typeof(r2.length > r1.length) : bool) - && is(typeof(r1 = r1[r2.length .. $]))) + enum bool isPredComparable(T) = ifTestable!(T, binaryFun!pred); + + /** + Params: + haystack = The $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to + move forward. + needles = The $(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) + representing the prefix of `r1` to skip over. + es = The element to match. + + Returns: + `true` if the prefix of `haystack` matches any range of `needles` fully + or `pred` evaluates to true, and `haystack` has been advanced to the point past this segment; + otherwise false, and `haystack` is left in its original position. + + Note: + By definition, empty ranges are matched fully and if `needles` contains an empty range, + `skipOver` will return `true`. + */ + bool skipOver(Haystack, Needles...)(ref Haystack haystack, Needles needles) + if (is(typeof(binaryFun!pred(haystack.front, needles[0].front))) && + isForwardRange!Haystack && + allSatisfy!(isInputRange, Needles) && + !is(CommonType!(staticMap!(ElementType, staticMap!(Unqual, Needles))) == void)) { - if (r2.length > r1.length || r1[0 .. r2.length] != r2) + static if (__traits(isSame, pred, (a, b) => a == b) + && is(typeof(haystack[0 .. $] == needles[0]) : bool) + && is(typeof(haystack = haystack[0 .. $])) + && hasLength!Haystack && allSatisfy!(hasLength, Needles)) { + ptrdiff_t longestMatch = -1; + static foreach (r2; needles) + { + if (r2.length <= haystack.length && longestMatch < ptrdiff_t(r2.length) + && (haystack[0 .. r2.length] == r2 || r2.length == 0)) + longestMatch = r2.length; + } + if (longestMatch >= 0) + { + if (longestMatch > 0) + haystack = haystack[longestMatch .. $]; + + return true; + } return false; } - r1 = r1[r2.length .. $]; - return true; - } - else - { - return skipOver!((a, b) => a == b)(r1, r2); + else + { + import std.algorithm.comparison : min; + auto r = haystack.save; + + static if (hasLength!Haystack && allSatisfy!(hasLength, Needles)) + { + import std.algorithm.iteration : map; + import std.algorithm.searching : minElement; + import std.range : only; + // Shortcut opportunity! + if (needles.only.map!(a => a.length).minElement > haystack.length) + return false; + } + + // compatibility: return true if any range was empty + bool hasEmptyRanges; + static foreach (i, r2; needles) + { + if (r2.empty) + hasEmptyRanges = true; + } + + bool hasNeedleMatch; + size_t inactiveNeedlesLen; + bool[Needles.length] inactiveNeedles; + for (; !r.empty; r.popFront) + { + static foreach (i, r2; needles) + { + if (!r2.empty && !inactiveNeedles[i]) + { + if (binaryFun!pred(r.front, r2.front)) + { + r2.popFront; + if (r2.empty) + { + // we skipped over a new match + hasNeedleMatch = true; + inactiveNeedlesLen++; + // skip over haystack + haystack = r; + } + } + else + { + inactiveNeedles[i] = true; + inactiveNeedlesLen++; + } + } + } + + // are we done? + if (inactiveNeedlesLen == needles.length) + break; + } + + if (hasNeedleMatch) + haystack.popFront; + + return hasNeedleMatch || hasEmptyRanges; + } } -} -/// Ditto -bool skipOver(alias pred, R1, R2)(ref R1 r1, R2 r2) -if (is(typeof(binaryFun!pred(r1.front, r2.front))) && - isForwardRange!R1 && - isInputRange!R2) -{ - static if (hasLength!R1 && hasLength!R2) + /// Ditto + bool skipOver(R)(ref R r1) + if (isForwardRange!R && + ifTestable!(typeof(r1.front), unaryFun!pred)) { - // Shortcut opportunity! - if (r2.length > r1.length) + if (r1.empty || !unaryFun!pred(r1.front)) return false; + + do + r1.popFront(); + while (!r1.empty && unaryFun!pred(r1.front)); + return true; } - auto r = r1.save; - while (!r2.empty && !r.empty && binaryFun!pred(r.front, r2.front)) + + /// Ditto + bool skipOver(R, Es...)(ref R r, Es es) + if (isInputRange!R && is(typeof(binaryFun!pred(r.front, es[0])))) { - r.popFront(); - r2.popFront(); - } - if (r2.empty) - r1 = r; - return r2.empty; -} + if (r.empty) + return false; -/// Ditto -bool skipOver(alias pred, R)(ref R r1) -if (isForwardRange!R && - ifTestable!(typeof(r1.front), unaryFun!pred)) -{ - if (r1.empty || !unaryFun!pred(r1.front)) + static foreach (e; es) + { + if (binaryFun!pred(r.front, e)) + { + r.popFront(); + return true; + } + } return false; - - do - r1.popFront(); - while (!r1.empty && unaryFun!pred(r1.front)); - return true; + } } /// @@ -3972,15 +4377,19 @@ if (isForwardRange!R && auto s1 = "Hello world"; assert(!skipOver(s1, "Ha")); assert(s1 == "Hello world"); - assert(skipOver(s1, "Hell") && s1 == "o world"); + assert(skipOver(s1, "Hell") && s1 == "o world", s1); string[] r1 = ["abc", "def", "hij"]; dstring[] r2 = ["abc"d]; - assert(!skipOver!((a, b) => a.equal(b))(r1, ["def"d])); + assert(!skipOver!((a, b) => a.equal(b))(r1, ["def"d]), r1[0]); assert(r1 == ["abc", "def", "hij"]); assert(skipOver!((a, b) => a.equal(b))(r1, r2)); assert(r1 == ["def", "hij"]); +} +/// +@safe unittest +{ import std.ascii : isWhite; import std.range.primitives : empty; @@ -3992,38 +4401,16 @@ if (isForwardRange!R && assert(s4.skipOver!isWhite && s3.empty); } -/** -Skip over the first element of the given range if it matches the given element, -otherwise do nothing. - -Params: - pred = The predicate that determines whether an element from the range - matches the given element. - - r = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to skip - over. - - e = The element to match. - -Returns: -true if the first element matches the given element according to the given -predicate, and the range has been advanced by one element; otherwise false, and -the range is left untouched. - */ -bool skipOver(R, E)(ref R r, E e) -if (isInputRange!R && is(typeof(r.front == e) : bool)) +/// Variadic skipOver +@safe unittest { - return skipOver!((a, b) => a == b)(r, e); -} + auto s = "Hello world"; + assert(!skipOver(s, "hello", "HellO")); + assert(s == "Hello world"); -/// Ditto -bool skipOver(alias pred, R, E)(ref R r, E e) -if (is(typeof(binaryFun!pred(r.front, e))) && isInputRange!R) -{ - if (r.empty || !binaryFun!pred(r.front, e)) - return false; - r.popFront(); - return true; + // the range is skipped over the longest matching needle is skipped + assert(skipOver(s, "foo", "hell", "Hello ")); + assert(s == "world"); } /// @@ -4047,11 +4434,154 @@ if (is(typeof(binaryFun!pred(r.front, e))) && isInputRange!R) assert(!s2.skipOver('a')); } +/// Partial instantiation +@safe unittest +{ + import std.ascii : isWhite; + import std.range.primitives : empty; + + alias whitespaceSkiper = skipOver!isWhite; + + auto s2 = "\t\tvalue"; + auto s3 = ""; + auto s4 = "\t\t\t"; + assert(whitespaceSkiper(s2) && s2 == "value"); + assert(!whitespaceSkiper(s2)); + assert(whitespaceSkiper(s4) && s3.empty); +} + +// variadic skipOver +@safe unittest +{ + auto s = "DLang.rocks"; + assert(!s.skipOver("dlang", "DLF", "DLang ")); + assert(s == "DLang.rocks"); + + assert(s.skipOver("dlang", "DLANG", "DLF", "D", "DL", "DLanpp")); + assert(s == "ang.rocks"); + s = "DLang.rocks"; + + assert(s.skipOver("DLang", "DLANG", "DLF", "D", "DL", "DLang ")); + assert(s == ".rocks"); + s = "DLang.rocks"; + + assert(s.skipOver("dlang", "DLANG", "DLF", "D", "DL", "DLang.")); + assert(s == "rocks"); +} + +// variadic with custom pred +@safe unittest +{ + import std.ascii : toLower; + + auto s = "DLang.rocks"; + assert(!s.skipOver("dlang", "DLF", "DLang ")); + assert(s == "DLang.rocks"); + + assert(s.skipOver!((a, b) => a.toLower == b.toLower)("dlang", "DLF", "DLang ")); + assert(s == ".rocks"); +} + +// variadic skipOver with mixed needles +@safe unittest +{ + auto s = "DLang.rocks"; + assert(!s.skipOver("dlang"d, "DLF", "DLang "w)); + assert(s == "DLang.rocks"); + + assert(s.skipOver("dlang", "DLANG"d, "DLF"w, "D"d, "DL", "DLanp")); + assert(s == "ang.rocks"); + s = "DLang.rocks"; + + assert(s.skipOver("DLang", "DLANG"w, "DLF"d, "D"d, "DL", "DLang ")); + assert(s == ".rocks"); + s = "DLang.rocks"; + + assert(s.skipOver("dlang", "DLANG"w, "DLF", "D"d, "DL"w, "DLang."d)); + assert(s == "rocks"); + + import std.algorithm.iteration : filter; + s = "DLang.rocks"; + assert(s.skipOver("dlang", "DLang".filter!(a => true))); + assert(s == ".rocks"); +} + +// variadic skipOver with auto-decoding +@safe unittest +{ + auto s = "☢☣☠.☺"; + assert(s.skipOver("a", "☢", "☢☣☠")); + assert(s == ".☺"); +} + +// skipOver with @nogc +@safe @nogc pure nothrow unittest +{ + static immutable s = [0, 1, 2]; + immutable(int)[] s2 = s[]; + + static immutable skip1 = [0, 2]; + static immutable skip2 = [0, 1]; + assert(s2.skipOver(skip1, skip2)); + assert(s2 == s[2 .. $]); +} + +// variadic skipOver with single elements +@safe unittest +{ + auto s = "DLang.rocks"; + assert(!s.skipOver('a', 'd', 'e')); + assert(s == "DLang.rocks"); + + assert(s.skipOver('a', 'D', 'd', 'D')); + assert(s == "Lang.rocks"); + s = "DLang.rocks"; + + assert(s.skipOver(wchar('a'), dchar('D'), 'd')); + assert(s == "Lang.rocks"); + + dstring dstr = "+Foo"; + assert(!dstr.skipOver('.', '-')); + assert(dstr == "+Foo"); + + assert(dstr.skipOver('+', '-')); + assert(dstr == "Foo"); +} + +// skipOver with empty ranges must return true (compatibility) +@safe unittest +{ + auto s = "DLang.rocks"; + assert(s.skipOver("")); + assert(s.skipOver("", "")); + assert(s.skipOver("", "foo")); + + auto s2 = "DLang.rocks"d; + assert(s2.skipOver("")); + assert(s2.skipOver("", "")); + assert(s2.skipOver("", "foo")); +} + +// dxml regression +@safe unittest +{ + import std.utf : byCodeUnit; + import std.algorithm.comparison : equal; + + bool stripStartsWith(Text)(ref Text text, string needle) + { + return text.skipOver(needle.byCodeUnit()); + } + auto text = "<xml></xml>"d.byCodeUnit; + assert(stripStartsWith(text, "<xml>")); + assert(text.equal("</xml>")); +} + /** Checks whether the given $(REF_ALTTEXT input range, isInputRange, std,range,primitives) starts with (one of) the given needle(s) or, if no needles are given, -if its front element fulfils predicate $(D pred). +if its front element fulfils predicate `pred`. Params: @@ -4070,24 +4600,38 @@ Returns: 0 if the needle(s) do not occur at the beginning of the given range; otherwise the position of the matching needle, that is, 1 if the range starts -with $(D withOneOfThese[0]), 2 if it starts with $(D withOneOfThese[1]), and so +with `withOneOfThese[0]`, 2 if it starts with `withOneOfThese[1]`, and so on. -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 +In the case where `doesThisStart` starts with multiple of the ranges or +elements in `withOneOfThese`, then the shortest one matches (if there are +two which match which are of the same length (e.g. `"a"` and `'a'`), then the left-most of them in the argument list matches). -In the case when no needle parameters are given, return $(D true) iff front of -$(D doesThisStart) fulfils predicate $(D pred). +In the case when no needle parameters are given, return `true` iff front of +`doesThisStart` fulfils predicate `pred`. */ -uint startsWith(alias pred = "a == b", Range, Needles...)(Range doesThisStart, Needles withOneOfThese) +uint startsWith(alias pred = (a, b) => 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)) + allSatisfy!(canTestStartsWith!(pred, Range), Needles)) { - alias haystack = doesThisStart; + template checkType(T) + { + enum checkType = is(immutable ElementEncodingType!Range == immutable T); + } + + // auto-decoding special case + static if (__traits(isSame, binaryFun!pred, (a, b) => a == b) && + isNarrowString!Range && allSatisfy!(checkType, Needles)) + { + import std.utf : byCodeUnit; + auto haystack = doesThisStart.byCodeUnit; + } + else + { + alias haystack = doesThisStart; + } alias needles = withOneOfThese; // Make one pass looking for empty ranges in needles @@ -4168,17 +4712,18 @@ if (isInputRange!R1 && 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. + // Note: Although narrow strings don't have a "true" length, for a narrow string to start with another + // narrow string, 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)) + ((hasLength!R1 || isNarrowString!R1) && (hasLength!R2 || 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))) + is(immutable ElementEncodingType!R1 == immutable ElementEncodingType!R2)) { //Array slice comparison mode return haystack[0 .. needle.length] == needle; @@ -4231,16 +4776,27 @@ if (isInputRange!R && if (doesThisStart.empty) return false; + static if (is(typeof(pred) : string)) + enum isDefaultPred = pred == "a == b"; + else + enum isDefaultPred = false; + alias predFunc = binaryFun!pred; // auto-decoding special case static if (isNarrowString!R) { + // statically determine decoding is unnecessary to evaluate pred + static if (isDefaultPred && isSomeChar!E && E.sizeof <= ElementEncodingType!R.sizeof) + return doesThisStart[0] == withThis; // specialize for ASCII as to not change previous behavior - if (withThis <= 0x7F) - return predFunc(doesThisStart[0], withThis); else - return predFunc(doesThisStart.front, withThis); + { + if (withThis <= 0x7F) + return predFunc(doesThisStart[0], withThis); + else + return predFunc(doesThisStart.front, withThis); + } } else { @@ -4295,16 +4851,17 @@ if (isInputRange!R && import std.meta : AliasSeq; import std.range; - foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - { + static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + (){ // workaround slow optimizations for large functions + // https://issues.dlang.org/show_bug.cgi?id=2396 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; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) - (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 + static foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) + { //Lots of strings assert(startsWith(to!S("abc"), to!T(""))); assert(startsWith(to!S("ab"), to!T("a"))); @@ -4337,16 +4894,16 @@ if (isInputRange!R && 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; AliasSeq!(int, short)) - { + static foreach (T; AliasSeq!(int, short)) + {{ immutable arr = cast(T[])[0, 1, 2, 3, 4, 5]; //RA range @@ -4377,12 +4934,18 @@ if (isInputRange!R && //Non-default pred assert(startsWith!("a%10 == b%10")(arr, [10, 11])); assert(!startsWith!("a%10 == b%10")(arr, [10, 12])); - } + }} +} + +private template canTestStartsWith(alias pred, Haystack) +{ + enum bool canTestStartsWith(Needle) = is(typeof( + (ref Haystack h, ref Needle n) => startsWith!pred(h, n))); } /* (Not yet documented.) -Consume all elements from $(D r) that are equal to one of the elements -$(D es). +Consume all elements from `r` that are equal to one of the elements +`es`. */ private void skipAll(alias pred = "a == b", R, Es...)(ref R r, Es es) //if (is(typeof(binaryFun!pred(r1.front, es[0])))) @@ -4411,34 +4974,34 @@ private void skipAll(alias pred = "a == b", R, Es...)(ref R r, Es es) /** Interval option specifier for `until` (below) and others. -If set to $(D OpenRight.yes), then the interval is open to the right +If set to `OpenRight.yes`, then the interval is open to the right (last element is not included). -Otherwise if set to $(D OpenRight.no), then the interval is closed to the right +Otherwise if set to `OpenRight.no`, then the interval is closed to the right (last element included). */ alias OpenRight = Flag!"openRight"; /** -Lazily iterates $(D range) _until the element $(D e) for which -$(D pred(e, sentinel)) is true. +Lazily iterates `range` _until the element `e` for which +`pred(e, sentinel)` is true. This is similar to `takeWhile` in other languages. Params: pred = Predicate to determine when to stop. - range = The $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) + range = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to iterate over. sentinel = The element to stop at. openRight = Determines whether the element for which the given predicate is - true should be included in the resulting range ($(D No.openRight)), or - not ($(D Yes.openRight)). + true should be included in the resulting range (`No.openRight`), or + not (`Yes.openRight`). Returns: - An $(REF_ALTTEXT input _range, isInputRange, std,_range,primitives) that + An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) that iterates over the original range's elements, but ends when the specified predicate becomes true. If the original range is a - $(REF_ALTTEXT forward _range, isForwardRange, std,_range,primitives) or + $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) or higher, this range will be a forward range. */ Until!(pred, Range, Sentinel) @@ -4468,6 +5031,7 @@ if (isInputRange!Range) private bool _done; static if (!is(Sentinel == void)) + { /// this(Range input, Sentinel sentinel, OpenRight openRight = Yes.openRight) @@ -4477,7 +5041,17 @@ if (isInputRange!Range) _openRight = openRight; _done = _input.empty || openRight && predSatisfied(); } + private this(Range input, Sentinel sentinel, OpenRight openRight, + bool done) + { + _input = input; + _sentinel = sentinel; + _openRight = openRight; + _done = done; + } + } else + { /// this(Range input, OpenRight openRight = Yes.openRight) { @@ -4485,6 +5059,13 @@ if (isInputRange!Range) _openRight = openRight; _done = _input.empty || openRight && predSatisfied(); } + private this(Range input, OpenRight openRight, bool done) + { + _input = input; + _openRight = openRight; + _done = done; + } + } /// @property bool empty() @@ -4495,7 +5076,7 @@ if (isInputRange!Range) /// @property auto ref front() { - assert(!empty); + assert(!empty, "Can not get the front of an empty Until"); return _input.front; } @@ -4510,7 +5091,7 @@ if (isInputRange!Range) /// void popFront() { - assert(!empty); + assert(!empty, "Can not popFront of an empty Until"); if (!_openRight) { _done = predSatisfied(); @@ -4526,27 +5107,14 @@ if (isInputRange!Range) 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; - } + /// + @property Until save() + { + static if (is(Sentinel == void)) + return Until(_input.save, _openRight, _done); + else + return Until(_input.save, _sentinel, _openRight, _done); + } } } @@ -4574,7 +5142,8 @@ if (isInputRange!Range) assert(equal(until!"a == 2"(a, No.openRight), [1, 2])); } -@system unittest // bugzilla 13171 +// https://issues.dlang.org/show_bug.cgi?id=13171 +@system unittest { import std.algorithm.comparison : equal; import std.range; @@ -4583,7 +5152,8 @@ if (isInputRange!Range) assert(a == [4]); } -@safe unittest // Issue 10460 +// https://issues.dlang.org/show_bug.cgi?id=10460 +@safe unittest { import std.algorithm.comparison : equal; auto a = [1, 2, 3, 4]; @@ -4592,9 +5162,29 @@ if (isInputRange!Range) assert(equal(a, [0, 0, 3, 4])); } -@safe unittest // Issue 13124 +// https://issues.dlang.org/show_bug.cgi?id=13124 +@safe unittest { import std.algorithm.comparison : among, equal; auto s = "hello how\nare you"; assert(equal(s.until!(c => c.among!('\n', '\r')), "hello how")); } + +// https://issues.dlang.org/show_bug.cgi?id=18657 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : refRange; + { + string s = "foobar"; + auto r = refRange(&s).until("bar"); + assert(equal(r.save, "foo")); + assert(equal(r.save, "foo")); + } + { + string s = "foobar"; + auto r = refRange(&s).until!(e => e == 'b'); + assert(equal(r.save, "foo")); + assert(equal(r.save, "foo")); + } +} diff --git a/libphobos/src/std/algorithm/setops.d b/libphobos/src/std/algorithm/setops.d index 05a6e7e..ede1831 100644 --- a/libphobos/src/std/algorithm/setops.d +++ b/libphobos/src/std/algorithm/setops.d @@ -40,7 +40,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_setops.d) +Source: $(PHOBOSSRC std/algorithm/setops.d) Macros: T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) @@ -49,19 +49,17 @@ module std.algorithm.setops; import std.range.primitives; -// FIXME -import std.functional; // : unaryFun, binaryFun; +import std.functional : unaryFun, binaryFun; import std.traits; -// FIXME -import std.meta; // : AliasSeq, staticMap, allSatisfy, anySatisfy; +import std.meta : AliasSeq, staticMap, allSatisfy, anySatisfy; -import std.algorithm.sorting; // : Merge; +import std.algorithm.sorting : Merge; import std.typecons : No; // cartesianProduct /** Lazily computes the Cartesian product of two or more ranges. The product is a -_range of tuples of elements from each respective range. +range of tuples of elements from each respective range. The conditions for the two-range case are as follows: @@ -69,8 +67,8 @@ If both ranges are finite, then one must be (at least) a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) and the other an $(REF_ALTTEXT input range, isInputRange, std,range,primitives). -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 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. @@ -348,7 +346,7 @@ if (!allSatisfy!(isForwardRange, R1, R2) || } } -// Issue 13091 +// https://issues.dlang.org/show_bug.cgi?id=13091 pure nothrow @safe @nogc unittest { int[1] a = [1]; @@ -393,7 +391,7 @@ if (ranges.length >= 2 && return mixin(algoFormat("tuple(%(current[%d].front%|,%))", iota(0, current.length))); } - void popFront() + void popFront() scope { foreach_reverse (i, ref r; current) { @@ -406,25 +404,27 @@ if (ranges.length >= 2 && r = ranges[i].save; // rollover } } - @property Result save() + @property Result save() scope return { Result copy = this; foreach (i, r; ranges) { - copy.ranges[i] = r.save; + copy.ranges[i] = ranges[i].save; copy.current[i] = current[i].save; } return copy; } } - static assert(isForwardRange!Result); + static assert(isForwardRange!Result, Result.stringof ~ " must be a forward" + ~ " range"); return Result(ranges); } +// cartesian product of empty ranges should be empty +// https://issues.dlang.org/show_bug.cgi?id=10693 @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); @@ -446,9 +446,9 @@ if (ranges.length >= 2 && assert(cprod.init.empty); } +// https://issues.dlang.org/show_bug.cgi?id=13393 @safe unittest { - // Issue 13393 assert(!cartesianProduct([0],[0],[0]).save.empty); } @@ -486,7 +486,7 @@ if (!allSatisfy!(isForwardRange, R1, R2, RR) || 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(50, 23, 11))); assert(canFind(N3, tuple(9, 3, 0))); } @@ -504,10 +504,10 @@ if (!allSatisfy!(isForwardRange, R1, R2, RR) || assert(canFind(N4, tuple(1, 2, 3, 4))); assert(canFind(N4, tuple(4, 3, 2, 1))); - assert(canFind(N4, tuple(10, 31, 7, 12))); + assert(canFind(N4, tuple(10, 3, 1, 2))); } -// Issue 9878 +// https://issues.dlang.org/show_bug.cgi?id=9878 /// @safe unittest { @@ -546,7 +546,7 @@ pure @safe nothrow @nogc unittest assert(D.front == front1); } -// Issue 13935 +// https://issues.dlang.org/show_bug.cgi?id=13935 @safe unittest { import std.algorithm.iteration : map; @@ -554,12 +554,46 @@ pure @safe nothrow @nogc unittest foreach (pair; cartesianProduct(seq, seq)) {} } +@system unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + static struct SystemRange + { + int[] data; + + int front() @system @property inout + { + return data[0]; + } + + bool empty() @system @property inout + { + return data.length == 0; + } + + void popFront() @system + { + data = data[1 .. $]; + } + + SystemRange save() @system + { + return this; + } + } + + assert(SystemRange([1, 2]).cartesianProduct(SystemRange([3, 4])) + .equal([tuple(1, 3), tuple(1, 4), tuple(2, 3), tuple(2, 4)])); +} + // largestPartialIntersection /** Given a range of sorted $(REF_ALTTEXT forward ranges, isForwardRange, std,range,primitives) -$(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. +`ror`, copies to `tgt` the elements that are common to most ranges, along with their number +of occurrences. All ranges in `ror` are assumed to be sorted by $(D +less). Only the most frequent `tgt.length` elements are returned. Params: less = The predicate the ranges are sorted by. @@ -567,27 +601,27 @@ Params: tgt = The target range to copy common elements to. sorted = Whether the elements copied should be in sorted order. -The function $(D largestPartialIntersection) is useful for +The function `largestPartialIntersection` is useful for e.g. searching an $(LINK2 https://en.wikipedia.org/wiki/Inverted_index, 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 +is $(BIGOH n * log(tgt.length)), where `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). +requires less memory (`largestPartialIntersection` builds its +result directly in `tgt` and requires no extra memory). If at least one of the ranges is a multiset, then all occurences of a duplicate element are taken into account. The result is equivalent to merging all ranges and picking the most frequent -$(D tgt.length) elements. +`tgt.length` elements. -Warning: Because $(D largestPartialIntersection) does not allocate -extra memory, it will leave $(D ror) modified. Namely, $(D -largestPartialIntersection) assumes ownership of $(D ror) and +Warning: Because `largestPartialIntersection` does not allocate +extra memory, it will leave `ror` modified. Namely, $(D +largestPartialIntersection) assumes ownership of `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 to `largestPartialIntersection` (and perhaps cache the duplicate in between calls). */ void largestPartialIntersection @@ -652,13 +686,13 @@ import std.algorithm.sorting : SortOutput; // FIXME // largestPartialIntersectionWeighted /** -Similar to $(D largestPartialIntersection), but associates a weight +Similar to `largestPartialIntersection`, but associates a weight with each distinct element in the intersection. If at least one of the ranges is a multiset, then all occurences of a duplicate element are taken into account. The result is equivalent to merging all input ranges and picking the highest -$(D tgt.length), weight-based ranking elements. +`tgt.length`, weight-based ranking elements. Params: less = The predicate the ranges are sorted by. @@ -798,15 +832,15 @@ void largestPartialIntersectionWeighted Merges 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 +complexity of one `popFront` operation is $(BIGOH +log(ror.length)). However, the length of `ror` decreases as ranges in it are exhausted, so the complexity of a full pass through $(D MultiwayMerge) is dependent on the distribution of the lengths of ranges -contained within $(D ror). If all ranges have the same length $(D n) +contained within `ror`. If all ranges have the same length `n` (worst case scenario), the complexity of a full pass through $(D MultiwayMerge) 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). +turn. The output comes sorted (unstably) by `less`. The length of the resulting range is the sum of all lengths of the ranges passed as input. This means that all elements (duplicates @@ -824,12 +858,15 @@ Params: Returns: A range of the union of the ranges in `ror`. -Warning: Because $(D MultiwayMerge) does not allocate extra memory, it -will leave $(D ror) modified. Namely, $(D MultiwayMerge) 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 MultiwayMerge) (and perhaps cache the +Warning: Because `MultiwayMerge` does not allocate extra memory, it +will leave `ror` modified. Namely, `MultiwayMerge` assumes ownership +of `ror` and discretionarily swaps and advances elements of it. If +you want `ror` to preserve its contents after the call, you may +want to pass a duplicate to `MultiwayMerge` (and perhaps cache the duplicate in between calls). + +See_Also: $(REF merge, std,algorithm,sorting) for an analogous function that + takes a static number of ranges of possibly disparate types. */ struct MultiwayMerge(alias less, RangeOfRanges) { @@ -883,7 +920,8 @@ struct MultiwayMerge(alias less, RangeOfRanges) return; } // Put the popped range back in the heap - _heap.conditionalInsert(_ror.back) || assert(false); + const bool worked = _heap.conditionalInsert(_ror.back); + assert(worked, "Failed to insert item into heap"); } } @@ -930,7 +968,8 @@ alias nWayUnion = multiwayMerge; alias NWayUnion = MultiwayMerge; /** -Computes the union of multiple ranges. The input ranges are passed +Computes the union of multiple ranges. The +$(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) 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. `multiwayUnion(ror)` is functionally equivalent to `multiwayMerge(ror).uniq`. @@ -949,7 +988,8 @@ See also: $(LREF multiwayMerge) auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) { import std.algorithm.iteration : uniq; - return ror.multiwayMerge.uniq; + import std.functional : not; + return ror.multiwayMerge!(less).uniq!(not!less); } /// @@ -980,17 +1020,26 @@ auto multiwayUnion(alias less = "a < b", RangeOfRanges)(RangeOfRanges ror) [ 7 ], ]; assert(equal(multiwayUnion(b), witness)); + + double[][] c = + [ + [9, 8, 8, 8, 7, 6], + [9, 8, 6], + [9, 8, 5] + ]; + auto witness2 = [9, 8, 7, 6, 5]; + assert(equal(multiwayUnion!"a > b"(c), witness2)); } /** -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 +Lazily computes the difference of `r1` and `r2`. The two ranges +are assumed to be sorted by `less`. The element types of the two ranges must have a common type. In the case of multisets, considering that element `a` appears `x` -times in $(D r1) and `y` times and $(D r2), the number of occurences -of `a` in the resulting range is going to be `x-y` if x > y or 0 othwerise. +times in `r1` and `y` times and `r2`, the number of occurences +of `a` in the resulting range is going to be `x-y` if x > y or 0 otherwise. Params: less = Predicate the given ranges are sorted by. @@ -1048,7 +1097,7 @@ public: /// @property auto ref front() { - assert(!empty); + assert(!empty, "Can not get front of empty SetDifference"); return r1.front; } @@ -1095,7 +1144,8 @@ SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2) assert(setDifference(r, x).empty); } -@safe unittest // Issue 10460 +// https://issues.dlang.org/show_bug.cgi?id=10460 +@safe unittest { import std.algorithm.comparison : equal; @@ -1107,8 +1157,9 @@ SetDifference!(less, R1, R2) setDifference(alias less = "a < b", R1, R2) } /** -Lazily computes the intersection of two or more input ranges $(D -ranges). The ranges are assumed to be sorted by $(D less). The element +Lazily computes the intersection of two or more +$(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) +`ranges`. The ranges are assumed to be sorted by `less`. The element types of the ranges must have a common type. In the case of multisets, the range with the minimum number of @@ -1179,11 +1230,12 @@ public: /// void popFront() { - assert(!empty); + assert(!empty, "Can not popFront of empty SetIntersection"); static if (Rs.length > 1) foreach (i, ref r; _input) { alias next = _input[(i + 1) % Rs.length]; - assert(!comp(r.front, next.front)); + assert(!comp(r.front, next.front), "Set elements must not" + ~ " contradict the less predicate"); } foreach (ref r; _input) @@ -1196,7 +1248,7 @@ public: /// @property ElementType front() { - assert(!empty); + assert(!empty, "Can not get front of empty SetIntersection"); return _input[0].front; } @@ -1274,20 +1326,20 @@ if (Rs.length >= 2 && allSatisfy!(isInputRange, Rs) && } /** -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 +Lazily computes the symmetric difference of `r1` and `r2`, +i.e. the elements that are present in exactly one of `r1` and $(D +r2). The two ranges are assumed to be sorted by `less`, and the +output is also sorted by `less`. The element types of the two ranges must have a common type. If both ranges are sets (without duplicated elements), the resulting range is going to be a set. If at least one of the ranges is a multiset, the number of occurences of an element `x` in the resulting range is `abs(a-b)` -where `a` is the number of occurences of `x` in $(D r1), `b` is the number of -occurences of `x` in $(D r2), and `abs` is the absolute value. +where `a` is the number of occurences of `x` in `r1`, `b` is the number of +occurences of `x` in `r2`, and `abs` is the absolute value. If both arguments are ranges of L-values of the same type then -$(D SetSymmetricDifference) will also be a range of L-values of +`SetSymmetricDifference` will also be a range of L-values of that type. Params: @@ -1336,7 +1388,7 @@ public: /// void popFront() { - assert(!empty); + assert(!empty, "Can not popFront of empty SetSymmetricDifference"); if (r1.empty) r2.popFront(); else if (r2.empty) r1.popFront(); else @@ -1348,7 +1400,8 @@ public: } else { - assert(comp(r2.front, r1.front)); + assert(comp(r2.front, r1.front), "Elements of R1 and R2" + ~ " must be different"); r2.popFront(); } } @@ -1358,9 +1411,11 @@ public: /// @property auto ref front() { - assert(!empty); + assert(!empty, "Can not get the front of an empty" + ~ " SetSymmetricDifference"); immutable chooseR1 = r2.empty || !r1.empty && comp(r1.front, r2.front); - assert(chooseR1 || r1.empty || comp(r2.front, r1.front)); + assert(chooseR1 || r1.empty || comp(r2.front, r1.front), "Failed to" + ~ " get appropriate front"); return chooseR1 ? r1.front : r2.front; } @@ -1410,7 +1465,8 @@ setSymmetricDifference(alias less = "a < b", R1, R2) assert(equal(setSymmetricDifference(c, d), [1, 1, 2, 5, 6, 7, 9])); } -@safe unittest // Issue 10460 +// https://issues.dlang.org/show_bug.cgi?id=10460 +@safe unittest { import std.algorithm.comparison : equal; @@ -1435,8 +1491,8 @@ TODO: once SetUnion got deprecated we can provide the usual definition (= merge + filter after uniqs) See: https://github.com/dlang/phobos/pull/4249 /** -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 +Lazily computes the union of two or more ranges `rs`. The ranges +are assumed to be sorted by `less`. Elements in the output are unique. The element types of all ranges must have a common type. Params: diff --git a/libphobos/src/std/algorithm/sorting.d b/libphobos/src/std/algorithm/sorting.d index 2400bca..dbfe37f 100644 --- a/libphobos/src/std/algorithm/sorting.d +++ b/libphobos/src/std/algorithm/sorting.d @@ -1,29 +1,28 @@ // Written in the D programming language. /** This is a submodule of $(MREF std, algorithm). -It contains generic _sorting algorithms. +It contains generic sorting algorithms. $(SCRIPT inhibitQuickIndex = 1;) $(BOOKTABLE Cheat Sheet, $(TR $(TH Function Name) $(TH Description)) $(T2 completeSort, - If $(D a = [10, 20, 30]) and $(D b = [40, 6, 15]), then - $(D completeSort(a, b)) leaves $(D a = [6, 10, 15]) and $(D b = [20, - 30, 40]). - The range $(D a) must be sorted prior to the call, and as a result the - combination $(D $(REF chain, std,range)(a, b)) is sorted.) + If `a = [10, 20, 30]` and `b = [40, 6, 15]`, then + `completeSort(a, b)` leaves `a = [6, 10, 15]` and `b = [20, 30, 40]`. + The range `a` must be sorted prior to the call, and as a result the + combination `$(REF chain, std,range)(a, b)` is sorted.) $(T2 isPartitioned, - $(D isPartitioned!"a < 0"([-1, -2, 1, 0, 2])) returns $(D true) because - the predicate is $(D true) for a portion of the range and $(D false) + `isPartitioned!"a < 0"([-1, -2, 1, 0, 2])` returns `true` because + the predicate is `true` for a portion of the range and `false` afterwards.) $(T2 isSorted, - $(D isSorted([1, 1, 2, 3])) returns $(D true).) + `isSorted([1, 1, 2, 3])` returns `true`.) $(T2 isStrictlyMonotonic, - $(D isStrictlyMonotonic([1, 1, 2, 3])) returns $(D false).) + `isStrictlyMonotonic([1, 1, 2, 3])` returns `false`.) $(T2 ordered, - $(D ordered(1, 1, 2, 3)) returns $(D true).) + `ordered(1, 1, 2, 3)` returns `true`.) $(T2 strictlyOrdered, - $(D strictlyOrdered(1, 1, 2, 3)) returns $(D false).) + `strictlyOrdered(1, 1, 2, 3)` returns `false`.) $(T2 makeIndex, Creates a separate index for a range.) $(T2 merge, @@ -36,10 +35,13 @@ $(T2 nextEvenPermutation, $(T2 nextPermutation, Computes the next lexicographically greater permutation of a range in-place.) +$(T2 nthPermutation, + Computes the nth permutation of a range + in-place.) $(T2 partialSort, - If $(D a = [5, 4, 3, 2, 1]), then $(D partialSort(a, 3)) leaves - $(D a[0 .. 3] = [1, 2, 3]). - The other elements of $(D a) are left in an unspecified order.) + If `a = [5, 4, 3, 2, 1]`, then `partialSort(a, 3)` leaves + `a[0 .. 3] = [1, 2, 3]`. + The other elements of `a` are left in an unspecified order.) $(T2 partition, Partitions a range according to a unary predicate.) $(T2 partition3, @@ -55,7 +57,7 @@ $(T2 schwartzSort, $(T2 sort, Sorts.) $(T2 topN, - Separates the top elements in a range.) + Separates the top elements in a range, akin to $(LINK2 https://en.wikipedia.org/wiki/Quickselect, Quickselect).) $(T2 topNCopy, Copies out the top elements of a range.) $(T2 topNIndex, @@ -68,7 +70,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.com, Andrei Alexandrescu) -Source: $(PHOBOSSRC std/algorithm/_sorting.d) +Source: $(PHOBOSSRC std/algorithm/sorting.d) Macros: T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) @@ -76,33 +78,34 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) module std.algorithm.sorting; import std.algorithm.mutation : SwapStrategy; -import std.functional; // : unaryFun, binaryFun; +import std.functional : unaryFun, binaryFun; import std.range.primitives; -import std.typecons : Flag; -// FIXME -import std.meta; // : allSatisfy; -import std.range; // : SortedRange; +import std.typecons : Flag, No, Yes; +import std.meta : allSatisfy; +import std.range : SortedRange; import std.traits; /** Specifies whether the output of certain algorithm is desired in sorted format. -If set to $(D SortOutput.no), the output should not be sorted. +If set to `SortOutput.no`, the output should not be sorted. -Otherwise if set to $(D SortOutput.yes), the output should be sorted. +Otherwise if set to `SortOutput.yes`, the output should be sorted. */ alias SortOutput = Flag!"sortOutput"; // 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)) +Sorts the random-access range `chain(lhs, rhs)` according to +predicate `less`. + +The left-hand side of the range `lhs` is assumed to be already sorted; +`rhs` is assumed to be unsorted. +The exact strategy chosen depends on the relative sizes of `lhs` and +`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). +rhs.length)) (worst-case) evaluations of $(REF_ALTTEXT swap, swap, std,algorithm,mutation). Params: less = The predicate to sort by. @@ -112,8 +115,9 @@ Params: sorted. */ void completeSort(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, - RandomAccessRange1, RandomAccessRange2)(SortedRange!(RandomAccessRange1, less) lhs, RandomAccessRange2 rhs) -if (hasLength!(RandomAccessRange2) && hasSlicing!(RandomAccessRange2)) + Lhs , Rhs)(SortedRange!(Lhs, less) lhs, Rhs rhs) +if (hasLength!(Rhs) && hasSlicing!(Rhs) + && hasSwappableElements!Lhs && hasSwappableElements!Rhs) { import std.algorithm.mutation : bringToFront; import std.range : chain, assumeSorted; @@ -143,8 +147,8 @@ if (hasLength!(RandomAccessRange2) && hasSlicing!(RandomAccessRange2)) // isSorted /** Checks whether a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) -is sorted according to the comparison operation $(D less). Performs $(BIGOH r.length) -evaluations of $(D less). +is sorted according to the comparison operation `less`. Performs $(BIGOH r.length) +evaluations of `less`. Unlike `isSorted`, `isStrictlyMonotonic` does not allow for equal values, i.e. values for which both `less(a, b)` and `less(b, a)` are false. @@ -222,7 +226,7 @@ if (isForwardRange!(Range)) { import std.conv : to; - // Issue 9457 + // https://issues.dlang.org/show_bug.cgi?id=9457 auto x = "abcd"; assert(isSorted(x)); auto y = "acbd"; @@ -291,16 +295,16 @@ if (isForwardRange!Range) } /** -Like $(D isSorted), returns $(D true) if the given $(D values) are ordered -according to the comparison operation $(D less). Unlike $(D isSorted), takes values +Like `isSorted`, returns `true` if the given `values` are ordered +according to the comparison operation `less`. Unlike `isSorted`, takes values directly instead of structured in a range. -$(D ordered) allows repeated values, e.g. $(D ordered(1, 1, 2)) is $(D true). To verify -that the values are ordered strictly monotonically, use $(D strictlyOrdered); -$(D strictlyOrdered(1, 1, 2)) is $(D false). +`ordered` allows repeated values, e.g. `ordered(1, 1, 2)` is `true`. To verify +that the values are ordered strictly monotonically, use `strictlyOrdered`; +`strictlyOrdered(1, 1, 2)` is `false`. With either function, the predicate must be a strict ordering. For example, -using $(D "a <= b") instead of $(D "a < b") is incorrect and will cause failed +using `"a <= b"` instead of `"a < b"` is incorrect and will cause failed assertions. Params: @@ -308,8 +312,8 @@ Params: less = The comparison predicate Returns: - $(D true) if the values are ordered; $(D ordered) allows for duplicates, - $(D strictlyOrdered) does not. + `true` if the values are ordered; `ordered` allows for duplicates, + `strictlyOrdered` does not. */ bool ordered(alias less = "a < b", T...)(T values) @@ -366,16 +370,16 @@ if (is(typeof(ordered!less(values)))) // partition /** -Partitions a range in two using the given $(D predicate). -Specifically, reorders the range $(D r = [left, right$(RPAREN)) using $(D swap) -such that all elements $(D i) for which $(D predicate(i)) is $(D true) come -before all elements $(D j) for which $(D predicate(j)) returns $(D false). +Partitions a range in two using the given `predicate`. + +Specifically, reorders the range `r = [left, right$(RPAREN)` using $(REF_ALTTEXT swap, swap, std,algorithm,mutation) +such that all elements `i` for which `predicate(i)` is `true` come +before all elements `j` for which `predicate(j)` returns `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). +r.length * log(r.length)) (if stable) evaluations of `less` and $(REF_ALTTEXT swap, swap, std,algorithm,mutation). +The unstable version computes the minimum possible evaluations of `swap` +(roughly half of those performed by the semistable version). Params: predicate = The predicate to partition by. @@ -384,20 +388,18 @@ Params: Returns: -The right part of $(D r) after partitioning. +The right part of `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 predicate(a) == -predicate(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 predicate(a) == predicate(b)). - -See_Also: - STL's $(HTTP sgi.com/tech/stl/_partition.html, _partition)$(BR) - STL's $(HTTP sgi.com/tech/stl/stable_partition.html, stable_partition) +If `ss == SwapStrategy.stable`, `partition` preserves the relative +ordering of all elements `a`, `b` in `r` for which +`predicate(a) == predicate(b)`. +If `ss == SwapStrategy.semistable`, `partition` preserves +the relative ordering of all elements `a`, `b` in the left part of `r` +for which `predicate(a) == predicate(b)`. */ Range partition(alias predicate, SwapStrategy ss, Range)(Range r) -if (ss == SwapStrategy.stable && isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range) +if (ss == SwapStrategy.stable && isRandomAccessRange!(Range) && hasLength!Range && + hasSlicing!Range && hasSwappableElements!Range) { import std.algorithm.mutation : bringToFront; @@ -464,7 +466,7 @@ if (ss != SwapStrategy.stable && isInputRange!Range && hasSwappableElements!Rang ++lo; } // found the left bound - assert(lo <= hi); + assert(lo <= hi, "lo must be <= hi"); for (;;) { if (lo == hi) return r[lo .. r.length]; @@ -489,7 +491,7 @@ if (ss != SwapStrategy.stable && isInputRange!Range && hasSwappableElements!Rang result.popFront(); } // found the left bound - assert(!r.empty); + assert(!r.empty, "r must not be empty"); for (;;) { if (pred(r.back)) break; @@ -572,22 +574,23 @@ if (ss != SwapStrategy.stable && isInputRange!Range && hasSwappableElements!Rang // pivotPartition /** - Partitions `r` around `pivot` using comparison function `less`, algorithm akin to $(LINK2 https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme, -Hoare partition). Specifically, permutes elements of `r` and returns -an index $(D k < r.length) such that: +Hoare partition). + +Specifically, permutes elements of `r` and returns +an index `k < r.length` such that: $(UL $(LI `r[pivot]` is swapped to `r[k]`) -$(LI All elements `e` in subrange $(D r[0 .. k]) satisfy $(D !less(r[k], e)) +$(LI All elements `e` in subrange `r[0 .. k]` satisfy `!less(r[k], e)` (i.e. `r[k]` is greater than or equal to each element to its left according to predicate `less`)) -$(LI All elements `e` in subrange $(D r[0 .. k]) satisfy $(D !less(e, -r[k])) (i.e. `r[k]` is less than or equal to each element to its right +$(LI All elements `e` in subrange `r[k .. $]` satisfy `!less(e, r[k])` +(i.e. `r[k]` is less than or equal to each element to its right according to predicate `less`))) If `r` contains equivalent elements, multiple permutations of `r` satisfy these @@ -602,7 +605,7 @@ less = The predicate used for comparison, modeled as a equivalence) r = The range being partitioned pivot = The index of the pivot for partitioning, must be less than `r.length` or -`0` is `r.length` is `0` +`0` if `r.length` is `0` Returns: The new position of the pivot @@ -615,9 +618,10 @@ Keynote), Andrei Alexandrescu. */ size_t pivotPartition(alias less = "a < b", Range) (Range r, size_t pivot) -if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) +if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range && hasAssignableElements!Range) { - assert(pivot < r.length || r.length == 0 && pivot == 0); + assert(pivot < r.length || r.length == 0 && pivot == 0, "pivot must be" + ~ " less than the length of r or r must be empty and pivot zero"); if (r.length <= 1) return 0; import std.algorithm.mutation : swapAt, move; alias lt = binaryFun!less; @@ -635,17 +639,20 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) auto p = r[0]; // Plant the pivot in the end as well as a sentinel size_t lo = 0, hi = r.length - 1; - auto save = move(r[hi]); + auto save = r.moveAt(hi); r[hi] = p; // Vacancy is in r[$ - 1] now // Start process for (;;) { // Loop invariant - version (unittest) + version (StdUnittest) { - import std.algorithm.searching : all; - assert(r[0 .. lo].all!(x => !lt(p, x))); - assert(r[hi + 1 .. r.length].all!(x => !lt(x, p))); + // this used to import std.algorithm.all, but we want to save + // imports when unittests are enabled if possible. + foreach (x; r[0 .. lo]) + assert(!lt(p, x), "p must not be less than x"); + foreach (x; r[hi+1 .. r.length]) + assert(!lt(x, p), "x must not be less than p"); } do ++lo; while (lt(r[lo], p)); r[hi] = r[lo]; @@ -656,17 +663,17 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) // Vacancy is not in r[hi] } // Fixup - assert(lo - hi <= 2); - assert(!lt(p, r[hi])); + assert(lo - hi <= 2, "Following compare not possible"); + assert(!lt(p, r[hi]), "r[hi] must not be less than p"); if (lo == hi + 2) { - assert(!lt(r[hi + 1], p)); + assert(!lt(r[hi + 1], p), "r[hi + 1] must not be less than p"); r[lo] = r[hi + 1]; --lo; } r[lo] = save; if (lt(p, save)) --lo; - assert(!lt(p, r[lo])); + assert(!lt(p, r[lo]), "r[lo] must not be less than p"); } else { @@ -679,14 +686,14 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) if (!lt(r[lo], r[0])) break; } // found the left bound: r[lo] >= r[0] - assert(lo <= hi); + assert(lo <= hi, "lo must be less or equal than hi"); for (;; --hi) { if (lo >= hi) break loop; if (!lt(r[0], r[hi])) break; } // found the right bound: r[hi] <= r[0], swap & make progress - assert(!lt(r[lo], r[hi])); + assert(!lt(r[lo], r[hi]), "r[lo] must not be less than r[hi]"); r.swapAt(lo, hi); } --lo; @@ -747,17 +754,18 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) assert(a == [ 42, 42 ]); import std.algorithm.iteration : map; - import std.random; - import std.stdio; - auto s = unpredictableSeed; - auto g = Random(s); + import std.array : array; + import std.format : format; + import std.random : Random, uniform, Xorshift; + import std.range : iota; + auto s = 123_456_789; + auto g = Xorshift(s); a = iota(0, uniform(1, 1000, g)) .map!(_ => uniform(-1000, 1000, g)) .array; - scope(failure) writeln("RNG seed was ", s); pivot = pivotPartition!less(a, a.length / 2); - assert(a[0 .. pivot].all!(x => x <= a[pivot])); - assert(a[pivot .. $].all!(x => x >= a[pivot])); + assert(a[0 .. pivot].all!(x => x <= a[pivot]), "RNG seed: %d".format(s)); + assert(a[pivot .. $].all!(x => x >= a[pivot]), "RNG seed: %d".format(s)); } test!"a < b"; static bool myLess(int a, int b) @@ -773,7 +781,7 @@ if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range) Params: pred = The predicate that the range should be partitioned by. r = The range to check. -Returns: $(D true) if $(D r) is partitioned according to predicate $(D pred). +Returns: `true` if `r` is partitioned according to predicate `pred`. */ bool isPartitioned(alias pred, Range)(Range r) if (isForwardRange!(Range)) @@ -799,13 +807,15 @@ if (isForwardRange!(Range)) // 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). +Rearranges elements in `r` in three adjacent ranges and returns +them. + +The first and leftmost range only contains elements in `r` +less than `pivot`. The second and middle range only contains +elements in `r` that are equal to `pivot`. Finally, the third +and rightmost range only contains elements in `r` that are greater +than `pivot`. The less-than test is defined by the binary function +`less`. Params: less = The predicate to use for the rearrangement. @@ -817,7 +827,7 @@ Returns: A $(REF Tuple, std,typecons) of the three resulting ranges. These ranges are slices of the original range. -BUGS: stable $(D partition3) has not been implemented yet. +BUGS: stable `partition3` has not been implemented yet. */ auto partition3(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range, E) (Range r, E pivot) @@ -843,15 +853,15 @@ if (ss == SwapStrategy.unstable && isRandomAccessRange!Range for (;; ++j) { if (j == k) break bigloop; - assert(j < r.length); + assert(j < r.length, "j must be less than r.length"); if (lessFun(r[j], pivot)) continue; if (lessFun(pivot, r[j])) break; r.swapAt(i++, j); } - assert(j < k); + assert(j < k, "j must be less than k"); for (;;) { - assert(k > 0); + assert(k > 0, "k must be positive"); if (!lessFun(pivot, r[--k])) { if (lessFun(r[k], pivot)) break; @@ -886,9 +896,9 @@ if (ss == SwapStrategy.unstable && isRandomAccessRange!Range @safe unittest { - import std.random : Random, uniform, unpredictableSeed; + import std.random : Random = Xorshift, uniform; - immutable uint[] seeds = [3923355730, 1927035882, unpredictableSeed]; + immutable uint[] seeds = [3923355730, 1927035882]; foreach (s; seeds) { auto r = Random(s); @@ -916,8 +926,9 @@ if (ss == SwapStrategy.unstable && isRandomAccessRange!Range // 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 +Computes an index for `r` based on the comparison `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 @@ -926,15 +937,15 @@ 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. +collection. The complexity is the same as `sort`'s. -The first overload of $(D makeIndex) writes to a range containing +The first overload of `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 +first overload requires `Range` to be a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives), and the latter requires it to be a random-access range. -$(D makeIndex) overwrites its second argument with the result, but +`makeIndex` overwrites its second argument with the result, but never reallocates it. Params: @@ -943,11 +954,12 @@ Params: r = The range to index. index = The resulting index. -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). +Returns: The pointer-based version returns a `SortedRange` wrapper +over index, of type +`SortedRange!(RangeIndex, (a, b) => binaryFun!less(*a, *b))` +thus reflecting the ordering of the +index. The index-based version returns `void` because the ordering +relation involves not only `index` but also `r`. Throws: If the second argument's length is less than that of the range indexed, an exception is thrown. @@ -960,7 +972,7 @@ makeIndex( RangeIndex) (Range r, RangeIndex index) if (isForwardRange!(Range) && isRandomAccessRange!(RangeIndex) - && is(ElementType!(RangeIndex) : ElementType!(Range)*)) + && is(ElementType!(RangeIndex) : ElementType!(Range)*) && hasAssignableElements!RangeIndex) { import std.algorithm.internal : addressOf; import std.exception : enforce; @@ -984,7 +996,7 @@ void makeIndex( (Range r, RangeIndex index) if (isRandomAccessRange!Range && !isInfinite!Range && isRandomAccessRange!RangeIndex && !isInfinite!RangeIndex && - isIntegral!(ElementType!RangeIndex)) + isIntegral!(ElementType!RangeIndex) && hasAssignableElements!RangeIndex) { import std.conv : to; import std.exception : enforce; @@ -1059,6 +1071,7 @@ if (isRandomAccessRange!Range && !isInfinite!Range && @safe unittest { import std.algorithm.comparison : equal; + import std.range : iota; ubyte[256] index = void; iota(256).makeIndex(index[]); @@ -1085,8 +1098,8 @@ if (Rs.length >= 2 && } import std.functional : binaryFun; + import std.meta : anySatisfy; import std.traits : isCopyable; - import std.typetuple : anySatisfy; private alias comp = binaryFun!less; private alias ElementType = CommonType!(staticMap!(.ElementType, Rs)); @@ -1119,7 +1132,7 @@ if (Rs.length >= 2 && foreach (i, _; Rs) { case i: - assert(!source[i].empty); + assert(!source[i].empty, "Can not get front of empty Merge"); return source[i].front; } } @@ -1158,8 +1171,7 @@ if (Rs.length >= 2 && { if (!source[i].empty) { - assert(previousFront == source[i].front || - comp(previousFront, source[i].front), + assert(!comp(source[i].front, previousFront), "Input " ~ i.stringof ~ " is unsorted"); // @nogc } } @@ -1182,7 +1194,7 @@ if (Rs.length >= 2 && foreach (i, _; Rs) { case i: - assert(!source[i].empty); + assert(!source[i].empty, "Can not get back of empty Merge"); return source[i].back; } } @@ -1225,8 +1237,7 @@ if (Rs.length >= 2 && { if (!source[i].empty) { - assert(previousBack == source[i].back || - comp(source[i].back, previousBack), + assert(!comp(previousBack, source[i].back), "Input " ~ i.stringof ~ " is unsorted"); // @nogc } } @@ -1273,7 +1284,9 @@ if (Rs.length >= 2 && /** Merge multiple sorted ranges `rs` with less-than predicate function `pred` into one single sorted output range containing the sorted union of the - elements of inputs. Duplicates are not eliminated, meaning that the total + elements of inputs. + + Duplicates are not eliminated, meaning that the total number of elements in the output is the sum of all elements in the ranges passed to it; the `length` member is offered if all inputs also have `length`. The element types of all the inputs must have a common type @@ -1308,6 +1321,9 @@ All of its inputs are assumed to be sorted. This can mean that inputs are If any of the inputs `rs` is infinite so is the result (`empty` being always `false`). + +See_Also: $(REF multiwayMerge, std,algorithm,setops) for an analogous function + that merges a dynamic number of ranges. */ Merge!(less, Rs) merge(alias less = "a < b", Rs...)(Rs rs) if (Rs.length >= 2 && @@ -1427,6 +1443,37 @@ if (Rs.length >= 2 && assert(m.empty); } +// Issue 21810: Check for sortedness must not use `==` +@nogc @safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.typecons : tuple; + + static immutable a = [ + tuple(1, 1), + tuple(3, 1), + tuple(3, 2), + tuple(5, 1), + ]; + static immutable b = [ + tuple(2, 1), + tuple(3, 1), + tuple(4, 1), + tuple(4, 2), + ]; + static immutable r = [ + tuple(1, 1), + tuple(2, 1), + tuple(3, 1), + tuple(3, 2), + tuple(3, 1), + tuple(4, 1), + tuple(4, 2), + tuple(5, 1), + ]; + assert(merge!"a[0] < b[0]"(a, b).equal(r)); +} + private template validPredicates(E, less...) { static if (less.length == 0) @@ -1440,24 +1487,23 @@ private template validPredicates(E, less...) } /** -$(D auto multiSort(Range)(Range r) - if (validPredicates!(ElementType!Range, less));) +Sorts a range by multiple keys. -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) +The call $(D multiSort!("a.id < b.id", +"a.date > b.date")(r)) sorts the range `r` by `id` ascending, +and sorts elements that have the same `id` by `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 +< b.id : a.date > b.date"(r)), but `multiSort` is faster because it does fewer comparisons (in addition to being more convenient). Returns: - The initial range wrapped as a $(D SortedRange) with its predicates + The initial range wrapped as a `SortedRange` with its predicates converted to an equivalent single predicate. */ template multiSort(less...) //if (less.length > 1) { auto multiSort(Range)(Range r) - if (validPredicates!(ElementType!Range, less)) + if (validPredicates!(ElementType!Range, less) && hasSwappableElements!Range) { import std.meta : AliasSeq; import std.range : assumeSorted; @@ -1485,6 +1531,17 @@ template multiSort(less...) //if (less.length > 1) } } +/// +@safe unittest +{ + import std.algorithm.mutation : SwapStrategy; + 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); +} + private bool multiSortPredFun(Range, funs...)(ElementType!Range a, ElementType!Range b) { foreach (f; funs) @@ -1526,17 +1583,6 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r) } } -/// -@safe unittest -{ - import std.algorithm.mutation : SwapStrategy; - 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 { import std.algorithm.comparison : equal; @@ -1556,7 +1602,8 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r) assert(pts4.multiSort!("a > b").release.equal(iota(10).retro)); } -@safe unittest //issue 9160 (L-value only comparators) +//https://issues.dlang.org/show_bug.cgi?id=9160 (L-value only comparators) +@safe unittest { static struct A { @@ -1580,7 +1627,9 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r) assert(points[1] == A(4, 1)); } -@safe unittest // issue 16179 (cannot access frame of function) +// cannot access frame of function +// https://issues.dlang.org/show_bug.cgi?id=16179 +@safe unittest { auto arr = [[1, 2], [2, 0], [1, 0], [1, 1]]; int c = 3; @@ -1592,7 +1641,8 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r) assert(arr == [[1, 0], [1, 1], [1, 2], [2, 0]]); } -@safe unittest //Issue 16413 - @system comparison function +// https://issues.dlang.org/show_bug.cgi?id=16413 - @system comparison function +@safe unittest { bool lt(int a, int b) { return a < b; } static @system auto a = [2, 1]; @@ -1676,7 +1726,7 @@ private void shortSort(alias less, Range)(Range r) break; } - assert(r.length >= 6); + assert(r.length >= 6, "r must have more than 5 elements"); /* The last 5 elements of the range are sorted. Proceed with expanding the sorted portion downward. */ immutable maxJ = r.length - 2; @@ -1687,17 +1737,30 @@ private void shortSort(alias less, Range)(Range r) auto t = r[0]; if (pred(t, r[0])) r[0] = r[0]; }))) // Can we afford to temporarily invalidate the array? { + import core.lifetime : move; + size_t j = i + 1; - auto temp = r[i]; + static if (hasLvalueElements!Range) + auto temp = move(r[i]); + else + auto temp = r[i]; + if (pred(r[j], temp)) { do { - r[j - 1] = r[j]; + static if (hasLvalueElements!Range) + trustedMoveEmplace(r[j], r[j - 1]); + else + r[j - 1] = r[j]; ++j; } while (j < r.length && pred(r[j], temp)); - r[j - 1] = temp; + + static if (hasLvalueElements!Range) + trustedMoveEmplace(temp, r[j - 1]); + else + r[j - 1] = move(temp); } } else @@ -1714,9 +1777,16 @@ private void shortSort(alias less, Range)(Range r) } } +/// @trusted wrapper for moveEmplace +private void trustedMoveEmplace(T)(ref T source, ref T target) @trusted +{ + import core.lifetime : moveEmplace; + moveEmplace(source, target); +} + @safe unittest { - import std.random : Random, uniform; + import std.random : Random = Xorshift, uniform; auto rnd = Random(1); auto a = new int[uniform(100, 200, rnd)]; @@ -1734,7 +1804,7 @@ Sorts the first 5 elements exactly of range r. */ private void sort5(alias lt, Range)(Range r) { - assert(r.length >= 5); + assert(r.length >= 5, "r must have more than 4 elements"); import std.algorithm.mutation : swapAt; @@ -1748,7 +1818,8 @@ private void sort5(alias lt, Range)(Range r) r.swapAt(0, 2); r.swapAt(1, 3); } - assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[3], r[2])); + assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[3], r[2]), "unexpected" + ~ " order"); // 3. Insert 4 into [0, 1, 3] if (lt(r[4], r[1])) @@ -1764,10 +1835,11 @@ private void sort5(alias lt, Range)(Range r) { r.swapAt(3, 4); } - assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[4], r[3])); + assert(!lt(r[1], r[0]) && !lt(r[3], r[1]) && !lt(r[4], r[3]), "unexpected" + ~ " order"); // 4. Insert 2 into [0, 1, 3, 4] (note: we already know the last is greater) - assert(!lt(r[4], r[2])); + assert(!lt(r[4], r[2]), "unexpected order"); if (lt(r[2], r[1])) { r.swapAt(1, 2); @@ -1787,6 +1859,7 @@ private void sort5(alias lt, Range)(Range r) { import std.algorithm.iteration : permutations; import std.algorithm.mutation : copy; + import std.range : iota; int[5] buf; foreach (per; iota(5).permutations) @@ -1799,14 +1872,15 @@ private void sort5(alias lt, Range)(Range r) // sort /** -Sorts a random-access range according to the predicate $(D less). Performs -$(BIGOH r.length * log(r.length)) evaluations of $(D less). If `less` involves +Sorts a random-access range according to the predicate `less`. + +Performs $(BIGOH r.length * log(r.length)) evaluations of `less`. If `less` involves expensive computations on the _sort key, it may be worthwhile to use $(LREF schwartzSort) instead. -Stable sorting requires $(D hasAssignableElements!Range) to be true. +Stable sorting requires `hasAssignableElements!Range` to be true. -$(D sort) returns a $(REF SortedRange, std,range) over the original range, +`sort` returns a $(REF SortedRange, std,range) over the original range, allowing functions that can take advantage of sorted data to know that the range is sorted and adjust accordingly. The $(REF SortedRange, std,range) is a wrapper around the original range, so both it and the original range are sorted. @@ -1815,14 +1889,14 @@ they $(I can) know that $(REF SortedRange, std,range) has been sorted. Preconditions: -The predicate is expected to satisfy certain rules in order for $(D sort) to +The predicate is expected to satisfy certain rules in order for `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 +others) when not compiled in release mode, due to the cursory `assumeSorted` +check. Specifically, `sort` expects `less(a,b) && less(b,c)` to imply +`less(a,c)` (transitivity), and, conversely, `!less(a,b) && !less(b,c)` to +imply `!less(a,c)`. Note that the default predicate (`"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. +will always be `false` when either `a` or `b` is NaN. Use $(REF cmp, std,math) instead. Params: @@ -1830,8 +1904,8 @@ Params: ss = The swapping strategy to use. r = The range to sort. -Returns: The initial range wrapped as a $(D SortedRange) with the predicate -$(D binaryFun!less). +Returns: The initial range wrapped as a `SortedRange` with the predicate +`binaryFun!less`. Algorithms: $(HTTP en.wikipedia.org/wiki/Introsort, Introsort) is used for unstable sorting and $(HTTP en.wikipedia.org/wiki/Timsort, Timsort) is used for stable sorting. @@ -1916,7 +1990,8 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || double[] numbers = [-0.0, 3.0, -2.0, double.nan, 0.0, -double.nan]; import std.algorithm.comparison : equal; - import std.math : cmp, isIdentical; + import std.math.operations : cmp; + import std.math.traits : isIdentical; sort!((a, b) => cmp(a, b) < 0)(numbers); @@ -1927,7 +2002,10 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || @safe unittest { // Simple regression benchmark - import std.algorithm.iteration, std.algorithm.mutation, std.random; + import std.algorithm.iteration, std.algorithm.mutation; + import std.array : array; + import std.random : Random, uniform; + import std.range : iota; Random rng; int[] a = iota(20148).map!(_ => uniform(-1000, 1000, rng)).array; static uint comps; @@ -1941,7 +2019,7 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || // This should get smaller with time. On occasion it may go larger, but only // if there's thorough justification. - debug enum uint watermark = 1676280; + debug enum uint watermark = 1676220; else enum uint watermark = 1676220; import std.conv; @@ -1955,12 +2033,12 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || { import std.algorithm.internal : rndstuff; import std.algorithm.mutation : swapRanges; - import std.random : Random, unpredictableSeed, uniform; + import std.random : Random = Xorshift, uniform; import std.uni : toUpper; // sort using delegate auto a = new int[100]; - auto rnd = Random(unpredictableSeed); + auto rnd = Random(123_456_789); foreach (ref e; a) { e = uniform(-100, 100, rnd); @@ -2002,14 +2080,14 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || assert(isSorted!("toUpper(a) < toUpper(b)")(b)); { - // Issue 10317 + // https://issues.dlang.org/show_bug.cgi?id=10317 enum E_10317 { a, b } auto a_10317 = new E_10317[10]; sort(a_10317); } { - // Issue 7767 + // https://issues.dlang.org/show_bug.cgi?id=7767 // Unstable sort should complete without an excessive number of predicate calls // This would suggest it's running in quadratic time @@ -2050,16 +2128,29 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range || r.sort(); assert(proxySwapCalled); } + + // https://issues.dlang.org/show_bug.cgi?id=20751 + { + static bool refPred(ref int a, ref int b) + { + return a < b; + } + + auto sortedArr = [5,4,3,2,1].sort!refPred; + sortedArr.equalRange(3); + } } private void quickSortImpl(alias less, Range)(Range r, size_t depth) { import std.algorithm.comparison : min, max; import std.algorithm.mutation : swap, swapAt; + import std.conv : to; alias Elem = ElementType!(Range); enum size_t shortSortGetsBetter = max(32, 1024 / Elem.sizeof); - static assert(shortSortGetsBetter >= 1); + static assert(shortSortGetsBetter >= 1, Elem.stringof ~ " " + ~ to!string(Elem.sizeof)); // partition while (r.length > shortSortGetsBetter) @@ -2114,13 +2205,15 @@ package(std) template HeapOps(alias less, Range) { import std.algorithm.mutation : swapAt; - static assert(isRandomAccessRange!Range); - static assert(hasLength!Range); - static assert(hasSwappableElements!Range || hasAssignableElements!Range); + static assert(isRandomAccessRange!Range, Range.stringof ~ " must be a" + ~ " RandomAccessRange"); + static assert(hasLength!Range, Range.stringof ~ " must have length"); + static assert(hasSwappableElements!Range || hasAssignableElements!Range, + Range.stringof ~ " must have swappable of assignable Elements"); alias lessFun = binaryFun!less; - //template because of @@@12410@@@ + //template because of https://issues.dlang.org/show_bug.cgi?id=12410 void heapSort()(Range r) { // If true, there is nothing to do @@ -2135,7 +2228,7 @@ package(std) template HeapOps(alias less, Range) } } - //template because of @@@12410@@@ + //template because of https://issues.dlang.org/show_bug.cgi?id=12410 void buildHeap()(Range r) { immutable n = r.length; @@ -2143,7 +2236,7 @@ package(std) template HeapOps(alias less, Range) { siftDown(r, i, n); } - assert(isHeap(r)); + assert(isHeap(r), "r is not a heap"); } bool isHeap()(Range r) @@ -2160,7 +2253,7 @@ package(std) template HeapOps(alias less, Range) // Sifts down r[parent] (which is initially assumed to be messed up) so the // heap property is restored for r[parent .. end]. - // template because of @@@12410@@@ + // template because of https://issues.dlang.org/show_bug.cgi?id=12410 void siftDown()(Range r, size_t parent, immutable size_t end) { for (;;) @@ -2188,7 +2281,7 @@ package(std) template HeapOps(alias less, Range) // restored. So there are more swaps but fewer comparisons. Gains are made // when the final position is likely to end toward the bottom of the heap, // so not a lot of sifts back are performed. - //template because of @@@12410@@@ + //template because of https://issues.dlang.org/show_bug.cgi?id=12410 void percolate()(Range r, size_t parent, immutable size_t end) { immutable root = parent; @@ -2232,17 +2325,19 @@ private template TimSortImpl(alias pred, R) import core.bitop : bsr; import std.array : uninitializedArray; - static assert(isRandomAccessRange!R); - static assert(hasLength!R); - static assert(hasSlicing!R); - static assert(hasAssignableElements!R); + static assert(isRandomAccessRange!R, R.stringof ~ " must be a" + ~ " RandomAccessRange"); + static assert(hasLength!R, R.stringof ~ " must have a length"); + static assert(hasSlicing!R, R.stringof ~ " must support slicing"); + static assert(hasAssignableElements!R, R.stringof ~ " must support" + ~ " assigning elements"); alias T = ElementType!R; alias less = binaryFun!pred; - alias greater = (a, b) => less(b, a); - alias greaterEqual = (a, b) => !less(a, b); - alias lessEqual = (a, b) => !less(b, a); + bool greater()(auto ref T a, auto ref T b) { return less(b, a); } + bool greaterEqual()(auto ref T a, auto ref T b) { return !less(a, b); } + bool lessEqual()(auto ref T a, auto ref T b) { return !less(b, a); } enum minimalMerge = 128; enum minimalGallop = 7; @@ -2255,6 +2350,7 @@ private template TimSortImpl(alias pred, R) void sort()(R range, T[] temp) { import std.algorithm.comparison : min; + import std.format : format; // Do insertion sort on small range if (range.length <= minimalMerge) @@ -2312,15 +2408,27 @@ private template TimSortImpl(alias pred, R) } // Assert that the code above established the invariant correctly - version (assert) + version (StdUnittest) { - if (stackLen == 2) assert(stack[0].length > stack[1].length); + if (stackLen == 2) + { + assert(stack[0].length > stack[1].length, format! + "stack[0].length %s > stack[1].length %s"( + stack[0].length, stack[1].length + )); + } else if (stackLen > 2) { foreach (k; 2 .. stackLen) { - assert(stack[k - 2].length > stack[k - 1].length + stack[k].length); - assert(stack[k - 1].length > stack[k].length); + assert(stack[k - 2].length > stack[k - 1].length + stack[k].length, + format!"stack[k - 2].length %s > stack[k - 1].length %s + stack[k].length %s"( + stack[k - 2].length, stack[k - 1].length, stack[k].length + )); + assert(stack[k - 1].length > stack[k].length, + format!"stack[k - 1].length %s > stack[k].length %s"( + stack[k - 1].length, stack[k].length + )); } } } @@ -2352,9 +2460,10 @@ private template TimSortImpl(alias pred, R) size_t firstRun()(R range) out(ret) { - assert(ret <= range.length); + assert(ret <= range.length, "ret must be less or equal than" + ~ " range.length"); } - body + do { import std.algorithm.mutation : reverse; @@ -2377,9 +2486,9 @@ private template TimSortImpl(alias pred, R) void binaryInsertionSort()(R range, size_t sortedLen = 1) out { - if (!__ctfe) assert(isSorted!pred(range)); + if (!__ctfe) assert(isSorted!pred(range), "range must be sorted"); } - body + do { import std.algorithm.mutation : move; @@ -2400,8 +2509,17 @@ private template TimSortImpl(alias pred, R) //moveAll(retro(range[lower .. sortedLen]), // retro(range[lower+1 .. sortedLen+1])); for (upper=sortedLen; upper > lower; upper--) - range[upper] = range.moveAt(upper - 1); - range[lower] = move(item); + { + static if (hasLvalueElements!R) + move(range[upper -1], range[upper]); + else + range[upper] = range.moveAt(upper - 1); + } + + static if (hasLvalueElements!R) + move(item, range[lower]); + else + range[lower] = move(item); } } @@ -2409,10 +2527,12 @@ private template TimSortImpl(alias pred, R) void mergeAt()(R range, Slice[] stack, immutable size_t at, ref size_t minGallop, ref T[] temp) in { - assert(stack.length >= 2); - assert(stack.length - at == 2 || stack.length - at == 3); + import std.format : format; + assert(stack.length >= 2, "stack be be greater than 1"); + assert(stack.length - at == 2 || stack.length - at == 3, + format!"stack.length - at %s must be 2 or 3"(stack.length - at)); } - body + do { immutable base = stack[at].base; immutable mid = stack[at].length; @@ -2433,13 +2553,16 @@ private template TimSortImpl(alias pred, R) { if (!__ctfe) { - assert(isSorted!pred(range[0 .. mid])); - assert(isSorted!pred(range[mid .. range.length])); + assert(isSorted!pred(range[0 .. mid]), "range[0 .. mid] must be" + ~ " sorted"); + assert(isSorted!pred(range[mid .. range.length]), "range[mid .." + ~ " range.length] must be sorted"); } } - body + do { - assert(mid < range.length); + assert(mid < range.length, "mid must be less than the length of the" + ~ " range"); // Reduce range of elements immutable firstElement = gallopForwardUpper(range[0 .. mid], range[mid]); @@ -2466,9 +2589,9 @@ private template TimSortImpl(alias pred, R) T[] ensureCapacity()(size_t minCapacity, T[] temp) out(ret) { - assert(ret.length >= minCapacity); + assert(ret.length >= minCapacity, "ensuring the capacity failed"); } - body + do { if (temp.length < minCapacity) { @@ -2487,21 +2610,22 @@ private template TimSortImpl(alias pred, R) size_t mergeLo()(R range, immutable size_t mid, size_t minGallop, T[] temp) out { - if (!__ctfe) assert(isSorted!pred(range)); + if (!__ctfe) assert(isSorted!pred(range), "the range must be sorted"); } - body + do { import std.algorithm.mutation : copy; - assert(mid <= range.length); - assert(temp.length >= mid); + assert(mid <= range.length, "mid must be less than the length of the" + ~ " range"); + assert(temp.length >= mid, "temp.length must be greater or equal to mid"); // Copy run into temporary memory temp = temp[0 .. mid]; copy(range[0 .. mid], temp); // Move first element into place - range[0] = range[mid]; + moveEntry(range, mid, range, 0); size_t i = 1, lef = 0, rig = mid + 1; size_t count_lef, count_rig; @@ -2518,14 +2642,14 @@ private template TimSortImpl(alias pred, R) { if (lessEqual(temp[lef], range[rig])) { - range[i++] = temp[lef++]; + moveEntry(temp, lef++, range, i++); if (lef >= lef_end) break outer; ++count_lef; count_rig = 0; } else { - range[i++] = range[rig++]; + moveEntry(range, rig++, range, i++); if (rig >= range.length) break outer; count_lef = 0; ++count_rig; @@ -2536,14 +2660,14 @@ private template TimSortImpl(alias pred, R) do { count_lef = gallopForwardUpper(temp[lef .. $], range[rig]); - foreach (j; 0 .. count_lef) range[i++] = temp[lef++]; + foreach (j; 0 .. count_lef) moveEntry(temp, lef++, range, i++); if (lef >= temp.length) break outer; count_rig = gallopForwardLower(range[rig .. range.length], temp[lef]); - foreach (j; 0 .. count_rig) range[i++] = range[rig++]; + foreach (j; 0 .. count_rig) moveEntry(range, rig++, range, i++); if (rig >= range.length) while (true) { - range[i++] = temp[lef++]; + moveEntry(temp, lef++, range, i++); if (lef >= temp.length) break outer; } @@ -2556,11 +2680,11 @@ private template TimSortImpl(alias pred, R) // Move remaining elements from right while (rig < range.length) - range[i++] = range[rig++]; + moveEntry(range, rig++, range, i++); // Move remaining elements from left while (lef < temp.length) - range[i++] = temp[lef++]; + moveEntry(temp, lef++, range, i++); return minGallop > 0 ? minGallop : 1; } @@ -2570,21 +2694,24 @@ private template TimSortImpl(alias pred, R) size_t mergeHi()(R range, immutable size_t mid, size_t minGallop, T[] temp) out { - if (!__ctfe) assert(isSorted!pred(range)); + if (!__ctfe) assert(isSorted!pred(range), "the range must be sorted"); } - body + do { import std.algorithm.mutation : copy; + import std.format : format; - assert(mid <= range.length); - assert(temp.length >= range.length - mid); + assert(mid <= range.length, "mid must be less or equal to range.length"); + assert(temp.length >= range.length - mid, format! + "temp.length %s >= range.length %s - mid %s"(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]; + moveEntry(range, mid - 1, range, range.length - 1); size_t i = range.length - 2, lef = mid - 2, rig = temp.length - 1; size_t count_lef, count_rig; @@ -2600,19 +2727,19 @@ private template TimSortImpl(alias pred, R) { if (greaterEqual(temp[rig], range[lef])) { - range[i--] = temp[rig]; + moveEntry(temp, rig, range, i--); if (rig == 1) { // Move remaining elements from left while (true) { - range[i--] = range[lef]; + moveEntry(range, lef, range, i--); if (lef == 0) break; --lef; } // Move last element into place - range[i] = temp[0]; + moveEntry(temp, 0, range, i); break outer; } @@ -2622,10 +2749,10 @@ private template TimSortImpl(alias pred, R) } else { - range[i--] = range[lef]; + moveEntry(range, lef, range, i--); if (lef == 0) while (true) { - range[i--] = temp[rig]; + moveEntry(temp, rig, range, i--); if (rig == 0) break outer; --rig; } @@ -2641,7 +2768,7 @@ private template TimSortImpl(alias pred, R) count_rig = rig - gallopReverseLower(temp[0 .. rig], range[lef]); foreach (j; 0 .. count_rig) { - range[i--] = temp[rig]; + moveEntry(temp, rig, range, i--); if (rig == 0) break outer; --rig; } @@ -2649,10 +2776,10 @@ private template TimSortImpl(alias pred, R) count_lef = lef - gallopReverseUpper(range[0 .. lef], temp[rig]); foreach (j; 0 .. count_lef) { - range[i--] = range[lef]; + moveEntry(range, lef, range, i--); if (lef == 0) while (true) { - range[i--] = temp[rig]; + moveEntry(temp, rig, range, i--); if (rig == 0) break outer; --rig; } @@ -2676,9 +2803,10 @@ private template TimSortImpl(alias pred, R) size_t gallopSearch(R)(R range, T value) out(ret) { - assert(ret <= range.length); + assert(ret <= range.length, "ret must be less or equal to" + ~ " range.length"); } - body + do { size_t lower = 0, center = 1, upper = range.length; alias gap = center; @@ -2748,6 +2876,21 @@ private template TimSortImpl(alias pred, R) alias gallopForwardUpper = gallopSearch!(false, true); alias gallopReverseLower = gallopSearch!( true, false); alias gallopReverseUpper = gallopSearch!( true, true); + + /// Helper method that moves from[fIdx] into to[tIdx] if both are lvalues and + /// uses a plain assignment if not (necessary for backwards compatibility) + void moveEntry(X, Y)(ref X from, const size_t fIdx, ref Y to, const size_t tIdx) + { + // This template is instantiated with different combinations of range (R) and temp (T[]). + // T[] obviously has lvalue-elements, so checking R should be enough here + static if (hasLvalueElements!R) + { + import core.lifetime : move; + move(from[fIdx], to[tIdx]); + } + else + to[tIdx] = from[fIdx]; + } } @safe unittest @@ -2797,6 +2940,7 @@ private template TimSortImpl(alias pred, R) // Tests the Timsort function for correctness and stability static bool testSort(uint seed) { + import std.format : format; auto arr = genSampleData(seed); // Now sort the array! @@ -2808,12 +2952,15 @@ private template TimSortImpl(alias pred, R) sort!(comp, SwapStrategy.stable)(arr); // Test that the array was sorted correctly - assert(isSorted!comp(arr)); + assert(isSorted!comp(arr), "arr must be sorted"); // 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); + if (arr[i].value == arr[i + 1].value) + assert(arr[i].index < arr[i + 1].index, format! + "arr[i %s].index %s < arr[i + 1].index %s"( + i, arr[i].index, arr[i + 1].index)); } return true; @@ -2823,11 +2970,12 @@ private template TimSortImpl(alias pred, R) testSort(seed); enum result = testSort(seed); - assert(result == true); + assert(result == true, "invalid result"); } +// https://issues.dlang.org/show_bug.cgi?id=4584 @safe 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] @@ -2847,18 +2995,54 @@ private template TimSortImpl(alias pred, R) assert(y == "aebcd"d); } +// https://issues.dlang.org/show_bug.cgi?id=14223 @safe unittest { - // Issue 14223 import std.array, std.range; auto arr = chain(iota(0, 384), iota(0, 256), iota(0, 80), iota(0, 64), iota(0, 96)).array; sort!("a < b", SwapStrategy.stable)(arr); } +@safe unittest +{ + static struct NoCopy + { + pure nothrow @nogc @safe: + + int key; + this(scope const ref NoCopy) + { + assert(false, "Tried to copy struct!"); + } + ref opAssign()(scope const auto ref NoCopy other) + { + assert(false, "Tried to copy struct!"); + } + this(this) {} + } + + static NoCopy[] makeArray(const size_t size) + { + NoCopy[] array = new NoCopy[](size); + foreach (const i, ref t; array[0..$/2]) t.key = cast(int) (size - i); + foreach (const i, ref t; array[$/2..$]) t.key = cast(int) i; + return array; + } + + alias cmp = (ref NoCopy a, ref NoCopy b) => a.key < b.key; + enum minMerge = TimSortImpl!(cmp, NoCopy[]).minimalMerge; + + sort!(cmp, SwapStrategy.unstable)(makeArray(20)); + sort!(cmp, SwapStrategy.stable)(makeArray(minMerge - 5)); + sort!(cmp, SwapStrategy.stable)(makeArray(minMerge + 5)); +} + // schwartzSort /** Alternative sorting method that should be used when comparing keys involves an -expensive computation. Instead of using `less(a, b)` for comparing elements, +expensive computation. + +Instead of using `less(a, b)` for comparing elements, `schwartzSort` uses `less(transform(a), transform(b))`. The values of the `transform` function are precomputed in a temporary array, thus saving on repeatedly computing it. Conversely, if the cost of `transform` is small @@ -2882,46 +3066,75 @@ sort!((a, b) => hashFun(a) < hashFun(b))(array); schwartzSort!(hashFun, "a < b")(array); ---- -The $(D schwartzSort) function might require less temporary data and +The `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 +Schwartz sorting, a function `schwartzIsSorted` is not provided because the effect can be achieved by calling $(D isSorted!less(map!transform(r))). Params: - transform = The transformation to apply. - less = The predicate to sort by. + transform = The transformation to apply. Either a unary function + (`unaryFun!transform(element)`), or a binary function + (`binaryFun!transform(element, index)`). + less = The predicate to sort the transformed elements by. ss = The swapping strategy to use. r = The range to sort. -Returns: The initial range wrapped as a $(D SortedRange) with the -predicate $(D (a, b) => binaryFun!less(transform(a), -transform(b))). +Returns: The initial range wrapped as a `SortedRange` with the +predicate `(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) +if (isRandomAccessRange!R && hasLength!R && hasSwappableElements!R && + !is(typeof(binaryFun!less) == SwapStrategy)) { - import std.conv : emplace; + import core.lifetime : emplace; import std.range : zip, SortedRange; import std.string : representation; - alias T = typeof(unaryFun!transform(r.front)); + static if (is(typeof(unaryFun!transform(r.front)))) + { + alias transformFun = unaryFun!transform; + alias TB = typeof(transformFun(r.front)); + enum isBinary = false; + } + else static if (is(typeof(binaryFun!transform(r.front, 0)))) + { + alias transformFun = binaryFun!transform; + alias TB = typeof(transformFun(r.front, 0)); + enum isBinary = true; + } + else + static assert(false, "unsupported `transform` alias"); + + // The `transform` function might return a qualified type, e.g. const(int). + // Strip qualifiers if possible s.t. the temporary array is sortable. + static if (is(TB : Unqual!TB)) + alias T = Unqual!TB; + else + static assert(false, "`transform` returns an unsortable qualified type: " ~ TB.stringof); + static trustedMalloc(size_t len) @trusted { import core.checkedint : mulu; import core.stdc.stdlib : malloc; bool overflow; const nbytes = mulu(len, T.sizeof, overflow); - if (overflow) assert(0); - return (cast(T*) malloc(nbytes))[0 .. len]; + if (overflow) assert(false, "multiplication overflowed"); + T[] result = (cast(T*) malloc(nbytes))[0 .. len]; + static if (hasIndirections!T) + { + import core.memory : GC; + GC.addRange(result.ptr, nbytes); + } + return result; } auto xform1 = trustedMalloc(r.length); @@ -2935,13 +3148,21 @@ if (isRandomAccessRange!R && hasLength!R) static void trustedFree(T[] p) @trusted { import core.stdc.stdlib : free; + static if (hasIndirections!T) + { + import core.memory : GC; + GC.removeRange(p.ptr); + } free(p.ptr); } trustedFree(xform1); } for (; length != r.length; ++length) { - emplace(&xform1[length], unaryFun!transform(r[length])); + static if (isBinary) + emplace(&xform1[length], transformFun(r[length], length)); + else + emplace(&xform1[length], transformFun(r[length])); } // Make sure we use ubyte[] and ushort[], not char[] and wchar[] // for the intermediate array, lest zip gets confused. @@ -2957,6 +3178,13 @@ if (isRandomAccessRange!R && hasLength!R) return typeof(return)(r); } +/// ditto +auto schwartzSort(alias transform, SwapStrategy ss, R)(R r) +if (isRandomAccessRange!R && hasLength!R && hasSwappableElements!R) +{ + return schwartzSort!(transform, "a < b", ss, R)(r); +} + /// @safe unittest { @@ -3002,27 +3230,109 @@ if (isRandomAccessRange!R && hasLength!R) @safe unittest { - // issue 4909 + // binary transform function + string[] strings = [ "one", "two", "three" ]; + schwartzSort!((element, index) => size_t.max - index)(strings); + assert(strings == [ "three", "two", "one" ]); +} + +// https://issues.dlang.org/show_bug.cgi?id=4909 +@safe unittest +{ import std.typecons : Tuple; Tuple!(char)[] chars; schwartzSort!"a[0]"(chars); } +// https://issues.dlang.org/show_bug.cgi?id=5924 @safe unittest { - // issue 5924 import std.typecons : Tuple; Tuple!(char)[] chars; schwartzSort!((Tuple!(char) c){ return c[0]; })(chars); } +// https://issues.dlang.org/show_bug.cgi?id=13965 +@safe unittest +{ + import std.typecons : Tuple; + Tuple!(char)[] chars; + schwartzSort!("a[0]", SwapStrategy.stable)(chars); +} + +// https://issues.dlang.org/show_bug.cgi?id=13965 +@safe unittest +{ + import std.algorithm.iteration : map; + import std.numeric : entropy; + + auto lowEnt = [ 1.0, 0, 0 ], + midEnt = [ 0.1, 0.1, 0.8 ], + highEnt = [ 0.31, 0.29, 0.4 ]; + auto arr = new double[][3]; + arr[0] = midEnt; + arr[1] = lowEnt; + arr[2] = highEnt; + + schwartzSort!(entropy, SwapStrategy.stable)(arr); + + assert(arr[0] == lowEnt); + assert(arr[1] == midEnt); + assert(arr[2] == highEnt); + assert(isSorted!("a < b")(map!(entropy)(arr))); +} + +// https://issues.dlang.org/show_bug.cgi?id=20799 +@safe unittest +{ + import std.range : iota, retro; + import std.array : array; + + auto arr = 1_000_000.iota.retro.array; + arr.schwartzSort!( + n => new int(n), + (a, b) => *a < *b + ); + assert(arr.isSorted()); +} + +// https://issues.dlang.org/show_bug.cgi?id=21183 +@safe unittest +{ + static T get(T)(int) { return T.init; } + + // There's no need to actually sort, just checking type interference + if (false) + { + int[] arr; + + // Fine because there are no indirections + arr.schwartzSort!(get!(const int)); + + // Fine because it decays to immutable(int)* + arr.schwartzSort!(get!(immutable int*)); + + // Disallowed because it would require a non-const reference + static assert(!__traits(compiles, arr.schwartzSort!(get!(const Object)))); + + static struct Wrapper + { + int* ptr; + } + + // Disallowed because Wrapper.ptr would become mutable + static assert(!__traits(compiles, arr.schwartzSort!(get!(const Wrapper)))); + } +} + // 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 +Reorders the random-access range `r` such that the range `r[0 .. mid]` +is the same as if the entire `r` were sorted, and leaves +the range `r[mid .. r.length]` in no particular order. + +Performs $(BIGOH r.length * log(mid)) evaluations of `pred`. The +implementation simply calls `topN!(less, ss)(r, n)` and then $(D sort!(less, ss)(r[0 .. n])). Params: @@ -3077,17 +3387,20 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && // 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 +Reorders the range `r` using $(REF_ALTTEXT swap, swap, std,algorithm,mutation) +such that `r[nth]` refers to the element that would fall there if the range +were fully sorted. + +It is akin to $(LINK2 https://en.wikipedia.org/wiki/Quickselect, Quickselect), +and partitions `r` such that all elements +`e1` from `r[0]` to `r[nth]` satisfy `!less(r[nth], e1)`, +and all elements `e2` from `r[nth]` to `r[r.length]` satisfy +`!less(e2, r[nth])`. Effectively, it finds the `nth + 1` smallest +(according to `less`) elements in `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 stable) evaluations of `less` and $(REF_ALTTEXT swap, swap, std,algorithm,mutation). -If $(D n >= r.length), the algorithm has no effect and returns +If `n >= r.length`, the algorithm has no effect and returns `r[0 .. r.length]`. Params: @@ -3097,9 +3410,10 @@ Params: nth = The index of the element that should be in sorted position after the function is done. +Returns: a slice from `r[0]` to `r[nth]`, excluding `r[nth]` itself. + See_Also: $(LREF topNIndex), - $(HTTP sgi.com/tech/stl/nth_element.html, STL's nth_element) BUGS: @@ -3108,7 +3422,8 @@ Stable topN has not been implemented yet. auto topN(alias less = "a < b", SwapStrategy ss = SwapStrategy.unstable, Range)(Range r, size_t nth) -if (isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range) +if (isRandomAccessRange!(Range) && hasLength!Range && + hasSlicing!Range && hasAssignableElements!Range) { static assert(ss == SwapStrategy.unstable, "Stable topN not yet implemented"); @@ -3119,10 +3434,11 @@ if (isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range) // Workaround for https://issues.dlang.org/show_bug.cgi?id=16528 // Safety checks: enumerate all potentially unsafe generic primitives // then use a @trusted implementation. - binaryFun!less(r[0], r[r.length - 1]); + cast(void) binaryFun!less(r[0], r[r.length - 1]); import std.algorithm.mutation : swapAt; r.swapAt(size_t(0), size_t(0)); - static assert(is(typeof(r.length) == size_t)); + static assert(is(typeof(r.length) == size_t), + typeof(r.length).stringof ~ " must be of type size_t"); pivotPartition!less(r, 0); } bool useSampling = true; @@ -3130,6 +3446,28 @@ if (isRandomAccessRange!(Range) && hasLength!Range && hasSlicing!Range) return ret; } +/// +@safe unittest +{ + int[] v = [ 25, 7, 9, 2, 0, 5, 21 ]; + topN!"a < b"(v, 100); + assert(v == [ 25, 7, 9, 2, 0, 5, 21 ]); + auto n = 4; + topN!((a, b) => a < b)(v, n); + assert(v[n] == 9); +} + +// https://issues.dlang.org/show_bug.cgi?id=8341 +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : zip; + import std.typecons : tuple; + auto a = [10, 30, 20]; + auto b = ["c", "b", "a"]; + assert(topN!"a[0] > b[0]"(zip(a, b), 2).equal([tuple(20, "a"), tuple(30, "b")])); +} + private @trusted void topNImpl(alias less, R)(R r, size_t n, ref bool useSampling) { @@ -3212,7 +3550,7 @@ void topNImpl(alias less, R)(R r, size_t n, ref bool useSampling) } } - assert(pivot != size_t.max); + assert(pivot != size_t.max, "pivot must be not equal to size_t.max"); // See how the pivot fares if (pivot == n) { @@ -3230,20 +3568,11 @@ void topNImpl(alias less, R)(R r, size_t n, ref bool useSampling) } } -/// -@safe unittest -{ - int[] v = [ 25, 7, 9, 2, 0, 5, 21 ]; - topN!"a < b"(v, 100); - assert(v == [ 25, 7, 9, 2, 0, 5, 21 ]); - auto n = 4; - topN!"a < b"(v, n); - assert(v[n] == 9); -} - private size_t topNPartition(alias lp, R)(R r, size_t n, bool useSampling) { - assert(r.length >= 9 && n < r.length); + import std.format : format; + assert(r.length >= 9 && n < r.length, "length must be longer than 9" + ~ " and n must be less than r.length"); immutable ninth = r.length / 9; auto pivot = ninth / 2; // Position subrange r[lo .. hi] to have length equal to ninth and its upper @@ -3252,9 +3581,11 @@ private size_t topNPartition(alias lp, R)(R r, size_t n, bool useSampling) // the median in already sorted ranges. immutable lo = r.length / 2 - pivot, hi = lo + ninth; // We have either one straggler on the left, one on the right, or none. - assert(lo - (r.length - hi) <= 1 || (r.length - hi) - lo <= 1); - assert(lo >= ninth * 4); - assert(r.length - hi >= ninth * 4); + assert(lo - (r.length - hi) <= 1 || (r.length - hi) - lo <= 1, + format!"straggler check failed lo %s, r.length %s, hi %s"(lo, r.length, hi)); + assert(lo >= ninth * 4, format!"lo %s >= ninth * 4 %s"(lo, ninth * 4)); + assert(r.length - hi >= ninth * 4, + format!"r.length %s - hi %s >= ninth * 4 %s"(r.length, hi, ninth * 4)); // Partition in groups of 3, and the mid tertile again in groups of 3 if (!useSampling) @@ -3270,12 +3601,15 @@ private size_t topNPartition(alias lp, R)(R r, size_t n, bool useSampling) private void p3(alias less, Range)(Range r, size_t lo, immutable size_t hi) { - assert(lo <= hi && hi < r.length); + import std.format : format; + assert(lo <= hi && hi < r.length, + format!"lo %s <= hi %s && hi < r.length %s"(lo, hi, r.length)); immutable ln = hi - lo; for (; lo < hi; ++lo) { - assert(lo >= ln); - assert(lo + ln < r.length); + assert(lo >= ln, format!"lo %s >= ln %s"(lo, ln)); + assert(lo + ln < r.length, format!"lo %s + ln %s < r.length %s"( + lo, ln, r.length)); medianOf!less(r, lo - ln, lo, lo + ln); } } @@ -3283,12 +3617,15 @@ private void p3(alias less, Range)(Range r, size_t lo, immutable size_t hi) private void p4(alias less, Flag!"leanRight" f, Range) (Range r, size_t lo, immutable size_t hi) { - assert(lo <= hi && hi < r.length); + import std.format : format; + assert(lo <= hi && hi < r.length, format!"lo %s <= hi %s && hi < r.length %s"( + lo, hi, r.length)); immutable ln = hi - lo, _2ln = ln * 2; for (; lo < hi; ++lo) { - assert(lo >= ln); - assert(lo + ln < r.length); + assert(lo >= ln, format!"lo %s >= ln %s"(lo, ln)); + assert(lo + ln < r.length, format!"lo %s + ln %s < r.length %s"( + lo, ln, r.length)); static if (f == Yes.leanRight) medianOf!(less, f)(r, lo - _2ln, lo - ln, lo, lo + ln); else @@ -3299,8 +3636,8 @@ private void p4(alias less, Flag!"leanRight" f, Range) private size_t topNPartitionOffMedian(alias lp, Flag!"leanRight" f, R) (R r, size_t n, bool useSampling) { - assert(r.length >= 12); - assert(n < r.length); + assert(r.length >= 12, "The length of r must be greater than 11"); + assert(n < r.length, "n must be less than the length of r"); immutable _4 = r.length / 4; static if (f == Yes.leanRight) immutable leftLimit = 2 * _4; @@ -3337,19 +3674,23 @@ size_t expandPartition(alias lp, R)(R r, size_t lo, size_t pivot, size_t hi) in { import std.algorithm.searching : all; - assert(lo <= pivot); - assert(pivot < hi); - assert(hi <= r.length); - assert(r[lo .. pivot + 1].all!(x => !lp(r[pivot], x))); - assert(r[pivot + 1 .. hi].all!(x => !lp(x, r[pivot]))); + assert(lo <= pivot, "lo must be less than or equal pivot"); + assert(pivot < hi, "pivot must be less than hi"); + assert(hi <= r.length, "hi must be less than or equal to the length of r"); + assert(r[lo .. pivot + 1].all!(x => !lp(r[pivot], x)), + "r[lo .. pivot + 1] failed less than test"); + assert(r[pivot + 1 .. hi].all!(x => !lp(x, r[pivot])), + "r[pivot + 1 .. hi] failed less than test"); } out { import std.algorithm.searching : all; - assert(r[0 .. pivot + 1].all!(x => !lp(r[pivot], x))); - assert(r[pivot + 1 .. r.length].all!(x => !lp(x, r[pivot]))); + assert(r[0 .. pivot + 1].all!(x => !lp(r[pivot], x)), + "r[0 .. pivot + 1] failed less than test"); + assert(r[pivot + 1 .. r.length].all!(x => !lp(x, r[pivot])), + "r[pivot + 1 .. r.length] failed less than test"); } -body +do { import std.algorithm.mutation : swapAt; import std.algorithm.searching : all; @@ -3372,10 +3713,14 @@ body r.swapAt(left, rite); } - assert(r[lo .. pivot + 1].all!(x => !lp(r[pivot], x))); - assert(r[pivot + 1 .. hi + 1].all!(x => !lp(x, r[pivot]))); - assert(r[0 .. left].all!(x => !lp(r[pivot], x))); - assert(r[rite + 1 .. r.length].all!(x => !lp(x, r[pivot]))); + assert(r[lo .. pivot + 1].all!(x => !lp(r[pivot], x)), + "r[lo .. pivot + 1] failed less than test"); + assert(r[pivot + 1 .. hi + 1].all!(x => !lp(x, r[pivot])), + "r[pivot + 1 .. hi + 1] failed less than test"); + assert(r[0 .. left].all!(x => !lp(r[pivot], x)), + "r[0 .. left] failed less than test"); + assert(r[rite + 1 .. r.length].all!(x => !lp(x, r[pivot])), + "r[rite + 1 .. r.length] failed less than test"); immutable oldPivot = pivot; @@ -3387,7 +3732,7 @@ body if (left == lo) goto done; if (!lp(r[oldPivot], r[left])) continue; --pivot; - assert(!lp(r[oldPivot], r[pivot])); + assert(!lp(r[oldPivot], r[pivot]), "less check failed"); r.swapAt(left, pivot); } // Second loop: make left and pivot meet @@ -3414,7 +3759,7 @@ body if (rite == hi) goto done; if (!lp(r[rite], r[oldPivot])) continue; ++pivot; - assert(!lp(r[pivot], r[oldPivot])); + assert(!lp(r[pivot], r[oldPivot]), "less check failed"); r.swapAt(rite, pivot); } // Second loop: make left and pivot meet @@ -3441,23 +3786,18 @@ done: { auto a = [ 10, 5, 3, 4, 8, 11, 13, 3, 9, 4, 10 ]; assert(expandPartition!((a, b) => a < b)(a, 4, 5, 6) == 9); - a = randomArray; + + import std.algorithm.iteration : map; + import std.array : array; + import std.random : uniform; + import std.range : iota; + auto size = uniform(1, 1000); + a = iota(0, size).map!(_ => uniform(0, 1000)).array; if (a.length == 0) return; expandPartition!((a, b) => a < b)(a, a.length / 2, a.length / 2, a.length / 2 + 1); } -version (unittest) -private T[] randomArray(Flag!"exactSize" flag = No.exactSize, T = int)( - size_t maxSize = 1000, - T minValue = 0, T maxValue = 255) -{ - import std.algorithm.iteration : map; - import std.random : unpredictableSeed, Random, uniform; - auto size = flag == Yes.exactSize ? maxSize : uniform(1, maxSize); - return iota(0, size).map!(_ => uniform(minValue, maxValue)).array; -} - @safe unittest { import std.algorithm.comparison : max, min; @@ -3509,9 +3849,9 @@ private T[] randomArray(Flag!"exactSize" flag = No.exactSize, T = int)( { import std.algorithm.comparison : max, min; import std.algorithm.iteration : reduce; - import std.random : Random, uniform, unpredictableSeed; + import std.random : Random = Xorshift, uniform; - immutable uint[] seeds = [90027751, 2709791795, 1374631933, 995751648, 3541495258, 984840953, unpredictableSeed]; + immutable uint[] seeds = [90027751, 2709791795, 1374631933, 995751648, 3541495258, 984840953]; foreach (s; seeds) { auto r = Random(s); @@ -3534,7 +3874,7 @@ private T[] randomArray(Flag!"exactSize" flag = No.exactSize, T = int)( } } -// bug 12987 +// https://issues.dlang.org/show_bug.cgi?id=12987 @safe unittest { int[] a = [ 25, 7, 9, 2, 0, 5, 21 ]; @@ -3584,7 +3924,7 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && assert(a == [0, 1, 2, 2, 3]); } -// bug 15421 +// https://issues.dlang.org/show_bug.cgi?id=15421 @system unittest { import std.algorithm.comparison : equal; @@ -3628,7 +3968,7 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && } } -// bug 15421 +// https://issues.dlang.org/show_bug.cgi?id=15421 @system unittest { auto a = [ 9, 8, 0, 3, 5, 25, 43, 4, 2, 0, 7 ]; @@ -3647,7 +3987,7 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && assert(a == b); } -// bug 12987 +// https://issues.dlang.org/show_bug.cgi?id=12987 @system unittest { int[] a = [ 5, 7, 2, 6, 7 ]; @@ -3657,7 +3997,7 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && assert(t == [ 0, 1, 2, 2, 3 ]); } -// bug 15420 +// https://issues.dlang.org/show_bug.cgi?id=15420 @system unittest { int[] a = [ 5, 7, 2, 6, 7 ]; @@ -3668,11 +4008,12 @@ if (isRandomAccessRange!(Range1) && hasLength!Range1 && } /** -Copies the top $(D n) elements of the -$(REF_ALTTEXT input range, isInputRange, std,range,primitives) $(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 +Copies the top `n` elements of the +$(REF_ALTTEXT input range, isInputRange, std,range,primitives) `source` into the +random-access range `target`, where `n = target.length`. + +Elements of `source` are not touched. If $(D +sorted) is `true`, the target is sorted. Otherwise, the target respects the $(HTTP en.wikipedia.org/wiki/Binary_heap, heap property). Params: @@ -3714,10 +4055,10 @@ if (isInputRange!(SRange) && isRandomAccessRange!(TRange) @system unittest { - import std.random : Random, unpredictableSeed, uniform, randomShuffle; + import std.random : Random = Xorshift, uniform, randomShuffle; import std.typecons : Yes; - auto r = Random(unpredictableSeed); + auto r = Random(123_456_789); ptrdiff_t[] a = new ptrdiff_t[uniform(1, 1000, r)]; foreach (i, ref e; a) e = i; randomShuffle(a, r); @@ -3735,7 +4076,7 @@ Similar to $(LREF topN), except that the range is not modified. Params: less = A binary predicate that defines the ordering of range elements. - Defaults to $(D a < b). + Defaults to `a < b`. ss = $(RED (Not implemented yet.)) Specify the swapping strategy. r = A $(REF_ALTTEXT random-access range, isRandomAccessRange, std,range,primitives) @@ -3743,13 +4084,13 @@ Params: index = A $(REF_ALTTEXT random-access range, isRandomAccessRange, std,range,primitives) with assignable elements to build the index in. The length of this range - determines how many top elements to index in $(D r). + determines how many top elements to index in `r`. This index range can either have integral elements, in which case the constructed index will consist of zero-based numerical indices into - $(D r); or it can have pointers to the element type of $(D r), in which + `r`; or it can have pointers to the element type of `r`, in which case the constructed index will be pointers to the top elements in - $(D r). + `r`. sorted = Determines whether to sort the index by the elements they refer to. @@ -3852,7 +4193,7 @@ Private for the time being. Computes the median of 2 to 5 arbitrary indexes in random-access range `r` using hand-written specialized algorithms. The indexes must be distinct (if not, behavior is implementation-defined). The function also partitions the elements -involved around the median, e.g. $(D medianOf(r, a, b, c)) not only fills `r[b]` +involved around the median, e.g. `medianOf(r, a, b, c)` not only fills `r[b]` with the median of `r[a]`, `r[b]`, and `r[c]`, but also puts the minimum in `r[a]` and the maximum in `r[c]`. @@ -3861,9 +4202,9 @@ less = The comparison predicate used, modeled as a $(LINK2 https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings, strict weak ordering) (irreflexive, antisymmetric, transitive, and implying a transitive equivalence). flag = Used only for even values of `T.length`. If `No.leanRight`, the median -"leans left", meaning $(D medianOf(r, a, b, c, d)) puts the lower median of the +"leans left", meaning `medianOf(r, a, b, c, d)` puts the lower median of the four in `r[b]`, the minimum in `r[a]`, and the two others in `r[c]` and `r[d]`. -Conversely, $(D median!("a < b", Yes.leanRight)(r, a, b, c, d)) puts the upper +Conversely, `median!("a < b", Yes.leanRight)(r, a, b, c, d)` puts the upper median of the four in `r[c]`, the maximum in `r[d]`, and the two others in `r[a]` and `r[b]`. r = The range containing the indexes. @@ -3879,37 +4220,45 @@ if (isRandomAccessRange!Range && hasLength!Range && Indexes.length >= 2 && Indexes.length <= 5 && allSatisfy!(isUnsigned, Indexes)) { - assert(r.length >= Indexes.length); + assert(r.length >= Indexes.length, "r.length must be greater equal to" + ~ " Indexes.length"); import std.functional : binaryFun; alias lt = binaryFun!less; enum k = Indexes.length; import std.algorithm.mutation : swapAt; + import std.format : format; alias a = i[0]; - static assert(is(typeof(a) == size_t)); + static assert(is(typeof(a) == size_t), typeof(a).stringof ~ " must be" + ~ " of type size_t"); static if (k >= 2) { alias b = i[1]; - static assert(is(typeof(b) == size_t)); - assert(a != b); + static assert(is(typeof(b) == size_t), typeof(b).stringof ~ " must be" + ~ " of type size_t"); + assert(a != b, "a != b "); } static if (k >= 3) { alias c = i[2]; - static assert(is(typeof(c) == size_t)); - assert(a != c && b != c); + static assert(is(typeof(c) == size_t), typeof(c).stringof ~ " must be" + ~ " of type size_t"); + assert(a != c && b != c, "a != c && b != c"); } static if (k >= 4) { alias d = i[3]; - static assert(is(typeof(d) == size_t)); - assert(a != d && b != d && c != d); + static assert(is(typeof(d) == size_t), typeof(d).stringof ~ " must be" + ~ " of type size_t"); + assert(a != d && b != d && c != d, "a != d && b != d && c != d failed"); } static if (k >= 5) { alias e = i[4]; - static assert(is(typeof(e) == size_t)); - assert(a != e && b != e && c != e && d != e); + static assert(is(typeof(e) == size_t), typeof(e).stringof ~ " must be" + ~ " of type size_t"); + assert(a != e && b != e && c != e && d != e, + "a != e && b != e && c != e && d != e failed"); } static if (k == 2) @@ -3942,8 +4291,8 @@ if (isRandomAccessRange!Range && hasLength!Range && if (lt(r[c], r[b])) r.swapAt(b, c); } } - assert(!lt(r[b], r[a])); - assert(!lt(r[c], r[b])); + assert(!lt(r[b], r[a]), "less than check failed"); + assert(!lt(r[c], r[b]), "less than check failed"); } else static if (k == 4) { @@ -3965,12 +4314,12 @@ if (isRandomAccessRange!Range && hasLength!Range && else static if (k == 5) { // Credit: Teppo Niinimäki - version (unittest) scope(success) + version (StdUnittest) scope(success) { - assert(!lt(r[c], r[a])); - assert(!lt(r[c], r[b])); - assert(!lt(r[d], r[c])); - assert(!lt(r[e], r[c])); + assert(!lt(r[c], r[a]), "less than check failed"); + assert(!lt(r[c], r[b]), "less than check failed"); + assert(!lt(r[d], r[c]), "less than check failed"); + assert(!lt(r[e], r[c]), "less than check failed"); } if (lt(r[c], r[a])) r.swapAt(a, c); @@ -4025,16 +4374,16 @@ if (isRandomAccessRange!Range && hasLength!Range && // nextPermutation /** - * Permutes $(D range) in-place to the next lexicographically greater + * Permutes `range` in-place to the next lexicographically greater * permutation. * - * The predicate $(D less) defines the lexicographical ordering to be used on + * The predicate `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 + * sorting it according to `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 @@ -4094,7 +4443,7 @@ if (isBidirectionalRange!BidirectionalRange && auto j = find!((a) => binaryFun!less(i.front, a))( takeExactly(retro(range), n)); - assert(!j.empty); // shouldn't happen since i.front < last.front + assert(!j.empty, "j must not be empty"); // shouldn't happen since i.front < last.front swap(i.front, j.front); reverse(takeExactly(retro(range), n)); @@ -4242,7 +4591,7 @@ if (isBidirectionalRange!BidirectionalRange && assert(a == [3,2,1]); } -// Issue 13594 +// https://issues.dlang.org/show_bug.cgi?id=13594 @safe unittest { int[3] a = [1,2,3]; @@ -4252,10 +4601,10 @@ if (isBidirectionalRange!BidirectionalRange && // nextEvenPermutation /** - * Permutes $(D range) in-place to the next lexicographically greater $(I even) + * Permutes `range` in-place to the next lexicographically greater $(I even) * permutation. * - * The predicate $(D less) defines the lexicographical ordering to be used on + * The predicate `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 @@ -4351,7 +4700,7 @@ if (isBidirectionalRange!BidirectionalRange && takeExactly(retro(range), n)); // shouldn't happen since i.front < last.front - assert(!j.empty); + assert(!j.empty, "j must not be empty"); swap(i.front, j.front); oddParity = !oddParity; @@ -4417,9 +4766,9 @@ if (isBidirectionalRange!BidirectionalRange && assert(b == [ 1, 3, 2 ]); } +// https://issues.dlang.org/show_bug.cgi?id=13594 @safe unittest { - // Issue 13594 int[3] a = [1,2,3]; assert(nextEvenPermutation(a[])); assert(a == [2,3,1]); @@ -4431,7 +4780,7 @@ shapes. Here's a non-trivial example: */ @safe unittest { - import std.math : sqrt; + import std.math.algebraic : sqrt; // Print the 60 vertices of a uniform truncated icosahedron (soccer ball) enum real Phi = (1.0 + sqrt(5.0)) / 2.0; // Golden ratio @@ -4466,3 +4815,177 @@ shapes. Here's a non-trivial example: } assert(n == 60); } + +/** +Permutes `range` into the `perm` permutation. + +The algorithm has a constant runtime complexity with respect to the number of +permutations created. +Due to the number of unique values of `ulong` only the first 21 elements of +`range` can be permuted. The rest of the range will therefore not be +permuted. +This algorithm uses the $(HTTP en.wikipedia.org/wiki/Lehmer_code, Lehmer +Code). + +The algorithm works as follows: +$(D_CODE + auto pem = [4,0,4,1,0,0,0]; // permutation 2982 in factorial + auto src = [0,1,2,3,4,5,6]; // the range to permutate + + auto i = 0; // range index + // range index iterates pem and src in sync + // pem[i] + i is used as index into src + // first src[pem[i] + i] is stored in t + auto t = 4; // tmp value + src = [0,1,2,3,n,5,6]; + + // then the values between i and pem[i] + i are moved one + // to the right + src = [n,0,1,2,3,5,6]; + // at last t is inserted into position i + src = [4,0,1,2,3,5,6]; + // finally i is incremented + ++i; + + // this process is repeated while i < pem.length + + t = 0; + src = [4,n,1,2,3,5,6]; + src = [4,0,1,2,3,5,6]; + ++i; + t = 6; + src = [4,0,1,2,3,5,n]; + src = [4,0,n,1,2,3,5]; + src = [4,0,6,1,2,3,5]; +) + +Returns: + The permuted range. + +Params: + range = The Range to permute. The original ordering will be lost. + perm = The permutation to permutate `range` to. +*/ +auto ref Range nthPermutation(Range) + (auto ref Range range, const ulong perm) +if (isRandomAccessRange!Range && hasLength!Range) +{ + if (!nthPermutationImpl(range, perm)) + { + throw new Exception( + "The range to permutate must not have less" + ~ " elements than the factorial number has digits"); + } + + return range; +} + +/// +pure @safe unittest +{ + auto src = [0, 1, 2, 3, 4, 5, 6]; + auto rslt = [4, 0, 6, 2, 1, 3, 5]; + + src = nthPermutation(src, 2982); + assert(src == rslt); +} + +/** +Returns: `true` in case the permutation worked, `false` in case `perm` had + more digits in the factorial number system than range had elements. + This case must not occur as this would lead to out of range accesses. +*/ +bool nthPermutationImpl(Range) + (auto ref Range range, ulong perm) +if (isRandomAccessRange!Range && hasLength!Range) +{ + import std.range.primitives : ElementType; + import std.numeric : decimalToFactorial; + + // ulong.max has 21 digits in the factorial number system + ubyte[21] fac; + size_t idx = decimalToFactorial(perm, fac); + + if (idx > range.length) + { + return false; + } + + ElementType!Range tmp; + size_t i = 0; + + for (; i < idx; ++i) + { + size_t re = fac[i]; + tmp = range[re + i]; + for (size_t j = re + i; j > i; --j) + { + range[j] = range[j - 1]; + } + range[i] = tmp; + } + + return true; +} + +/// +pure @safe unittest +{ + auto src = [0, 1, 2, 3, 4, 5, 6]; + auto rslt = [4, 0, 6, 2, 1, 3, 5]; + + bool worked = nthPermutationImpl(src, 2982); + assert(worked); + assert(src == rslt); +} + +pure @safe unittest +{ + auto rslt = [4, 0, 6, 2, 1, 3, 5]; + + auto src = nthPermutation([0, 1, 2, 3, 4, 5, 6], 2982); + assert(src == rslt); +} + +pure @safe unittest +{ + auto src = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto rslt = [4, 0, 6, 2, 1, 3, 5, 7, 8, 9, 10]; + + src = nthPermutation(src, 2982); + assert(src == rslt); +} + +pure @safe unittest +{ + import std.exception : assertThrown; + + auto src = [0, 1, 2, 3]; + + assertThrown(nthPermutation(src, 2982)); +} + +pure @safe unittest +{ + import std.internal.test.dummyrange; + import std.meta : AliasSeq; + + auto src = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + auto rsl = [4, 0, 6, 2, 1, 3, 5, 7, 8, 9, 10]; + + foreach (T; AliasSeq!( + DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, int[]), + DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, int[]))) + { + static assert(isRandomAccessRange!(T)); + static assert(hasLength!(T)); + auto dr = T(src.dup); + dr = nthPermutation(dr, 2982); + + int idx; + foreach (it; dr) + { + assert(it == rsl[idx++]); + } + } +} |