Элементы списка матчей в любом порядке
Я все еще новичок, когда дело доходит до многих областей F#. Я задаю этот вопрос больше из любопытства, чем из реальной деловой необходимости. Есть ли способ сопоставить первые n элементов в списке независимо от того, в каком порядке они отображаются? Чтобы уточнить, рассмотрим следующий пример:
type MyEnum =
| Foo of int
| Bar of float
| Baz of string
let list = [ Foo 1 ; Bar 1.0 ; Baz "1" ]
Теперь предположим, что я хочу позвонить some_func
если первые два элемента в списке Foo
и Bar
, в любом порядке. Довольно просто сопоставить две возможные перестановки:
let result =
match list with
| Foo n :: Bar x :: _
| Bar x :: Foo n :: _ -> some_func n x
| _ -> failwith "First 2 items must be Foo and Bar"
Однако, что если мне нужно вызвать функцию, если первые 3 элемента Foo
, Bar
а также Baz
в любом порядке? Использование той же техники, описанной выше, потребовало бы от меня написания всех 6 различных перестановок (или n! Для n элементов). В идеале я хотел бы иметь возможность сделать что-то вроде этого:
let result =
match list with
| (AnyOrder [ Foo n ; Bar x ; Baz s ]) :: _ -> some_func n x s
| _ -> failwith "First 3 items must be Foo, Bar, and Baz"
Есть ли способ сделать это с помощью какого-то активного шаблона, без необходимости жестко кодировать различные перестановки?
2 ответа
Очень интересный случай. Предлагаемое решение Mark отлично работает, но недостатком является то, что функция не может быть использована повторно, я имею в виду, что она очень специфична для этого DU.
Вот общее решение, но недостатком здесь является необходимость указывать список в порядке, в котором был создан DU.
let (|AnyOrderOfFirst|) n = Seq.take n >> Seq.sort >> Seq.toList
let result =
match list with
| AnyOrderOfFirst 3 [ Foo n ; Bar x ; Baz s ] -> some_func n x s
| _ -> failwith "First 3 items must be Foo, Bar, and Baz"
Если вы измените свой DU, изменив порядок тегов, и забудете изменить их порядок в списке, он никогда не будет совпадать.
Если вы хотите пойти по этому пути, мой совет: создайте модульный тест, чтобы убедиться, что совпадение всегда работает.
Вот одна попытка решения проблемы. Он оценивает каждый случай объединения, используя растровое изображение, и проверяет, что сумма баллов равна 7:
let (|AnyOrder|_|) s =
let score = function | Foo _ -> 1 | Bar _ -> 2 | Baz _ -> 4
let firstElementsWithScores =
s
|> Seq.truncate 3
|> Seq.map (fun x -> x, score x)
|> Seq.sortBy (fun (_, x) -> x)
|> Seq.toList
let sumOfScores =
firstElementsWithScores |> List.sumBy (fun (_, x) -> x)
if sumOfScores = 7
then
match firstElementsWithScores |> List.map fst with
| [Foo x ; Bar y ; Baz z ] -> Some (x, y, z)
| _ -> None
else None
Если счет равен 7, он усекает входной список и сортирует его, а затем использует сопоставление с шаблоном по усеченному, забитому списку, чтобы создать кортеж из соответствующих элементов.
Вот один из способов его использования:
let checkMatches s =
match s with
| AnyOrder (x, y, z) -> [Foo x; Bar y; Baz z]
| _ -> []
Некоторые примеры из FSI:
> checkMatches list;;
val it : MyEnum list = [Foo 1; Bar 1.0; Baz "1"]
> checkMatches [Foo 1; Bar 1.0; Foo 2];;
val it : MyEnum list = []
> checkMatches [Foo 1; Bar 1.0; Baz "1"; Foo 2];;
val it : MyEnum list = [Foo 1; Bar 1.0; Baz "1"]
> checkMatches [Foo 1; Bar 1.0; Bar 2.0; Foo 2];;
val it : MyEnum list = []
> checkMatches [Bar 1.0; Foo 1; Baz "2.0"; Foo 2];;
val it : MyEnum list = [Foo 1; Bar 1.0; Baz "2.0"]
AnyOrder
частичный активный шаблон с подписью
seq<MyEnum> -> (int * float * string) option