Skip to content

Commit 4f70614

Browse files
authored
Merge pull request #45 from JuliaArrays/teh/fallback_axes
Make `axes` provide defaults for AbstractArray
2 parents b449d1b + cbe52ce commit 4f70614

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)