Small maps preserve insertion order

jolt maps were HAMTs with hash iteration order; Clojure keeps small maps as
PersistentArrayMap (insertion order), converting to PersistentHashMap past a
threshold. Map literals, array-map, assoc, into/transient, merge, zipmap,
select-keys, update-keys/vals, frequencies and group-by now iterate in insertion
order for <=8 entries, matching the JVM. hash-map and >8-entry maps stay hash
order; sets stay hash order.

The pmap record gains an order field (the insertion-order key list, or #f once
hashed); the HAMT still backs the values so equality/hash/lookup are unchanged.
pmap-fold visits an array-mode map last-to-first so the runtime's cons-accumulate
idiom reconstructs insertion order without touching its many call sites, and
hash-mode output stays byte-identical; pmap-fold-fwd visits in order for the few
sites that build a value directly. Transient maps track insertion order and
promote to hash past max(8, source-count), matching TransientArrayMap.

The hash-map native-op retargets to a hash-order builder so (hash-map ...) stays
hash-ordered while {...} literals are ordered; syntax-quote builds maps via the
hash builder (Clojure expands `{...} to apply hash-map). The core overlay map
builders seed from {} instead of (hash-map) to keep order.

Threshold is 8 for any key (the keyword exception in newer Clojure isn't in
1.12.5). honeysql now passes 832/0/0; 19 JVM-certified corpus rows added.
This commit is contained in:
Yogthos 2026-06-27 05:48:17 -04:00
parent e2efff6c8e
commit bfa2cbf49d
13 changed files with 217 additions and 102 deletions

View file

@ -287,26 +287,92 @@
;; ============================================================================
;; persistent map / set over the HAMT
;; ============================================================================
(define-record-type pmap (fields root cnt) (nongenerative chez-pmap-v1))
(define empty-pmap (make-pmap empty-hnode 0))
;; A small map keeps its keys in INSERTION order (Clojure's PersistentArrayMap),
;; converting to hash order past a threshold (PersistentHashMap). The HAMT root
;; always backs the values; `order` is the auxiliary insertion-order key list when
;; the map is in array mode, or #f once it has grown into hash mode. Equality and
;; hashing fold over the entries order-independently, so this only affects
;; iteration order (seq/keys/vals/print), matching the JVM.
(define-record-type pmap (fields root cnt order) (nongenerative chez-pmap-v2))
(define empty-pmap (make-pmap empty-hnode 0 '())) ; {} = empty array map
(define empty-pmap-hash (make-pmap empty-hnode 0 #f)) ; hash-order backing (sets)
(define pmap-absent (list 'absent)) ; unique missing-key sentinel
;; PersistentArrayMap threshold: assoc of a new key promotes to hash mode once the
;; map already holds 8 entries (array.length >= 16 in the reference).
(define array-map-limit 8)
(define (append-key ord k) (append ord (list k)))
(define (remove-key ord k) (let loop ((o ord)) (cond ((null? o) '()) ((jolt= (car o) k) (cdr o)) (else (cons (car o) (loop (cdr o)))))))
;; growth rule (PersistentArrayMap.assoc): a new key appends to the order while in
;; array mode under the limit; otherwise the result is hash-ordered. Replacing an
;; existing key (or assoc onto an already-hash map) keeps the current order.
(define (pmap-assoc m k v)
(let* ((added (box #f)) (r (node-assoc (pmap-root m) 0 (key-hash k) k v added))
(cnt (pmap-cnt m)) (ord (pmap-order m)))
(if (unbox added)
(if (and ord (fx<? cnt array-map-limit))
(make-pmap r (fx+ cnt 1) (append-key ord k))
(make-pmap r (fx+ cnt 1) #f))
(make-pmap r cnt ord))))
;; force-ordered / force-hash inserts for rebuilding a map whose final mode is
;; already decided (array-map ctor, transient persistent!).
(define (pmap-put-ordered m k v)
(let* ((added (box #f)) (r (node-assoc (pmap-root m) 0 (key-hash k) k v added)))
(make-pmap r (if (unbox added) (fx+ (pmap-cnt m) 1) (pmap-cnt m)))))
(if (unbox added)
(make-pmap r (fx+ (pmap-cnt m) 1) (append-key (or (pmap-order m) '()) k))
(make-pmap r (pmap-cnt m) (pmap-order m)))))
(define (pmap-put-hash m k v)
(let* ((added (box #f)) (r (node-assoc (pmap-root m) 0 (key-hash k) k v added)))
(make-pmap r (if (unbox added) (fx+ (pmap-cnt m) 1) (pmap-cnt m)) #f)))
(define (pmap->hash m) (if (pmap-order m) (make-pmap (pmap-root m) (pmap-cnt m) #f) m))
(define (pmap-dissoc m k)
(let* ((removed (box #f)) (r (node-dissoc (pmap-root m) 0 (key-hash k) k removed)))
(make-pmap r (if (unbox removed) (fx- (pmap-cnt m) 1) (pmap-cnt m)))))
(let* ((removed (box #f)) (r (node-dissoc (pmap-root m) 0 (key-hash k) k removed))
(ord (pmap-order m)))
(if (unbox removed)
(make-pmap r (fx- (pmap-cnt m) 1) (if ord (remove-key ord k) #f))
m)))
(define (pmap-get m k default) (node-get (pmap-root m) 0 (key-hash k) k default))
(define (pmap-contains? m k) (not (eq? pmap-absent (node-get (pmap-root m) 0 (key-hash k) k pmap-absent))))
(define (pmap-fold m proc acc) (node-fold (pmap-root m) proc acc))
;; The universal fold idiom across the runtime is `(pmap-fold m (lambda (k v a)
;; (cons ... a)) '())`, which accumulates in REVERSE visitation order. So that this
;; reconstructs the map's INSERTION order, pmap-fold visits an array-mode map's keys
;; in reverse insertion order; a hash-mode map visits HAMT order (its iteration
;; order is unspecified, so reverse-of-HAMT is equivalent and matches prior
;; behaviour). Use pmap-fold-fwd when building a value directly in iteration order.
(define (pmap-fold m proc acc)
(let ((ord (pmap-order m)))
(if ord
(fold-right (lambda (k a) (proc k (pmap-get m k jolt-nil) a)) acc ord) ; visits last->first
(node-fold (pmap-root m) proc acc))))
;; visit entries in iteration (insertion) order — for code that builds a new map /
;; ordered value directly rather than via cons-accumulation.
(define (pmap-fold-fwd m proc acc)
(let ((ord (pmap-order m)))
(if ord
(let loop ((ks ord) (a acc))
(if (null? ks) a (loop (cdr ks) (proc (car ks) (pmap-get m (car ks) jolt-nil) a))))
(node-fold (pmap-root m) proc acc))))
;; map LITERAL ({...}): array map up to 8 entries, hash map beyond (RT.map).
(define (jolt-hash-map . kvs)
(let loop ((m empty-pmap) (kvs kvs))
(cond ((null? kvs) m)
(cond ((null? kvs) (if (fx>? (pmap-cnt m) array-map-limit) (pmap->hash m) m))
((null? (cdr kvs)) (error 'hash-map "odd number of map literal entries"))
(else (loop (pmap-assoc m (car kvs) (cadr kvs)) (cddr kvs))))))
(else (loop (pmap-put-ordered m (car kvs) (cadr kvs)) (cddr kvs))))))
;; array-map ctor: insertion-ordered regardless of size (createAsIfByAssoc).
(define (jolt-array-map-build kvs)
(let loop ((m empty-pmap) (kvs kvs))
(cond ((null? kvs) m)
((null? (cdr kvs)) (error 'array-map "odd number of map entries"))
(else (loop (pmap-put-ordered m (car kvs) (cadr kvs)) (cddr kvs))))))
;; hash-map ctor: hash order (PersistentHashMap).
(define (jolt-hash-map-build kvs)
(let loop ((m empty-pmap-hash) (kvs kvs))
(cond ((null? kvs) m)
((null? (cdr kvs)) (error 'hash-map "odd number of map entries"))
(else (loop (pmap-put-hash m (car kvs) (cadr kvs)) (cddr kvs))))))
(define-record-type pset (fields m) (nongenerative chez-pset-v1))
(define empty-pset (make-pset empty-pmap))
(define empty-pset (make-pset empty-pmap-hash)) ; sets are hash-ordered
(define (pset-conj s e) (if (pmap-contains? (pset-m s) e) s (make-pset (pmap-assoc (pset-m s) e e))))
(define (pset-disj s e) (make-pset (pmap-dissoc (pset-m s) e)))
(define (pset-contains? s e) (pmap-contains? (pset-m s) e))
@ -327,7 +393,7 @@
((empty-list-t? coll) (cseq-list x jolt-nil))
((pmap? coll)
(cond ((jolt-nil? x) coll) ; (conj m nil) = m
((pmap? x) (pmap-fold x (lambda (k v m) (pmap-assoc m k v)) coll)) ; merge
((pmap? x) (pmap-fold-fwd x (lambda (k v m) (pmap-assoc m k v)) coll)) ; merge in x's order
((and (pvec? x) (fx=? 2 (pvec-count x)))
(pmap-assoc coll (pvec-nth-d x 0 jolt-nil) (pvec-nth-d x 1 jolt-nil)))
(else (error 'conj "conj on a map expects a [k v] pair or a map"))))

View file

@ -198,7 +198,7 @@
(or (jolt-nil? dm) (jolt-nil? (jolt-get dm hc-kw-line))))
(jolt-with-meta dst
(if (pmap? dm)
(pmap-fold sp (lambda (k v acc) (jolt-assoc1 acc k v)) dm)
(pmap-fold-fwd sp (lambda (k v acc) (jolt-assoc1 acc k v)) dm)
sp))
dst))
dst))

View file

@ -4,11 +4,12 @@
;; binds the public clojure.core names to them. Loaded after def-var! (rt.ss) +
;; the collections + seq tiers. hash-map/array-map/hash-set/set/rand semantics.
;; hash-map / hash-set: variadic kvs / elems straight onto the existing ctors.
;; array-map: Clojure preserves insertion order, but jolt's `=` is structural and
;; the parity corpus compares by value, so a pmap is observationally equal for
;; the tested cases; keys-ordering is a separate (untested-here) concern.
(define (jolt-array-map . kvs) (apply jolt-hash-map kvs))
;; array-map: insertion-ordered, any size (Clojure's PersistentArrayMap, via
;; createAsIfByAssoc). hash-map: hash order (PersistentHashMap). The map LITERAL
;; ctor (jolt-hash-map, emitted for {...}) is array-ordered up to 8 entries and
;; hash beyond, matching RT.map.
(define (jolt-array-map . kvs) (jolt-array-map-build kvs))
(define (jolt-hash-map-fn . kvs) (jolt-hash-map-build kvs))
;; set: realize any seqable to a list, then dedup through the set ctor. nil -> #{}.
(define (jolt-set coll)
@ -20,7 +21,7 @@
(let ((r (random 1.0)))
(if (null? n) r (* r (exact->inexact (car n))))))
(def-var! "clojure.core" "hash-map" jolt-hash-map)
(def-var! "clojure.core" "hash-map" jolt-hash-map-fn)
(def-var! "clojure.core" "hash-set" jolt-hash-set)
(def-var! "clojure.core" "array-map" jolt-array-map)
(def-var! "clojure.core" "set" jolt-set)

View file

@ -31,7 +31,7 @@
(define (meta-copy x)
(cond
((pvec? x) (make-pvec (pvec-v x) (pvec-ent x)))
((pmap? x) (make-pmap (pmap-root x) (pmap-cnt x)))
((pmap? x) (make-pmap (pmap-root x) (pmap-cnt x) (pmap-order x)))
((pset? x) (make-pset (pset-m x)))
((jrec? x) (make-jrec (jrec-desc x) (jrec-vec-copy (jrec-vals x)) (jrec-ext x)))
;; a reify shares its (read-only) method table + protos but gets a fresh

View file

@ -299,7 +299,7 @@
(define (rdr-merge-meta old new)
(if (pmap? old)
(pmap-fold new (lambda (k v acc) (jolt-assoc1 acc k v)) old)
(pmap-fold-fwd new (lambda (k v acc) (jolt-assoc1 acc k v)) old)
new))
(define (rdr-attach-meta target meta)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -41,7 +41,9 @@
;; expansion still re-analyzes as a set literal.
(define (jolt-sqset . parts) (apply jolt-hash-set (sq-flatten parts)))
;; map FORM: a plain pmap (the analyzer's form-map? = pmap with no :jolt/type).
(define (jolt-sqmap . parts) (apply jolt-hash-map parts))
;; Clojure's syntaxQuote builds the map via `apply hash-map`, so a `{...} template
;; is HASH-ordered (unlike a {...} literal, which keeps insertion order).
(define (jolt-sqmap . parts) (jolt-hash-map-build parts))
(def-var! "clojure.core" "__sq1" jolt-sq1)
(def-var! "clojure.core" "__sqcat" jolt-sqcat)

View file

@ -16,11 +16,17 @@
;; this record, not a pvec), which group-by relies on. Loaded after collections.ss
;; (persistent ops + key-hash) and converters.ss.
;; For a transient MAP, `n` holds the array-mode capacity (entries it can hold
;; before promoting to hash order) and `ord` the reverse insertion-order key list;
;; for a vector `n` is the element count. A transient array map promotes to hash
;; at max(8, source-count) entries (TransientArrayMap, array sized max(16, len)),
;; with no keyword exception — unlike the persistent assoc growth rule.
(define-record-type jolt-transient
(fields kind (mutable buf) (mutable n) (mutable active))
(nongenerative jolt-transient-v2))
(fields kind (mutable buf) (mutable n) (mutable active) (mutable ord))
(nongenerative jolt-transient-v3))
(define tvec-min-cap 8)
(define tmap-min-cap 8)
(define (jolt-transient-new coll)
(cond
@ -28,16 +34,27 @@
(let* ((v (pvec-v coll)) (cnt (vector-length v)) (cap (fxmax tvec-min-cap cnt))
(buf (make-vector cap jolt-nil)))
(let loop ((i 0)) (when (fx<? i cnt) (vector-set! buf i (vector-ref v i)) (loop (fx+ i 1))))
(make-jolt-transient 'vec buf cnt #t)))
(make-jolt-transient 'vec buf cnt #t #f)))
((pmap? coll)
(let ((ht (make-hashtable key-hash jolt=2)))
(pmap-fold coll (lambda (k v acc) (hashtable-set! ht k v) acc) 0)
(make-jolt-transient 'map ht 0 #t)))
(let ((ht (make-hashtable key-hash jolt=2)) (ord '()) (cnt 0))
;; visit in iteration order so `ord` ends up reverse-insertion (persistent! reverses it back)
(pmap-fold-fwd coll (lambda (k v acc) (hashtable-set! ht k v) (set! ord (cons k ord)) (set! cnt (fx+ cnt 1)) acc) 0)
(make-jolt-transient 'map ht (fxmax tmap-min-cap cnt) #t ord)))
((pset? coll)
(let ((ht (make-hashtable key-hash jolt=2)))
(pset-fold coll (lambda (e acc) (hashtable-set! ht e #t) acc) 0)
(make-jolt-transient 'set ht 0 #t)))
(else (make-jolt-transient 'cow coll 0 #t))))
(make-jolt-transient 'set ht 0 #t #f)))
(else (make-jolt-transient 'cow coll 0 #t #f))))
;; map put/delete that maintain the reverse insertion-order list in `ord`.
(define (tmap-put! t k v)
(let ((ht (jolt-transient-buf t)))
(unless (hashtable-contains? ht k) (jolt-transient-ord-set! t (cons k (jolt-transient-ord t))))
(hashtable-set! ht k v)))
(define (tmap-del! t k)
(let ((ht (jolt-transient-buf t)))
(when (hashtable-contains? ht k) (jolt-transient-ord-set! t (remove-key (jolt-transient-ord t) k)))
(hashtable-delete! ht k)))
(define (jolt-trans-check t who)
(unless (jolt-transient? t) (error #f (string-append who ": not a transient") t))
@ -60,9 +77,17 @@
(if (fx<? i cnt) (begin (vector-set! out i (vector-ref buf i)) (loop (fx+ i 1)))
(make-pvec out)))))))
((map)
(let ((ht (jolt-transient-buf t)) (m empty-pmap))
(vector-for-each (lambda (k) (set! m (pmap-assoc m k (hashtable-ref ht k jolt-nil)))) (hashtable-keys ht))
m))
(let* ((ht (jolt-transient-buf t)) (cnt (hashtable-size ht)) (cap (jolt-transient-n t)))
(if (fx>? cnt cap)
;; promoted past the array capacity: hash order
(let ((m empty-pmap-hash))
(vector-for-each (lambda (k) (set! m (pmap-put-hash m k (hashtable-ref ht k jolt-nil)))) (hashtable-keys ht))
m)
;; array map: rebuild in insertion order
(let ((m empty-pmap))
(for-each (lambda (k) (set! m (pmap-put-ordered m k (hashtable-ref ht k jolt-nil))))
(reverse (jolt-transient-ord t)))
m))))
((set)
(let ((ht (jolt-transient-buf t)) (s empty-pset))
(vector-for-each (lambda (e) (set! s (pset-conj s e))) (hashtable-keys ht))
@ -91,8 +116,8 @@
(define (tmap-conj-entry! t x)
(cond
((jolt-nil? x) #t)
((pvec? x) (hashtable-set! (jolt-transient-buf t) (pvec-nth-d x 0 jolt-nil) (pvec-nth-d x 1 jolt-nil)))
((pmap? x) (pmap-fold x (lambda (k v acc) (hashtable-set! (jolt-transient-buf t) k v) acc) 0))
((pvec? x) (tmap-put! t (pvec-nth-d x 0 jolt-nil) (pvec-nth-d x 1 jolt-nil)))
((pmap? x) (pmap-fold-fwd x (lambda (k v acc) (tmap-put! t k v) acc) 0))
(else (error #f "conj!: a transient map takes a map entry or a map" x))))
;; (conj!) -> fresh transient vector; (conj! coll) -> the 1-arity transducer-
@ -119,14 +144,14 @@
(let ((kvs (assoc-pad kvs0)))
(when (odd? (length kvs)) (error #f "assoc!: no value supplied for key"))
(case (jolt-transient-kind t)
((map) (let lp ((xs kvs)) (unless (null? xs) (hashtable-set! (jolt-transient-buf t) (car xs) (cadr xs)) (lp (cddr xs)))))
((map) (let lp ((xs kvs)) (unless (null? xs) (tmap-put! t (car xs) (cadr xs)) (lp (cddr xs)))))
((vec) (let lp ((xs kvs)) (unless (null? xs) (tvec-assoc1! t (car xs) (cadr xs)) (lp (cddr xs)))))
(else (jolt-transient-buf-set! t (apply jolt-assoc (jolt-transient-buf t) kvs)))))
t)
(define (jolt-dissoc! t . ks)
(jolt-trans-check t "dissoc!")
(case (jolt-transient-kind t)
((map) (for-each (lambda (k) (hashtable-delete! (jolt-transient-buf t) k)) ks))
((map) (for-each (lambda (k) (tmap-del! t k)) ks))
(else (jolt-transient-buf-set! t (apply jolt-dissoc (jolt-transient-buf t) ks))))
t)
(define (jolt-disj! t . xs)

View file

@ -224,13 +224,14 @@
(defn inst-ms [x]
(if (inst? x) (get x :ms) (throw (str "inst-ms requires an inst, got: " x))))
;; Clojure 1.11 map transformers. PHM base so transformed keys canonicalize
;; (collisions: last entry in seq order wins, matching the reference).
;; Clojure 1.11 map transformers. An empty-map base keeps insertion order;
;; transformed keys canonicalize via assoc (collisions: last entry in seq order
;; wins, matching the reference).
(defn update-keys [m f]
(reduce-kv (fn [acc k v] (assoc acc (f k) v)) (hash-map) m))
(reduce-kv (fn [acc k v] (assoc acc (f k) v)) {} m))
(defn update-vals [m f]
(reduce-kv (fn [acc k v] (assoc acc k (f v))) (hash-map) m))
(reduce-kv (fn [acc k v] (assoc acc k (f v))) {} m))
;; Vector-returning partition variants (1.11): lazy seqs OF vectors.
(defn partitionv

View file

@ -21,14 +21,15 @@
(defn true? [x] (= true x))
(defn false? [x] (= false x))
;; Presence-preserving: a key with a nil value is kept ((hash-map) base keeps
;; nil values and canonicalizes collection keys).
;; Presence-preserving and order-preserving: a key with a nil value is kept, and
;; the result follows keyseq order (an empty-map base keeps nil values and
;; canonicalizes collection keys).
(defn select-keys [map keyseq]
(reduce (fn [m k] (if (contains? map k) (assoc m k (get map k)) m))
(hash-map) keyseq))
{} keyseq))
(defn zipmap [keys vals]
(loop [m (hash-map) ks (seq keys) vs (seq vals)]
(loop [m {} ks (seq keys) vs (seq vals)]
(if (and ks vs)
(recur (assoc m (first ks) (first vs)) (next ks) (next vs))
m)))
@ -37,7 +38,7 @@
;; no-ops; all-nil (or no args) is nil.
(defn merge [& maps]
(when (some identity maps)
(reduce (fn [acc m] (if (nil? m) acc (conj (or acc (hash-map)) m)))
(reduce (fn [acc m] (if (nil? m) acc (conj (or acc {}) m)))
maps)))
(defn merge-with [f & maps]
@ -49,7 +50,7 @@
(assoc m k (f (get m k) v))
(assoc m k v))))
merge2 (fn [m1 m2]
(reduce merge-entry (or m1 (hash-map)) (seq m2)))]
(reduce merge-entry (or m1 {}) (seq m2)))]
(reduce merge2 maps))))
(defn get-in

View file

@ -23,7 +23,7 @@
"=" "jolt=" "inc" "jolt-inc" "dec" "jolt-dec" "not" "jolt-not"
"min" "min" "max" "max"
"mod" "modulo" "rem" "remainder" "quot" "quotient"
"vector" "jolt-vector" "hash-map" "jolt-hash-map" "hash-set" "jolt-hash-set"
"vector" "jolt-vector" "hash-map" "jolt-hash-map-fn" "hash-set" "jolt-hash-set"
"conj" "jolt-conj" "get" "jolt-get" "nth" "jolt-nth" "count" "jolt-count"
"assoc" "jolt-assoc" "dissoc" "jolt-dissoc" "contains?" "jolt-contains?"
"empty?" "jolt-empty?" "peek" "jolt-peek" "pop" "jolt-pop"

View file

@ -911,6 +911,25 @@
{:suite "maps / literal construction" :label "equality with phm" :expected "true" :actual "(= {:a 1 :b 2} (assoc {:a 1} :b 2))"}
{:suite "maps / literal construction" :label "keys work after assoc" :expected "2" :actual "(:b (assoc {:a 1 :b 2} :c 3))"}
{:suite "maps / literal construction" :label "literal in fn body" :expected "12" :actual "(do (defn mfp-mk [x] {:v (* x 2)}) (:v (mfp-mk 6)))"}
{:suite "maps / insertion order" :label "small literal keys" :expected "[:c :a :b]" :actual "(vec (keys {:c 3 :a 1 :b 2}))"}
{:suite "maps / insertion order" :label "small literal vals" :expected "[3 1 2]" :actual "(vec (vals {:c 3 :a 1 :b 2}))"}
{:suite "maps / insertion order" :label "seq follows keys" :expected "[:c :a :b]" :actual "(mapv key (seq {:c 3 :a 1 :b 2}))"}
{:suite "maps / insertion order" :label "array-map any size" :expected "[:z :y :x :w :v :u :t :s :r :q]" :actual "(vec (keys (array-map :z 1 :y 2 :x 3 :w 4 :v 5 :u 6 :t 7 :s 8 :r 9 :q 10)))"}
{:suite "maps / insertion order" :label "assoc appends new key" :expected "[:c :a :z]" :actual "(vec (keys (assoc {:c 3 :a 1} :z 9)))"}
{:suite "maps / insertion order" :label "assoc replace keeps position" :expected "[:c :a]" :actual "(vec (keys (assoc {:c 3 :a 1} :c 9)))"}
{:suite "maps / insertion order" :label "dissoc preserves order" :expected "[:c :b]" :actual "(vec (keys (dissoc {:c 3 :a 1 :b 2} :a)))"}
{:suite "maps / insertion order" :label "into onto empty (<=8)" :expected "[:c :a :b]" :actual "(vec (keys (into {} [[:c 3] [:a 1] [:b 2]])))"}
{:suite "maps / insertion order" :label "reduce assoc" :expected "[:e :d :c :b :a]" :actual "(vec (keys (reduce (fn [m k] (assoc m k k)) {} [:e :d :c :b :a])))"}
{:suite "maps / insertion order" :label "conj a map merges in order" :expected "[:a :b :c]" :actual "(vec (keys (conj {:a 1} {:b 2 :c 3})))"}
{:suite "maps / insertion order" :label "merge keeps right-hand order" :expected "[:a :c :b]" :actual "(vec (keys (merge {:a 1} {:c 3 :b 2})))"}
{:suite "maps / insertion order" :label "assoc stays ordered through 8 entries" :expected "true" :actual "(= (vec (keys (reduce (fn [m i] (assoc m (keyword (str \"k\" i)) i)) {} (range 8)))) (mapv (fn [i] (keyword (str \"k\" i))) (range 8)))"}
{:suite "maps / insertion order" :label "assoc promotes to hash past 8 entries" :expected "9" :actual "(count (reduce (fn [m i] (assoc m (keyword (str \"k\" i)) i)) {} (range 9)))"}
{:suite "maps / insertion order" :label "array-map keeps order past 8" :expected "[:k0 :k1 :k2 :k3 :k4 :k5 :k6 :k7 :k8 :k9 :k10 :k11]" :actual "(vec (keys (apply array-map (mapcat (fn [i] [(keyword (str \"k\" i)) i]) (range 12)))))"}
{:suite "maps / insertion order" :label "update-keys preserves order" :expected "[:a :b :c]" :actual "(vec (keys (update-vals (array-map :a 1 :b 2 :c 3) inc)))"}
{:suite "maps / insertion order" :label "zipmap from ordered keys/vals" :expected "[:c :a :b]" :actual "(vec (keys (zipmap (keys {:c 3 :a 1 :b 2}) (vals {:c 3 :a 1 :b 2}))))"}
{:suite "maps / insertion order" :label "select-keys follows keyseq" :expected "[:c :a]" :actual "(vec (keys (select-keys {:a 1 :b 2 :c 3} [:c :a])))"}
{:suite "maps / insertion order" :label "frequencies first-occurrence order" :expected "[:c :a :b]" :actual "(vec (keys (frequencies [:c :a :b :a])))"}
{:suite "maps / insertion order" :label "group-by first-occurrence order" :expected "[:c :a :b]" :actual "(vec (keys (group-by identity [:c :a :b :a])))"}
{:suite "clojure.math" :label "sqrt" :expected "true" :actual "(< 1.4142 (clojure.math/sqrt 2) 1.4143)"}
{:suite "clojure.math" :label "pow" :expected "1024" :actual "(long (clojure.math/pow 2 10))"}
{:suite "clojure.math" :label "tan of 0" :expected "0" :actual "(long (clojure.math/tan 0))"}