在F#中,如何在较大的管道​​环境中使用Seq.unfold?

时间:2016-12-29 06:33:44

标签: 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

以下错误出现在管道上下文中的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

3 个答案:

答案 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.mapSeq.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.unfoldexpandRows。这是通过映射完成的。

之后,您希望将seq<seq<string>>展平为一个较大的seq<string>,该Seq.concat位于{{1}}下方。