强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Guile/Scheme 编程教程 / 第8章:数据结构

第 8 章:数据结构

8.1 记录类型(Record Type)

8.1.1 SRFI-9 记录类型

SRFI-9 提供了标准的记录类型定义方式,是 Guile 中最常用的数据结构定义方法。

(use-modules (srfi srfi-9))

;; 定义记录类型
(define-record-type <person>
  (make-person name age email)
  person?
  (name person-name)
  (age person-age)
  (email person-email))

;; 创建实例
(define alice (make-person "Alice" 30 "[email protected]"))

;; 访问字段
(person-name alice)   ; => "Alice"
(person-age alice)    ; => 30
(person-email alice)  ; => "[email protected]"

;; 类型检查
(person? alice)       ; => #t
(person? "Alice")     ; => #f

8.1.2 带可变字段的记录

(use-modules (srfi srfi-9))

;; 定义可变字段(使用第三个参数 #t)
(define-record-type <bank-account>
  (make-bank-account owner balance)
  bank-account?
  (owner bank-account-owner)
  (balance bank-account-balance set-bank-account-balance!))

(define account (make-bank-account "Alice" 1000))

(bank-account-balance account)  ; => 1000
(set-bank-account-balance! account 1500)
(bank-account-balance account)  ; => 1500

8.1.3 SRFI-9 的扩展用法

;; 自定义构造函数
(define-record-type <point>
  (%make-point x y)  ; 内部构造函数
  point?
  (x point-x)
  (y point-y))

;; 自定义构造逻辑
(define (make-point x y)
  (unless (and (number? x) (number? y))
    (error "坐标必须是数字"))
  (%make-point x y))

;; 继承(Guile 扩展)
(use-modules (srfi srfi-9)
             (srfi srfi-9 gnu))

(define-record-type <shape>
  (make-shape color)
  shape?
  (color shape-color))

(define-record-type <circle>
  (make-circle color radius)
  circle?
  (radius circle-radius))

;; 将 circle 设置为 shape 的子类型
(record-type-inherits! <circle> <shape>)

(define c (make-circle "red" 5))
(shape? c)            ; => #t(继承了 shape)
(circle? c)           ; => #t
(shape-color c)       ; => "red"
(circle-radius c)     ; => 5

8.1.4 记录打印控制

(use-modules (srfi srfi-9)
             (srfi srfi-9 gnu))

(define-record-type <config>
  (make-config host port debug)
  config?
  (host config-host)
  (port config-port)
  (debug config-debug))

;; 自定义打印方式
(set-record-type-printer! <config>
  (lambda (record port)
    (format port "#<config host=~a port=~a debug=~a>"
            (config-host record)
            (config-port record)
            (config-debug record))))

(make-config "localhost" 8080 #t)
;; => #<config host=localhost port=8080 debug=#t>

8.2 向量(Vector)

向量是固定长度的数组,支持 O(1) 随机访问。

8.2.1 向量基本操作

;; 创建向量
#(1 2 3 4 5)                    ; 字面量
(vector 1 2 3 4 5)             ; 构造函数
(make-vector 5 0)              ; 5个0
(make-vector 5)                ; 5个未初始化值
(list->vector '(1 2 3 4 5))   ; 从列表转换

;; 访问
(define v #(10 20 30 40 50))
(vector-ref v 0)               ; => 10
(vector-ref v 3)               ; => 40
(vector-length v)              ; => 5

;; 修改
(define v2 (vector 1 2 3 4 5))
(vector-set! v2 0 99)
v2                              ; => #(99 2 3 4 5)

;; 拼接
(vector-append #(1 2) #(3 4) #(5))
;; => #(1 2 3 4 5)

;; 子向量
(subvector #(1 2 3 4 5) 1 4)  ; => #(2 3 4)

;; 转换
(vector->list #(1 2 3))        ; => (1 2 3)
(vector->string #(#\H #\i))   ; => "Hi"

8.2.2 向量遍历与操作

(use-modules (srfi srfi-43))

;; 向量映射
(vector-map (lambda (i x) (* x x)) #(1 2 3 4 5))
;; => #(1 4 9 16 25)

;; 向量过滤
(vector-filter even? #(1 2 3 4 5 6))
;; => #(2 4 6)

;; 向量折叠
(vector-fold (lambda (i acc x) (+ acc x)) 0 #(1 2 3 4 5))
;; => 15

;; 向量查找
(vector-index even? #(1 3 4 7 8))  ; => 2

;; 向量排序
(define v (vector 3 1 4 1 5 9))
(vector-sort v <)                ; => #(1 1 3 4 5 9)
(vector-sort! v <)               ; 原地排序

;; 向量遍历
(vector-for-each
  (lambda (i x) (format #t "v[~a]=~a " i x))
  #(10 20 30))
;; 输出: v[0]=10 v[1]=20 v[2]=30

;; 带索引的映射
(vector-map (lambda (i x) (cons i x)) #(a b c d))
;; => #((0 . a) (1 . b) (2 . c) (3 . d))

8.2.3 数值向量(SRFI-4)

(use-modules (srfi srfi-4))

;; 特定类型的数值向量,内存效率更高
(define v-u8  (u8vector  1 2 3))         ; 无符号 8-bit
(define v-s16 (s16vector -1 0 1))        ; 有符号 16-bit
(define v-f64 (f64vector 1.0 2.0 3.0))  ; 双精度浮点

;; 访问
(f64vector-ref v-f64 1)   ; => 2.0

;; 类型大小对比
;; u8vector:  每个元素 1 字节
;; u16vector: 每个元素 2 字节
;; s32vector: 每个元素 4 字节
;; f64vector: 每个元素 8 字节

;; 大数据量时使用数值向量
(define big-array (make-f64vector 1000000 0.0))
(f64vector-set! big-array 0 3.14)

8.3 哈希表(Hash Table)

8.3.1 基本操作

;; 创建哈希表
(define ht (make-hash-table))          ; 使用 equal? 比较
(define ht-eq (make-hash-table))       ; 默认
(define ht-eqv (make-hash-table))      ; 使用 eqv?

;; Guile 提供多种哈希表
(define ht1 (make-hash-table))          ; equal? 哈希
(define ht2 (make-hash-table))          ; eq? 哈希
;; 使用 SRFI-69 提供 eq/equal 指定

;; 或者直接用 Guile 原生 API
(define ht (make-hash-table))

;; 插入
(hash-set! ht 'name "Alice")
(hash-set! ht 'age 30)
(hash-set! ht "key" "value")    ; 字符串键也可以

;; 访问
(hash-ref ht 'name)             ; => "Alice"
(hash-ref ht 'age)              ; => 30
(hash-ref ht 'missing)          ; => #f(键不存在)

;; 带默认值
(hash-ref ht 'missing "默认值")  ; => "默认值"

;; 删除
(hash-remove! ht "key")
(hash-ref ht "key")             ; => #f

;; 检查存在
(hash-table? ht)                 ; => #t
(hash-count ht)                  ; => 2

8.3.2 高级操作

;; 遍历
(define scores (make-hash-table))
(hash-set! scores "Alice" 95)
(hash-set! scores "Bob" 87)
(hash-set! scores "Charlie" 92)

;; for-each 遍历
(hash-for-each
  (lambda (key value)
    (format #t "~a: ~a~%" key value))
  scores)

;; 映射为列表
(hash-map->list cons scores)
;; => (("Alice" . 95) ("Bob" . 87) ("Charlie" . 92))

;; fold 折叠
(hash-fold
  (lambda (key value acc)
    (+ value acc))
  0 scores)
;; => 274

;; 查找所有键
(hash-keys scores)    ; => ("Alice" "Bob" "Charlie")
;; Guile 3.0 原生支持

;; 查找所有值
(hash-values scores)  ; => (95 87 92)

;; 过滤
(let ((result (make-hash-table)))
  (hash-for-each
    (lambda (k v)
      (when (>= v 90)
        (hash-set! result k v)))
    scores)
  result)
;; => {Alice: 95, Charlie: 92}

8.3.3 哈希表性能

操作平均时间最坏情况
插入 hash-set!O(1)O(n)
查找 hash-refO(1)O(n)
删除 hash-remove!O(1)O(n)
遍历 hash-for-eachO(n)O(n)
;; 性能优化:预分配大小
(define big-ht (make-hash-table 10000))  ; 预分配 10000 桶

;; 选择正确的比较函数
;; equal?: 字符串键、复合键(默认)
;; eq?:    符号键(更快)
;; eqv?:   数字键

;; 符号键哈希表(推荐用于配置等场景)
(define config (make-hash-table))
(hash-set! config 'host "localhost")  ; 符号键比字符串键快

8.4 数组(Array / SRFI-25)

(use-modules (srfi srfi-25))

;; 多维数组
(define arr (shape 3 3))  ; 3x3 矩阵

;; 初始化
(define arr2 (array (shape 0 3 0 3)
                    1 2 3
                    4 5 6
                    7 8 9))

;; 访问
(array-ref arr2 0 0)  ; => 1
(array-ref arr2 1 2)  ; => 6

;; 修改
(array-set! arr2 0 0 99)

;; Guile 原生数组(更推荐)
(use-modules (ice-9 arrays))

;; 创建共享数组
(define base (make-array 0 3 3))
(array-set! base 1 0 0)
(array-set! base 2 0 1)

;; 数组切片
(define row (make-shared-array base (lambda (i) (list 0 i)) 3))

;; 数组遍历
(array-for-each
  (lambda (x) (display x) (display " "))
  base)

8.5 集合(Set)

;; 使用列表实现集合(小规模)
(use-modules (srfi srfi-1))

(define (list->set lst)
  (delete-duplicates lst equal?))

(define (set-add set item)
  (if (member item set)
      set
      (cons item set)))

(define (set-remove set item)
  (delete item set equal?))

(define (set-union s1 s2)
  (lset-union equal? s1 s2))

(define (set-intersection s1 s2)
  (lset-intersection equal? s1 s2))

(define (set-difference s1 s2)
  (lset-difference equal? s1 s2))

(define (set-member? set item)
  (and (member item set) #t))

;; 使用示例
(define s1 (list->set '(1 2 3 4)))
(define s2 (list->set '(3 4 5 6)))

(set-union s1 s2)        ; => (1 2 3 4 5 6)
(set-intersection s1 s2) ; => (3 4)
(set-difference s1 s2)   ; => (1 2)

;; 使用哈希表实现集合(大规模,O(1) 查找)
(define (make-hash-set)
  (make-hash-table))

(define (hash-set-add! hs item)
  (hash-set! hs item #t))

(define (hash-set-remove! hs item)
  (hash-remove! hs item))

(define (hash-set-member? hs item)
  (if (hash-ref hs item) #t #f))

(define (hash-set->list hs)
  (hash-map->list (lambda (k v) k) hs))

;; 使用示例
(define hs (make-hash-set))
(hash-set-add! hs 'a)
(hash-set-add! hs 'b)
(hash-set-add! hs 'c)
(hash-set-member? hs 'b)  ; => #t
(hash-set-member? hs 'd)  ; => #f

8.6 队列(Queue)

;; Guile 内置队列(ice-9 q)
(use-modules (ice-9 q))

;; 创建队列
(define q (make-q))

;; 入队
(enq! q 'a)
(enq! q 'b)
(enq! q 'c)
;; 队列: a → b → c

;; 出队
(deq! q)  ; => a
(deq! q)  ; => b

;; 查看队首(不移除)
(car (q-list q))  ; => c

;; 队列长度
(q-length q)  ; => 1

;; 检查空队列
(q-empty? q)  ; => #f

;; 清空队列
(q-pop! q)  ; => c
(q-empty? q)  ; => #t

;; 用列表实现简单的队列
;; 双列表队列实现(高效)
(define (make-queue) (cons '() '()))

(define (queue-push q item)
  (let ((new-rear (cons item (cdr q))))
    (if (null? (car q))
        (set-car! q new-rear))
    (set-cdr! q new-rear)))

(define (queue-pop q)
  (if (null? (car q))
      (error "队列为空")
      (let ((item (caar q)))
        (set-car! q (cdar q))
        (when (null? (car q))
          (set-cdr! q '()))
        item)))

8.7 栈(Stack)

;; 使用列表实现栈
(define (make-stack) '())

(define (stack-push stack item)
  (cons item stack))

(define (stack-pop stack)
  (if (null? stack)
      (error "栈为空")
      (values (car stack) (cdr stack))))

(define (stack-peek stack)
  (if (null? stack)
      (error "栈为空")
      (car stack)))

(define (stack-empty? stack)
  (null? stack))

;; 使用示例
(define s (make-stack))
(set! s (stack-push s 1))
(set! s (stack-push s 2))
(set! s (stack-push s 3))
(stack-peek s)  ; => 3
(receive (val rest) (stack-pop s)
  (format #t "弹出: ~a~%" val))
;; 输出: 弹出: 3

8.8 堆(Heap / Priority Queue)

;; 简易优先队列(二叉堆)
(define (make-heap) (vector '()))

(define (heap-insert! heap item)
  (let ((data (vector-ref heap 0)))
    (vector-set! heap 0
      (let insert ((lst data))
        (cond
          ((null? lst) (list item))
          ((< item (car lst))
           (cons item lst))
          (else (cons (car lst) (insert (cdr lst)))))))))

(define (heap-extract-min! heap)
  (let ((data (vector-ref heap 0)))
    (if (null? data)
        (error "堆为空")
        (begin
          (vector-set! heap 0 (cdr data))
          (car data)))))

;; 使用示例
(define h (make-heap))
(heap-insert! h 5)
(heap-insert! h 2)
(heap-insert! h 8)
(heap-insert! h 1)
(heap-extract-min! h)  ; => 1
(heap-extract-min! h)  ; => 2

8.9 位向量(Bitvector)

;; Guile 内置位向量
(define bv (make-bitvector 8 #f))  ; 8位,全部为 0

(bitvector-set! bv 0 #t)
(bitvector-set! bv 3 #t)
(bitvector-set! bv 7 #t)
(bitvector-ref bv 0)    ; => #t
(bitvector-ref bv 1)    ; => #f

;; 从整数创建
(define bv2 (integer->bitvector 42))
;; 42 = 101010

;; 转回整数
(bitvector->integer bv2)  ; => 42

;; 位操作
(define a (integer->bitvector #b1100))
(define b (integer->bitvector #b1010))

8.10 业务场景:选择合适的数据结构

需求推荐数据结构原因
键值配置关联列表/哈希表小规模用 alist,大规模用哈希
数值计算数组/数值向量连续内存,缓存友好
频繁增删列表O(1) 头部操作
去重哈希表集合O(1) 查找
有序数据排序列表/向量视规模选择
FIFO队列双端队列高效
LIFO栈(列表)列表天然栈
优先级堆/优先队列O(log n) 插入提取
;; 场景:用户管理系统
;; 选择:哈希表存储用户,索引用符号键

(define user-db (make-hash-table))

(define (add-user! id name email)
  (hash-set! user-db id
    `((name . ,name) (email . ,email))))

(define (get-user id)
  (hash-ref user-db id #f))

(define (find-users-by-name name)
  (hash-fold
    (lambda (id user acc)
      (if (string=? name (assq-ref user 'name))
          (cons (cons id user) acc)
          acc))
    '()
    user-db))

;; 使用
(add-user! 1 "Alice" "[email protected]")
(add-user! 2 "Bob" "[email protected]")
(add-user! 3 "Alice" "[email protected]")

(get-user 1)
;; => ((name . "Alice") (email . "[email protected]"))

(find-users-by-name "Alice")
;; => ((3 . ...) (1 . ...))

8.11 本章小结

主题要点
记录类型SRFI-9,类型安全的数据结构
向量O(1) 随机访问的数组
哈希表O(1) 键值存储
数组多维数值数据
集合列表或哈希表实现
队列/栈Guile 内置或列表实现

扩展阅读


上一章:第 7 章:宏系统 下一章:第 9 章:模块系统