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:
parent
e2efff6c8e
commit
bfa2cbf49d
13 changed files with 217 additions and 102 deletions
|
|
@ -287,26 +287,92 @@
|
||||||
;; ============================================================================
|
;; ============================================================================
|
||||||
;; persistent map / set over the HAMT
|
;; persistent map / set over the HAMT
|
||||||
;; ============================================================================
|
;; ============================================================================
|
||||||
(define-record-type pmap (fields root cnt) (nongenerative chez-pmap-v1))
|
;; A small map keeps its keys in INSERTION order (Clojure's PersistentArrayMap),
|
||||||
(define empty-pmap (make-pmap empty-hnode 0))
|
;; 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
|
(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)
|
(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)))
|
(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)
|
(define (pmap-dissoc m k)
|
||||||
(let* ((removed (box #f)) (r (node-dissoc (pmap-root m) 0 (key-hash k) k removed)))
|
(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)))))
|
(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-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-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)
|
(define (jolt-hash-map . kvs)
|
||||||
(let loop ((m empty-pmap) (kvs 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"))
|
((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-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-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-disj s e) (make-pset (pmap-dissoc (pset-m s) e)))
|
||||||
(define (pset-contains? s e) (pmap-contains? (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))
|
((empty-list-t? coll) (cseq-list x jolt-nil))
|
||||||
((pmap? coll)
|
((pmap? coll)
|
||||||
(cond ((jolt-nil? x) coll) ; (conj m nil) = m
|
(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)))
|
((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)))
|
(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"))))
|
(else (error 'conj "conj on a map expects a [k v] pair or a map"))))
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@
|
||||||
(or (jolt-nil? dm) (jolt-nil? (jolt-get dm hc-kw-line))))
|
(or (jolt-nil? dm) (jolt-nil? (jolt-get dm hc-kw-line))))
|
||||||
(jolt-with-meta dst
|
(jolt-with-meta dst
|
||||||
(if (pmap? dm)
|
(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))
|
sp))
|
||||||
dst))
|
dst))
|
||||||
dst))
|
dst))
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@
|
||||||
;; binds the public clojure.core names to them. Loaded after def-var! (rt.ss) +
|
;; 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.
|
;; 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: insertion-ordered, any size (Clojure's PersistentArrayMap, via
|
||||||
;; array-map: Clojure preserves insertion order, but jolt's `=` is structural and
|
;; createAsIfByAssoc). hash-map: hash order (PersistentHashMap). The map LITERAL
|
||||||
;; the parity corpus compares by value, so a pmap is observationally equal for
|
;; ctor (jolt-hash-map, emitted for {...}) is array-ordered up to 8 entries and
|
||||||
;; the tested cases; keys-ordering is a separate (untested-here) concern.
|
;; hash beyond, matching RT.map.
|
||||||
(define (jolt-array-map . kvs) (apply jolt-hash-map kvs))
|
(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 -> #{}.
|
;; set: realize any seqable to a list, then dedup through the set ctor. nil -> #{}.
|
||||||
(define (jolt-set coll)
|
(define (jolt-set coll)
|
||||||
|
|
@ -20,7 +21,7 @@
|
||||||
(let ((r (random 1.0)))
|
(let ((r (random 1.0)))
|
||||||
(if (null? n) r (* r (exact->inexact (car n))))))
|
(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" "hash-set" jolt-hash-set)
|
||||||
(def-var! "clojure.core" "array-map" jolt-array-map)
|
(def-var! "clojure.core" "array-map" jolt-array-map)
|
||||||
(def-var! "clojure.core" "set" jolt-set)
|
(def-var! "clojure.core" "set" jolt-set)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
(define (meta-copy x)
|
(define (meta-copy x)
|
||||||
(cond
|
(cond
|
||||||
((pvec? x) (make-pvec (pvec-v x) (pvec-ent x)))
|
((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)))
|
((pset? x) (make-pset (pset-m x)))
|
||||||
((jrec? x) (make-jrec (jrec-desc x) (jrec-vec-copy (jrec-vals x)) (jrec-ext 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
|
;; a reify shares its (read-only) method table + protos but gets a fresh
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,7 @@
|
||||||
|
|
||||||
(define (rdr-merge-meta old new)
|
(define (rdr-merge-meta old new)
|
||||||
(if (pmap? old)
|
(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))
|
new))
|
||||||
|
|
||||||
(define (rdr-attach-meta target meta)
|
(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
|
|
@ -41,7 +41,9 @@
|
||||||
;; expansion still re-analyzes as a set literal.
|
;; expansion still re-analyzes as a set literal.
|
||||||
(define (jolt-sqset . parts) (apply jolt-hash-set (sq-flatten parts)))
|
(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).
|
;; 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" "__sq1" jolt-sq1)
|
||||||
(def-var! "clojure.core" "__sqcat" jolt-sqcat)
|
(def-var! "clojure.core" "__sqcat" jolt-sqcat)
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,17 @@
|
||||||
;; this record, not a pvec), which group-by relies on. Loaded after collections.ss
|
;; this record, not a pvec), which group-by relies on. Loaded after collections.ss
|
||||||
;; (persistent ops + key-hash) and converters.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
|
(define-record-type jolt-transient
|
||||||
(fields kind (mutable buf) (mutable n) (mutable active))
|
(fields kind (mutable buf) (mutable n) (mutable active) (mutable ord))
|
||||||
(nongenerative jolt-transient-v2))
|
(nongenerative jolt-transient-v3))
|
||||||
|
|
||||||
(define tvec-min-cap 8)
|
(define tvec-min-cap 8)
|
||||||
|
(define tmap-min-cap 8)
|
||||||
|
|
||||||
(define (jolt-transient-new coll)
|
(define (jolt-transient-new coll)
|
||||||
(cond
|
(cond
|
||||||
|
|
@ -28,16 +34,27 @@
|
||||||
(let* ((v (pvec-v coll)) (cnt (vector-length v)) (cap (fxmax tvec-min-cap cnt))
|
(let* ((v (pvec-v coll)) (cnt (vector-length v)) (cap (fxmax tvec-min-cap cnt))
|
||||||
(buf (make-vector cap jolt-nil)))
|
(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))))
|
(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)
|
((pmap? coll)
|
||||||
(let ((ht (make-hashtable key-hash jolt=2)))
|
(let ((ht (make-hashtable key-hash jolt=2)) (ord '()) (cnt 0))
|
||||||
(pmap-fold coll (lambda (k v acc) (hashtable-set! ht k v) acc) 0)
|
;; visit in iteration order so `ord` ends up reverse-insertion (persistent! reverses it back)
|
||||||
(make-jolt-transient 'map ht 0 #t)))
|
(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)
|
((pset? coll)
|
||||||
(let ((ht (make-hashtable key-hash jolt=2)))
|
(let ((ht (make-hashtable key-hash jolt=2)))
|
||||||
(pset-fold coll (lambda (e acc) (hashtable-set! ht e #t) acc) 0)
|
(pset-fold coll (lambda (e acc) (hashtable-set! ht e #t) acc) 0)
|
||||||
(make-jolt-transient 'set ht 0 #t)))
|
(make-jolt-transient 'set ht 0 #t #f)))
|
||||||
(else (make-jolt-transient 'cow coll 0 #t))))
|
(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)
|
(define (jolt-trans-check t who)
|
||||||
(unless (jolt-transient? t) (error #f (string-append who ": not a transient") t))
|
(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)))
|
(if (fx<? i cnt) (begin (vector-set! out i (vector-ref buf i)) (loop (fx+ i 1)))
|
||||||
(make-pvec out)))))))
|
(make-pvec out)))))))
|
||||||
((map)
|
((map)
|
||||||
(let ((ht (jolt-transient-buf t)) (m empty-pmap))
|
(let* ((ht (jolt-transient-buf t)) (cnt (hashtable-size ht)) (cap (jolt-transient-n t)))
|
||||||
(vector-for-each (lambda (k) (set! m (pmap-assoc m k (hashtable-ref ht k jolt-nil)))) (hashtable-keys ht))
|
(if (fx>? cnt cap)
|
||||||
m))
|
;; 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)
|
((set)
|
||||||
(let ((ht (jolt-transient-buf t)) (s empty-pset))
|
(let ((ht (jolt-transient-buf t)) (s empty-pset))
|
||||||
(vector-for-each (lambda (e) (set! s (pset-conj s e))) (hashtable-keys ht))
|
(vector-for-each (lambda (e) (set! s (pset-conj s e))) (hashtable-keys ht))
|
||||||
|
|
@ -91,8 +116,8 @@
|
||||||
(define (tmap-conj-entry! t x)
|
(define (tmap-conj-entry! t x)
|
||||||
(cond
|
(cond
|
||||||
((jolt-nil? x) #t)
|
((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)))
|
((pvec? x) (tmap-put! 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))
|
((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))))
|
(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-
|
;; (conj!) -> fresh transient vector; (conj! coll) -> the 1-arity transducer-
|
||||||
|
|
@ -119,14 +144,14 @@
|
||||||
(let ((kvs (assoc-pad kvs0)))
|
(let ((kvs (assoc-pad kvs0)))
|
||||||
(when (odd? (length kvs)) (error #f "assoc!: no value supplied for key"))
|
(when (odd? (length kvs)) (error #f "assoc!: no value supplied for key"))
|
||||||
(case (jolt-transient-kind t)
|
(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)))))
|
((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)))))
|
(else (jolt-transient-buf-set! t (apply jolt-assoc (jolt-transient-buf t) kvs)))))
|
||||||
t)
|
t)
|
||||||
(define (jolt-dissoc! t . ks)
|
(define (jolt-dissoc! t . ks)
|
||||||
(jolt-trans-check t "dissoc!")
|
(jolt-trans-check t "dissoc!")
|
||||||
(case (jolt-transient-kind t)
|
(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))))
|
(else (jolt-transient-buf-set! t (apply jolt-dissoc (jolt-transient-buf t) ks))))
|
||||||
t)
|
t)
|
||||||
(define (jolt-disj! t . xs)
|
(define (jolt-disj! t . xs)
|
||||||
|
|
|
||||||
|
|
@ -224,13 +224,14 @@
|
||||||
(defn inst-ms [x]
|
(defn inst-ms [x]
|
||||||
(if (inst? x) (get x :ms) (throw (str "inst-ms requires an inst, got: " 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
|
;; Clojure 1.11 map transformers. An empty-map base keeps insertion order;
|
||||||
;; (collisions: last entry in seq order wins, matching the reference).
|
;; transformed keys canonicalize via assoc (collisions: last entry in seq order
|
||||||
|
;; wins, matching the reference).
|
||||||
(defn update-keys [m f]
|
(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]
|
(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.
|
;; Vector-returning partition variants (1.11): lazy seqs OF vectors.
|
||||||
(defn partitionv
|
(defn partitionv
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,15 @@
|
||||||
(defn true? [x] (= true x))
|
(defn true? [x] (= true x))
|
||||||
(defn false? [x] (= false x))
|
(defn false? [x] (= false x))
|
||||||
|
|
||||||
;; Presence-preserving: a key with a nil value is kept ((hash-map) base keeps
|
;; Presence-preserving and order-preserving: a key with a nil value is kept, and
|
||||||
;; nil values and canonicalizes collection keys).
|
;; the result follows keyseq order (an empty-map base keeps nil values and
|
||||||
|
;; canonicalizes collection keys).
|
||||||
(defn select-keys [map keyseq]
|
(defn select-keys [map keyseq]
|
||||||
(reduce (fn [m k] (if (contains? map k) (assoc m k (get map k)) m))
|
(reduce (fn [m k] (if (contains? map k) (assoc m k (get map k)) m))
|
||||||
(hash-map) keyseq))
|
{} keyseq))
|
||||||
|
|
||||||
(defn zipmap [keys vals]
|
(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)
|
(if (and ks vs)
|
||||||
(recur (assoc m (first ks) (first vs)) (next ks) (next vs))
|
(recur (assoc m (first ks) (first vs)) (next ks) (next vs))
|
||||||
m)))
|
m)))
|
||||||
|
|
@ -37,7 +38,7 @@
|
||||||
;; no-ops; all-nil (or no args) is nil.
|
;; no-ops; all-nil (or no args) is nil.
|
||||||
(defn merge [& maps]
|
(defn merge [& maps]
|
||||||
(when (some identity 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)))
|
maps)))
|
||||||
|
|
||||||
(defn merge-with [f & maps]
|
(defn merge-with [f & maps]
|
||||||
|
|
@ -49,7 +50,7 @@
|
||||||
(assoc m k (f (get m k) v))
|
(assoc m k (f (get m k) v))
|
||||||
(assoc m k v))))
|
(assoc m k v))))
|
||||||
merge2 (fn [m1 m2]
|
merge2 (fn [m1 m2]
|
||||||
(reduce merge-entry (or m1 (hash-map)) (seq m2)))]
|
(reduce merge-entry (or m1 {}) (seq m2)))]
|
||||||
(reduce merge2 maps))))
|
(reduce merge2 maps))))
|
||||||
|
|
||||||
(defn get-in
|
(defn get-in
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
"=" "jolt=" "inc" "jolt-inc" "dec" "jolt-dec" "not" "jolt-not"
|
"=" "jolt=" "inc" "jolt-inc" "dec" "jolt-dec" "not" "jolt-not"
|
||||||
"min" "min" "max" "max"
|
"min" "min" "max" "max"
|
||||||
"mod" "modulo" "rem" "remainder" "quot" "quotient"
|
"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"
|
"conj" "jolt-conj" "get" "jolt-get" "nth" "jolt-nth" "count" "jolt-count"
|
||||||
"assoc" "jolt-assoc" "dissoc" "jolt-dissoc" "contains?" "jolt-contains?"
|
"assoc" "jolt-assoc" "dissoc" "jolt-dissoc" "contains?" "jolt-contains?"
|
||||||
"empty?" "jolt-empty?" "peek" "jolt-peek" "pop" "jolt-pop"
|
"empty?" "jolt-empty?" "peek" "jolt-peek" "pop" "jolt-pop"
|
||||||
|
|
|
||||||
|
|
@ -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 "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 "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 / 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 "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 "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))"}
|
{:suite "clojure.math" :label "tan of 0" :expected "0" :actual "(long (clojure.math/tan 0))"}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue