Clojure中的习惯用语片段

Idiomatic sequence slice in Clojure

在Python中,有一种方便的方法可以获取称为"切片"的列表:

1
2
3
4
5
6
7
a = [1,2,3,4,5,6,7,8,9,10] # ≡ a = range(1,10)
a[:3] # get first 3 elements
a[3:] # get all elements except the first 3
a[:-3] # get all elements except the last 3
a[-3:] # get last 3 elements
a[3:7] # get 4 elements starting from 3rd (≡ from 3rd to 7th exclusive)
a[3:-3] # get all elements except the first 3 and the last 3

在Clojure中玩clojure.repl/doc时,我发现了所有它们的等效项,但是我不确定它们是惯用的。

1
2
3
4
5
6
7
(def a (take 10 (iterate inc 1)))
(take 3 a)
(drop 3 a)
(take (- (count a) 3) a)
(drop (- (count a) 3) a)
(drop 3 (take 7 a))
(drop 3 (take (- (count a) 3) a))

我的问题是:如何在Clojure中切片序列? 换句话说,返回序列不同部分的正确方法是什么?


您可以使用take-lastdrop-last来简化所有使用count的代码:

1
2
3
4
5
6
7
(def a (take 10 (iterate inc 1)))
(take 3 a) ; get first 3 elements
(drop 3 a) ; get all elements except the first 3
(drop-last 3 a) ; get all elements except the last 3
(take-last 3 a) ; get last 3 elements
(drop 3 (take 7 a)) ; get 4 elements starting from 3
(drop 3 (drop-last 3 a)) ; get all elements except the first and the last 3

并且如下面的注释中所建议,您可以使用->>宏将多个操作"线程化"在一起。例如,最后两行也可以这样写:

1
2
(->> a (take 7) (drop 3)) ; get 4 elements starting from 3
(->> a (drop-last 3) (drop 3)) ; get all elements except the first and the last 3

我认为如果仅对列表应用两个操作,则这两种方法都非常易读,但是当您有一个长字符串,如takemapfilterdropfirst时,请使用->>宏可使代码更易于阅读,甚至可能更易于编写。


Python的序列概念与Clojure的概念非常不同。

在Python中,

  • 序列是由索引的有限有序集
    非负数;和
  • 列表是可变序列,您可以在其中添加切片或删除切片
    切片。

在Clojure中

  • 序列是支持firstrest
    cons;
  • 列表是具有cons的不可变顺序集合(或
    rest)添加(或删除)first元素(因此返回列表
    修改)。

Clojure中最接近Python列表的是向量。正如Adam Sznajder所建议的那样,尽管不能像在Python中那样添加或删除切片,但可以使用subvec对其进行切片。

subvec是快速的恒定时间操作,而drop使您为被绕过的元素数量付费(take使您为遍历的元素付费,但这是您感兴趣的元素)。

你的例子变成...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(def a (vec (range 1 (inc 10))))

(subvec a 0 3)
; [1 2 3]

(subvec a 3)
; [4 5 6 7 8 9 10]

(subvec a 0 (- (count a) 3))
; [1 2 3 4 5 6 7]

(subvec a (- (count a) 3))
; [8 9 10]

(subvec a 3 (+ 3 4))
; [4 5 6 7]

(subvec a 3 (- (count a) 3))
; [4 5 6 7]

有一个功能subvec。不幸的是,它仅适用于向量,因此您必须转换序列:

http://clojuredocs.org/clojure_core/clojure.core/subvec


切片序列有点"代码异味"-序列通常设计用于顺序访问项目。

如果您要进行大量切片/串联,则可以使用更好的数据结构,尤其是检出RRB-Tree矢量实现:

  • https://github.com/clojure/core.rrb-vector

这支持非常有效的subveccatvec操作。