在 F# 中,如何在较大管道的上下文中使用 Seq.unfold?
In F#, How do I use Seq.unfold in the context of a larger pipeline?
我有一个包含两列的 CSV 文件,text 和 count。目标是从以下文件转换文件:
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 会返回字符串序列,就像在我的独立测试中一样。因为这既不是 "Expecting" 也不是 "given",我很困惑。有人能指出我正确的方向吗?
代码要点在这里:
https://gist.github.com/akucheck/e0ff316e516063e6db224ab116501498
听起来你想做的实际上是
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.unfold
和 expandRows
将序列中每个带有计数的元组转换为 seq<string>
。这是通过映射完成的。
之后,您想将 seq<seq<string>>
展平成一个大的 seq<string>
,它位于 Seq.concat
.
下方
Seq.map
产生一个序列,但 Seq.unfold
不接受序列,它接受单个值。所以你不能直接将 Seq.map
的输出通过管道传输到 Seq.unfold
。您需要逐个元素地进行。
但是,对于每个元素,您的 Seq.unfold
都会产生一个序列,因此最终结果将是一个序列的序列。您可以使用 Seq.collect
:
在单个序列中收集所有这些 "subsequences"
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
。
在这种情况下,由于您只是想多次重复某个值,因此没有理由使用 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
执行的唯一工作是 'unpack' 一个元组并将其值组合成柯里化形式。
虽然 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
我有一个包含两列的 CSV 文件,text 和 count。目标是从以下文件转换文件:
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 会返回字符串序列,就像在我的独立测试中一样。因为这既不是 "Expecting" 也不是 "given",我很困惑。有人能指出我正确的方向吗?
代码要点在这里: https://gist.github.com/akucheck/e0ff316e516063e6db224ab116501498
听起来你想做的实际上是
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.unfold
和 expandRows
将序列中每个带有计数的元组转换为 seq<string>
。这是通过映射完成的。
之后,您想将 seq<seq<string>>
展平成一个大的 seq<string>
,它位于 Seq.concat
.
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
。
在这种情况下,由于您只是想多次重复某个值,因此没有理由使用 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
执行的唯一工作是 'unpack' 一个元组并将其值组合成柯里化形式。
虽然 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