Как рассчитать декартово произведение 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, есть несколько решений проблемы, когда все списки имеют одинаковый тип (в этом случае аргумент является списком списков). Но я не видел пример, когда списки бывают разных типов.
Какие-либо предложения?
3 ответа
Теоретически, вы не можете делать то, что хотите, но можете приблизиться к этому. Причина, по которой вы не можете создать такую функцию, заключается в статической типизации.
С кортежами вы можете комбинировать значения разных типов, но для обеспечения безопасности типов кортеж должен быть фиксированного размера, а тип каждого элемента должен быть известен.
Список может содержать переменное количество элементов, но из-за этого тип каждого элемента должен быть одинаковым. В противном случае вы не могли бы работать с ним на языке статической типизации.
В динамически типизированном языке вы можете, например, создать одну функцию, которая принимает список из списка (A) и другой список (B). Затем вы добавляете каждый элемент из B в каждый список внутри A, и все готово. Вы также можете сделать то же самое на языке статической типизации с двумя идеями:
- Вы конвертируете каждый элемент списка в
object
первый. - Сначала вы создаете Дискриминационный Союз каждого типа.
Первая идея будет означать, что вам нужно много понижений и повышений, это обычно не то, что вы хотите в статически типизированном языке. Второй подход работает, но вы должны преобразовать каждый отдельный список в ваш тип DU (вам также нужно создать DU), и позже вам нужно будет сопоставить с шаблоном. Технически это то же самое, что и 1. только в более безопасном для типов виде.
Другой подход, и это то, что я рекомендую, это использование Applicative. Аппликатив фактически означает, что вы обновляете функцию, поэтому каждый аргумент функции может быть опцией, списком и так далее. Итак, вы сначала создаете 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]
Единственное, что вы должны создать на лету - это первая линия. Функция, которая отображает четыре, пять, шесть,... аргументов в кортеж.
Если вы хотите узнать больше о Applicatives и о том, как он работает, я написал две публикации в блоге на эту тему:
http://sidburn.github.io/blog/2016/04/13/applicative-list http://sidburn.github.io/blog/2016/03/31/applicative-functors
Это не решение для п. Использование пространства имен отражения 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])
В соответствии с этим вопросом Stackru может оказаться невозможным написать функцию, которая принимает в качестве аргумента кортеж любой длины.
Этот вопрос задан давно, и я не уверен, есть ли в F# какие-либо обновления и изменения, которые делают это возможным.