Skip to content

Commit cbe52ce

Browse files
committed
Make axes(A::AbstractArray) provide default axes
This also rewrites the main constructor in lispy form. The motivation was to make sure that the AbstractUnitRange types would match indices (which the former UnitRange definitely did not).
1 parent 60d2a11 commit cbe52ce

File tree

2 files changed

+46
-27
lines changed

2 files changed

+46
-27
lines changed

src/core.jl

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -161,33 +161,43 @@ immutable AxisArray{T,N,D,Ax} <: AbstractArray{T,N}
161161
end
162162
#
163163
_defaultdimname(i) = i == 1 ? (:row) : i == 2 ? (:col) : i == 3 ? (:page) : Symbol(:dim_, i)
164+
165+
default_axes(A::AbstractArray) = _default_axes(A, indices(A), ())
166+
_default_axes{T,N}(A::AbstractArray{T,N}, inds, axs::NTuple{N}) = axs
167+
@inline _default_axes{T,N,M}(A::AbstractArray{T,N}, inds, axs::NTuple{M}) =
168+
_default_axes(A, inds, (axs..., _nextaxistype(A, axs)(inds[M+1])))
169+
# Why doesn't @pure work here?
170+
@generated function _nextaxistype{T,M}(A::AbstractArray{T}, axs::NTuple{M})
171+
name = _defaultdimname(M+1)
172+
:(Axis{$(Expr(:quote, name))})
173+
end
174+
164175
AxisArray(A::AbstractArray, axs::Axis...) = AxisArray(A, axs)
165-
@generated function AxisArray{T,N,L}(A::AbstractArray{T,N}, axs::NTuple{L,Axis})
166-
ax = Expr(:tuple)
167-
Ax = Tuple{axs.parameters...,
168-
ntuple(i->Axis{_defaultdimname(i+L),UnitRange{Int64}},N-L)...}
169-
if !all(x->isa(axisname(x),Symbol), axs.parameters)
170-
return :(throw(ArgumentError("the Axis names must be Symbols")))
171-
end
172-
for i=1:L
173-
push!(ax.args, :(axs[$i]))
174-
end
175-
for i=L+1:N
176-
push!(ax.args, :(Axis{_defaultdimname($i)}(indices(A, $i))))
177-
end
178-
quote
179-
for i = 1:length(axs)
180-
checkaxis(axs[i].val)
181-
if _length(axs[i].val) != _size(A, i)
182-
throw(ArgumentError("the length of each axis must match the corresponding size of data"))
183-
end
184-
end
185-
if length(unique(axisnames($(ax.args...)))) != N
186-
throw(ArgumentError("axis names $(axisnames($(ax.args...))) must be unique"))
187-
end
188-
$(AxisArray{T,N,A,Ax})(A, $ax)
176+
function AxisArray{T,N}(A::AbstractArray{T,N}, axs::NTuple{N,Axis})
177+
checksizes(axs, _size(A)) || throw(ArgumentError("the length of each axis must match the corresponding size of data"))
178+
checknames(axisnames(axs...)...)
179+
AxisArray{T,N,typeof(A),typeof(axs)}(A, axs)
180+
end
181+
function AxisArray{L}(A::AbstractArray, axs::NTuple{L,Axis})
182+
newaxs = _default_axes(A, indices(A), axs)
183+
AxisArray(A, newaxs)
184+
end
185+
186+
@inline checksizes(axs, sz) =
187+
(length(axs[1]) == sz[1]) & checksizes(tail(axs), tail(sz))
188+
checksizes(::Tuple{}, sz) = true
189+
190+
@inline function checknames(name::Symbol, names...)
191+
matches = false
192+
for n in names
193+
matches |= name == n
189194
end
195+
matches && throw(ArgumentError("axis name :$name is used more than once"))
196+
checknames(names...)
190197
end
198+
checknames(name, names...) = throw(ArgumentError("the Axis names must be Symbols"))
199+
checknames() = ()
200+
191201
# Simple non-type-stable constructors to specify just the name or axis values
192202
AxisArray(A::AbstractArray) = AxisArray(A, ()) # Disambiguation
193203
AxisArray(A::AbstractArray, names::Symbol...) = AxisArray(A, map((name,ind)->Axis{name}(ind), names, indices(A)))
@@ -365,9 +375,9 @@ axisnames{T,N,D,Ax}(::Type{AxisArray{T,N,D,Ax}}) = _axisnames(Ax)
365375
axisnames{Ax<:Tuple{Vararg{Axis}}}(::Type{Ax}) = _axisnames(Ax)
366376
@pure _axisnames(Ax) = axisnames(Ax.parameters...)
367377
axisnames() = ()
368-
axisnames{name }(::Axis{name}, B::Axis...) = tuple(name, axisnames(B...)...)
369-
axisnames{name }(::Type{Axis{name}}, B::Type...) = tuple(name, axisnames(B...)...)
370-
axisnames{name,T}(::Type{Axis{name,T}}, B::Type...) = tuple(name, axisnames(B...)...)
378+
@inline axisnames{name }(::Axis{name}, B::Axis...) = tuple(name, axisnames(B...)...)
379+
@inline axisnames{name }(::Type{Axis{name}}, B::Type...) = tuple(name, axisnames(B...)...)
380+
@inline axisnames{name,T}(::Type{Axis{name,T}}, B::Type...) = tuple(name, axisnames(B...)...)
371381

372382
axisname{name,T}(::Type{Axis{name,T}}) = name
373383
axisname{name }(::Type{Axis{name }}) = name
@@ -392,6 +402,9 @@ Returns the tuple of axis vectors for an AxisArray. If an specific `Axis` is
392402
specified, then only that axis vector is returned. Note that when extracting a
393403
single axis vector, `axes(A, Axis{1})`) is type-stable and will perform better
394404
than `axes(A)[1]`.
405+
406+
For an AbstractArray without `Axis` information, `axes` returns the
407+
default axes, i.e., those that would be produced by `AxisArray(A)`.
395408
""" ->
396409
axes(A::AxisArray) = A.axes
397410
axes(A::AxisArray, dim::Int) = A.axes[dim]
@@ -400,6 +413,8 @@ axes(A::AxisArray, ax::Axis) = axes(A, typeof(ax))
400413
dim = axisdim(A, T)
401414
:(A.axes[$dim])
402415
end
416+
axes(A::AbstractArray) = default_axes(A)
417+
axes(A::AbstractArray, dim::Int) = default_axes(A)[dim]
403418

404419
### Axis traits ###
405420
abstract AxisTrait

test/core.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ A = AxisArray(reshape(1:24, 2,3,4),
138138
@test @inferred(axes(A, Axis{:y})) == @inferred(axes(A, Axis{:y}())) == Axis{:y}(1//10:1//10:3//10)
139139
@test @inferred(axes(A, Axis{:z})) == @inferred(axes(A, Axis{:z}())) == Axis{:z}(["a", "b", "c", "d"])
140140
@test axes(A, 2) == Axis{:y}(1//10:1//10:3//10)
141+
Aplain = rand(2,3)
142+
@test @inferred(axes(Aplain)) === axes(AxisArray(Aplain))
143+
@test axes(Aplain, 1) === axes(AxisArray(Aplain))[1]
144+
@test axes(Aplain, 2) === axes(AxisArray(Aplain))[2]
141145

142146
@test Axis{:col}(1) == Axis{:col}(1)
143147
@test Axis{:col}(1) != Axis{:com}(1)

0 commit comments

Comments
 (0)