Skip to content

Commit cc5e4cc

Browse files
authored
[210_10] 实现 bag 查询与转换接口 (#393)
* feat: add bag query helpers * wip * wip * wip * 修改实现方式 * wip * wip
1 parent cdf6f58 commit cc5e4cc

File tree

4 files changed

+294
-5
lines changed

4 files changed

+294
-5
lines changed

devel/210_10.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@
1010
bin/goldfish tests/goldfish/liii/bag-test.scm
1111
```
1212

13+
## 2026/01/29 实现 bag 查询与转换接口
14+
### What
15+
补齐 SRFI-113 的 bag 查询能力与复制/列表转换接口,并在 (liii bag) 中导出与补齐测试。
16+
17+
1. 在 goldfish/srfi/srfi-113.scm 中实现 bag-size / bag-find / bag-count / bag-any? / bag-every? / bag-copy / list->bag / list->bag! 并添加到导出列表
18+
2. 在 goldfish/liii/bag.scm 中导出以上函数,并提供默认比较器的 list->bag
19+
3. 在 tests/goldfish/liii/bag-test.scm 中添加注释与覆盖测试(含空 bag、空列表与类型错误分支)
20+
21+
### Why
22+
提供多重集的基础查询能力与列表互操作接口,支持按条件计数与搜索,并符合 SRFI-113 语义。
23+
24+
### How
25+
1. bag-size 汇总 entries 的计数,返回元素总数(含重复)
26+
2. bag-find 遍历 entries,找到满足 predicate 的元素立即返回,否则调用 failure
27+
3. bag-count 统计满足 predicate 的元素出现次数(含重复)
28+
4. bag-any? / bag-every? 使用短路遍历判断是否存在/是否全部满足
29+
5. bag-copy 复制 entries 的计数并保持 comparator
30+
6. list->bag 使用 comparator 构造 bag,保留重复元素
31+
7. list->bag! 将列表元素累加到现有 bag
32+
8. 关键路径与类型错误通过测试覆盖
33+
1334
## 2026/01/29 实现 bag 构造器、访问器与谓词
1435
### What
1536
实现 SRFI-113 的 bag 基础构造、访问与谓词函数,并在 (liii bag) 中提供默认比较器封装与测试用例。

goldfish/liii/bag.scm

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,25 @@
1616

1717
(define-library (liii bag)
1818
(import (rename (srfi srfi-113)
19-
(bag make-bag-with-comparator))
19+
(bag make-bag-with-comparator)
20+
(list->bag list->bag-with-comparator))
2021
(only (srfi srfi-113)
2122
bag-unfold bag-member bag-comparator bag->list
22-
bag? bag-contains? bag-empty? bag-disjoint?)
23+
list->bag! bag-copy
24+
bag? bag-contains? bag-empty? bag-disjoint?
25+
bag-size bag-find bag-count bag-any? bag-every?)
2326
(srfi srfi-128))
2427
(export bag bag-unfold bag-member bag-comparator
25-
bag->list
26-
bag? bag-contains? bag-empty? bag-disjoint?)
28+
bag->list list->bag list->bag! bag-copy
29+
bag? bag-contains? bag-empty? bag-disjoint?
30+
bag-size bag-find bag-count bag-any? bag-every?)
2731

2832
(define comp (make-default-comparator))
2933

3034
(define (bag . elements)
3135
(apply make-bag-with-comparator comp elements))
3236

37+
(define (list->bag elements)
38+
(list->bag-with-comparator comp elements))
39+
3340
) ; end of define-library

goldfish/srfi/srfi-113.scm

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
set-adjoin set-adjoin! set-replace set-replace!
4040
set-delete set-delete! set-delete-all set-delete-all!
4141
bag bag-unfold bag-member bag-comparator bag->list
42-
bag? bag-contains? bag-empty? bag-disjoint?)
42+
bag-copy list->bag list->bag!
43+
bag? bag-contains? bag-empty? bag-disjoint?
44+
bag-size bag-find bag-count bag-any? bag-every?)
4345
(begin
4446

4547
(define-record-type set-impl
@@ -626,6 +628,24 @@
626628
(bag-increment! result (mapper seed) 1)
627629
(loop (successor seed)))))))
628630

631+
(define (list->bag comparator elements)
632+
(apply bag comparator elements))
633+
634+
(define (list->bag! bag elements)
635+
(check-bag bag)
636+
(for-each (lambda (x) (bag-increment! bag x 1)) elements)
637+
bag)
638+
639+
(define (bag-copy bag)
640+
(check-bag bag)
641+
(let ((entries (make-hash-table (bag-comparator bag))))
642+
(hash-table-for-each
643+
(lambda (k entry)
644+
(hash-table-set! entries k entry))
645+
(bag-entries bag))
646+
(%make-bag entries (bag-comparator bag))))
647+
648+
629649
(define (bag-member bag element default)
630650
(check-bag bag)
631651
(if (hash-table-contains? (bag-entries bag) element)
@@ -644,5 +664,42 @@
644664
(bag-entries bag))
645665
result))
646666

667+
(define (bag-size bag)
668+
(bag-count (lambda (x) #t) bag))
669+
670+
(define (bag-find predicate bag failure)
671+
(check-bag bag)
672+
(let ((found (find predicate (hash-table-keys (bag-entries bag)))))
673+
(or found (failure))))
674+
675+
(define (bag-count predicate bag)
676+
(check-bag bag)
677+
(let ((entries (bag-entries bag)))
678+
(hash-table-fold
679+
(lambda (k entry acc)
680+
(if (predicate k)
681+
(+ acc entry)
682+
acc))
683+
0
684+
entries)))
685+
686+
(define (bag-any? predicate bag)
687+
(check-bag bag)
688+
(let ((found
689+
(hash-table-find
690+
(lambda (k entry) (predicate k))
691+
(bag-entries bag)
692+
#f)))
693+
(if found #t #f)))
694+
695+
(define (bag-every? predicate bag)
696+
(check-bag bag)
697+
(let ((found
698+
(hash-table-find
699+
(lambda (k entry) (not (predicate k)))
700+
(bag-entries bag)
701+
#f)))
702+
(if found #f #t)))
703+
647704
) ; end of begin
648705
) ; end of define-library

tests/goldfish/liii/bag-test.scm

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,87 @@ element ... : any
5555
(check-false (not (member 2 b-list)))
5656
(check (length b-list) => 3)
5757

58+
#|
59+
bag-copy
60+
复制一个 bag。
61+
62+
语法
63+
----
64+
(bag-copy bag)
65+
66+
参数
67+
----
68+
bag : bag
69+
目标 bag。
70+
71+
返回值
72+
-----
73+
返回一个新的 bag,包含原 bag 的所有元素,比较器相同。
74+
|#
75+
(let ((copy (bag-copy b-1-2)))
76+
(check-true (bag? copy))
77+
(check-false (eq? copy b-1-2))
78+
(check-true (eq? (bag-comparator copy) comp))
79+
(check (bag-size copy) => 3)
80+
(check (bag-count (lambda (x) (= x 2)) copy) => 2))
81+
(check-true (bag-empty? (bag-copy b-empty)))
82+
(check-catch 'type-error (bag-copy "not a bag"))
83+
84+
#|
85+
list->bag
86+
将列表转换为 bag。
87+
88+
语法
89+
----
90+
(list->bag list)
91+
92+
参数
93+
----
94+
list : list
95+
要转换的列表。
96+
97+
返回值
98+
-----
99+
返回包含列表中所有元素的 bag(使用默认比较器,重复元素保留)。
100+
|#
101+
(define b-list-1 (list->bag '(1 2 2 3)))
102+
(check-true (bag? b-list-1))
103+
(check-true (eq? (bag-comparator b-list-1) comp))
104+
(check (bag-size b-list-1) => 4)
105+
(check (bag-count (lambda (x) (= x 2)) b-list-1) => 2)
106+
(define b-list-empty (list->bag '()))
107+
(check-true (bag-empty? b-list-empty))
108+
109+
#|
110+
list->bag!
111+
将列表元素并入 bag(可变操作)。
112+
113+
语法
114+
----
115+
(list->bag! bag list)
116+
117+
参数
118+
----
119+
bag : bag
120+
目标 bag。
121+
122+
list : list
123+
要并入的元素列表。
124+
125+
返回值
126+
------
127+
返回修改后的 bag(与传入的 bag 是同一个对象)。
128+
|#
129+
(define b-list-merge (bag 1 2))
130+
(define b-list-merge-result (list->bag! b-list-merge '(2 3 3)))
131+
(check-true (eq? b-list-merge-result b-list-merge))
132+
(check (bag-size b-list-merge) => 5)
133+
(check (bag-count (lambda (x) (= x 2)) b-list-merge) => 2)
134+
(check (bag-count (lambda (x) (= x 3)) b-list-merge) => 2)
135+
(list->bag! b-list-merge '())
136+
(check (bag-size b-list-merge) => 5)
137+
(check-catch 'type-error (list->bag! "not a bag" '(1 2)))
138+
58139

59140

60141

@@ -258,6 +339,129 @@ bag2 : bag
258339
(check-catch 'type-error (bag-disjoint? "not a bag" (bag 1)))
259340
(check-catch 'type-error (bag-disjoint? (bag 1) "not a bag"))
260341

342+
#|
343+
bag-size
344+
返回 bag 中元素总数(含重复)。
345+
346+
语法
347+
----
348+
(bag-size bag)
349+
350+
参数
351+
----
352+
bag : bag
353+
目标 bag。
354+
355+
返回值
356+
-----
357+
返回 bag 中元素总数(包含重复元素)。
358+
|#
359+
(check (bag-size b-empty) => 0)
360+
(check (bag-size b-1-2) => 3)
361+
(check-catch 'type-error (bag-size "not a bag"))
362+
363+
#|
364+
bag-find
365+
查找满足条件的元素。
366+
367+
语法
368+
----
369+
(bag-find predicate bag failure)
370+
371+
参数
372+
----
373+
predicate : procedure
374+
判断函数,接收元素并返回布尔值。
375+
376+
bag : bag
377+
目标 bag。
378+
379+
failure : procedure
380+
未找到时调用的过程。
381+
382+
返回值
383+
-----
384+
返回第一个满足 predicate 的元素,否则返回 failure 的结果。
385+
|#
386+
(check (bag-find even? b-1-2 (lambda () 'none)) => 2)
387+
(check (bag-find (lambda (x) (> x 9)) b-1-2 (lambda () 'missing)) => 'missing)
388+
(check-catch 'type-error (bag-find even? "not a bag" (lambda () 'none)))
389+
390+
#|
391+
bag-count
392+
统计满足条件的元素数量(含重复)。
393+
394+
语法
395+
----
396+
(bag-count predicate bag)
397+
398+
参数
399+
----
400+
predicate : procedure
401+
判断函数,接收元素并返回布尔值。
402+
403+
bag : bag
404+
目标 bag。
405+
406+
返回值
407+
-----
408+
返回满足 predicate 的元素总数(含重复)。
409+
|#
410+
(check (bag-count even? b-1-2) => 2)
411+
(check (bag-count (lambda (x) (> x 9)) b-1-2) => 0)
412+
(check-catch 'type-error (bag-count even? "not a bag"))
413+
414+
#|
415+
bag-any?
416+
判断是否存在满足条件的元素。
417+
418+
语法
419+
----
420+
(bag-any? predicate bag)
421+
422+
参数
423+
----
424+
predicate : procedure
425+
判断函数,接收元素并返回布尔值。
426+
427+
bag : bag
428+
目标 bag。
429+
430+
返回值
431+
-----
432+
如果存在满足 predicate 的元素,返回 #t,否则返回 #f。
433+
|#
434+
(check-true (bag-any? even? b-1-2))
435+
(check-false (bag-any? (lambda (x) (> x 9)) b-1-2))
436+
(check-false (bag-any? even? b-empty))
437+
(check-catch 'type-error (bag-any? even? "not a bag"))
438+
439+
#|
440+
bag-every?
441+
判断是否所有元素都满足条件。
442+
443+
语法
444+
----
445+
(bag-every? predicate bag)
446+
447+
参数
448+
----
449+
predicate : procedure
450+
判断函数,接收元素并返回布尔值。
451+
452+
bag : bag
453+
目标 bag。
454+
455+
返回值
456+
-----
457+
如果 bag 中所有元素都满足 predicate 返回 #t,否则返回 #f。
458+
空 bag 返回 #t。
459+
|#
460+
(check-true (bag-every? (lambda (x) (> x 0)) b-1-2))
461+
(check-false (bag-every? even? b-1-2))
462+
(check-true (bag-every? even? b-empty))
463+
(check-catch 'type-error (bag-every? even? "not a bag"))
464+
261465
#|
262466
bag-comparator
263467
获取 bag 的 comparator。

0 commit comments

Comments
 (0)