Engee 文档

具有自定义索引的数组

传统上,Julia中的数组从1开始索引,而在其他一些语言中,编号从0甚至开始(例如,在Fortran中),您可以指定任意的初始索引。 虽然标准编号(例如,在Julia中从1开始)具有许多优点,但如果索引超出`1:size(A,d)(或`0:size(A,d)-1)的范围是可能的,则某些算法会显着简化。 为了简化这样的计算,Julia支持具有任意索引的数组。

本页回答了"在您自己的代码中支持此类数组需要做些什么?"首先,让我们来看看最简单的情况:如果你知道你永远不必在你的代码中处理具有非标准索引的数组,那么答案可能是什么都没有。 如果使用导出的Julia接口,使用传统数组的旧代码应该在没有任何更改的情况下工作。 如果简单地禁止使用非统一索引的非传统数组似乎更方便,则可以添加:

Base.require_one_based_indexing(arrays...)

其中'数组。..'是一个数组对象的列表,需要检查是否有索引违规。

通用现有代码

一般程序如下:

  • 将`size’替换为’axes`;

  • 将`1:length(A)替换为`eachindex(A),或者在某些情况下替换为’LinearIndices(A)`;

  • 替换显式内存分配,如'Array{Int}(undef,size(B))',to'similar(Array{Int},轴(B))`。

关于这一点的更多细节如下所述。

可能的危害

由于许多人可能理所当然地认为所有数组都是从一个开始索引的,因此在使用具有非标准索引的数组时总是有可能出错。 最烦人的事情是得到不正确的结果或程序崩溃(Julia崩溃)。 例如,考虑以下功能。

function mycopy!(dest::AbstractVector, src::AbstractVector)
    length(dest) == length(src) || throw(DimensionMismatch("vectors must match"))
    # Теперь можно спокойно использовать @inbounds, верно? (Уже нет!)
    for i = 1:length(src)
        @inbounds dest[i] = src[i]
    end
    dest
end

此代码隐式假定向量是从一个索引的;如果’dest`不是从与`src’相同的索引开始,则有可能发生紧急终止。 (如果发生这种情况,那么要找出原因,请尝试使用`--check-bounds=yes`参数运行Julia。)

使用"轴"检查边界并迭代循环

'Axes(A)方法(类似于`size(A))返回一个`AbstractUnitRange’对象的元组{<:Integer}`,为数组`A’的每个维度定义允许的索引范围。 如果`A’以非标准方式索引,则范围可能不会以一个开头。 要获取特定维度’d’的范围,请使用`axes(A,d)'调用。

基本模块实现了自定义范围类型’OneTo`:'OneTo(n)`的意思与`1:n’相同,但保证(通过类型系统)下标为1。 适用于任何新型 `AbstractArray'默认情况下,axes函数返回此类型的对象。 这意味着这种类型的数组使用传统的索引-从一个开始。

请注意,有称为"checkbounds"和"checkindex"的特殊功能用于检查边界,有时可以简化此任务。

线性索引('LinearIndices')

一些算法使用线性索引`A[i]最方便(或更有效)实现,即使`A`是多维数组。 无论数组自己的索引是什么,线性索引总是有一个`1:length(A)'的范围。 然而,在一维数组的情况下(即 `AbstractVector)出现了一个模糊性:`v[i]'是否意味着线性索引或具有自己的数组索引的笛卡尔索引?

出于这个原因,最佳解决方案可能是使用`eachindex(A)遍历数组,或者,如果索引必须是连续的整数,则通过调用`LinearIndices(A)'来获得一系列索引。 因此,如果A是AbstractVector,则将返回`axes(A,1),否则将返回相当于`1:length(A)'的值。

根据这个定义,一维数组总是使用笛卡尔索引和它们自己的数组索引。 为了证实这一事实,值得注意的是,如果数组的形状对应于具有非标准索引的一维数组(即'Tuple{UnitRange}`,而不是元组’OneTo')。 对于具有标准索引的数组,这些函数将照常工作。

这是重写’mycopy!'使用’轴’和’LinearIndices'。

function mycopy!(dest::AbstractVector, src::AbstractVector)
    axes(dest) == axes(src) || throw(DimensionMismatch("vectors must match"))
    for i in LinearIndices(src)
        @inbounds dest[i] = src[i]
    end
    dest
end

使用"类似"概括分配存储空间

