Skip to content

Commit 8418940

Browse files
committed
rewording and minor tweaks
1 parent 13c5a59 commit 8418940

1 file changed

Lines changed: 30 additions & 21 deletions

File tree

src/math/sets/negative_sets.clj

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
;; membership — reduce to three map primitives. No special types,
2626
;; no wrapper objects, no case explosion.
2727

28+
;; Clojure obviously offers sets as a built-in type — but they are just a thin
29+
;; wrapper around maps. For the rest of this article we will work directly with
30+
;; maps, since they are the underlying representation of sets. This more
31+
;; clearly shows that the same idea applies to any language with associative
32+
;; data structures.
33+
2834
;; ## The Key Insight
2935

3036
;; A **positive set** is a map where every key maps to itself:
@@ -41,8 +47,11 @@
4147
;; "everything except a and b" → {:neg :neg, a a, b b}
4248
;; ```
4349

44-
;; The sentinel is just data. It flows through map operations like any
45-
;; other key. This is the entire trick.
50+
;; The sentinel is just data. It flows through map operations like any other
51+
;; key. This is the entire trick.
52+
53+
;; Note that whichever value is used to denote negative sets is no longer
54+
;; available as a normal element so choose wisely.
4655

4756
;; ## Implementation
4857

@@ -62,7 +71,7 @@
6271
(defn map-remove
6372
"Remove A's keys that are also in B."
6473
[a b]
65-
(apply dissoc a (keys b)))
74+
(reduce dissoc a (keys b)))
6675

6776
;; That's it for primitives. Everything else is built from these three.
6877

@@ -106,8 +115,8 @@
106115
"Is x a member of set s?"
107116
[s x]
108117
(if (negative? s)
109-
(nil? (get s x)) ;; negative: member if NOT listed
110-
(some? (get s x)))) ;; positive: member if listed
118+
(not (contains? s x)) ;; negative: member if NOT listed
119+
(contains? s x)))
111120

112121
;; For positive sets, membership is the usual map lookup. For negative
113122
;; sets, the logic inverts — an element is a member if it is *not*
@@ -123,8 +132,8 @@
123132
:member-b (member? vowels :b)
124133
:member-z (member? vowels :z)}
125134
:consonants {:member-a (member? consonants :a)
126-
:member-b (member? consonants :b)
127-
:member-z (member? consonants :z)}}
135+
:member-b (member? consonants :b)
136+
:member-z (member? consonants :z)}}
128137

129138
;; `vowels` is a positive set containing `:a :e :i :o :u`.
130139
;; `consonants` is a negative set excluding `:a :e :i :o :u` —
@@ -147,9 +156,8 @@
147156

148157
;; Verify that complement round-trips:
149158

150-
^kind/table
151-
{:vowels-complement (= consonants (complement-set vowels))
152-
:double-complement (= vowels (complement-set (complement-set vowels)))}
159+
(= consonants (complement-set vowels))
160+
(= vowels (complement-set (complement-set vowels)))
153161

154162
;; ## The Operation Table
155163

@@ -159,18 +167,19 @@
159167
;; whether each operand is positive or negative:
160168

161169
^kind/table
162-
[{:A "pos" :B "pos" :union "merge(A,B)" :intersect "keep(A,B)" :difference "remove(A,B)"}
163-
{:A "neg" :B "neg" :union "keep(A,B)" :intersect "merge(A,B)" :difference "remove(B,A)"}
164-
{:A "pos" :B "neg" :union "remove(B,A)" :intersect "remove(A,B)" :difference "keep(A,B)"}
165-
{:A "neg" :B "pos" :union "remove(A,B)" :intersect "remove(B,A)" :difference "merge(A,B)"}]
170+
[{:A "+" :B "+" :union "(merge A B)" :intersect "(keep A B)" :difference "(remove A B)"}
171+
{:A "-" :B "-" :union "(keep A B)" :intersect "(merge A B)" :difference "(remove B A)"}
172+
{:A "+" :B "-" :union "(remove B A)" :intersect "(remove A B)" :difference "(keep A B)"}
173+
{:A "-" :B "+" :union "(remove A B)" :intersect "(remove B A)" :difference "(merge A B)"}]
166174

167175
;; Read each row as: "when A has this polarity and B has that polarity,
168176
;; use this map primitive."
169177

170-
;; Notice the symmetry. Each of the three primitives appears exactly
171-
;; once in every column — there are no redundant cases and no gaps.
172-
;; The `neg` sentinel key participates in the map operations naturally,
173-
;; so the result automatically has the correct polarity.
178+
;; Notice the symmetry. merge and keep appear once and remove appears twice in
179+
;; a complementary pattern in each column. The four cases are exhaustive and
180+
;; mutually exclusive — every combination of positive and negative sets is
181+
;; covered. The `neg` sentinel key participates in the map operations
182+
;; naturally, so the result automatically has the correct polarity.
174183

175184
;; ### Why Does This Work?
176185

@@ -347,7 +356,7 @@
347356

348357
;; ## Why This Matters
349358

350-
;; **It's just maps.** No new types, no wrappers, no separate code
359+
;; **It's just maps.** No new types, wrappers or separate code
351360
;; paths. Any system that can store and manipulate maps can represent
352361
;; both finite and cofinite sets. Serialization, hashing, comparison,
353362
;; indexing — they all come for free from the underlying map.
@@ -384,6 +393,8 @@
384393

385394
;; ## Summary
386395

396+
;; One sentinel key. Three map primitives. Complete set algebra.
397+
387398
;; | Concept | Representation |
388399
;; |---------|----------------|
389400
;; | Positive set `{a, b}` | `{a: a, b: b}` |
@@ -393,5 +404,3 @@
393404
;; | Complement | Toggle `:neg` key |
394405
;; | Membership | pos: `get ≠ nil` · neg: `get = nil` |
395406
;; | All operations | Three map primitives × four polarity cases |
396-
397-
;; One sentinel key. Three map primitives. Complete set algebra.

0 commit comments

Comments
 (0)