Skip to content
This repository was archived by the owner on May 4, 2019. It is now read-only.

[RFC/WIP] Few additional methods #155

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
24 changes: 16 additions & 8 deletions src/indexing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,21 @@ to store `v` at `I`.
return v
end

function unsafe_getindex_notnull(X::NullableArray, I::Int...)
# return the value of non-null X element wrapped in Nullable
@inline function unsafe_getindex_notnull(X::NullableArray, I::Int...)
return Nullable(getindex(X.values, I...))
end
@inline function unsafe_getindex_notnull{T}(X::AbstractArray{Nullable{T}}, I::Int...)
return getindex(X, I...)
end

function unsafe_getvalue_notnull(X::NullableArray, I::Int...)
# return the value of non-null X element
@inline function unsafe_getvalue_notnull(X::NullableArray, I::Int...)
return getindex(X.values, I...)
end
@inline function unsafe_getvalue_notnull{T}(X::AbstractArray{Nullable{T}}, I::Int...)
return get(getindex(X, I...))
end

if VERSION >= v"0.5.0-dev+4697"
function Base.checkindex(::Type{Bool}, inds::AbstractUnitRange, i::Nullable)
Expand All @@ -109,17 +117,17 @@ else
end

function Base.checkbounds(::Type{Bool}, sz::Int, I::NullableVector{Bool})
any(isnull, I) && throw(NullException())
any(isnull, I) && throw(NullException())
length(I) == sz
end
end

function Base.checkbounds{T<:Real}(::Type{Bool}, sz::Int, I::NullableArray{T})
inbounds = true
any(isnull, I) && throw(NullException())
for i in 1:length(I)
any(isnull, I) && throw(NullException())
for i in 1:length(I)
@inbounds v = unsafe_getvalue_notnull(I, i)
inbounds &= checkbounds(Bool, sz, v)
end
end
return inbounds
end
end
Expand All @@ -134,6 +142,6 @@ end

This is a convenience method to set the entry of `X` at index `I` to be null
"""
function nullify!(X::NullableArray, I...)
@inline function nullify!(X::NullableArray, I...)
setindex!(X, Nullable{eltype(X)}(), I...)
end
5 changes: 3 additions & 2 deletions src/primitives.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Base.isnull(X::NullableArray, I::Int...) = X.isnull[I...]
Base.values(X::NullableArray, I::Int...) = X.values[I...]
@inline Base.isnull(X::NullableArray, I::Int...) = X.isnull[I...]
@inline Base.isnull{T}(X::AbstractArray{Nullable{T}}, I::Int...) = isnull(X[I...]) # fallback method
@inline Base.values(X::NullableArray, I::Int...) = X.values[I...]

"""
size(X::NullableArray, [d::Real])
Expand Down
55 changes: 17 additions & 38 deletions src/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,33 +70,24 @@ mapreduce_impl_skipnull(f, op::typeof(@functorize(+)), X::NullableArray) =

# general mapreduce interface

function _mapreduce_skipnull{T}(f, op, X::NullableArray{T}, missingdata::Bool)
function _mapreduce_skipnull{T}(f, op, X::NullableArray{T})
n = length(X)
!missingdata && return Nullable(Base.mapreduce_impl(f, op, X.values, 1, n))

nnull = countnz(X.isnull)
nnull == n && return Base.mr_empty(f, op, T)
nnull == n - 1 && return Base.r_promote(op, f(X.values[findnext(x -> x == false), X, 1]))
# nnull == 0 && return Base.mapreduce_impl(f, op, X, 1, n)
# handle specific corner cases (no nulls, all nulls, one non-null)
nnull == 0 && return Nullable(Base.mapreduce_impl(f, op, X.values, 1, n))
nnull == n && return Nullable(Base.mr_empty(f, op, T))
@inbounds (nnull == n - 1 && return Nullable(Base.r_promote(op, f(X.values[findfirst(X.isnull, false)]))))

return mapreduce_impl_skipnull(f, op, X)
end

function Base._mapreduce(f, op, X::NullableArray, missingdata)
missingdata && return Base._mapreduce(f, op, X)
Nullable(Base._mapreduce(f, op, X.values))
end
Base._mapreduce(f, op, X::NullableArray, missingdata::Bool) =
missingdata ? Base._mapreduce(f, op, X) : Nullable(Base._mapreduce(f, op, X.values))

