我有一个包含两列文本和计数的 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
下面的错误发生在管道上下文中的 expandRows 上。
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
最佳答案
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
。关于f# - 在 F# 中,如何在更大管道的上下文中使用 Seq.unfold?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41374343/