lisp:对向量进行分块
如前所述,我试图通过实现lisp来自学lisp 我基本上没有使用lisp的经验,所以在js中微不足道的工作对我来说是陌生的 例如,我正在研究一个lisp:对向量进行分块,lisp,common-lisp,Lisp,Common Lisp,如前所述,我试图通过实现lisp来自学lisp 我基本上没有使用lisp的经验,所以在js中微不足道的工作对我来说是陌生的 例如,我正在研究一个..chunk方法的实现,它在js中采用一个数组和一个size变量,并按大小“chunk”数组: _.chunk(['a', 'b', 'c', 'd'], 2); // => [['a', 'b'], ['c', 'd']] _.chunk(['a', 'b', 'c', 'd'], 3); // => [['a', 'b', 'c'],
..chunk
方法的实现,它在js中采用一个数组和一个size
变量,并按大小“chunk”数组:
_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]
_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]
作为一个对常见lisp数据类型完全陌生的人,我假设类似的类型是向量,而不是数组,对吗
其次,我用算法解决这个问题的方法是保留length
变量和pointer
变量,并获取数组/向量的子集,[pointer-to-pointer+size],而pointer+size为(defun _.chunk (vector &optional (size 1 size-p))
(if (or (not size-p) (eq size 1))
vector
((let (
(array_length (array-total-size array))
(pointer)
)
???
))
)
)
对于这个实现,我将首先编写一个惯用的通用Lisp版本的chunk,它可以在CL程序中使用(高效等),然后编写一个仅围绕这些函数的薄薄lodash层 例如,我将首先编写一个helper函数,以允许与分块向量共享存储。置换数组是指具有偏移量和不同大小的另一个数组。将块作为原始向量的唯一视图可能很有用,这样它们都共享相同的底层存储阵列。这不仅仅是一种内存优化:当改变块或原始向量时,行为是不同的,因为其中一个的任何变化在另一个中都是可见的。但据我所知,lodash是(曾经是?)一种纯函数式语言,因此,如果不进行变异,共享一些数据是有意义的。有些语言将这种间接数组称为“切片”
因此,我还将使
chunk vector
接受:start
和:end
参数,就像通常所做的一样,以及sharedp
参数,该参数指定是否应与原始向量共享存储:
(defun chunk-vector (size vector &key start end sharedp)
(check-type size (integer 1))
(loop
with slicer = (if sharedp #'slice #'subseq)
and low = (or start 0)
and high = (or end (length vector))
for s from low below high by size
for e from (+ low size) by size
collect (funcall slicer vector s (min e high))))
注意:我假设nil
是end
的一个可能值,表示向量的结束,以反映subseq
的工作方式。我对start
也这样做,因为对于这些变量,可以使用nil值来表示“默认值”,而不会产生歧义。我还可以在lambda列表中定义默认值,就像tfb的回答中那样
以下是一些测试:
(chunk-vector 3 #(0 1 2 3 4 5 6 7 8 9) :sharedp t)
(#(0 1 2) #(3 4 5) #(6 7 8) #(9))
(chunk-vector 2 #(0 1 2 3 4 5 6 7 8 9))
(#(0 1) #(2 3) #(4 5) #(6 7) #(8 9))
(chunk-vector 1 #(0 1 2 3 4 5 6 7 8 9))
(#(0) #(1) #(2) #(3) #(4) #(5) #(6) #(7) #(8) #(9))
同样,您还可以定义块列表
函数,并根据序列类型将lodashchunck
函数分派到每个专用版本
这可以通过CLOS来完成,但因为这已经在另一个答案中演示过了,所以我将只定义单独的专用函数
下面是基于的块列表的实现。
我首先尝试在一个函数中混合所有情况,但这变得不必要的复杂
下面是第一个无界块函数:
(defun chunk-list/unbounded (size list)
(loop
for front = list then next
for next = (nthcdr size front)
collect (ldiff front next)
while next))
(defgeneric chunk (thing &key)
;; in real life we might want to specify some of the keyword
;; arguments at the GF level, but we won't
)
(defmethod chunk ((thing vector) &key
(size 1) (start 0) (end (length thing)) (sharedp nil))
(check-type size (integer 1))
(let ((slicer (if sharedp #'slice #'subseq)))
(loop for s from start below end by size
for e from (+ start size) by size
collect (funcall slicer thing s (min e end)))))
front
最初定义为list
,然后是每个步骤的next
的当前值
next
是下一个块,使用size
计算;这很好地处理了没有足够元素的列表,因为在这种情况下,nthcdr
只返回剩余的元素
处理end
参数需要更复杂的情况,为此,我们定义了有界版本,其中还有一个额外的上限
计数器,它在迭代的每个步骤中减少大小
。它表示要添加的剩余元素数,并与大小一起用于计算下一个块的大小(最小大小上限)
:
(defun chunk-list/bounded (size list upper-limit)
(loop
for front = list then next
for next = (nthcdr (min size upper-limit) front)
collect (ldiff front next)
do (decf upper-limit size)
while (and next (plusp upper-limit))))
最后,chunk list
根据end
是否为零对两个版本进行调度;调用在此处内联(因为我们可以):
一些例子:
(chunk-list 3 '(1 2 3 4 5 6 7))
((1 2 3) (4 5 6) (7))
(chunk-list 29 '(1 2))
((1 2))
(chunk-list 2 (alexandria:iota 100 :start 0) :start 10 :end 20)
((10 11) (12 13) (14 15) (16 17) (18 19))
我建议使用dotimes
对区块索引进行逐步切片迭代(因为您可以很容易地找到区块的总量)
这可能类似于以下内容:
(defun chunked (seq size)
(let* ((total (length seq))
(amount (ceiling total size))
(res (make-array amount :fill-pointer 0)))
(dotimes (i amount res)
(vector-push (subseq seq (* i size) (min (* (1+ i) size) total))
res))))
CL-USER> (chunked "abcdefgh" 3)
;; #("abc" "def" "gh")
CL-USER> (chunked #*00101 2)
;; #(#*00 #*10 #*1)
CL-USER> (chunked (list :a :b :c :d :e) 1)
;; #((:A) (:B) (:C) (:D) (:E))
CL-USER> (chunked (list :a :b :c :d :e) 4)
;; #((:A :B :C :D) (:E))
这是coredump回答的附录,同时也引用了Kaz的评论。其中大部分是关于风格的,这始终是一个意见问题,我并不认为我的意见比他们的好:我只是觉得谈论选择很有趣,因为Lisp编程非常关注风格选择,因为与大多数其他语言相比,该语言非常灵活。然而,最后一节(“扩展”)可能很有趣
参数顺序
签名(大小向量…
的问题在于大小
不能是可选的。如果您希望它是,它不能是函数的第一个参数。我不知道这是否超过了部分应用程序库的简单实用性(但是,本着“做正确的事情”的精神,如果我编写了部分应用程序库,它将允许您指定它使用的参数,因此这不会是一个问题)
因此,如果大小
需要是可选的,那么参数顺序必须是(向量大小…
)
此外,由于coredump的答案使用关键字参数,我会将size
设置为一个,因为您几乎不想混合关键字和可选参数。这样就产生了一个签名,(vector&key size start-end-sharedp)
,然后我将实际函数写成
(defun chunk-vector (vector &key (size 1) (start 0) (end (length vector))
(sharedp nil))
(check-type size (integer 1))
(let ((slicer (if sharedp #'slice #'subseq)))
(loop for s from start below end by size
for e from (+ start size) by size
collect (funcall slicer thing s (min e end)))))
这稍微改进了coredump的版本,默认了arglist中的参数,而不是更高版本
扩展块向量
很明显,您可能需要对其他类型的内容进行分块,例如列表,而且很明显,对列表进行分块的算法与对向量进行分块的算法会有很大的不同,因为您确实不希望在列表上重复调用subseq
这是w
(defgeneric chunk (thing &key)
;; in real life we might want to specify some of the keyword
;; arguments at the GF level, but we won't
)
(defmethod chunk ((thing vector) &key
(size 1) (start 0) (end (length thing)) (sharedp nil))
(check-type size (integer 1))
(let ((slicer (if sharedp #'slice #'subseq)))
(loop for s from start below end by size
for e from (+ start size) by size
collect (funcall slicer thing s (min e end)))))
(defmethod chunk ((thing list) &key
(size 1) (start 0) (end nil endp) (sharedp nil))
;; This does not implemenent SHAREDP: this could only be useful for
;; the last chunk, and since you don't know if you could share a
;; chunk until you have already walked the list it did not seem
;; worth it. It may also be buggy in its handling of END.
(declare (ignorable sharedp))
(flet ((next (lt)
(nthcdr size lt))
(the-chunk (lt p)
(loop for c below (if endp (min size (- end p)) size)
for e in lt
do (print c)
collect e)))
(loop for tail on (nthcdr start thing) by #'next
for pos upfrom start by size
while (or (not endp) (< pos end))
collect (the-chunk tail pos))))
(defun reshape-2d (column-count vector &optional padding-element)
(let* ((row-count (ceiling (length vector) column-count))
(array (make-array (list row-count column-count)
:initial-element padding-element)))
(loop :for i :below (length vector)
:do (setf (row-major-aref array i) (aref vector i)))
array))
(defun chunkv (size vector)
(let ((vectors (make-array (ceiling (length vector) size))))
(loop :for i :below (length vector) :by size
:for j :below (length vectors)
:do (setf (aref vectors j) (subseq vector
i
(min (1- (length vector))
(+ i size)))))
vectors))
(defun chunkl (size vector)
(loop :for i :below (length vector) :by size
:collect (subseq vector
i
(min (1- (length vector))
(+ i size)))))