2013-10-21 17 views
2

我需要爲可變數量的數組有效實施笛卡爾乘積。函數參數的就地更新

我曾嘗試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版本的非常天真的翻譯(請參閱上面的鏈接)。很可能存在內部語言差異導致翻譯不可行的情況。我不認爲這是正確的,因爲從朱莉婭文檔的functions節本帖:

朱莉婭函數參數遵循有時也被稱爲公約「傳址共享」,這意味着值不會被複制當它們傳遞給函數時。函數參數本身作爲新的變量綁定(可以引用值的新位置),但它們引用的值與傳遞的值相同。對函數內的可變值(例如數組)的修改對調用者來說是可見的。這與Scheme中的行爲相同,大多數Lisp,Python,Ruby和Perl等動態語言。

我該如何使這個(或等效)函數在Julia中工作?

+0

由於'out'的類型不能被編譯器推斷(它可能是'None'或它可能是一個'Array'),無論你如何處理array-切片。一個更好的方法可能是避免第二個參數的關鍵字(只需將分號改爲逗號)並將其初始化爲Array(eltype(arrs [1]),prod([size(i,1) ]),長度(arrs))'。 – tholy

回答

1

我想通了。

它不是Julia沒有更新函數參數的問題,而是使用使用slice操作符a[ind]的問題,它會複製數據,而不是通過引用來索引我的數組。在multi dimensional array文檔的這一部分舉行了答案:

子陣列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)) 
+0

我認爲slice操作符會在未來的Julia版本中返回一個SubArray,但是這可能不會破壞這個代碼。 – ivarne

+0

很高興知道。感謝您的注意 – spencerlyon2

3

有一個在基地一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 

行的順序是不是你的解決方案不同,但也許這並不重要?

+0

這工作得很好。感謝你的回答。我只是檢查了它,笛卡爾包非常棒(儘管我需要花一些時間來圍繞如何使用這些宏)。是否可以進一步優化'out [k,i] = arrs [i] [I [i]]'以便不在這個最內層循環中執行4個切片操作? (這個想法來自於我的Python背景,在嚴格的內部循環中切片成本相當高) – spencerlyon2

+0

僅供參考,對於像cartesian([[1:j])函數的性能比該函數的性能差1.1-1.5倍,這取決於'i'和'j'的值。所以這是一個改進! – spencerlyon2

+0

然而,我並沒有想象的那麼好。如果您使用的是小陣列,那麼解決這個問題可能無關緊要。 – tholy