用每个元素的(简单)计算状态装饰 F# 序列
Decorate an F# sequence with (simple) computed state for each element
我有一个解决方案,还有几个可行但不令人满意的解决方案,但它花费了大量的工作,而且似乎不必要地复杂。
我在 F# 中遗漏了什么吗?
问题
我有一个数字序列
let nums = seq { 9; 12; 4; 17; 9; 7; 13; }
我想给每个数字都加上一个“索引”,所以结果是
seq [(9, 0); (12, 1); (4, 2); (17, 3); ...]
看起来很简单!
在实践中,输入可能非常大且大小不确定。在我的应用程序中,它来自 REST 服务。
进一步
- 操作必须支持惰性求值(因为有 REST 后端)
- 必须是纯函数的,这消除了明显的
seq { let mutable i = o; for num in nums do .. }
解决方案,同上 while ... do ...
让我们调用 (seq<'a> -> seq<'a * int>)
类型的函数 decorate
,它会按如下方式工作:
nums
|> decorate
|> Seq.iter (fun (n,index) -> printfn "%d: %d" index n)
制作中:
0: 9
1: 12
2: 4
...
6: 13
对于列表来说这是一个微不足道的问题(除了惰性求值),但对于序列来说就很棘手了。
我的解决方案是使用Seq.unfold,如下:
let decorate numSeq =
(0,numSeq)
|> Seq.unfold
(fun (count,(nums : int seq)) ->
if Seq.isEmpty nums then
None
else
let result = ((Seq.head nums),count)
let remaining = Seq.tail nums
Some( result, (count+1,remaining)))
这符合所有要求,是我想到的最好的。
这是完整的解决方案,带有显示延迟计算的诊断:
let nums =
seq {
// With diagnostic
let getN n =
printfn "get: %d" n
n
getN 9;
getN 12;
getN 4;
getN 17;
getN 9;
getN 7;
getN 13
}
let decorate numSeq =
(0,numSeq)
|> Seq.unfold
(fun (count,(nums : int seq)) ->
if Seq.isEmpty nums then
None
else
let result = ((Seq.head nums),count)
let remaining = Seq.tail nums
printfn "unfold: %A" result
Some( result, (count+1,remaining)))
nums
|> Seq.cache
// To prevent re-computation of the sequence.
// Will be necessary for any solution. This solution required only one.
|> decorate
|> Seq.iter (fun (n,index) -> printfn "ITEM %d: %d" index n)
问题: 这需要很多工作才能实现。与(显然)简单的要求相比,它看起来很复杂。
问题:是否有更简单的解决方案?
一些备选方案的讨论。
所有工作,但由于给出的原因不令人满意
// Most likely: Seq.mapFold
// Fails lazy evalation. The final state must be evaluated, even if not used
let decorate numSeq =
numSeq
|> Seq.mapFold
(fun count num ->
let result = (num,count)
printfn "yield: %A" result
(result,(count + 1)))
0
|> fun (nums,finalState) -> nums // And, no, using "_" doesn't fix it!
// 'for' loop, with MUTABLE
// Lazy evaluation works
// Not extensible, as the state 'count' is specific to this problem
let decorate numSeq =
let mutable count = 0
seq {
for num in numSeq do
let result = num,count
printfn "yield: %A" result
yield result;
count <- count+1
}
// 'for' loop, without mutable
// Fails lazy evaluation, and is ugly
let decorate numSeq =
seq {
for index in 0..((Seq.length numSeq) - 1) do
let result = ((Seq.item index numSeq), // Ugly!
index)
printfn "yield: %A" result
yield result
}
// "List" like recursive descent,
// Fails lazy evaluation. Ugly, because we are not meant to use recursion on Sequences
//
let rec decorate' count (nums : int seq) =
if Seq.isEmpty nums then
Seq.empty
else
let hd = Seq.head nums
let tl = Seq.tail nums
let result = (hd,count)
let tl' = decorate' (count+1) tl
printfn "yield: %A" result
seq { yield result; yield! tl'}
let decorate : (seq<'a> -> seq<'a * int>) = decorate' 0
您可以使用 Seq.mapi
来做您需要的事情。
let nums = seq { 9; 12; 4; 17; 9; 7; 13; }
nums |> Seq.mapi (fun i num -> (num, i))
这给出 (9, 0); (12, 1); etc...
Seq
与 C# 中的 IEnumerable
意义相同。
您可以在此处阅读有关 Seq.mapi
的信息:
https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-seqmodule.html#mapi
在此处详细了解 map
的使用:
除了Sean的回答中提到的Seq.mapi
函数,F#还有一个内置的Seq.indexed
函数,用索引修饰一个序列。这并不完全符合您的要求,因为索引成为元组的第一个元素,但根据您的用例,它可能会起作用:
> let nums = seq { 9; 12; 4; 17; 9; 7; 13; };;
val nums : seq<int>
> Seq.indexed nums;;
val it : seq<int * int> = seq [(0, 9); (1, 12); (2, 4); (3, 17); ...]
如果我尝试使用更原始的函数自行实现它,可以使用 Seq.scan
来完成,这有点像折叠,但会产生一个惰性状态序列。唯一棘手的事情是你必须构造初始状态,然后处理序列的其余部分:
Seq.tail nums
|> Seq.scan (fun (prevIndex, _) v -> (prevIndex+1, v)) (0, Seq.head nums)
这不适用于空列表,即使函数在逻辑上应该能够处理这个问题。
用for
也不错,还是错的。 for
和 yield
在 seq {}
中是你编写新的 seq
函数的方式,如果 Seq
模块中提供的函数中的 none 是最好的-合身。使用这种特殊结构既没有错也没有坏处。它与 C# foreach
和 yield
语法相同。
在有限范围内使用可变变量,也没有错。可变变量是一个坏主意,如果它们超出了范围。例如,您 return 来自函数的可变值。
将可变变量放在 seq
内部而不是外部很重要。你的版本不对。
让我们假设这个
let xs = decorate [3;6;7;12;9]
for x in xs do
printfn "%A" x
for x in xs do
printfn "%A" x
现在你有两个版本的装饰。第一版
let decorate numSeq =
let mutable count = 0
seq {
for num in numSeq do
yield (num,count)
count <- count + 1
}
将打印:
(3, 0)
(6, 1)
(7, 2)
(12, 3)
(9, 4)
(3, 5)
(6, 6)
(7, 7)
(12, 8)
(9, 9)
或者换句话说。每当您遍历序列时,可变变量就会在所有调用中共享。作为一般提示。如果你想 return 一个 seq
然后把你所有的代码放到 seq
中。并将 seq {}
放在 =
符号之后。如果您改为这样做。
let decorate numSeq = seq {
let mutable count = 0
for num in numSeq do
yield (num,count)
count <- count + 1
}
你得到正确的输出:
(3, 0)
(6, 1)
(7, 2)
(12, 3)
(9, 4)
(3, 0)
(6, 1)
(7, 2)
(12, 3)
(9, 4)
请您解释一下,这个版本不是“可扩展的”。但是 mapi
你 select 的版本是“正确的”。有同样的问题,它只提供一个索引,仅此而已。
如果你想要一个更通用的版本,你总是可以创建一个函数,它期望它的值作为函数参数。例如,您可以将上述功能更改为此代码。
let decorate2 f (state:'State) (xs:'T seq) = seq {
let mutable state = state
for x in xs do
yield state, x
let newState = f state x
state <- newState
}
现在decorate2
需要一个你可以自由通过的状态,以及一个改变状态的函数。使用此功能,您可以编写:
decorate2 (fun state _ -> state+1) 0 [3;6;7;12;9]
函数签名与Seq.scan
几乎相同,但还是有一点不同。但是如果你想创建一个 indexed
函数,你可以像这样使用 scan
。
let indexed xs =
Seq.scan (fun (count,_) x -> (count+1,x)) (0,Seq.head xs) (Seq.skip 1 xs)
仅在我看来。与 decorate
或 decorate2
.
相比,这个版本更难阅读、理解,而且非常糟糕
还有一张纸条。标准库中已经有一个 Seq.indexed
函数,可以满足您的需求。
for x in Seq.indexed [3;6;7;12;9] do
printfn "%A" x
将打印
(0, 3)
(1, 6)
(2, 7)
(3, 12)
(4, 9)
我有一个解决方案,还有几个可行但不令人满意的解决方案,但它花费了大量的工作,而且似乎不必要地复杂。
我在 F# 中遗漏了什么吗?
问题
我有一个数字序列
let nums = seq { 9; 12; 4; 17; 9; 7; 13; }
我想给每个数字都加上一个“索引”,所以结果是
seq [(9, 0); (12, 1); (4, 2); (17, 3); ...]
看起来很简单!
在实践中,输入可能非常大且大小不确定。在我的应用程序中,它来自 REST 服务。
进一步
- 操作必须支持惰性求值(因为有 REST 后端)
- 必须是纯函数的,这消除了明显的
seq { let mutable i = o; for num in nums do .. }
解决方案,同上while ... do ...
让我们调用 (seq<'a> -> seq<'a * int>)
类型的函数 decorate
,它会按如下方式工作:
nums
|> decorate
|> Seq.iter (fun (n,index) -> printfn "%d: %d" index n)
制作中:
0: 9
1: 12
2: 4
...
6: 13
对于列表来说这是一个微不足道的问题(除了惰性求值),但对于序列来说就很棘手了。
我的解决方案是使用Seq.unfold,如下:
let decorate numSeq =
(0,numSeq)
|> Seq.unfold
(fun (count,(nums : int seq)) ->
if Seq.isEmpty nums then
None
else
let result = ((Seq.head nums),count)
let remaining = Seq.tail nums
Some( result, (count+1,remaining)))
这符合所有要求,是我想到的最好的。
这是完整的解决方案,带有显示延迟计算的诊断:
let nums =
seq {
// With diagnostic
let getN n =
printfn "get: %d" n
n
getN 9;
getN 12;
getN 4;
getN 17;
getN 9;
getN 7;
getN 13
}
let decorate numSeq =
(0,numSeq)
|> Seq.unfold
(fun (count,(nums : int seq)) ->
if Seq.isEmpty nums then
None
else
let result = ((Seq.head nums),count)
let remaining = Seq.tail nums
printfn "unfold: %A" result
Some( result, (count+1,remaining)))
nums
|> Seq.cache
// To prevent re-computation of the sequence.
// Will be necessary for any solution. This solution required only one.
|> decorate
|> Seq.iter (fun (n,index) -> printfn "ITEM %d: %d" index n)
问题: 这需要很多工作才能实现。与(显然)简单的要求相比,它看起来很复杂。
问题:是否有更简单的解决方案?
一些备选方案的讨论。
所有工作,但由于给出的原因不令人满意
// Most likely: Seq.mapFold
// Fails lazy evalation. The final state must be evaluated, even if not used
let decorate numSeq =
numSeq
|> Seq.mapFold
(fun count num ->
let result = (num,count)
printfn "yield: %A" result
(result,(count + 1)))
0
|> fun (nums,finalState) -> nums // And, no, using "_" doesn't fix it!
// 'for' loop, with MUTABLE
// Lazy evaluation works
// Not extensible, as the state 'count' is specific to this problem
let decorate numSeq =
let mutable count = 0
seq {
for num in numSeq do
let result = num,count
printfn "yield: %A" result
yield result;
count <- count+1
}
// 'for' loop, without mutable
// Fails lazy evaluation, and is ugly
let decorate numSeq =
seq {
for index in 0..((Seq.length numSeq) - 1) do
let result = ((Seq.item index numSeq), // Ugly!
index)
printfn "yield: %A" result
yield result
}
// "List" like recursive descent,
// Fails lazy evaluation. Ugly, because we are not meant to use recursion on Sequences
//
let rec decorate' count (nums : int seq) =
if Seq.isEmpty nums then
Seq.empty
else
let hd = Seq.head nums
let tl = Seq.tail nums
let result = (hd,count)
let tl' = decorate' (count+1) tl
printfn "yield: %A" result
seq { yield result; yield! tl'}
let decorate : (seq<'a> -> seq<'a * int>) = decorate' 0
您可以使用 Seq.mapi
来做您需要的事情。
let nums = seq { 9; 12; 4; 17; 9; 7; 13; }
nums |> Seq.mapi (fun i num -> (num, i))
这给出 (9, 0); (12, 1); etc...
Seq
与 C# 中的 IEnumerable
意义相同。
您可以在此处阅读有关 Seq.mapi
的信息:
https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-seqmodule.html#mapi
在此处详细了解 map
的使用:
除了Sean的回答中提到的Seq.mapi
函数,F#还有一个内置的Seq.indexed
函数,用索引修饰一个序列。这并不完全符合您的要求,因为索引成为元组的第一个元素,但根据您的用例,它可能会起作用:
> let nums = seq { 9; 12; 4; 17; 9; 7; 13; };;
val nums : seq<int>
> Seq.indexed nums;;
val it : seq<int * int> = seq [(0, 9); (1, 12); (2, 4); (3, 17); ...]
如果我尝试使用更原始的函数自行实现它,可以使用 Seq.scan
来完成,这有点像折叠,但会产生一个惰性状态序列。唯一棘手的事情是你必须构造初始状态,然后处理序列的其余部分:
Seq.tail nums
|> Seq.scan (fun (prevIndex, _) v -> (prevIndex+1, v)) (0, Seq.head nums)
这不适用于空列表,即使函数在逻辑上应该能够处理这个问题。
用
for
也不错,还是错的。for
和yield
在seq {}
中是你编写新的seq
函数的方式,如果Seq
模块中提供的函数中的 none 是最好的-合身。使用这种特殊结构既没有错也没有坏处。它与 C#foreach
和yield
语法相同。在有限范围内使用可变变量,也没有错。可变变量是一个坏主意,如果它们超出了范围。例如,您 return 来自函数的可变值。
将可变变量放在
seq
内部而不是外部很重要。你的版本不对。
让我们假设这个
let xs = decorate [3;6;7;12;9]
for x in xs do
printfn "%A" x
for x in xs do
printfn "%A" x
现在你有两个版本的装饰。第一版
let decorate numSeq =
let mutable count = 0
seq {
for num in numSeq do
yield (num,count)
count <- count + 1
}
将打印:
(3, 0)
(6, 1)
(7, 2)
(12, 3)
(9, 4)
(3, 5)
(6, 6)
(7, 7)
(12, 8)
(9, 9)
或者换句话说。每当您遍历序列时,可变变量就会在所有调用中共享。作为一般提示。如果你想 return 一个 seq
然后把你所有的代码放到 seq
中。并将 seq {}
放在 =
符号之后。如果您改为这样做。
let decorate numSeq = seq {
let mutable count = 0
for num in numSeq do
yield (num,count)
count <- count + 1
}
你得到正确的输出:
(3, 0)
(6, 1)
(7, 2)
(12, 3)
(9, 4)
(3, 0)
(6, 1)
(7, 2)
(12, 3)
(9, 4)
请您解释一下,这个版本不是“可扩展的”。但是 mapi
你 select 的版本是“正确的”。有同样的问题,它只提供一个索引,仅此而已。
如果你想要一个更通用的版本,你总是可以创建一个函数,它期望它的值作为函数参数。例如,您可以将上述功能更改为此代码。
let decorate2 f (state:'State) (xs:'T seq) = seq {
let mutable state = state
for x in xs do
yield state, x
let newState = f state x
state <- newState
}
现在decorate2
需要一个你可以自由通过的状态,以及一个改变状态的函数。使用此功能,您可以编写:
decorate2 (fun state _ -> state+1) 0 [3;6;7;12;9]
函数签名与Seq.scan
几乎相同,但还是有一点不同。但是如果你想创建一个 indexed
函数,你可以像这样使用 scan
。
let indexed xs =
Seq.scan (fun (count,_) x -> (count+1,x)) (0,Seq.head xs) (Seq.skip 1 xs)
仅在我看来。与 decorate
或 decorate2
.
还有一张纸条。标准库中已经有一个 Seq.indexed
函数,可以满足您的需求。
for x in Seq.indexed [3;6;7;12;9] do
printfn "%A" x
将打印
(0, 3)
(1, 6)
(2, 7)
(3, 12)
(4, 9)