首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用(简单)计算状态修饰每个元素的F#序列

用(简单)计算状态修饰每个元素的F#序列
EN

Stack Overflow用户
提问于 2021-09-24 10:37:49
回答 3查看 118关注 0票数 1

我对此有一个解决方案,还有几个可行但不令人满意的解决方案,但它花费了大量的工作,似乎没有必要的复杂。

我是不是在F#上漏掉了什么?

问题

我有一个数字序列

代码语言:javascript
复制
let nums = seq { 9; 12; 4; 17; 9; 7; 13; }

我想用“索引”来修饰每个数字,所以结果是

代码语言:javascript
复制
seq [(9, 0); (12, 1); (4, 2); (17, 3); ...]

看起来很简单!

在实践中,输入可以是非常大和不确定的大小。在我的应用程序中,它来自REST服务。

进一步

  • 操作必须支持惰性评估(因为有REST后端),
  • 必须是纯功能的,这消除了明显的seq { let mutable i = o; for num in nums do .. }解决方案,while ... do ...

也是如此。

让我们调用函数decorate,类型为(seq<'a> -> seq<'a * int>),因此它将按照以下方式工作:

代码语言:javascript
复制
nums
|> decorate
|> Seq.iter (fun (n,index) -> printfn "%d: %d" index n)

制作:

代码语言:javascript
复制
0: 9
1: 12
2: 4
...
6: 13

对于列表来说,这是一个微不足道的问题(除了懒惰的计算),但对于序列来说却是一个棘手的问题。

我的解决方案是使用Seq.unfold,如下所示:

代码语言:javascript
复制
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)))

这符合所有的要求,也是我想出的最好的。

以下是整个解决方案,并提供诊断以显示延迟评估:

代码语言:javascript
复制
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)

问题:需要做大量的工作才能到达。与(显然)简单的要求相比,它看起来很复杂。

问题:有更简单的解决方案吗?

讨论一些备选方案。

所有工作,但由于所述原因而不能令人满意

代码语言:javascript
复制
// 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
// https://stackoverflow.com/questions/11451727/recursive-functions-for-sequences-in-f
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 
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-09-24 10:52:05

您可以使用Seq.mapi来做您需要的事情。

代码语言:javascript
复制
let nums = seq { 9; 12; 4; 17; 9; 7; 13; }
nums |> Seq.mapi (fun i num  -> (num, i)) 

这给了(9, 0); (12, 1); etc...

SeqIEnumerable在C#中“懒惰”的含义相同。

您可以在这里阅读有关Seq.mapi的内容:

https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-seqmodule.html#mapi

在这里阅读有关map使用的更多信息:

https://fsharpforfunandprofit.com/posts/elevated-world/#map

票数 4
EN

Stack Overflow用户

发布于 2021-09-24 12:16:46

除了Sean的答案中提到的Seq.mapi函数之外,F#还有一个内置的Seq.indexed函数,它用索引来修饰序列。这并不能完全满足您的要求,因为索引成为元组的第一个元素,但取决于您的用例,它可能会起作用:

代码语言:javascript
复制
> 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来实现,这有点像折叠,但会产生一个惰性的状态序列。唯一棘手的是,您必须构造初始状态,然后处理序列的其余部分:

代码语言:javascript
复制
Seq.tail nums 
|> Seq.scan (fun (prevIndex, _) v -> (prevIndex+1, v)) (0, Seq.head nums)

这将不适用于空列表,即使该函数在逻辑上应该能够处理这个问题。

票数 4
EN

Stack Overflow用户

发布于 2021-09-25 17:49:30

  1. 使用for并不坏,或者说是错的。foryieldseq {}中是如何编写新的seq函数的,如果在Seq模块中提供的任何函数都不是最合适的。使用这种特殊结构既不错误,也不坏。它与C# foreachyield语法相同.

在有限的范围内使用可变的

  1. 也是正确的。如果变性人逃过了范围,那是个坏主意。例如,从函数.

返回可变值。

  1. 重要的是将可变的内容放在seq中,而不是放在外部。你的版本错了。

让我们假设

代码语言:javascript
复制
let xs = decorate [3;6;7;12;9]

for x in xs do
    printfn "%A" x

for x in xs do
    printfn "%A" x

现在你有两个版本的装饰。第一个版本

代码语言:javascript
复制
let decorate numSeq = 
    let mutable count = 0
    seq { 
        for num in numSeq do 
            yield (num,count)
            count <- count + 1
    }

将印刷:

代码语言:javascript
复制
(3, 0)
(6, 1)
(7, 2)
(12, 3)
(9, 4)
(3, 5)
(6, 6)
(7, 7)
(12, 8)
(9, 9)

或者换句话说。每次迭代序列时,都会在所有调用之间共享可变的。作为一般的提示。如果您想返回一个seq,那么将所有代码放入seq中。然后把seq {}放在=标志后面。如果你这样做的话。

代码语言:javascript
复制
let decorate numSeq = seq { 
    let mutable count = 0
    for num in numSeq do 
        yield (num,count)
        count <- count + 1
}

得到正确的输出:

代码语言:javascript
复制
(3, 0)
(6, 1)
(7, 2)
(12, 3)
(9, 4)
(3, 0)
(6, 1)
(7, 2)
(12, 3)
(9, 4)

此外,您还解释说,这个版本不是“可扩展的”。但是使用mapi的版本选择为“正确”。有同样的问题,它只提供一个索引,仅此而已。

如果您想要一个更通用的版本,您总是可以创建一个函数,它的值是函数参数。例如,您可以将上述函数更改为此代码。

代码语言:javascript
复制
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需要一个可以自由传递的状态,以及一个更改状态的函数。有了这个函数,您就可以编写:

代码语言:javascript
复制
decorate2 (fun state _ -> state+1) 0 [3;6;7;12;9]

函数签名与Seq.scan几乎相同,但仍然有点不同。但是,如果您想要创建一个indexed函数,可以像这样使用scan

代码语言:javascript
复制
let indexed xs =
    Seq.scan (fun (count,_) x -> (count+1,x)) (0,Seq.head xs) (Seq.skip 1 xs)

在我看来。与decoratedecorate2相比,这个版本更难理解、更难理解,也更难理解。

还有一张纸条。标准库中已经有一个Seq.indexed函数,它可以做您想做的事情。

代码语言:javascript
复制
for x in Seq.indexed [3;6;7;12;9] do
    printfn "%A" x

将打印

代码语言:javascript
复制
(0, 3)
(1, 6)
(2, 7)
(3, 12)
(4, 9)
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69313609

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档