diff --git a/.gitignore b/.gitignore index 2251642..c7e6298 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -Manifest.toml \ No newline at end of file +Manifest.toml +.vscode diff --git a/docs/src/index.md b/docs/src/index.md index 50e066f..f113fb7 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -197,9 +197,11 @@ the table-specific use-case, knowing that it will Just Work™️. Before moving on to _implementing_ the Tables.jl interfaces, we take a quick break to highlight some useful utility functions provided by Tables.jl: + ```@docs Tables.Schema Tables.schema +Tables.getrows Tables.partitions Tables.partitioner Tables.rowtable @@ -239,6 +241,7 @@ For a type `MyTable`, the interface to becoming a proper table is straightforwar | **Optional methods** | | | | `Tables.schema(x::MyTable)` | `Tables.schema(x) = nothing` | Return a [`Tables.Schema`](@ref) object from your `Tables.AbstractRow` iterator or `Tables.AbstractColumns` object; or `nothing` for unknown schema | | `Tables.materializer(::Type{MyTable})` | `Tables.columntable` | Declare a "materializer" sink function for your table type that can construct an instance of your type from any Tables.jl input | +| `Tables.getrows(x::MyTable, inds; view)` | | Return a row or a sub-table of the original table Based on whether your table type has defined `Tables.rows` or `Tables.columns`, you then ensure that the `Tables.AbstractRow` iterator or `Tables.AbstractColumns` object satisfies the respective interface. diff --git a/src/Tables.jl b/src/Tables.jl index 6f39462..b4848ef 100644 --- a/src/Tables.jl +++ b/src/Tables.jl @@ -565,6 +565,31 @@ struct Partitioner{T} x::T end +""" + Tables.getrows(x, inds; view=nothing) + +Return one or more rows from table `x` according to the position(s) specified by `inds`: + +- If `inds` is a single non-boolean integer return a row object. +- If `inds` is a vector of non-boolean integers, a vector of booleans, or a `:`, return an indexable object of rows. + In this case, the returned type is not necessarily the same as the original table type. + +If other type of `inds` is passed than specified above the behavior is undefined. + +The `view` argument influences whether the returned object is a view of the original table +or an independent copy: + +- If `view=nothing` (the default) then the implementation for a specific table type + is free to decide whether to return a copy or a view. +- If `view=true` then a view is returned and if `view=false` a copy is returned. + This applies both to returning a row or a table. + +Any specialized implementation of `getrows` must support the `view=nothing` argument. +Support for `view=true` or `view=false` is optional +(i.e. implementations might error on them if they are not supported). +""" +function getrows end + """ Tables.partitioner(f, itr) Tables.partitioner(x) diff --git a/src/namedtuples.jl b/src/namedtuples.jl index 7b2f3e5..7ac24fd 100644 --- a/src/namedtuples.jl +++ b/src/namedtuples.jl @@ -106,6 +106,14 @@ function rowtable(itr::T) where {T} return collect(namedtupleiterator(eltype(r), r)) end +function getrows(x::RowTable, inds; view::Union{Bool,Nothing} = nothing) + if view === true + return Base.view(x, inds) + else + return x[inds] + end +end + # NamedTuple of arrays of matching dimensionality const ColumnTable = NamedTuple{names, T} where {names, T <: NTuple{N, AbstractArray{S, D} where S}} where {N, D} rowcount(c::ColumnTable) = length(c) == 0 ? 0 : length(c[1]) @@ -173,3 +181,11 @@ function columntable(itr::T) where {T} return columntable(schema(cols), cols) end columntable(x::ColumnTable) = x + +function getrows(x::ColumnTable, inds; view::Union{Bool,Nothing} = nothing) + if view === true + return map(c -> Base.view(c, inds), x) + else + return map(c -> c[inds], x) + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 88e5874..f45d6ee 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -145,6 +145,34 @@ end @test Tables.buildcolumns(nothing, rt) == nt @test Tables.columntable(nothing, nt) == nt + @testset "columntable getrows" begin + @test Tables.getrows(nt, 1) == (a=1, b=4.0, c="7") + @test Tables.getrows(nt, 1, view=false) == (a=1, b=4.0, c="7") + @test Tables.getrows(nt, 1, view=nothing) == (a=1, b=4.0, c="7") + @test Tables.getrows(nt, 1:2) == (a=[1,2], b=[4.0, 5.0], c=["7","8"]) + @test Tables.getrows(nt, 1:2, view=false) == (a=[1,2], b=[4.0, 5.0], c=["7","8"]) + @test Tables.getrows(nt, 1:2, view=nothing) == (a=[1,2], b=[4.0, 5.0], c=["7","8"]) + + @test Tables.getrows(nt, 1, view=true) == (a = fill(1), b = fill(4.0), c = fill("7")) + rs = Tables.getrows(nt, 1:2, view=true) + @test rs == (a=[1,2], b=[4.0, 5.0], c=["7","8"]) + @test rs.a.parent === nt.a + end + + @testset "rowtable getrows" begin + @test Tables.getrows(rt, 1) == (a=1, b=4.0, c="7") + @test Tables.getrows(rt, 1, view=false) == (a=1, b=4.0, c="7") + @test Tables.getrows(rt, 1, view=nothing) == (a=1, b=4.0, c="7") + @test Tables.getrows(rt, 1:2) == [(a=1, b=4.0, c="7"), (a=2, b=5.0, c="8")] + @test Tables.getrows(rt, 1:2, view=false) == [(a=1, b=4.0, c="7"), (a=2, b=5.0, c="8")] + @test Tables.getrows(rt, 1:2, view=nothing) == [(a=1, b=4.0, c="7"), (a=2, b=5.0, c="8")] + + @test Tables.getrows(rt, 1, view=true) == fill((a = 1, b = 4.0, c = "7")) + rs = Tables.getrows(rt, 1:2, view=true) + @test rs == [(a=1, b=4.0, c="7"), (a=2, b=5.0, c="8")] + @test rs.parent === rt + end + # test push! rtf = Iterators.Filter(x->x.a >= 1, rt) @test Tables.columntable(rtf) == nt @@ -798,4 +826,5 @@ Tables.columnnames(::WideTable2) = [Symbol("x", i) for i = 1:1000] @test nm isa Symbol @test col isa Vector{Float64} end + end