数据结构

优质
小牛编辑
143浏览
2023-12-01

数据结构

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