我想在 Julia 中连接匿名调用

I would like to concatenate anonymous calls in Julia

所以,我正在学习更多关于 Julia 的知识,我想做以下事情:

我有一个 3 行 2 列的矩阵,它是固定的, A = rand(2,3)

julia> A = rand(2,3)
2×3 Matrix{Float64}:
 0.705942  0.553562  0.731246
 0.205833  0.106978  0.131893

然后,我想要一个匿名函数,它执行以下操作:

a = ones(1,3);
a[2] = rand();

最后,我要广播 broadcast(+, ones(1,3) => a[2]=rand(), A)

所以我有 A 的中间列,即 A[:,2],加上两个 不同的 随机数,在其余列中,我们加一个。

编辑: 如果我按原样添加 a

julia> a = ones(1,3)
1×3 Matrix{Float64}:
 1.0  1.0  1.0

julia> a[2] = rand()
0.664824196431979

julia> a
1×3 Matrix{Float64}:
 1.0  0.664824  1.0

我希望这个 a 是动态的,并且是一个函数。

这样:

broadcast(+, a, A)

会给:

julia> broadcast(+, a, A)
2×3 Matrix{Float64}:
 1.70594  0.553562 + rand()  (correct)          1.73125
 1.20583  0.106970 + rand()  (different rand()) 1.13189

而不是:

julia> broadcast(+, a, A)
2×3 Matrix{Float64}:
 1.70594  1.21839  (0.553562 + -> 0.664824)   1.73125
 1.20583  0.771802 (0.106978 + -> 0.664824)   1.13189 

所以,我想到了这个伪代码

broadcast(+, a=ones(1,3) => a[2]=rand(), A)

形式化:

broadcast(+, <anonymous-fucntion>, A)

第二次编辑:

Rules/Constrains:

我同意你不太清楚你想要实现什么,即使你想学习的是如何实现某事或理论上某事是如何运作的。 如果您只想将一个随机向量添加到矩阵列(在其他地方添加 1 个),它就像...将一个随机向量添加到所需的矩阵列和在其他地方添加 1 个一样简单:

julia> A = rand(2,3)
2×3 Matrix{Float64}:
 0.94194   0.691855  0.583107
 0.198166  0.740017  0.914133

julia>  A[:,[1,3]] .+= 1
2×2 view(::Matrix{Float64}, :, [1, 3]) with eltype Float64:
 1.94194  1.58311
 1.19817  1.91413

julia> A[:,2] += rand(size(A,1))
2-element Vector{Float64}:
 1.0306116987831297
 0.8757712661515558

julia> A
2×3 Matrix{Float64}:
 1.94194  1.03061   1.58311
 1.19817  0.875771  1.91413

为什么不直接让 aA 大小一样,然后你甚至不需要广播或任何奇怪的技巧:

julia> A = rand(2,3)
2×3 Matrix{Float64}:
 0.564824  0.765611  0.841353
 0.566965  0.322331  0.109889

julia> a = ones(2,3);

julia> a[:, 2] .= [rand() for _ in 1:size(a, 1)] #in every row, make the second column's value a different rand() result

julia> a
2×3 Matrix{Float64}:
 1.0  0.519228   1.0
 1.0  0.0804104  1.0

julia> A + a
2×3 Matrix{Float64}:
 1.56482  1.28484   1.84135
 1.56696  0.402741  1.10989

首先,我想说其他答案中采用的方法是性能最高的方法。看起来你想要最后的整个矩阵,在这种情况下,为了获得最佳性能,通常最好大批量获取数据(如随机性)并且不要从编译器中“隐藏”数据(尤其是类型信息)。使用更高级别的抽象可以实现很多有趣的事情,但是既然你说性能很重要,那么让我们建立一个基线:

function approach1(A)
    a = ones(2,3)
    @. a[:, 2] = rand()
    broadcast(+, a, A)
end
julia> A = rand(2,3)                         
 2×3 Matrix{Float64}:                         
   0.199619   0.273481  0.99254                
   0.0927839  0.179071  0.188591                                                                  julia> @btime approach1($A)                    
   65.420 ns (2 allocations: 256 bytes)         
 2×3 Matrix{Float64}:                           
  1.19962  0.968391  1.99254                    
  1.09278  1.14451   1.18859

让我们尝试其他一些解决方案。

如果带有惰性元素的单行不算作辅助变量,这似乎是一个很好的起点:

function approach2(A)                           
    a = Matrix{Function}(undef, 1, 3)
    fill!(a, ()->1.0)
    a[2] = rand
    broadcast((a,b)->a() + b, a, A)                 
end   

我们得到一行 a = [()->1.0 rand ()->1.0] 并在广播获取该元素时评估每个函数。

julia> @btime approach2($A)
  1.264 μs (24 allocations: 960 bytes)

性能差了20倍,为什么?我们已经从编译器中隐藏了类型信息,它不能仅通过断言来判断 a()Float64(将最后一行更改为 broadcast((a,b)->a()::Float64 + b, a, A) 将性能提高近十倍:

julia> @btime approach2($A)                   
    164.108 ns (14 allocations: 432 bytes)

如果这是可以接受的,我们可以让它更干净:引入一个 LazyNumber 类型来跟踪 return 类型,并提升 rules/operators 这样我们就可以回到 broadcast(+, ...)。但是,我们还是慢了2-3倍,我们可以做得更好吗?

一种可以让我们挤出更多的方法是懒惰地表示整个数组。类似于 Fill 类型,LazySetItem 应用于矩阵之上。再次实际创建数组会更便宜,除非你可以避免获取数组的一部分

请注意,我不知道通过设置这样的人为规则你能真正学到多少,一些更简洁的方法是:

julia> A = [ 0.705942  0.553562  0.731246
             0.205833  0.106978  0.131893 ];  # as given

julia> r = 0.664824196431979;  # the one random number

julia> (A' .+ (1, r, 1))'  # no extra vector
2×3 adjoint(::Matrix{Float64}) with eltype Float64:
 1.70594  1.21839   1.73125
 1.20583  0.771802  1.13189

julia> mapslices(row -> row .+ (1, r, 1), A; dims=2)  # one line, but slow
2×3 Matrix{Float64}:
 1.70594  1.21839   1.73125
 1.20583  0.771802  1.13189

julia> B = A .+ 1; @views B[:, 2] .+= (-1 + r); B  # fast, no extra allocations
2×3 Matrix{Float64}:
 1.70594  1.21839   1.73125
 1.20583  0.771802  1.13189

从你的问题中我无法判断你是想要一个随机数还是两个不同的随机数。如果你想要两个,那么你可以这样做:

julia> using Random

julia> Random.seed!(1); mapslices(row -> row .+ (1, rand(), 1), A; dims=2)
2×3 Matrix{Float64}:
 1.70594  0.675436  1.73125
 1.20583  0.771383  1.13189

julia> Random.seed!(1); B = A .+ 1; @views B[:, 2] .+= (-1 .+ rand.()); B 
2×3 Matrix{Float64}:
 1.70594  0.675436  1.73125
 1.20583  0.771383  1.13189

注意 (-1 .+ rand.()) 并没有在右侧创建一个新数组,它被 .+= 融合到 B 列的一个循环中。另请注意,B[:,2] .= stuff 只是写入 B,但 B[:, 2] .+= stuff 表示 B[:, 2] .= B[:, 2] .+ stuff,因此,如果没有 @views,右侧的切片 B[:, 2] 将分配一个副本。