Quantcast
Channel: First steps - JuliaLang
Viewing all articles
Browse latest Browse all 2795

Stubborn 1-based LinearIndices for the custom type with non-zero based indexing

$
0
0

Greetings and apologies if this is very basic.

I am trying to create a custom type subtyped from AbstractArray to dispatch on it in place of a dense array at times. I’ve been following instructions in the manual for specifying arrays with custom indices. I can make dereferencing and iteration work, but can’t get pairs() to function properly, it stubbornly produces unit-based indices, but unlike enumerate(), also pairs them with the non-existant elements.

Here is my MWE

import Base: length,size,iterate,getindex,axes, has_offset_axes
struct myrange <: AbstractVector{Int}  
    LStart::Int
    LStop::Int
end
IndexStyle(::Type{myrange}) = IndexLinear()
length(r::myrange) = r.LStop-r.LStart+1
size(r::myrange) = (length(r),)
function Base.getindex(r::myrange,i::Integer)
    @boundscheck r.LStart≤ i ≤ r.LStop || throw(BoundsError(r,i))
    i
end
firstindex(r::myrange) = r.LStart
lastindex(r::myrange) = r.LStop

The manual says it’s better to create a custom class for the indices range, but suggests it’s possible to use UnitRange. This is the approach I follow as I don’t need anything fancier

@inline Base.axes(r::myrange) = (UnitRange(r.LStart,r.LStop),)
Base.has_offset_axes(::myrange) = true

I also overloaded similar() as suggested in the manual, but this does not seem to matter.

Now, this seems to work:

julia> axes(z)[1]
4:8

But when attempting to use the class:

julia> LinearIndices(axes(z))
5-element LinearIndices{1,Tuple{UnitRange{Int64}}}:
1
2
3
4
5

and consequently

julia> pairs(IndexLinear(), z)
pairs(::myrange{Int64}) with 5 entriesError showing value of type Base.Iterators.Pairs{Int64,Integer,LinearIndices{1,Tuple{UnitRange{Int64}}},myrange{Int64}}:

ERROR: BoundsError: attempt to access 5-element myrange{Int64} with indices 4:8 at index [1]

The post mortem reveals that pairs() has indeed got the wrong indices from LinearIndices:

julia> properties(pairs(IndexLinear(), z))
2-element Array{Tuple{Symbol,AbstractArray{T,1} where T},1}:
(:data, Integer[#undef, #undef, #undef, 4, 5])
(:itr, [1, 2, 3, 4, 5])

But curiously

julia> LinearIndices(axes(z)).indices
(4:8,)

One might think that overloading LinearIndices for the custom class is required, but do observe this working perfectly:

zz=OffsetArray(4:8,4:8)
LinearIndices(axes(zz))
pairs(IndexLinear(), zz)

@which LinearIndices(axes(zz)) shows that both myrange and OffsetArray get dispatched to the same (default) LinearIndices constructor (in Base at indices.jl:444)

So my questions are:

  • how is it possible that LinearIndices carries wrong indices despite explicitly containing the correct ones in the eponymous field? What is the difference between axes(::myrange) and axes(::OffsetArray) that causes the divergence?

  • what is the minimal way to modify my class to avoid the above behaviour?

I suspect there may be hints in the paragraph below, but I am not getting conversion errors, nor does this explain the mystery of LinearIndices(::myrange)

By this definition, 1-dimensional arrays always use Cartesian indexing with the array’s native indices. To help enforce this, it’s worth noting that the index conversion functions will throw an error if shape indicates a 1-dimensional array with unconventional indexing (i.e., is a Tuple{UnitRange} rather than a tuple of OneTo). For arrays with conventional indexing, these functions continue to work the same as always.

3 posts - 2 participants

Read full topic


Viewing all articles
Browse latest Browse all 2795

Latest Images

Trending Articles



Latest Images