From fcc95298ce20816406282b421a00c430bf4b2499 Mon Sep 17 00:00:00 2001 From: David Gold Date: Mon, 14 Sep 2015 15:11:41 -0700 Subject: [PATCH] Add in-line docstring documentation This commit also removes design notes and code section delineators in preparation for a release version. --- src/broadcast.jl | 31 +++++-- src/indexing.jl | 43 ++++++++-- src/map.jl | 18 +++- src/nullablevector.jl | 120 +++++++++++++++++++++++--- src/primitives.jl | 193 +++++++++++++++++++++++++++++------------- src/reduce.jl | 30 +++++-- src/subarray.jl | 1 - 7 files changed, 341 insertions(+), 95 deletions(-) diff --git a/src/broadcast.jl b/src/broadcast.jl index 7df9cda..2475f5e 100644 --- a/src/broadcast.jl +++ b/src/broadcast.jl @@ -60,6 +60,18 @@ function Base.broadcast!(f, X::NullableArray; lift::Bool=false) end @eval let cache = Dict{Any, Dict{Bool, Dict{Int, Dict{Int, Any}}}}() + @doc """ + `broadcast!(f, B::NullableArray, As::NullableArray...; lift::Bool=false)` + + This method implements the same behavior as that of `broadcast!` when called on + regular `Array` arguments. It also includes the `lift` keyword argument, which + when set to true will lift `f` over the entries of the `As`. Lifting is + disabled by default. Note that this method's signature specifies the destination + `B` array as well as the source `As` arrays as all `NullableArray`s. + Thus, calling `broadcast!` on a arguments consisting of both `Array`s and + `NullableArray`s will fall back to the implementation of `broadcast!` in + `base/broadcast.jl`. + """ -> function Base.broadcast!(f, B::NullableArray, As::NullableArray...; lift::Bool=false) nd = ndims(B) narrays = length(As) @@ -74,19 +86,24 @@ end end end # let cache +@doc """ +`broadcast(f, As::NullableArray...;lift::Bool=false)` + +This method implements the same behavior as that of `broadcast` when called on +regular `Array` arguments. It also includes the `lift` keyword argument, which +when set to true will lift `f` over the entries of the `As`. Lifting is +disabled by default. Note that this method's signature specifies the source +`As` arrays as all `NullableArray`s. Thus, calling `broadcast!` on a arguments +consisting of both `Array`s and `NullableArray`s will fall back to the +implementation of `broadcast` in `base/broadcast.jl`. +""" -> function Base.broadcast(f, As::NullableArray...;lift::Bool=false) return broadcast!(f, NullableArray(eltype(Base.promote_eltype(As...)), Base.Broadcast.broadcast_shape(As...)), As...; lift=lift) end -#----- broadcasted binary operations -----------------------------------------# - -# The following require specialized implementations because the base/broadcast -# methods return BitArrays instead of similars of the arguments. -# An alternative to the following implementations is simply to let the base -# implementations use convert(::Type{Bool}, ::Nullable{Bool}), but this is -# slower. +# broadcasted ops for (op, scalar_op) in ( (:(Base.(:(.==))), :(==)), (:(Base.(:.!=)), :!=), diff --git a/src/indexing.jl b/src/indexing.jl index 6588955..b71b846 100644 --- a/src/indexing.jl +++ b/src/indexing.jl @@ -1,8 +1,6 @@ # NullableArray is dense and allows fast linear indexing. import Base: LinearFast -#----- GENERAL INDEXING METHODS ----------------------------------------------# - Base.linearindexing{T <: NullableArray}(::Type{T}) = LinearFast() # resolve ambiguity created by the two definitions that follow. @@ -10,6 +8,14 @@ function Base.getindex{T, N}(X::NullableArray{T, N}) return X end +@doc """ +`getindex{T, N}(X::NullableArray{T, N}, I::Int...)` + +Retrieve a single entry from a `NullableArray`. If the value in the entry +designated by `I` is present, then it will be returned wrapped in a +`Nullable{T}` container. If the value is missing, then this method returns +`Nullable{T}()`. +""" -> # Extract a scalar element from a `NullableArray`. @inline function Base.getindex{T, N}(X::NullableArray{T, N}, I::Int...) if isbits(T) @@ -23,6 +29,12 @@ end end end +@doc """ +`getindex{T, N}(X::NullableArray{T, N}, I::Nullable{Int}...)` + +Just as above, with the additional behavior that this method throws an error if +any component of the index `I` is null. +""" -> @inline function Base.getindex{T, N}(X::NullableArray{T, N}, I::Nullable{Int}...) anynull(I) && throw(NullException()) @@ -30,6 +42,15 @@ end return getindex(X, values...) end +@doc """ +`setindex!(X::NullableArray, v::Nullable, I::Int...)` + +Set the entry of `X` at position `I` equal to a `Nullable` value `v`. If +`v` is null, then only `X.isnull` is updated to indicate that the entry at +index `I` is null. If `v` is not null, then `X.isnull` is updated to indicate +that the entry at index `I` is present and `X.values` is updated to store the +value wrapped in `v`. +""" -> # Insert a scalar element from a `NullableArray` from a `Nullable` value. @inline function Base.setindex!(X::NullableArray, v::Nullable, I::Int...) if isnull(v) @@ -41,6 +62,13 @@ end return v end +@doc """ +`setindex!(X::NullableArray, v::Any, I::Int...)` + +Set the entry of `X` at position `I` equal to `v`. This method always updates +`X.isnull` to indicate that the entry at index `I` is present and `X.values` +to store `v` at `I`. +""" -> # Insert a scalar element from a `NullableArray` from a non-Nullable value. @inline function Base.setindex!(X::NullableArray, v::Any, I::Int...) X.values[I...] = v @@ -48,8 +76,6 @@ end return v end -#----- UNSAFE INDEXING METHODS -----------------------------------------------# - function unsafe_getindex_notnull(X::NullableArray, I::Int...) return Nullable(getindex(X.values, I...)) end @@ -58,8 +84,6 @@ function unsafe_getvalue_notnull(X::NullableArray, I::Int...) return getindex(X.values, I...) end -# ----- Base._checkbounds ----------------------------------------------------# - function Base.checkbounds{T<:Real}(::Type{Bool}, sz::Int, x::Nullable{T}) isnull(x) ? throw(NullException()) : checkbounds(Bool, sz, get(x)) end @@ -79,15 +103,16 @@ function Base.checkbounds{T<:Real}(::Type{Bool}, sz::Int, I::NullableArray{T}) return inbounds end -# ----- Base.to_index --------------------------------------------------------# - function Base.to_index(X::NullableArray) anynull(X) && throw(NullException()) Base.to_index(X.values) end -# ----- nullify! --------------------------------------------------------------# +@doc """ +`nullify!(X::NullableArray, I...)` +This is a convenience method to set the entry of `X` at index `I` to be null +""" -> function nullify!(X::NullableArray, I...) setindex!(X, Nullable{eltype(X)}(), I...) end diff --git a/src/map.jl b/src/map.jl index 9d2072d..25f33bf 100644 --- a/src/map.jl +++ b/src/map.jl @@ -1,7 +1,5 @@ using Base.Cartesian -# utilities - function gen_nullcheck(narrays::Int) As = [symbol("A_"*string(i)) for i = 1:narrays] e_nullcheck = :($(As[1]).isnull[i]) @@ -131,6 +129,14 @@ end # Base.map! @eval let cache = Dict{Bool, Dict{Int, Dict{Base.Callable, Function}}}() + @doc """ + `map!{F}(f::F, dest::NullableArray, As::AbstractArray...; lift::Bool=false)` + + This method implements the same behavior as that of `map!` when called on + regular `Array` arguments. It also includes the `lift` keyword argument, which + when set to true will lift `f` over the entries of the `As`. Lifting is + disabled by default. + """ -> function Base.map!{F}(f::F, dest::NullableArray, As::AbstractArray...; lift::Bool=false) narrays = length(As) @@ -148,6 +154,14 @@ Base.map!{F}(f::F, X::NullableArray; lift::Bool=false) = map!(f, X, X; lift=lift # Base.map @eval let cache = Dict{Int, Dict{Base.Callable, Function}}() + @doc """ + `map{F}(f::F, As::AbstractArray...; lift::Bool=false)` + + This method implements the same behavior as that of `map!` when called on + regular `Array` arguments. It also includes the `lift` keyword argument, which + when set to true will lift `f` over the entries of the `As`. Lifting is + disabled by default. + """ -> function Base.map{F}(f::F, As::NullableArray...; lift::Bool=false) narrays = length(As) _map_to! = gensym() diff --git a/src/nullablevector.jl b/src/nullablevector.jl index 8e4efb0..08512e3 100644 --- a/src/nullablevector.jl +++ b/src/nullablevector.jl @@ -1,16 +1,36 @@ -#----- head/tail -------------------------------------------------------------# +@doc """ +`head(X::NullableArray)` +Returns the first six elements of `X` as a `NullableArray`. If `X` contains +fewer than six elements, then this method returns `X`. +""" -> head(X::NullableArray) = X[1:min(6, length(X))] + +@doc """ +`tail(X::NullableArray)` + +Returns the last six elements of `X` as a `NullableArray`. If `X` contains +fewer than six elements, then this method returns `X`. +""" -> tail(X::NullableArray) = X[max(1, length(X) - 5):length(X)] -#----- Base.push! ------------------------------------------------------------# +@doc """ +`push!{T, V}(X::NullableVector{T}, v::V)` +Insert `v` at the end of `X`, which registers `v` as a present value. +""" -> function Base.push!{T, V}(X::NullableVector{T}, v::V) push!(X.values, v) push!(X.isnull, false) return X end +@doc """ +`push!{T, V}(X::NullableVector{T}, v::Nullable{V})` + +Insert a value at the end of `X` from a `Nullable` value `v`. If `v` is null +then this method adds a null entry at the end of `X`. Returns `X`. +""" -> function Base.push!{T, V}(X::NullableVector{T}, v::Nullable{V}) if v.isnull resize!(X.values, length(X.values) + 1) @@ -22,15 +42,23 @@ function Base.push!{T, V}(X::NullableVector{T}, v::Nullable{V}) return X end -#----- Base.pop! -------------------------------------------------------------# +@doc """ +`pop!{T}(X::NullableVector{T})` +Remove the last entry from `X` and return it. If the value in that entry is +missing, then this method returns `Nullable{T}()`. +""" -> function Base.pop!{T}(X::NullableVector{T}) # -> T val, isnull = pop!(X.values), pop!(X.isnull) isnull ? Nullable{T}() : Nullable(val) end -#----- Base.unshift! ---------------------------------------------------------# +@doc """ +`unshift!(X::NullableVector, v::Nullable)` +Insert a value at the beginning of `X` from a `Nullable` value `v`. If `v` is +null then this method inserts a null entry at the beginning of `X`. Returns `X`. +""" -> function Base.unshift!(X::NullableVector, v::Nullable) # -> NullableVector{T} if v.isnull ccall(:jl_array_grow_beg, Void, (Any, UInt), X.values, 1) @@ -42,18 +70,31 @@ function Base.unshift!(X::NullableVector, v::Nullable) # -> NullableVector{T} return X end +@doc """ +`unshift!(X::NullableVector, v::Nullable)` + +Insert a value `v` at the beginning of `X` and return `X`. +""" -> function Base.unshift!(X::NullableVector, v) # -> NullableVector{T} unshift!(X.values, v) unshift!(X.isnull, false) return X end +@doc """ +`unshift!(X::NullableVector, vs...)` + +Insert multiple values `vs` at the beginning of `X` and return `X`. +""" -> function Base.unshift!(X::NullableVector, vs...) return unshift!(unshift!(X, last(vs)), vs[1:endof(vs)-1]...) end -#----- Base.shift! -----------------------------------------------------------# +@doc """ +`shift!{T}(X::NullableVector{T})` +Remove the first entry from `X` and return it as a `Nullable` object. +""" -> function Base.shift!{T}(X::NullableVector{T}) # -> Nullable{T} val, isnull = shift!(X.values), shift!(X.isnull) if isnull @@ -63,10 +104,15 @@ function Base.shift!{T}(X::NullableVector{T}) # -> Nullable{T} end end -#----- Base.splice! ----------------------------------------------------------# - const _default_splice = [] +@doc """ +`splice!(X::NullableVector, i::Integer, [ins])` + +Remove the item at index `i` and return the removed item. Subsequent items +are shifted down to fill the resulting gap. If specified, replacement values from +an ordered collection will be spliced in place of the removed item. +""" -> function Base.splice!(X::NullableVector, i::Integer, ins=_default_splice) v = X[i] m = length(ins) @@ -85,6 +131,17 @@ function Base.splice!(X::NullableVector, i::Integer, ins=_default_splice) return v end +@doc """ +`splice!{T<:Integer}(X::NullableVector, rng::UnitRange{T}, [ins])` + +Remove items in the specified index range, and return a collection containing +the removed items. Subsequent items are shifted down to fill the resulting gap. +If specified, replacement values from an ordered collection will be spliced in +place of the removed items. + +To insert `ins` before an index `n` without removing any items, use +`splice!(X, n:n-1, ins)`. +""" -> function Base.splice!{T<:Integer}(X::NullableVector, rng::UnitRange{T}, ins=_default_splice) # -> @@ -127,16 +184,29 @@ function Base.splice!{T<:Integer}(X::NullableVector, return vs end -#----- Base.deleteat! ---------------------------------------------------------# +@doc """ +`deleteat!(X::NullableVector, inds)` +Delete the entry at `inds` from `X` and then return `X`. Note that `inds` may +be either a single scalar index or a collection of sorted, pairwise unique +indices. Subsequent items after deleted entries are shifted down to fill the +resulting gaps. +""" -> function Base.deleteat!(X::NullableVector, inds) deleteat!(X.values, inds) deleteat!(X.isnull, inds) return X end -#----- Base.append! ----------------------------------------------------------# +@doc """ +`append!(X::NullableVector, items::AbstractVector)` +Add the elements of `items` to the end of `X`. + +Note that `append!(X, [1, 2, 3])` is equivalent to `push!(X, 1, 2, 3)`, +where the items to be added to `X` are passed individually to `push!` and as a +collection to `append!`. +""" -> function Base.append!(X::NullableVector, items::AbstractVector) old_length = length(X) nitems = length(items) @@ -145,27 +215,46 @@ function Base.append!(X::NullableVector, items::AbstractVector) return X end -#----- Base.sizehint! --------------------------------------------------------# +@doc """ +`sizehint!(X::NullableVector, newsz::Integer)` +Suggest that collection `X` reserve capacity for at least `newsz` elements. +This can improve performance. +""" -> function Base.sizehint!(X::NullableVector, newsz::Integer) sizehint!(X.values, newsz) sizehint!(X.isnull, newsz) end -#----- padnull!/padnull ------------------------------------------------------# +@doc """ +`padnull!(X::NullableVector, front::Integer, back::Integer)` +Insert `front` null entries at the beginning of `X` and add `back` null entries +at the end of `X`. Returns `X`. +""" -> function padnull!{T}(X::NullableVector{T}, front::Integer, back::Integer) unshift!(X, fill(Nullable{T}(), front)...) append!(X, fill(Nullable{T}(), back)) return X end +@doc """ +`padnull(X::NullableVector, front::Integer, back::Integer)` + +return a copy of `X` with `front` null entries inserted at the beginning of +the copy and `back` null entries inserted at the end. +""" -> function padnull(X::NullableVector, front::Integer, back::Integer) return padnull!(copy(X), front, back) end -#----- Base.reverse/Base.reverse! --------------------------------------------# +@doc """ +`reverse!(X::NullableVector, [s], [n])` +Modify `X` by reversing the first `n` elements starting at index `s` +(inclusive). If unspecified, `s` and `n` will default to `1` and `length(X)`, +respectively. +""" -> function Base.reverse!(X::NullableVector, s=1, n=length(X)) if isbits(eltype(X)) || !anynull(X) reverse!(X.values, s, n) @@ -189,6 +278,13 @@ function Base.reverse!(X::NullableVector, s=1, n=length(X)) return X end +@doc """ +`reverse(X::NullableVector, [s], [n])` + +Return a copy of `X` with the first `n` elements starting at index `s` +(inclusive) reversed. If unspecified, `s` and `n` will default to `1` and +`length(X)`, respectively. +""" -> function Base.reverse(X::NullableVector, s=1, n=length(X)) return reverse!(copy(X), s, n) end diff --git a/src/primitives.jl b/src/primitives.jl index d0cf9e5..f8e81c3 100644 --- a/src/primitives.jl +++ b/src/primitives.jl @@ -1,25 +1,45 @@ -# ----- Base.size ------------------------------------------------------------# +Base.isnull(X::NullableArray, I::Int...) = X.isnull[I...] +Base.values(X::NullableArray, I::Int...) = X.values[I...] + +@doc """ +`size(X::NullableArray, [d::Real])` -# We determine the size of a NullableArray array using the size of its -# `values` field. We could equivalently use the `.isnull` field. +Return a tuple containing the lengths of each dimension of `X`, or if `d` is +specific, the length of `X` along dimension `d`. +""" -> Base.size(X::NullableArray) = size(X.values) # -> NTuple{Int} -# ----- Base.similar ---------------------------------------------------------# +@doc """ +`similar(X::NullableArray, [T], [dims])` -# Allocate a similar array -function Base.similar{T <: Nullable}(X::NullableArray, ::Type{T}, dims::Dims) +Allocate an uninitialized `NullableArray` of element type `T` and with +size `dims`. If unspecified, `T` and `dims` default to the element type and size +equal to that of `X`. +""" -> +function Base.similar{T<:Nullable}(X::NullableArray, ::Type{T}, dims::Dims) NullableArray(eltype(T), dims) end -# Allocate a similar array +# @doc """ +# +# """ -> Base.similar(X::NullableArray, T, dims::Dims) = NullableArray(T, dims) -# ----- Base.copy/copy! ------------------------------------------------------# +@doc """ +`copy(X::NullableArray)` +Return a shallow copy of `X`; the outer structure of `X` will be copied, but +all elements will be identical to those of `X`. +""" -> Base.copy(X::NullableArray) = Base.copy!(similar(X), X) -# Copies the initialized values of a source NullableArray into the respective -# indices of the destination NullableArray. +@doc """ +`copy!(dest::NullableArray, src::NullableArray)` + +Copy the initialized values of a source NullableArray into the respective +indices of the destination NullableArray. If an entry in `src` is null, then +this method nullifies the respective entry in `dest`. +""" -> function Base.copy!(dest::NullableArray, src::NullableArray) # -> NullableArray{T, N} if isbits(eltype(dest)) && isbits(eltype(src)) @@ -28,8 +48,7 @@ function Base.copy!(dest::NullableArray, dest_values = dest.values src_values = src.values length(dest_values) >= length(src_values) || throw(BoundsError()) - # Copy only initilialized values from src into dest - # TODO: investigate "BoolArray.chunks" + # copy only initilialized values from src into dest for i in 1:length(src_values) @inbounds !(src.isnull[i]) && (dest.values[i] = src.values[i]) end @@ -38,8 +57,13 @@ function Base.copy!(dest::NullableArray, return dest end -# ----- Base.fill! -----------------------------------------------------------# +@doc """ +`fill!(X::NullableArray, x::Nullable)` +Fill `X` with the value `x`. If `x` is empty, then `fill!(X, x)` nullifies each +entry of `X`. Otherwise, `fill!(X, x)` fills `X.values` with the value of `x` +and designates each entry of `X` as present. +""" -> function Base.fill!(X::NullableArray, x::Nullable) # -> NullableArray{T, N} if isnull(x) fill!(X.isnull, true) @@ -50,21 +74,38 @@ function Base.fill!(X::NullableArray, x::Nullable) # -> NullableArray{T, N} return X end +@doc """ +`fill!(X::NullableArray, x::Nullable)` + +Fill `X` with the value `x` and designate each entry as present. If `x` is an +object reference, all elements will refer to the same object. Note that +`fill!(X, Foo())` will return `X` filled with the result of evaluating `Foo()` +once. +""" -> function Base.fill!(X::NullableArray, x::Any) # -> NullableArray{T, N} fill!(X.values, x) fill!(X.isnull, false) return X end -# ----- Base.deepcopy --------------------------------------------------------# +@doc """ +`Base.deepcopy(X::NullableArray)` +Return a `NullableArray` object whose internal `values` and `isnull` fields are +deep copies of `X.values` and `X.isnull` respectively. +""" -> function Base.deepcopy(X::NullableArray) # -> NullableArray{T} return NullableArray(deepcopy(X.values), deepcopy(X.isnull)) end -# ----- Base.resize! ---------------------------------------------------------# +@doc """ +`resize!(X::NullableVector, n::Int)` -function Base.resize!{T}(X::NullableArray{T,1}, n::Int) # -> NullableArray{T, N} +Resize a one-dimensional `NullableArray` `X` to contain precisely `n` elements. +If `n` is greater than the current length of `X`, then each new entry will be +designated as null. +""" -> +function Base.resize!{T}(X::NullableArray{T,1}, n::Int) # -> NullableArray{T, 1} resize!(X.values, n) oldn = length(X.isnull) resize!(X.isnull, n) @@ -72,46 +113,63 @@ function Base.resize!{T}(X::NullableArray{T,1}, n::Int) # -> NullableArray{T, N} return X end -# ----- Base.ndims -----------------------------------------------------------# +@doc """ +`ndims(X::NullableArray)` +Returns the number of dimensions of `X`. +""" -> Base.ndims(X::NullableArray) = ndims(X.values) # -> Int -# ----- Base.length ----------------------------------------------------------# +@doc """ +`length(X::NullableArray)` +Returns the maximum index `i` for which `getindex(X, i)` is valid. +""" -> Base.length(X::NullableArray) = length(X.values) # -> Int -# ----- Base.endof -----------------------------------------------------------# +@doc """ +`endof(X::NullableArray)` +Returns the last entry of `X`. +""" -> Base.endof(X::NullableArray) = endof(X.values) # -> Int -# ----- Base.find ------------------------------------------------------------# +@doc """ +""" -> function Base.find(X::NullableArray{Bool}) # -> Array{Int} ntrue = 0 @inbounds for (i, isnull) in enumerate(X.isnull) ntrue += !isnull && X.values[i] end - target = Array(Int, ntrue) + res = Array(Int, ntrue) ind = 1 @inbounds for (i, isnull) in enumerate(X.isnull) if !isnull && X.values[i] - target[ind] = i + res[ind] = i ind += 1 end end - return target + return res end -# TODO: implement further 'find' methods - -# ----- dropnull -------------------------------------------------------------# +@doc """ +`dropnull(X::NullableVector)` +Return a `Vector` containing only the non-null entries of `X`. +""" -> dropnull(X::NullableVector) = copy(X.values[!X.isnull]) # -> Vector{T} -# ----- anynull --------------------------------------------------------------# +@doc """ +`anynull(X::NullableArray)` +Returns whether or not any entries of `X` are null. +""" -> anynull(X::NullableArray) = any(X.isnull) # -> Bool +# @doc """ +# +# """ -> # NOTE: the following currently short-circuits. function anynull(A::AbstractArray) # -> Bool for a in A @@ -121,41 +179,69 @@ function anynull(A::AbstractArray) # -> Bool end return false end - +# +# @doc """ +# +# """ -> function anynull(xs::NTuple) # -> Bool return anynull(collect(xs)) end -# ----- allnull --------------------------------------------------------------# +@doc """ +`allnull(X::NullableArray)` +Returns whether or not all the entries in `X` are null. +""" -> allnull(X::NullableArray) = all(X.isnull) # -> Bool -# ----- Base.isnan -----------------------------------------------------------# +@doc """ +`isnan(X::NullableArray)` +Test whether each entry of `X` is null and if not, test whether the entry is +not a number (`NaN`). Return the results as `NullableArray{Bool}`. Note that +null entries of `X` will be reflected by null entries of the resultant +`NullableArray`. +""" -> function Base.isnan(X::NullableArray) # -> NullableArray{Bool} return NullableArray(isnan(X.values), copy(X.isnull)) end -# ----- Base.isfinite --------------------------------------------------------# +@doc """ +`isfinite(X::NullableArray)` +Test whether each entry of `X` is null and if not, test whether the entry is +finite. Return the results as `NullableArray{Bool}`. Note that +null entries of `X` will be reflected by null entries of the resultant +`NullableArray`. +""" -> function Base.isfinite(X::NullableArray) # -> NullableArray{Bool} - n = length(X) - target = Array(Bool, size(X)) - for i in 1:n + res = Array(Bool, size(X)) + for i in eachindex(X) if !X.isnull[i] - target[i] = isfinite(X.values[i]) + res[i] = isfinite(X.values[i]) end end - return NullableArray(target, copy(X.isnull)) + return NullableArray(res, copy(X.isnull)) end -# ----- Conversion methods ---------------------------------------------------# +@doc """ +`convert(T, X::NullableArray)` + +Convert `X` to an `AbstractArray` of type `T`. Note that if `X` contains any +null entries then calling `convert` without supplying a replacement value for +null entries will result in an error. + +Currently supported return type arguments include: `Array`, `Array{T}`, +`Vector`, `Matrix`. +`convert(T, X::NullableArray, replacement)` + +Convert `X` to an `AbstractArray` of type `T` and replace all null entries of +`X` with `replacement` in the result. +""" -> function Base.convert{S, T, N}(::Type{Array{S, N}}, X::NullableArray{T, N}) # -> Array{S, N} if anynull(X) - # err = "Cannot convert NullableArray with null values." - #TODO: Investigate constructors for NullException throw(NullException()) else return convert(Array{S, N}, X.values) @@ -186,15 +272,15 @@ function Base.convert{S, T, N}(::Type{Array{S, N}}, X::NullableArray{T, N}, replacement::Any) # -> Array{S, N} replacementS = convert(S, replacement) - target = Array(S, size(X)) + res = Array(S, size(X)) for i in 1:length(X) if X.isnull[i] - target[i] = replacementS + res[i] = replacementS else - target[i] = X.values[i] + res[i] = X.values[i] end end - return target + return res end function Base.convert{T}(::Type{Vector}, @@ -235,23 +321,14 @@ function Base.convert{S, T, N}(::Type{NullableArray{S, N}}, return NullableArray(convert(Array{S}, A.values), A.isnull) end +@doc """ +`float(X::NullableArray)` +Return a copy of `X` in which each non-null entry is converted to a floating +point type. Note that this method will throw an error for arguments `X` whose +element type is not "isbits". +""" -> function Base.float(X::NullableArray) # -> NullableArray{T, N} isbits(eltype(X)) || error() return NullableArray(float(X.values), copy(X.isnull)) end - -# ----- Base.hash ------------------------------------------------------------# - -# Use ready-made method for AbstractArrays or implement method specific to -# NullableArrays, possibly for performance purposes? - -# ----- Base.unique ----------------------------------------------------------# - -# Use ready-made method for AbstractArrays or implement method specific to -# NullableArrays, possibly for performance purposes? - -### - -Base.isnull(X::NullableArray, I::Int...) = X.isnull[I...] -Base.values(X::NullableArray, I::Int...) = X.values[I...] diff --git a/src/reduce.jl b/src/reduce.jl index 51c40bb..bb34c9c 100644 --- a/src/reduce.jl +++ b/src/reduce.jl @@ -1,4 +1,4 @@ -#----- Base.mapreduce interface for skipping null entries --------------------# +# interface for skipping null entries function skipnull_init(f, op, X::NullableArray, ifirst::Int, ilast::Int) @@ -60,7 +60,7 @@ mapreduce_impl_skipnull(f, op::Base.AddFun, X::NullableArray) = mapreduce_pairwise_impl_skipnull(f, op, X, 1, length(X.values), max(128, Base.sum_pairwise_blocksize(f))) -## general mapreduce interface +# general mapreduce interface function _mapreduce_skipnull{T}(f, op, X::NullableArray{T}, missingdata::Bool) n = length(X) @@ -79,6 +79,16 @@ function Base._mapreduce(f, op, X::NullableArray, missingdata) Nullable(Base._mapreduce(f, op, X.values)) end +@doc """ +`mapreduce(f, op::Function, X::NullableArray; [skipnull::Bool=false])` + +Map a function `f` over the elements of `X` and reduce the result under the +operation `op`. One can set the behavior of this method to skip the null entries +of `X` by setting the keyword argument `skipnull` equal to true. If `skipnull` +behavior is enabled, `f` will be automatically lifted over the elements of `X`. +Note that, in general, mapreducing over a `NullableArray` will return a +`Nullable` object regardless of whether `skipnull` is set to `true` or `false`. +""" -> function Base.mapreduce(f, op::Function, X::NullableArray; skipnull::Bool = false) missingdata = anynull(X) @@ -110,10 +120,20 @@ function Base.mapreduce(f, op, X::NullableArray; skipnull::Bool = false) end end +@doc """ +`mapreduce(f, op::Function, X::NullableArray; [skipnull::Bool=false])` + +Reduce `X`under the operation `op`. One can set the behavior of this method to +skip the null entries of `X` by setting the keyword argument `skipnull` equal +to true. If `skipnull` behavior is enabled, `f` will be automatically lifted +over the elements of `X`. Note that, in general, mapreducing over a +`NullableArray` will return a `Nullable` object regardless of whether `skipnull` +is set to `true` or `false`. +""" -> Base.reduce(op, X::NullableArray; skipnull::Bool = false) = mapreduce(Base.IdFun(), op, X; skipnull = skipnull) -#----- Standard reductions ---------------------------------------------------# +# standard reductions for (fn, op) in ((:(Base.sum), Base.AddFun()), (:(Base.prod), Base.MulFun()), @@ -135,9 +155,7 @@ for (fn, f, op) in ((:(Base.sumabs), Base.AbsFun(), Base.AddFun()), mapreduce($f, $op, X; skipnull=skipnull) end -#----- Base.minimum / Base.maximum -------------------------------------------# - -# internal methods +# internal methods for Base.minimum and Base.maximum for Op in (:(Base.MinFun), :(Base.MaxFun)) @eval begin function Base._mapreduce{T}(::Base.IdFun, ::$Op, diff --git a/src/subarray.jl b/src/subarray.jl index f2cb8bc..04c3d44 100644 --- a/src/subarray.jl +++ b/src/subarray.jl @@ -1,4 +1,3 @@ - const unsafe_getindex = Base.unsafe_getindex @generated function Base.isnull{T,N,P<:NullableArray,IV,LD}(V::SubArray{T,N,P,IV,LD}, I::Int...)