我想在 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:调用必须是数据透明的。也就是说,
A
不能改变状态,就像我们调用 f.(A)
. 一样
- 规则二:不创建辅助变量(
a
不得存在)。在调用之前和之后唯一必须存在的向量是 A
.
- 规则3:
f.(A)
必须匿名;也就是说,您不能使用 define f
as function f(A) ... end
我同意你不太清楚你想要实现什么,即使你想学习的是如何实现某事或理论上某事是如何运作的。
如果您只想将一个随机向量添加到矩阵列(在其他地方添加 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
为什么不直接让 a
和 A
大小一样,然后你甚至不需要广播或任何奇怪的技巧:
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]
将分配一个副本。
所以,我正在学习更多关于 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:调用必须是数据透明的。也就是说,
A
不能改变状态,就像我们调用f.(A)
. 一样
- 规则二:不创建辅助变量(
a
不得存在)。在调用之前和之后唯一必须存在的向量是A
. - 规则3:
f.(A)
必须匿名;也就是说,您不能使用 definef
asfunction f(A) ... end
我同意你不太清楚你想要实现什么,即使你想学习的是如何实现某事或理论上某事是如何运作的。 如果您只想将一个随机向量添加到矩阵列(在其他地方添加 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
为什么不直接让 a
和 A
大小一样,然后你甚至不需要广播或任何奇怪的技巧:
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]
将分配一个副本。