6

给定一个列表,我如何一次处理N个项目?Ruby 有这样做each_slice的方法Enumerable;Lisp 的等价物是什么?

4

4 回答 4

7

Common Lisploop可以很好地用于此目的,如以下两个示例所示。第一个示例(x y z)在列表中循环。但是,默认步骤是cdr( rest),所以如果列表是(1 2 3 4 5),你会得到(1 2 3)(2 3 4),等等,对于(x y z)

CL-USER> (loop for (x y z) on '(1 2 3 4 5 6 7 8 9 10 11 12)
              do (print (list z y x)))

(3 2 1) 
(4 3 2) 
(5 4 3) 
(6 5 4) 
(7 6 5) 
(8 7 6) 
(9 8 7) 
(10 9 8) 
(11 10 9) 
(12 11 10) 
(NIL 12 11) 
(NIL NIL 12) 
NIL

如果您不希望迭代之间的重叠,请将步进函数指定为在列表中向下移动的更远的东西。例如,如果您一次拉三个元素,请使用cdddr

CL-USER> (loop for (x y z) on '(1 2 3 4 5 6 7 8 9 10 11 12) by 'cdddr
              do (print (list z y x)))
(3 2 1) 
(6 5 4) 
(9 8 7) 
(12 11 10) 
NIL

用这种技术实现分区

each_slice另一个答案实现了使用辅助功能的对应物。但是,请注意partition(在这个意义上)只是each_slice使用身份功能。这表明我们应该能够使用上面的习语来实现它。匿名函数

(lambda (list)
  (nthcdr n list))

是我们需要的阶跃函数。由于直到运行时我们才知道单元格有多少元素,所以我们不能像上面那样绑定每个元素(x y z)。当我们下台并提取子序列n 个元素时,我们确实必须匹配列表的每个尾部。这looppartition.

CL-USER> (defun partition (list cell-size)
           (loop for cell on list by #'(lambda (list)
                                         (nthcdr cell-size list))
              collecting (subseq cell 0 cell-size)))
PARTITION

CL-USER> (partition '(1 2 3 4 5 6) 2)
((1 2) (3 4) (5 6))
于 2013-06-19T19:17:23.777 回答
3

如果您想在谓词上拆分列表(而不是固定长度的子列表),我会推荐nsplit-list.

对于固定长度的子列表,您可能需要使用loop

(defun by-N (list n fun) 
  (loop for tail on list by (lambda (l) (nthcdr n l)) 
    do (funcall fun (subseq tail 0 (min (length tail) n)))))
(by-n (loop for i from 0 to 20 collect i) 5 #'print)

(0 1 2 3 4) 
(5 6 7 8 9) 
(10 11 12 13 14) 
(15 16 17 18 19) 
(20) 

请注意,这不是很有效(它会扫描列表而不是必要的,并分配一个新的子列表以传递给fun)。

高效的版本更复杂:

(defun batch-map (list batch-size function)
  "Call FUNCTION on sublists of LIST of size BATCH-SIZE.
Returns the list of return values of FUNCTION."
  (do ((tail list (cdr end)) end ret (bs1 (1- batch-size)))
      ((endp tail) (nreverse ret))
    (setq end (nthcdr bs1 tail))
    (if (consp end)
        (let ((next (cdr end)))
          (setf (cdr end) nil)
          (unwind-protect (push (funcall function tail) ret)
            (setf (cdr end) next)))
        (push (funcall function tail) ret))))
于 2013-06-19T19:30:46.747 回答
3
(defun partition-helper (lst acc x)
  (if (< (length lst) x)
    acc
    (partition-helper (subseq lst x) (cons (subseq lst 0 x) acc) x)))

(defun partition (lst x)
  (reverse (partition-helper lst '() x)))

那么你也能:

[25]> (PARTITION '(1 2 3 4 5 6) 2)
((1 2) (3 4) (5 6))

或者:

[26]> (PARTITION '(1 2 3 4 5 6) 3)
((1 2 3) (4 5 6))

然后mapcar在列表上一次处理 2 或 3 个元素。

于 2013-06-19T19:07:46.717 回答
0

所有答案都是实用的并且可以使用,但我相信没有一个完全复制 Ruby 的行为:

> 1.upto(7).each_slice(3) { |x, y, z| p [x, y, z] }
[1, 2, 3]
[4, 5, 6]
[7, nil, nil]

为了模拟 Ruby,我相信正确的代码类似于:

CL-USER> (defun each-slice (n list thunk)
  (apply thunk (loop for i below n collect (nth i list)))

  (if (> (length list) n)
      (each-slice n (subseq list n) thunk)))

调用时生成类似的响应:

CL-USER> (each-slice 3 '(1 2 3 4 5 6 7) (lambda (x y z) (print (list x y z))))

(1 2 3) 
(4 5 6) 
(7 NIL NIL) 
NIL
于 2020-04-15T10:14:54.943 回答