|
1 |
| -using Base.Cartesian |
| 1 | +if VERSION < v"0.5.0-dev+3294" |
| 2 | + include("map0_4.jl") |
| 3 | +else |
| 4 | + using Base: collect_similar, Generator, ith_all |
| 5 | + |
| 6 | + macro nullcheck(Xs, nargs) |
| 7 | + res = :($(Xs)[1].isnull[i]) |
| 8 | + for i = 2:nargs |
| 9 | + e = :($(Xs)[$i].isnull[i]) |
| 10 | + res = Expr(:||, res, e) |
| 11 | + end |
| 12 | + return res |
| 13 | + end |
2 | 14 |
|
3 |
| -function gen_nullcheck(narrays::Int) |
4 |
| - As = [Symbol("A_"*string(i)) for i = 1:narrays] |
5 |
| - e_nullcheck = :($(As[1]).isnull[i]) |
6 |
| - for i = 2:narrays |
7 |
| - e_nullcheck = Expr(:||, e_nullcheck, :($(As[i]).isnull[i])) |
| 15 | + macro fcall(Xs, nargs) |
| 16 | + res = Expr(:call, :f) |
| 17 | + for i in 1:nargs |
| 18 | + push!(res.args, :($(Xs)[$i].values[i])) |
| 19 | + end |
| 20 | + return res |
8 | 21 | end
|
9 |
| - return e_nullcheck |
10 |
| -end |
11 | 22 |
|
12 |
| -function gen_map!_body{F}(narrays::Int, lift::Bool, f::F) |
13 |
| - _f = Expr(:quote, f) |
14 |
| - e_nullcheck = gen_nullcheck(narrays) |
15 |
| - if lift |
16 |
| - return quote |
17 |
| - for i in 1:length(dest) |
18 |
| - if $e_nullcheck |
| 23 | + # Base.map! |
| 24 | + |
| 25 | + Base.map!{F}(f::F, X::NullableArray; lift=false) = map!(f, X, X; lift=lift) |
| 26 | + function Base.map!{F}(f::F, dest::NullableArray, X::NullableArray; lift=false) |
| 27 | + if lift |
| 28 | + for (i, j) in zip(eachindex(dest), eachindex(X)) |
| 29 | + if X.isnull[j] |
19 | 30 | dest.isnull[i] = true
|
20 | 31 | else
|
21 |
| - dest[i] = $_f((@ntuple $narrays j->A_j.values[i])...) |
| 32 | + dest.isnull[i] = false |
| 33 | + dest.values[i] = f(X.values[j]) |
22 | 34 | end
|
23 | 35 | end
|
24 |
| - end |
25 |
| - else |
26 |
| - return quote |
27 |
| - for i in 1:length(dest) |
28 |
| - dest[i] = $_f((@ntuple $narrays j->A_j[i])...) |
| 36 | + else |
| 37 | + for (i, j) in zip(eachindex(dest), eachindex(X)) |
| 38 | + dest[i] = f(X[j]) |
29 | 39 | end
|
30 | 40 | end
|
| 41 | + return dest |
31 | 42 | end
|
32 |
| -end |
33 | 43 |
|
34 |
| -function gen_map_to!_body{F}(_map_to!::Symbol, narrays::Int, f::F) |
35 |
| - _f = Expr(:quote, f) |
36 |
| - e_nullcheck = gen_nullcheck(narrays) |
37 |
| - return quote |
38 |
| - @inbounds for i in offs:length(A_1) |
39 |
| - if lift |
40 |
| - if $e_nullcheck |
41 |
| - # we don't need to compute anything if A.isnull[i], since |
42 |
| - # the return type is specified by T and by the algorithm in |
43 |
| - # 'body' |
| 44 | + function Base.map!{F}(f::F, dest::NullableArray, X1::NullableArray, |
| 45 | + X2::NullableArray; lift=false) |
| 46 | + if lift |
| 47 | + for (i, j, k) in zip(eachindex(dest), eachindex(X1), eachindex(X2)) |
| 48 | + if X1.isnull[j] | X2.isnull[k] |
44 | 49 | dest.isnull[i] = true
|
45 |
| - continue |
46 | 50 | else
|
47 |
| - v = $_f((@ntuple $narrays j->A_j.values[i])...) |
| 51 | + dest.isnull[i] = false |
| 52 | + dest.values[i] = f(X1.values[j], X2.values[k]) |
48 | 53 | end
|
49 |
| - else |
50 |
| - v = $_f((@ntuple $narrays j->A_j[i])...) |
51 | 54 | end
|
52 |
| - S = typeof(v) |
53 |
| - if S !== T && !(S <: T) |
54 |
| - R = typejoin(T, S) |
55 |
| - new = similar(dest, R) |
56 |
| - copy!(new, 1, dest, 1, i - 1) |
57 |
| - new[i] = v |
58 |
| - return $(_map_to!)(new, i + 1, (@ntuple $narrays j->A_j)...; lift=lift) |
| 55 | + else |
| 56 | + for (i, j, k) in zip(eachindex(dest), eachindex(X1), eachindex(X2)) |
| 57 | + dest[i] = f(X1[j], X2[k]) |
59 | 58 | end
|
60 |
| - dest[i] = v::T |
61 | 59 | end
|
62 | 60 | return dest
|
63 | 61 | end
|
64 |
| -end |
65 | 62 |
|
66 |
| -function gen_map_body{F}(_map_to!::Symbol, narrays::Int, f::F) |
67 |
| - _f = Expr(:quote, f) |
68 |
| - e_nullcheck = gen_nullcheck(narrays) |
69 |
| - if narrays == 1 |
70 |
| - pre = quote |
71 |
| - isempty(A_1) && return isa(f, Type) ? similar(A_1, f) : similar(A_1) |
72 |
| - end |
73 |
| - else |
74 |
| - pre = quote |
75 |
| - shape = mapreduce(size, promote_shape, (@ntuple $narrays j->A_j)) |
76 |
| - prod(shape) == 0 && return similar(A_1, promote_type((@ntuple $narrays j->A_j)...), shape) |
| 63 | + function Base.map!{F}(f::F, dest::NullableArray, Xs::NullableArray...; lift=false) |
| 64 | + _map!(f, dest, Xs, lift) |
| 65 | + end |
| 66 | + |
| 67 | + @generated function _map!{F, N}(f::F, dest::NullableArray, Xs::Tuple{Vararg{NullableArray, N}}, lift) |
| 68 | + return quote |
| 69 | + if lift |
| 70 | + for i in linearindices(Xs[1]) |
| 71 | + if @nullcheck Xs $N |
| 72 | + dest.isnull[i] = true |
| 73 | + else |
| 74 | + dest.isnull[i] = false |
| 75 | + dest.values[i] = @fcall Xs $N |
| 76 | + end |
| 77 | + end |
| 78 | + else |
| 79 | + for i in linearindices(Xs[1]) |
| 80 | + dest[i] = f(ith_all(i, Xs)...) |
| 81 | + end |
| 82 | + end |
| 83 | + return dest |
77 | 84 | end
|
78 | 85 | end
|
79 |
| - return quote |
80 |
| - $pre |
| 86 | + |
| 87 | + # Base.map |
| 88 | + |
| 89 | + function Base.map(f, X::NullableArray; lift=false) |
| 90 | + lift ? _liftedmap(f, X) : collect_similar(X, Generator(f, X)) |
| 91 | + end |
| 92 | + function Base.map(f, X1::NullableArray, X2::NullableArray; lift=false) |
| 93 | + lift ? _liftedmap(f, X1, X2) : collect(Generator(f, X1, X2)) |
| 94 | + end |
| 95 | + function Base.map(f, Xs::NullableArray...; lift=false) |
| 96 | + lift ? _liftedmap(f, Xs...) : collect(Generator(f, Xs...)) |
| 97 | + end |
| 98 | + |
| 99 | + function _liftedmap(f, X::NullableArray) |
| 100 | + len = length(X) |
| 101 | + # if X is empty, fall back on type inference |
| 102 | + len > 0 || return NullableArray{Base.return_types(f, (eltype(X),)), 0}() |
81 | 103 | i = 1
|
82 |
| - # find first non-null entry in A_1, ... A_narrays |
83 |
| - if lift == true |
84 |
| - emptyel = $e_nullcheck |
85 |
| - while (emptyel && i < length(A_1)) |
86 |
| - i += 1 |
87 |
| - emptyel &= $e_nullcheck |
88 |
| - end |
89 |
| - # if all entries are null, return a similar |
90 |
| - i == length(A_1) && return isa(f, Type) ? similar(A_1, f) : similar(A_1) |
91 |
| - v = $_f((@ntuple $narrays j->A_j.values[i])...) |
92 |
| - else |
93 |
| - v = $_f((@ntuple $narrays j->A_j[i])...) |
| 104 | + while X.isnull[i] |
| 105 | + i += 1 |
94 | 106 | end
|
95 |
| - dest = similar(A_1, typeof(v)) |
| 107 | + # if X is all null, fall back on type inference |
| 108 | + i <= len || return similar(X, Base.return_types(f, (eltype(X),))) |
| 109 | + v = f(X.values[i]) |
| 110 | + dest = similar(X, typeof(v)) |
96 | 111 | dest[i] = v
|
97 |
| - return $(_map_to!)(dest, i + 1, (@ntuple $narrays j->A_j)...; lift=lift) |
| 112 | + map_to!(f, dest, X, i+1, len) |
98 | 113 | end
|
99 |
| -end |
100 | 114 |
|
101 |
| -function gen_map!_function{F}(narrays::Int, lift::Bool, f::F) |
102 |
| - As = [Symbol("A_"*string(i)) for i = 1:narrays] |
103 |
| - body = gen_map!_body(narrays, lift, f) |
104 |
| - @eval let |
105 |
| - local _F_ |
106 |
| - function _F_(dest, $(As...)) |
107 |
| - $body |
| 115 | + function _liftedmap(f, X1::NullableArray, X2::NullableArray) |
| 116 | + len = prod(promote_shape(X1, X2)) |
| 117 | + len > 0 || return NullableArray{Base.return_types(f, (eltype(X1), eltype(X2))), 0}() |
| 118 | + i = 1 |
| 119 | + while X1.isnull[i] | X2.isnull[i] |
| 120 | + i += 1 |
108 | 121 | end
|
109 |
| - _F_ |
| 122 | + i <= len || return similar(X1, Base.return_types(f, (eltype(X1), eltype(X2)))) |
| 123 | + v = f(X1.values[i], X2.values[i]) |
| 124 | + dest = similar(X1, typeof(v)) |
| 125 | + dest[i] = v |
| 126 | + map_to!(f, dest, X1, X2, i+1, len) |
110 | 127 | end
|
111 |
| -end |
112 |
| - |
113 |
| -function gen_map_function{F}(_map_to!::Symbol, narrays::Int, f::F) |
114 |
| - As = [Symbol("A_"*string(i)) for i = 1:narrays] |
115 |
| - body_map_to! = gen_map_to!_body(_map_to!, narrays, f) |
116 |
| - body_map = gen_map_body(_map_to!, narrays, f) |
117 | 128 |
|
118 |
| - @eval let $_map_to! # create a closure for subsequent calls to $_map_to! |
119 |
| - function $(_map_to!){T}(dest::NullableArray{T}, offs, $(As...); lift::Bool=false) |
120 |
| - $body_map_to! |
121 |
| - end |
122 |
| - local _F_ |
123 |
| - function _F_($(As...); lift::Bool=false) |
124 |
| - $body_map |
| 129 | + @generated function _liftedmap{N}(f, Xs::Vararg{NullableArray, N}) |
| 130 | + return quote |
| 131 | + shp = size(zip(Xs...)) |
| 132 | + len = prod(shp) |
| 133 | + i = 1 |
| 134 | + while @nullcheck Xs $N |
| 135 | + i += 1 |
| 136 | + end |
| 137 | + i <= len || return similar(X1, Base.return_types(f, tuple([ eltype(X) for X in Xs ]))) |
| 138 | + v = @fcall Xs $N |
| 139 | + dest = similar(Xs[1], typeof(v)) |
| 140 | + dest[i] = v |
| 141 | + map_to!(f, dest, Xs, i+1, len) |
125 | 142 | end
|
126 |
| - return _F_ |
127 |
| - end # let $_map_to! |
128 |
| -end |
129 |
| - |
130 |
| -# Base.map! |
131 |
| -@eval let cache = Dict{Bool, Dict{Int, Dict{Base.Callable, Function}}}() |
132 |
| - @doc """ |
133 |
| - `map!{F}(f::F, dest::NullableArray, As::AbstractArray...; lift::Bool=false)` |
134 |
| -
|
135 |
| - This method implements the same behavior as that of `map!` when called on |
136 |
| - regular `Array` arguments. It also includes the `lift` keyword argument, which |
137 |
| - when set to true will lift `f` over the entries of the `As`. Lifting is |
138 |
| - disabled by default. |
139 |
| - """ -> |
140 |
| - function Base.map!{F}(f::F, dest::NullableArray, As::AbstractArray...; |
141 |
| - lift::Bool=false) |
142 |
| - narrays = length(As) |
143 |
| - |
144 |
| - cache_lift = Base.@get! cache lift Dict{Int, Dict{Base.Callable, Function}}() |
145 |
| - cache_f = Base.@get! cache_lift narrays Dict{Base.Callable, Function}() |
146 |
| - func = Base.@get! cache_f f gen_map!_function(narrays, lift, f) |
| 143 | + end |
147 | 144 |
|
148 |
| - func(dest, As...) |
| 145 | + function map_to!{T}(f, dest::NullableArray{T}, X, offs, len) |
| 146 | + # map to dest array, checking the type of each result. if a result does not |
| 147 | + # match, widen the result type and re-dispatch. |
| 148 | + i = offs |
| 149 | + while i <= len |
| 150 | + @inbounds if X.isnull[i] |
| 151 | + i += 1; continue |
| 152 | + end |
| 153 | + @inbounds el = f(X.values[i]) |
| 154 | + S = typeof(el) |
| 155 | + if S === T || S <: T |
| 156 | + @inbounds dest[i] = el::T |
| 157 | + i += 1 |
| 158 | + else |
| 159 | + R = typejoin(T, S) |
| 160 | + new = similar(dest, R) |
| 161 | + copy!(new, 1, dest, 1, i-1) |
| 162 | + @inbounds new[i] = el |
| 163 | + return map_to!(f, new, X, i+1, len) |
| 164 | + end |
| 165 | + end |
149 | 166 | return dest
|
150 | 167 | end
|
151 |
| -end |
152 | 168 |
|
153 |
| -Base.map!{F}(f::F, X::NullableArray; lift::Bool=false) = map!(f, X, X; lift=lift) |
154 |
| - |
155 |
| -# Base.map |
156 |
| -@eval let cache = Dict{Int, Dict{Base.Callable, Function}}() |
157 |
| - @doc """ |
158 |
| - `map{F}(f::F, As::AbstractArray...; lift::Bool=false)` |
159 |
| -
|
160 |
| - This method implements the same behavior as that of `map!` when called on |
161 |
| - regular `Array` arguments. It also includes the `lift` keyword argument, which |
162 |
| - when set to true will lift `f` over the entries of the `As`. Lifting is |
163 |
| - disabled by default. |
164 |
| - """ -> |
165 |
| - function Base.map{F}(f::F, As::NullableArray...; lift::Bool=false) |
166 |
| - narrays = length(As) |
167 |
| - _map_to! = gensym() |
168 |
| - |
169 |
| - cache_fs = Base.@get! cache narrays Dict{Base.Callable, Function}() |
170 |
| - _map = Base.@get! cache_fs f gen_map_function(_map_to!, narrays, f) |
| 169 | + function map_to!{T}(f, dest::NullableArray{T}, X1, X2, offs, len) |
| 170 | + i = offs |
| 171 | + while i <= len |
| 172 | + @inbounds if X1.isnull[i] | X2.isnull[i] |
| 173 | + i += 1; continue |
| 174 | + end |
| 175 | + @inbounds el = f(X1.values[i], X2.values[i]) |
| 176 | + S = typeof(el) |
| 177 | + if S === T || S <: T |
| 178 | + @inbounds dest[i] = el::T |
| 179 | + i += 1 |
| 180 | + else |
| 181 | + R = typejoin(T, S) |
| 182 | + new = similar(dest, R) |
| 183 | + copy!(new, 1, dest, 1, i-1) |
| 184 | + @inbounds new[i] = el |
| 185 | + return map_to!(f, new, X1, X2, i+1, len) |
| 186 | + end |
| 187 | + end |
| 188 | + return dest |
| 189 | + end |
171 | 190 |
|
172 |
| - return _map(As...; lift=lift) |
| 191 | + @generated function map_to!{T, N}(f, dest::NullableArray{T}, Xs::Tuple{Vararg{NullableArray, N}}, offs, len) |
| 192 | + return quote |
| 193 | + i = offs |
| 194 | + while i <= len |
| 195 | + @inbounds if @nullcheck Xs $N |
| 196 | + i += 1; continue |
| 197 | + end |
| 198 | + @inbounds el = @fcall Xs $N |
| 199 | + S = typeof(el) |
| 200 | + if S === T || S <: T |
| 201 | + @inbounds dest[i] = el::T |
| 202 | + i += 1 |
| 203 | + else |
| 204 | + R = typejoin(T, S) |
| 205 | + new = similar(dest, R) |
| 206 | + copy!(new, 1, dest, 1, i-1) |
| 207 | + @inbounds new[i] = el |
| 208 | + return map_to!(f, new, Xs, i+1, len) |
| 209 | + end |
| 210 | + end |
| 211 | + return dest |
| 212 | + end |
173 | 213 | end
|
174 | 214 | end
|
0 commit comments