我有一个包含两列,文本和计数的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
答案 0 :(得分:6)
Seq.map
生成一个序列,但Seq.unfold
不接受序列,它只需要一个值。因此,您无法将Seq.map
的输出直接导入Seq.unfold
。你需要逐个元素地做它。
但是,对于每个元素,您的Seq.unfold
将生成一个序列,因此最终结果将是一系列序列。你可以收集所有这些&#34;子序列&#34;在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
。
答案 1 :(得分:6)
在这种情况下,由于您只想重复一次数值,因此没有理由使用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
所执行的唯一工作是解包&#39;一个元组,并将其值组成咖喱形式。
虽然F#在其核心库中没有这样的通用函数,但您可以轻松定义它(和other similarly useful functions):
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
答案 2 :(得分:2)
听起来你想要做的事实上是
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
位于{{1}}下方。