1

Take integer partition problem for example, I can write the following code to output all partitions of a positive integer n:

(defn foo
  ([n] (foo n n []))
  ([n k buf]
    (if (zero? n)
      (println buf)
      (doseq [i (range 1 (inc (min n k)))]
        (foo (- n i) i (conj buf i))))))

Then (foo 5) outputs:

[1 1 1 1 1]
[2 1 1 1]
[2 2 1]
[3 1 1]
[3 2]
[4 1]
[5]

The question is how could I write a function bar which generates a lazy-seq containing such results? For example, I want (bar 5) generates ([1 1 1 1 1] [2 1 1 1] [2 2 1] [3 1 1] [3 2] [4 1] [5]).

2
  • 3
    Indeed, if you think for a minute about how lazy sequences work, you'll realize that being able to have all your state encapsulated for a recur call is pretty much exactly the same thing as having your state encapsulated for deferred (lazy) execution! Commented Nov 6, 2014 at 0:58
  • 1
    @CharlesDuffy I would appreciate it if you show me an example for this question. Commented Nov 6, 2014 at 1:26

2 Answers 2

2

Here is a roughly equivalent function generating the partitions as a sequence of sequences:

(defn bar
  ([n] (bar n n))
  ([n k]
   (if (zero? n)
     [[]]
     (for [i (range 1 (inc (min n k))), tail (bar (- n i) i)]
       (cons i tail)))))

For example,

(bar 5)
; ((1 1 1 1 1) (2 1 1 1) (2 2 1) (3 1 1) (3 2) (4 1) (5))

How lazy is bar?

  • for is lazy.
  • to make it lazier, wrap the body in a lazy-seq.

I have my doubts about the above.

  • The function recurses to depth n.
  • Wrapping the body in lazy-seq I suspect just stacks up lazy sequences, producing just as deep a recursive call stack on accessing the first element.

Besides, the same tails are repeatedly computed; the more so since (bar n k) is the same for all k >= n.

If performance of this function is a specific concern, there are iterative algorithms with constant time per step. As @CharlesDuffy's comment implies, these can be re-jigged to produce lazy sequences.


Why gaze into the crystal ball when you can read the book?

The standard namespace clojure.math.combinatorics, hosted here, contains a partition function that produces a lazy sequence of the partitions of any sequence of objects - fast. Integer partition is where we count the elements of each partition of identical objects. They come out in reverse lexicographic order.

For example

(map #(map count %) (combo/partitions (repeat 5 :whatever)))
;((5) (4 1) (3 2) (3 1 1) (2 2 1) (2 1 1 1) (1 1 1 1 1))

No doubt the code can be stripped down to deal with just this case.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you very much! This works fine for a small n. For a large n, I have to implement a better algorithm.
@SaltyEgg My comment is redundant. Mark Engelberg has translated the relevant Knuth algorithm into idiomatic Clojure, which the answer now points to.
1

Here is recursive solution. There are some ways to optimize it and code is not the best.

(defn partitions [n]
  (loop [m n
         res [(init-step n m)]]
    (let [l (last res)]
      (if (= m 1)
        res
        (if (last-step? (last res))
          (recur (- m 1) (vec (conj res (init-step n (- m 1)))))
          (recur m (next-step res)))))))


(defn init-step [n m]
  (if (= n m)
    [n]
    (loop [res [m (- n m)]]
      (let [l (last res)
            f (first res)]
        (if (<= l f)
          res
          (recur (vec (conj (vec (butlast res)) f (- l f)))))))))

(defn next-step [res]
  (let [input-vec (last res)
        cnt (count input-vec)
        i (.indexOf input-vec 1)
        j (if (> i -1) (- i 1) (- cnt 1))
        m (- cnt j)
        new-vec (conj (vec (take j input-vec)) (- (input-vec j) 1))]
    (conj res (vec (concat new-vec (repeat m 1))))))


(defn last-step? [input-vec]
  (if
    (or (nil? input-vec)
        (= (count input-vec) 1)
        (= (input-vec 1) 1)) true
    false))

(partitions 10)
#=> [[10] [9 1] [8 2] [8 1 1] [7 3] [7 2 1] [7 1 1 1] [6 4] [6 3 1] [6 2 1 1] [6 1 1 1 1] [5 5] [5 4 1] [5 3 1 1] [5 2 1 1 1] [5 1 1 1 1 1] [4 4 2] [4 4 1 1] [4 3 1 1 1] [4 2 1 1 1 1] [4 1 1 1 1 1 1] [3 3 3 1] [3 3 2 1 1] [3 3 1 1 1 1] [3 2 1 1 1 1 1] [3 1 1 1 1 1 1 1] [2 2 2 2 2] [2 2 2 2 1 1] [2 2 2 1 1 1 1] [2 2 1 1 1 1 1 1] [2 1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1 1 1]]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.