Разделить последовательность в F # - PullRequest
7 голосов
/ 18 июля 2011

Я должен разделить seq<a> на seq<seq<a>> по атрибуту элементов.Если этот атрибут равен заданному значению, он должен быть «разделен» в этой точке.Как я могу сделать это в FSharp?

Было бы хорошо передать ему «функцию», которая возвращает логическое значение, если его нужно разделить на этот элемент или нет.1008 * Пример: последовательность ввода: seq: {1,2,3,4,1,5,6,7,1,9} Она должна быть разбита на каждый элемент, когда он равен 1, поэтому результат должен быть:

seq
{
seq{1,2,3,4}
seq{1,5,6,7}
seq{1,9}
}

Ответы [ 5 ]

10 голосов
/ 18 июля 2011

Все, что вы действительно делаете, - это группировка - создание новой группы каждый раз, когда встречается значение.

let splitBy f input =
  let i = ref 0
  input 
  |> Seq.map  (fun x -> 
    if f x then incr i
    !i, x)
  |> Seq.groupBy fst
  |> Seq.map (fun (_, b) -> Seq.map snd b)

Пример

let items = seq [1;2;3;4;1;5;6;7;1;9]
items |> splitBy ((=) 1)

Опять же, короче, с милым Стивеномулучшения:

let splitBy f input =
  let i = ref 0
  input
  |> Seq.groupBy (fun x ->
    if f x then incr i
    !i)
  |> Seq.map snd
4 голосов
/ 18 июля 2011

К сожалению, написание функций, работающих с последовательностями (тип seq<'T>), немного затруднительно.Они плохо работают с такими функциональными понятиями, как сопоставление с образцом в списках.Вместо этого вы должны использовать метод GetEnumerator и полученный тип IEnumerator<'T>.Это часто делает код довольно обязательным.В этом случае я бы написал следующее:

let splitUsing special (input:seq<_>) = seq { 
  use en = input.GetEnumerator()
  let finished = ref false
  let start = ref true
  let rec taking () = seq {
    if not (en.MoveNext()) then finished := true
    elif en.Current = special then start := true
    else 
      yield en.Current
      yield! taking() }

  yield taking()
  while not (!finished) do
    yield Seq.concat [ Seq.singleton special; taking()] }

Я бы не рекомендовал использовать функциональный стиль (например, Seq.skip и Seq.head), потому что это довольно неэффективно - он создаетцепочка последовательностей, которая берет значение из другой последовательности и просто возвращает его (поэтому обычно существует сложность O (N ^ 2)).

В качестве альтернативы, вы можете написать это, используя построитель вычислений для работы с IEnumerator<'T>но это не стандартно.Вы можете найти его здесь , если хотите поиграть с ним.

4 голосов
/ 18 июля 2011

Следующее является нечистой реализацией, но возвращает ленивые последовательности лениво:

let unflatten f s = seq {
    let buffer = ResizeArray()

    let flush() = seq { 
        if buffer.Count > 0 then 
            yield Seq.readonly (buffer.ToArray())
            buffer.Clear() }

    for item in s do
        if f item then yield! flush()
        buffer.Add(item)

    yield! flush() }

f - это функция, используемая для проверки, должен ли элемент быть точкой разделения:

[1;2;3;4;1;5;6;7;1;9] |> unflatten (fun item -> item = 1)
2 голосов
/ 18 июля 2011

Вероятно, не самое эффективное решение, но это работает:

let takeAndSkipWhile f s = Seq.takeWhile f s, Seq.skipWhile f s

let takeAndSkipUntil f = takeAndSkipWhile (f >> not)

let rec splitOn f s =
    if Seq.isEmpty s then
        Seq.empty
    else
        let pre, post =
            if f (Seq.head s) then
                takeAndSkipUntil f (Seq.skip 1 s)
                |> fun (a, b) ->
                    Seq.append [Seq.head s] a, b
            else
                takeAndSkipUntil f s
        if Seq.isEmpty pre then
            Seq.singleton post
        else
            Seq.append [pre] (splitOn f post)

splitOn ((=) 1) [1;2;3;4;1;5;6;7;1;9] // int list is compatible with seq<int>

Тип splitOn: ('a -> bool) -> seq <' a> -> seq>. Я не тестировал его на многих входах, но, похоже, он работает.

0 голосов
/ 19 июля 2011

В случае, если вы ищете что-то, что на самом деле работает как разделение как разделение строки (т.е. элемент не включен, для которого предикат возвращает true), ниже приведено то, что я придумал ... попытался быть максимально функциональным :)

let fromEnum (input : 'a IEnumerator) = 
    seq {
        while input.MoveNext() do
            yield input.Current
    }

let getMore (input : 'a IEnumerator) = 
    if input.MoveNext() = false then None
    else Some ((input |> fromEnum) |> Seq.append [input.Current])

let splitBy (f : 'a -> bool) (input : 'a seq)  = 
    use s = input.GetEnumerator()
    let rec loop (acc : 'a seq seq) = 
        match s |> getMore with 
        | None -> acc
        | Some x ->[x |> Seq.takeWhile (f >> not) |> Seq.toList |> List.toSeq]
                   |> Seq.append acc
                   |> loop
    loop Seq.empty |> Seq.filter (Seq.isEmpty >> not)

seq [1;2;3;4;1;5;6;7;1;9;5;5;1]
|> splitBy ( (=) 1) |> printfn "%A"
...