AnyMath 文档

具有自定义索引的数组

通常,Julia的数组从1开始索引,而其他一些语言从0开始编号,而其他语言(例如Fortran)允许您指定任意起始索引。 虽然选择一个标准(即Julia为1)有很多优点,但如果您可以在范围之外进行索引,则有一些算法可以大大简化 1:尺寸(A,d) (而不仅仅是 0:尺寸(A,d)-1,要么)。 为了方便这样的计算,Julia支持具有任意索引的数组。

本页的目的是解决这样一个问题:"我必须做些什么才能在自己的代码中支持这样的数组?"首先,让我们解决最简单的情况:如果你知道你的代码永远不需要处理非常规索引的数组,希望答案是"什么都没有。"旧代码,在传统的数组上,只要使用Julia的导出接口,就应该基本上不改变它的功能。 如果您发现强制用户提供索引从一开始的传统数组更方便,您可以添加

Base.require_one_based_indexing(arrays...)

哪里 数组。.. 是您希望检查任何违反基于1的索引的数组对象的列表。

泛化现有代码

作为概述,步骤是:

*取代许多用途 大小轴心 *更换 1:长度(A)每个索引(A),或者在某些情况下 线性元件(A) *替换显式分配,如 数组{Int}(undef,尺寸(B))类似(数组{Int},轴(B))

下面更详细地描述这些。

需要注意的事情

由于非常规索引打破了许多人的假设,即所有数组都以1开始索引,因此总是有机会使用此类数组会触发错误。 最令人沮丧的错误是不正确的结果或segfaults(Julia的总崩溃)。 例如,考虑以下函数:

function mycopy!(dest::AbstractVector, src::AbstractVector)
    length(dest) == length(src) || throw(DimensionMismatch("vectors must match"))
    # OK, now we're safe to use @inbounds, right? (not anymore!)
    for i = 1:length(src)
        @inbounds dest[i] = src[i]
    end
    dest
end

此代码隐式假设向量从1索引;如果 德斯特 从不同的索引开始 src公司,此代码可能会触发segfault。 (如果你确实得到了segfaults,为了帮助找到原因,请尝试使用选项运行julia --检查边界=是.)

使用 轴心 用于边界检查和循环迭代

轴(A) (让人想起 尺寸(A))返回一个元组 N.抽象,抽象{<:Integer} 对象,指定沿 A. 何时 A 具有非常规索引,范围可能不会从1开始。 如果您只想要特定维度的范围 d,有 轴(A,d).

Base实现自定义范围类型, 一个,一个,在哪里 一个(n) 意思是和 1:n 但以保证(通过类型系统)较低索引为1的形式。 对于任何新的 抽象阵列类型,这是默认返回的 轴心,并且它表明该数组类型使用"常规"基于1的索引。

对于边界检查,请注意有专用函数 支票/支票检查索引 这有时可以简化这样的测试。

线性索引(线性;线性)

一些算法是最方便(或有效)写在一个单一的线性索引方面, A[i] 即使 A 是多维的。 无论数组的本机索引如何,线性索引的范围始终为 1:长度(A). 然而,这引起了一维数组的模糊性(又名。, [医文摘]):确实 v[i] 平均线性索引,或笛卡尔索引与数组的本机索引?

出于这个原因,你最好的选择可能是迭代数组 每个索引(A),或者,如果您要求索引是顺序整数,则可以通过调用来获取索引范围 线性元件(A). 这将返回 轴(A,1) 如果A是AbstractVector,则相当于 1:长度(A) 否则。

根据这个定义,一维数组始终使用笛卡尔索引和数组的本机索引。 为了帮助强制执行这一点,值得注意的是,如果shape指示具有非常规索引的一维数组(即,是 元组{UnitRange} 而不是一个元组 一个,一个). 对于具有常规索引的数组,这些函数继续一如既往地工作。

使用 轴心线性;线性,这是你可以重写的一种方法 复制!:

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

使用 类似的

