spec: sets (make-set/set-add!/set-member?/union/intersection/etc)
Adds 13 set primitives to stdlib.sets. OCaml: SxSet as (string,value) Hashtbl keyed by inspect(val); JS: SxSet wrapping Map keyed by write-to-string. Structural equality — (make-set '(1 2)) contains 1. Includes union, intersection, difference, for-each, map. 33 tests in test-sets.sx, all pass on both JS and OCaml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1116,3 +1116,83 @@
|
||||
:doc "Denominator of rational r (after reduction, always positive).")
|
||||
|
||||
(define-module :stdlib.hash-table)
|
||||
|
||||
(define-module :stdlib.sets)
|
||||
|
||||
(define-primitive
|
||||
"make-set"
|
||||
:params (&rest (lst :as list))
|
||||
:returns "set"
|
||||
:doc "Create a fresh empty set. Optional list argument seeds the set: (make-set '(1 2 3)).")
|
||||
|
||||
(define-primitive
|
||||
"set?"
|
||||
:params (v)
|
||||
:returns "boolean"
|
||||
:doc "True if v is a set.")
|
||||
|
||||
(define-primitive
|
||||
"set-add!"
|
||||
:params (s val)
|
||||
:returns "nil"
|
||||
:doc "Add val to set s in place. No-op if already present.")
|
||||
|
||||
(define-primitive
|
||||
"set-member?"
|
||||
:params (s val)
|
||||
:returns "boolean"
|
||||
:doc "True if val is in set s.")
|
||||
|
||||
(define-primitive
|
||||
"set-remove!"
|
||||
:params (s val)
|
||||
:returns "nil"
|
||||
:doc "Remove val from set s in place. No-op if absent.")
|
||||
|
||||
(define-primitive
|
||||
"set-size"
|
||||
:params (s)
|
||||
:returns "integer"
|
||||
:doc "Number of elements in set s.")
|
||||
|
||||
(define-primitive
|
||||
"set->list"
|
||||
:params (s)
|
||||
:returns "list"
|
||||
:doc "All elements of set s as a list (unspecified order).")
|
||||
|
||||
(define-primitive
|
||||
"list->set"
|
||||
:params (lst)
|
||||
:returns "set"
|
||||
:doc "Create a new set containing all elements of lst.")
|
||||
|
||||
(define-primitive
|
||||
"set-union"
|
||||
:params (s1 s2)
|
||||
:returns "set"
|
||||
:doc "New set with all elements from s1 and s2.")
|
||||
|
||||
(define-primitive
|
||||
"set-intersection"
|
||||
:params (s1 s2)
|
||||
:returns "set"
|
||||
:doc "New set with elements present in both s1 and s2.")
|
||||
|
||||
(define-primitive
|
||||
"set-difference"
|
||||
:params (s1 s2)
|
||||
:returns "set"
|
||||
:doc "New set with elements in s1 that are not in s2.")
|
||||
|
||||
(define-primitive
|
||||
"set-for-each"
|
||||
:params (s fn)
|
||||
:returns "nil"
|
||||
:doc "Call (fn val) for each element in set s. Order unspecified.")
|
||||
|
||||
(define-primitive
|
||||
"set-map"
|
||||
:params (s fn)
|
||||
:returns "set"
|
||||
:doc "New set of results of (fn val) for each element in s.")
|
||||
|
||||
200
spec/tests/test-sets.sx
Normal file
200
spec/tests/test-sets.sx
Normal file
@@ -0,0 +1,200 @@
|
||||
;; ==========================================================================
|
||||
;; test-sets.sx — Tests for set primitives
|
||||
;; ==========================================================================
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; make-set / set?
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"sets:create"
|
||||
(deftest "make-set returns a set" (assert (set? (make-set))))
|
||||
(deftest "empty set has size 0" (assert= (set-size (make-set)) 0))
|
||||
(deftest
|
||||
"make-set from list"
|
||||
(let ((s (make-set (list 1 2 3)))) (assert= (set-size s) 3)))
|
||||
(deftest
|
||||
"make-set deduplicates"
|
||||
(let ((s (make-set (list 1 2 2 3 3)))) (assert= (set-size s) 3)))
|
||||
(deftest "set? true for sets" (assert (set? (make-set))))
|
||||
(deftest "set? false for list" (assert (not (set? (list 1 2 3)))))
|
||||
(deftest "set? false for nil" (assert (not (set? nil))))
|
||||
(deftest "set? false for number" (assert (not (set? 42)))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; set-add! / set-member? / set-remove!
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"sets:mutation"
|
||||
(deftest
|
||||
"set-add! increases size"
|
||||
(let
|
||||
((s (make-set)))
|
||||
(set-add! s 1)
|
||||
(assert= (set-size s) 1)))
|
||||
(deftest
|
||||
"set-add! idempotent"
|
||||
(let
|
||||
((s (make-set)))
|
||||
(set-add! s 1)
|
||||
(set-add! s 1)
|
||||
(assert= (set-size s) 1)))
|
||||
(deftest
|
||||
"set-member? true after add"
|
||||
(let
|
||||
((s (make-set)))
|
||||
(set-add! s "hello")
|
||||
(assert (set-member? s "hello"))))
|
||||
(deftest
|
||||
"set-member? false for absent"
|
||||
(let
|
||||
((s (make-set (list 1 2 3))))
|
||||
(assert (not (set-member? s 99)))))
|
||||
(deftest
|
||||
"set-remove! reduces size"
|
||||
(let
|
||||
((s (make-set (list 1 2 3))))
|
||||
(set-remove! s 2)
|
||||
(assert= (set-size s) 2)))
|
||||
(deftest
|
||||
"set-remove! removes element"
|
||||
(let
|
||||
((s (make-set (list 1 2 3))))
|
||||
(set-remove! s 2)
|
||||
(assert (not (set-member? s 2)))))
|
||||
(deftest
|
||||
"set-remove! no-op for absent"
|
||||
(let
|
||||
((s (make-set (list 1 2 3))))
|
||||
(set-remove! s 99)
|
||||
(assert= (set-size s) 3)))
|
||||
(deftest
|
||||
"set handles strings"
|
||||
(let
|
||||
((s (make-set)))
|
||||
(set-add! s "a")
|
||||
(set-add! s "b")
|
||||
(assert (and (set-member? s "a") (set-member? s "b")))))
|
||||
(deftest
|
||||
"set handles symbols"
|
||||
(let
|
||||
((s (make-set)))
|
||||
(set-add! s (quote foo))
|
||||
(assert (set-member? s (quote foo))))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; set->list / list->set
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"sets:conversion"
|
||||
(deftest
|
||||
"list->set creates set"
|
||||
(let ((s (list->set (list 1 2 3)))) (assert (set? s))))
|
||||
(deftest
|
||||
"list->set size"
|
||||
(let ((s (list->set (list 1 2 3)))) (assert= (set-size s) 3)))
|
||||
(deftest
|
||||
"list->set deduplicates"
|
||||
(let ((s (list->set (list 1 1 2)))) (assert= (set-size s) 2)))
|
||||
(deftest
|
||||
"set->list has all elements"
|
||||
(let
|
||||
((s (make-set (list 1 2 3)))
|
||||
(lst (set->list s)))
|
||||
(assert= (length lst) 3)))
|
||||
(deftest
|
||||
"set->list round-trip membership"
|
||||
(let
|
||||
((s (make-set (list 10 20 30)))
|
||||
(lst (set->list s)))
|
||||
(assert
|
||||
(and
|
||||
(set-member? (list->set lst) 10)
|
||||
(set-member? (list->set lst) 20)
|
||||
(set-member? (list->set lst) 30))))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; set-union / set-intersection / set-difference
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"sets:operations"
|
||||
(deftest
|
||||
"union size"
|
||||
(let
|
||||
((a (make-set (list 1 2 3)))
|
||||
(b (make-set (list 3 4 5))))
|
||||
(assert= (set-size (set-union a b)) 5)))
|
||||
(deftest
|
||||
"union contains all"
|
||||
(let
|
||||
((u (set-union (make-set (list 1 2)) (make-set (list 3 4)))))
|
||||
(assert
|
||||
(and
|
||||
(set-member? u 1)
|
||||
(set-member? u 3)
|
||||
(set-member? u 4)))))
|
||||
(deftest
|
||||
"intersection size"
|
||||
(let
|
||||
((a (make-set (list 1 2 3)))
|
||||
(b (make-set (list 2 3 4))))
|
||||
(assert= (set-size (set-intersection a b)) 2)))
|
||||
(deftest
|
||||
"intersection contains overlap"
|
||||
(let
|
||||
((i (set-intersection (make-set (list 1 2 3)) (make-set (list 2 3 4)))))
|
||||
(assert (and (set-member? i 2) (set-member? i 3) (not (set-member? i 1))))))
|
||||
(deftest
|
||||
"intersection empty when disjoint"
|
||||
(let
|
||||
((a (make-set (list 1 2)))
|
||||
(b (make-set (list 3 4))))
|
||||
(assert= (set-size (set-intersection a b)) 0)))
|
||||
(deftest
|
||||
"difference size"
|
||||
(let
|
||||
((a (make-set (list 1 2 3)))
|
||||
(b (make-set (list 2 3))))
|
||||
(assert= (set-size (set-difference a b)) 1)))
|
||||
(deftest
|
||||
"difference keeps only a-exclusive"
|
||||
(let
|
||||
((d (set-difference (make-set (list 1 2 3)) (make-set (list 2 3 4)))))
|
||||
(assert (and (set-member? d 1) (not (set-member? d 2)) (not (set-member? d 4))))))
|
||||
(deftest
|
||||
"union does not mutate inputs"
|
||||
(let
|
||||
((a (make-set (list 1 2)))
|
||||
(b (make-set (list 3 4))))
|
||||
(set-union a b)
|
||||
(assert= (set-size a) 2))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; set-for-each / set-map
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"sets:higher-order"
|
||||
(deftest
|
||||
"set-for-each visits all"
|
||||
(let
|
||||
((s (make-set (list 1 2 3)))
|
||||
(acc (list)))
|
||||
(set-for-each s (fn (v) (set! acc (cons v acc))))
|
||||
(assert= (length acc) 3)))
|
||||
(deftest
|
||||
"set-map doubles values"
|
||||
(let
|
||||
((s (make-set (list 1 2 3)))
|
||||
(s2 (set-map s (fn (v) (* v 2)))))
|
||||
(assert
|
||||
(and
|
||||
(set-member? s2 2)
|
||||
(set-member? s2 4)
|
||||
(set-member? s2 6)))))
|
||||
(deftest
|
||||
"set-map result is a set"
|
||||
(assert (set? (set-map (make-set (list 1 2)) (fn (v) v))))))
|
||||
Reference in New Issue
Block a user