2017-05-02 29 views
4

下面的代碼(對不起,我不記得在哪裏從複製它)計算兩個列表的笛卡爾(或外)產品,它可以是不同類型的:如何計算n個不同類型列表的笛卡爾乘積?

let outer2 xs ys = 
    xs |> List.collect (fun x -> ys |> List.map (fun y -> x, y)) 

從這一個可以編寫一個函數其計算兩個列表,它們是2元組的元素的外積:

let outerTup tup = outer2 (fst tup) (snd tup) 

不難將其擴展到包含三個列表的元組的情況下。然而,我找不到一種方法來編寫一個函數,該函數將採用元素爲列表的任何長度的元組(可能具有不同類型)並計算列表的笛卡爾乘積。

在SO和F#Snippets中,對於所有列表具有相同類型(在這種情況下參數是列表列表)的問題,有幾種解決方案。但是我還沒有看到一個列表是不同類型的例子。

有什麼建議嗎?

+2

其實是有一個'List.allPairs'功能F#4.1爲兩個列表執行此操作。我不認爲你可以開箱即用,但你可能想看看這個[Combinatorics library](https://www.nuget.org/packages/Combinatorics)。它可能仍然沒有做你想要的,雖然... – s952163

+2

當你發現自己在查看可變大小的元組時,你很可能會將它們與列表混淆。 –

回答

3

從理論上講,你不能完全按照你想要做的做,但你可以非常接近它。你無法創建這樣的功能的原因是因爲靜態類型。

使用元組可以組合不同類型的值,但要確保類型安全,元組必須是固定大小的,並且必須知道每個元素的類型。

列表可以包含可變數量的元素,但正因爲如此,每個元素的類型必須相同。否則,你不能用靜態類型語言來處理它。

在動態類型化語言中,例如,您可以創建一個包含列表(A)和另一個列表(B)的列表的單個函數。然後,將B中的每個元素添加到A中的每個列表中,然後完成。您也可以在靜態類型語言中使用兩種方法:

  1. 您首先將列表中的每個元素轉換爲object
  2. 您首先創建每種類型的歧視聯盟。

第一個想法意味着你需要大量的向下向上投射,這通常不是你想要的靜態類型語言。第二種方法可行,但您必須將每個列表轉換爲您的DU類型(您還需要創建DU),稍後您需要進行模式匹配。從技術上講,它只與一種更安全的方式相同。

另一種方法,那就是我推薦的應用程序的用法。一個應用程序實際上意味着你升級一個函數,所以函數的每個參數可以是一個選項,列表等。所以,你首先要創建一個apply功能是這樣的:

let apply fs xs = [ 
    for f in fs do 
    for x in xs do 
     yield f x 
] 
let (<*>) = apply 

一旦你擁有這樣的功能,你可以寫這樣的事情:

[fun a b c d -> (a,b,c,d)] 
    <*> [1..5] 
    <*> ["a";"b"] 
    <*> [(0,0);(1,1)] 
    <*> [100;200] 

這則返回一個包含列表:

[(1, "a", (0, 0), 100); (1, "a", (0, 0), 200); (1, "a", (1, 1), 100); 
(1, "a", (1, 1), 200); (1, "b", (0, 0), 100); (1, "b", (0, 0), 200); 
(1, "b", (1, 1), 100); (1, "b", (1, 1), 200); (2, "a", (0, 0), 100); 
(2, "a", (0, 0), 200); (2, "a", (1, 1), 100); (2, "a", (1, 1), 200); 
(2, "b", (0, 0), 100); (2, "b", (0, 0), 200); (2, "b", (1, 1), 100); 
(2, "b", (1, 1), 200); (3, "a", (0, 0), 100); (3, "a", (0, 0), 200); 
(3, "a", (1, 1), 100); (3, "a", (1, 1), 200); (3, "b", (0, 0), 100); 
(3, "b", (0, 0), 200); (3, "b", (1, 1), 100); (3, "b", (1, 1), 200); 
(4, "a", (0, 0), 100); (4, "a", (0, 0), 200); (4, "a", (1, 1), 100); 
(4, "a", (1, 1), 200); (4, "b", (0, 0), 100); (4, "b", (0, 0), 200); 
(4, "b", (1, 1), 100); (4, "b", (1, 1), 200); (5, "a", (0, 0), 100); 
(5, "a", (0, 0), 200); (5, "a", (1, 1), 100); (5, "a", (1, 1), 200); 
(5, "b", (0, 0), 100); (5, "b", (0, 0), 200); (5, "b", (1, 1), 100); 
(5, "b", (1, 1), 200)] 

如果您不想創建運營商<*>您還可以編寫:

[fun a b c d -> (a,b,c,d)] 
    |> apply <| [1..5] 
    |> apply <| ["a";"b"] 
    |> apply <| [(0,0);(1,1)] 
    |> apply <| [100;200] 

但我通常不鼓勵使用<|。我更喜歡這個:

let ap xs fs = [ 
    for f in fs do 
    for x in xs do 
     yield f x 
] 

[fun a b c d -> (a,b,c,d)] 
    |> ap [1..5] 
    |> ap ["a";"b"] 
    |> ap [(0,0);(1,1)] 
    |> ap [100;200] 

你必須在飛行中創建的唯一一件事是第一線。一個將4,5,6,...個參數映射到元組的函數。

如果你想知道更多關於Applicatives,它是怎麼工作的,我已經寫了關於這個議題二博客,帖子:

http://sidburn.github.io/blog/2016/04/13/applicative-list http://sidburn.github.io/blog/2016/03/31/applicative-functors

2

這不是n的解決方案。使用FSharp反射命名空間通常是不可取的。

let outer2 xs ys = 
    xs |> List.collect (fun x -> List.map (fun y -> x, y) ys) 

let outerTup2 (a,b) = outer2 a b 

let outerTup3 (a,b,c) = 
    a 
    |> outer2 b 
    |> outer2 c 
    |> List.map (fun (c,(b,a))->a,b,c) 

let outerTup4 (a,b,c,d) = 
    a 
    |> outer2 b 
    |> outer2 c 
    |> outer2 d 
    |> List.map (fun (d,(c,(b,a)))->a,b,c,d) 

// etc... 

outerTup2 ([1;2],[3;4]) 

outerTup3 ([1;2],[3;4],[5;6]) 

outerTup4 ([1;2],[3;4],[5;6],[7;8]) 
2

根據該StackOverflow的問題,這可能是不可能的寫一個函數,它在任何長度作爲參數的一個元組。

Variable length tuples in f#

這個問題是問在很久以前,我不知道,如果F#有,它使任何更新和變化。