From f1bafe7996fcecad448bf865a7801d96135fc9df Mon Sep 17 00:00:00 2001 From: Andy Ferris Date: Thu, 21 Jun 2018 21:34:17 +1000 Subject: [PATCH] Move `v0` to an `init` keyword argument for `reduce`, etc The initial value `v0` has been moved to a keyword argument `init` in `reduce`, `mapreduce`, `foldl`, `mapfoldl`, `foldr` and `mapfoldr`. (This will allow the future addition of multiple input iterators like we allow for `map`). --- NEWS.md | 4 + base/Enums.jl | 2 +- base/abstractset.jl | 10 +- base/array.jl | 2 +- base/bitset.jl | 6 +- base/compiler/ssair/passes.jl | 4 +- base/compiler/tfuncs.jl | 6 +- base/compiler/typelimits.jl | 2 +- base/compiler/typeutils.jl | 2 +- base/deprecated.jl | 8 ++ base/gmp.jl | 2 +- base/intfuncs.jl | 2 +- base/reduce.jl | 180 ++++++++++++++-------------------- base/reducedim.jl | 42 ++++---- base/strings/unicode.jl | 2 +- doc/src/base/collections.md | 6 -- test/reduce.jl | 24 ++--- test/reducedim.jl | 8 +- 18 files changed, 136 insertions(+), 176 deletions(-) diff --git a/NEWS.md b/NEWS.md index ec04c0ab218edf..d573c07345f0b2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -710,6 +710,10 @@ Library improvements * Added an optimized method of `vecdot` for taking the Frobenius inner product of sparse matrices. ([#27470]) + * The initial element `v0` in `reduce(op, v0, itr)` has been replaced with an `init` + optional keyword argument, as in `reduce(op, itr; init=v0)`. Similarly for `foldl`, + `foldr`, `mapreduce`, `mapfoldl` and `mapfoldr`. ([#27711]) + Compiler/Runtime improvements ----------------------------- diff --git a/base/Enums.jl b/base/Enums.jl index a6cc1db4772e49..7f406f066e7768 100644 --- a/base/Enums.jl +++ b/base/Enums.jl @@ -20,7 +20,7 @@ function membershiptest(expr, values) if length(values) == hi - lo + 1 :($lo <= $expr <= $hi) elseif length(values) < 20 - foldl((x1,x2)->:($x1 || ($expr == $x2)), :($expr == $(values[1])), values[2:end]) + foldl((x1,x2)->:($x1 || ($expr == $x2)), values[2:end]; init = :($expr == $(values[1]))) else :($expr in $(Set(values))) end diff --git a/base/abstractset.jl b/base/abstractset.jl index 49182e805b46b9..cf231ad3fccf18 100644 --- a/base/abstractset.jl +++ b/base/abstractset.jl @@ -59,7 +59,7 @@ julia> a Set([7, 4, 3, 5, 1]) ``` """ -union!(s::AbstractSet, sets...) = foldl(union!, s, sets) +union!(s::AbstractSet, sets...) = foldl(union!, sets; init = s) max_values(::Type) = typemax(Int) max_values(T::Type{<:Union{Nothing,BitIntegerSmall}}) = 1 << (8*sizeof(T)) @@ -109,7 +109,7 @@ const ∩ = intersect Intersect all passed in sets and overwrite `s` with the result. Maintain order with arrays. """ -intersect!(s::AbstractSet, itrs...) = foldl(intersect!, s, itrs) +intersect!(s::AbstractSet, itrs...) = foldl(intersect!, itrs; init = s) intersect!(s::AbstractSet, s2::AbstractSet) = filter!(_in(s2), s) intersect!(s::AbstractSet, itr) = intersect!(s, union!(emptymutable(s, eltype(itr)), itr)) @@ -147,8 +147,8 @@ julia> a Set([4]) ``` """ -setdiff!(s::AbstractSet, itrs...) = foldl(setdiff!, s, itrs) -setdiff!(s::AbstractSet, itr) = foldl(delete!, s, itr) +setdiff!(s::AbstractSet, itrs...) = foldl(setdiff!, itrs; init = s) +setdiff!(s::AbstractSet, itr) = foldl(delete!, itr; init = s) """ @@ -185,7 +185,7 @@ Construct the symmetric difference of the passed in sets, and overwrite `s` with When `s` is an array, the order is maintained. Note that in this case the multiplicity of elements matters. """ -symdiff!(s::AbstractSet, itrs...) = foldl(symdiff!, s, itrs) +symdiff!(s::AbstractSet, itrs...) = foldl(symdiff!, itrs; init = s) function symdiff!(s::AbstractSet, itr) for x in itr diff --git a/base/array.jl b/base/array.jl index 218f765fac9295..be1adb8e79d006 100644 --- a/base/array.jl +++ b/base/array.jl @@ -2366,7 +2366,7 @@ _shrink_filter!(keep) = _unique_filter!(∈, pop!, keep) function _grow!(pred!, v::AbstractVector, itrs) filter!(pred!, v) # uniquify v - foldl(v, itrs) do v, itr + foldl(itrs; init = v) do v, itr mapfilter(pred!, push!, itr, v) end end diff --git a/base/bitset.jl b/base/bitset.jl index 25850add41b641..84230fd2fa9204 100644 --- a/base/bitset.jl +++ b/base/bitset.jl @@ -29,7 +29,7 @@ very large integers), use [`Set`](@ref) instead. BitSet(itr) = union!(BitSet(), itr) # Special implementation for BitSet, which lacks a fast `length` method. -union!(s::BitSet, itr) = foldl(push!, s, itr) +union!(s::BitSet, itr) = foldl(push!, itr; init = s) @inline intoffset(s::BitSet) = s.offset << 6 @@ -274,7 +274,7 @@ intersect!(s1::BitSet, s2::BitSet) = _matched_map!(&, s1, s2) setdiff!(s1::BitSet, s2::BitSet) = _matched_map!((p, q) -> p & ~q, s1, s2) -symdiff!(s::BitSet, ns) = foldl(int_symdiff!, s, ns) +symdiff!(s::BitSet, ns) = foldl(int_symdiff!, ns; init = s) function int_symdiff!(s::BitSet, n::Integer) n0 = _check_bitset_bounds(n) @@ -309,7 +309,7 @@ function last(s::BitSet) idx == -1 ? _throw_bitset_notempty_error() : idx + intoffset(s) end -length(s::BitSet) = bitcount(s.bits) # = mapreduce(count_ones, +, 0, s.bits) +length(s::BitSet) = bitcount(s.bits) # = mapreduce(count_ones, +, s.bits; init = 0) function show(io::IO, s::BitSet) print(io, "BitSet([") diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index b960715441b014..425e0f4bcd6d3e 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -71,7 +71,7 @@ end function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks, du, phinodes, fidx, curblock) curblock = find_curblock(domtree, allblocks, curblock) - def = reduce(max, 0, stmt for stmt in du.defs if block_for_inst(ir.cfg, stmt) == curblock) + def = reduce(max, stmt for stmt in du.defs if block_for_inst(ir.cfg, stmt) == curblock; init = 0) def == 0 ? phinodes[curblock] : val_for_def_expr(ir, def, fidx) end @@ -680,7 +680,7 @@ function getfield_elim_pass!(ir::IRCode, domtree) # not to include any intermediaries that have dead uses. As a result, missing uses will only ever # show up in the nuses_total count. nleaves = length(defuse.uses) + length(defuse.defs) + length(defuse.ccall_preserve_uses) - nuses_total = compact.used_ssas[idx] + mapreduce(idx->compact.used_ssas[idx], +, 0, intermediaries) - length(intermediaries) + nuses_total = compact.used_ssas[idx] + mapreduce(idx->compact.used_ssas[idx], +, intermediaries; init = 0) - length(intermediaries) nleaves == nuses_total || continue # Find the type for this allocation defexpr = ir[SSAValue(idx)] diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 9fc15e731238d1..0d498dc371e396 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -616,7 +616,7 @@ function getfield_tfunc(@nospecialize(s00), @nospecialize(name)) return rewrap_unionall(unwrapva(s.types[1]), s00) end # union together types of all fields - return reduce(tmerge, Bottom, map(t -> rewrap_unionall(unwrapva(t), s00), s.types)) + return reduce(tmerge, map(t -> rewrap_unionall(unwrapva(t), s00), s.types); init = Bottom) end fld = name.val if isa(fld,Symbol) @@ -688,8 +688,8 @@ function fieldtype_tfunc(@nospecialize(s0), @nospecialize(name)) if !(Int <: name || Symbol <: name) return Bottom end - return reduce(tmerge, Bottom, - Any[ fieldtype_tfunc(s0, Const(i)) for i = 1:length(ftypes) ]) + return reduce(tmerge, Any[ fieldtype_tfunc(s0, Const(i)) for i = 1:length(ftypes) ]; + init = Bottom) end fld = name.val diff --git a/base/compiler/typelimits.jl b/base/compiler/typelimits.jl index 6ca2f0dddf9e64..99c15c1115a5a2 100644 --- a/base/compiler/typelimits.jl +++ b/base/compiler/typelimits.jl @@ -20,7 +20,7 @@ function limit_tuple_type_n(@nospecialize(t), lim::Int) p = t.parameters n = length(p) if n > lim - tail = reduce(typejoin, Bottom, Any[p[lim:(n-1)]..., unwrapva(p[n])]) + tail = reduce(typejoin, Any[p[lim:(n-1)]..., unwrapva(p[n])]; init = Bottom) return Tuple{p[1:(lim-1)]..., Vararg{tail}} end return t diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index 9e93b22125e4d2..950a426f525f53 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -85,7 +85,7 @@ _typename(a::DataType) = Const(a.name) function tuple_tail_elem(@nospecialize(init), ct) # FIXME: this is broken: it violates subtyping relations and creates invalid types with free typevars tmerge_maybe_vararg(@nospecialize(a), @nospecialize(b)) = tmerge(a, tvar_extent(unwrapva(b))) - return Vararg{widenconst(foldl(tmerge_maybe_vararg, init, ct))} + return Vararg{widenconst(foldl(tmerge_maybe_vararg, ct; init = init))} end function countunionsplit(atypes) diff --git a/base/deprecated.jl b/base/deprecated.jl index 8c93747d6090da..b0a1ef7dfed120 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1723,6 +1723,14 @@ end @deprecate_moved eigs "Arpack" @deprecate_moved svds "Arpack" +# PR #27711 +@deprecate reduce(op, v0, itr) reduce(op, itr; init=v0) +@deprecate foldl(op, v0, itr) foldl(op, itr; init=v0) +@deprecate foldr(op, v0, itr) foldr(op, itr; init=v0) +@deprecate mapreduce(f, op, v0, itr) mapreduce(f, op, itr; init=v0) +@deprecate mapfoldl(f, op, v0, itr) mapfoldl(f, op, itr; init=v0) +@deprecate mapfoldr(f, op, v0, itr) mapfoldr(f, op, itr; init=v0) + # END 0.7 deprecations # BEGIN 1.0 deprecations diff --git a/base/gmp.jl b/base/gmp.jl index be9b729cd8f4c0..4618765973c39c 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -553,7 +553,7 @@ function gcdx(a::BigInt, b::BigInt) g, s, t end -sum(arr::AbstractArray{BigInt}) = foldl(MPZ.add!, BigInt(0), arr) +sum(arr::AbstractArray{BigInt}) = foldl(MPZ.add!, arr; init = BigInt(0)) # note: a similar implementation for `prod` won't be efficient: # 1) the time complexity of the allocations is negligible compared to the multiplications # 2) assuming arr contains similarly sized BigInts, the multiplications are much more diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 51281eaf834e37..e7af67c856e5bf 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -79,7 +79,7 @@ lcm(a::Integer, b::Integer) = lcm(promote(a,b)...) gcd(a::Integer, b::Integer...) = gcd(a, gcd(b...)) lcm(a::Integer, b::Integer...) = lcm(a, lcm(b...)) -lcm(abc::AbstractArray{<:Integer}) = reduce(lcm,one(eltype(abc)),abc) +lcm(abc::AbstractArray{<:Integer}) = reduce(lcm, abc; init = one(eltype(abc))) function gcd(abc::AbstractArray{<:Integer}) a = zero(eltype(abc)) diff --git a/base/reduce.jl b/base/reduce.jl index 989e34c5d95b05..0e81956b02f587 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -4,6 +4,14 @@ ###### Generic (map)reduce functions ###### +""" + Base.NoInit() + +Singleton used for the purpose of indicating to the [`reduce`](@ref) family of functions +that no initial value has been specified for the reduction. +""" +struct NoInit; end + if Int === Int32 const SmallSigned = Union{Int8,Int16} const SmallUnsigned = Union{UInt8,UInt16} @@ -34,12 +42,12 @@ mul_prod(x::SmallUnsigned,y::SmallUnsigned) = UInt(x) * UInt(y) ## foldl && mapfoldl -@noinline function mapfoldl_impl(f, op, v0, itr, i...) - # Unroll the while loop once; if v0 is known, the call to op may +@noinline function mapfoldl_impl(f, op, init, itr, i...) + # Unroll the while loop once; if init is known, the call to op may # be evaluated at compile time y = iterate(itr, i...) - y === nothing && return v0 - v = op(v0, f(y[1])) + y === nothing && return init + v = op(init, f(y[1])) while true y = iterate(itr, y[2]) y === nothing && break @@ -48,70 +56,54 @@ mul_prod(x::SmallUnsigned,y::SmallUnsigned) = UInt(x) * UInt(y) return v end -""" - mapfoldl(f, op, v0, itr) - -Like [`mapreduce`](@ref), but with guaranteed left associativity, as in [`foldl`](@ref). -`v0` will be used exactly once. -""" -mapfoldl(f, op, v0, itr) = mapfoldl_impl(f, op, v0, itr) - -""" - mapfoldl(f, op, itr) - -Like `mapfoldl(f, op, v0, itr)`, but using the first element of `itr` to generate `v0`. -Specifically, `mapfoldl(f, op, itr)` produces the same result as -`mapfoldl(f, op, f(first(itr)), drop(itr, 1))`. -In general, this cannot be used with empty collections (see [`reduce(op, itr)`](@ref)). -""" -function mapfoldl(f, op, itr) +function mapfoldl_impl(f, op, ::NoInit, itr) y = iterate(itr) if y === nothing return Base.mapreduce_empty_iter(f, op, itr, IteratorEltype(itr)) end (x, i) = y - v0 = mapreduce_first(f, op, x) - mapfoldl_impl(f, op, v0, itr, i) + init = mapreduce_first(f, op, x) + mapfoldl_impl(f, op, init, itr, i) end -""" - foldl(op, v0, itr) -Like [`reduce`](@ref), but with guaranteed left associativity. `v0` will be used -exactly once. +""" + mapfoldl(f, op, itr; init=Base.NoInit()) -# Examples -```jldoctest -julia> foldl(=>, 0, 1:4) -(((0=>1)=>2)=>3) => 4 -``` +Like [`mapreduce`](@ref), but with guaranteed left associativity, as in [`foldl`](@ref). +If provided, `init` will be used exactly once. In general, it will be necessary to provide +`init` to work with empty collections. """ -foldl(op, v0, itr) = mapfoldl(identity, op, v0, itr) +mapfoldl(f, op, itr; init=NoInit()) = mapfoldl_impl(f, op, init, itr) """ - foldl(op, itr) + foldl(op, itr; init=Base.NoInit()) -Like `foldl(op, v0, itr)`, but using the first element of `itr` as `v0`. In general, this -cannot be used with empty collections (see [`reduce(op, itr)`](@ref)). +Like [`reduce`](@ref), but with guaranteed left associativity. If provided, `init` will be +used exactly once. In general, it will be necessary to provide `init` to work with empty +collections. # Examples ```jldoctest julia> foldl(=>, 1:4) ((1=>2)=>3) => 4 + +julia> foldl(=>, 1:4; init=0) +(((0=>1)=>2)=>3) => 4 ``` """ -foldl(op, itr) = mapfoldl(identity, op, itr) +foldl(op, itr; init=NoInit()) = mapfoldl(identity, op, itr; init=init) ## foldr & mapfoldr -function mapfoldr_impl(f, op, v0, itr, i::Integer) - # Unroll the while loop once; if v0 is known, the call to op may +function mapfoldr_impl(f, op, init, itr, i::Integer) + # Unroll the while loop once; if init is known, the call to op may # be evaluated at compile time if isempty(itr) || i == 0 - return v0 + return init else x = itr[i] - v = op(f(x), v0) + v = op(f(x), init) while i > 1 x = itr[i -= 1] v = op(f(x), v) @@ -120,24 +112,7 @@ function mapfoldr_impl(f, op, v0, itr, i::Integer) end end -""" - mapfoldr(f, op, v0, itr) - -Like [`mapreduce`](@ref), but with guaranteed right associativity, as in [`foldr`](@ref). -`v0` will be used exactly once. -""" -mapfoldr(f, op, v0, itr) = mapfoldr_impl(f, op, v0, itr, lastindex(itr)) - -""" - mapfoldr(f, op, itr) - -Like `mapfoldr(f, op, v0, itr)`, but using the first element of `itr` to generate `v0`. -Specifically, `mapfoldr(f, op, itr)` produces the same result as -`mapfoldr(f, op, f(last(itr)), take(itr, length(itr)-1))`. -In general, this cannot be used with empty collections (see [`reduce(op, itr)`](@ref)). -""" -function mapfoldr(f, op, itr) - i = lastindex(itr) +function mapfoldr_impl(f, op, ::NoInit, itr, i::Integer) if isempty(itr) return Base.mapreduce_empty_iter(f, op, itr, IteratorEltype(itr)) end @@ -145,32 +120,32 @@ function mapfoldr(f, op, itr) end """ - foldr(op, v0, itr) + mapfoldr(f, op, itr; init=Base.NoInit()) -Like [`reduce`](@ref), but with guaranteed right associativity. `v0` will be used -exactly once. - -# Examples -```jldoctest -julia> foldr(=>, 0, 1:4) -1 => (2=>(3=>(4=>0))) -``` +Like [`mapreduce`](@ref), but with guaranteed right associativity, as in [`foldr`](@ref). If +provided, `init` will be used exactly once. In general, it will be necessary to provide +`init` to work with empty collections. """ -foldr(op, v0, itr) = mapfoldr(identity, op, v0, itr) +mapfoldr(f, op, itr; init=NoInit()) = mapfoldr_impl(f, op, init, itr, lastindex(itr)) + """ - foldr(op, itr) + foldr(op, itr; init=Base.NoInit()) -Like `foldr(op, v0, itr)`, but using the last element of `itr` as `v0`. In general, this -cannot be used with empty collections (see [`reduce(op, itr)`](@ref)). +Like [`reduce`](@ref), but with guaranteed right associativity. If provided, `init` will be +used exactly once. In general, it will be necessary to provide `init` to work with empty +collections. # Examples ```jldoctest julia> foldr(=>, 1:4) 1 => (2=>(3=>4)) + +julia> foldr(=>, 1:4; init=0) +1 => (2=>(3=>(4=>0))) ``` """ -foldr(op, itr) = mapfoldr(identity, op, itr) +foldr(op, itr; init=NoInit()) = mapfoldr(identity, op, itr; init=init) ## reduce & mapreduce @@ -208,23 +183,17 @@ mapreduce_impl(f, op, A::AbstractArray, ifirst::Integer, ilast::Integer) = mapreduce_impl(f, op, A, ifirst, ilast, pairwise_blocksize(f, op)) """ - mapreduce(f, op, itr) - -Like `mapreduce(f, op, v0, itr)`. In general, this cannot be used with empty collections -(see `reduce(op, itr)`). -""" -mapreduce(f, op, itr) = mapfoldl(f, op, itr) - -""" - mapreduce(f, op, v0, itr) + mapreduce(f, op, itr; init=Base.NoInit()) Apply function `f` to each element in `itr`, and then reduce the result using the binary -function `op`. `v0` must be a neutral element for `op` that will be returned for empty -collections. It is unspecified whether `v0` is used for non-empty collections. +function `op`. If provided, `init` must be a neutral element for `op` that will be returne +for empty collections. It is unspecified whether `init` is used for non-empty collections. +In general, it will be necessary to provide `init` to work with empty collections. -[`mapreduce`](@ref) is functionally equivalent to calling `reduce(op, v0, -map(f, itr))`, but will in general execute faster since no intermediate collection needs to -be created. See documentation for [`reduce`](@ref) and [`map`](@ref). +[`mapreduce`](@ref) is functionally equivalent to calling +`reduce(op, map(f, itr); init=init)`, but will in general execute faster since no +intermediate collection needs to be created. See documentation for [`reduce`](@ref) and +[`map`](@ref). # Examples ```jldoctest @@ -237,7 +206,7 @@ implementations may reuse the return value of `f` for elements that appear multi `itr`. Use [`mapfoldl`](@ref) or [`mapfoldr`](@ref) instead for guaranteed left or right associativity and invocation of `f` for every value. """ -mapreduce(f, op, v0, itr) = mapfoldl(f, op, v0, itr) +mapreduce(f, op, itr; init=NoInit()) = mapfoldl(f, op, itr; init=init) # Note: sum_seq usually uses four or more accumulators after partial # unrolling, so each accumulator gets at most 256 numbers @@ -361,11 +330,15 @@ mapreduce(f, op, a::Number) = mapreduce_first(f, op, a) _mapreduce(f, op, ::IndexCartesian, A::AbstractArray) = mapfoldl(f, op, A) """ - reduce(op, v0, itr) + reduce(op, itr; init=Base.NoInit()) + +Reduce the given collection `itr` with the given binary operator `op`. If provided, the +initial value `init` must be a neutral element for `op` that will be returned for empty +collections. It is unspecified whether `init` is used for non-empty collections. -Reduce the given collection `itr` with the given binary operator `op`. `v0` must be a -neutral element for `op` that will be returned for empty collections. It is unspecified -whether `v0` is used for non-empty collections. +For empty collections, providing `init` will be necessary, except for some special cases +(e.g. when `op` is one of `+`, `*`, `max`, `min`, `&`, `|`) when Julia can determine the +neutral element of `op`. Reductions for certain commonly-used operators may have special implementations, and should be used instead: `maximum(itr)`, `minimum(itr)`, `sum(itr)`, `prod(itr)`, @@ -380,29 +353,18 @@ Some operations accumulate error. Parallelism will be easier if the reduction ca executed in groups. Future versions of Julia might change the algorithm. Note that the elements are not reordered if you use an ordered collection. -# Examples -```jldoctest -julia> reduce(*, 1, [2; 3; 4]) -24 -``` -""" -reduce(op, v0, itr) = mapreduce(identity, op, v0, itr) - -""" - reduce(op, itr) - -Like `reduce(op, v0, itr)`. This cannot be used with empty collections, except for some -special cases (e.g. when `op` is one of `+`, `*`, `max`, `min`, `&`, `|`) when Julia can -determine the neutral element of `op`. - # Examples ```jldoctest julia> reduce(*, [2; 3; 4]) 24 + +julia> reduce(*, [2; 3; 4]; init=-1) +-24 ``` """ -reduce(op, itr) = mapreduce(identity, op, itr) -reduce(op, a::Number) = a +reduce(op, itr; init=NoInit()) = mapreduce(identity, op, itr; init=init) + +reduce(op, a::Number) = a # Do we want this? ###### Specific reduction functions ###### diff --git a/base/reducedim.jl b/base/reducedim.jl index c8d5e720b6f899..72339a9727ba2a 100644 --- a/base/reducedim.jl +++ b/base/reducedim.jl @@ -76,8 +76,8 @@ for (Op, initval) in ((:(typeof(&)), true), (:(typeof(|)), false)) end # reducedim_initarray is called by -reducedim_initarray(A::AbstractArray, region, v0, ::Type{R}) where {R} = fill!(similar(A,R,reduced_indices(A,region)), v0) -reducedim_initarray(A::AbstractArray, region, v0::T) where {T} = reducedim_initarray(A, region, v0, T) +reducedim_initarray(A::AbstractArray, region, init, ::Type{R}) where {R} = fill!(similar(A,R,reduced_indices(A,region)), init) +reducedim_initarray(A::AbstractArray, region, init::T) where {T} = reducedim_initarray(A, region, init, T) function reducedim_initarray0(A::AbstractArray{T}, region, f, ops) where T ri = reduced_indices0(A, region) @@ -248,9 +248,9 @@ reducedim!(op, R::AbstractArray{RT}, A::AbstractArray) where {RT} = mapreducedim!(identity, op, R, A) """ - mapreduce(f, op[, v0], A::AbstractArray; dims) + mapreduce(f, op, A::AbstractArray; dims=:, init=Base.NoInit()) -Evaluates to the same as `reduce(op, f(v0), map(f, A); dims)`, but is generally +Evaluates to the same as `reduce(op, map(f, A); dims=dims, init=init)`, but is generally faster because the intermediate array is avoided. # Examples @@ -271,30 +271,28 @@ julia> mapreduce(isodd, |, true, a, dims=1) true true true true ``` """ -mapreduce(f, op, v0, A::AbstractArray; dims=:) = _mapreduce_dim(f, op, v0, A, dims) +mapreduce(f, op, A::AbstractArray; dims=:, init=NoInit()) = _mapreduce_dim(f, op, init, A, dims) -mapreduce(f, op, A::AbstractArray; dims=:) = _mapreduce_dim(f, op, A, dims) +_mapreduce_dim(f, op, init, A::AbstractArray, ::Colon) = mapfoldl(f, op, A; init=init) -_mapreduce_dim(f, op, v0, A::AbstractArray, ::Colon) = mapfoldl(f, op, v0, A) +_mapreduce_dim(f, op, ::NoInit, A::AbstractArray, ::Colon) = _mapreduce(f, op, IndexStyle(A), A) -_mapreduce_dim(f, op, A::AbstractArray, ::Colon) = _mapreduce(f, op, IndexStyle(A), A) +_mapreduce_dim(f, op, init, A::AbstractArray, dims) = + mapreducedim!(f, op, reducedim_initarray(A, dims, init), A) -_mapreduce_dim(f, op, v0, A::AbstractArray, dims) = - mapreducedim!(f, op, reducedim_initarray(A, dims, v0), A) - -_mapreduce_dim(f, op, A::AbstractArray, dims) = +_mapreduce_dim(f, op, ::NoInit, A::AbstractArray, dims) = mapreducedim!(f, op, reducedim_init(f, op, A, dims), A) """ - reduce(f[, v0], A; dims) + reduce(f, A; dims=:, init=Base.NoInit()) Reduce 2-argument function `f` along dimensions of `A`. `dims` is a vector specifying the -dimensions to reduce, and `v0` is the initial value to use in the reductions. For `+`, `*`, -`max` and `min` the `v0` argument is optional. +dimensions to reduce, and `init` is the initial value to use in the reductions. For `+`, `*`, +`max` and `min` the `init` argument is optional. The associativity of the reduction is implementation-dependent; if you need a particular -associativity, e.g. left-to-right, you should write your own loop. See documentation for -[`reduce`](@ref). +associativity, e.g. left-to-right, you should write your own loop or consider using +[`foldl`](@ref) or [`foldr`](@ref). See documentation for [`reduce`](@ref). # Examples ```jldoctest @@ -317,15 +315,9 @@ julia> reduce(max, a, dims=1) 4 8 12 16 ``` """ -reduce(op, v0, A::AbstractArray; dims=:) = _reduce_dim(op, v0, A, dims) - -_reduce_dim(op, v0, A, dims) = mapreduce(identity, op, v0, A, dims=dims) -_reduce_dim(op, v0, A, ::Colon) = mapreduce(identity, op, v0, A) - -reduce(op, A::AbstractArray; dims=:) = _reduce_dim(op, A, dims) +reduce(op, A::AbstractArray; dims=:, init=NoInit()) = _reduce_dim(op, init, A, dims) -_reduce_dim(op, A, dims) = mapreduce(identity, op, A, dims=dims) -_reduce_dim(op, A, ::Colon) = mapreduce(identity, op, A) +_reduce_dim(op, init, A, dims) = mapreduce(identity, op, A; dims = dims, init=init) ##### Specific reduction functions ##### """ diff --git a/base/strings/unicode.jl b/base/strings/unicode.jl index 39eb6b015ff038..3b113bedfb4d82 100644 --- a/base/strings/unicode.jl +++ b/base/strings/unicode.jl @@ -234,7 +234,7 @@ julia> textwidth("March") 5 ``` """ -textwidth(s::AbstractString) = mapreduce(textwidth, +, 0, s) +textwidth(s::AbstractString) = mapreduce(textwidth, +, s; init = 0) lowercase(c::T) where {T<:AbstractChar} = isascii(c) ? ('A' <= c <= 'Z' ? c + 0x20 : c) : T(ccall(:utf8proc_tolower, UInt32, (UInt32,), c)) diff --git a/doc/src/base/collections.md b/doc/src/base/collections.md index 4a64aeaa82a2bc..471b74ca4de42d 100644 --- a/doc/src/base/collections.md +++ b/doc/src/base/collections.md @@ -82,11 +82,8 @@ Base.indexin Base.unique Base.unique! Base.allunique -Base.reduce(::Any, ::Any, ::Any) Base.reduce(::Any, ::Any) -Base.foldl(::Any, ::Any, ::Any) Base.foldl(::Any, ::Any) -Base.foldr(::Any, ::Any, ::Any) Base.foldr(::Any, ::Any) Base.maximum Base.maximum! @@ -116,11 +113,8 @@ Base.all(::Any, ::Any) Base.foreach Base.map Base.map! -Base.mapreduce(::Any, ::Any, ::Any, ::Any) Base.mapreduce(::Any, ::Any, ::Any) -Base.mapfoldl(::Any, ::Any, ::Any, ::Any) Base.mapfoldl(::Any, ::Any, ::Any) -Base.mapfoldr(::Any, ::Any, ::Any, ::Any) Base.mapfoldr(::Any, ::Any, ::Any) Base.first Base.last diff --git a/test/reduce.jl b/test/reduce.jl index 67d750df3aa8ab..9c99fb6068620c 100644 --- a/test/reduce.jl +++ b/test/reduce.jl @@ -6,36 +6,36 @@ using Random @test foldl(+, Int64[]) === Int64(0) # In reference to issues #7465/#20144 (PR #20160) @test foldl(+, Int16[]) === Int16(0) # In reference to issues #21536 @test foldl(-, 1:5) == -13 -@test foldl(-, 10, 1:5) == -5 +@test foldl(-, 1:5; init=10) == -5 @test Base.mapfoldl(abs2, -, 2:5) == -46 -@test Base.mapfoldl(abs2, -, 10, 2:5) == -44 +@test Base.mapfoldl(abs2, -, 2:5; init=10) == -44 @test Base.mapfoldl(abs2, /, 2:5) ≈ 1/900 -@test Base.mapfoldl(abs2, /, 10, 2:5) ≈ 1/1440 +@test Base.mapfoldl(abs2, /, 2:5; init=10) ≈ 1/1440 -@test Base.mapfoldl((x)-> x ⊻ true, &, true, [true false true false false]) == false @test Base.mapfoldl((x)-> x ⊻ true, &, [true false true false false]) == false +@test Base.mapfoldl((x)-> x ⊻ true, &, [true false true false false]; init=true) == false @test Base.mapfoldl((x)-> x ⊻ true, |, [true false true false false]) == true -@test Base.mapfoldl((x)-> x ⊻ true, |, false, [true false true false false]) == true +@test Base.mapfoldl((x)-> x ⊻ true, |, [true false true false false]; init=false) == true @test foldr(+, Int64[]) === Int64(0) # In reference to issue #20144 (PR #20160) @test foldr(+, Int16[]) === Int16(0) # In reference to issues #21536 @test foldr(-, 1:5) == 3 -@test foldr(-, 10, 1:5) == -7 +@test foldr(-, 1:5; init=10) == -7 @test foldr(+, [1]) == 1 # Issue #21493 @test Base.mapfoldr(abs2, -, 2:5) == -14 -@test Base.mapfoldr(abs2, -, 10, 2:5) == -4 +@test Base.mapfoldr(abs2, -, 2:5; init=10) == -4 # reduce @test reduce(+, Int64[]) === Int64(0) # In reference to issue #20144 (PR #20160) @test reduce(+, Int16[]) === Int16(0) # In reference to issues #21536 @test reduce((x,y)->"($x+$y)", 9:11) == "((9+10)+11)" @test reduce(max, [8 6 7 5 3 0 9]) == 9 -@test reduce(+, 1000, 1:5) == (1000 + 1 + 2 + 3 + 4 + 5) -@test reduce(+,1) == 1 +@test reduce(+, 1:5; init=1000) == (1000 + 1 + 2 + 3 + 4 + 5) +@test reduce(+, 1) == 1 # mapreduce @test mapreduce(-, +, [-10 -9 -3]) == ((10 + 9) + 3) @@ -118,12 +118,12 @@ sum2(itr) = invoke(sum, Tuple{Any}, itr) plus(x,y) = x + y sum3(A) = reduce(plus, A) sum4(itr) = invoke(reduce, Tuple{Function, Any}, plus, itr) -sum5(A) = reduce(plus, 0, A) +sum5(A) = reduce(plus, A; int=0) sum6(itr) = invoke(reduce, Tuple{Function, Int, Any}, plus, 0, itr) sum7(A) = mapreduce(x->x, plus, A) sum8(itr) = invoke(mapreduce, Tuple{Function, Function, Any}, x->x, plus, itr) -sum9(A) = mapreduce(x->x, plus, 0, A) -sum10(itr) = invoke(mapreduce, Tuple{Function, Function, Int, Any}, x->x,plus,0,itr) +sum9(A) = mapreduce(x->x, plus, A; init=0) +sum10(itr) = invoke(mapreduce, Tuple{Function, Function, Int, Any}, x->x,plus, 0, itr) for f in (sum2, sum5, sum6, sum9, sum10) @test sum(z) == f(z) @test sum(Int[]) == f(Int[]) == 0 diff --git a/test/reducedim.jl b/test/reducedim.jl index 2c9f622cb49058..88da2c60547afc 100644 --- a/test/reducedim.jl +++ b/test/reducedim.jl @@ -111,15 +111,15 @@ end @test sum(Union{Float32, Float64}[1.0], dims=1) == [1.0] @test prod(Union{Float32, Float64}[1.0], dims=1) == [1.0] -@test reduce((a,b) -> a|b, false, [true false; false false], dims=1) == [true false] -let R = reduce((a,b) -> a+b, 0.0, [1 2; 3 4], dims=2) +@test reduce((a,b) -> a|b, [true false; false false], dims=1, init=false) == [true false] +let R = reduce((a,b) -> a+b, [1 2; 3 4], dims=2, init=0.0) @test eltype(R) == Float64 @test R ≈ [3,7] end -@test reduce((a,b) -> a+b, 0, [1 2; 3 4], dims=1) == [4 6] +@test reduce((a,b) -> a+b, [1 2; 3 4], dims=1, init=0) == [4 6] # inferred return types -@test typeof(@inferred(reduce(+, 0.0, ones(3,3,3), dims=1))) == Array{Float64, 3} +@test typeof(@inferred(reduce(+, ones(3,3,3), dims=1, init=0.0))) == Array{Float64, 3} @testset "empty cases" begin A = Matrix{Int}(undef, 0,1)