【问题标题】:In F#, How do I use Seq.unfold in the context of a larger pipeline?在 F# 中,如何在较大管道的上下文中使用 Seq.unfold?
【发布时间】:2017-05-13 11:05:57
【问题描述】:

我有一个包含两列文本和计数的 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

【问题讨论】:

    标签: f# seq.unfold


    【解决方案1】:

    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.mapSeq.concat 组合在一个函数中。另外,如果你来自 C#,Seq.collect 在那边被称为SelectMany

    【讨论】:

    • 非常有帮助的解释;正是我需要的。谢谢!
    • 很高兴我能帮上忙。
    【解决方案2】:

    在这种情况下,由于您只是想多次重复一个值,因此没有理由使用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# 在其核心库中没有提供这样的通用函数,但您可以轻松定义它(以及 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
    

    【讨论】:

    • 我喜欢通过删除 Seq.unfold 来简化的想法,但我在 MSDN 文档中没有看到对 Seq.replicate 的引用。我忽略了什么?
    • 有趣。在 FSI 中工作,编译,在 Mono w System.MissingMethodException 上运行时失败。需要深入研究这个...
    • @akucheck Seq.replicate 在 F# 4 中添加:github.com/Microsoft/visualfsharp/blob/fsharp4/CHANGELOG.md
    • 谢谢!我记得看过那张图表,但没有保存指向它的链接——一直在寻找它。奇怪的是 MSDN 没有显示这一点。 似乎这个 API 在 Mono 中还不可用。代码(显然)在 Windows 上运行良好。
    • 即使Seq.replicate 不存在,仍然不需要unfold。你也可以这样做seq { for _ in 1..number -&gt; text }
    【解决方案3】:

    听起来你想做的其实是

    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.unfoldexpandRows 将序列中带有count 的每个元组转换为seq&lt;string&gt;。这是通过映射完成的。

    之后,您想将您的seq&lt;seq&lt;string&gt;&gt; 扁平化为一个大的seq&lt;string&gt;,它通过Seq.concat 向下。

    【讨论】:

    • map &gt;&gt; concat === collect
    • 涂料。我忘了收集。谢谢提醒!
    猜你喜欢
    • 2010-10-06
    • 2020-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    • 2021-10-07
    相关资源
    最近更新 更多