10
votes

I am new to Clojure programming, and would like to know what is the idiomatic way to do the following thing:

  1. I would like to sum a collection of numbers nums, which may contains a large number of numbers, let's assume there are only positive numbers.

  2. I don't care the exact sum if the sum is very large. For example, if the sum of the numbers is larger than 9999, I would simply return 10000 without summing the remaining numbers at all.

If I implement it with some OO language such as Java, I may do it like below:

private int sum(int[] nums) {
  int sum = 0;
  for(int n : nums) {
    if(sum > 9999) {
      sum = 10000;
      break;
    } else {
      sum += n;
    }
  }
  return sum;
}

A naive implementation in Clojure may look like:

(let [sum (reduce + nums)]
   (if (> sum 9999) 10000 sum))

However, this seems to waste some CPU resource to sum the entire collection of numbers, which is not desired. I am looking for something like take-while function but for reduce, but cannot find it. Is there something like:

(reduce-while pred f val coll)

Or is there any other Clojure idiomatic way to solve this problem? I think the solution can be applied to a set of problems requiring similar logic.

Any comment is appreciated. Thanks.

2

2 Answers

20
votes

If you're using Clojure 1.5.x then you may take advantage of new reduced function:

(reduce #(if (> %1 9999) (reduced 10000) (+ %1 %2)) nums)
11
votes

One of the lesser known Clojure functions seems to be reductions. It will give you all the intermediate results of your computation:

(reductions + (range 4)) ;; => (0 1 2 3)
(reduce + (range 4))     ;; => 3

The last element of reductions' result seq will be the reduced value. There are multiple ways to enforce your predicate, e.g. using some:

(let [sums (reductions + nums)]
  (if (some #(> % 9999) sums)
    10000
    (last sums)))

The reduce/reduced version given by @leonid-beschastny is probably faster (no lazy sequence overhead, reducers, ...) but this one will work in earlier Clojure versions, too.