Как сопоставить несколько копий значения?
Соответствие шаблону 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
Ответы
Ответ 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-х кортежей - полный активный шаблон были бы выполнимы и, вероятно, предпочтительнее - исчерпывающие матчи являются "более безопасными", чем не исчерпывающие матчи). Или, возможно, вам нужна более сложная структура данных.