В F#, как я могу использовать Seq.unfold в контексте большего конвейера?

У меня есть файл CSV с двумя столбцами, текст и количество. Цель состоит в том, чтобы преобразовать файл из этого:

some text once,1
some text twice,2
some text thrice,3

К этому:

some text once,1
some text twice,1
some text twice,1
some text thrice,1
some text thrice,1
some text thrice,1

повторяя каждую строку, считайте время и распространяйте счет по этому количеству строк.

Это кажется мне хорошим кандидатом на Seq.unfold, генерируя дополнительные строки, когда мы читаем файл. У меня есть следующая функция генератора:

let expandRows (text:string, number:int32) =
    if number = 0 
    then None
    else
        let element = text                  // "element" will be in the generated sequence
        let nextState = (element, number-1) // threaded state replacing looping 
        Some (element, nextState)

FSI выдает следующую сигнатуру функции:

val expandRows : text:string * number:int32 -> (string * (string * int32)) option

Выполнение следующего в FSI:

let expandedRows = Seq.unfold expandRows ("some text thrice", 3)

дает ожидаемое:

val it : seq<string> = seq ["some text thrice"; "some text thrice"; "some text thrice"]

Вопрос заключается в следующем: как мне подключить это в контексте более крупного конвейера ETL? Например:

File.ReadLines(inFile)                  
    |> Seq.map createTupleWithCount
    |> Seq.unfold expandRows // type mismatch here
    |> Seq.iter outFile.WriteLine

Ошибка, приведенная ниже, относится к расширениям в контексте конвейера.

Type mismatch. 
Expecting a 'seq<string * int32> -> ('a * seq<string * int32>) option'    
but given a     'string * int32 -> (string * (string * int32)) option' 
The type    'seq<string * int 32>' does not match the type 'string * int32'

Я ожидал, что expandRows возвращает seq строки, как в моем изолированном тесте. Поскольку это не "ожидание" или "данное", я запутался. Может ли кто-нибудь указать мне правильное направление?

Суть кода здесь: https://gist.github.com/akucheck/e0ff316e516063e6db224ab116501498

3 ответа

Решение

Seq.map производит последовательность, но Seq.unfold не принимает последовательность, она принимает одно значение. Таким образом, вы не можете напрямую передать вывод Seq.map в Seq.unfold, Вы должны сделать это элемент за элементом вместо этого.

Но тогда для каждого элемента ваш Seq.unfold будет производить последовательность, поэтому конечным результатом будет последовательность последовательностей. Вы можете собрать все эти "подпоследовательности" в одну последовательность с Seq.collect:

File.ReadLines(inFile) 
    |> Seq.map createTupleWithCount 
    |> Seq.collect (Seq.unfold expandRows)
    |> Seq.iter outFile.WriteLine

Seq.collect принимает функцию и входную последовательность. Для каждого элемента входной последовательности предполагается, что функция создает другую последовательность, и Seq.collect объединит все эти последовательности в одну. Вы можете думать о Seq.collect как Seq.map а также Seq.concat объединены в одну функцию. Кроме того, если вы пришли из C#, Seq.collect называется SelectMany там.

В этом случае, поскольку вы просто хотите повторить значение несколько раз, нет смысла использовать Seq.unfold, Ты можешь использовать Seq.replicate вместо:

// 'a * int -> seq<'a>
let expandRows (text, number) = Seq.replicate number text

Ты можешь использовать Seq.collect составить это:

File.ReadLines(inFile)
|> Seq.map createTupleWithCount
|> Seq.collect expandRows
|> Seq.iter outFile.WriteLine

Фактически единственная работа, выполненная этой версией expandRows это "распаковать" кортеж и скомпоновать его значения в форме карри.

Хотя F# не имеет такой обобщенной функции в своей базовой библиотеке, вы можете легко определить ее (и другие аналогично полезные функции):

module Tuple2 =
    let curry f x y = f (x, y)    
    let uncurry f (x, y) = f x y    
    let swap (x, y) = (y, x)

Это позволит вам составить ваш конвейер из известных функциональных строительных блоков:

File.ReadLines(inFile)
|> Seq.map createTupleWithCount
|> Seq.collect (Tuple2.swap >> Tuple2.uncurry Seq.replicate)
|> Seq.iter outFile.WriteLine

Похоже, что вы хотите сделать, это на самом деле

File.ReadLines(inFile)                  
|> Seq.map createTupleWithCount
|> Seq.map (Seq.unfold expandRows) // Map each tuple to a seq<string>
|> Seq.concat // Flatten the seq<seq<string>> to seq<string>
|> Seq.iter outFile.WriteLine

так как кажется, что вы хотите преобразовать каждый кортеж с количеством в вашей последовательности в seq<string> с помощью Seq.unfold а также expandRows, Это делается путем картирования.

После этого вы хотите сгладить seq<seq<string>> в большой seq<string>, который вниз через Seq.concat,

Другие вопросы по тегам