存储空间通常使用`Array'分配。{Int}(undef,dims)类似(A,args...)`. 如果结果必须匹配其他数组的索引,这可能并不总是足够的。 此类模板的通用替换是使用"类似(storagetype,shape)"。 'storagetype’表示所需的基本标准行为类型,例如'Array{Int}','BitArray’甚至'dims->zeros(Float32,dims)'(在这种情况下,只有零的数组被放置在内存中)。 'shape’是值的元组 'Integer'或’AbstractUnitRange',它定义了要在结果中使用的索引。 请注意:要获得与a的索引相对应的只有零的数组,简单地使用`zeros(A)'很方便。

让我们看几个说明性的例子。 首先,如果’A’具有标准索引,则'similar(Array{Int},axes(A))'将导致调用'Array{Int}(undef,size(A))并返回数组。 如果’A’是具有非标准索引的AbstractArray类型,则调用'similar(Array{Int},axes(A))'应该返回类似于Array的行为{Int},但具有对应于`A`的表单(包括索引)。 (最明显的实现是在内存中放置一个Array'对象。{Int}(undef,size(A))`后跟其封装在索引移位的类型中。)

还要记住,调用'similar(Array{Int},(axes(A,2),))'将一个对象放入内存'AbstractVector{Int}'(即一维数组)对应的列索引`A'。

使用非统一索引编写自定义数组类型

大多数需要定义的方法对于任何类型的"AbstractArray"都是标准的;请参阅该部分 抽象数组。 本节介绍识别非标准索引所需的步骤。

自定义’AbstractUnitRange’类型

如果要创建具有非单位索引的数组类型,则需要专门化axes函数,以便它返回UnitRange对象或(甚至更好)自定义AbstractUnitRange对象。 用户定义类型的优点是它告诉诸如`similar’之类的函数有关内存分配的类型。 在编写从零开始索引的数组类型时,最好先创建一个新类型`AbstractUnitRange`--ZeroRange,其中`ZeroRange(n)`相当于`0:n-1'。

一般来说,你不应该从一个包中导出`ZeroRange`:可能还有其他包实现了他们自己的`ZeroRange`类型,并且有几个单独类型的`ZeroRange`,奇怪的是,是一个优势。 'ModuleA。ZeroRange’表示’类似’函数应该创建’ModuleA。ZeroArray',和’ModuleB。ZeroRange’对应于类型’ModuleB。零阵列'。 这种方法确保了许多不同的用户定义数组类型的和平共存。

请注意:为了不编写自己的`ZeroRange`类型,您有时可以使用Julia包。 https://github.com/JuliaArrays/CustomUnitRanges.jl [CustomUnitRanges.jl]。

"轴"的专业化

创建自己的’AbstractUnitRange’类型后,您需要专门化’axes’函数。

Base.axes(A::ZeroArray) = map(n->ZeroRange(n), A.size)

这里假设’ZeroArray’有一个名为`size’的字段(这可以通过其他方式实现)。

在某些情况下,备份定义是’axes(A,d)'。

axes(A::AbstractArray{T,N}, d) where {T,N} = d <= N ? axes(A)[d] : OneTo(1)

它可能无法满足您的需求。:您可能需要专门化它,以便它为`d>ndims(A)返回`OneTo(1)`以外的东西。 同样,'Base’有一个特殊的函数’axes1',它相当于’axes(A,1),但忽略了检查。 'ndims(A)>0'(运行时)。 (其目的仅仅是为了提高生产率。)定义如下。

axes1(A::AbstractArray{T,0}) where {T} = OneTo(1)
axes1(A::AbstractArray) = axes(A)[1]

如果第一种情况(一维数组)对您的自定义数组类型造成问题,请实施适当的特化。

专业化"类似`

对于您自己的类型’ZeroRange`,您还必须添加以下两个专业化"类似"。

function Base.similar(A::AbstractArray, T::Type, shape::Tuple{ZeroRange,Vararg{ZeroRange}})
    # тело
end

function Base.similar(f::Union{Function,DataType}, shape::Tuple{ZeroRange,Vararg{ZeroRange}})
    # тело
end

它们都应该实现您的自定义数组类型。

"重塑"的专业化

如有必要,定义以下方法。

Base.reshape(A::AbstractArray, shape::Tuple{ZeroRange,Vararg{ZeroRange}}) = ...

这将改变数组的形状(reshape),使其具有自定义索引。

模仿AbstractArray行为的对象,但不是其子类型

'Has_offset_axes’方法的值取决于是否为其调用的对象定义了’axes’方法。 如果由于某种原因没有为对象定义axes方法,则可以定义以下方法。

Base.has_offset_axes(obj::MyNon1IndexedArraylikeObject) = true

这将允许您在假定从unity索引的代码中识别问题,并给出有用的错误消息,而不是返回不正确的结果或Julia崩溃。

错误拦截

如果新的数组类型在代码的其他地方导致错误,您可以尝试在"getindex"和"setindex"的实现中注释掉"@boundscheck"!'进行调试。 这将确保在访问每个元素时,检查边界。 或者,您可以使用`--check-bounds=yes`参数重新启动Julia。

在某些情况下,暂时禁用新数组类型的`size`和`length’函数可能会很有用,因为它们通常在假设不正确的代码中使用。