В 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
,