定义一个新的方法,只需要做一些改动
Define a new method with only a few changes
我想写一个接受补充参数的版本。与初始版本的区别仅在于几行代码,可能在循环中。一个典型的例子是使用一个权重为 w
的向量。
一个解决方案是完全重写一个新函数
function f(Vector::a)
...
for x in a
...
s += x[i]
...
end
...
end
function f(a::Vector, w::Vector)
...
for x in a
...
s += x[i] * w[i]
...
end
...
end
此解决方案重复代码,因此使程序更难维护。
我可以将 ...
拆分为不同的辅助函数,这两个函数都会调用它们,但是生成的代码很难理解
另一种解决方案是只编写一个函数并为应该更改的每一行使用 ? :
结构
function f(a, w::Union(Nothing, Vector) = nothing)
....
for x in a
...
s += (w == nothing)? x[i] : x[i] * w[i]
...
end
....
end
此代码需要在循环中的每一步检查条件,与第一个版本相比,这听起来效率不高。
我确信有更好的解决方案,也许是使用宏。处理这个问题的好方法是什么?
在这种情况下,使用 Optional Arguments 可能会奏效。
只需将 w
参数默认设置为 ones()
。
这个问题我遇到过几次。如果您想避免循环内的条件 if 语句,一种可能性是对某些虚拟类型使用多重分派。例如:
abstract MyFuncTypes
type FuncWithNoWeight <: MyFuncTypes; end
evaluate(x::Vector, i::Int, ::FuncWithNoWeight) = x[i]
type FuncWithWeight{T} <: MyFuncTypes
w::Vector{T}
end
evaluate(x::Vector, i::Int, wT::FuncWithWeight) = x[i] * wT.w[i]
function f(a, w::MyFuncTypes=FuncWithNoWeight())
....
for x in a
...
s += evaluate(x, i, w)
...
end
....
end
我将 evaluate
方法扩展到 FuncWithNoWeight
和 FuncWithWeight
以获得适当的行为。我还将这些类型嵌套在抽象类型 MyFuncTypes
中,这是 f
的第二个输入(默认值为 FuncWithNoWeight
)。从这里开始,多重分派和 Julia 的类型系统会处理剩下的事情。
这种方法的一个巧妙之处在于,如果您稍后决定要在循环内添加第三种行为(不一定是加权,几乎任何类型的转换都是可能的),它就像就像定义一个新类型一样简单,将其嵌套在 MyFuncTypes
下,并将 evaluate
方法扩展到新类型。
更新: 正如 Matt B. 所指出的,我的第一个版本的答案不小心将类型不稳定性引入了我的解决方案的函数中。作为一般规则,我通常会发现如果 Matt 发布了一些值得密切关注的内容(提示,提示,查看他的回答)。我仍在学习很多关于 Julia 的知识(并且正在 Whosebug 上回答问题以促进学习)。我更新了我的答案以消除 Matt 指出的类型不稳定性。
有很多方法可以做这种事情,从可选参数到自定义类型,再到使用 @eval
代码生成的元编程(这会在您循环时拼接每个新方法的更改在可能性列表上)。
我认为在这种情况下,我会结合使用@ColinTBowers 和@GnimucKey 建议的方法。
定义自定义数组类型相当简单 ones
:
immutable Ones{N} <: AbstractArray{Int,N}
dims::NTuple{N, Int}
end
Base.size(O::Ones) = O.dims
Base.getindex(O::Ones, I::Int...) = (checkbounds(O, I...); 1)
我选择使用 Int
作为元素类型,因为它往往能够很好地推广。现在您所需要的只是在参数列表中更加灵活一点,您就可以开始了:
function f(a::Vector, w::AbstractVector=Ones(size(a))
…
这应该比其他提议的解决方案有更低的开销; getindex
应该很好地内联作为边界检查和数字 1
,没有类型不稳定,您不需要重写您的算法。如果您确定所有访问都在边界内,您甚至可以删除边界检查作为额外的优化。或者在最近的 0.4 上,您可以定义和使用 Base.unsafe_getindex(O::Ones, I::Int...) = 1
(这在 0.3 上不太适用,因为不能保证为所有 AbstractArrays 定义)。
我想写一个接受补充参数的版本。与初始版本的区别仅在于几行代码,可能在循环中。一个典型的例子是使用一个权重为 w
的向量。
一个解决方案是完全重写一个新函数
function f(Vector::a)
...
for x in a
...
s += x[i]
...
end
...
end
function f(a::Vector, w::Vector)
...
for x in a
...
s += x[i] * w[i]
...
end
...
end
此解决方案重复代码,因此使程序更难维护。
我可以将 ...
拆分为不同的辅助函数,这两个函数都会调用它们,但是生成的代码很难理解
另一种解决方案是只编写一个函数并为应该更改的每一行使用 ? :
结构
function f(a, w::Union(Nothing, Vector) = nothing)
....
for x in a
...
s += (w == nothing)? x[i] : x[i] * w[i]
...
end
....
end
此代码需要在循环中的每一步检查条件,与第一个版本相比,这听起来效率不高。
我确信有更好的解决方案,也许是使用宏。处理这个问题的好方法是什么?
在这种情况下,使用 Optional Arguments 可能会奏效。
只需将 w
参数默认设置为 ones()
。
这个问题我遇到过几次。如果您想避免循环内的条件 if 语句,一种可能性是对某些虚拟类型使用多重分派。例如:
abstract MyFuncTypes
type FuncWithNoWeight <: MyFuncTypes; end
evaluate(x::Vector, i::Int, ::FuncWithNoWeight) = x[i]
type FuncWithWeight{T} <: MyFuncTypes
w::Vector{T}
end
evaluate(x::Vector, i::Int, wT::FuncWithWeight) = x[i] * wT.w[i]
function f(a, w::MyFuncTypes=FuncWithNoWeight())
....
for x in a
...
s += evaluate(x, i, w)
...
end
....
end
我将 evaluate
方法扩展到 FuncWithNoWeight
和 FuncWithWeight
以获得适当的行为。我还将这些类型嵌套在抽象类型 MyFuncTypes
中,这是 f
的第二个输入(默认值为 FuncWithNoWeight
)。从这里开始,多重分派和 Julia 的类型系统会处理剩下的事情。
这种方法的一个巧妙之处在于,如果您稍后决定要在循环内添加第三种行为(不一定是加权,几乎任何类型的转换都是可能的),它就像就像定义一个新类型一样简单,将其嵌套在 MyFuncTypes
下,并将 evaluate
方法扩展到新类型。
更新: 正如 Matt B. 所指出的,我的第一个版本的答案不小心将类型不稳定性引入了我的解决方案的函数中。作为一般规则,我通常会发现如果 Matt 发布了一些值得密切关注的内容(提示,提示,查看他的回答)。我仍在学习很多关于 Julia 的知识(并且正在 Whosebug 上回答问题以促进学习)。我更新了我的答案以消除 Matt 指出的类型不稳定性。
有很多方法可以做这种事情,从可选参数到自定义类型,再到使用 @eval
代码生成的元编程(这会在您循环时拼接每个新方法的更改在可能性列表上)。
我认为在这种情况下,我会结合使用@ColinTBowers 和@GnimucKey 建议的方法。
定义自定义数组类型相当简单 ones
:
immutable Ones{N} <: AbstractArray{Int,N}
dims::NTuple{N, Int}
end
Base.size(O::Ones) = O.dims
Base.getindex(O::Ones, I::Int...) = (checkbounds(O, I...); 1)
我选择使用 Int
作为元素类型,因为它往往能够很好地推广。现在您所需要的只是在参数列表中更加灵活一点,您就可以开始了:
function f(a::Vector, w::AbstractVector=Ones(size(a))
…
这应该比其他提议的解决方案有更低的开销; getindex
应该很好地内联作为边界检查和数字 1
,没有类型不稳定,您不需要重写您的算法。如果您确定所有访问都在边界内,您甚至可以删除边界检查作为额外的优化。或者在最近的 0.4 上,您可以定义和使用 Base.unsafe_getindex(O::Ones, I::Int...) = 1
(这在 0.3 上不太适用,因为不能保证为所有 AbstractArrays 定义)。