Элементы списка матчей в любом порядке

Я все еще новичок, когда дело доходит до многих областей 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
Другие вопросы по тегам