flan/test/programs/map-remove.flan
Joseph Ferano a0f37e72a2 The ! suffix retires: a mutator is named for what it does, not marked
The !-means-mutates convention distinguished nothing — there is no
immutable counterpart to contrast with — so every mutating name drops
the mark: sort, sort-by, sort-bytes, swap, reverse, append, append-i64,
append-f64, encode-rune, split-next, map-remove, map-next, and the test
helpers beside them. Two could not simply shed it: map! is map-in-place,
because map is the into transform's word and means the non-mutating
thing; put! is put-at, because put is the Map builtin. The ?-means-asks
convention stays. Dated records keep the old spellings; watch.clj's
reset-spies! and the other Clojure names are not ours to rename.
2026-09-19 05:21:02 +07:00

122 lines
5.5 KiB
Plaintext

;;;; (map-remove m k) — the operation a Map has been missing.
;;;;
;;;; Removal is the one map operation that can break the *other* ones: Robin
;;;; Hood lookups stop at the first empty slot, so a hole punched in the middle
;;;; of a probe run hides every entry after it, and the entries it hides are
;;;; found by no test that only removes and asks about what it removed. So the
;;;; rows below are mostly about the survivors.
;;;;
;;;; Odin marks a tombstone and repairs on the next insert; this shifts the run
;;;; back and stays tombstone-free, which is the arrangement the rest of the
;;;; file already assumed. A version that punched the hole and left it passes
;;;; row 1 and fails row 3.
(defstruct Cell [x i32 y i32])
(defn main [] i32
;; (1) The value comes back, the length drops, and the key is gone. An
;; absent key is None and changes nothing — the same answer get gives, since
;; a key that was not there is an answer and not a failure.
(let [m (map-new i32 i64)]
(put m 1 100)
(put m 2 200)
(match (map-remove m 1)
(Some v) (do (print v) (println "")) ; 100
None (println "missing"))
(print (len m)) (println "") ; 1
(print (has-key? m 1)) (println "") ; false
(match (map-remove m 1) (Some v) (do (print v) (println "")) None (println "gone"))
(println (len m)) ; gone, then 1
(free m))
;; (2) Removing every entry empties the map, and it is usable afterwards:
;; the block is still there and the slots are empty rather than poisoned.
(let [m (map-new i32 i32)]
(dotimes [i 64] (put m i i))
(dotimes [i 64] (map-remove m i))
(print (len m)) (println "") ; 0
(put m 7 77)
(match (get m 7) (Some v) (do (print v) (println "")) None (println "?")) ; 77
(print (len m)) (println "") ; 1
(free m))
;; (3) The survivors, which is the row that matters. 2000 entries is eight
;; grows, so the runs are long and interleaved; removing the even keys and
;; then asking after every odd one is asking whether any probe run was cut.
;; A backward shift that stopped one slot early loses entries here and
;; nowhere else.
(let [m (map-new i32 i64)]
(dotimes [i 2000] (put m i (* (i64 i) 3)))
(let [taken 0]
(dotimes [i 2000]
(if (= 0 (% i 2))
(match (map-remove m i)
(Some v) (if (= v (* (i64 i) 3)) (set taken (+ taken 1)))
None (set taken taken))))
(print taken) (println "")) ; 1000
(print (len m)) (println "") ; 1000
(let [lost 0 ghosts 0]
(dotimes [i 2000]
(match (get m i)
(Some v) (if (or (= 0 (% i 2)) (not (= v (* (i64 i) 3))))
(set ghosts (+ ghosts 1)))
None (if (not (= 0 (% i 2))) (set lost (+ lost 1)))))
(print lost) (println "") ; 0
(print ghosts) (println "")) ; 0
;; Re-inserting what was taken out puts the length back, through no grow:
;; the block still has the room the removals freed up.
(dotimes [i 2000] (if (= 0 (% i 2)) (put m i (* (i64 i) 3))))
(print (len m)) (println "") ; 2000
(free m))
;; (4) A struct key and a string key, so the emitted hash/equality pair is
;; on the removal path too and not only on get's.
(let [g (map-new Cell i32)]
(dotimes [i 20] (dotimes [j 20] (put g (Cell {.x i .y j}) (+ (* i 100) j))))
(match (map-remove g (Cell {.x 7 .y 9}))
(Some v) (do (print v) (println "")) ; 709
None (println "?"))
(print (has-key? g (Cell {.x 7 .y 9}))) (println "") ; false
(print (has-key? g (Cell {.x 7 .y 10}))) (println "") ; true
(print (len g)) (println "") ; 399
(free g))
(let [s (map-new string i32)]
(put s "alpha" 1)
(put s "beta" 2)
(match (map-remove s "alpha")
(Some v) (do (print v) (println "")) ; 1
None (println "?"))
(print (has-key? s "beta")) (println "") ; true
(print (len s)) (println "") ; 1
(free s))
;; (5) Iteration after removals answers exactly the survivors. The cursor is
;; started fresh — a cursor held *across* a removal is invalidated by the
;; shift, the same way a put that grows invalidates one.
(let [m (map-new i32 i32)]
(dotimes [i 100] (put m i i))
(dotimes [i 100] (if (= 0 (% i 3)) (map-remove m i)))
(let [cur (i64 0) k 0 v 0 seen 0 sum 0]
(while (map-next m (addr cur) (addr k) (addr v))
(set seen (+ seen 1))
(if (not (= k v)) (set sum (+ sum 1))))
(print seen) (println "") ; 66
(print sum) (println "")) ; 0
(print (len m)) (println "") ; 66
(free m))
;; (6) An arena, which refuses can-free. Removal asks the allocator for
;; nothing and hands it back nothing — a key and a value live inside the one
;; block the map allocated — so it means here exactly what it means on the
;; heap, and the region is released whole as always.
(let [ar (arena-new 1048576)]
(with-allocator ar
(let [t (map-new i32 i32)]
(dotimes [i 300] (put t i (* i 2)))
(dotimes [i 300] (if (= 0 (% i 2)) (map-remove t i)))
(print (len t)) (println "") ; 150
(match (get t 299) (Some v) (do (print v) (println "")) None (println "?")) ; 598
(print (has-key? t 298)) (println ""))) ; false
(free-all ar))
0)