How to find partitions of a list in Scheme

2019-06-27 23:54发布

问题:

Say there is any given list in Scheme. This list is ‘(2 3 4)

I want to find all possible partitions of this list. This means a partition where a list is separated into two subsets such that every element of the list must be in one or the other subsets but not both, and no element can be left out of a split.

So, given the list ‘(2 3 4), I want to find all such possible partitions. These partitions would be the following: {2, 3} and {4}, {2, 4} and {3}, and the final possible partition being {3, 4} and {2}.

I want to be able to recursively find all partitions given a list in Scheme, but I have no ideas on how to do so. Code or psuedocode will help me if anyone can provide it for me! I do believe I will have to use lambda for my recursive function.

回答1:

I discuss several different types of partitions at my blog, though not this specific one. As an example, consider that an integer partition is the set of all sets of positive integers that sum to the given integer. For instance, the partitions of 4 is the set of sets ((1 1 1 1) (1 1 2) (1 3) (2 2) (4)).

The process is building the partitions is recursive. There is a single partition of 0, the empty set (). There is a single partition of 1, the set (1). There are two partitions of 2, the sets (1 1) and (2). There are three partitions of 3, the sets (1 1 1), (1 2) and (3). There are five partitions of 4, the sets (1 1 1 1), (1 1 2), (1 3), (2 2), and (4). There are seven partitions of 5, the sets (1 1 1 1 1), (1 1 1 2), (1 2 2), (1 1 3), (1 4), (2 3) and (5). And so on. In each case, the next-larger set of partitions is determined by adding each integer x less than or equal to the desired integer n to all the sets formed by the partition of nx, eliminating any duplicates. Here's how I implement that:

Petite Chez Scheme Version 8.4
Copyright (c) 1985-2011 Cadence Research Systems

> (define (set-cons x xs)
    (if (member x xs) xs
      (cons x xs)))
> (define (parts n)
    (if (zero? n) (list (list))
      (let x-loop ((x 1) (xs (list)))
        (if (= x n) (cons (list n) xs)
          (let y-loop ((yss (parts (- n x))) (xs xs))
            (if (null? yss) (x-loop (+ x 1) xs)
              (y-loop (cdr yss)
                      (set-cons (sort < (cons x (car yss)))
                                xs))))))))
> (parts 6)
((6) (3 3) (2 2 2) (2 4) (1 1 4) (1 1 2 2) (1 1 1 1 2)
     (1 1 1 1 1 1) (1 1 1 3) (1 2 3) (1 5))

I'm not going to solve your homework for you, but your solution will be similar to the one given above. You need to state your algorithm in recursive fashion, then write code to implement that algorithm. Your recursion is going to be something like this: For each item in the set, add the item to each partition of the remaining items of the set, eliminating duplicates.

That will get you started. If you have specific questions, come back here for additional help.

EDIT: Here is my solution. I'll let you figure out how it works.

