|
| 1 | +mutable struct ArrayTable{N, C <: AbstractDictionary{Symbol, <:AbstractArray{<:Any, N}}, I} <: AbstractArray{NamedTuple, N} |
| 2 | + columns::C |
| 3 | + indices::I |
| 4 | + |
| 5 | + # Inner constructor, to compare axes? |
| 6 | + @inline function ArrayTable{N, C, I}(columns::C, indices::I) where {N, C, I} |
| 7 | + @boundscheck check_indices_match(columns, indices) |
| 8 | + new(columns, indices) |
| 9 | + end |
| 10 | +end |
| 11 | + |
| 12 | +function check_indices_match(columns, indices) |
| 13 | + foreach(pairs(columns)) do (name, column) |
| 14 | + if keys(column) !== indices |
| 15 | + # TODO the keys print in long form... |
| 16 | + throw(DimensionMismatch("Column $name has indices $(keys(column)), which does not match table indices $indices")) |
| 17 | + end |
| 18 | + end |
| 19 | +end |
| 20 | + |
| 21 | +Tables.columns(t::ArrayTable) = getfield(t, :columns) |
| 22 | +_indices(t::ArrayTable) = getfield(t, :indices) |
| 23 | + |
| 24 | +columnnames(t::ArrayTable) = keys(columns(t)) |
| 25 | + |
| 26 | +ArrayTable() = ArrayTable(Dictionary{Symbol, Vector}(), LinearIndices{1,Tuple{Base.OneTo(0)}}) |
| 27 | +@propagate_inbounds function ArrayTable(cols::AbstractDictionary{Symbol, <:AbstractArray{<:Any, N}}) where N |
| 28 | + if isempty(cols) |
| 29 | + if N == 1 |
| 30 | + inds = LinearIndices((0,)) |
| 31 | + else |
| 32 | + inds = CartesianIndices(ntuple(_ -> 0, Val(N))) |
| 33 | + end |
| 34 | + else |
| 35 | + inds = keys(first(cols)) |
| 36 | + end |
| 37 | + return ArrayTable{N, typeof(cols), typeof(inds)}(cols, inds) |
| 38 | +end |
| 39 | + |
| 40 | +Base.IndexStyle(::ArrayTable{<:Any, <:Any, <:LinearIndices}) = Base.IndexLinear() |
| 41 | +Base.IndexStyle(::ArrayTable{<:Any, <:Any, <:CartesianIndices}) = Base.IndexCartesian() |
| 42 | + |
| 43 | +Base.axes(t::ArrayTable) = axes(_indices(t)) |
| 44 | +Base.keys(t::ArrayTable) = keys(_indices(t)) |
| 45 | +Base.length(t::ArrayTable) = length(_indices(t)) |
| 46 | +Base.size(t::ArrayTable) = length(_indices(t)) |
| 47 | + |
| 48 | +@propagate_inbounds Base.getproperty(t::ArrayTable, s::Symbol) = getindex(columns(t), s) |
| 49 | + |
| 50 | +@inline function Base.getindex(t::ArrayTable{<:Any, C, <:LinearIndices}, i::Integer) where {C} |
| 51 | + @boundscheck checkbounds(_indices(t), i) |
| 52 | + return ArrayTableRow{Any, C, typeof(i)}(columns(t), i) |
| 53 | +end |
| 54 | + |
| 55 | +@inline function Base.getindex(t::ArrayTable{<:Any, C, <:CartesianIndices}, i::Integer...) where {C} |
| 56 | + @boundscheck checkbounds(_indices(t), i) |
| 57 | + return ArrayTableRow{Any, C, typeof(i)}(columns(t), i) |
| 58 | +end |
| 59 | + |
| 60 | +struct ArrayTableRow{T, C <: AbstractDictionary{Symbol, <:AbstractArray}, I} <: AbstractDictionary{Symbol, T} |
| 61 | + columns::C |
| 62 | + index::I |
| 63 | +end |
| 64 | + |
| 65 | +_columns(r::ArrayTableRow) = getfield(r, :columns) |
| 66 | +_index(r::ArrayTableRow) = getfield(r, :index) |
| 67 | + |
| 68 | +Dictionaries.keys(r::ArrayTableRow) = keys(_columns(r)) |
| 69 | + |
| 70 | +Dictionaries.isinsertable(::ArrayTableRow) = false |
| 71 | +Dictionaries.issettable(r) = true # Should depend on array type and can vary from column to column? |
| 72 | + |
| 73 | +Dictionaries.isassigned(r::ArrayTableRow, s::Symbol) = isassigned(_columns(r), s) |
| 74 | +@propagate_inbounds function Dictionaries.getindex(r::ArrayTableRow, s::Symbol) |
| 75 | + c = _columns(r)[s] |
| 76 | + return @inbounds c[_index(r)] |
| 77 | +end |
| 78 | +@propagate_inbounds function Dictionaries.setindex!(r::ArrayTableRow{T}, value::T, s::Symbol) where {T} |
| 79 | + c = _columns(r)[s] |
| 80 | + return @inbounds c[_index(r)] = value |
| 81 | +end |
| 82 | + |
| 83 | +Dictionaries.istokenizable(r::ArrayTableRow) = istokenizable(_columns(r)) |
| 84 | +Dictionaries.gettoken(r::ArrayTableRow, s::Symbol) = gettoken(_columns(r), s) |
| 85 | +Dictionaries.istokenassigned(r::ArrayTableRow, token) = istokenassigned(_columns(r), token) |
| 86 | +Dictionaries.gettokenvalue(r::ArrayTableRow, token) = @inbounds gettokenvalue(_columns(r), token)[_index(r)] |
| 87 | +Dictionaries.settokenvalue!(r::ArrayTableRow{T}, token, value::T) where {T} = @inbounds gettokenvalue(_columns(r), token)[_index(r)] = value |
| 88 | + |
| 89 | +# show |
| 90 | + |
| 91 | +Base.show(io::IO, ::MIME"text/plain", t::ArrayTable) = showtable(io, t) |
| 92 | +Base.show(io::IO, t::ArrayTable) = showtable(io, t) |
| 93 | + |
| 94 | +# Support Vector / deque interface (mutable-length vectors) |
| 95 | + |
| 96 | +function Base.empty!(t::ArrayTable) |
| 97 | + map(empty!, columns(t)) |
| 98 | + return t |
| 99 | +end |
| 100 | + |
| 101 | +function Base.pop!(t::ArrayTable) |
| 102 | + return map(pop!, columns(t)) |
| 103 | +end |
| 104 | + |
| 105 | +function Base.push!(t::ArrayTable, v::AbstractDictionary) |
| 106 | + map(push!, columns(t), v) |
| 107 | + return t |
| 108 | +end |
| 109 | + |
| 110 | +function Base.append!(t::ArrayTable, t2::AbstractVector) |
| 111 | + map(append!, columns(t), columns(t2)) |
| 112 | + return t |
| 113 | +end |
| 114 | + |
| 115 | +function Base.popfirst!(t::ArrayTable) |
| 116 | + return map(popfirst!, columns(t)) |
| 117 | +end |
| 118 | + |
| 119 | +function Base.pushfirst!(t::ArrayTable, v::AbstractDictionary) |
| 120 | + map(pushfirst!, columns(t), v) |
| 121 | + return t |
| 122 | +end |
| 123 | + |
| 124 | +function Base.prepend!(t::ArrayTable, t2::AbstractVector) |
| 125 | + map(prepend!, columns(t), columns(t2)) |
| 126 | + return t |
| 127 | +end |
| 128 | + |
| 129 | +function Base.deleteat!(t::ArrayTable, i) |
| 130 | + map(col -> deleteat!(col, i), columns(t)) |
| 131 | + return t |
| 132 | +end |
| 133 | + |
| 134 | +function Base.insert!(t::ArrayTable, i::Integer, v::AbstractDictionary) |
| 135 | + map((col, val) -> insert!(col, i, val), columns(t), v) |
| 136 | + return t |
| 137 | +end |
| 138 | + |
| 139 | +function Base.splice!(t::ArrayTable, inds::Integer) |
| 140 | + return map(col -> splice!(col, inds), columns(t)) |
| 141 | +end |
| 142 | + |
| 143 | +function Base.splice!(t::ArrayTable, inds::AbstractArray) |
| 144 | + cols = map(col -> splice!(col, inds), columns(t)) |
| 145 | + return @inbounds ArrayTable(cols) |
| 146 | +end |
| 147 | + |
| 148 | +function Base.splice!(t::ArrayTable, inds::Integer, ins::AbstractDictionary) |
| 149 | + return map((col, vals) -> splice!(col, inds, vals), columns(t), ins) |
| 150 | +end |
| 151 | + |
| 152 | +function Base.splice!(t::ArrayTable, inds::AbstractArray, ins::AbstractDictionary) |
| 153 | + cols = map((col, vals) -> splice!(col, inds, vals), columns(t), ins) |
| 154 | + return @inbounds ArrayTable(cols) |
| 155 | +end |
| 156 | + |
| 157 | +function Base.splice!(t::ArrayTable, inds::Integer, ins::AbstractVector) |
| 158 | + return map((col, vals) -> splice!(col, inds, vals), columns(t), columns(ins)) |
| 159 | +end |
| 160 | + |
| 161 | +function Base.splice!(t::ArrayTable, inds::AbstractArray, ins::AbstractVector) |
| 162 | + cols = map((col, vals) -> splice!(col, inds, vals), columns(t), columns(ins)) |
| 163 | + return @inbounds ArrayTable(cols) |
| 164 | +end |
0 commit comments