Прервать итерацию и получить значения и состояния? - PullRequest
0 голосов
/ 21 сентября 2018

Мне нужно вызвать функцию для каждого элемента в списке;и немедленно выйдите, если функция вернет -1.Мне нужно вернуть сумму результатов функции и строку «Готово» или «Ошибка».

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 голосов
/ 21 сентября 2018

Простейшим способом эффективного достижения именно того, что идиоматически запрашивается, было бы использование внутренней рекурсивной функции для обхода последовательности:

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")

0 голосов
/ 12 октября 2018

Вот монадический способ сделать это, используя монаду 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.bind и Result.map вызывают только их лямбда-функции, только если предоставленное значение равно Ok, если оно равно Error, оно игнорируется.Result.mapError используется для замены сообщения об ошибке из calcR на текущее итоговое значение в качестве ошибки.

Это называется следующим образом:

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

input |> sumWhileOk (calcR calc1)
// return:  Error 6
0 голосов
/ 21 сентября 2018

Более эффективный вариант того же, что показан ниже.Это означает, что теперь это по сути копия ответа @ 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

, чтобы проверить, есть ли Nones в результирующем seq (вы можете направить его на нет, если хотите получить противоположный результат).

Если вам просто нужно исключить Nones из списка, вы можете использовать

Seq.choose id

, который удалит все None, оставив параметры без изменений.

Для суммирования списка, предполагая, что вы использоваливыберите остаться только с некоторыми, тогда вы можете сделать

Seq.sumBy Option.get
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...