(define range (case-lambda ; start, start+step, ..., start+step<stop
  ((stop) (range 0 stop (if (negative? stop) -1 1)))
  ((start stop) (range start stop (if (< start stop) 1 -1)))
  ((start stop step) (let ((le? (if (negative? step) >= <=)))
    (let loop ((x start) (xs (list)))
      (if (le? stop x) (reverse xs) (loop (+ x step) (cons x xs))))))
  (else (error 'range "unrecognized arguments"))))

(define (sum xs) (apply + xs)) ; sum of elements of xs

(define digits (case-lambda ; list of base-b digits of n
  ((n) (digits n 10))
  ((n b) (do ((n n (quotient n b))
              (ds (list) (cons (modulo n b) ds)))
             ((zero? n) ds)))))

(define (part k xs) ; k'th lexicographical left-partition of xs
  (let loop ((ds (reverse (digits k 2))) (xs xs) (ys (list)))
    (if (null? ds) (reverse ys)
      (if (zero? (car ds))
          (loop (cdr ds) (cdr xs) ys)
          (loop (cdr ds) (cdr xs) (cons (car xs) ys))))))

(define (max-lcm xs) ; max lcm of part-sums of 2-partitions of xs
  (let ((len (length xs)) (tot (sum xs)))
    (apply max (map (lambda (s) (lcm s (- tot s)))
                    (map sum (map (lambda (k) (part k xs))
                                  (range (expt 2 (- len 1)))))))))

(display (max-lcm '(2 3 4))) (newline) ; 20
(display (max-lcm '(2 3 4 6))) (newline) ; 56


回答2:

You can find all 2-partitions of a list using the built-in combinations procedure. The idea is, for every element of a (len-k)-combination, there will be an element in the k-combination that complements it, producing a pair of lists whose union is the original list and intersection is the empty list.

For example:

(define (2-partitions lst)
  (define (combine left right)
    (map (lambda (x y) (list x y)) left right))
  (let loop ((m (sub1 (length lst)))
             (n 1))
    (cond
      ((< m n) '())
      ((= m n)
       (let* ((l (combinations lst m))
              (half (/ (length l) 2)))
         (combine (take l half)
                  (reverse (drop l half)))))
      (else
       (append
        (combine (combinations lst m)
                 (reverse (combinations lst n)))
        (loop (sub1 m) (add1 n)))))))

then you can build the partitions as:

(2-partitions '(2 3 4))
=> '(((2 3) (4)) 
     ((2 4) (3)) 
     ((3 4) (2)))
(2-partitions '(4 6 7 9))
=> '(((4 6 7) (9))
     ((4 6 9) (7))
     ((4 7 9) (6))
     ((6 7 9) (4))
     ((4 6) (7 9))
     ((4 7) (6 9))
     ((6 7) (4 9)))

Furthermore, you can find the max lcm of the partitions:

(define (max-lcm lst)
  (define (local-lcm arg)
    (lcm (apply + (car arg))
         (apply + (cadr arg))))
  (apply max (map local-lcm (2-partitions lst))))

For example:

(max-lcm '(2 3 4))
=> 20
(max-lcm '(4 6 7 9))
=> 165


回答3:

To partition a list is straightforward recursive non-deterministic programming.

Given an element, we put it either into one bag, or the other.

The very first element will go into the first bag, without loss of generality.

The very last element must go into an empty bag only, if such is present at that time. Since we start by putting the first element into the first bag, it can only be the second:

(define (two-parts xs)
  (if (or (null? xs) (null? (cdr xs)))
    (list  xs  '())
    (let go ((acc (list  (list (car xs))  '()))     ; the two bags
             (xs  (cdr xs))                         ; the rest of list
             (i   (- (length xs) 1))                ;  and its length
             (z   '()))
      (if (= i 1)                          ; the last element in the list is reached:
        (if (null? (cadr acc))                               ; the 2nd bag is empty:
          (cons  (list  (car acc)  (list (car xs)))          ; add only to the empty 2nd
                 z)                                                     ; otherwise,
          (cons  (list  (cons (car xs) (car acc))  (cadr acc))          ; two solutions, 
                 (cons  (list  (car acc)  (cons (car xs) (cadr acc)))   ; adding to
                        z)))                                    ; either of the two bags;
        (go (list  (cons (car xs) (car acc))  (cadr acc))    ; all solutions after
            (cdr xs)                                         ; adding to the 1st bag
            (- i 1)                                               ;   and then,
            (go (list  (car acc)  (cons (car xs) (cadr acc)))     ;   all solutions
                (cdr xs)                                          ;   after adding
                (- i 1)                                           ;   to the 2nd instead
                z))))))

And that's that!

In writing this I was helped by following this earlier related answer of mine.

Testing:

(two-parts (list 1 2 3))
; => '(((2 1) (3)) ((3 1) (2)) ((1) (3 2)))

(two-parts (list 1 2 3 4))
; =>     '(((3 2 1) (4))
;          ((4 2 1) (3))
;          ((2 1) (4 3))
;          ((4 3 1) (2))
;          ((3 1) (4 2))
;          ((4 1) (3 2))
;          ((1) (4 3 2)))

It is possible to reverse the parts before returning, or course; I wanted to keep the code short and clean, without the extraneous details.

edit: The code makes use of a technique by Richard Bird, of replacing (append (g A) (g B)) with (g' A (g' B z)) where (append (g A) y) = (g' A y) and the initial value for z is an empty list.

Another possibility is for the nested call to go to be put behind lambda (as the OP indeed suspected) and activated when the outer call to go finishes its job, making the whole function tail recursive, essentially in CPS style.