Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ArrayTable based on Dictionaries.jl #66

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
authors = ["Andy Ferris <ferris.andy@gmail.com>"]
name = "TypedTables"
uuid = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9"
version = "1.2.2"
version = "1.3.0"

[deps]
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
Expand All @@ -12,6 +13,7 @@ Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
julia = "1"
SplitApplyCombine = "1"
Tables = "1"
Dictionaries = "0.3.8"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
164 changes: 164 additions & 0 deletions src/ArrayTable.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
mutable struct ArrayTable{N, C <: AbstractDictionary{Symbol, <:AbstractArray{<:Any, N}}, I} <: AbstractArray{NamedTuple, N}
columns::C
indices::I

# Inner constructor, to compare axes?
@inline function ArrayTable{N, C, I}(columns::C, indices::I) where {N, C, I}
@boundscheck check_indices_match(columns, indices)
new(columns, indices)
end
end

function check_indices_match(columns, indices)
foreach(pairs(columns)) do (name, column)
if keys(column) !== indices
# TODO the keys print in long form...
throw(DimensionMismatch("Column $name has indices $(keys(column)), which does not match table indices $indices"))
end
end
end

Tables.columns(t::ArrayTable) = getfield(t, :columns)
_indices(t::ArrayTable) = getfield(t, :indices)

columnnames(t::ArrayTable) = keys(columns(t))

ArrayTable() = ArrayTable(Dictionary{Symbol, Vector}(), LinearIndices{1,Tuple{Base.OneTo(0)}})
@propagate_inbounds function ArrayTable(cols::AbstractDictionary{Symbol, <:AbstractArray{<:Any, N}}) where N
if isempty(cols)
if N == 1
inds = LinearIndices((0,))
else
inds = CartesianIndices(ntuple(_ -> 0, Val(N)))
end
else
inds = keys(first(cols))
end
return ArrayTable{N, typeof(cols), typeof(inds)}(cols, inds)
end

Base.IndexStyle(::ArrayTable{<:Any, <:Any, <:LinearIndices}) = Base.IndexLinear()
Base.IndexStyle(::ArrayTable{<:Any, <:Any, <:CartesianIndices}) = Base.IndexCartesian()

Base.axes(t::ArrayTable) = axes(_indices(t))
Base.keys(t::ArrayTable) = keys(_indices(t))
Base.length(t::ArrayTable) = length(_indices(t))
Base.size(t::ArrayTable) = length(_indices(t))

@propagate_inbounds Base.getproperty(t::ArrayTable, s::Symbol) = getindex(columns(t), s)

@inline function Base.getindex(t::ArrayTable{<:Any, C, <:LinearIndices}, i::Integer) where {C}
@boundscheck checkbounds(_indices(t), i)
return ArrayTableRow{Any, C, typeof(i)}(columns(t), i)
end

@inline function Base.getindex(t::ArrayTable{<:Any, C, <:CartesianIndices}, i::Integer...) where {C}
@boundscheck checkbounds(_indices(t), i)
return ArrayTableRow{Any, C, typeof(i)}(columns(t), i)
end

struct ArrayTableRow{T, C <: AbstractDictionary{Symbol, <:AbstractArray}, I} <: AbstractDictionary{Symbol, T}
columns::C
index::I
end

_columns(r::ArrayTableRow) = getfield(r, :columns)
_index(r::ArrayTableRow) = getfield(r, :index)

Dictionaries.keys(r::ArrayTableRow) = keys(_columns(r))

Dictionaries.isinsertable(::ArrayTableRow) = false
Dictionaries.issettable(r) = true # Should depend on array type and can vary from column to column?

Dictionaries.isassigned(r::ArrayTableRow, s::Symbol) = isassigned(_columns(r), s)
@propagate_inbounds function Dictionaries.getindex(r::ArrayTableRow, s::Symbol)
c = _columns(r)[s]
return @inbounds c[_index(r)]
end
@propagate_inbounds function Dictionaries.setindex!(r::ArrayTableRow{T}, value::T, s::Symbol) where {T}
c = _columns(r)[s]
return @inbounds c[_index(r)] = value
end

