【问题标题】:Array range complement数组范围补码
【发布时间】:2017-02-22 03:33:28
【问题描述】:

有没有办法覆盖[] 以在数组中补码范围?

julia> a=[1:8...]
8-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8

julia> a[-1] == a[2:8]
julia> a[-(1:3)] == a[4:8]
julia> a[-end] == a[1:7]

【问题讨论】:

  • 这将非常具有误导性。即使在支持负索引的语言(例如 python)中,这些也会导致 indexing 操作,而不是 slice。你为什么要首先制造这样的混乱?
  • “你的科学家们全神贯注于他们是否可以,他们没有停下来思考是否应该做。” ~ 侏罗纪公园

标签: arrays julia


【解决方案1】:

我之前没有研究过索引的内部结构,但乍一看,以下内容可能会在不破坏太多的情况下工作:

immutable Not{T}
    idx::T
end    

if :to_indices in names(Base)
    # 0.6
    import Base: to_indices, uncolon, tail, _maybetail

    @inline to_indices(A, inds, I::Tuple{Not, Vararg{Any}}) =
       (setdiff(uncolon(inds, (:, tail(I)...)), I[1].idx), to_indices(A, _maybetail(inds), tail(I))...)       
else
    # 0.5
    import Base: getindex, _getindex

    not_index(a::AbstractArray, I, i::Int) = I
    not_index(a::AbstractArray, I::Not, i::Int) = setdiff(indices(a, i), I.idx)

    getindex(a::AbstractArray, I::Not) = getindex(a, setdiff(linearindices(a), I.idx))
    _getindex(::Base.LinearIndexing, a::AbstractArray, I::Vararg{Union{Real, AbstractArray, Colon, Not}}) = 
        Base._getindex(Base.linearindexing(a), a, (not_index(a, idx, i) for (i,idx) in enumerate(I))...)
end

例如:

julia> a = reshape(1:9, (3, 3))
3×3 Base.ReshapedArray{Int64,2,UnitRange{Int64},Tuple{}}:
1  4  7
2  5  8
3  6  9

julia> a[Not(2:8)]
2-element Array{Int64,1}:
1
9

julia> a[Not(1:2), :]
1×3 Array{Int64,2}:
3  6  9


julia> a[Not(end), end]
2-element Array{Int64,1}:
7
8

我不关心性能,也没有进行广泛的测试,所以当然可以改进。

编辑:

我将 0.6 的代码替换为来自 cmets 中链接的他的 github 评论中的 Matt B. 版本。

感谢他对 0.6 的数组索引实现的出色设计,只需要扩展一个函数即可获得 getindexsetindexview 的补码索引,例如,

julia> view(a, Not(2:8))
2-element SubArray{Int64,1,UnitRange{Int64},Tuple{Array{Int64,1}},false}:
1
9

# collect because ranges are immutable
julia> b = collect(a); b[Not(2), Not(2)] = 10; b
3×3 Array{Int64,2}:
10  4  10
 2  5   8
10  6  10  

【讨论】:

  • 这是正确的前进方向!我明确设计了内部结构以允许在 0.6 上使用这种索引类型。您甚至不需要 getindex 定义。参考。 github.com/JuliaLang/julia/pull/19730#issuecomment-270012805.
  • @MattB。做得好!在 0.6 上做什么确实比在 0.5 上要清楚得多。我唯一错过的是uncolon 函数。这就是为什么我保留了额外的 getindex 方法来进行 0.5 所需的线性索引。我可以用你的 github 评论中的代码更新我的答案吗?
  • 谢谢!去吧。这个界面背后的整个想法是让其他人能够做这种事情。
【解决方案2】:

直接覆盖[](即getindex)很容易破坏Base中许多与索引相关的东西,但我们可以编写一个数组包装器来解决它。我们只需要定义以下三种方法就可以让您的特定测试用例通过:

immutable ComplementVector{T} <: AbstractArray{T,1}
    data::Vector{T}
end
Base.size(A:: ComplementVector) = size(A.data)
Base.getindex(A:: ComplementVector, i::Integer) = i > 0 ? A.data[i] : A.data[setdiff(1:end, (-i))]
Base.getindex(A:: ComplementVector, I::StepRange) = all(x->x>0, I) ? A.data[I] : A.data[setdiff(1:end, -I)]

julia> a = ComplementVector([1:8...])

julia> a[-1] == a[2:8]
true

julia> a[-(1:3)] == a[4:8]
true

julia> a[-end] == a[1:7]
true

如果您想进一步扩展ComplementVector,请阅读有关Interfaces 的文档。

更新:

为了安全起见,我们最好不要像@Fengyang Wang 在评论中建议的那样扩展AbstractArray

immutable ComplementVector{T}
    data::Vector{T}
end
Base.endof(A::ComplementVector) = length(A.data)
Base.getindex(A::ComplementVector, i::Integer) = i > 0 ? A.data[i] : A.data[setdiff(1:end, (-i))]
Base.getindex(A::ComplementVector, I::OrdinalRange) = all(x->x>0, I) ? A.data[I] : A.data[setdiff(1:end, -I)]

【讨论】:

  • 这似乎也容易出问题,因为它违反了AbstractArray的假设。最好为索引定义一个新类型,比如ComplementCartesianRange,而不是一个新的索引类型。
  • @FengyangWang 我也这么认为,但似乎 OP 正在寻找一种方法来覆盖 getindex 以获得“语法糖”(例如 a[-1]a[2:end]) .恕我直言,一个函数对于这种用法来说已经足够公平了。
  • 也许它不应该扩展AbstractArray呢?这似乎更安全。
  • 感谢您直接回答我的问题。但是我接受了 tim 的回答,因为它是一种更合适的方式,并且支持切片。
猜你喜欢
  • 1970-01-01
  • 2018-02-25
  • 1970-01-01
  • 2010-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-18
  • 2019-10-17
相关资源
最近更新 更多