# to fix ambiguity warnings
function Base.mapreduce(f, op::Union{typeof(@functorize(&)), typeof(@functorize(|))},
X::NullableArray, skipnull::Bool = false)
missingdata = any(isnull, X)
if skipnull
return _mapreduce_skipnull(f, op, X, missingdata)
else
return Base._mapreduce(f, op, X, missingdata)
end
end
Base.mapreduce(f, op::Union{typeof(@functorize(&)), typeof(@functorize(|))},
X::NullableArray, skipnull::Bool = false) =
skipnull ? _mapreduce_skipnull(f, op, X) : Base._mapreduce(f, op, X, any(isnull, X))


if VERSION >= v"0.5.0-dev+3701"
Expand All @@ -115,28 +106,16 @@ 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 = any(isnull, X)
if skipnull
return _mapreduce_skipnull(f, specialized_binary(op),
X, missingdata)
else
return Base._mapreduce(f, specialized_binary(op), X, missingdata)
end
end
Base.mapreduce(f, op::Function, X::NullableArray; skipnull::Bool = false) =
skipnull ? _mapreduce_skipnull(f, specialized_binary(op), X) :
Base._mapreduce(f, specialized_binary(op), X, any(isnull, X))

function Base.mapreduce(f, op, X::NullableArray; skipnull::Bool = false)
missingdata = any(isnull, X)
if skipnull
return _mapreduce_skipnull(f, op, X, missingdata)
else
return Base._mapreduce(f, op, X, missingdata)
end
end
Base.mapreduce(f, op, X::NullableArray; skipnull::Bool = false) =
skipnull ? _mapreduce_skipnull(f, specialized_binary(op), X) :
Base._mapreduce(f, specialized_binary(op), X, any(isnull, X))

"""
mapreduce(f, op::Function, X::NullableArray; [skipnull::Bool=false])
reduce(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
Expand Down
46 changes: 31 additions & 15 deletions test/reduce.jl
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
module TestReduce
using NullableArrays
using Base.Test
import Base.mr_empty

srand(1)
f(x) = 5 * x
f{T<:Number}(x::Nullable{T}) = ifelse(isnull(x), Nullable{typeof(5 * x.value)}(),
Nullable(5 * x.value))
# FIXME should Base/NullableArrays handle this automatically?
Base.mr_empty(::typeof(f), op::typeof(+), T) = Base.r_promote(op, zero(T)::T)
Base.mr_empty(::typeof(f), op::typeof(*), T) = Base.r_promote(op, one(T)::T)

for N in (10, 2050)
srand(1)
for (N, allnull) in ((2, true), (2, false), (10, false), (2050, false))
A = rand(N)
M = rand(Bool, N)
i = rand(1:N)
M[i] = true
j = rand(1:N)
while j == i
j = rand(1:N)
if allnull
M = fill(true, N)
else
M = rand(Bool, N)
i = rand(1:N)
# should have at least one null and at least one non-null
M[i] = true
j = rand(1:(N-1))
(j == i) && (j += 1)
M[j] = false
end
M[j] = false
X = NullableArray(A)
Y = NullableArray(A, M)
B = A[find(x->!x, M)]
Expand All @@ -42,13 +49,22 @@ module TestReduce
@test isequal(method(X), Nullable(method(A)))
@test isequal(method(f, X), Nullable(method(f, A)))
@test isequal(method(Y), Nullable{Float64}())
v = method(Y, skipnull=true)
@test v.value ≈ method(B)
@test !isnull(v)
@test isequal(method(f, Y), Nullable{Float64}())
v = method(f, Y, skipnull=true)
@test v.value ≈ method(f, B)
@test !isnull(v)
# test skipnull=true
if !allnull || method ∈ [sum, prod]
# reduce
v_r = method(Y, skipnull=true)
@test v_r.value ≈ method(B)
@test !isnull(v_r)
# mapreduce
v_mr = method(f, Y, skipnull=true)
@test v_mr.value ≈ method(f, B)
@test !isnull(v_mr)
else
# reduction over empty collection not defined for these methods
@test_throws ArgumentError method(Y, skipnull=true)
@test_throws ArgumentError method(f, Y, skipnull=true)
end
end

@test isequal(extrema(X), (Nullable(minimum(A)), Nullable(maximum(A))))
Expand Down