Возьмите, пока общая сумма меньше значения

Я пытаюсь создать список четных целых чисел, в то время как сумма элементов в списке меньше заданного числа. Например, если порог k равен 20, то ожидаемый результат равен [0;2;4;6;8]

Я могу создать список, в котором наибольшее значение меньше порогового значения, например:

let listOfEvenNumbersSmallerThanTwenty =
    Seq.unfold (fun x -> Some(x, x + 1)) 0 // natural numbers
    |> Seq.filter (fun x -> x % 2 = 0) // even numbers
    |> Seq.takeWhile (fun x -> x <= 20)
    |> List.ofSeq

(Я знаю, что могу объединить развертку и фильтрацию в Some(x, x + 2), но эта задача носит ознакомительный характер)

Мне удалось создать другой список с промежуточным итогом меньше порога:

let runningTotal =
    listOfEvenNumbersSmallerThanTwenty 
    |> Seq.scan (+) 0
    |> Seq.filter (fun x -> x < 20)
    |> List.ofSeq

Но для этого я установил порог в listOfEvenNumbersSmallerThanTwenty (что намного больше, чем нужно) и потерял исходную последовательность. Я также пытался найти это, используя изменяемое значение, но мне не очень понравился этот маршрут.


person Thanasis K    schedule 22.01.2021    source источник


Ответы (2)


Вы можете создать небольшую предикатную функцию, которая будет инкапсулировать изменяемую сумму.

let sumLessThan threshold =
    let mutable sum = 0
    fun x ->
        sum <- sum + x
        sum < threshold

Использование очень простое, и его можно применить к любой последовательности.

Seq.initInfinite ((*) 2) |> Seq.takeWhile (sumLessThan 20)

Нет ничего плохого в использовании изменяемого состояния, когда оно инкапсулировано (проверьте использование изменяемой переменной в Seq модуль)

person Sergey Berezovskiy    schedule 22.01.2021
comment
Спасибо @Сергей за ответ. Я пытаюсь изучить F # в данный момент, и я думаю, что, насколько мне известно, я могу чрезмерно избегать изменяемого состояния. Я думаю, что нужен дополнительный опыт, чтобы понять, когда выбор изменчивости — это просто лучший способ. Пострадает ли это решение, если его попытаются запустить параллельно? - person Thanasis K; 28.01.2021
comment
@ThanasisK ну, она не должна работать параллельно, как и другие функции последовательности - exists, forAll, fold и т.д. Даже сложно представить, как это можно сделать параллельно - у вас будет два узких места с общим состоянием, которое надо синхронизировать между потоками — чтение элементов из разделенной исходной последовательности и проверка общей суммы. Мне это кажется синхронной многопоточной обработкой. - person Sergey Berezovskiy; 28.01.2021
comment
Кстати, Seq.takeWhile, Seq.initInfinite и особенно Seq.scan используют изменяемое состояние. Так что дело не в том, чтобы избежать мутаций, а в том, чтобы быть декларативным, а не императивным. ИМХО Seq.takeWhile (sumLessThan 20) очень декларативно :) - person Sergey Berezovskiy; 28.01.2021

Вот решение, которое я считаю довольно элегантным (хотя и не самым эффективным):

let evens = Seq.initInfinite (fun i -> 2 * i)
Seq.initInfinite (fun i -> Seq.take i evens)
    |> Seq.takeWhile (fun seq ->
        Seq.sum seq <= 20)
    |> Seq.last
    |> List.ofSeq
person brianberns    schedule 22.01.2021