liamjc
liamjc

Reputation: 165

Adding a list of numbers based on an operator

I am really new to F# and am struggling to adapt to the whole functional programming mindset. Basically I am trying to figure out how to iterate through a list of numbers and add them up while the sum is less than a certain number (200 for instance).

At the moment I have something along the lines of the following:

let nums = [20 .. 50]
let mutable total = 0

let addUp (num, (total : byref<int>)) = 
    if (num + total < 200) then
        total <- total + num

for num in nums do
    addUp (num, &total)

This does get the job done, but I feel like there must be a more appropriate way to do this while still remaining within the style of functional programming, and without out having to rely on a mutable. I still feel like I'm approaching things from an imperative mindset.

I was thinking I could somehow use List.fold to do this but after some experimentation I was unable to figure out how to utilize it in conjunction with the whole while < 200 thing. Any help would be hugely appreciated and I apologize in advance if this is a commonly asked and answered question (I couldn't find anything through my own search).

Upvotes: 1

Views: 107

Answers (2)

Be Brave Be Like Ukraine
Be Brave Be Like Ukraine

Reputation: 7735

For educational purposes, the answer above is fine as it lets you see what exactly happens within the fold or reduce.

For a real project, it is better to use existing library functions. You can even separate summation and bound checking. Consider this:

let addUp threshold xs =
    xs
    |> Seq.scan (+) 0                         // line 1
    |> Seq.takeWhile (fun x -> x < threshold) // line 2
    |> Seq.last                               // line 3

// Usage:
let nums = Seq.initInfinite ( (+) 20)         // line 4
nums
|> addUp 200
|> printfn "%d"       // prints "188"

A bit of explanation:

  • line 1 is a contraction of Seq.scan (fun state x -> state + x) 0 so it actually returns a sequence of intermediate sums (20, 41, 63, ...);
  • in line 2, we take only the elements that match or filtering predicate;
  • in line 3, we simply take the last element (that matched the filtering above);
  • in line 4, again, it's a contraction of Seq.initInfinite (fun x -> 20+x). I took my liberty to make your data an infinite sequence (20, 21, 22, ...), but it still works.

Note, the code looks like three calls, but the sequence is evaluated only once.

Note, in line 2, don't try contraction like above. The reason is that (<) threshold evaluates to fun x -> threshold < x which is wrong. If you. however, use (>) threshold, it reads counter-intuitive and confusing.

Note, there's no error checking in the function. Calling it with an empty source sequence will crash at Seq.last.

Upvotes: 1

kimsk
kimsk

Reputation: 2231

First, let's try to avoid mutable variable and one way to do it is to create a recursive function to iterate through the list with the latest total.

let rec addUp total nums =
    match nums with
    | [] -> total
    | num::tl -> 
        let newTotal = num + total
        if newTotal < 200 then
            addUp newTotal tl
        else
            total

addUp 0 nums

We create a recursive function addUp which accepts total and the list of numbers. The function extracts the first number of the list and adds it to the total if the new total is less than 200. Since we might still have more numbers on the list, we call addUp again with the new total and the rest of the numbers. Otherwise, we stop and return the new total.

We can also use List.fold which makes the code cleaner, but the whole list will be iterated. Here is the code:

List.fold (fun total num ->
            let newTotal = num + total
            if newTotal < 200 then
                newTotal
            else
                total
        ) 0 nums

Since the output is the same type as the member of the input, we can use List.reduce and get rid of initial state (0).

List.reduce (fun total num ->
            let newTotal = num + total
            if newTotal < 200 then
                newTotal
            else
                total
        ) nums

Hope this helps.

Upvotes: 1

Related Questions