'How do you split a list in scheme into n evenly sized chunks?

I am trying out some practice problems at in racket and am stuck at this one

What I want to do is define a list such as (partition '(1 2 3 4 5 6) 3) and then call return '((1 2) (3 4) (3 4)). (three evenly sized partition)

For Example (partition '(1 2 3 4) 3) would provide the output (((1) (2) (3 4)) Here 3 which is n is number of partitions to be made

Below I have tried my hands at the problem

(define (threesize n xs)
  (let loop ([grouped '()] [xs xs] [l (length xs])
    (cond
      [(empty? xs)
       (reverse grouped)]
      [(<= l n)
       (loop (cons xs grouped) '() 0)]
      [else (let-values ([(taken dropped) (split-at xs n)])
              (loop (cons taken grouped)
                    dropped
                    (- l n)))])))

Very new to programming and racket in general, so excuse my skills on that matter. Thank you



Solution 1:[1]

For those "new to programming and racket" it may be interesting to look at how a problem like this can be addressed using the HtDF (How to Design Functions) design method with a BSL (Beginning Student language) in DrRacket.

Start with a stub, incorporating signature and purpose, and a minimal "check-expect" example:
(Note: layout differs slightly from HtDF conventions)

(define (partition xs n) ;; (Listof X) Nat -> (Listof (Listof X)) ; *stub define* ;; *signature*
  ;; produce xs divided into n chunks                             ; *purpose statement*
  (list (list)))                                                  ; *stub body* (a valid result)

(check-expect (partition '() 1)  '( () ))                         ; *example*

This can be run in DrRacket:

Language: Beginning Student with List Abbreviations
The test passed!
>

The next steps in HtDF are template and inventory. There is a generic template for functions with a list argument, and because it can be seen that the function will have to "take" list elements, functions likely to be useful can be listed in inventory

(define (fn lox) ;; (Listof X) -> Y                  ; *template*
  (cond                                              ;
    [(empty? lox) ... ]  #|base case|# ;; Y          ;
    [else (...           #|something|# ;; X Y -> Y   ;
            (first lox) (fn (rest lox))) ]))         ;
            
(take xs n-to-take) ;; (Listof X) Nat -> (Listof X)  ; *inventory* 
(drop xs n-to-drop) ;; (Listof X) Nat -> (Listof X)  ;

#| (take and drop are not in BSL, so define them now) |#
(define (take xs n) ;; (Listof X) Nat -> (Listof X)
  ;; produce initial n elements of xs
  (if (zero? n)
      '()
      (cons (car xs) (take (cdr xs) (- n 1)))))
      
(define (drop xs n) ;; (Listof X) Nat -> (Listof X)
  ;; produce rest of xs after n elements
  (if (zero? n)
      xs
      (drop (cdr xs) (- n 1))))

Partitioning will consist of repeatedly taking and dropping a number of elements, the number generally being (quotient (length xs) n), so introduce this parameter now:

(define (partition xs n) ;; (Listof X) Nat -> (Listof (Listof X))
  ;; produce xs divided into n chunks of length (quotient (length xs) n) (when possible)
  (partition-at xs n (quotient (length xs) n)))

(define (partition-at xs n len) ;; (Listof X) Nat Nat -> (Listof (Listof X))
  ;; produce xs divided into n chunks of length len   (stub adapted from template)
  (cond
    [(= n 1) (list xs) ]
    [else    (error "not yet defined") ]))

(check-expect (partition    '(1 2) 1)   '( (1 2) ))
(check-expect (partition-at '(a b) 1 2) '( (a b) ))

Looking at the next check-expects (below) and the template, one can see how to complete partition-at (the generic template is for processing list elements one at a time, so here first becomes take and rest becomes drop):

(check-expect (partition-at '(a b) 2 1) '( (a) (b) ))
(check-expect (partition-at '(a b c d) 2 2) '( (a b) (c d) ))

(define (partition-at xs n len) ;; (Listof X) Nat Nat -> (Listof (Listof X))
  ;; produce xs divided into n chunks of length len (plus excess)
  (cond
    [(= n 1) (list xs) ]
    [else    (cons (take xs len)
                   (partition-at (drop xs len) (- n 1) len)) ]))

So the complete solution (given take and drop), with some more tests, is:

(define (partition xs n)  ;; (Listof X) Nat -> (Listof (Listof X))
  ;; produce xs divided into n chunks
  (partition-at xs n (quotient (length xs) n)))

(define (partition-at xs n len)  ;; (Listof X) Nat Nat -> (Listof (Listof X))
  ;; produce xs divided into n chunks of length len
  (cond
    [(= n 1) (list xs) ]
    [else    (cons (take xs len)
                   (partition-at (drop xs len) (- n 1) len)) ]))

(check-expect (partition '(1 2 3 4 5 6) 3) '((1 2) (3 4) (5 6)))
(check-expect (partition '(1 2 3 4) 3) '((1) (2) (3 4)))
(check-expect (partition '(1 2 3 4) 5) '(() () () () (1 2 3 4)))

(Of course, this is not the most efficient solution, but it didn't seem to require any big leaps of intuition, and once the HtDF systematic program design method has been learnt, simple functions can often be written down "correct by construction" :)

Solution 2:[2]

I have written this code for you. The idea is, you go once over the list to detect the size of each group (namely count) and a second time to create the groups.

(define partition
  (lambda (data num/groups)
    (let loop ((l data)
               (len 0)
               (ret (lambda (group rest __ ___)
                      (if (null? group)
                          rest
                          (cons group rest)))))
      (if (null? l)
        (ret '() '() 1 (integer-ceiling len num/groups))
        (loop
          (cdr l)
          (+ 1 len)
          (lambda (g r i count)
            (let ((g (cons (car l) g)))
              (if (= i count)
                (ret '() (cons g r) 1 count)
                (ret g r (+ 1 i) count)))))))))
(partition '(1 2 3 4 5 6) 3)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mnemenaut
Solution 2