存储通常与 数组{Int}(undef,dims)类似(A,args...). 当结果需要匹配其他数组的索引时,这可能并不总是足够的。 这种模式的通用替代品是使用 类似(storagetype,shape). 存储类型 指示您想要的潜在"常规"行为类型,例如, 数组{Int}比特阵列 甚至 dims->零(Float32,dims) (这将分配一个全零数组)。 形状 是一个元组 整数N.抽象,抽象 值,指定您希望结果使用的索引。 请注意,生成与a索引匹配的全零数组的一种方便方法是简单地 零(A).

让我们通过几个明确的例子。 首先,如果 A 有常规指数,那么 类似(数组{Int},轴(A)) 最终会打电话来 数组{Int}(undef,尺寸(A)),从而返回一个数组。 如果 A 是一个 抽象阵列 键入非常规索引,然后 类似(数组{Int},轴(A)) 应该返回一些"行为像"的东西 数组{Int} 但具有匹配的形状(包括索引) A. (最明显的实现是分配一个 数组{Int}(undef,尺寸(A)) 然后将其"包装"在一个改变索引的类型中。)

另请注意 类似(数组{Int},(轴(a,2),)) 会分配一个 AbstractVector{Int} (即,一维数组)匹配的列的索引 A.

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

您需要定义的大多数方法都是任何方法的标准 抽象阵列 类型,见 抽象数组。 本页重点介绍定义非常规索引所需的步骤。

海关规定 N.抽象,抽象 类别

如果您正在编写非1索引数组类型,则需要专门化 轴心 所以它返回一个 单位范围,或者(也许更好)一个习惯 N.抽象,抽象. 自定义类型的优点是它"发信号"函数的分配类型,例如 类似的. 如果我们正在编写一个索引将从0开始的数组类型,我们可能希望从创建一个新的 N.抽象,抽象, 零兰奇,在哪里 零量程(n) 相当于 0:n-1.

一般来说,你可能应该_not_export 零兰奇 从你的包中:可能还有其他包实现了自己的 零兰奇,并具有多个不同的 零兰奇 类型是(也许是违反直觉的)优势: ModuleA。零兰奇 表示 类似的 应该创建一个 ModuleA。零阵列,而 ModuleB。零兰奇 表示a ModuleB。零阵列 类型。 这种设计允许在许多不同的自定义阵列类型之间和平共处。

注意Julia包https://github.com/JuliaArrays/CustomUnitRanges.jl[CustomUnitRanges.jl]有时可以用来避免需要编写自己的 零兰奇 类型。

专业知识 轴心

一旦你有了你的 N.抽象,抽象 类型,然后专门化 轴心:

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

我们在哪里想象 零阵列 有一个名为 大小 (还有其他方法可以实现这一点)。

在某些情况下,回退定义为 轴(A,d):

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

可能不是你想要的:你可能需要专门化它以返回其他东西。 OneTo(1) 何时 d>ndims(A). 同样,在 基地 有专门的功能 轴1 这相当于 轴(A,1) 但这避免了检查(在运行时)是否 ndims(A)>0. (这纯粹是性能优化。)定义为:

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

如果其中第一个(零维情况)对您的自定义数组类型有问题,请确保适当地对其进行专门化。

专业知识 类似的

根据你的习惯 零兰奇 类型,那么您还应该为以下两个特化添加 类似的:

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

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

这两者都应该分配您的自定义数组类型。

专业知识 重塑形状

或者,定义一个方法

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

你可以 重塑形状 一个数组,以便结果具有自定义索引。

对于模仿AbstractArray但不是子类型的对象

has_offset_axes 取决于有 轴心 为您调用它的对象定义。 如果有什么原因你没有 轴心 为对象定义的方法,考虑定义一个方法

Base.has_offset_axes(obj::MyNon1IndexedArraylikeObject) = true

这将允许假定基于1的索引的代码检测到问题并抛出有用的错误,而不是返回不正确的结果或segfaulting julia。

捕捉错误

如果您的新数组类型在其他代码中触发错误,一个有用的调试步骤可以是注释掉 @boundscheck 在你的 getindex,getindexsetindex! 执行。 这将确保每个元素访问都检查边界。 或者,重新启动julia --检查边界=是.

在某些情况下,暂时禁用也可能有帮助 大小长度 对于您的新数组类型,由于做出错误假设的代码经常使用这些函数。