• 数据结构
    • Collection 和 Sequence
    • Sequence & List
    • Vector
    • Map
    • Set
    • 常用函数

    数据结构

    Collection 和 Sequence

    Clojure 常用的数据结构有 List, Map, Vector, Set.
    他们都属于 Collection, 之间的关系大致是这样:

    数据结构 - 图1

    属于 Clojure 当中实现的数据结构都是 Collection. 编码当中会遇到 Host 平台的数据类型, 不属于 Collection.

    实现了 Collection 的接口的数据结构都支持这些函数: =, count, conj, empty, seq.
    实现了 Sequence 的接口的数据结构支持这些函数: first, rest, next.

    关于数据结构的细节推荐阅读 Collections and Sequences in Clojure.

    Clojure 中的数据结构被设计成不可变的, 用来配合函数式编程. 这和主流编程语言存在区别.

    Sequence & List

    Clojure 中的 List 是单链表. 适合从头部访问, 而不适合随机访问. Clojure 采用 Lisp 语法来定义 List:

    1. '(1 2 3 4)
    2. ; (1 2 3 4)
    3. (list? '(1 2 3 4))
    4. ; true

    这个语法实际上等同于 quote:

    1. (quote (1 2 3 4))
    2. ; (1 2 3 4)

    或者通过调用的写法可以创建 List:

    1. (list 1 2 3 4)
    2. ; (1 2 3 4)

    Sequence 是一种抽象的数据结构, List 本身是一种 Sequence, 同时 Sequence 本身可以表示惰性的列表.

    1. (type (range))
    2. ; cljs.core/Range
    3. (seq? (range))
    4. ; true

    可以用 seq 函数来创建 Sequence, 不过可以粗略地把它被 List 混在一起理解和使用:

    1. (seq [1 2 3])
    2. ; (1 2 3)

    常用的操作:

    1. (first (list 1 2 3 4))
    2. ; 1
    3. (rest (list 1 2 3 4))
    4. ; (2 3 4)
    5. (cons 1 '(2 3 4 5 6))
    6. ; (1 2 3 4 5 6)

    Vector

    Vector 支持随机访问, 通过 [] 语法可以快速定义:

    1. [1 2 3 4]
    2. ; [1 2 3 4]
    3. (vector? [])
    4. ; true

    也支持是用 vecvector 函数通过调用的方式定义:

    1. (vector 1 2 3 4)
    2. ; [1 2 3 4]
    3. (vec '(1 2 3 4))
    4. ; [1 2 3 4]

    常用的操作比如:

    1. (get [:a :b :c :d] 1)
    2. ; :b
    3. (nth [:a :b :c :d] 1)
    4. ; :b
    5. (peek [1 2 3 4])
    6. ; 4
    7. (pop [1 2 3])
    8. ; [1 2]
    9. (conj [1 2 3] 4)
    10. ; [1 2 3 4]
    11. (subvec [1 2 3] 1 2) ; 截取向量
    12. ; [2]

    Map

    Map 是关联列表, 可以通过 {} 语法定义 Map, 其中 , 等同于空白. 一般键和值的类型没有具体限制:

    1. {:a "a", "b" 3, 4 []}
    2. ; {:a "a", "b" 3, 4 []}
    3. (map? {})
    4. ; true

    一般使用当中会用 Keyword 来作为键:

    1. {:name "Clojure", :born 2007}

    除了通过语法, 也可以用 hash-map 等函数通过调用的方式创建:

    1. (hash-map 1 2 3 4)
    2. ; {1 2, 3 4}
    3. (zipmap [:a :b :c] [1 2 3])
    4. ; {:a 1, :b 2, :c 3}

    常用的操作有:

    1. (:a {:a 1})
    2. ; 1
    3. (assoc {:a 1, :b 2} :c 3)
    4. ; {:a 1, :b 2, :c 3}
    5. (dissoc {:a 1} :b 2)
    6. ; {:a 1}
    7. (contains? {:a 1} :b)
    8. ; false
    9. (merge {:a 1} {:b 2})
    10. ; {:a 1, :b 2}
    11. (update {:a 1} :a inc)
    12. ; {:a 2}

    Set

    集合, 相同的元素之允许出现一次, 可以用 #{} 语法创建:

    1. #{1 2 3}
    2. ; #{1 2 3}
    3. (set? #{})
    4. ; true

    或者通过调用的方式创建:

    1. (hash-set 1 2 3)
    2. ; #{1 2 3}
    3. (set [1 2 3])
    4. ; #{1 2 3}

    常用的操作有:

    1. (conj #{1 2 3} 4)
    2. ; #{1 2 3 4}
    3. (disj #{1 2 3} 3)
    4. ; #{1 2}
    5. (contains? #{1 2 3} 4)
    6. ; false

    此外 clojure.set 提供了其他一些集合的常用函数.

    常用函数

    推荐去 Cheatsheet 了解有哪些语言本身提供的函数.

    • into

    除了前面提到的函数, 还可以通过 into 来做强制数据结构转换:

    1. (into [] '(1 2 3))
    2. ; [1 2 3]
    3. (into #{} '(1 2 3))
    4. ; #{1 2 3}
    5. (into '() [1 2 3])
    6. ; (3 2 1)
    7. (into {} [[:a 1] [:b 2]])
    8. ; {:a 1, :b 2}
    • first

    first 取出 Sequence 的第一个元素, 但是对于 Map 来说能获取键值对:

    1. (first [1 2 3])
    2. ; 1
    3. (first '(1 2 3))
    4. ; 1
    5. (first #{1 2 3})
    6. ; 1
    7. (first {:a 1 :b 2})
    8. ; [:a 1]
    • map

    注意 map 函数返回的是 Sequence, 并不是和原始的数据类型一致, 而且 reduce 等函数也是这样的:

    1. (map inc [1 2 3])
    2. ; (2 3 4)
    3. (map inc '(1 2 3))
    4. ; (2 3 4)
    5. (map inc #{1 2 3})
    6. ; (2 3 4)
    7. (map identity {:a 1 :b 2})
    8. ; ([:a 1] [:b 2])

    具体到 Vector 的情况下, 可使用 mapv 来得到 Vector 类型的结果, 或者用 into 强行转换:

    1. (mapv identity {:a 1 :b 2})
    2. ; [[:a 1] [:b 2]]
    3. (into [] (map identity {:a 1 :b 2}))
    4. ; [[:a 1] [:b 2]]
    • peek, popconj

    由于 Sequence 和 Vector 存在区别, 有些函数虽然通用, 但是效果并不一样:

    1. (peek [1 2 3])
    2. ; 3
    3. (peek '(1 2 3))
    4. ; 1
    5. (pop '(1 2 3))
    6. ; (2 3)
    7. (pop [1 2 3])
    8. ; [1 2]
    9. (conj [1 2 3] 4)
    10. ; [1 2 3 4]
    11. (conj '(1 2 3) 4)
    12. ; (4 1 2 3)
    • count
    1. (count '(1 2 3))
    2. ; 3
    3. (count [1 2 3])
    4. ; 3
    5. (count "123")
    6. ; 3
    7. (count #{1 2 3})
    8. ; 3
    9. (count {:a 1 :b 2 :c 3})
    10. ; 3

    对于长度为 0 可以用 empty? 函数来判断:

    1. (empty? [])
    2. ; true
    3. (empty? {})
    4. ; true
    5. (empty? #{})
    6. ; true
    7. (empty? '())
    8. ; true
    • assoc-inupdate-in

    对于嵌套比较深的数据结构, 会用到这些函数, 主要是 Vector, Map 或者混用两种类型:

    1. (get-in {:a [1 2 3]} [:a 0])
    2. ; 1
    3. (assoc-in {:a [1 2 3]} [:a 0] 4)
    4. ; {:a [4 2 3]}
    5. (update-in {:a [1 2 3]} [:a 0] inc)
    6. ; {:a [2 2 3]}

    更多的函数还是建议阅读 Cheatsheet