中断迭代并获得值和状态?

时间:2018-09-20 22:58:28

标签: f#

我需要在列表中的每个项目上调用一个函数;并在函数返回-1时立即退出。我需要返回函数结果和字符串“ Done”或“ Error”的总和。

let input = seq { 0..4 } // fake input

let calc1 x = // mimic failing after input 3. It's a very expensive function and should stop running after failing
    if x >= 3 then -1 else x * 2

let run input calc = 
    input 
    |> Seq.map(fun x -> 
        let v = calc x
        if v = -1 then .... // Error occurred, stop the execution if gets -1. Calc will not work anymore
        v)
     |> Seq.sum, if hasError then "Error" else "Done"  

run input calc // should return (6, "Error")
run input id   // should return (20, "Done")

3 个答案:

答案 0 :(得分:4)

有效地 准确实现惯用方式所要求的最简单方法是使用内部递归函数遍历序列:

let run input calc =
    let rec inner unprocessed sum =
        match unprocessed with
        | [] -> (sum, "Done")
        | x::xs -> let res = calc x
                   if res < 0 then (sum, "Error") else inner xs (sum + res)
    inner (input |> Seq.toList) 0

然后run (seq {0..4}) (fun x -> if x >=3 then -1 else x * 2)返回(6,"Error")的同时 run (seq [0;1;2;1;0;0;1;1;2;2]) (fun x -> if x >=3 then -1 else x * 2)返回(20, "Done")

答案 1 :(得分:2)

下面显示的相同内容的更有效版本。这意味着它现在实际上是@GeneBelitski答案的副本。

let run input calc = 
    let inputList = Seq.toList input
    let rec subrun inp acc = 
        match inp with
        | [] -> (acc, "Done")
        | (x :: xs) -> 
            let res = calc x
            match res with
            | Some(y) -> subrun xs (acc + y)
            | None -> (acc, "Error")
    subrun inputList 0

请注意,下面的此功能非常慢,可能是因为它使用了Seq.tail(我认为这与List.tail相同)。我留给后代。

我可以想到的最简单的方法是在F#中使用尾递归函数。

let run input calc = 
    let rec subrun inp acc = 
        if Seq.isEmpty inp then
            (acc, "Done")
        else
            let res = calc (Seq.head inp)
            match res with
            | Some(x) -> subrun (Seq.tail inp) (acc + x)
            | None -> (acc, "Error")
    subrun input 0

我不确定100%的效率会是多少。以我的经验,有时由于某种原因,我自己的尾递归函数似乎比使用内置的高阶函数慢得多。这至少应该使您获得正确的结果。


下面的内容虽然显然没有回答实际的问题,但为了对某人有用而保留了它们。

处理此问题的典型方法是使calc函数返回Option或Result类型,例如

let calc1 x = if x = 3 then None else Some(x*2)

,然后将其映射到您的输入。之后,您可以轻松完成

之类的操作
|> Seq.exists Option.isNone

查看结果seq中是否存在None(如果需要相反的结果,可以将其管道传递给not)。

如果您只需要从列表中消除无,则可以使用

Seq.choose id

这将消除所有“无”,同时保持选项不变。

要汇总列表,假设您曾经选择只剩下Somes,那么您可以

Seq.sumBy Option.get

答案 2 :(得分:0)

这是使用Result单子的单子方式。

首先,我们创建函数calcR,如果calc返回-1,则返回Error,否则返回Ok,其值是:

let calcR f x = 
    let r = f x
    if  r = -1 then Error "result was = -1" else
    Ok  r

然后,我们创建函数sumWhileOk,该函数在输入上使用Seq.fold,只要结果为Ok,就将它们相加。

let sumWhileOk fR = 
    Seq.fold(fun totalR v -> 
        totalR 
        |> Result.bind(fun total -> 
            fR v 
            |> Result.map      (fun r -> total + r) 
            |> Result.mapError (fun _ -> total    )
        ) 
    ) (Ok 0)

Result.bindResult.map仅在提供的值为Ok时调用它们的lambda函数,如果提供的值为Error,则会被绕过。 Result.mapError用于将来自calcR的错误消息替换为当前总数作为错误。

这种方式称为:

input |> sumWhileOk (calcR id)
// returns: Ok 10

input |> sumWhileOk (calcR calc1)
// return:  Error 6