-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
findnext
and degenerate multidimensional arrays
#36938
Comments
Good catch. I guess the reason for the current behavior is to have A simple fix would be to use More fundamentally I'm not sure why |
Sure thing.
I don't have a justification handy (@mbauman comes to mind, so maybe they do), but I suppose this choice is made to be consistent with One guiding desire is which is true right now by definition, on
The choice of what julia> first(keys(zeros(0, 2)))
CartesianIndex(1, 1)
julia> first(collect(keys(zeros(0, 2))))
ERROR: BoundsError: attempt to access 0×2 Array{CartesianIndex{2},2} at index [1] which I bet is chosen to align with: julia> first(LinearIndices(fill(0, (0, 2))))
1
julia> firstindex(fill(0, (0, 2)))
1 It's peculiar that julia> first(keys(fill(0, (1, 2))))
CartesianIndex(1, 1)
julia> first(keys(fill(0, (0, 2))))
CartesianIndex(1, 1) Here's one explanation: #34697 (comment) |
@nalimilan I am not sure if Current behavior: julia> findnext(fill(false, 2, 2), CartesianIndex(-1, -1))
ERROR: BoundsError: attempt to access 2×2 Matrix{Bool} at index [-1, -1] If I understood your suggestion with |
Mind the |
Perhaps you understood that Let me know if this clarifies it function _findnext(A, start)
l = last(keys(A))
i = oftype(l, start)
# i > l && return nothing # original line
checkbounds(Bool, A, i) || return nothing
while true
A[i] && return i
i == l && break
# nextind(A, l) can throw/overflow
i = nextind(A, i)
end
return nothing
end
_findfirst(A) = _findnext(A, first(keys(A)))
#
findfirst(fill(false, 0, 2)) # broken
findfirst(fill(false, 2, 0)) # good
_findfirst(fill(false, 0, 2)) # the change fixed this, good 👍
_findfirst(fill(false, 2, 0)) # this was working before, and still is.
findnext(fill(false, 2, 2), CartesianIndex(0, 0)) # throws `BoundsError`, good
_findnext(fill(false, 2, 2), CartesianIndex(0, 0)) # does not. newly broken. 👎 |
Instead of: i > l && return nothing what about simply doing: (i > l || isempty(A)) && return nothing (Edit: oh, I see, this will similarly not throw BoundsErrors like you want) I think it's just fine — and a feature — that I agree it feels unsatisfactory that |
Thinking about this more, I don't think we want to throw bounds errors from |
I wrote findnext(fill(false, 2, 2), CartesianIndex(0, 0)) # throws `BoundsError`, good
_findnext(fill(false, 2, 2), CartesianIndex(0, 0)) # does not. newly broken. 👎 But I actually wasn't sure whether it should or should not throw Would it be non-breaking to make |
cross-ref #36768 |
Probably others already know this, so consider it a note-to-self. I had suspected that there might be something wrong with the ordering on import OffsetArrays # for `fill` to work on `UnitRange`
"""
Clumsy implementation based on definition in https://github.com/JuliaLang/julia/pull/16054 :
`isless(I1, I2)` returns true if `I1` would be visited before `I2` during `CartesianRange` iteration
"""
function isless2(I1::CartesianIndex, I2::CartesianIndex)
ranges = map(x->UnitRange(x...), minmax.(Tuple(I1), Tuple(I2)))
A = fill(nothing, ranges)
for i = CartesianIndices(A)
i === I1 && return true
i === I2 && return false
end
error()
end
@show isless(CartesianIndex(1, 1), CartesianIndex(0, 2))
@show isless(CartesianIndex(1, 1), CartesianIndex(2, 0))
@show isless2(CartesianIndex(1, 1), CartesianIndex(0, 2))
@show isless2(CartesianIndex(1, 1), CartesianIndex(2, 0)) So indeed the implementation of |
Could you elaborate on this? I think I missed some context. |
My point is simply that for integer indices, there aren't values between |
Thanks for clarifying. I am not sure I've understood what (or whether) we've converged on in terms of desired behavior. Note that there seems to be an issue with the bounds checking that is unrelated to degenerate array dimensions. I wrote some tests here: https://github.com/JuliaLang/julia/pull/36967/files#diff-f5dc554ce02d9b8c6323d7a7e8077ccaR594 @nalimilan @mbauman do those tests encode the behavior we want?
The tests show situations where |
OK so we currently have an asymmetry in the integer bounds check (we throw an error when it's too low, but return So regarding your tests, I'm not sure we really want Regarding the implementation, maybe we should just do |
You mentioned the too-low-vs-too-high asymmetry which is showing up because the fix for the issue should either intentionally preserve the current behavior, or intentionally improve it. Another asymmetry that currently exists, that I'd like to get rid of, is that these do different things: @test findnext(fill(true, (2, 2)), CartesianIndex(1, 3)) === nothing
@test findnext(fill(true, (2, 2)), CartesianIndex(3, 1)) === nothing # 👎
@test findnext(fill(true, (2, 2)), CartesianIndex(3, 3)) === nothing Foremost, I believe they should either all throw or all evaluate to If I understood correctly, |
x = fill(true, (2, 2))
i = first(keys(x))
while (v = findnext(x, i)) !== nothing
i = nextind(x, i)
...
end So returning OTC, |
You probably meant
and
That helps me understand better what's going on. Thanks for explaining. |
This behavior strikes me as inconsistent:
I had expected both calls to
findnext
to returnnothing
.Honing in, the inconsistency arises because of
and so the check at
julia/base/array.jl
Line 1726 in 4c1e3c0
I believe the intention in
findnext
is to compareCartesianIndex
s in a way that agrees with the corresponding comparison on theLinearIndex
into the array. For example:(I tried on Julia 1.4 and Julia 1.5)
The text was updated successfully, but these errors were encountered: