2016-09-22 5 views
4

Обновление: Я создал запрос UserVoice для этого: Expand on the Cardinality functions for Seq.Non-throwing версия Seq.exactlyOne для проверки последовательности однократных сигналов

Мне нужна функциональность Seq.exactlyOne, но с семантикой Some/None. Другими словами, мне нужен либо Seq.head, либо, если последовательность пуста или содержит более одного элемента, мне ничего не нужно. Использование Seq.exactlyOne будет забрасывать в таких случаях.

Я не думаю, что есть встроенный способ получить это (хотя это звучит так тривиально, что можно было бы ожидать, что есть аналог для Seq.singleton). Я придумал это, но он чувствует себя запутанным:

let trySingleton sq = 
    match Seq.isEmpty sq with 
    | true -> None 
    | false -> 
     match sq |> Seq.indexed |> Seq.tryFind (fst >> ((=) 1)) with 
     | Some _ -> None 
     | None -> Seq.exactlyOne sq |> Some 

Дает:

> trySingleton [|1;2;3|];; 
val it : int option = None 
> trySingleton Seq.empty<int>;; 
val it : int option = None 
> trySingleton [|1|];; 
val it : int option = Some 1 

Есть более простой, или даже встроенный в путь? Я мог бы попробовать/поймать на Seq.exactlyOne, но это построение бизнес-логики вокруг исключений, я бы предпочел (и это дорого).

UPDATE: я не был осведомлен о функции Seq.tryItem, что бы сделать это проще:

let trySingleton sq = 
    match sq |> Seq.tryItem 1 with 
    | Some _ -> None 
    | None -> Seq.tryHead sq 

(лучше, но он по-прежнему чувствует себя довольно неловко)

+1

Кстати, вы можете предложите 'tryExactlyOne' в качестве дополнения к языку: https://fslang.uservoice.com/forums/245727-f-language Я бы добавил предложение сам, но у меня нет голосов на этом форуме. –

+0

@MarkSeemann: сделано, как [можно увидеть здесь] (https://fslang.uservoice.com/forums/245727-f-language/suggestions/16308904-expand-on-cardinality-functions-seq-exactlyone-wi) , tx для предложения. – Abel

ответ

6

Почему бы не подойти к проблема, требуя переустройства счетчика?

let trySingleton' (xs : seq<_>) = 
    use en = xs.GetEnumerator() 
    if en.MoveNext() then 
     let res = en.Current 
     if en.MoveNext() then None 
     else Some res 
    else None 

trySingleton' Seq.empty<int> // None 
trySingleton' [1]    // Some 1 
trySingleton' [1;2]    // None 
+0

Это не «просто», но он чувствует себя «чище». Любой ответ показывает, что я не пропустил тривиальный (смысл: основная библиотека) подход. Скорее произвольно между этими двумя решениями, я соглашусь с этим за его творчество и чистоту, спасибо :). – Abel

3

Я не знаю о встроенной функции для этого, но вот альтернативный способ ее реализации:

let tryExactlyOne xs = 
    match xs |> Seq.truncate 2 |> Seq.toList with 
    | [x] -> Some x 
    | _ -> None 

FSI демо:

> [42] |> List.toSeq |> tryExactlyOne;; 
val it : int option = Some 42 

> [42; 1337] |> List.toSeq |> tryExactlyOne;; 
val it : int option = None 

> Seq.empty<int> |> tryExactlyOne;; 
val it : int option = None 
+0

Я не знал, что 'truncate' не выбрасывает, но документы четко говорят (« не более X элементов »). Хорошее решение! Не уверен, что' Seq.toList' будет иметь пагубное влияние на производительность, но так как это будет только когда-либо список 0, 1 или 2, я ожидаю, что он будет иметь мало влияния. – Abel

+0

@Abel Вам нужно попытаться повторить итерацию дважды, чтобы определить, является ли элемент синглом, поэтому Я не думаю, что вы можете сделать это более эффективно, чем это ('Seq.truncate' ленив). Оба варианта kaefer и ваше собственное решение также повторяются дважды. –

+0

Да, это правда. Хотя ваше и мое решение повторяются дважды по вызову (на по крайней мере, я думаю), kaefer не выполняет итерацию дважды по вызову, но сбрасывает указатель итерации, поэтому вторая итерация происходит только в более чем одном сценарии. Поэтому я считаю, что его решение «чище». – Abel