如何在Racket(Scheme)中将列表拆分为大小均匀的块?

时间:2012-01-04 11:09:45

标签: list scheme racket

例:
如何转换列表:
    '(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)

列表清单:
    '((0 1 2 3)(4 5 6 7)(8 9 10 11)(12 13 14 15))

根据目前为止提供的答案,这就是我提出的:

首先定义从列表的开头接收'n'个元素的函数:

(define (take-up-to n xs)
  (define (iter xs n taken)
    (cond
      [(or (zero? n) (empty? xs)) (reverse taken)]
      [else (iter (cdr xs) (- n 1) (cons (car xs) taken))]))
  (iter xs n '()))

第二个是列表其余部分的类似功能:

(define (drop-up-to n xs)
  (define (iter xs n taken)
    (cond
      [(or (zero? n) (empty? xs)) xs]
      [else (iter (cdr xs) (- n 1) (cons (car xs) taken))]))
  (iter xs n '()))

这可以作为一个函数完成,它返回两个值,而Racket有一个函数'split-at',产生相同的结果,但我这样做是为了练习。

PS。这是否正确使用尾递归?

比分裂成块可以这样写:

(define (split-into-chunks n xs)
  (if (null? xs)
      '()
      (let ((first-chunk (take-up-to n xs))
            (rest-of-list (drop-up-to n xs)))
        (cons first-chunk (split-into-chunks n rest-of-list)))))

PPS。这个可以改进得更多还是“足够好”?

6 个答案:

答案 0 :(得分:6)

SRFI-1 library(Racket提供,但我不记得如何导入它)中有一个常见的效用函数,名为take,它采用初始n列表中的元素:

(take 4 '(0 1 2 3 4 5 6 7 8))
=> '(0 1 2 3)

在同一个库中还有一个名为drop的函数,它从列表中删除了初始的n元素:

(drop 4 '(0 1 2 3 4 5 6 7 8))
=> '(4 5 6 7 8)

您可以使用这些功能将问题分解为更小的部分。因此,解决问题的第一个(但不正确的)近似值是:

(define (split-into-chunks n xs)
  (if (null? xs)
      '()
      (let ((first-chunk (take n xs))
            (rest (drop n xs)))
        (cons first-chunk (split-into-chunks n rest)))))

然而,正如我所指出的,这种解决方案并不是最理想的。为什么?由于(take n xs)列表xs的元素少于n时,n会出错。转换为您的问题,如果列表中有非take-up-to个元素,则会出现错误。但是,您可以通过编写一对函数drop-up-totake来解决此问题,这些函数的作用类似于drop(take-up-to 4 '(0 1 2)) => '(0 1 2) (drop-up-to 4 '(0 1 2)) => '() ,但没有这个问题。因此,函数的示例用法如下所示:

take

这就像我要告诉你的那样。我建议你做这些事情:

  • 编写您自己的droptake-up-todrop-up-totake-up-to的实现,并使用它们来编写您尝试实现的功能。
  • 浏览the documentation for the SRFI-1 library并熟悉其中的功能。很多这些列表问题都分解为这些函数的简单组合,因此您需要了解它们。
  • 了解如何将此库导入Racket。 (在那里帮不了你。)
  • 作为练习,请尝试编写自己的某些SRFI-1功能的实现。 (为了练习,可以稍微简化它们;例如,虽然这些函数中的许多函数将处理多个列表参数,但是为了练习目的,可以编写仅处理一个列表的版本。)

编辑:以下是(define (take-up-to n xs) (if (or (zero? n) (null? xs)) '() (cons (car xs) (take-up-to (- n 1) (cdr xs))))) 的简单实现:

{{1}}

可以进一步改进这一点,仅使用尾调用(因此在恒定空间中运行)。这是另一项练习。

答案 1 :(得分:3)

你问一个很好的通用问题,但我认为你想要的是使用字节串而不是列表的东西。这里有一些代码(包括错误检查),以及一个测试用例:

#lang racket

(require rackunit)

;; given a byte string, split it into a vector of byte-strings
;; of length 4
(define (split-bytes bytes)
  (define len (bytes-length bytes))
  (unless (= 0 (modulo len 4))
    (raise-type-error 'split-bytes
                      "byte string of length divisible by 4"
                      0 bytes))
  (for/vector ([i (in-range (/ len 4))])
     (subbytes bytes (* 4 i) (* 4 (add1 i)))))

(check-equal?
 (split-bytes
  #"hhoh.h9ew,n49othsv97")
 (vector #"hhoh"
         #".h9e"
         #"w,n4"
         #"9oth"
         #"sv97"))

答案 2 :(得分:3)

对我来说就像是

(define (split-by lst n)
   (if (not (empty? lst))
       (cons (take lst n) (split-by (drop lst n) n))
       '() ))

例如

(split-by '(3 2 1 43 54 25 100 -14 -42) 3)

产量

'((3 2 1) (43 54 25) (100 -14 -42))

答案 3 :(得分:2)

如果你想更好地解决这类问题,我高度推荐The Little Schemer。它将训练你以能够使这些问题易于处理的方式进行思考,并且只需要几个小时就能完成,覆盖到掩盖。

答案 4 :(得分:1)

另一种方法是提供一个更高阶的函数map-n,它从列表中取n个值并对它们应用函数:

(define (map-n n fn l . lists)
  (if (any (lambda(l)(< (length l) n)) (cons l lists))
      '()
      (cons (apply fn (append-map (lambda(l)(take l n)) (cons l lists)))
            (apply map-n n fn (map (lambda(l)(drop l n)) (cons l lists))))))

(e.g. (map-n 4 + '(1 2 3 4 5 6 7 8)) ===> (10 26))
(e.g. (map-n 3 (lambda (a b c) a) '(1 2 3 4 5 6)) ===> (1 4))

拥有此功能,可以简单地

(define (split-by n l)
  (map-n n list l))

缺点可能是如果列表的长度不能被n整除,则剩余部分将从结果中被拒绝。

另一种时髦的方法是创建一个将列表拆分为任意大小的块的函数:

(define (chunk-list l . chunk-sizes)
  (assert (and (list? l)
               (every natual? chunk-sizes)
               (<= (fold + 0 chunk-sizes) (length l))))
  (let loop ((result '())
             (sizes chunk-sizes)
             (rest l))
    (match sizes
      (()
       (reverse result))
      ((size . sizes)
       (let-values (((this rest) (split-at rest size)))
         (loop `(,this ,@result) sizes rest))))))

(e.g. (chunk-list '(1 2 3 4 5 6 7 8 9 10) 0 1 2 3 4)
      ===> (() (1) (2 3) (4 5 6) (7 8 9 10))

然后使用make-list定义split-by:

(define (split-by n l)
  (let ((size (quotient (length l) n)))
    (apply chunk-list l (make-list size n))))

请注意,map-n的定义使用any中的srfi-1函数,chunk-list使用Alex Shinn的pattern matcher(尽管可能可以使用普通的if,eq?,car和cdr轻松地重写

答案 5 :(得分:1)

如果您正在寻找尾递归解决方案,一种方法是使用名为let 的

(define (group n xs)
  (let loop ([grouped '()] [xs xs])
    (if (empty? xs)
        (reverse grouped)
        (loop (cons (take xs n) grouped)
              (drop xs n)))))

但是,如果xs有剩余元素,则会失败,因此我们需要添加一个检查此内容的案例:

(define (group n xs)
  (let loop ([grouped '()] [xs xs])
    (cond
      [(empty? xs)
       (reverse grouped)]
      [(<= (length xs) n)
       (loop (cons xs grouped) '())]
      [else (loop (cons (take xs n) grouped)
                  (drop xs n))])))

这有效,但我们可以做得更好。这里的问题是计算(length xs)线性时间中运行,因为找到简单列表长度的唯一方法是遍历整个列表。由于这是在一个循环中运行的次数与xs的大小成比例,因此该代码在二次时间运行时应该很容易在线性时间内完成它。我们可以通过计算xs的长度一次,然后在每次迭代中从中减去n来解决这个问题:

(define (group n xs)
  (let loop ([grouped '()] [xs xs] [l (length xs)])
    (cond
      [(empty? xs)
       (reverse grouped)]
      [(<= l n)
       (loop (cons xs grouped) '() 0)]
      [else (loop (cons (take xs n) grouped)
                  (drop xs n)
                  (- l n))])))

我们回到线性时间,同时仍然保留尾递归,因此保持恒定的空间。然而,我们可以做出另一项改进。 Racket函数split-at结合了takedrop的功能,并将两个列表作为两个值返回。要使用多值返回函数,我们可以使用let-values

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

这稍快一些,因为split-at可以避免忽略n部分功能中的第一个drop元素的重复工作,因为{{1}已经消耗了这些元素}}。但是,此代码并未考虑用户输入错误。如果用户提供的take大于n的长度,则会在返回xs时抛出错误。这很容易检查,但我们的代码正在通过所有这些嵌套向右扩展。除了检查这一点,我们可以将循环拆分为内部定义的函数:

(list xs)

此函数是尾递归,不会多次计算(define (group n xs) (define (loop grouped xs l) (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)))])) (let ([l (length xs)]) (if (>= n l) (list xs) (loop '() xs l)))) ,确保(length xs)求值为(group 4 '(1 2 3)),并且不均匀分组,'((1 2 3))求值为{{ 1}},以线性时间和恒定空间运行。