Dictionaries.istokenizable(r::ArrayTableRow) = istokenizable(_columns(r))
Dictionaries.gettoken(r::ArrayTableRow, s::Symbol) = gettoken(_columns(r), s)
Dictionaries.istokenassigned(r::ArrayTableRow, token) = istokenassigned(_columns(r), token)
Dictionaries.gettokenvalue(r::ArrayTableRow, token) = @inbounds gettokenvalue(_columns(r), token)[_index(r)]
Dictionaries.settokenvalue!(r::ArrayTableRow{T}, token, value::T) where {T} = @inbounds gettokenvalue(_columns(r), token)[_index(r)] = value

# show

Base.show(io::IO, ::MIME"text/plain", t::ArrayTable) = showtable(io, t)
Base.show(io::IO, t::ArrayTable) = showtable(io, t)

# Support Vector / deque interface (mutable-length vectors)

function Base.empty!(t::ArrayTable)
map(empty!, columns(t))
return t
end

function Base.pop!(t::ArrayTable)
return map(pop!, columns(t))
end

function Base.push!(t::ArrayTable, v::AbstractDictionary)
map(push!, columns(t), v)
return t
end

function Base.append!(t::ArrayTable, t2::AbstractVector)
map(append!, columns(t), columns(t2))
return t
end

function Base.popfirst!(t::ArrayTable)
return map(popfirst!, columns(t))
end

function Base.pushfirst!(t::ArrayTable, v::AbstractDictionary)
map(pushfirst!, columns(t), v)
return t
end

function Base.prepend!(t::ArrayTable, t2::AbstractVector)
map(prepend!, columns(t), columns(t2))
return t
end

function Base.deleteat!(t::ArrayTable, i)
map(col -> deleteat!(col, i), columns(t))
return t
end

function Base.insert!(t::ArrayTable, i::Integer, v::AbstractDictionary)
map((col, val) -> insert!(col, i, val), columns(t), v)
return t
end

function Base.splice!(t::ArrayTable, inds::Integer)
return map(col -> splice!(col, inds), columns(t))
end

function Base.splice!(t::ArrayTable, inds::AbstractArray)
cols = map(col -> splice!(col, inds), columns(t))
return @inbounds ArrayTable(cols)
end

function Base.splice!(t::ArrayTable, inds::Integer, ins::AbstractDictionary)
return map((col, vals) -> splice!(col, inds, vals), columns(t), ins)
end

function Base.splice!(t::ArrayTable, inds::AbstractArray, ins::AbstractDictionary)
cols = map((col, vals) -> splice!(col, inds, vals), columns(t), ins)
return @inbounds ArrayTable(cols)
end

function Base.splice!(t::ArrayTable, inds::Integer, ins::AbstractVector)
return map((col, vals) -> splice!(col, inds, vals), columns(t), columns(ins))
end

function Base.splice!(t::ArrayTable, inds::AbstractArray, ins::AbstractVector)
cols = map((col, vals) -> splice!(col, inds, vals), columns(t), columns(ins))
return @inbounds ArrayTable(cols)
end
6 changes: 4 additions & 2 deletions src/TypedTables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ module TypedTables
using Unicode
using Tables
using SplitApplyCombine
using Dictionaries

using Base: @propagate_inbounds, @pure, OneTo, Fix2
import Tables.columns, Tables.rows

export @Compute, @Select
export Table, FlexTable, columns, rows, columnnames, showtable
export Table, FlexTable, ArrayTable, columns, rows, columnnames, showtable

# Resultant element type of given column arrays
@generated function _eltypes(a::NamedTuple{names, T}) where {names, T <: Tuple{Vararg{AbstractArray}}}
Expand All @@ -19,7 +20,7 @@ export Table, FlexTable, columns, rows, columnnames, showtable
return NamedTuple{names, Tuple{Ts...}}
end

_ndims(a::NamedTuple{<:Any, T}) where {T} = _ndims(T)
_ndims(::NamedTuple{<:Any, T}) where {T} = _ndims(T)
_ndims(::Type{<:Tuple{Vararg{AbstractArray{<:Any, n}}}}) where {n} = n

# The following code causes newer versions of Julia to hang in precompilation
Expand All @@ -42,6 +43,7 @@ end
include("properties.jl")
include("Table.jl")
include("FlexTable.jl")
include("ArrayTable.jl")
include("columnops.jl")
include("show.jl")

Expand Down
2 changes: 1 addition & 1 deletion src/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ end

function showtable(io::IO, @nospecialize t)
row_inds = keys(t)
col_inds = columnnames(t)
col_inds = collect(columnnames(t))
nrows = length(row_inds)::Int
nrowstring = join(map(string, size(t)), "×")
ncols = length(col_inds)::Int
Expand Down