就地更新函数参数

时间:2013-10-21 15:14:37

标签: julia

我需要为可变数量的数组有效地实现笛卡尔积。

我已经尝试了product中的Iterators.jl功能,但性能不足。

我是一名蟒蛇黑客,并使用了sklearn中的this function并且使用它获得了良好的性能结果。

我曾尝试编写此函数的Julia版本,但无法生成与python函数相同的结果。

我的代码是:

function my_repeat(a, n)
    # mimics numpy.repeat
    m = size(a, 1)
    out = Array(eltype(a), n * m)
    out[1:n] = a[1]
    for i=2:m
        out[(i-1)*n+1:i*n] = a[i]
    end
    return out
end

function cartesian(arrs; out=None)
    dtype = eltype(arrs[1])

    n = prod([size(i, 1) for i in arrs])

    if is(out, None)
        out = Array(dtype, n, length(arrs))
    end

    m = int(n / size(arrs[1], 1))
    out[:, 1] = my_repeat(arrs[1], m)

    if length(arrs[2:]) > 0
        cartesian(arrs[2:], out=out[1:m, 2:])
        for j = 1:size(arrs[1], 1)-1
            out[(j*m + 1):(j+1)*m, 2:] = out[1:m, 2:]
        end
    end

    return out
end

我用以下方法测试它:

aa = ([1, 2, 3], [4, 5], [6, 7])
cartesian(aa)

返回值为:

12x3 Array{Float64,2}:
1.0  9.88131e-324  2.13149e-314
1.0  2.76235e-318  2.13149e-314
1.0  9.88131e-324  2.13676e-314
1.0  9.88131e-324  2.13676e-314
2.0  9.88131e-324  2.13149e-314
2.0  2.76235e-318  2.13149e-314
2.0  9.88131e-324  2.13676e-314
2.0  9.88131e-324  2.13676e-314
3.0  9.88131e-324  2.13149e-314
3.0  2.76235e-318  2.13149e-314
3.0  9.88131e-324  2.13676e-314
3.0  9.88131e-324  2.13676e-314

我认为这里的问题是当我使用这一行:cartesian(arrs[2:], out=out[1:m, 2:])时,关键字参数out不会在递归调用中就地更新。

可以看出,我对这个函数的Python版本进行了非常天真的翻译(参见上面的链接)。很可能存在内部语言差异使得无法进行天真的翻译。我不认为这是真的,因为julia文档的functions部分引用了这句话:

  

Julia函数参数遵循有时称为“pass-by-sharing”的约定,这意味着值在传递给函数时不会被复制。函数参数本身充当新的变量绑定(可以引用值的新位置),但它们引用的值与传递的值相同。调用者可以看到对函数内可变值(如数组)的修改。这与Scheme,大多数Lisps,Python,Ruby和Perl以及其他动态语言中的行为相同。

如何在Julia中使这个(或同等的)功能起作用?

3 个答案:

答案 0 :(得分:3)

Base中有一个repeat函数。

更短更快的变体可能会使用笛卡尔包中的@forcartesian宏:

using Cartesian

function cartprod(arrs, out=Array(eltype(arrs[1]), prod([length(a) for a in arrs]), length(arrs)))
    sz = Int[length(a) for a in arrs]
    narrs = length(arrs)
    @forcartesian I sz begin
        k = sub2ind(sz, I)
        for i = 1:narrs
            out[k,i] = arrs[i][I[i]]
        end
    end
    out
end

行的顺序与您的解决方案不同,但也许这无关紧要?

答案 1 :(得分:1)

我明白了。

这不是Julia没有更新函数参数的问题,而是使用切片操作符a[ind]的问题,它使得数据的副本,而不是通过引用索引我的数组。 multi dimensional array文档的这一部分得到了答案:

  

SubArray是AbstractArray的一个特化,它通过引用而不是通过复制来执行索引。使用子函数创建SubArray,其方式与getindex相同(带有数组和一系列索引参数)。 sub的结果看起来与getindex的结果相同,只是数据保留在原位。 sub将输入索引向量存储在SubArray对象中,该对象稍后可用于间接索引原始数组。

通过更改此行来解决此问题:

cartesian(arrs[2:], out=out[1:m, 2:])

以下内容:

out_end = size(out, 2)
cartesian(arrs[2:], out=sub(out, 1:m, 2:out_end))

答案 2 :(得分:0)

这是一个老问题,但随着 Julia 的进步,答案已经改变。

基本问题是像 a[1:3,:] 这样的切片进行复制。如果您在函数中更新该副本,它对 a 本身没有影响。

现代的答案是使用 @view a[1:3,:] 来获取对底层数组的一部分的引用。此视图的更新将反映在底层数组中。

您可以通过 @views 宏强制整个代码块使用视图语义。

有关更多讨论,请参阅 Inplace updating of function arguments