按照序号拆分列表

我在 dash 的函数里找了,但是没有找到需要的

(2 6 9)  ;; 序号列表
(a b c d e f g h i j k l m n)  ;; 原始列表
((a b c) (d e f g) (h i j) (k l m n))  ;; 拆分后的列表

好像是没有现成的,不太了解 dash,组合行么?

可能比较渣,仅供参考:

(defun my-split (index-list alist)
  (-non-nil
   (--map (-slice alist (car it) (cadr it))
          (-partition-all-in-steps 2 1 (cons 0 (-map #'1+ (sort index-list #'<)))))))

(my-split '(2 6 9) '(a b c d e f g h i j k l m n))
;; => ((a b c) (d e f g) (h i j) (k l m n))
2 个赞
(defun your-split-by-indexes (list indexes)
  (let ((idx 0)
        (tmp ())
        (result ()))
    (dolist (elt list)
      (push elt tmp)
      (when (and indexes (= idx (car indexes)))
        (pop indexes)
        (push (reverse tmp) result)
        (setq tmp nil))
      (cl-incf idx))
    (push (nreverse tmp) result)
    (nreverse result)))

(your-split-by-indexes '(a b c d e f g h i j k l m n)
                       '(2 6 9))
;; => ((a b c) (d e f g) (h i j) (k l m n))

(your-split-by-indexes (number-sequence 0 9)
                       '(3 7))
;; => ((0 1 2 3) (4 5 6 7) (8 9))
2 个赞

学习了,已收藏

(defun split-by-indexes (LIST INDEXES)
  (lexical-let* ((start 0))
    (append 
     (mapcar (lambda (index)
               (let* ((end (1+ index))
                      (s (-slice LIST start end)))
                 (setq start end)
                 s))
             INDEXES)
     (list (-slice LIST start)))))

(split-by-indexes '(a b c d e f g h i j k l m n) '(2 6 9))
;; => ((a b c) (d e f g) (h i j) (k l m n))

(split-by-indexes '(a b c d e f g h i) '(2 6 9))
;; => ((a b c) (d e f g) (h i) nil)

递归版:

(defun split-by-indexes-recursive (LIST INDEXES start)
  (if INDEXES
      (let ((end (1+ (car INDEXES))))
        (append (list (-slice LIST start end))
                (split-by-indexes-recursive LIST (cdr INDEXES) end)))
    (list (-slice LIST start))))

(split-by-indexes-recursive '(a b c d e f g h i j k l m n) '(2 6 9) 0)
;; => ((a b c) (d e f g) (h i j) (k l m n))

(split-by-indexes-recursive '(a b c d e f g h i) '(2 6 9) 0)
;; => ((a b c) (d e f g) (h i) nil)

生成器版:

(setq lexical-binding t)
(require 'generator)

(iter-defun indexes-generator (INDEXES)
  (let ((start 0)
        (end (1+ (car INDEXES)))
        (REMAINS (cdr INDEXES)))
    (while end
      (iter-yield (cons start end))
      (cl-psetq start end
                end (if (car REMAINS) (1+ (car REMAINS)))
                REMAINS (cdr REMAINS)))
      (iter-yield (cons start nil))))

(let* ((INDEXES '(2 6 9))
       (LIST '(a b c d e f g h i j k l m n)))
  (cl-loop for index-pair iter-by (indexes-generator INDEXES)
           collect (-slice LIST (car index-pair) (cdr index-pair))))
;; => ((a b c) (d e f g) (h i j) (k l m n))
4 个赞

谢谢!已收藏

今天遇到一个类似的问题,就是按照 list 2 的样子改造 list 1 (也就是把 list 1 缺少的元素插入进去),这段代码分享如下:

(let ((list1 '(1 2 3 4 5 6 7 8))
      (list2 '(a 1 2 3 b c 4 5 d e f 6 g 7 8 h))
      (idx 0)
      )
  (dolist (item list2 list1)
    (when (not (memq item list1))
      (setq list1 (-insert-at idx item list1))
      )
    (cl-incf idx)
    )
  )

;; ===> (a 1 2 3 b c 4 5 d e f 6 g 7 8 h)

轻轻丢一个 -unfold 版本的,要比之前的 -slice 版更灵活:

(defun my-split (index-list alist)
  (--unfold (pcase (and (car it) it)
              (`(,al ,idx nil   ) (list al))
              (`(,al ,idx ,idxes)
               (append (-split-at (- (car idxes) idx) al)
                       (list (car idxes) (cdr idxes)))))
            (list alist -1 (-> index-list (sort #'<) (-uniq)))))

(my-split '(2 6 9) '(a b c d e f g h i j k l m n))
;; => ((a b c) (d e f g) (h i j) (k l m n))
1 个赞

似乎把 ,idxes 拆成 (,a . ,l) 更直观?不知效率上哪个更好。

大家对 dash.el 都挺熟,我几乎只了解 --map

(--map (+ 2 it) '(1 2 3))
;; => (3 4 5)

我是一点也不熟悉这个包 :joy:

我是看了二楼的回复才知道有 --slice 方法。其它也不是很了解,印象比较深的是 -when-let 方法,emacs 内置包 subr-x 里也有个 when-let 方法,但是这两个行为不一致,被坑过一回所以长记性了:

;; dash
(-when-let (val 42)
  (message "value: %d" val))

;; subr-x
(when-let ((val 42))
  (message "value: %d" val))

回到楼主这个题目,最关键是产生拆分的起止点 (start . end)。使用 dash 这样更高阶的函数库,可以写出更紧凑的代码,但是性能上难免要牺牲一点。

1 个赞

when-let 我赞成用 emacs 自带的,和 let 的用法比较一致

从一致性来讲,我也赞成。但是自带的 when-let 比较尴尬,一度说要废弃但是目前 26.1 又回归了(De-obsolete if-let and when-let)。这一局 dash 胜,或者说是 cl 的胜利(Macro When-Let )。

链接里多了一个中文句号

有一致的地方:when-let 也支持 (val 42)

(when-let (val 42)
  (message "value: %d" val))
;; => "value: 42"

尽管 when-let* 只能用 ((val 42))

这个问题影响应该不大,因为原本计划废弃的也是 26.1。

刚才测试了一下,这两种写法对性能的影响几乎无区别,但现在比较疑惑 pcase 这类在什么时候用比较合适。


一些不太靠谱的总结(勿当真):

  • pcase 做 pattern,很吃性能,大概是纯 letwhenif 的 70% 左右(仅测了这例)。
  • when-letif-let (同 -when-let-if-let) 性能大概是 let 的 90%–95%。
  • -slice 版本在数据列表很长时很渣(几到几十倍差距),而且序号列表越长越明显。
  • dolist 版在序号列表越长时越稳定,还可以再优化,瓶颈是序号列表很短时停不下来:indexes 为 nil 时应及时跳出。
  • -unfold 版很快,特别是序号列表很短时。不用 pcase 改用纯 let,去掉了 -> 当中的语句,极端情况下不如 dolist 版,但大多数情况是差不多的,偶而还更快?有点出乎意料。
1 个赞