例:
如何转换列表:
'(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。这个可以改进得更多还是“足够好”?
答案 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-to
和take
来解决此问题,这些函数的作用类似于drop
和(take-up-to 4 '(0 1 2))
=> '(0 1 2)
(drop-up-to 4 '(0 1 2))
=> '()
,但没有这个问题。因此,函数的示例用法如下所示:
take
这就像我要告诉你的那样。我建议你做这些事情:
drop
,take-up-to
,drop-up-to
和take-up-to
的实现,并使用它们来编写您尝试实现的功能。 编辑:以下是(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
结合了take
和drop
的功能,并将两个列表作为两个值返回。要使用多值返回函数,我们可以使用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}},以线性时间和恒定空间运行。