Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partitioning a list in Racket

Tags:

scheme

racket

In an application I'm working on in Racket I need to take a list of numbers and partition the list into sub-lists of consecutive numbers: (In the actual application, I'll actually be partitioning pairs consisting of a number and some data, but the principle is the same.)

i.e. if my procedure is called chunkify then:

(chunkify '(1 2 3  5 6 7  9 10 11)) -> '((1 2 3) (5 6 7) (9 10 11))  
(chunkify '(1 2 3)) ->  '((1 2 3))  
(chunkify '(1  3 4 5  7  9 10 11 13)) -> '((1) (3 4 5) (7) (9 10 11) (13))  
(chunkify '(1)) -> '((1))  
(chunkify '()) -> '(())  

etc.

I've come up with the following in Racket:

#lang racket  
(define (chunkify lst)  
  (call-with-values   
   (lambda ()  
     (for/fold ([chunk '()] [tail '()]) ([cell  (reverse lst)])  
       (cond  
         [(empty? chunk)                     (values (cons cell chunk) tail)]  
         [(equal? (add1 cell) (first chunk)) (values (cons cell chunk) tail)]  
         [else (values   (list cell) (cons  chunk tail))])))  
   cons))  

This works just fine, but I'm wondering given the expressiveness of Racket if there isn't a more straightforward simpler way of doing this, some way to get rid of the "call-with-values" and the need to reverse the list in the procedure etc., perhaps some way comepletely different.

My first attempt was based very loosely on a pattern with a collector in "The Little Schemer" and that was even less straightforward than the above:

(define (chunkify-list lst)  
 (define (lambda-to-chunkify-list chunk) (list chunk))

 (let chunkify1 ([list-of-chunks '()] 
                 [lst lst]
                 [collector lambda-to-chunkify-list])
   (cond 
     [(empty? (rest lst)) (append list-of-chunks (collector (list (first lst))))]
     [(equal? (add1 (first lst)) (second lst))  
      (chunkify1 list-of-chunks (rest lst)
                 (lambda (chunk) (collector (cons (first lst) chunk))))] 
     [else
      (chunkify1 (append list-of-chunks
                         (collector (list (first lst)))) (rest lst) list)]))) 

What I'm looking for is something simple, concise and straightforward.

like image 416
Harry Spier Avatar asked Apr 29 '12 00:04

Harry Spier


People also ask

How do you find the smallest number in a list racket?

You default case can be as follows: You use find-smallest to find the smallest of the rest of the list and compare this to the first element, eg. with < . The smallest should then be the result.

What is a list in Scheme?

In contrast to Scheme's unstructured data types, such as symbols and numbers, lists are structures that contain other values as elements. A list is an ordered collection of values. In Scheme, lists can be heterogeneous, in that they may contain different kinds of values.

How do you find the last element of a list in racquet?

First find the lenght of a list by cdring it down. Then use list-ref x which gives the x element of the list. For example list-ref yourlistsname 0 gives the first element (basically car of the list.) And (list-ref yourlistsname (- length 1)) gives the last element of the list.


1 Answers

Here's how I'd do it:

;; chunkify : (listof number) -> (listof (non-empty-listof number))
;; Split list into maximal contiguous segments.
(define (chunkify lst)
  (cond [(null? lst) null]
        [else (chunkify/chunk (cdr lst) (list (car lst)))]))

;; chunkify/chunk : (listof number) (non-empty-listof number)
;;               -> (listof (non-empty-listof number)
;; Continues chunkifying a list, given a partial chunk.
;; rchunk is the prefix of the current chunk seen so far, reversed
(define (chunkify/chunk lst rchunk)
  (cond [(and (pair? lst)
              (= (car lst) (add1 (car rchunk))))
         (chunkify/chunk (cdr lst)
                         (cons (car lst) rchunk))]
        [else (cons (reverse rchunk) (chunkify lst))]))

It disagrees with your final test case, though:

(chunkify '()) -> '()  ;; not '(()), as you have

I consider my answer more natural; if you really want the answer to be '(()), then I'd rename chunkify and write a wrapper that handles the empty case specially.

If you prefer to avoid the mutual recursion, you could make the auxiliary function return the leftover list as a second value instead of calling chunkify on it, like so:

;; chunkify : (listof number) -> (listof (non-empty-listof number))
;; Split list into maximal contiguous segments.
(define (chunkify lst)
  (cond [(null? lst) null]
        [else
         (let-values ([(chunk tail) (get-chunk (cdr lst) (list (car lst)))])
           (cons chunk (chunkify tail)))]))

;; get-chunk : (listof number) (non-empty-listof number)
;;          -> (values (non-empty-listof number) (listof number))
;; Consumes a single chunk, returns chunk and unused tail.
;; rchunk is the prefix of the current chunk seen so far, reversed
(define (get-chunk lst rchunk)
  (cond [(and (pair? lst)
              (= (car lst) (add1 (car rchunk))))
         (get-chunk (cdr lst)
                    (cons (car lst) rchunk))]
        [else (values (reverse rchunk) lst)]))
like image 142
Ryan Culpepper Avatar answered Oct 11 '22 19:10

Ryan Culpepper