2

I am trying to generate a list of even integers while the sum of the items in the list is less equal a given number. For instance if the threshold k is 20, then the expected output is [0;2;4;6;8]

I can generate a list where the largest value is smaller by the threshold like this:

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

(I know that I can combine the unfold and filter to Some(x, x + 2) but this task is for educational purposes)

I managed to create a different list with a running total smaller than the threshold:

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

But in order to do that, I have set the threshold in listOfEvenNumbersSmallerThanTwenty (which is way more than the items needed) and I have lost the initial sequence. I did also try to find that using a mutable value but didn't really like that route.

Thanasis K
  • 125
  • 6

2 Answers2

3

Here's a solution that I think is pretty elegant (although not the most efficient):

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
Brian Berns
  • 15,499
  • 2
  • 30
  • 40
3

You can create a small predicate function that will encapsulate a mutable sum.

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

Usage is very simple and it can be applied to any sequence

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

There is nothing bad in using mutable state when its encapsulated (check usages of the mutable variable in Seq module)

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • Thanks @Sergey for your answer. I am trying to learn F# at the moment and I think I may be over-avoiding mutable state as of know. I think it needs further experience in order to know when opting for mutability is simply the best way. Would that solution suffer if tried to run it in parallel? – Thanasis K Jan 28 '21 at 12:53
  • @ThanasisK well, it's not supposed to run in parallel, like other sequence functions - exists, forAll, fold etc. It's even hard to imagine how this can be done in parallel - you will have two bottlenecks with a shared state that must be synchronized between threads - reading items from the shrared source sequence and checking the shared sum. That looks like synchronous multithreaded processing to me. – Sergey Berezovskiy Jan 28 '21 at 16:59
  • BTW `Seq.takeWhile`, `Seq.initInfinite`, and especially `Seq.scan` use mutable state. So it's not about avoiding mutations - it's about being declarative instead of being imperative. IMHO `Seq.takeWhile (sumLessThan 20)` is very declarative :) – Sergey Berezovskiy Jan 28 '21 at 17:14