7

我有一个包含两列文本和计数的 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 返回字符串的序列,就像在我的独立测试中一样。由于这既不是“期望”也不是“给定”,我很困惑。有人可以指出我正确的方向吗?

代码要点在这里: https ://gist.github.com/akucheck/e0ff316e516063e6db224ab116501498

4

3 回答 3

6

Seq.map产生一个序列,但Seq.unfold不带一个序列,它只带一个值。所以你不能直接通过管道输出Seq.mapinto 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.collectasSeq.mapSeq.concat结合在一个功能中。此外,如果您来自 C#,Seq.collectSelectMany在此处调用。

于 2016-12-29T06:54:48.523 回答
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是“解包”一个元组并将其值组合成柯里化形式。

虽然 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
于 2016-12-29T07:01:03.097 回答
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

因为您似乎想将序列中带有 count 的每个元组转换为seq<string>viaSeq.unfoldexpandRows。这是通过映射完成的。

之后,你想把你的扁平seq<seq<string>>化成一个大的seq<string>,它是通过Seq.concat.

于 2016-12-29T06:50:05.473 回答