BoundsError using reduce function (Julia 1.3.1)

I get the following error when I try to reduce the tuple array. Honestly I have no idea why this happen.

julia> a = [(x, rand()*10) for x in 1:20]
20-element Array{Tuple{Int64,Float64},1}:
(1, 8.090189006962254)
(2, 2.117390143738971)
(3, 3.720862674831744)
(4, 3.3448022839992686)
(5, 8.663485519459988)
(6, 2.8559985846713887)
(7, 8.90791074876892)
(8, 1.8252199991786422)
(9, 8.850720581613224)
(10, 2.3484428215704645)
(11, 7.05697492936725)
(12, 5.204279968934786)
(13, 9.661816648445496)
(14, 8.822892862843808)
(15, 9.937308741925413)
(16, 4.386983103895896)
(17, 6.657011304452505)
(18, 3.1464881449135285)
(19, 5.202863099252719)
(20, 5.450333298946468)

Correctly reduce on first tuple argument:

julia> reduce((x, y) → x[1] + y[1], a)
210

Error when reducing on second tuple argument:

julia> reduce((x, y) → x[2] + y[2], a)
ERROR: BoundsError
Stacktrace:
[1] getindex at .\number.jl:78 [inlined]
[2] #21 at .\REPL[10]:1 [inlined]
[3] macro expansion at .\reduce.jl:158 [inlined]
[4] macro expansion at .\simdloop.jl:77 [inlined]
[5] mapreduce_impl(::typeof(identity), ::var"#21#22", ::Array{Tuple{Int64,Float64},1}, ::Int64, ::Int64, ::Int64) at .\reduce.jl:156
[6] mapreduce_impl at .\reduce.jl:170 [inlined]
[7] _mapreduce(::typeof(identity), ::var"#21#22", ::IndexLinear, ::Array{Tuple{Int64,Float64},1}) at .\reduce.jl:316
[8] _mapreduce_dim(::Function, ::Function, ::NamedTuple{(),Tuple{}}, ::Array{Tuple{Int64,Float64},1}, ::Colon) at .\reducedim.jl:312
[9] #mapreduce#584 at .\reducedim.jl:307 [inlined]
[10] mapreduce at .\reducedim.jl:307 [inlined]
[11] #reduce#586 at .\reducedim.jl:352 [inlined]
[12] reduce(::Function, ::Array{Tuple{Int64,Float64},1}) at .\reducedim.jl:352
[13] top-level scope at REPL[10]:1

Note that in your example, a typical argument is

x, y = 1, 8.090189006962254

so I am not sure what you are expecting from y[2].

The fact that y[1] etc works is a design wart of Julia, cf

julia> 2[1]
2

I think what you want is mapreduce(x -> x[1], +, a) and mapreduce(x -> x[2], +, a).

Note that the argument op for reduce is fed back to one of its arguments. If you are returning a number from op you need to be able to handle a number.

Since it is an array of tuple and a tuple is an indexable collection, what I expect is the sum of all element at second place. In fact the expression:

sum(t → t[2], a)
130.64905626543398

Do the job. (Which is a special implementation of reduce as Julia official doc say)

Yes, and I think is more terse this other solution:

sum(t → t[2], a)

As I wrote above, but in case the operator was not so simple still remain this unexpected behaviour. Why this happen just for second element of the tuple?

This does what you want:

reduce((x, y) -> x + y[2], a, init = 0.0)

To better understand what was going on in your example, try

reduce((x, y) -> (@show x y; x[1] + y[1]), a)
3 Likes

Thanks! :grinning:

This is wrong. It works because of an implementation detail (reduce fallbacks to foldl in this case). Please read the documentation:

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.

— https://docs.julialang.org/en/latest/base/collections/#Base.reduce-Tuple{Any,Any}

It says init has to be a neutral element aka identity of op. This is to say, following must hold:

for x in a
    @assert op(x, init) == x
    @assert op(init, x) == x
end

However, op = (x, y) -> x + y[2] and init = 0.0 does not satisfy this. Element-wise processing must use mapreduce, not reduce. Alternatively, just use foldl. It is correct to invoke foldl((x, y) -> x + y[2], a, init = 0.0).

Note also that op for reduce has to be associative (i.e., it has to be a monoid). op = (x, y) -> x + y[2] does not satisfy this.

A general advice is to be careful with “asymmetric looking” op when using reduce. If you can’t convince yourself that given op is associative and init is its identity, use foldl.

(All these complication may discourage you to use reduce. However, it is one of the most fundamental tool for parallel computation. So I think it’s important to learn how to use reduce.)

2 Likes