问题 在Racket中对列表进行分区


在我正在使用Racket的应用程序中,我需要获取一个数字列表并将列表分成连续数字的子列表: (在实际应用中,我实际上是分区对由数字和一些数据组成的对,但原理是相同的。)

即如果我的程序被调用 chunkify 然后:

(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 '()) -> '(())  

等等

我在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))  

这工作得很好,但我想知道如果没有一个更直接的简单方法做到这一点,有一些摆脱“带值调用”和需要反转列表的Racket的表现力在程序等,也许某种方式完全不同。

我的第一次尝试是基于一个带有收藏家的模式 “小计划者” 这比上面更简单:

(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)]))) 

我正在寻找的是简单,简洁和直接的东西。


6545
2018-04-29 00:55


起源

这更像是“请查看我的代码”,而不是“我的代码有什么问题”,所以我认为这属于www.codereview.stackexchange.com - Liam McInroy


答案:


我是这样做的:

;; 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))]))

不过,它不同意你的最终测试用例:

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

我认为我的答案更自然;如果你真的想要答案的话 '(()),然后我重命名 chunkify 并编写一个专门处理空案例的包装器。

如果您希望避免相互递归,可以使辅助函数将剩余列表作为第二个值返回而不是调用 chunkify 在它上面,像这样:

;; 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)]))

4
2018-04-29 04:15



谢谢瑞恩。最终的测试用例无关紧要,所以'()或'(())都可以。我只是把它放进去,因为我测试它只是为了看到我没有得到一些奇怪的东西。 - Harry Spier
再次感谢。相互递归是我正在寻找的模式,但无法弄清楚。一个用于创建块的递归和另一个用于将它们组合到块列表中的递归。 - Harry Spier


我可以想到一个简单,直接的解决方案,使用一个只有原始列表操作和尾递归的过程(没有 valueslet-valuescall-with-values) - 而且效率很高。它适用于所有测试用例,但需要添加几个测试用例 if 初始化期间用于处理空列表案例的表达式。由您来决定这是否简洁:

(define (chunkify lst)
  (let ((lst (reverse lst))) ; it's easier if we reverse the input list first
    (let loop ((lst (if (null? lst) '() (cdr lst)))        ; list to chunkify
               (cur (if (null? lst) '() (list (car lst)))) ; current sub-list
               (acc '()))                                  ; accumulated answer
      (cond ((null? lst)                    ; is the input list empty?
             (cons cur acc))
            ((= (add1 (car lst)) (car cur)) ; is this a consecutive number?
             (loop (cdr lst) (cons (car lst) cur) acc))
            (else                           ; time to create a new sub-list
             (loop (cdr lst) (list (car lst)) (cons cur acc)))))))

3
2018-04-29 02:18



谢谢奥斯卡,这非常快(距我的帖子40分钟)。当我想到我提出解决方案需要多长时间时,它有点谦卑:-)干杯。 - Harry Spier


还有另一种方法。

#lang racket

(define (split-between pred xs)
  (let loop ([xs xs]
             [ys '()]
             [xss '()])
    (match xs
      [(list)                 (reverse (cons (reverse ys) xss))]
      [(list x)               (reverse (cons (reverse (cons x ys)) xss))]
      [(list x1 x2 more ...)  (if (pred x1 x2) 
                                  (loop more (list x2) (cons (reverse (cons x1 ys)) xss))
                                  (loop (cons x2 more) (cons x1 ys) xss))])))

(define (consecutive? x y)
  (= (+ x 1) y))

(define (group-consecutives xs)
  (split-between (λ (x y) (not (consecutive? x y))) 
                 xs))


(group-consecutives '(1 2 3 5 6 7 9 10 11))
(group-consecutives '(1 2 3))
(group-consecutives '(1 3 4 5 7 9 10 11 13))
(group-consecutives '(1))
(group-consecutives '())

3
2018-04-29 15:26



谢谢soegaard,特别是用于展示模式匹配的使用。 - Harry Spier


我要玩。

在核心,这并不是什么与什么有很大不同 已经提供但它确实把它放在for / fold循环中。我有 我觉得他们做的很多,就像for循环一样 更“可查看”(不一定是可读的)代码。但是,(IMO - 在舒适的早期阶段 racket / scheme我认为最好坚持使用递归表达式。

(define (chunkify lst)  
    (define-syntax-rule (consecutive? n chunk)     
      (= (add1 (car chunk)) n))
    (if (null? lst)
        'special-case:no-chunks
        (reverse 
         (map reverse
              (for/fold  ([store    `((,(car lst)))])
                         ([n         (cdr lst)])
                 (let*([chunk   (car store)])
                   (cond 
                    [(consecutive? n chunk)
                        (cons  (cons n chunk)  (cdr store))]
                    [else
                        (cons  (list n)  (cons chunk (cdr store)))])))))))


(for-each
 (ƛ (lst)  
    (printf "input   :  ~s~n" lst)
    (printf "output  :  ~s~n~n" (chunkify lst)))
 '((1 2 3 5 6 7 9 10 11)
   (1 2 3)
   (1 3 4 5 7 9 10 11 13)
   (1)
   ()))

1
2018-05-05 23:21



非常感谢dlm。使用Racket有很多不同的方法可以做。我和Ryan的解决方案一起走了,但我连续“通过”?作为一流的功能。因为我在应用程序中使用“Chunkify”和几种不同类型的列表列表。 - Harry Spier


这是我的版本:

(define (chunkify lst)
  (let loop ([lst lst] [last #f] [resint '()] [resall '()])
    (if (empty? lst) 
        (append resall (list (reverse resint)))
        (begin
          (let ([ca (car lst)] [cd (cdr lst)])
            (if (or (not last) (= last (sub1 ca)))
                (loop cd ca (cons ca resint) resall)
                (loop cd ca (list ca) (append resall (list (reverse resint))))))))))

它也适用于最后一个测试用例。


1
2018-05-06 11:26