Подтвердить что ты не робот

Как сопоставить несколько копий значения?

Соответствие шаблону F # очень мощное, поэтому было естественно писать:

match (tuple1, tuple2) with
| ((a, a), (a, a)) -> "all values are the same"
| ((a, b), (a, b)) -> "tuples are the same"
| ((a, b), (a, c)) -> "first values are the same"
// etc

Однако первое совпадение шаблонов дает ошибку компилятора:

'a' is bound twice in this pattern

Есть ли более чистый способ сделать это, чем следующее?

match (tuple1, tuple2) with
| ((a, b), (c, d)) when a = b && b = c && c = d -> "all values are the same"
| ((a, b), (c, d)) when a = c && b = d -> "tuples are the same"
| ((a, b), (c, d)) when a = c -> "first values are the same"
// etc
4b9b3361

Ответ 1

Это идеальный вариант использования для активных шаблонов F #. Вы можете определить пару из них следующим образом:

let (|Same|_|) (a, b) =
    if a = b then Some a else None

let (|FstEqual|_|) ((a, _), (c, _)) =
    if a = c then Some a else None

И затем очистите свой шаблон, соответствующий им; обратите внимание, как первый случай (где все значения равны) использует вложенный шаблон Same для проверки того, что первый и второй элементы кортежа равны:

match tuple1, tuple2 with
| Same (Same x) ->
    "all values are the same"
| Same (x, y) ->
    "tuples are the same"
| FstEqual a ->
    "first values are the same"
| _ ->
    failwith "TODO"

Совет по производительности. Мне нравится отмечать простые активные шаблоны, подобные этим, с помощью inline - поскольку логика в активных шаблонах проста (всего несколько инструкций IL), имеет смысл встроить их и избежать накладных расходов вызов функции.

Ответ 2

Вы можете использовать параметризованные активные шаблоны, чтобы устранить проблему.

let (|TuplePairPattern|_|) ((p1, p2), (p3, p4)) ((a, b), (c, d)) =
    let matched =
        [(p1, a); (p2, b); (p3, c); (p4, d)]
        |> Seq.groupBy fst
        |> Seq.map (snd >> Set.ofSeq)
        |> Seq.forall (fun s -> Set.count s = 1)
    if matched then Some () else None

В частности, вы должны определить шаблон в виде литералов (символы, строки и т.д.).

match tuple1, tuple2 with
| TuplePairPattern(('a', 'a'), ('a', 'a')) -> "all values are the same"
| TuplePairPattern(('a', 'b'), ('a', 'b')) -> "tuples are the same"
| TuplePairPattern(("a", "b"), ("a", "c")) -> "first values are the same"
// etc

Ответ 3

Я думаю, самый элегантный способ может быть достигнут путем объединения двух отличных ответов, предоставленных @Stephen Swensen и @pad.

Первая идея состоит в том, что структура (кортеж, содержащий два кортежа) может быть распакована один раз, вместо того, чтобы делать это в каждом случае match.
Вторая идея - работать с последовательностями значений, все из которых должны быть равны друг другу.

Здесь код:

let comparer ((a,b),(c,d)) =
    let same = Set.ofSeq >> Set.count >> ((=) 1)
    if   same[a; b; c; d]         then "all values are the same"
    elif same[a; c] && same[b; d] then "tuples are the same"
    elif same[a; c]               then "first values are the same"
    else                                "none of above"

Вы можете изменить elif на match, но для меня это не представляется возможным.

Ответ 4

На практике я, вероятно, распакую кортежи вперед, а затем сделаю серию выражений if/then/else:

let a,b = tuple1
let c,d = tuple2

if a = b && b = c && c = d then "all values are the same"
elif a = c && b = d then "tuples are the same"
elif a = c then "first values are the same"
...

Если вы часто это делаете, может потребоваться активный шаблон (а в случае 2-х кортежей - полный активный шаблон были бы выполнимы и, вероятно, предпочтительнее - исчерпывающие матчи являются "более безопасными", чем не исчерпывающие матчи). Или, возможно, вам нужна более сложная структура данных.