flan/test/programs/map-iter.flan

110 lines
3.6 KiB
Plaintext

;; Walking a map, which is what flan_map_next exists for. Every other map
;; operation addresses one entry by hashing it; this is the only thing that
;; reads the block in order.
;;
;; Block order is the hash's order and not the insertion's, so nothing here
;; may depend on which entry comes first: the checks are a sum, a count and a
;; membership test, all of them order-free. That is not a weakness of the test,
;; it is the contract — a caller that wants an order sorts what it collected.
(defn sum-and-count [] Unit
(let [m (map-new i32 i32)]
(put m 1 10)
(put m 2 20)
(put m 3 30)
(put m 4 40)
(let [cur (i64 0)
k 0
v 0
keys 0
vals 0
n 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(set keys (+ keys k))
(set vals (+ vals v))
(set n (+ n 1)))
(print n) (print " ") (print keys) (print " ") (print vals) (println ""))
(free m)))
;; A map that never allocated has no block at all, and one that allocated and
;; holds nothing has a block of nothing but zeroed hashes. Both walk zero
;; times, and they are different code paths to get there.
(defn the-empty-cases [] Unit
(let [m (map-new i32 i32)
cur (i64 0)
k 0
v 0
n 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(set n (+ n 1)))
(print "never allocated: ") (print n) (println "")
(reserve m 64)
(set cur (i64 0))
(while (map-next! m (addr cur) (addr k) (addr v))
(set n (+ n 1)))
(print "allocated and empty: ") (print n) (println "")
(free m)))
;; A cursor left past the end keeps answering false rather than wrapping, so a
;; second loop over a spent cursor is empty and not a repeat.
(defn a-spent-cursor [] Unit
(let [m (map-new i32 i32)]
(put m 5 50)
(put m 6 60)
(let [cur (i64 0) k 0 v 0 n 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(set n (+ n 1)))
(while (map-next! m (addr cur) (addr k) (addr v))
(set n (+ n 1)))
(print "spent: ") (print n) (println ""))
(free m)))
;; A string key and a struct value: the key run and the value run have
;; different element sizes and different cell packing, so this is the case that
;; would catch the two runs being indexed with one geometry.
(defstruct Point [x i32 y i32])
(defn wider-entries [] Unit
(let [m (map-new string Point)]
(put m "a" (Point {.x 1 .y 2}))
(put m "bb" (Point {.x 3 .y 4}))
(put m "ccc" (Point {.x 5 .y 6}))
(let [cur (i64 0)
k ""
v (Point {})
chars 0
xs 0
ys 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(set chars (+ chars (len k)))
(set xs (+ xs (.x v)))
(set ys (+ ys (.y v))))
(print chars) (print " ") (print xs) (print " ") (print ys) (println ""))
(free m)))
;; Growth past the 75% threshold rehashes into a new block, so this walks a map
;; whose layout is nothing like its insertion order and at a capacity several
;; doublings past the minimum.
(defn after-growth [] Unit
(let [m (map-new i64 i64)]
(dotimes [i 500]
(put m (i64 i) (* (i64 i) 2)))
(let [cur (i64 0)
k (i64 0)
v (i64 0)
n 0
doubled 0]
(while (map-next! m (addr cur) (addr k) (addr v))
(set n (+ n 1))
(when (= v (* k 2)) (set doubled (+ doubled 1))))
(print n) (print " ") (print doubled) (print " ") (print (len m)) (println ""))
(free m)))
(defn main [] i32
(sum-and-count)
(the-empty-cases)
(a-spent-cursor)
(wider-entries)
(after-growth)
0)