├── script ├── build.sh ├── bootstrap.sh ├── clean.sh ├── nrepl.sh ├── test_clj.sh ├── test_cljs.sh ├── bench_cljs.sh ├── test_cljs_watch.sh ├── bench_clj.sh └── repl.sh ├── .gitignore ├── extras └── become_a_patron_button@2x.png ├── package.json ├── src-java └── me │ └── tonsky │ └── persistent_sorted_set │ ├── RefType.java │ ├── ISeek.java │ ├── JavaIter.java │ ├── Stitch.java │ ├── ArrayUtil.java │ ├── IPersistentSortedSet.java │ ├── IStorage.java │ ├── Settings.java │ ├── Chunk.java │ ├── APersistentSortedSet.java │ ├── ANode.java │ ├── Seq.java │ ├── Leaf.java │ ├── PersistentSortedSet.java │ └── Branch.java ├── shadow-cljs.edn ├── test-clojure └── me │ └── tonsky │ └── persistent_sorted_set │ └── test │ ├── small.cljc │ ├── stress.cljc │ ├── storage.clj │ └── core.cljc ├── LICENSE ├── project.clj ├── CHANGES.md ├── deps.edn ├── .github └── workflows │ └── build-deploy.yml ├── bench-clojure └── me │ └── tonsky │ └── persistent_sorted_set │ ├── bench │ └── core.cljc │ └── bench.cljc ├── src-clojure └── me │ └── tonsky │ ├── persistent_sorted_set │ └── arrays.cljc │ ├── persistent_sorted_set.clj │ └── persistent_sorted_set.cljs ├── README.md └── yarn.lock /script/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "$(dirname "$0")/.." 4 | 5 | lein javac -------------------------------------------------------------------------------- /script/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "$(dirname "$0")/.." 4 | 5 | yarn install -------------------------------------------------------------------------------- /script/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "$(dirname "$0")/.." 4 | 5 | rm -rf target -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nrepl-port 2 | .lein-* 3 | target 4 | .DS_Store 5 | .idea 6 | pom.xml 7 | bench-clojure/*.edn 8 | node_modules 9 | .shadow-cljs -------------------------------------------------------------------------------- /extras/become_a_patron_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonsky/persistent-sorted-set/HEAD/extras/become_a_patron_button@2x.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "shadow-cljs": "^2.20.12", 4 | "source-map-support": "^0.5.21" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /script/nrepl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "$(dirname "$0")/.." 4 | 5 | clj -M:dev -m nrepl.cmdline --interactive -------------------------------------------------------------------------------- /script/test_clj.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "$(dirname "$0")/.." 4 | 5 | ./script/build.sh 6 | clojure -X:test -------------------------------------------------------------------------------- /script/test_cljs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "$(dirname "$0")/.." 4 | 5 | yarn shadow-cljs release test 6 | node target/test.js -------------------------------------------------------------------------------- /script/bench_cljs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "$(dirname "$0")/.." 4 | 5 | yarn shadow-cljs release bench 6 | node target/bench.js $@ -------------------------------------------------------------------------------- /script/test_cljs_watch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "$(dirname "$0")/.." 4 | 5 | yarn shadow-cljs watch test --config-merge '{:autorun true}' -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/RefType.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | public enum RefType { 4 | STRONG, 5 | SOFT, 6 | WEAK; 7 | } -------------------------------------------------------------------------------- /script/bench_clj.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "$(dirname "$0")/.." 4 | 5 | ./script/build.sh 6 | clojure -M:bench -m me.tonsky.persistent-sorted-set.bench $@ -------------------------------------------------------------------------------- /script/repl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit -o nounset -o pipefail 3 | cd "`dirname $0`/.." 4 | 5 | echo "Starting Socket REPL server on port 5555" 6 | clj -X clojure.core.server/start-server :name repl :port 5555 :accept clojure.core.server/repl :server-daemon false 7 | -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/ISeek.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.util.*; 4 | import clojure.lang.*; 5 | 6 | public interface ISeek { 7 | ISeq seek(Object to, Comparator cmp); 8 | default ISeq seek(Object to) { return seek(to, RT.DEFAULT_COMPARATOR); } 9 | } 10 | -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:source-paths ["src-clojure" "test-clojure" "bench-clojure"] 2 | :builds 3 | {:bench 4 | {:target :node-script 5 | :main me.tonsky.persistent-sorted-set.bench/-main 6 | :output-to "target/bench.js" 7 | :compiler-options {:infer-externs true}} 8 | 9 | :test 10 | {:target :node-test 11 | :output-to "target/test.js" 12 | :ns-regexp "me\\.tonsky\\.persistent-sorted-set\\.test\\..*$" 13 | :compiler-options {:infer-externs true}}}} -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/JavaIter.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.util.*; 4 | import clojure.lang.*; 5 | 6 | class JavaIter implements Iterator { 7 | final Seq _seq; 8 | boolean _over; 9 | 10 | JavaIter(Seq seq) { 11 | _seq = seq; 12 | _over = seq == null; 13 | } 14 | public boolean hasNext() { return !_over; } 15 | public Object next() { 16 | Object res = _seq.first(); 17 | _over = false == _seq.advance(); 18 | return res; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/Stitch.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | public class Stitch { 4 | Object[] target; 5 | int offset; 6 | 7 | public Stitch(Object[] target, int offset) { 8 | this.target = target; 9 | this.offset = offset; 10 | } 11 | 12 | public Stitch copyAll(Object[] source, int from, int to) { 13 | if (to >= from) { 14 | if (source != null) { 15 | System.arraycopy(source, from, target, offset, to - from); 16 | } 17 | offset += to - from; 18 | } 19 | return this; 20 | } 21 | 22 | public Stitch copyOne(Object val) { 23 | target[offset] = val; 24 | ++offset; 25 | return this; 26 | } 27 | } -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/ArrayUtil.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.util.*; 4 | import java.lang.reflect.Array; 5 | import clojure.lang.*; 6 | 7 | public class ArrayUtil { 8 | public static T[] copy(T[] src, int from, int to, T[] target, int offset) { 9 | System.arraycopy(src, from, target, offset, to-from); 10 | return target; 11 | } 12 | 13 | public static Object indexedToArray(Class type, Indexed coll, int from, int to) { 14 | int len = to - from; 15 | Object ret = Array.newInstance(type, len); 16 | for (int i = 0; i < len; ++i) 17 | Array.set(ret, i, coll.nth(i+from)); 18 | return ret; 19 | } 20 | 21 | public static int distinct(Comparator cmp, Object[] arr) { 22 | int to = 0; 23 | for (int idx = 1; idx < arr.length; ++idx) { 24 | if (cmp.compare(arr[idx], arr[to]) != 0) { 25 | ++to; 26 | if (to != idx) arr[to] = arr[idx]; 27 | } 28 | } 29 | return to + 1; 30 | } 31 | } -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/IPersistentSortedSet.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.util.*; 4 | import clojure.lang.*; 5 | 6 | @SuppressWarnings("unchecked") 7 | public interface IPersistentSortedSet extends Seqable, Reversible, Sorted { 8 | ISeq slice(Key from, Key to, Comparator cmp); 9 | ISeq rslice(Key from, Key to, Comparator cmp); 10 | 11 | default ISeq slice(Key from, Key to) { return slice(from, to, comparator()); } 12 | default ISeq rslice(Key from, Key to) { return rslice(from, to, comparator()); } 13 | 14 | // Seqable 15 | default ISeq seq() { return slice(null, null, comparator()); } 16 | 17 | // Reversible 18 | default ISeq rseq() { return rslice(null, null, comparator()); } 19 | 20 | // Sorted 21 | default ISeq seq(boolean asc) { return asc ? slice(null, null, comparator()) : rslice(null, null, comparator()); } 22 | default ISeq seqFrom(Object key, boolean asc) { return asc ? slice((Key) key, null, comparator()) : rslice((Key) key, null, comparator()); } 23 | } -------------------------------------------------------------------------------- /test-clojure/me/tonsky/persistent_sorted_set/test/small.cljc: -------------------------------------------------------------------------------- 1 | (ns me.tonsky.persistent-sorted-set.test.small 2 | (:require 3 | [me.tonsky.persistent-sorted-set :as set] 4 | [clojure.test :as t :refer [is are deftest testing]])) 5 | 6 | (deftest test-small 7 | (is (= (range 10 20) 8 | (seq (into (set/sorted-set) (range 10 20))))) 9 | 10 | (is (= (range 10 20) 11 | (seq (into (set/sorted-set) (reverse (range 10 20)))))) 12 | 13 | (is (= (range 10 20) 14 | (seq 15 | (set/slice 16 | (into (set/sorted-set) (reverse (range 0 100))) 17 | 10 18 | 19)))) 19 | 20 | (is (= (range 19 9 -1) 21 | (rseq (into (set/sorted-set) (range 10 20))))) 22 | 23 | (is (= (range 19 9 -1) 24 | (set/rslice (into (set/sorted-set) (range 0 40)) 19 10))) 25 | 26 | (is (= (range 10 20) 27 | (set/slice (into (set/sorted-set) (range 0 40)) 10 19))) 28 | 29 | (let [s (into (set/sorted-set) (shuffle (range 0 5001)))] 30 | (is (= (set/rslice s 5000 nil) 31 | (some-> (set/rslice s 5000 nil) rseq reverse)))) 32 | ) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nikita Prokopov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject persistent-sorted-set "0.0.0" 2 | :description "Fast B-tree based persistent sorted set for Clojure/Script" 3 | :license {:name "MIT"} 4 | :url "https://github.com/tonsky/persistent-sorted-set" 5 | 6 | :dependencies 7 | [[org.clojure/clojure "1.11.1" :scope "provided"] 8 | [org.clojure/clojurescript "1.11.60" :scope "provided"]] 9 | 10 | :plugins 11 | [[lein-cljsbuild "1.1.7"]] 12 | 13 | :source-paths ["src-clojure"] 14 | :java-source-paths ["src-java"] 15 | :test-paths ["test-clojure"] 16 | 17 | :javac-options ["-Xlint:unchecked" "-Xlint:-options" "-target" "8" "-source" "8" "-bootclasspath" ~(str (or (System/getenv "JAVA8_HOME") (throw (Exception. "Please set JAVA8_HOME"))) "/jre/lib/rt.jar")] 18 | :jvm-opts ["-ea"] 19 | 20 | :profiles 21 | {:1.9 22 | {:dependencies 23 | [[org.clojure/clojure "1.9.0" :scope "provided"] 24 | [org.clojure/clojurescript "1.9.946" :scope "provided"]]}} 25 | 26 | :deploy-repositories 27 | {"clojars" 28 | {:url "https://clojars.org/repo" 29 | :username "tonsky" 30 | :password :env/clojars_token 31 | :sign-releases false}}) 32 | -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/IStorage.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | public interface IStorage { 4 | /** 5 | * Given address, reconstruct and (optionally) cache the node. 6 | * Set itself would not store any strong references to nodes and 7 | * might request them by address during its operation many times. 8 | * 9 | * Use ANode.restore() or Leaf(keys)/Branch(level, keys, addresses) ctors 10 | */ 11 | ANode restore(Address address); 12 | 13 | /** 14 | * Tell the storage layer that address is accessed. 15 | * Useful for e.g. implementing LRU cache in storage. 16 | */ 17 | default void accessed(Address address) { 18 | } 19 | 20 | /** 21 | * Will be called after all children of node has been stored and have addresses. 22 | * 23 | * For node instanceof Leaf, store node.keys() 24 | * For node instanceof Branch, store node.level(), node.keys() and node.addresses() 25 | * Generate and return new address for node 26 | * Return null if doesn’t need to be stored 27 | */ 28 | Address store(ANode node); 29 | } 30 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 0.3.0 2 | 3 | - JVM: Per-set branching factor 4 | - JVM: Choose type of reference for stored nodes (strong, soft, weak) per set 5 | - JVM: Defaults to 512 branching factor, soft ref-type 6 | - Added `settings` and `sorted-set*` 7 | - JVM: Added `storage` and `opts` args to ctors 8 | - JVM: Short-circuit `walkAddresses` 9 | 10 | # 0.2.3 11 | 12 | - Support set > 1M in CLJS (< 16^6 = 16M for fast path, up to 32^10 = 10^15 theoretically) 13 | 14 | # 0.2.2 15 | 16 | - Made Seq class public #11 via @FiV0 17 | 18 | # 0.2.1 19 | 20 | Added: 21 | 22 | - `seek` to jump ahead during iteration #9 via @FiV0 23 | 24 | # 0.2.0 25 | 26 | Added: 27 | 28 | - Durability in Clojure version #7 with @whilo 29 | - `IStorage`, `store`, `restore`, `restore-by`, `walk-addresses`, `set-branching-factor!` 30 | 31 | # 0.1.4 32 | 33 | Special handling of nils in slice/rslice in CLJS, matching CLJ behaviour #6 34 | 35 | # 0.1.3 36 | 37 | Fixed NPE in `me.tonsky.persistent-sorted-set.arrays/array?` #4 #5 thx @timothypratley 38 | 39 | # 0.1.2 40 | 41 | Throw if iterating over a transient set that has been mutated. 42 | 43 | # 0.1.1 44 | 45 | Recompiled for Java 8. 46 | 47 | # 0.1.0 48 | 49 | Initial. -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src-clojure" "target/classes"] 2 | :deps 3 | {org.clojure/clojure {:mvn/version "1.11.1"}} 4 | :aliases 5 | {:dev 6 | {:extra-deps 7 | {nrepl/nrepl {:mvn/version "1.0.0"}} 8 | :extra-paths ["test-clojure" "dev"] 9 | :jvm-opts ["-ea"]} 10 | 11 | :cljs 12 | {:extra-deps 13 | {org.clojure/clojurescript {:mvn/version "1.11.60"}}} 14 | 15 | :build 16 | {:extra-deps {io.github.clojure/tools.build {:git/tag "v0.8.5" :git/sha "9c738da" #_#_:exclusions [org.slf4j/slf4j-nop]}} 17 | :ns-default build} 18 | 19 | :test 20 | {:extra-paths ["test-clojure"] 21 | :extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}} 22 | :main-opts ["-m" "cognitect.test-runner"] 23 | :exec-fn cognitect.test-runner.api/test 24 | :exec-args {:dirs ["test-clojure"] 25 | :patterns ["me\\.tonsky\\.persistent-sorted-set\\.test.*"]}} 26 | 27 | :bench 28 | {:extra-paths ["test-clojure" "bench-clojure"] 29 | :extra-deps 30 | {com.clojure-goes-fast/clj-async-profiler {:mvn/version "1.0.0"} 31 | criterium/criterium {:mvn/version "0.4.6"}} 32 | :jvm-opts ["-server" 33 | "-Duser.language=en-US" 34 | "-Djdk.attach.allowAttachSelf" 35 | "-XX:+UnlockDiagnosticVMOptions" 36 | "-XX:+DebugNonSafepoints"]}}} 37 | -------------------------------------------------------------------------------- /.github/workflows/build-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | tags: 8 | - '[0-9]+.[0-9]+.[0-9]+' 9 | paths: 10 | - 'src-clojure/**' 11 | - 'src-java/**' 12 | - 'test-clojure/**' 13 | - project.clj 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-22.04 18 | 19 | env: 20 | CLOJARS_TOKEN: ${{ secrets.CLOJARS_DEPLOY_TOKEN }} 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - run: | 26 | echo "JAVA_HOME=$JAVA_HOME_11_X64" >> $GITHUB_ENV 27 | echo "JAVA8_HOME=$JAVA_HOME_8_X64" >> $GITHUB_ENV 28 | echo "$JAVA_HOME_11_X64/bin" >> $GITHUB_PATH 29 | 30 | - name: Setup Clojure 31 | uses: DeLaGuardo/setup-clojure@10.0 32 | with: 33 | cli: 1.11.1.1200 34 | 35 | - run: ./script/bootstrap.sh 36 | 37 | - run: ./script/test_clj.sh 38 | 39 | - run: ./script/test_cljs.sh 40 | 41 | - if: ${{ startsWith(github.ref, 'refs/tags/') }} 42 | name: Set version 43 | run: | 44 | sed -i 's/"0.0.0"/"${{ github.ref_name }}"/g' project.clj 45 | 46 | - run: lein jar 47 | 48 | - if: ${{ startsWith(github.ref, 'refs/tags/') }} 49 | name: Deploy to Clojars 50 | run: | 51 | lein deploy clojars 52 | 53 | - uses: actions/upload-artifact@v3 54 | with: 55 | name: jar 56 | path: 'target/*.jar' -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/Settings.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.lang.ref.*; 4 | import java.util.concurrent.atomic.*; 5 | 6 | public class Settings { 7 | public final int _branchingFactor; 8 | public final RefType _refType; 9 | public final AtomicBoolean _edit; 10 | 11 | public Settings(int branchingFactor, RefType refType, AtomicBoolean edit) { 12 | _branchingFactor = branchingFactor; 13 | _refType = refType; 14 | _edit = edit; 15 | } 16 | 17 | public Settings() { 18 | this(0, null); 19 | } 20 | 21 | public Settings(int branchingFactor) { 22 | this(branchingFactor, null); 23 | } 24 | 25 | public Settings(int branchingFactor, RefType refType) { 26 | if (branchingFactor <= 0) { 27 | branchingFactor = 512; 28 | } 29 | if (null == refType) { 30 | refType = RefType.SOFT; 31 | } 32 | _branchingFactor = branchingFactor; 33 | _refType = refType; 34 | _edit = null; 35 | } 36 | 37 | public int minBranchingFactor() { 38 | return _branchingFactor >>> 1; 39 | } 40 | 41 | public int branchingFactor() { 42 | return _branchingFactor; 43 | } 44 | 45 | public int expandLen() { 46 | return 8; 47 | } 48 | 49 | public RefType refType() { 50 | return _refType; 51 | } 52 | 53 | public boolean editable() { 54 | return _edit != null && _edit.get(); 55 | } 56 | 57 | public Settings editable(boolean value) { 58 | assert !editable(); 59 | assert value == true; 60 | return new Settings(_branchingFactor, _refType, new AtomicBoolean(value)); 61 | } 62 | 63 | public void persistent() { 64 | assert _edit != null; 65 | _edit.set(false); 66 | } 67 | 68 | public Object makeReference(T value) { 69 | switch (_refType) { 70 | case STRONG: 71 | return value; 72 | case SOFT: 73 | return new SoftReference(value); 74 | case WEAK: 75 | return new WeakReference(value); 76 | default: 77 | throw new RuntimeException("Unexpected _refType: " + _refType); 78 | } 79 | } 80 | 81 | public Object readReference(Object ref) { 82 | return ref instanceof Reference ? ((Reference) ref).get() : ref; 83 | } 84 | } -------------------------------------------------------------------------------- /bench-clojure/me/tonsky/persistent_sorted_set/bench/core.cljc: -------------------------------------------------------------------------------- 1 | (ns me.tonsky.persistent-sorted-set.bench.core 2 | #?(:cljs 3 | (:require-macros me.tonsky.persistent-sorted-set.bench.core))) 4 | 5 | ; Measure time 6 | 7 | (def ^:dynamic *warmup-ms* 2000) 8 | (def ^:dynamic *bench-ms* 1000) 9 | (def ^:dynamic *samples* 5) 10 | (def ^:dynamic *batch* 10) 11 | 12 | #?(:cljs (defn ^number now [] (system-time)) 13 | :clj (defn now ^double [] (/ (System/nanoTime) 1000000.0))) 14 | 15 | #?(:clj 16 | (defmacro dotime 17 | "Runs form duration, returns average time (ms) per iteration" 18 | [duration & body] 19 | `(let [start-t# (now) 20 | end-t# (+ ~duration start-t#)] 21 | (loop [iterations# *batch*] 22 | (dotimes [_# *batch*] ~@body) 23 | (let [now# (now)] 24 | (if (< now# end-t#) 25 | (recur (+ *batch* iterations#)) 26 | (double (/ (- now# start-t#) iterations#)))))))) 27 | 28 | (defn- if-cljs [env then else] 29 | (if (:ns env) then else)) 30 | 31 | (defn median [xs] 32 | (nth (sort xs) (quot (count xs) 2))) 33 | 34 | (defn to-fixed [n places] 35 | #?(:cljs (.toFixed n places) 36 | :clj (String/format java.util.Locale/ROOT (str "%." places "f") (to-array [(double n)])))) 37 | 38 | (defn round [n] 39 | (cond 40 | (> n 1) (to-fixed n 1) 41 | (> n 0.01) (to-fixed n 3) 42 | :else (to-fixed n 7))) 43 | 44 | (defn left-pad [s l] 45 | (if (<= (count s) l) 46 | (str (apply str (repeat (- l (count s)) " ")) s) 47 | s)) 48 | 49 | (defn right-pad [s l] 50 | (if (<= (count s) l) 51 | (str s (apply str (repeat (- l (count s)) " "))) 52 | s)) 53 | 54 | #?(:clj 55 | (defmacro bench 56 | "Runs for *wramup-ms* + *bench-ms*, returns median time (ms) per iteration" 57 | [& body] 58 | (if-cljs &env 59 | `(let [_# (dotime *warmup-ms* ~@body) 60 | times# (mapv 61 | (fn [_#] 62 | (dotime *bench-ms* ~@body)) 63 | (range *samples*))] 64 | {:mean-ms (median times#)}) 65 | `(let [results# (criterium.core/quick-benchmark (do ~@body) {}) 66 | [mean# & _#] (:mean results#)] 67 | {:mean-ms (* mean# 1000.0)})))) -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/Chunk.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.util.*; 4 | import clojure.lang.*; 5 | 6 | @SuppressWarnings("unchecked") 7 | class Chunk implements IChunk { 8 | final PersistentSortedSet _set; 9 | final Object[] _keys; 10 | final int _idx, _end; 11 | final boolean _asc; 12 | final int _version; 13 | 14 | Chunk(Seq seq) { 15 | _set = seq._set; 16 | _asc = seq._asc; 17 | _idx = seq._idx; 18 | _keys = seq._node._keys; 19 | _version = seq._version; 20 | if (_asc) { 21 | int end = seq._node._len - 1; 22 | if (seq._keyTo != null) 23 | while (end > _idx && seq._cmp.compare(_keys[end], seq._keyTo) > 0) 24 | --end; 25 | _end = end; 26 | } else { 27 | int end = 0; 28 | if (seq._keyTo != null) 29 | while (end < _idx && seq._cmp.compare(_keys[end], seq._keyTo) < 0) 30 | ++end; 31 | _end = end; 32 | } 33 | } 34 | 35 | Chunk(PersistentSortedSet set, Object[] keys, int idx, int end, boolean asc, int version) { 36 | _set = set; 37 | _keys = keys; 38 | _idx = idx; 39 | _end = end; 40 | _asc = asc; 41 | _version = version; 42 | } 43 | 44 | void checkVersion() { 45 | if (_version != _set._version) 46 | throw new RuntimeException("Tovarisch, you are iterating and mutating a transient set at the same time!"); 47 | } 48 | 49 | public IChunk dropFirst() { 50 | checkVersion(); 51 | if (_idx == _end) 52 | throw new IllegalStateException("dropFirst of empty chunk"); 53 | return new Chunk(_set, _keys, _asc ? _idx+1 : _idx-1, _end, _asc, _version); 54 | } 55 | 56 | public Object reduce(IFn f, Object start) { 57 | checkVersion(); 58 | Object ret = f.invoke(start, _keys[_idx]); 59 | if (ret instanceof Reduced) 60 | return ((Reduced) ret).deref(); 61 | if (_asc) 62 | for (int x = _idx + 1; x <= _end; ++x) { 63 | ret = f.invoke(ret, _keys[x]); 64 | if (ret instanceof Reduced) 65 | return ((Reduced) ret).deref(); 66 | } 67 | else // !_asc 68 | for (int x = _idx - 1; x >= _end; --x) { 69 | ret = f.invoke(ret, _keys[x]); 70 | if (ret instanceof Reduced) 71 | return ((Reduced) ret).deref(); 72 | } 73 | return ret; 74 | } 75 | 76 | public Object nth(int i) { 77 | checkVersion(); 78 | assert (i >= 0 && i < count()); 79 | return _asc ? _keys[_idx + i] : _keys[_idx - i]; 80 | } 81 | 82 | public Object nth(int i, Object notFound) { 83 | checkVersion(); 84 | if (i >= 0 && i < count()) 85 | return nth(i); 86 | return notFound; 87 | } 88 | 89 | public int count() { 90 | checkVersion(); 91 | if (_asc) return _end - _idx + 1; 92 | else return _idx - _end + 1; 93 | } 94 | } -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/APersistentSortedSet.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.util.*; 4 | import clojure.lang.*; 5 | 6 | @SuppressWarnings("unchecked") 7 | public abstract class APersistentSortedSet extends AFn implements IObj, Counted, IPersistentSet, ILookup, Iterable, Set, java.io.Serializable, IHashEq { 8 | int _hash; 9 | int _hasheq; 10 | final IPersistentMap _meta; 11 | final Comparator _cmp; 12 | 13 | public APersistentSortedSet(IPersistentMap meta, Comparator cmp) { 14 | _meta = meta; 15 | _cmp = cmp; 16 | } 17 | 18 | // IMeta 19 | public IPersistentMap meta() { return _meta; } 20 | 21 | // IPersistentCollection 22 | public boolean equiv(Object obj) { 23 | if (!(obj instanceof Set)) return false; 24 | Set s = (Set) obj; 25 | if (s.size() != count()) return false; 26 | return containsAll(s); 27 | } 28 | 29 | // IPersistentSet 30 | public Object get(Object key) { return contains(key) ? key : null; } 31 | 32 | // ILookup 33 | public Object valAt(Object key) { return contains(key) ? key : null; } 34 | public Object valAt(Object key, Object notFound) { return contains(key) ? key : notFound; } 35 | 36 | // IFn 37 | public Object invoke(Object key) { return contains(key) ? key : null; } 38 | public Object invoke(Object key, Object notFound) { return contains(key) ? key : notFound; } 39 | 40 | // IHashEq 41 | public int hasheq() { 42 | if (_hasheq == 0) 43 | _hasheq = Murmur3.hashUnordered(this); 44 | return _hasheq; 45 | } 46 | 47 | // Collection 48 | public boolean containsAll​(Collection c) { 49 | for (Object o: c) 50 | if (!contains(o)) 51 | return false; 52 | return true; 53 | } 54 | public int size() { return count(); } 55 | public boolean isEmpty() { return count() == 0; } 56 | public Object[] toArray() { return RT.seqToArray(seq()); } 57 | public Object[] toArray(Object[] arr) { return RT.seqToPassedArray(seq(), arr); } 58 | public boolean add(Object o) { throw new UnsupportedOperationException(); } 59 | public boolean remove(Object o) { throw new UnsupportedOperationException(); } 60 | public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } 61 | public void clear() { throw new UnsupportedOperationException(); } 62 | public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } 63 | public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } 64 | 65 | // Object 66 | public int hashCode() { 67 | int hash = _hash; 68 | if (hash == 0) { 69 | for (Object o: this) 70 | hash += Util.hash(o); 71 | _hash = hash; 72 | } 73 | return hash; 74 | } 75 | 76 | public boolean equals(Object obj) { 77 | return equiv(obj); 78 | } 79 | 80 | public String toString() { 81 | return RT.printString(this); 82 | } 83 | } -------------------------------------------------------------------------------- /bench-clojure/me/tonsky/persistent_sorted_set/bench.cljc: -------------------------------------------------------------------------------- 1 | (ns me.tonsky.persistent-sorted-set.bench 2 | (:require 3 | #?(:clj [clj-async-profiler.core :as profiler]) 4 | #?(:clj [criterium.core :as criterium]) 5 | [me.tonsky.persistent-sorted-set :as set] 6 | [me.tonsky.persistent-sorted-set.bench.core :as bench.core] 7 | #?(:clj [me.tonsky.persistent-sorted-set.test.storage :as storage]))) 8 | 9 | (def ints-10K 10 | (vec (shuffle (range 10000)))) 11 | 12 | (def set-10K 13 | (into (set/sorted-set) ints-10K)) 14 | 15 | (def ints-50K 16 | (vec (shuffle (range 50000)))) 17 | 18 | (def set-50K 19 | (into (set/sorted-set) ints-50K)) 20 | 21 | (def ints-300K 22 | (vec (shuffle (range 300000)))) 23 | 24 | (def set-300K 25 | (into (set/sorted-set) ints-300K)) 26 | 27 | #?(:clj 28 | (def storage-300K 29 | (storage/storage))) 30 | 31 | #?(:clj 32 | (def address-300K 33 | (set/store (into (set/sorted-set) ints-300K) storage-300K))) 34 | 35 | (defn conj-10K [] 36 | (reduce conj (set/sorted-set) ints-10K)) 37 | 38 | #?(:clj 39 | (defn conj-transient-10K [] 40 | (persistent! (reduce conj! (transient (set/sorted-set)) ints-10K)))) 41 | 42 | (defn disj-10K [] 43 | (reduce disj set-10K ints-10K)) 44 | 45 | #?(:clj 46 | (defn disj-transient-10K [] 47 | (persistent! (reduce disj! (transient set-10K) ints-10K)))) 48 | 49 | (defn contains-10K [] 50 | (doseq [x ints-10K] 51 | (contains? set-10K x))) 52 | 53 | (defn doseq-300K [] 54 | (let [*res (volatile! 0)] 55 | (doseq [x set-300K] 56 | (vswap! *res + x)) 57 | @*res)) 58 | 59 | (defn next-300K [] 60 | (loop [xs set-300K 61 | res 0] 62 | (if-some [x (first xs)] 63 | (recur (next xs) (+ res x)) 64 | res))) 65 | 66 | (defn reduce-300K [] 67 | (reduce + 0 set-300K)) 68 | 69 | #?(:clj 70 | (defn into-50K [] 71 | (into (set/sorted-set) ints-50K))) 72 | 73 | #?(:clj 74 | (defn store-50K [] 75 | (set/store 76 | (into (set/sorted-set) ints-50K) 77 | (storage/storage)))) 78 | 79 | #?(:clj 80 | (defn reduce-300K-lazy [] 81 | (reset! (:*memory storage-300K) {}) 82 | (reduce + 0 (set/restore address-300K storage-300K)))) 83 | 84 | (def benches 85 | {"conj-10K" conj-10K 86 | "disj-10K" disj-10K 87 | "contains-10K" contains-10K 88 | "doseq-300K" doseq-300K 89 | "next-300K" next-300K 90 | "reduce-300K" reduce-300K 91 | #?@(:clj 92 | ["conj-transient-10K" conj-transient-10K 93 | "disj-transient-10K" disj-transient-10K 94 | "into-50K" into-50K 95 | "store-50K" store-50K 96 | "reduce-300K-lazy" reduce-300K-lazy])}) 97 | 98 | (defn ^:export -main [& args] 99 | (let [names (or (not-empty args) (sort (keys benches))) 100 | _ (apply println #?(:clj "CLJ:" :cljs "CLJS:") names) 101 | longest (last (sort-by count names))] 102 | (doseq [name names 103 | :let [fn (benches name)]] 104 | (if (nil? fn) 105 | (println "Unknown benchmark:" name) 106 | (let [{:keys [mean-ms]} (bench.core/bench (fn))] 107 | (println 108 | (bench.core/right-pad name (count longest)) 109 | " " 110 | (bench.core/left-pad (bench.core/round mean-ms) 6) "ms/op")))))) 111 | -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/ANode.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.util.*; 4 | import java.util.function.*; 5 | import clojure.lang.*; 6 | 7 | @SuppressWarnings("unchecked") 8 | public abstract class ANode { 9 | // >= 0 10 | public int _len; 11 | 12 | // NotNull 13 | // Only valid [0 ... _len-1] 14 | public final Key[] _keys; 15 | 16 | public final Settings _settings; 17 | 18 | public ANode(int len, Key[] keys, Settings settings) { 19 | assert keys.length >= len; 20 | 21 | _len = len; 22 | _keys = keys; 23 | _settings = settings; 24 | } 25 | 26 | public int len() { 27 | return _len; 28 | } 29 | 30 | public Key minKey() { 31 | return _keys[0]; 32 | } 33 | 34 | public Key maxKey() { 35 | return _keys[_len - 1]; 36 | } 37 | 38 | public List keys() { 39 | if (_keys.length == _len) { 40 | return Arrays.asList(_keys); 41 | } else { 42 | return Arrays.asList(Arrays.copyOfRange(_keys, 0, _len)); 43 | } 44 | } 45 | 46 | public boolean editable() { 47 | return _settings.editable(); 48 | } 49 | 50 | public int search(Key key, Comparator cmp) { 51 | return Arrays.binarySearch(_keys, 0, _len, key, cmp); 52 | 53 | // int low = 0, high = _len; 54 | // while (high - low > 16) { 55 | // int mid = (high + low) >>> 1; 56 | // int d = cmp.compare(_keys[mid], key); 57 | // if (d == 0) return mid; 58 | // else if (d > 0) high = mid; 59 | // else low = mid; 60 | // } 61 | 62 | // // linear search 63 | // for (int i = low; i < high; ++i) { 64 | // int d = cmp.compare(_keys[i], key); 65 | // if (d == 0) return i; 66 | // else if (d > 0) return -i - 1; // i 67 | // } 68 | 69 | // return -high - 1; // high 70 | } 71 | 72 | public int searchFirst(Key key, Comparator cmp) { 73 | int low = 0, high = _len; 74 | while (low < high) { 75 | int mid = (high + low) >>> 1; 76 | int d = cmp.compare(_keys[mid], key); 77 | if (d < 0) 78 | low = mid + 1; 79 | else 80 | high = mid; 81 | } 82 | return low; 83 | } 84 | 85 | public int searchLast(Key key, Comparator cmp) { 86 | int low = 0, high = _len; 87 | while (low < high) { 88 | int mid = (high + low) >>> 1; 89 | int d = cmp.compare(_keys[mid], key); 90 | if (d <= 0) 91 | low = mid + 1; 92 | else 93 | high = mid; 94 | } 95 | return low - 1; 96 | } 97 | 98 | public static ANode restore(int level, List keys, List
addresses, Settings settings) { 99 | if (level == 0 || addresses == null) { 100 | return new Leaf(keys, settings); 101 | } else { 102 | return new Branch(level, keys, addresses, settings); 103 | } 104 | } 105 | 106 | @Override 107 | public String toString() { 108 | StringBuilder sb = new StringBuilder(); 109 | toString(sb, null, ""); 110 | return sb.toString(); 111 | } 112 | 113 | public abstract int count(IStorage storage); 114 | // 0 for Leafs, 1+ for Branches 115 | public abstract int level(); 116 | public abstract boolean contains(IStorage storage, Key key, Comparator cmp); 117 | public abstract ANode[] add(IStorage storage, Key key, Comparator cmp, Settings settings); 118 | public abstract ANode[] remove(IStorage storage, Key key, ANode left, ANode right, Comparator cmp, Settings settings); 119 | public abstract String str(IStorage storage, int lvl); 120 | public abstract void walkAddresses(IStorage storage, IFn onAddress); 121 | public abstract Address store(IStorage storage); 122 | public abstract void toString(StringBuilder sb, Address address, String indent); 123 | 124 | protected static int newLen(int len, Settings settings) { 125 | if (settings.editable()) 126 | return Math.min(settings.branchingFactor(), len + settings.expandLen()); 127 | else 128 | return len; 129 | } 130 | 131 | protected static int safeLen(ANode node) { 132 | return node == null ? -1 : node._len; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src-clojure/me/tonsky/persistent_sorted_set/arrays.cljc: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc me.tonsky.persistent-sorted-set.arrays 2 | (:require 3 | [clojure.string :as str]) 4 | (:refer-clojure :exclude [make-array into-array array amap aget aset alength array? aclone]) 5 | #?(:cljs (:require-macros me.tonsky.persistent-sorted-set.arrays)) 6 | #?(:clj (:import [java.util Arrays]))) 7 | 8 | 9 | (defn- if-cljs [env then else] 10 | (if (:ns env) then else)) 11 | 12 | 13 | #?(:cljs 14 | (defn ^array make-array [size] (js/Array. size)) 15 | :clj 16 | (defn make-array ^{:tag "[[Ljava.lang.Object;"} [size] 17 | (clojure.core/make-array java.lang.Object size))) 18 | 19 | 20 | #?(:cljs 21 | (defn ^array into-array [aseq] 22 | (reduce (fn [a x] (.push a x) a) (js/Array.) aseq)) 23 | :clj 24 | (defn into-array ^{:tag "[[Ljava.lang.Object;"} [aseq] 25 | (clojure.core/into-array java.lang.Object aseq))) 26 | 27 | 28 | #?(:clj 29 | (defmacro aget [arr i] 30 | (if-cljs &env 31 | (list 'js* "(~{}[~{}])" arr i) 32 | `(clojure.lang.RT/aget ~(vary-meta arr assoc :tag "[[Ljava.lang.Object;") (int ~i))))) 33 | 34 | 35 | #?(:clj 36 | (defmacro alength [arr] 37 | (if-cljs &env 38 | (-> (list 'js* "~{}.length" arr) 39 | (vary-meta assoc :tag 'number)) 40 | `(clojure.lang.RT/alength ~(vary-meta arr assoc :tag "[[Ljava.lang.Object;"))))) 41 | 42 | 43 | #?(:clj 44 | (defmacro aset [arr i v] 45 | (if-cljs &env 46 | (list 'js* "(~{}[~{}] = ~{})" arr i v) 47 | `(clojure.lang.RT/aset ~(vary-meta arr assoc :tag "[[Ljava.lang.Object;") (int ~i) ~v)))) 48 | 49 | 50 | #?(:clj 51 | (defmacro array [& args] 52 | (if-cljs &env 53 | (-> 54 | (list* 'js* (str "[" (str/join "," (repeat (count args) "~{}")) "]") args) 55 | (vary-meta assoc :tag 'array)) 56 | (let [len (count args)] 57 | (if (zero? len) 58 | 'clojure.lang.RT/EMPTY_ARRAY 59 | `(let [arr# (clojure.core/make-array java.lang.Object ~len)] 60 | (doto ^{:tag "[[Ljava.lang.Object;"} arr# 61 | ~@(map #(list 'aset % (nth args %)) (range len))))))))) 62 | 63 | 64 | #?(:clj 65 | (defmacro acopy [from from-start from-end to to-start] 66 | (if-cljs &env 67 | `(let [l# (- ~from-end ~from-start)] 68 | (dotimes [i# l#] 69 | (aset ~to (+ i# ~to-start) (aget ~from (+ i# ~from-start))))) 70 | `(let [l# (- ~from-end ~from-start)] 71 | (when (pos? l#) 72 | (System/arraycopy ~from ~from-start ~to ~to-start l#)))))) 73 | 74 | 75 | (defn aclone [from] 76 | #?(:clj (Arrays/copyOf ^{:tag "[[Ljava.lang.Object;"} from (alength from)) 77 | :cljs (.slice from 0))) 78 | 79 | 80 | (defn aconcat [a b] 81 | #?(:cljs (.concat a b) 82 | :clj (let [al (alength a) 83 | bl (alength b) 84 | res (Arrays/copyOf ^{:tag "[[Ljava.lang.Object;"} a (+ al bl))] 85 | (System/arraycopy ^{:tag "[[Ljava.lang.Object;"} b 0 res al bl) 86 | res))) 87 | 88 | 89 | #?(:cljs 90 | (defn amap [f arr] 91 | (.map arr f)) 92 | :clj 93 | (defn amap 94 | ([f arr] 95 | (amap f Object arr)) 96 | ([f type arr] ;; TODO check if faster in Java 97 | (let [res (clojure.core/make-array type (alength arr))] 98 | (dotimes [i (alength arr)] 99 | (aset res i (f (aget arr i)))) 100 | res)))) 101 | 102 | 103 | (defn asort [arr cmp] 104 | #?(:cljs (.sort arr cmp) 105 | :clj (doto arr (Arrays/parallelSort cmp)))) 106 | 107 | 108 | #?(:cljs 109 | (defn ^boolean array? [x] 110 | (if (identical? *target* "nodejs") 111 | (.isArray js/Array x) 112 | (instance? js/Array x))) 113 | :clj 114 | (defn array? [^Object x] 115 | (some-> x .getClass .isArray))) 116 | 117 | 118 | #?(:clj 119 | (defmacro alast [arr] 120 | `(let [arr# ~arr] 121 | (aget arr# (dec (alength arr#)))))) 122 | 123 | 124 | #?(:clj 125 | (defmacro half [x] 126 | `(unsigned-bit-shift-right ~x 1))) 127 | 128 | 129 | #?(:clj 130 | (def array-type 131 | (memoize 132 | (fn [type] 133 | (.getClass ^Object (java.lang.reflect.Array/newInstance ^Class type 0)))))) 134 | -------------------------------------------------------------------------------- /test-clojure/me/tonsky/persistent_sorted_set/test/stress.cljc: -------------------------------------------------------------------------------- 1 | (ns me.tonsky.persistent-sorted-set.test.stress 2 | (:require 3 | [me.tonsky.persistent-sorted-set :as set] 4 | #?(:clj [me.tonsky.persistent-sorted-set.test.storage :as storage]) 5 | [clojure.test :as t :refer [is are deftest testing]])) 6 | 7 | (def iters 100) 8 | 9 | (defn into-via-doseq [to from] 10 | (let [res (transient [])] 11 | (doseq [x from] ;; checking chunked iter 12 | (conj! res x)) 13 | (persistent! res))) 14 | 15 | (deftest stresstest-btset 16 | (println " testing stresstest-btset...") 17 | (dotimes [i iters] 18 | (let [size 10000 19 | xs (vec (repeatedly (+ 1 (rand-int size)) #(rand-int size))) 20 | xs-sorted (vec (distinct (sort xs))) 21 | rm (vec (repeatedly (rand-int (* size 5)) #(rand-nth xs))) 22 | full-rm (shuffle (concat xs rm)) 23 | xs-rm (reduce disj (into (sorted-set) xs) rm)] 24 | (doseq [[method set0] [["conj" (into (set/sorted-set) xs)] 25 | ["bulk" (apply set/sorted-set xs)] 26 | #?(:clj ["lazy" (storage/roundtrip (into (set/sorted-set) xs))])] 27 | :let [set1 (reduce disj set0 rm) 28 | set2 (persistent! (reduce disj (transient set0) rm)) 29 | set3 (reduce disj set0 full-rm) 30 | set4 (persistent! (reduce disj (transient set0) full-rm))]] 31 | (testing 32 | (str "Iter:" (inc i) "/" iters 33 | "set:" method 34 | "adds:" (str (count xs) " (" (count xs-sorted) " distinct),") 35 | "removals:" (str (count rm) " (down to " (count xs-rm) ")")) 36 | (testing "conj, seq" 37 | (is (= (vec set0) xs-sorted))) 38 | (testing "eq" 39 | (is (= set0 (set xs-sorted)) xs-sorted)) 40 | (testing "count" 41 | (is (= (count set0) (count xs-sorted)))) 42 | (testing "doseq" 43 | (is (= (into-via-doseq [] set0) xs-sorted))) 44 | (testing "disj" 45 | (is (= (vec set1) (vec xs-rm))) 46 | (is (= (count set1) (count xs-rm))) 47 | (is (= set1 xs-rm))) 48 | (testing "disj transient" 49 | (is (= (vec set2) (vec xs-rm))) 50 | (is (= (count set2) (count xs-rm))) 51 | (is (= set2 xs-rm))) 52 | (testing "full disj" 53 | (is (= set3 #{})) 54 | (is (= set4 #{})))))))) 55 | 56 | (deftest stresstest-slice 57 | (println " testing stresstest-slice...") 58 | (dotimes [i iters] 59 | (let [xs (repeatedly (+ 1 (rand-int 20000)) #(rand-int 20000)) 60 | xs-sorted (distinct (sort xs)) 61 | [from to] (sort [(- 10000 (rand-int 20000)) (+ 10000 (rand-int 20000))]) 62 | expected (filter #(<= from % to) xs-sorted)] 63 | (doseq [[method set] [["conj" (into (set/sorted-set) xs)] 64 | #?(:clj ["lazy" (storage/roundtrip (into (set/sorted-set) xs))])] 65 | :let [set-range (set/slice set from to)]] 66 | (testing 67 | (str 68 | "Iter: " (inc i) "/" iters 69 | ", set:" method 70 | ", from:" (count xs-sorted) " elements" 71 | ", down to:" (count expected)) 72 | (let [set (into (set/sorted-set) (shuffle xs-sorted))] 73 | (is (= (set/rslice set 30000 -10) 74 | (-> (set/rslice set 30000 -10) rseq reverse)))) 75 | (is (= (vec set-range) (vec (seq set-range)))) ;; checking IReduce on BTSetIter 76 | (is (= (vec set-range) expected)) 77 | (is (= (into-via-doseq [] set-range) expected)) 78 | (is (= (vec (rseq set-range)) (reverse expected))) 79 | (is (= (vec (rseq (rseq set-range))) expected))))))) 80 | 81 | (deftest stresstest-rslice 82 | (println " testing stresstest-rslice...") 83 | (dotimes [i 1000] 84 | (let [len 3000 85 | xs (vec (shuffle (range 0 (inc 3000)))) 86 | s (into (set/sorted-set) xs)] 87 | (testing (str "Iter: " i "/1000") 88 | (is (= 89 | (set/rslice s (+ 3000 100) -100) 90 | (-> (set/rslice s (+ 3000 100) -100) rseq reverse))))))) 91 | 92 | (deftest stresstest-seek 93 | (println " testing stresstest-seek...") 94 | (dotimes [i iters] 95 | (let [xs (repeatedly (inc (rand-int 20000)) #(rand-int 20000)) 96 | xs-sorted (distinct (sort xs)) 97 | seek-to (rand-int 20000) 98 | set (into (set/sorted-set) xs-sorted)] 99 | (testing (str "Iter: " i "/" iters ", seek to " seek-to) 100 | (is (= (seq (drop-while #(< % seek-to) xs-sorted)) 101 | (set/seek (seq set) seek-to))) 102 | 103 | (is (= (seq (drop-while #(< % 19999) xs-sorted)) 104 | (set/seek (seq set) 19999))) 105 | 106 | (is (= (seq (reverse (take-while #(<= % seek-to) xs-sorted))) 107 | (set/seek (rseq set) seek-to))) 108 | 109 | (is (= (seq (reverse (take-while #(<= % 1) xs-sorted))) 110 | (set/seek (rseq set) 1))))))) 111 | 112 | (deftest test-overflow 113 | (println " testing test-overflow...") 114 | (let [len 4000000 115 | part (quot len 100) 116 | xss (partition-all part (shuffle (range 0 len))) 117 | s (reduce into (set/sorted-set) xss)] 118 | (is (= 10 (count (take 10 s)))))) 119 | -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/Seq.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.util.*; 4 | import clojure.lang.*; 5 | 6 | @SuppressWarnings("unchecked") 7 | public class Seq extends ASeq implements IReduce, Reversible, IChunkedSeq, ISeek{ 8 | final PersistentSortedSet _set; 9 | Seq _parent; 10 | ANode _node; 11 | int _idx; 12 | final Object _keyTo; 13 | final Comparator _cmp; 14 | final boolean _asc; 15 | final int _version; 16 | 17 | Seq(IPersistentMap meta, PersistentSortedSet set, Seq parent, ANode node, int idx, Object keyTo, Comparator cmp, boolean asc, int version) { 18 | super(meta); 19 | _set = set; 20 | _parent = parent; 21 | _node = node; 22 | _idx = idx; 23 | _keyTo = keyTo; 24 | _cmp = cmp; 25 | _asc = asc; 26 | _version = version; 27 | } 28 | 29 | void checkVersion() { 30 | if (_version != _set._version) 31 | throw new RuntimeException("Tovarisch, you are iterating and mutating a transient set at the same time!"); 32 | } 33 | 34 | ANode child() { 35 | assert _node instanceof Branch : _node; 36 | return ((Branch) _node).child(_set._storage, _idx); 37 | } 38 | 39 | boolean over() { 40 | if (_keyTo == null) return false; 41 | int d = _cmp.compare(first(), _keyTo); 42 | return _asc ? d > 0 : d < 0; 43 | } 44 | 45 | boolean advance() { 46 | checkVersion(); 47 | if (_asc) { 48 | if (_idx < _node._len - 1) { 49 | _idx++; 50 | return !over(); 51 | } else if (_parent != null) { 52 | _parent = _parent.next(); 53 | if (_parent != null) { 54 | _node = _parent.child(); 55 | _idx = 0; 56 | return !over(); 57 | } 58 | } 59 | } else { // !_asc 60 | if (_idx > 0) { 61 | _idx--; 62 | return !over(); 63 | } else if (_parent != null) { 64 | _parent = _parent.next(); 65 | if (_parent != null) { 66 | _node = _parent.child(); 67 | _idx = _node._len - 1; 68 | return !over(); 69 | } 70 | } 71 | } 72 | return false; 73 | } 74 | 75 | protected Seq clone() { 76 | return new Seq(meta(), _set, _parent, _node, _idx, _keyTo, _cmp, _asc, _version); 77 | } 78 | 79 | // ASeq 80 | public Object first() { 81 | checkVersion(); 82 | // assert _node.leaf(); 83 | return _node._keys[_idx]; 84 | } 85 | 86 | public Seq next() { 87 | Seq next = clone(); 88 | return next.advance() ? next : null; 89 | } 90 | 91 | public Obj withMeta(IPersistentMap meta) { 92 | if (meta() == meta) return this; 93 | return new Seq(meta, _set, _parent, _node, _idx, _keyTo, _cmp, _asc, _version); 94 | } 95 | 96 | // IReduce 97 | public Object reduce(IFn f) { 98 | checkVersion(); 99 | Seq clone = clone(); 100 | Object ret = clone.first(); 101 | while (clone.advance()) { 102 | ret = f.invoke(ret, clone.first()); 103 | if (ret instanceof Reduced) 104 | return ((Reduced) ret).deref(); 105 | } 106 | return ret; 107 | } 108 | 109 | public Object reduce(IFn f, Object start) { 110 | checkVersion(); 111 | Seq clone = clone(); 112 | Object ret = start; 113 | do { 114 | ret = f.invoke(ret, clone.first()); 115 | if (ret instanceof Reduced) 116 | return ((Reduced) ret).deref(); 117 | } while (clone.advance()); 118 | return ret; 119 | } 120 | 121 | // Iterable 122 | public Iterator iterator() { checkVersion(); return new JavaIter(clone()); } 123 | 124 | // IChunkedSeq 125 | public Chunk chunkedFirst() { checkVersion(); return new Chunk(this); } 126 | 127 | public Seq chunkedNext() { 128 | checkVersion(); 129 | if (_parent == null) return null; 130 | Seq nextParent = _parent.next(); 131 | if (nextParent == null) return null; 132 | ANode node = nextParent.child(); 133 | Seq seq = new Seq(meta(), _set, nextParent, node, _asc ? 0 : node._len - 1, _keyTo, _cmp, _asc, _version); 134 | return seq.over() ? null : seq; 135 | } 136 | 137 | public ISeq chunkedMore() { 138 | Seq seq = chunkedNext(); 139 | if (seq == null) return PersistentList.EMPTY; 140 | return seq; 141 | } 142 | 143 | // Reversible 144 | boolean atBeginning() { 145 | return _idx == 0 && (_parent == null || _parent.atBeginning()); 146 | } 147 | 148 | boolean atEnd() { 149 | return _idx == _node._len-1 && (_parent == null || _parent.atEnd()); 150 | } 151 | 152 | public Seq rseq() { 153 | checkVersion(); 154 | if (_asc) 155 | return _set.rslice(_keyTo, atBeginning() ? null : first(), _cmp); 156 | else 157 | return _set.slice(_keyTo, atEnd() ? null : first(), _cmp); 158 | } 159 | 160 | public Seq seek(Object to) { return seek(to, _cmp); } 161 | public Seq seek(Object to, Comparator cmp) { 162 | if (to == null) throw new RuntimeException("seek can't be called with a nil key!"); 163 | 164 | Seq seq = this._parent; 165 | ANode node = this._node; 166 | 167 | if (_asc) { 168 | 169 | while (node != null && cmp.compare(node.maxKey(), to) < 0){ 170 | if (seq == null) { 171 | return null; 172 | } else { 173 | node = seq._node; 174 | seq = seq._parent; 175 | } 176 | } 177 | 178 | while (true) { 179 | int idx = node.searchFirst(to, cmp); 180 | if (idx < 0) 181 | idx = -idx - 1; 182 | if (idx == node._len) 183 | return null; 184 | if (node instanceof Branch) { 185 | seq = new Seq(null, this._set, seq, node, idx, null, null, true, _version); 186 | node = seq.child(); 187 | } else { // Leaf 188 | seq = new Seq(null, this._set, seq, node, idx, this._keyTo, cmp, true, _version); 189 | return seq.over() ? null : seq; 190 | } 191 | } 192 | 193 | } else { 194 | 195 | // NOTE: We can't shortcircuit here as we don't know the minKey. Might go up one level too high. 196 | while (cmp.compare(to, node.minKey()) < 0 && seq != null){ 197 | node = seq._node; 198 | seq = seq._parent; 199 | } 200 | 201 | while (true) { 202 | if (node instanceof Branch) { 203 | int idx = node.searchLast(to, cmp) + 1; 204 | if (idx == node._len) --idx; // last or beyond, clamp to last 205 | seq = new Seq(null, this._set, seq, node, idx, null, null, false, _version); 206 | node = seq.child(); 207 | } else { // Leaf 208 | int idx = node.searchLast(to, cmp); 209 | if (idx == -1) { // not in this, so definitely in prev 210 | seq = new Seq(null, this._set, seq, node, 0, this._keyTo, cmp, false, _version); 211 | return seq.advance() ? seq : null; 212 | } else { // exact match 213 | seq = new Seq(null, this._set, seq, node, idx, this._keyTo, cmp, false, _version); 214 | return seq.over() ? null : seq; 215 | } 216 | } 217 | } 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A B-tree based persistent sorted set for Clojure/Script. 2 | 3 | PersistentSortedSet supports: 4 | 5 | - transients, 6 | - custom comparators, 7 | - fast iteration, 8 | - efficient slices (iterator over a part of the set) 9 | - efficient `rseq` on slices. 10 | 11 | Almost a drop-in replacement for `clojure.core/sorted-set`, the only difference being this one can’t store `nil`. 12 | 13 | Implementations are provided for Clojure and ClojureScript. 14 | 15 | ## Building 16 | 17 | ``` 18 | export JAVA8_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home" 19 | lein jar 20 | ``` 21 | 22 | ## Support us 23 | 24 | Become a Patron! 25 | 26 | ## Usage 27 | 28 | Dependency: 29 | 30 | ```clj 31 | [persistent-sorted-set "0.3.0"] 32 | ``` 33 | 34 | Code: 35 | 36 | ```clj 37 | (require '[me.tonsky.persistent-sorted-set :as set]) 38 | 39 | (set/sorted-set 3 2 1) 40 | ;=> #{1 2 3} 41 | 42 | (-> (set/sorted-set 1 2 3 4) 43 | (conj 2.5)) 44 | ;=> #{1 2 2.5 3 4} 45 | 46 | (-> (set/sorted-set 1 2 3 4) 47 | (disj 3)) 48 | ;=> #{1 2 4} 49 | 50 | (-> (set/sorted-set 1 2 3 4) 51 | (contains? 3)) 52 | ;=> true 53 | 54 | (-> (apply set/sorted-set (range 10000)) 55 | (set/slice 5000 5010)) 56 | ;=> (5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010) 57 | 58 | (-> (apply set/sorted-set (range 10000)) 59 | (set/rslice 5010 5000)) 60 | ;=> (5010 5009 5008 5007 5006 5005 5004 5003 5002 5001 5000) 61 | 62 | (set/sorted-set-by > 1 2 3) 63 | ;=> #{3 2 1} 64 | ``` 65 | One can also efficiently seek on the iterators. 66 | 67 | ```clj 68 | (-> (seq (into (set/sorted-set) (range 10))) 69 | (set/seek 5)) 70 | ;; => (5 6 7 8 9) 71 | 72 | (-> (into (set/sorted-set) (range 100)) 73 | (set/rslice 75 25) 74 | (set/seek 60) 75 | (set/seek 30)) 76 | ;; => (30 29 28 27 26 25) 77 | ``` 78 | 79 | ## Durability 80 | 81 | Clojure version allows efficient storage of Persistent Sorted Set on disk/DB/anywhere. 82 | 83 | To do that, implement `IStorage` interface: 84 | 85 | ```clojure 86 | (defrecord Storage [*storage] 87 | IStorage 88 | (store [_ node] 89 | (let [address (random-uuid)] 90 | (swap! *storage assoc address 91 | (pr-str 92 | (if (instance? Branch node) 93 | {:level (.level ^Branch node) 94 | :keys (.keys ^Branch node) 95 | :addresses (.addresses ^Branch node)} 96 | (.keys ^Leaf node)))) 97 | address)) 98 | 99 | (restore [_ address] 100 | (let [value (-> (get @*storage address) 101 | (edn/read-string))] 102 | (if (map? value) 103 | (Branch. (int (:level value)) ^java.util.List (:keys value) ^java.util.List (:addresses value)) 104 | (Leaf. ^java.util.List value))))) 105 | ``` 106 | 107 | Storing Persistent Sorted Set works per node. This will save each node once: 108 | 109 | ```clojure 110 | (def set 111 | (into (set/sorted-set) (range 1000000))) 112 | 113 | (def storage 114 | (Storage. (atom {}))) 115 | 116 | (def root 117 | (set/store set storage)) 118 | ``` 119 | 120 | If you try to store once again, no store operations will be issued: 121 | 122 | ```clojure 123 | (assert 124 | (= root 125 | (set/store set storage))) 126 | ``` 127 | 128 | If you modify set and store new one, only nodes that were changed will be stored. For a tree of depth 3, it’s usually just \~3 nodes. The root will be new, though: 129 | 130 | ```clojure 131 | (def set2 132 | (into set [-1 -2 -3])) 133 | 134 | (assert 135 | (not= root 136 | (set/store set2 storage))) 137 | ``` 138 | 139 | Finally, one can construct a new set from its stored snapshot. You’ll need address for that: 140 | 141 | ```clojure 142 | (def set-lazy 143 | (set/restore root storage)) 144 | ``` 145 | 146 | Restore operation is lazy. By default it won’t do anything, but when you start accessing returned set, `IStorage::restore` operations will be issued and part of the set will be reconstructed in memory. Only nodes needed for a particular operation will be loaded. 147 | 148 | E.g. this will load \~3 nodes for a set of depth 3: 149 | 150 | ```clojure 151 | (first set-lazy) 152 | ``` 153 | 154 | This will load \~50 nodes on default settings: 155 | 156 | ```clojure 157 | (take 5000 set-lazy) 158 | ``` 159 | 160 | Internally Persistent Sorted Set does not caches returned nodes, so don’t be surprised if subsequent `first` loads the same nodes again. One must implement cache inside IStorage implementation for efficient retrieval of already loaded nodes. Also see `IStorage::accessed` for access stats, e.g. for LRU. 161 | 162 | Any operation that can be done on in-memory PSS can be done on a lazy one, too. It will fetch required nodes when needed, completely transparently for the user. Lazy PSS can exist arbitrary long without ever being fully realized in memory: 163 | 164 | ```clojure 165 | (def set3 166 | (conj set-lazy [-1 -2 -3])) 167 | 168 | (def set4 169 | (disj set-lazy [4 5 6 7 8])) 170 | 171 | (contains? set-lazy 5000) 172 | ``` 173 | 174 | Last piece of the puzzle: `set/walk-addresses`. Use it to check which nodes are actually in use by current PSS and optionally clean up garbage in your storage that is not referenced by it anymore: 175 | 176 | ```clojure 177 | (let [*alive-addresses (volatile! [])] 178 | (set/walk-addresses set #(vswap! *alive-addresses conj %)) 179 | @*alive-addresses) 180 | ``` 181 | 182 | See [test_storage.clj](test-clojure/me/tonsky/persistent_sorted_set/test_storage.clj) for more examples. 183 | 184 | Durability for ClojureScript is not yet supported. 185 | 186 | ## Performance 187 | 188 | To reproduce: 189 | 190 | 1. Install `[com.datomic/datomic-free "0.9.5703"]` locally. 191 | 2. Run `lein bench`. 192 | 193 | `PersistentTreeSet` is Clojure’s Red-black tree based sorted-set. 194 | `BTSet` is Datomic’s B-tree based sorted set (no transients, no disjoins). 195 | `PersistentSortedSet` is this implementation. 196 | 197 | Numbers I get on my 3.2 GHz i7-8700B: 198 | 199 | ### Conj 100k randomly sorted Integers 200 | 201 | ``` 202 | PersistentTreeSet 143..165ms 203 | BTSet 125..141ms 204 | PersistentSortedSet 105..121ms 205 | PersistentSortedSet (transient) 50..54ms 206 | ``` 207 | 208 | ### Call contains? 100k times with random Integer on a 100k Integers set 209 | 210 | ``` 211 | PersistentTreeSet 51..54ms 212 | BTSet 45..47ms 213 | PersistentSortedSet 46..47ms 214 | ``` 215 | 216 | ### Iterate with java.util.Iterator over a set of 1M Integers 217 | 218 | ``` 219 | PersistentTreeSet 70..77ms 220 | PersistentSortedSet 10..11ms 221 | ``` 222 | 223 | ### Iterate with ISeq.first/ISeq.next over a set of 1M Integers 224 | 225 | ``` 226 | PersistentTreeSet 116..124ms 227 | BTSet 92..105ms 228 | PersistentSortedSet 56..68ms 229 | ``` 230 | 231 | ### Iterate over a part of a set from 1 to 999999 in a set of 1M Integers 232 | 233 | For `PersistentTreeSet` we use ISeq produced by `(take-while #(<= % 999999) (.seqFrom set 1 true))`. 234 | 235 | For `PersistentSortedSet` we use `(.slice set 1 999999)`. 236 | 237 | ``` 238 | PersistentTreeSet 238..256ms 239 | PersistentSortedSet 70..91ms 240 | ``` 241 | 242 | ### Disj 100k elements in randomized order from a set of 100k Integers 243 | 244 | ``` 245 | PersistentTreeSet 151..155ms 246 | PersistentSortedSet 91..98ms 247 | PersistentSortedSet (transient) 47..50ms 248 | ``` 249 | 250 | ## Projects using PersistentSortedSet 251 | 252 | - [Datascript](https://github.com/tonsky/datascript), persistent in-memory database 253 | 254 | ## License 255 | 256 | Copyright © 2019 Nikita Prokopov 257 | 258 | Licensed under MIT (see [LICENSE](LICENSE)). 259 | -------------------------------------------------------------------------------- /src-clojure/me/tonsky/persistent_sorted_set.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:author "Nikita Prokopov" 2 | :doc "A B-tree based persistent sorted set. Supports transients, custom comparators, fast iteration, efficient slices (iterator over a part of the set) and reverse slices. Almost a drop-in replacement for [[clojure.core/sorted-set]], the only difference being this one can’t store nil."} 3 | me.tonsky.persistent-sorted-set 4 | (:refer-clojure :exclude [conj disj sorted-set sorted-set-by]) 5 | (:require 6 | [me.tonsky.persistent-sorted-set.arrays :as arrays]) 7 | (:import 8 | [clojure.lang RT] 9 | [java.lang.ref SoftReference] 10 | [java.util Comparator Arrays] 11 | [java.util.function BiConsumer] 12 | [me.tonsky.persistent_sorted_set ANode ArrayUtil Branch IStorage Leaf PersistentSortedSet RefType Settings Seq])) 13 | 14 | (set! *warn-on-reflection* true) 15 | 16 | (defn conj 17 | "Analogue to [[clojure.core/conj]] but with comparator that overrides the one stored in set." 18 | [^PersistentSortedSet set key ^Comparator cmp] 19 | (.cons set key cmp)) 20 | 21 | (defn disj 22 | "Analogue to [[clojure.core/disj]] with comparator that overrides the one stored in set." 23 | [^PersistentSortedSet set key ^Comparator cmp] 24 | (.disjoin set key cmp)) 25 | 26 | (defn slice 27 | "An iterator for part of the set with provided boundaries. 28 | `(slice set from to)` returns iterator for all Xs where from <= X <= to. 29 | `(slice set from nil)` returns iterator for all Xs where X >= from. 30 | Optionally pass in comparator that will override the one that set uses. Supports efficient [[clojure.core/rseq]]." 31 | ([^PersistentSortedSet set from to] 32 | (.slice set from to)) 33 | ([^PersistentSortedSet set from to ^Comparator cmp] 34 | (.slice set from to cmp))) 35 | 36 | (defn rslice 37 | "A reverse iterator for part of the set with provided boundaries. 38 | `(rslice set from to)` returns backwards iterator for all Xs where from <= X <= to. 39 | `(rslice set from nil)` returns backwards iterator for all Xs where X <= from. 40 | Optionally pass in comparator that will override the one that set uses. Supports efficient [[clojure.core/rseq]]." 41 | ([^PersistentSortedSet set from to] 42 | (.rslice set from to)) 43 | ([^PersistentSortedSet set from to ^Comparator cmp] 44 | (.rslice set from to cmp))) 45 | 46 | (defn seek 47 | "An efficient way to seek to a specific key in a seq (either returned by [[clojure.core.seq]] or a slice.) 48 | `(seek (seq set) to)` returns iterator for all Xs where to <= X. 49 | Optionally pass in comparator that will override the one that set uses." 50 | ([seq to] 51 | (.seek ^Seq seq to)) 52 | ([seq to cmp] 53 | (.seek ^Seq seq to ^Comparator cmp))) 54 | 55 | (defn- array-from-indexed [coll type from to] 56 | (cond 57 | (instance? clojure.lang.Indexed coll) 58 | (ArrayUtil/indexedToArray type coll from to) 59 | 60 | (arrays/array? coll) 61 | (Arrays/copyOfRange coll from to (arrays/array-type type)))) 62 | 63 | (defn- split 64 | ([coll to type avg max] 65 | (persistent! (split (transient []) 0 coll to type avg max))) 66 | ([res from coll to type avg max] 67 | (let [len (- to from)] 68 | (cond 69 | (== 0 len) 70 | res 71 | 72 | (>= len (* 2 avg)) 73 | (recur (conj! res (array-from-indexed coll type from (+ from avg))) (+ from avg) coll to type avg max) 74 | 75 | (<= len max) 76 | (conj! res (array-from-indexed coll type from to)) 77 | 78 | :else 79 | (-> res 80 | (conj! (array-from-indexed coll type from (+ from (quot len 2)))) 81 | (conj! (array-from-indexed coll type (+ from (quot len 2)) to))))))) 82 | 83 | (defn- map->settings ^Settings [m] 84 | (Settings. 85 | (int (or (:branching-factor m) 0)) 86 | (case (:ref-type m) 87 | :strong RefType/STRONG 88 | :soft RefType/SOFT 89 | :weak RefType/WEAK 90 | nil))) 91 | 92 | (defn- settings->map [^Settings s] 93 | {:branching-factor (.branchingFactor s) 94 | :ref-type (condp identical? (.refType s) 95 | RefType/STRONG :strong 96 | RefType/SOFT :soft 97 | RefType/WEAK :weak)}) 98 | 99 | (defn from-sorted-array 100 | "Fast path to create a set if you already have a sorted array of elements on your hands." 101 | ([^Comparator cmp keys] 102 | (from-sorted-array cmp keys (arrays/alength keys) (Settings.))) 103 | ([^Comparator cmp keys len] 104 | (from-sorted-array cmp keys len (Settings.))) 105 | ([^Comparator cmp keys len opts] 106 | (let [settings (map->settings opts) 107 | max-branching-factor (.branchingFactor settings) 108 | avg-branching-factor (-> (.minBranchingFactor settings) (+ max-branching-factor) (quot 2)) 109 | storage (:storage opts) 110 | ->Leaf (fn [keys] 111 | (Leaf. (count keys) keys settings)) 112 | ->Branch (fn [level ^objects children] 113 | (Branch. 114 | level 115 | (count children) 116 | ^objects (arrays/amap #(.maxKey ^ANode %) Object children) 117 | nil 118 | children 119 | settings))] 120 | (loop [level 1 121 | nodes (mapv ->Leaf (split keys len Object avg-branching-factor max-branching-factor))] 122 | (case (count nodes) 123 | 0 (PersistentSortedSet. {} cmp storage settings) 124 | 1 (PersistentSortedSet. {} cmp nil storage (first nodes) len settings 0) 125 | (recur (inc level) (mapv #(->Branch level %) (split nodes (count nodes) Object avg-branching-factor max-branching-factor)))))))) 126 | 127 | (defn from-sequential 128 | "Create a set with custom comparator and a collection of keys. Useful when you don’t want to call [[clojure.core/apply]] on [[sorted-set-by]]." 129 | ([^Comparator cmp keys] 130 | (from-sequential cmp keys (Settings.))) 131 | ([^Comparator cmp keys opts] 132 | (let [arr (to-array keys) 133 | _ (arrays/asort arr cmp) 134 | len (ArrayUtil/distinct cmp arr)] 135 | (from-sorted-array cmp arr len opts)))) 136 | 137 | (defn sorted-set* 138 | "Create a set with custom comparator, metadata and settings" 139 | [opts] 140 | (PersistentSortedSet. 141 | (:meta opts) 142 | ^Comparator (or (:cmp opts) compare) 143 | (:storage opts) 144 | (map->settings opts))) 145 | 146 | (defn sorted-set-by 147 | "Create a set with custom comparator." 148 | ([cmp] (PersistentSortedSet. ^Comparator cmp)) 149 | ([cmp & keys] (from-sequential cmp keys))) 150 | 151 | (defn sorted-set 152 | "Create a set with default comparator." 153 | ([] (PersistentSortedSet/EMPTY)) 154 | ([& keys] (from-sequential compare keys))) 155 | 156 | (defn restore-by 157 | "Constructs lazily-loaded set from storage, root address and custom comparator. 158 | Supports all operations that normal in-memory impl would, 159 | will fetch missing nodes by calling IStorage::restore when needed" 160 | ([cmp address ^IStorage storage] 161 | (restore-by cmp address storage {})) 162 | ([cmp address ^IStorage storage opts] 163 | (PersistentSortedSet. nil cmp address storage nil -1 (map->settings opts) 0))) 164 | 165 | (defn restore 166 | "Constructs lazily-loaded set from storage and root address. 167 | Supports all operations that normal in-memory impl would, 168 | will fetch missing nodes by calling IStorage::restore when needed" 169 | ([address storage] 170 | (restore-by RT/DEFAULT_COMPARATOR address storage {})) 171 | ([address ^IStorage storage opts] 172 | (restore-by RT/DEFAULT_COMPARATOR address storage opts))) 173 | 174 | (defn walk-addresses 175 | "Visit each address used by this set. Usable for cleaning up 176 | garbage left in storage from previous versions of the set" 177 | [^PersistentSortedSet set consume-fn] 178 | (.walkAddresses set consume-fn)) 179 | 180 | (defn store 181 | "Store each not-yet-stored node by calling IStorage::store and remembering 182 | returned address. Incremental, won’t store same node twice on subsequent calls. 183 | Returns root address. Remember it and use it for restore" 184 | ([^PersistentSortedSet set] 185 | (.store set)) 186 | ([^PersistentSortedSet set ^IStorage storage] 187 | (.store set storage))) 188 | 189 | (defn settings [^PersistentSortedSet set] 190 | (settings->map (.-_settings set))) 191 | -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/Leaf.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.util.*; 4 | import java.util.function.*; 5 | import clojure.lang.*; 6 | 7 | @SuppressWarnings("unchecked") 8 | public class Leaf extends ANode { 9 | public Leaf(int len, Key[] keys, Settings settings) { 10 | super(len, keys, settings); 11 | } 12 | 13 | public Leaf(int len, Settings settings) { 14 | super(len, (Key[]) new Object[ANode.newLen(len, settings)], settings); 15 | } 16 | 17 | public Leaf(List keys, Settings settings) { 18 | this(keys.size(), (Key[]) keys.toArray(), settings); 19 | } 20 | 21 | @Override 22 | public int level() { 23 | return 0; 24 | } 25 | 26 | @Override 27 | public int count(IStorage storage) { 28 | return _len; 29 | } 30 | 31 | @Override 32 | public boolean contains(IStorage storage, Key key, Comparator cmp) { 33 | return search(key, cmp) >= 0; 34 | } 35 | 36 | @Override 37 | public ANode[] add(IStorage storage, Key key, Comparator cmp, Settings settings) { 38 | int idx = search(key, cmp); 39 | if (idx >= 0) // already in set 40 | return PersistentSortedSet.UNCHANGED; 41 | 42 | int ins = -idx - 1; 43 | assert 0 <= ins && ins <= _len; 44 | 45 | // can modify array in place 46 | if (editable() && _len < _keys.length) { 47 | if (ins == _len) { 48 | _keys[_len] = key; 49 | _len += 1; 50 | return new ANode[]{this}; // maxKey needs updating 51 | } else { 52 | ArrayUtil.copy(_keys, ins, _len, _keys, ins+1); 53 | _keys[ins] = key; 54 | _len += 1; 55 | return PersistentSortedSet.EARLY_EXIT; 56 | } 57 | } 58 | 59 | // simply adding to array 60 | if (_len < _settings.branchingFactor()) { 61 | ANode n = new Leaf(_len + 1, settings); 62 | new Stitch(n._keys, 0) 63 | .copyAll(_keys, 0, ins) 64 | .copyOne(key) 65 | .copyAll(_keys, ins, _len); 66 | return new ANode[]{n}; 67 | } 68 | 69 | // splitting 70 | int half1 = (_len + 1) >>> 1, 71 | half2 = _len + 1 - half1; 72 | 73 | // goes to first half 74 | if (ins < half1) { 75 | Leaf n1 = new Leaf(half1, settings), 76 | n2 = new Leaf(half2, settings); 77 | new Stitch(n1._keys, 0) 78 | .copyAll(_keys, 0, ins) 79 | .copyOne(key) 80 | .copyAll(_keys, ins, half1 - 1); 81 | ArrayUtil.copy(_keys, half1 - 1, _len, n2._keys, 0); 82 | return new ANode[]{n1, n2}; 83 | } 84 | 85 | // copy first, insert to second 86 | Leaf n1 = new Leaf(half1, settings), 87 | n2 = new Leaf(half2, settings); 88 | ArrayUtil.copy(_keys, 0, half1, n1._keys, 0); 89 | new Stitch(n2._keys, 0) 90 | .copyAll(_keys, half1, ins) 91 | .copyOne(key) 92 | .copyAll(_keys, ins, _len); 93 | return new ANode[]{n1, n2}; 94 | } 95 | 96 | @Override 97 | public ANode[] remove(IStorage storage, Key key, ANode _left, ANode _right, Comparator cmp, Settings settings) { 98 | Leaf left = (Leaf) _left; 99 | Leaf right = (Leaf) _right; 100 | 101 | int idx = search(key, cmp); 102 | if (idx < 0) // not in set 103 | return PersistentSortedSet.UNCHANGED; 104 | 105 | int newLen = _len - 1; 106 | 107 | // nothing to merge 108 | if (newLen >= _settings.minBranchingFactor() || (left == null && right == null)) { 109 | 110 | // transient, can edit in place 111 | if (editable()) { 112 | ArrayUtil.copy(_keys, idx + 1, _len, _keys, idx); 113 | _len = newLen; 114 | if (idx == newLen) // removed last, need to signal new maxKey 115 | return new ANode[]{left, this, right}; 116 | return PersistentSortedSet.EARLY_EXIT; 117 | } 118 | 119 | // persistent 120 | Leaf center = new Leaf(newLen, settings); 121 | new Stitch(center._keys, 0) 122 | .copyAll(_keys, 0, idx) 123 | .copyAll(_keys, idx + 1, _len); 124 | return new ANode[] { left, center, right }; 125 | } 126 | 127 | // can join with left 128 | if (left != null && left._len + newLen <= _settings.branchingFactor()) { 129 | Leaf join = new Leaf(left._len + newLen, settings); 130 | new Stitch(join._keys, 0) 131 | .copyAll(left._keys, 0, left._len) 132 | .copyAll(_keys, 0, idx) 133 | .copyAll(_keys, idx + 1, _len); 134 | return new ANode[] { null, join, right }; 135 | } 136 | 137 | // can join with right 138 | if (right != null && newLen + right.len() <= _settings.branchingFactor()) { 139 | Leaf join = new Leaf(newLen + right._len, settings); 140 | new Stitch(join._keys, 0) 141 | .copyAll(_keys, 0, idx) 142 | .copyAll(_keys, idx + 1, _len) 143 | .copyAll(right._keys, 0, right._len); 144 | return new ANode[]{ left, join, null }; 145 | } 146 | 147 | // borrow from left 148 | if (left != null && (left.editable() || right == null || left._len >= right._len)) { 149 | int totalLen = left._len + newLen, 150 | newLeftLen = totalLen >>> 1, 151 | newCenterLen = totalLen - newLeftLen, 152 | leftTail = left._len - newLeftLen; 153 | 154 | Leaf newLeft, newCenter; 155 | 156 | // prepend to center 157 | if (editable() && newCenterLen <= _keys.length) { 158 | newCenter = this; 159 | ArrayUtil.copy(_keys, idx + 1, _len, _keys, leftTail + idx); 160 | ArrayUtil.copy(_keys, 0, idx, _keys, leftTail); 161 | ArrayUtil.copy(left._keys, newLeftLen, left._len, _keys, 0); 162 | _len = newCenterLen; 163 | } else { 164 | newCenter = new Leaf(newCenterLen, settings); 165 | new Stitch(newCenter._keys, 0) 166 | .copyAll(left._keys, newLeftLen, left._len) 167 | .copyAll(_keys, 0, idx) 168 | .copyAll(_keys, idx+1, _len); 169 | } 170 | 171 | // shrink left 172 | if (left.editable()) { 173 | newLeft = left; 174 | left._len = newLeftLen; 175 | } else { 176 | newLeft = new Leaf(newLeftLen, settings); 177 | ArrayUtil.copy(left._keys, 0, newLeftLen, newLeft._keys, 0); 178 | } 179 | 180 | return new ANode[]{ newLeft, newCenter, right }; 181 | } 182 | 183 | // borrow from right 184 | if (right != null) { 185 | int totalLen = newLen + right._len, 186 | newCenterLen = totalLen >>> 1, 187 | newRightLen = totalLen - newCenterLen, 188 | rightHead = right._len - newRightLen; 189 | 190 | Leaf newCenter, newRight; 191 | 192 | // append to center 193 | if (editable() && newCenterLen <= _keys.length) { 194 | newCenter = this; 195 | new Stitch(_keys, idx) 196 | .copyAll(_keys, idx + 1, _len) 197 | .copyAll(right._keys, 0, rightHead); 198 | _len = newCenterLen; 199 | } else { 200 | newCenter = new Leaf(newCenterLen, settings); 201 | new Stitch(newCenter._keys, 0) 202 | .copyAll(_keys, 0, idx) 203 | .copyAll(_keys, idx + 1, _len) 204 | .copyAll(right._keys, 0, rightHead); 205 | } 206 | 207 | // cut head from right 208 | if (right.editable()) { 209 | newRight = right; 210 | ArrayUtil.copy(right._keys, rightHead, right._len, right._keys, 0); 211 | right._len = newRightLen; 212 | } else { 213 | newRight = new Leaf(newRightLen, settings); 214 | ArrayUtil.copy(right._keys, rightHead, right._len, newRight._keys, 0); 215 | } 216 | 217 | return new ANode[]{ left, newCenter, newRight }; 218 | } 219 | throw new RuntimeException("Unreachable"); 220 | } 221 | 222 | @Override 223 | public void walkAddresses(IStorage storage, IFn onAddress) { 224 | // noop 225 | } 226 | 227 | @Override 228 | public Address store(IStorage storage) { 229 | return storage.store(this); 230 | } 231 | 232 | @Override 233 | public String str(IStorage storage, int lvl) { 234 | StringBuilder sb = new StringBuilder("{"); 235 | for (int i = 0; i < _len; ++i) { 236 | if (i > 0) sb.append(" "); 237 | sb.append(_keys[i].toString()); 238 | } 239 | return sb.append("}").toString(); 240 | } 241 | 242 | @Override 243 | public void toString(StringBuilder sb, Address address, String indent) { 244 | sb.append(indent); 245 | sb.append("Leaf addr: " + address + " len: " + _len + " "); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/PersistentSortedSet.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import clojure.lang.*; 4 | import java.util.*; 5 | import java.util.function.*; 6 | 7 | @SuppressWarnings("unchecked") 8 | public class PersistentSortedSet extends APersistentSortedSet 9 | implements IEditableCollection, 10 | ITransientSet, 11 | Reversible, 12 | Sorted, 13 | IReduce, 14 | IPersistentSortedSet { 15 | 16 | public static ANode[] EARLY_EXIT = new ANode[0]; 17 | public static ANode[] UNCHANGED = new ANode[0]; 18 | 19 | public static final PersistentSortedSet EMPTY = new PersistentSortedSet(); 20 | 21 | public Address _address; 22 | public Object _root; // Object == ANode | SoftReference | WeakReference 23 | public int _count; 24 | public int _version; 25 | public final Settings _settings; 26 | public IStorage _storage; 27 | 28 | public PersistentSortedSet() { 29 | this(null, RT.DEFAULT_COMPARATOR); 30 | } 31 | 32 | public PersistentSortedSet(Comparator cmp) { 33 | this(null, cmp); 34 | } 35 | 36 | public PersistentSortedSet(IPersistentMap meta, Comparator cmp) { 37 | this(meta, cmp, null, new Settings()); 38 | } 39 | 40 | public PersistentSortedSet(IPersistentMap meta, Comparator cmp, IStorage storage, Settings settings) { 41 | this(meta, cmp, null, storage, new Leaf(0, settings), 0, settings, 0); 42 | } 43 | 44 | public PersistentSortedSet(IPersistentMap meta, Comparator cmp, Address address, IStorage storage, Object root, int count, Settings settings, int version) { 45 | super(meta, cmp); 46 | _address = address; 47 | _root = root; 48 | _count = count; 49 | _version = version; 50 | _settings = settings; 51 | _storage = storage; 52 | } 53 | 54 | public ANode root() { 55 | assert _address != null || _root != null; 56 | ANode root = (ANode) _settings.readReference(_root); 57 | if (root == null && _address != null) { 58 | root = _storage.restore(_address); 59 | _root = _settings.makeReference(root); 60 | } 61 | return root; 62 | } 63 | 64 | private int alterCount(int delta) { 65 | return _count < 0 ? _count : _count + delta; 66 | } 67 | 68 | public boolean editable() { 69 | return _settings.editable(); 70 | } 71 | 72 | public Address address(Address address) { 73 | _address = address; 74 | return address; 75 | } 76 | 77 | // IPersistentSortedSet 78 | @Override 79 | public Seq slice(Key from, Key to) { 80 | return slice(from, to, _cmp); 81 | } 82 | 83 | @Override 84 | public Seq slice(Key from, Key to, Comparator cmp) { 85 | assert from == null || to == null || cmp.compare(from, to) <= 0 : "From " + from + " after to " + to; 86 | Seq seq = null; 87 | ANode node = root(); 88 | 89 | if (node.len() == 0) { 90 | return null; 91 | } 92 | 93 | if (from == null) { 94 | while (true) { 95 | if (node instanceof Branch) { 96 | seq = new Seq(null, this, seq, node, 0, null, null, true, _version); 97 | node = seq.child(); 98 | } else { 99 | seq = new Seq(null, this, seq, node, 0, to, cmp, true, _version); 100 | return seq.over() ? null : seq; 101 | } 102 | } 103 | } 104 | 105 | while (true) { 106 | int idx = node.searchFirst(from, cmp); 107 | if (idx < 0) idx = -idx - 1; 108 | if (idx == node._len) return null; 109 | if (node instanceof Branch) { 110 | seq = new Seq(null, this, seq, node, idx, null, null, true, _version); 111 | node = seq.child(); 112 | } else { 113 | seq = new Seq(null, this, seq, node, idx, to, cmp, true, _version); 114 | return seq.over() ? null : seq; 115 | } 116 | } 117 | } 118 | 119 | public Seq rslice(Key from, Key to) { 120 | return rslice(from, to, _cmp); 121 | } 122 | 123 | public Seq rslice(Key from, Key to, Comparator cmp) { 124 | assert from == null || to == null || cmp.compare(from, to) >= 0 : "From " + from + " before to " + to; 125 | Seq seq = null; 126 | ANode node = root(); 127 | 128 | if (node.len() == 0) return null; 129 | 130 | if (from == null) { 131 | while (true) { 132 | int idx = node._len - 1; 133 | if (node instanceof Branch) { 134 | seq = new Seq(null, this, seq, node, idx, null, null, false, _version); 135 | node = seq.child(); 136 | } else { 137 | seq = new Seq(null, this, seq, node, idx, to, cmp, false, _version); 138 | return seq.over() ? null : seq; 139 | } 140 | } 141 | } 142 | 143 | while (true) { 144 | if (node instanceof Branch) { 145 | int idx = node.searchLast(from, cmp) + 1; 146 | if (idx == node._len) --idx; // last or beyond, clamp to last 147 | seq = new Seq(null, this, seq, node, idx, null, null, false, _version); 148 | node = seq.child(); 149 | } else { 150 | int idx = node.searchLast(from, cmp); 151 | if (idx == -1) { // not in this, so definitely in prev 152 | seq = new Seq(null, this, seq, node, 0, to, cmp, false, _version); 153 | return seq.advance() ? seq : null; 154 | } else { // exact match 155 | seq = new Seq(null, this, seq, node, idx, to, cmp, false, _version); 156 | return seq.over() ? null : seq; 157 | } 158 | } 159 | } 160 | } 161 | 162 | public void walkAddresses(IFn onAddress) { 163 | if (_address != null) { 164 | if (!RT.booleanCast(onAddress.invoke(_address))) { 165 | return; 166 | } 167 | } 168 | root().walkAddresses(_storage, onAddress); 169 | } 170 | 171 | public Address store() { 172 | assert _storage != null; 173 | 174 | if (_address == null) { 175 | ANode root = (ANode) _settings.readReference(_root); 176 | address(root.store(_storage)); 177 | _root = _settings.makeReference(root); 178 | } 179 | 180 | return _address; 181 | } 182 | 183 | public Address store(IStorage storage) { 184 | _storage = storage; 185 | return store(); 186 | } 187 | 188 | public String toString() { 189 | StringBuilder sb = new StringBuilder("#{"); 190 | for (Object o: this) { 191 | sb.append(o).append(" "); 192 | } 193 | if (sb.charAt(sb.length() - 1) == " ".charAt(0)) { 194 | sb.delete(sb.length() - 1, sb.length()); 195 | } 196 | sb.append("}"); 197 | return sb.toString(); 198 | } 199 | 200 | public String str() { 201 | return root().str(_storage, 0); 202 | } 203 | 204 | // IObj 205 | public PersistentSortedSet withMeta(IPersistentMap meta) { 206 | if (_meta == meta) { 207 | return this; 208 | } 209 | return new PersistentSortedSet(meta, _cmp, _address, _storage, _root, _count, _settings, _version); 210 | } 211 | 212 | // Counted 213 | public int count() { 214 | if (_count < 0) _count = root().count(_storage); 215 | // assert _count == _root.count(_storage) : _count + " != " + _root.count(_storage); 216 | return _count; 217 | } 218 | 219 | // Sorted 220 | public Comparator comparator() { 221 | return _cmp; 222 | } 223 | 224 | public Object entryKey(Object entry) { 225 | return entry; 226 | } 227 | 228 | // IReduce 229 | public Object reduce(IFn f) { 230 | Seq seq = (Seq) seq(); 231 | return seq == null ? f.invoke() : seq.reduce(f); 232 | } 233 | 234 | public Object reduce(IFn f, Object start) { 235 | Seq seq = (Seq) seq(); 236 | return seq == null ? start : seq.reduce(f, start); 237 | } 238 | 239 | // IPersistentCollection 240 | public PersistentSortedSet empty() { 241 | return new PersistentSortedSet(_meta, _cmp, _storage, _settings); 242 | } 243 | 244 | public PersistentSortedSet cons(Object key) { 245 | return cons(key, _cmp); 246 | } 247 | 248 | public PersistentSortedSet cons(Object key, Comparator cmp) { 249 | ANode[] nodes = root().add(_storage, (Key) key, cmp, _settings); 250 | 251 | if (UNCHANGED == nodes) return this; 252 | 253 | if (editable()) { 254 | if (1 == nodes.length) { 255 | _address = null; 256 | _root = nodes[0]; 257 | } else if (2 == nodes.length) { 258 | Object[] keys = new Object[] {nodes[0].maxKey(), nodes[1].maxKey()}; 259 | _address = null; 260 | 261 | _root = new Branch(nodes[0].level() + 1, 2, keys, null, new Object[] {nodes[0], nodes[1]}, _settings); 262 | } 263 | _count = alterCount(1); 264 | _version += 1; 265 | return this; 266 | } 267 | 268 | if (1 == nodes.length) 269 | return new PersistentSortedSet(_meta, _cmp, null, _storage, nodes[0], alterCount(1), _settings, _version + 1); 270 | 271 | Object[] keys = new Object[] {nodes[0].maxKey(), nodes[1].maxKey()}; 272 | Object[] children = Arrays.copyOf(nodes, nodes.length, new Object[0].getClass()); 273 | 274 | ANode newRoot = new Branch(nodes[0].level() + 1, 2, keys, null, children, _settings); 275 | return new PersistentSortedSet(_meta, _cmp, null, _storage, newRoot, alterCount(1), _settings, _version + 1); 276 | } 277 | 278 | // IPersistentSet 279 | public PersistentSortedSet disjoin(Object key) { 280 | return disjoin(key, _cmp); 281 | } 282 | 283 | public PersistentSortedSet disjoin(Object key, Comparator cmp) { 284 | ANode[] nodes = root().remove(_storage, (Key) key, null, null, cmp, _settings); 285 | 286 | // not in set 287 | if (UNCHANGED == nodes) return this; 288 | 289 | // in place update 290 | if (nodes == EARLY_EXIT) { 291 | _address = null; 292 | _count = alterCount(-1); 293 | _version += 1; 294 | return this; 295 | } 296 | 297 | ANode newRoot = nodes[1]; 298 | if (editable()) { 299 | if (newRoot instanceof Branch && newRoot._len == 1) 300 | newRoot = ((Branch) newRoot).child(_storage, 0); 301 | _address = null; 302 | _root = newRoot; 303 | _count = alterCount(-1); 304 | _version += 1; 305 | return this; 306 | } 307 | if (newRoot instanceof Branch && newRoot._len == 1) { 308 | newRoot = ((Branch) newRoot).child(_storage, 0); 309 | return new PersistentSortedSet(_meta, _cmp, null, _storage, newRoot, alterCount(-1), _settings, _version + 1); 310 | } 311 | return new PersistentSortedSet(_meta, _cmp, null, _storage, newRoot, alterCount(-1), _settings, _version + 1); 312 | } 313 | 314 | public boolean contains(Object key) { 315 | return root().contains(_storage, (Key) key, _cmp); 316 | } 317 | 318 | // IEditableCollection 319 | public PersistentSortedSet asTransient() { 320 | if (editable()) { 321 | throw new IllegalStateException("Expected persistent set"); 322 | } 323 | return new PersistentSortedSet(_meta, _cmp, _address, _storage, _root, _count, _settings.editable(true), _version); 324 | } 325 | 326 | // ITransientCollection 327 | public PersistentSortedSet conj(Object key) { 328 | return cons(key, _cmp); 329 | } 330 | 331 | public PersistentSortedSet persistent() { 332 | if (!editable()) { 333 | throw new IllegalStateException("Expected transient set"); 334 | } 335 | _settings.persistent(); 336 | return this; 337 | } 338 | 339 | // Iterable 340 | public Iterator iterator() { 341 | return new JavaIter((Seq) seq()); 342 | } 343 | } -------------------------------------------------------------------------------- /test-clojure/me/tonsky/persistent_sorted_set/test/storage.clj: -------------------------------------------------------------------------------- 1 | (ns me.tonsky.persistent-sorted-set.test.storage 2 | (:require 3 | [clojure.edn :as edn] 4 | [clojure.string :as str] 5 | [clojure.test :as t :refer [is are deftest testing]] 6 | [me.tonsky.persistent-sorted-set :as set]) 7 | (:import 8 | [clojure.lang RT] 9 | [java.lang.ref Reference] 10 | [java.util Comparator Arrays] 11 | [me.tonsky.persistent_sorted_set ANode ArrayUtil Branch IStorage Leaf PersistentSortedSet Settings])) 12 | 13 | (set! *warn-on-reflection* true) 14 | 15 | (def ^:dynamic *debug* 16 | false) 17 | 18 | (defn gen-addr [] 19 | (random-uuid) 20 | #_(str (str/join (repeatedly 10 #(rand-nth "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))))) 21 | 22 | (def *stats 23 | (atom 24 | {:reads 0 25 | :writes 0 26 | :accessed 0})) 27 | 28 | (defmacro with-stats [& body] 29 | `(do 30 | (reset! *stats {:reads 0 :writes 0 :accessed 0}) 31 | ~@body)) 32 | 33 | (defrecord Storage [*memory *disk ^Settings settings] 34 | IStorage 35 | (store [_ node] 36 | (swap! *stats update :writes inc) 37 | (let [node ^ANode node 38 | address (gen-addr)] 39 | (swap! *disk assoc address 40 | (pr-str 41 | {:level (.level node) 42 | :keys (.keys node) 43 | :addresses (when (instance? Branch node) 44 | (.addresses ^Branch node))})) 45 | address)) 46 | (accessed [_ address] 47 | (swap! *stats update :accessed inc) 48 | nil) 49 | (restore [_ address] 50 | (or 51 | (@*memory address) 52 | (let [{:keys [level 53 | ^java.util.List keys 54 | ^java.util.List addresses]} (edn/read-string (@*disk address)) 55 | node (if addresses 56 | (Branch. (int level) ^java.util.List keys ^java.util.List addresses settings) 57 | (Leaf. keys settings))] 58 | (swap! *stats update :reads inc) 59 | (swap! *memory assoc address node) 60 | node)))) 61 | 62 | (defn storage 63 | (^IStorage [] 64 | (->Storage (atom {}) (atom {}) (Settings.))) 65 | (^IStorage [*disk] 66 | (->Storage (atom {}) *disk (Settings.))) 67 | (^IStorage [*memory *disk] 68 | (->Storage *memory *disk (Settings.)))) 69 | 70 | (defn roundtrip [set] 71 | (let [storage (storage) 72 | address (set/store set storage)] 73 | (set/restore address storage))) 74 | 75 | (defn loaded-ratio 76 | ([^PersistentSortedSet set] 77 | (let [storage (.-_storage set) 78 | address (.-_address set) 79 | root (.-_root set)] 80 | (loaded-ratio (some-> storage :*memory deref) address root))) 81 | ([memory address node] 82 | (when *debug* 83 | (println address (contains? memory address) node (memory address))) 84 | (if (and address (not (contains? memory address))) 85 | 0.0 86 | (let [node (if (instance? Reference node) (.get ^Reference node) node) 87 | node (or node (memory address))] 88 | (if (instance? Leaf node) 89 | 1.0 90 | (let [node ^Branch node 91 | len (.len node)] 92 | (double 93 | (/ (->> 94 | (mapv 95 | (fn [_ child-addr child] 96 | (loaded-ratio memory child-addr child)) 97 | (range len) 98 | (or (.-_addresses node) (repeat len nil)) 99 | (or (.-_children node) (repeat len nil))) 100 | (reduce + 0)) 101 | len)))))))) 102 | 103 | (defn durable-ratio 104 | ([^PersistentSortedSet set] 105 | (double (durable-ratio (.-_address set) (.-_root set)))) 106 | ([address ^ANode node] 107 | (cond 108 | (some? address) 1.0 109 | (instance? Leaf node) 0.0 110 | :else 111 | (let [len (.len node)] 112 | (/ (->> 113 | (map 114 | (fn [_ child-addr child] 115 | (durable-ratio child-addr child)) 116 | (range len) 117 | (.-_addresses ^Branch node) 118 | (.-_children ^Branch node)) 119 | (reduce + 0)) 120 | len))))) 121 | 122 | (deftest test-lazy-remove 123 | "Check that invalidating middle branch does not invalidates siblings" 124 | (let [size 7000 ;; 3-4 branches 125 | xs (shuffle (range size)) 126 | set (into (set/sorted-set* {:branching-factor 64}) xs)] 127 | (set/store set (storage)) 128 | (is (= 1.0 (durable-ratio set)) 129 | (let [set' (disj set 3500)] ;; one of the middle branches 130 | (is (< 0.98 (durable-ratio set'))))))) 131 | 132 | (defmacro dobatches [[sym coll] & body] 133 | `(loop [coll# ~coll] 134 | (when (seq coll#) 135 | (let [batch# (rand-nth [1 2 3 4 5 10 20 30 40 50 100]) 136 | [~sym tail#] (split-at batch# coll#)] 137 | ~@body 138 | (recur tail#))))) 139 | 140 | (deftest stresstest-stable-addresses 141 | (let [size 10000 142 | adds (shuffle (range size)) 143 | removes (shuffle adds) 144 | *set (atom (set/sorted-set)) 145 | *disk (atom {}) 146 | storage (storage *disk) 147 | invariant (fn invariant 148 | ([^PersistentSortedSet o] 149 | (invariant (.root o) (some? (.-_address o)))) 150 | ([^ANode o stored?] 151 | (condp instance? o 152 | Branch 153 | (let [node ^Branch o 154 | len (.len node)] 155 | (doseq [i (range len) 156 | :let [addr (nth (.-_addresses node) i) 157 | child (.child node storage (int i)) 158 | {:keys [keys addresses]} (edn/read-string (@*disk addr))]] 159 | ;; nodes inside stored? has to ALL be stored 160 | (when stored? 161 | (is (some? addr))) 162 | (when (some? addr) 163 | (is (= keys 164 | (take (.len ^ANode child) (.-_keys ^ANode child)))) 165 | (is (= addresses 166 | (when (instance? Branch child) 167 | (take (.len ^Branch child) (.-_addresses ^Branch child)))))) 168 | (invariant child (some? addr)))) 169 | Leaf 170 | true)))] 171 | (testing "Persist after each" 172 | (dobatches [xs adds] 173 | (let [set' (swap! *set into xs)] 174 | (invariant set') 175 | (set/store set' storage))) 176 | 177 | (invariant @*set) 178 | 179 | (dobatches [xs removes] 180 | (let [set' (swap! *set #(reduce disj % xs))] 181 | (invariant set') 182 | (set/store set' storage)))) 183 | 184 | (testing "Persist once" 185 | (reset! *set (into (set/sorted-set) adds)) 186 | (set/store @*set storage) 187 | (dobatches [xs removes] 188 | (let [set' (swap! *set #(reduce disj % xs))] 189 | (invariant set')))))) 190 | 191 | (deftest test-walk 192 | (let [size 1000000 193 | xs (shuffle (range size)) 194 | set (into (set/sorted-set* {:branching-factor 64}) xs) 195 | *stored (atom 0)] 196 | (set/walk-addresses set 197 | (fn [addr] 198 | (is (nil? addr)))) 199 | (set/store set (storage)) 200 | (set/walk-addresses set 201 | (fn [addr] 202 | (is (some? addr)) 203 | (swap! *stored inc))) 204 | (let [set' (conj set (* 2 size)) 205 | *stored' (atom 0)] 206 | (set/walk-addresses set' 207 | (fn [addr] 208 | (if (some? addr) 209 | (swap! *stored' inc)))) 210 | (is (= (- @*stored 4) @*stored'))))) 211 | 212 | (deftest test-lazyness 213 | (let [size 1000000 214 | xs (shuffle (range size)) 215 | rm (vec (repeatedly (quot size 5) #(rand-nth xs))) 216 | original (-> (reduce disj (into (set/sorted-set* {:branching-factor 64}) xs) rm) 217 | (disj (quot size 4) (quot size 2))) 218 | storage (storage) 219 | address (with-stats 220 | (set/store original storage)) 221 | _ (is (= 0 (:reads @*stats))) 222 | ; _ (is (> (:writes @*stats) (/ size PersistentSortedSet/MAX_LEN))) 223 | loaded (set/restore address storage {:branching-factor 64}) 224 | _ (is (= 0 (:reads @*stats))) 225 | _ (is (= 0.0 (loaded-ratio loaded))) 226 | _ (is (= 1.0 (durable-ratio loaded))) 227 | 228 | ; touch first 100 229 | _ (is (= (take 100 loaded) (take 100 original))) 230 | _ (is (<= 5 (:reads @*stats) 7)) 231 | l100 (loaded-ratio loaded) 232 | _ (is (< 0 l100 1.0)) 233 | 234 | ; touch first 5000 235 | _ (is (= (take 5000 loaded) (take 5000 original))) 236 | l5000 (loaded-ratio loaded) 237 | _ (is (< l100 l5000 1.0)) 238 | 239 | ; touch middle 240 | from (- (quot size 2) (quot size 200)) 241 | to (+ (quot size 2) (quot size 200)) 242 | _ (is (= (vec (set/slice loaded from to)) 243 | (vec (set/slice loaded from to)))) 244 | lmiddle (loaded-ratio loaded) 245 | _ (is (< l5000 lmiddle 1.0)) 246 | 247 | ; touch 100 last 248 | _ (is (= (take 100 (rseq loaded)) (take 100 (rseq original)))) 249 | lrseq (loaded-ratio loaded) 250 | _ (is (< lmiddle lrseq 1.0)) 251 | 252 | ; touch 10000 last 253 | from (- size (quot size 100)) 254 | to size 255 | _ (is (= (vec (set/slice loaded from to)) 256 | (vec (set/slice loaded from 1000000)))) 257 | ltail (loaded-ratio loaded) 258 | _ (is (< lrseq ltail 1.0)) 259 | 260 | ; conj to beginning 261 | loaded' (conj loaded -1) 262 | _ (is (= ltail (loaded-ratio loaded'))) 263 | _ (is (< (durable-ratio loaded') 1.0)) 264 | 265 | ; conj to middle 266 | loaded' (conj loaded (quot size 2)) 267 | _ (is (= ltail (loaded-ratio loaded'))) 268 | _ (is (< (durable-ratio loaded') 1.0)) 269 | 270 | ; conj to end 271 | loaded' (conj loaded Long/MAX_VALUE) 272 | _ (is (= ltail (loaded-ratio loaded'))) 273 | _ (is (< (durable-ratio loaded') 1.0)) 274 | 275 | ; conj to untouched area 276 | loaded' (conj loaded (quot size 4)) 277 | _ (is (< ltail (loaded-ratio loaded') 1.0)) 278 | _ (is (< ltail (loaded-ratio loaded) 1.0)) 279 | _ (is (< (durable-ratio loaded') 1.0)) 280 | 281 | ; transients conj 282 | xs (range -10000 0) 283 | loaded' (into loaded xs) 284 | _ (is (every? loaded' xs)) 285 | _ (is (< ltail (loaded-ratio loaded'))) 286 | _ (is (< (durable-ratio loaded') 1.0)) 287 | 288 | ; incremental persist 289 | _ (with-stats 290 | (set/store loaded' storage)) 291 | _ (is (< (:writes @*stats) 350)) ;; ~ 10000 / 32 + 10000 / 32 / 32 + 1 292 | _ (is (= 1.0 (durable-ratio loaded'))) 293 | 294 | ; transient disj 295 | xs (take 100 loaded) 296 | loaded' (reduce disj loaded xs) 297 | _ (is (every? #(not (loaded' %)) xs)) 298 | _ (is (< (durable-ratio loaded') 1.0)) 299 | 300 | ; count fetches everything 301 | _ (is (= (count loaded) (count original))) 302 | l0 (loaded-ratio loaded) 303 | _ (is (= 1.0 l0))])) 304 | -------------------------------------------------------------------------------- /test-clojure/me/tonsky/persistent_sorted_set/test/core.cljc: -------------------------------------------------------------------------------- 1 | (ns me.tonsky.persistent-sorted-set.test.core 2 | (:require 3 | [me.tonsky.persistent-sorted-set :as set] 4 | [clojure.test :as t :refer [is are deftest testing]]) 5 | #?(:clj 6 | (:import [clojure.lang IReduce]))) 7 | 8 | #?(:clj (set! *warn-on-reflection* true)) 9 | 10 | (def iters 5) 11 | 12 | ;; confirm that clj's use of sorted set works as intended. 13 | ;; allow for [:foo nil] to glob [:foo *]; data will never be inserted 14 | ;; w/ nil, but slice/subseq elements will. 15 | 16 | (defn cmp [x y] 17 | (if (and x y) 18 | (compare x y) 19 | 0)) 20 | 21 | (defn cmp-s [[x0 x1] [y0 y1]] 22 | (let [c0 (cmp x0 y0) 23 | c1 (cmp x1 y1)] 24 | (cond 25 | (= c0 0) c1 26 | (< c0 0) -1 27 | (> c0 0) 1))) 28 | 29 | (deftest semantic-test-btset-by 30 | (let [e0 (set/sorted-set-by cmp-s) 31 | ds [[:a :b] [:b :x] [:b :q] [:a :d]] 32 | e1 (reduce conj e0 ds)] 33 | (is (= (count ds) (count (seq e1)))) 34 | (is (= (vec (seq e1)) (vec (set/slice e1 [nil nil] [nil nil])))) ; * * 35 | (is (= [[:a :b] [:a :d]] (vec (set/slice e1 [:a nil] [:a nil] )))) ; :a * 36 | (is (= [[:b :q]] (vec (set/slice e1 [:b :q] [:b :q] )))) ; :b :q (specific) 37 | (is (= [[:a :d] [:b :q]] (vec (set/slice e1 [:a :d] [:b :q] )))) ; matching subrange 38 | (is (= [[:a :d] [:b :q]] (vec (set/slice e1 [:a :c] [:b :r] )))) ; non-matching subrange 39 | (is (= [[:b :x]] (vec (set/slice e1 [:b :r] [:c nil] )))) ; non-matching -> out of range 40 | (is (= [] (vec (set/slice e1 [:c nil] [:c nil] )))) ; totally out of range 41 | )) 42 | 43 | (defn irange [from to] 44 | (if (< from to) 45 | (range from (inc to)) 46 | (range from (dec to) -1))) 47 | 48 | (deftest test-slice 49 | (dotimes [i iters] 50 | (testing "straight 3 layers" 51 | (let [s (into (set/sorted-set) (shuffle (irange 0 5000)))] 52 | (are [from to expected] (= expected (set/slice s from to)) 53 | nil nil (irange 0 5000) 54 | 55 | -1 nil (irange 0 5000) 56 | 0 nil (irange 0 5000) 57 | 0.5 nil (irange 1 5000) 58 | 1 nil (irange 1 5000) 59 | 4999 nil [4999 5000] 60 | 4999.5 nil [5000] 61 | 5000 nil [5000] 62 | 5000.5 nil nil 63 | 64 | nil -1 nil 65 | nil 0 [0] 66 | nil 0.5 [0] 67 | nil 1 [0 1] 68 | nil 4999 (irange 0 4999) 69 | nil 4999.5 (irange 0 4999) 70 | nil 5000 (irange 0 5000) 71 | nil 5001 (irange 0 5000) 72 | 73 | -2 -1 nil 74 | -1 5001 (irange 0 5000) 75 | 0 5000 (irange 0 5000) 76 | 0.5 4999.5 (irange 1 4999) 77 | 2499.5 2500.5 [2500] 78 | 2500 2500 [2500] 79 | 2500.1 2500.9 nil 80 | 5001 5002 nil))) 81 | 82 | (testing "straight 1 layer, leaf == root" 83 | (let [s (into (set/sorted-set) (shuffle (irange 0 10)))] 84 | (are [from to expected] (= expected (set/slice s from to)) 85 | nil nil (irange 0 10) 86 | 87 | -1 nil (irange 0 10) 88 | 0 nil (irange 0 10) 89 | 0.5 nil (irange 1 10) 90 | 1 nil (irange 1 10) 91 | 9 nil [9 10] 92 | 9.5 nil [10] 93 | 10 nil [10] 94 | 10.5 nil nil 95 | 96 | nil -1 nil 97 | nil 0 [0] 98 | nil 0.5 [0] 99 | nil 1 [0 1] 100 | nil 9 (irange 0 9) 101 | nil 9.5 (irange 0 9) 102 | nil 10 (irange 0 10) 103 | nil 11 (irange 0 10) 104 | 105 | -2 -1 nil 106 | -1 10 (irange 0 10) 107 | 0 10 (irange 0 10) 108 | 0.5 9.5 (irange 1 9) 109 | 4.5 5.5 [5] 110 | 5 5 [5] 111 | 5.1 5.9 nil 112 | 11 12 nil))) 113 | 114 | (testing "reverse 3 layers" 115 | (let [s (into (set/sorted-set) (shuffle (irange 0 5000)))] 116 | (are [from to expected] (= expected (set/rslice s from to)) 117 | nil nil (irange 5000 0) 118 | 119 | 5001 nil (irange 5000 0) 120 | 5000 nil (irange 5000 0) 121 | 4999.5 nil (irange 4999 0) 122 | 4999 nil (irange 4999 0) 123 | 1 nil [1 0] 124 | 0.5 nil [0] 125 | 0 nil [0] 126 | -1 nil nil 127 | 128 | nil 5001 nil 129 | nil 5000 [5000] 130 | nil 4999.5 [5000] 131 | nil 4999 [5000 4999] 132 | nil 1 (irange 5000 1) 133 | nil 0.5 (irange 5000 1) 134 | nil 0 (irange 5000 0) 135 | nil -1 (irange 5000 0) 136 | 137 | 5002 5001 nil 138 | 5001 -1 (irange 5000 0) 139 | 5000 0 (irange 5000 0) 140 | 4999.5 0.5 (irange 4999 1) 141 | 2500.5 2499.5 [2500] 142 | 2500 2500 [2500] 143 | 2500.9 2500.1 nil 144 | -1 -2 nil))) 145 | 146 | (testing "reverse 1 layer, leaf == root" 147 | (let [s (into (set/sorted-set) (shuffle (irange 0 10)))] 148 | (are [from to expected] (= expected (set/rslice s from to)) 149 | nil nil (irange 10 0) 150 | 151 | 11 nil (irange 10 0) 152 | 10 nil (irange 10 0) 153 | 9.5 nil (irange 9 0) 154 | 9 nil (irange 9 0) 155 | 1 nil [1 0] 156 | 0.5 nil [0] 157 | 0 nil [0] 158 | -1 nil nil 159 | 160 | nil 11 nil 161 | nil 10 [10] 162 | nil 9.5 [10] 163 | nil 9 [10 9] 164 | nil 1 (irange 10 1) 165 | nil 0.5 (irange 10 1) 166 | nil 0 (irange 10 0) 167 | nil -1 (irange 10 0) 168 | 169 | 12 11 nil 170 | 11 -1 (irange 10 0) 171 | 10 0 (irange 10 0) 172 | 9.5 0.5 (irange 9 1) 173 | 5.5 4.5 [5] 174 | 5 5 [5] 175 | 5.9 5.1 nil 176 | -1 -2 nil))) 177 | 178 | (testing "seq-rseq equivalence" 179 | (let [s (into (set/sorted-set) (shuffle (irange 0 5000)))] 180 | (are [from to] (= (set/slice s from to) (some-> (set/slice s from to) (rseq) (reverse))) 181 | -1 nil 182 | 0 nil 183 | 2500 nil 184 | 5000 nil 185 | 5001 nil 186 | 187 | nil -1 188 | nil 0 189 | nil 1 190 | nil 2500 191 | nil 5000 192 | nil 5001 193 | 194 | nil nil 195 | 196 | -1 5001 197 | 0 5000 198 | 1 4999 199 | 2500 2500 200 | 2500.1 2500.9))) 201 | 202 | (testing "rseq-seq equivalence" 203 | (let [s (into (set/sorted-set) (shuffle (irange 0 5000)))] 204 | (are [from to] (= (set/rslice s from to) (some-> (set/rslice s from to) (rseq) (reverse))) 205 | -1 nil 206 | 0 nil 207 | 2500 nil 208 | 5000 nil 209 | 5001 nil 210 | 211 | nil -1 212 | nil 0 213 | nil 1 214 | nil 2500 215 | nil 5000 216 | nil 5001 217 | 218 | nil nil 219 | 220 | 5001 -1 221 | 5000 0 222 | 4999 1 223 | 2500 2500 224 | 2500.9 2500.1))) 225 | 226 | (testing "Slice with equal elements" 227 | (let [cmp10 (fn [a b] (compare (quot a 10) (quot b 10))) 228 | s10 (reduce #(set/conj %1 %2 compare) (set/sorted-set-by cmp10) (shuffle (irange 0 5000)))] 229 | (are [from to expected] (= expected (set/slice s10 from to)) 230 | 30 30 (irange 30 39) 231 | 130 4970 (irange 130 4979) 232 | -100 6000 (irange 0 5000)) 233 | (are [from to expected] (= expected (set/rslice s10 from to)) 234 | 30 30 (irange 39 30) 235 | 4970 130 (irange 4979 130) 236 | 6000 -100 (irange 5000 0))) 237 | 238 | (let [cmp100 (fn [a b] (compare (quot a 100) (quot b 100))) 239 | s100 (reduce #(set/conj %1 %2 compare) (set/sorted-set-by cmp100) (shuffle (irange 0 5000)))] 240 | (are [from to expected] (= expected (set/slice s100 from to)) 241 | 30 30 (irange 0 99) 242 | 2550 2550 (irange 2500 2599) 243 | 130 4850 (irange 100 4899) 244 | -100 6000 (irange 0 5000)) 245 | (are [from to expected] (= expected (set/rslice s100 from to)) 246 | 30 30 (irange 99 0) 247 | 2550 2550 (irange 2599 2500) 248 | 4850 130 (irange 4899 100) 249 | 6000 -100 (irange 5000 0)))) 250 | )) 251 | 252 | (defn ireduce 253 | ([f coll] (#?(:clj .reduce :cljs -reduce) ^IReduce coll f)) 254 | ([f val coll] (#?(:clj .reduce :cljs -reduce) ^IReduce coll f val))) 255 | 256 | (defn reduce-chunked [f val coll] 257 | (if-some [s (seq coll)] 258 | (if (chunked-seq? s) 259 | (recur f (#?(:clj .reduce :cljs -reduce) (chunk-first s) f val) (chunk-next s)) 260 | (recur f (f val (first s)) (next s))) 261 | val)) 262 | 263 | (deftest test-reduces 264 | (testing "IReduced" 265 | (testing "Empty" 266 | (let [s (set/sorted-set)] 267 | (is (= 0 (ireduce + s))) 268 | (is (= 0 (ireduce + 0 s))))) 269 | 270 | (testing "~3 layers" 271 | (let [s (into (set/sorted-set) (irange 0 5000))] 272 | (is (= 12502500 (ireduce + s))) 273 | (is (= 12502500 (ireduce + 0 s))) 274 | (is (= 12502500 (ireduce + (seq s)))) 275 | (is (= 12502500 (ireduce + 0 (seq s)))) 276 | (is (= 7502500 (ireduce + (set/slice s 1000 4000)))) 277 | (is (= 7502500 (ireduce + 0 (set/slice s 1000 4000)))) 278 | #?@(:clj [(is (= 12502500 (ireduce + (rseq s)))) 279 | (is (= 12502500 (ireduce + 0 (rseq s)))) 280 | (is (= 7502500 (ireduce + (set/rslice s 4000 1000)))) 281 | (is (= 7502500 (ireduce + 0 (set/rslice s 4000 1000))))]))) 282 | 283 | (testing "~1 layer" 284 | (let [s (into (set/sorted-set) (irange 0 10))] 285 | (is (= 55 (ireduce + s))) 286 | (is (= 55 (ireduce + 0 s))) 287 | (is (= 55 (ireduce + (seq s)))) 288 | (is (= 55 (ireduce + 0 (seq s)))) 289 | (is (= 35 (ireduce + (set/slice s 2 8)))) 290 | (is (= 35 (ireduce + 0 (set/slice s 2 8)))) 291 | #?@(:clj [(is (= 55 (ireduce + (rseq s)))) 292 | (is (= 55 (ireduce + 0 (rseq s)))) 293 | (is (= 35 (ireduce + (set/rslice s 8 2)))) 294 | (is (= 35 (ireduce + 0 (set/rslice s 8 2))))])))) 295 | 296 | (testing "IChunkedSeq" 297 | (testing "~3 layers" 298 | (let [s (into (set/sorted-set) (irange 0 5000))] 299 | (is (= 12502500 (reduce-chunked + 0 s))) 300 | (is (= 7502500 (reduce-chunked + 0 (set/slice s 1000 4000)))) 301 | (is (= 12502500 (reduce-chunked + 0 (rseq s)))) 302 | (is (= 7502500 (reduce-chunked + 0 (set/rslice s 4000 1000)))))) 303 | 304 | (testing "~1 layer" 305 | (let [s (into (set/sorted-set) (irange 0 10))] 306 | (is (= 55 (reduce-chunked + 0 s))) 307 | (is (= 35 (reduce-chunked + 0 (set/slice s 2 8)))) 308 | (is (= 55 (reduce-chunked + 0 (rseq s)))) 309 | (is (= 35 (reduce-chunked + 0 (set/rslice s 8 2)))))))) 310 | 311 | 312 | #?(:clj 313 | (deftest iter-over-transient 314 | (let [set (transient (into (set/sorted-set) (range 100))) 315 | seq (seq set)] 316 | (conj! set 100) 317 | (is (thrown-with-msg? Exception #"iterating and mutating" (first seq))) 318 | (is (thrown-with-msg? Exception #"iterating and mutating" (next seq))) 319 | (is (thrown-with-msg? Exception #"iterating and mutating" (reduce + seq))) 320 | (is (thrown-with-msg? Exception #"iterating and mutating" (reduce + 0 seq))) 321 | (is (thrown-with-msg? Exception #"iterating and mutating" (chunk-first seq))) 322 | (is (thrown-with-msg? Exception #"iterating and mutating" (chunk-next seq))) 323 | (is (thrown-with-msg? Exception #"iterating and mutating" (.iterator ^Iterable seq)))))) 324 | 325 | (deftest seek-for-seq-test 326 | (let [size 1000 327 | set (apply set/sorted-set (range size)) 328 | set-seq (seq set) 329 | set-rseq (rseq set)] 330 | (testing "simple seek for seq testing" 331 | (doseq [seek-loc (map #(* 100 %) (range 10))] 332 | (is (= seek-loc (first (set/seek set-seq seek-loc))))) 333 | (doseq [seek-loc (map #(* 100 %) (range 10))] 334 | (is (= seek-loc (first (set/seek set-rseq seek-loc)))))) 335 | 336 | (testing "multiple seek testing" 337 | (is (= 500 (-> set-seq (set/seek 250) (set/seek 500) first))) 338 | (is (= 500 (-> set-rseq (set/seek 750) (set/seek 500) first)))) 339 | 340 | (testing "normal seq behaviour after seek" 341 | (is (= (range 500 1000) (-> set-seq (set/seek 250) (set/seek 500)))) 342 | (is (= (range 999 499 -1) (-> set-seq (set/seek 250) (set/seek 500) rseq))) 343 | (is (= (range 500 -1 -1) (-> set-rseq (set/seek 750) (set/seek 500)))) 344 | (is (= (range 0 501) (-> set-rseq (set/seek 750) (set/seek 500) rseq)))) 345 | 346 | #?(:clj 347 | (testing "nil behaviour" 348 | (is (thrown-with-msg? Exception #"seek can't be called with a nil key!" (set/seek set-seq nil))))) 349 | 350 | (testing "slicing together with seek" 351 | (is (= (range 5000 7501) (-> (set/slice (apply set/sorted-set (range 10000)) 2500 7500) 352 | (set/seek 5000)))) 353 | (is (= (list 7500) (-> (set/slice (apply set/sorted-set (range 10000)) 2500 7500) 354 | (set/seek 5000) 355 | (set/seek 7500)))) 356 | (is (= (range 5000 2499 -1) (-> (set/rslice (apply set/sorted-set (range 10000)) 7500 2500) 357 | (set/seek 5000)))) 358 | (is (= (list 2500) (-> (set/rslice (apply set/sorted-set (range 10000)) 7500 2500) 359 | (set/seek 5000) 360 | (set/seek 2500))))))) -------------------------------------------------------------------------------- /src-java/me/tonsky/persistent_sorted_set/Branch.java: -------------------------------------------------------------------------------- 1 | package me.tonsky.persistent_sorted_set; 2 | 3 | import java.lang.ref.*; 4 | import java.util.*; 5 | import java.util.function.*; 6 | import clojure.lang.*; 7 | 8 | @SuppressWarnings("unchecked") 9 | public class Branch extends ANode { 10 | // 1+ for Branches 11 | public final int _level; 12 | 13 | // Nullable, null == no addresses 14 | // Only valid [0 ... _len-1] 15 | public Address[] _addresses; 16 | 17 | // Nullable, null == children not populated yet 18 | // Only valid [0 ... _len-1] 19 | // Object == ANode | SoftReference | WeakReference 20 | public Object[] _children; 21 | 22 | // For i in [0.._len): 23 | // 24 | // 1. Not stored: (_addresses == null || _addresses[i] == null) && _children[i] == ANode 25 | // 2. Stored: _addresses[i] == Object && _children[i] == WeakReference 26 | // 3. Not restored yet: _addresses[i] == Object && (_children == null || _children[i] == null) 27 | 28 | public Branch(int level, int len, Key[] keys, Address[] addresses, Object[] children, Settings settings) { 29 | super(len, keys, settings); 30 | assert level >= 1; 31 | assert addresses == null || addresses.length >= len : ("addresses = " + Arrays.toString(addresses) + ", len = " + len); 32 | assert children == null || children.length >= len; 33 | 34 | _level = level; 35 | _addresses = addresses; 36 | _children = children; 37 | } 38 | 39 | public Branch(int level, int len, Settings settings) { 40 | super(len, (Key[]) new Object[ANode.newLen(len, settings)], settings); 41 | assert level >= 1; 42 | 43 | _level = level; 44 | _addresses = null; 45 | _children = null; 46 | } 47 | 48 | public Branch(int level, List keys, List
addresses, Settings settings) { 49 | this(level, keys.size(), (Key[]) keys.toArray(), (Address[]) addresses.toArray(), null, settings); 50 | } 51 | 52 | protected Address[] ensureAddresses() { 53 | if (_addresses == null) { 54 | _addresses = (Address[]) new Object[_keys.length]; 55 | } 56 | return _addresses; 57 | } 58 | 59 | public List
addresses() { 60 | if (_addresses == null) { 61 | return (List
) Arrays.asList(new Object[_len]); 62 | } else if (_addresses.length == _len) { 63 | return Arrays.asList(_addresses); 64 | } else { 65 | return Arrays.asList(Arrays.copyOfRange(_addresses, 0, _len)); 66 | } 67 | } 68 | 69 | public Address address(int idx) { 70 | assert 0 <= idx && idx < _len; 71 | 72 | if (_addresses == null) { 73 | return null; 74 | } 75 | return _addresses[idx]; 76 | } 77 | 78 | public Address address(int idx, Address address) { 79 | assert 0 <= idx && idx < _len; 80 | 81 | if (_addresses != null || address != null) { 82 | ensureAddresses(); 83 | _addresses[idx] = address; 84 | // if (_children != null) { 85 | // _children[idx] = null; 86 | // } 87 | if (address != null && _children[idx] instanceof ANode) { 88 | _children[idx] = _settings.makeReference(_children[idx]); 89 | } 90 | } 91 | return address; 92 | } 93 | 94 | public ANode child(IStorage storage, int idx) { 95 | assert 0 <= idx && idx < _len; 96 | assert (_children != null && _children[idx] != null) || (_addresses != null && _addresses[idx] != null); 97 | 98 | ANode child = null; 99 | if (_children != null) { 100 | Object ref = _children[idx]; 101 | child = (ANode) _settings.readReference(ref); 102 | } 103 | 104 | if (child == null) { 105 | assert _addresses[idx] != null; 106 | child = storage.restore(_addresses[idx]); 107 | ensureChildren()[idx] = _settings.makeReference(child); 108 | } else { 109 | if (_addresses != null && _addresses[idx] != null) { 110 | storage.accessed(_addresses[idx]); 111 | } 112 | } 113 | return child; 114 | } 115 | 116 | public ANode child(int idx, ANode child) { 117 | address(idx, null); 118 | if (_children != null || child != null) { 119 | ensureChildren(); 120 | _children[idx] = child; 121 | } 122 | return child; 123 | } 124 | 125 | @Override 126 | public int count(IStorage storage) { 127 | int count = 0; 128 | for (int i = 0; i < _len; ++i) { 129 | count += child(storage, i).count(storage); 130 | } 131 | return count; 132 | } 133 | 134 | public int level() { 135 | return _level; 136 | } 137 | 138 | protected Object[] ensureChildren() { 139 | if (_children == null) { 140 | _children = new Object[_keys.length]; 141 | } 142 | return _children; 143 | } 144 | 145 | @Override 146 | public boolean contains(IStorage storage, Key key, Comparator cmp) { 147 | int idx = search(key, cmp); 148 | if (idx >= 0) return true; 149 | int ins = -idx - 1; 150 | if (ins == _len) return false; 151 | assert 0 <= ins && ins < _len; 152 | return child(storage, ins).contains(storage, key, cmp); 153 | } 154 | 155 | @Override 156 | public ANode[] add(IStorage storage, Key key, Comparator cmp, Settings settings) { 157 | int idx = search(key, cmp); 158 | if (idx >= 0) { // already in set 159 | return PersistentSortedSet.UNCHANGED; 160 | } 161 | 162 | int ins = -idx - 1; 163 | if (ins == _len) ins = _len - 1; 164 | assert 0 <= ins && ins < _len; 165 | ANode[] nodes = child(storage, ins).add(storage, key, cmp, settings); 166 | 167 | if (PersistentSortedSet.UNCHANGED == nodes) { // child signalling already in set 168 | return PersistentSortedSet.UNCHANGED; 169 | } 170 | 171 | if (PersistentSortedSet.EARLY_EXIT == nodes) { // child signalling nothing to update 172 | return PersistentSortedSet.EARLY_EXIT; 173 | } 174 | 175 | // same len, editable 176 | if (1 == nodes.length && editable()) { 177 | ANode node = nodes[0]; 178 | _keys[ins] = node.maxKey(); 179 | child(ins, node); 180 | if (ins == _len - 1 && node.maxKey() == maxKey()) // TODO why maxKey check? 181 | return new ANode[]{ this }; // update maxKey 182 | else 183 | return PersistentSortedSet.EARLY_EXIT; 184 | } 185 | 186 | // same len, not editable 187 | if (1 == nodes.length) { 188 | ANode node = nodes[0]; 189 | Key[] newKeys; 190 | if (0 == cmp.compare(node.maxKey(), _keys[ins])) { 191 | newKeys = _keys; 192 | } else { 193 | newKeys = Arrays.copyOfRange(_keys, 0, _len); 194 | newKeys[ins] = node.maxKey(); 195 | } 196 | 197 | Address[] newAddresses = null; 198 | Object[] newChildren = null; 199 | if (node == child(storage, ins)) { // TODO how is this possible? 200 | newAddresses = _addresses; 201 | newChildren = _children; 202 | } else { 203 | if (_addresses != null) { 204 | newAddresses = Arrays.copyOfRange(_addresses, 0, _len); 205 | newAddresses[ins] = null; 206 | } 207 | 208 | newChildren = _children == null ? new Object[_keys.length] : Arrays.copyOfRange(_children, 0, _len); 209 | newChildren[ins] = node; 210 | } 211 | 212 | return new ANode[]{ new Branch(_level, _len, newKeys, newAddresses, newChildren, settings) }; 213 | } 214 | 215 | // len + 1 216 | if (_len < _settings.branchingFactor()) { 217 | Branch n = new Branch(_level, _len + 1, settings); 218 | new Stitch(n._keys, 0) 219 | .copyAll(_keys, 0, ins) 220 | .copyOne(nodes[0].maxKey()) 221 | .copyOne(nodes[1].maxKey()) 222 | .copyAll(_keys, ins + 1, _len); 223 | 224 | if (_addresses != null) { 225 | n.ensureAddresses(); 226 | new Stitch(n._addresses, 0) 227 | .copyAll(_addresses, 0, ins) 228 | .copyOne(null) 229 | .copyOne(null) 230 | .copyAll(_addresses, ins + 1, _len); 231 | } 232 | 233 | n.ensureChildren(); 234 | new Stitch(n._children, 0) 235 | .copyAll(_children, 0, ins) 236 | .copyOne(nodes[0]) 237 | .copyOne(nodes[1]) 238 | .copyAll(_children, ins + 1, _len); 239 | 240 | return new ANode[]{n}; 241 | } 242 | 243 | // split 244 | int half1 = (_len + 1) >>> 1; 245 | if (ins+1 == half1) ++half1; 246 | int half2 = _len + 1 - half1; 247 | 248 | // add to first half 249 | if (ins < half1) { 250 | Key[] keys1 = (Key[]) new Object[half1]; 251 | new Stitch(keys1, 0) 252 | .copyAll(_keys, 0, ins) 253 | .copyOne(nodes[0].maxKey()) 254 | .copyOne(nodes[1].maxKey()) 255 | .copyAll(_keys, ins+1, half1-1); 256 | Key[] keys2 = (Key[]) new Object[half2]; 257 | ArrayUtil.copy(_keys, half1 - 1, _len, keys2, 0); 258 | 259 | Address[] addresses1 = null; 260 | Address[] addresses2 = null; 261 | if (_addresses != null) { 262 | addresses1 = (Address[]) new Object[half1]; 263 | new Stitch(addresses1, 0) 264 | .copyAll(_addresses, 0, ins) 265 | .copyOne(null) 266 | .copyOne(null) 267 | .copyAll(_addresses, ins + 1, half1 - 1); 268 | addresses2 = (Address[]) new Object[half2]; 269 | ArrayUtil.copy(_addresses, half1 - 1, _len, addresses2, 0); 270 | } 271 | 272 | Object[] children1 = new Object[half1]; 273 | Object[] children2 = null; 274 | new Stitch(children1, 0) 275 | .copyAll(_children, 0, ins) 276 | .copyOne(nodes[0]) 277 | .copyOne(nodes[1]) 278 | .copyAll(_children, ins + 1, half1 - 1); 279 | if (_children != null) { 280 | children2 = new Object[half2]; 281 | ArrayUtil.copy(_children, half1 - 1, _len, children2, 0); 282 | } 283 | 284 | return new ANode[] { 285 | new Branch(_level, half1, keys1, addresses1, children1, settings), 286 | new Branch(_level, half2, keys2, addresses2, children2, settings) 287 | }; 288 | } 289 | 290 | // add to second half 291 | Key[] keys1 = (Key[]) new Object[half1]; 292 | Key[] keys2 = (Key[]) new Object[half2]; 293 | ArrayUtil.copy(_keys, 0, half1, keys1, 0); 294 | 295 | new Stitch(keys2, 0) 296 | .copyAll(_keys, half1, ins) 297 | .copyOne(nodes[0].maxKey()) 298 | .copyOne(nodes[1].maxKey()) 299 | .copyAll(_keys, ins + 1, _len); 300 | 301 | Address addresses1[] = null; 302 | Address addresses2[] = null; 303 | if (_addresses != null) { 304 | addresses1 = (Address[]) new Object[half1]; 305 | ArrayUtil.copy(_addresses, 0, half1, addresses1, 0); 306 | addresses2 = (Address[]) new Object[half2]; 307 | new Stitch(addresses2, 0) 308 | .copyAll(_addresses, half1, ins) 309 | .copyOne(null) 310 | .copyOne(null) 311 | .copyAll(_addresses, ins + 1, _len); 312 | } 313 | 314 | Object[] children1 = null; 315 | Object[] children2 = new Object[half2]; 316 | if (_children != null) { 317 | children1 = new Object[half1]; 318 | ArrayUtil.copy(_children, 0, half1, children1, 0); 319 | } 320 | new Stitch(children2, 0) 321 | .copyAll(_children, half1, ins) 322 | .copyOne(nodes[0]) 323 | .copyOne(nodes[1]) 324 | .copyAll(_children, ins + 1, _len); 325 | 326 | return new ANode[]{ 327 | new Branch(_level, half1, keys1, addresses1, children1, settings), 328 | new Branch(_level, half2, keys2, addresses2, children2, settings) 329 | }; 330 | } 331 | 332 | @Override 333 | public ANode[] remove(IStorage storage, Key key, ANode _left, ANode _right, Comparator cmp, Settings settings) { 334 | Branch left = (Branch) _left; 335 | Branch right = (Branch) _right; 336 | 337 | int idx = search(key, cmp); 338 | if (idx < 0) idx = -idx - 1; 339 | 340 | if (idx == _len) // not in set 341 | return PersistentSortedSet.UNCHANGED; 342 | 343 | assert 0 <= idx && idx < _len; 344 | 345 | ANode leftChild = idx > 0 ? child(storage, idx - 1) : null, 346 | rightChild = idx < _len-1 ? child(storage, idx + 1) : null; 347 | int leftChildLen = safeLen(leftChild); 348 | int rightChildLen = safeLen(rightChild); 349 | ANode[] nodes = child(storage, idx).remove(storage, key, leftChild, rightChild, cmp, settings); 350 | 351 | if (PersistentSortedSet.UNCHANGED == nodes) // child signalling element not in set 352 | return PersistentSortedSet.UNCHANGED; 353 | 354 | if (PersistentSortedSet.EARLY_EXIT == nodes) { // child signalling nothing to update 355 | return PersistentSortedSet.EARLY_EXIT; 356 | } 357 | 358 | boolean leftChanged = leftChild != nodes[0] || leftChildLen != safeLen(nodes[0]); 359 | boolean rightChanged = rightChild != nodes[2] || rightChildLen != safeLen(nodes[2]); 360 | 361 | // nodes[1] always not nil 362 | int newLen = _len - 1 363 | - (leftChild != null ? 1 : 0) 364 | - (rightChild != null ? 1 : 0) 365 | + (nodes[0] != null ? 1 : 0) 366 | + 1 367 | + (nodes[2] != null ? 1 : 0); 368 | 369 | // no rebalance needed 370 | if (newLen >= _settings.minBranchingFactor() || (left == null && right == null)) { 371 | // can update in place 372 | if (editable() && idx < _len-2) { 373 | Stitch ks = new Stitch(_keys, Math.max(idx-1, 0)); 374 | if (nodes[0] != null) ks.copyOne(nodes[0].maxKey()); 375 | ks.copyOne(nodes[1].maxKey()); 376 | if (nodes[2] != null) ks.copyOne(nodes[2].maxKey()); 377 | if (newLen != _len) 378 | ks.copyAll(_keys, idx+2, _len); 379 | 380 | if (_addresses != null) { 381 | Stitch as = new Stitch(_addresses, Math.max(idx - 1, 0)); 382 | if (nodes[0] != null) as.copyOne(leftChanged ? null : address(idx - 1)); 383 | as.copyOne(null); 384 | if (nodes[2] != null) as.copyOne(rightChanged ? null : address(idx + 1)); 385 | if (newLen != _len) 386 | as.copyAll(_addresses, idx+2, _len); 387 | } 388 | 389 | ensureChildren(); 390 | Stitch cs = new Stitch(_children, Math.max(idx - 1, 0)); 391 | if (nodes[0] != null) cs.copyOne(nodes[0]); 392 | cs.copyOne(nodes[1]); 393 | if (nodes[2] != null) cs.copyOne(nodes[2]); 394 | if (newLen != _len) 395 | cs.copyAll(_children, idx+2, _len); 396 | 397 | _len = newLen; 398 | return PersistentSortedSet.EARLY_EXIT; 399 | } 400 | 401 | Branch newCenter = new Branch(_level, newLen, settings); 402 | 403 | Stitch ks = new Stitch(newCenter._keys, 0); 404 | ks.copyAll(_keys, 0, idx - 1); 405 | if (nodes[0] != null) ks.copyOne(nodes[0].maxKey()); 406 | ks.copyOne(nodes[1].maxKey()); 407 | if (nodes[2] != null) ks.copyOne(nodes[2].maxKey()); 408 | ks.copyAll(_keys, idx + 2, _len); 409 | 410 | if (_addresses != null) { 411 | Stitch as = new Stitch(newCenter.ensureAddresses(), 0); 412 | as.copyAll(_addresses, 0, idx - 1); 413 | if (nodes[0] != null) as.copyOne(leftChanged ? null : address(idx - 1)); 414 | as.copyOne(null); 415 | if (nodes[2] != null) as.copyOne(rightChanged ? null : address(idx + 1)); 416 | as.copyAll(_addresses, idx + 2, _len); 417 | } 418 | 419 | newCenter.ensureChildren(); 420 | Stitch cs = new Stitch(newCenter._children, 0); 421 | cs.copyAll(_children, 0, idx - 1); 422 | if (nodes[0] != null) cs.copyOne(nodes[0]); 423 | cs.copyOne(nodes[1]); 424 | if (nodes[2] != null) cs.copyOne(nodes[2]); 425 | cs.copyAll(_children, idx + 2, _len); 426 | 427 | return new ANode[] { left, newCenter, right }; 428 | } 429 | 430 | // can join with left 431 | if (left != null && left._len + newLen <= _settings.branchingFactor()) { 432 | Branch join = new Branch(_level, left._len + newLen, settings); 433 | 434 | Stitch ks = new Stitch(join._keys, 0); 435 | ks.copyAll(left._keys, 0, left._len); 436 | ks.copyAll(_keys, 0, idx - 1); 437 | if (nodes[0] != null) ks.copyOne(nodes[0].maxKey()); 438 | ks.copyOne(nodes[1].maxKey()); 439 | if (nodes[2] != null) ks.copyOne(nodes[2].maxKey()); 440 | ks.copyAll(_keys, idx + 2, _len); 441 | 442 | if (left._addresses != null || _addresses != null) { 443 | Stitch as = new Stitch(join.ensureAddresses(), 0); 444 | as.copyAll(left._addresses, 0, left._len); 445 | as.copyAll(_addresses, 0, idx - 1); 446 | if (nodes[0] != null) as.copyOne(leftChanged ? null : address(idx - 1)); 447 | as.copyOne(null); 448 | if (nodes[2] != null) as.copyOne(rightChanged ? null : address(idx + 1)); 449 | as.copyAll(_addresses, idx + 2, _len); 450 | } 451 | 452 | join.ensureChildren(); 453 | Stitch cs = new Stitch(join._children, 0); 454 | cs.copyAll(left._children, 0, left._len); 455 | cs.copyAll(_children, 0, idx - 1); 456 | if (nodes[0] != null) cs.copyOne(nodes[0]); 457 | cs.copyOne(nodes[1]); 458 | if (nodes[2] != null) cs.copyOne(nodes[2]); 459 | cs.copyAll(_children, idx + 2, _len); 460 | 461 | return new ANode[] { null, join, right }; 462 | } 463 | 464 | // can join with right 465 | if (right != null && newLen + right._len <= _settings.branchingFactor()) { 466 | Branch join = new Branch(_level, newLen + right._len, settings); 467 | 468 | Stitch ks = new Stitch(join._keys, 0); 469 | ks.copyAll(_keys, 0, idx - 1); 470 | if (nodes[0] != null) ks.copyOne(nodes[0].maxKey()); 471 | ks.copyOne(nodes[1].maxKey()); 472 | if (nodes[2] != null) ks.copyOne(nodes[2].maxKey()); 473 | ks.copyAll(_keys, idx + 2, _len); 474 | ks.copyAll(right._keys, 0, right._len); 475 | 476 | if (_addresses != null || right._addresses != null) { 477 | Stitch as = new Stitch(join.ensureAddresses(), 0); 478 | as.copyAll(_addresses, 0, idx - 1); 479 | if (nodes[0] != null) as.copyOne(leftChanged ? null : address(idx - 1)); 480 | as.copyOne(null); 481 | if (nodes[2] != null) as.copyOne(rightChanged ? null : address(idx + 1)); 482 | as.copyAll(_addresses, idx + 2, _len); 483 | as.copyAll(right._addresses, 0, right._len); 484 | } 485 | 486 | join.ensureChildren(); 487 | Stitch cs = new Stitch(join._children, 0); 488 | cs.copyAll(_children, 0, idx - 1); 489 | if (nodes[0] != null) cs.copyOne(nodes[0]); 490 | cs.copyOne(nodes[1]); 491 | if (nodes[2] != null) cs.copyOne(nodes[2]); 492 | cs.copyAll(_children, idx + 2, _len); 493 | cs.copyAll(right._children, 0, right._len); 494 | 495 | return new ANode[] { left, join, null }; 496 | } 497 | 498 | // borrow from left 499 | if (left != null && (right == null || left._len >= right._len)) { 500 | int totalLen = left._len + newLen; 501 | int newLeftLen = totalLen >>> 1; 502 | int newCenterLen = totalLen - newLeftLen; 503 | 504 | Branch newLeft = new Branch(_level, newLeftLen, settings); 505 | Branch newCenter = new Branch(_level, newCenterLen, settings); 506 | 507 | ArrayUtil.copy(left._keys, 0, newLeftLen, newLeft._keys, 0); 508 | 509 | Stitch ks = new Stitch(newCenter._keys, 0); 510 | ks.copyAll(left._keys, newLeftLen, left._len); 511 | ks.copyAll(_keys, 0, idx - 1); 512 | if (nodes[0] != null) ks.copyOne(nodes[0].maxKey()); 513 | ks.copyOne(nodes[1].maxKey()); 514 | if (nodes[2] != null) ks.copyOne(nodes[2].maxKey()); 515 | ks.copyAll(_keys, idx + 2, _len); 516 | 517 | if (left._addresses != null) { 518 | ArrayUtil.copy(left._addresses, 0, newLeftLen, newLeft.ensureAddresses(), 0); 519 | } 520 | if (left._children != null) { 521 | ArrayUtil.copy(left._children, 0, newLeftLen, newLeft.ensureChildren(), 0); 522 | } 523 | 524 | if (left._addresses != null || _addresses != null) { 525 | Stitch as = new Stitch(newCenter.ensureAddresses(), 0); 526 | as.copyAll(left._addresses, newLeftLen, left._len); 527 | as.copyAll(_addresses, 0, idx - 1); 528 | if (nodes[0] != null) as.copyOne(leftChanged ? null : address(idx - 1)); 529 | as.copyOne(null); 530 | if (nodes[2] != null) as.copyOne(rightChanged ? null : address(idx + 1)); 531 | as.copyAll(_addresses, idx + 2, _len); 532 | } 533 | 534 | newCenter.ensureChildren(); 535 | Stitch cs = new Stitch(newCenter._children, 0); 536 | cs.copyAll(left._children, newLeftLen, left._len); 537 | cs.copyAll(_children, 0, idx - 1); 538 | if (nodes[0] != null) cs.copyOne(nodes[0]); 539 | cs.copyOne(nodes[1]); 540 | if (nodes[2] != null) cs.copyOne(nodes[2]); 541 | cs.copyAll(_children, idx + 2, _len); 542 | 543 | return new ANode[] { newLeft, newCenter, right }; 544 | } 545 | 546 | // borrow from right 547 | if (right != null) { 548 | int totalLen = newLen + right._len, 549 | newCenterLen = totalLen >>> 1, 550 | newRightLen = totalLen - newCenterLen, 551 | rightHead = right._len - newRightLen; 552 | 553 | Branch newCenter = new Branch(_level, newCenterLen, settings), 554 | newRight = new Branch(_level, newRightLen, settings); 555 | 556 | Stitch ks = new Stitch(newCenter._keys, 0); 557 | ks.copyAll(_keys, 0, idx - 1); 558 | if (nodes[0] != null) ks.copyOne(nodes[0].maxKey()); 559 | ks.copyOne(nodes[1].maxKey()); 560 | if (nodes[2] != null) ks.copyOne(nodes[2].maxKey()); 561 | ks.copyAll(_keys, idx + 2, _len); 562 | ks.copyAll(right._keys, 0, rightHead); 563 | 564 | ArrayUtil.copy(right._keys, rightHead, right._len, newRight._keys, 0); 565 | 566 | if (_addresses != null || right._addresses != null) { 567 | Stitch as = new Stitch(newCenter.ensureAddresses(), 0); 568 | as.copyAll(_addresses, 0, idx - 1); 569 | if (nodes[0] != null) as.copyOne(leftChanged ? null : address(idx - 1)); 570 | as.copyOne(null); 571 | if (nodes[2] != null) as.copyOne(rightChanged ? null : address(idx + 1)); 572 | as.copyAll(_addresses, idx + 2, _len); 573 | as.copyAll(right._addresses, 0, rightHead); 574 | } 575 | 576 | newCenter.ensureChildren(); 577 | Stitch cs = new Stitch(newCenter._children, 0); 578 | cs.copyAll(_children, 0, idx - 1); 579 | if (nodes[0] != null) cs.copyOne(nodes[0]); 580 | cs.copyOne(nodes[1]); 581 | if (nodes[2] != null) cs.copyOne(nodes[2]); 582 | cs.copyAll(_children, idx + 2, _len); 583 | cs.copyAll(right._children, 0, rightHead); 584 | 585 | if (right._addresses != null) { 586 | ArrayUtil.copy(right._addresses, rightHead, right._len, newRight.ensureAddresses(), 0); 587 | } 588 | if (right._children != null) { 589 | ArrayUtil.copy(right._children, rightHead, right._len, newRight.ensureChildren(), 0); 590 | } 591 | 592 | return new ANode[] { left, newCenter, newRight }; 593 | } 594 | 595 | throw new RuntimeException("Unreachable"); 596 | } 597 | 598 | @Override 599 | public void walkAddresses(IStorage storage, IFn onAddress) { 600 | for (int i = 0; i < _len; ++i) { 601 | Address address = address(i); 602 | if (address != null) { 603 | if (!RT.booleanCast(onAddress.invoke(address))) { 604 | continue; 605 | } 606 | } 607 | if (_level > 1) { 608 | child(storage, i).walkAddresses(storage, onAddress); 609 | } 610 | } 611 | } 612 | 613 | @Override 614 | public Address store(IStorage storage) { 615 | ensureAddresses(); 616 | for (int i = 0; i < _len; ++i) { 617 | if (_addresses[i] == null) { 618 | assert _children != null; 619 | assert _children[i] != null; 620 | assert _children[i] instanceof ANode; 621 | address(i, ((ANode) _children[i]).store(storage)); 622 | } 623 | } 624 | return storage.store(this); 625 | } 626 | 627 | public String str(IStorage storage, int lvl) { 628 | StringBuilder sb = new StringBuilder(); 629 | for (int i = 0; i < _len; ++i) { 630 | sb.append("\n"); 631 | for (int j = 0; j < lvl; ++j) 632 | sb.append("| "); 633 | sb.append(_keys[i] + ": " + child(storage, i).str(storage, lvl+1)); 634 | } 635 | return sb.toString(); 636 | } 637 | 638 | @Override 639 | public void toString(StringBuilder sb, Address address, String indent) { 640 | sb.append(indent); 641 | sb.append("Branch addr: " + address + " len: " + _len + " "); 642 | for (int i = 0; i < _len; ++i) { 643 | sb.append("\n"); 644 | ANode child = null; 645 | if (_children != null) { 646 | Object ref = _children[i]; 647 | if (ref != null) { 648 | child = (ANode) _settings.readReference(ref); 649 | } 650 | } 651 | if (child != null) 652 | child.toString(sb, address(i), indent + " "); 653 | else 654 | sb.append(indent + " " + address(i) + ": "); 655 | } 656 | } 657 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asn1.js@^5.2.0: 6 | version "5.4.1" 7 | resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" 8 | integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== 9 | dependencies: 10 | bn.js "^4.0.0" 11 | inherits "^2.0.1" 12 | minimalistic-assert "^1.0.0" 13 | safer-buffer "^2.1.0" 14 | 15 | assert@^1.1.1: 16 | version "1.5.0" 17 | resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" 18 | integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== 19 | dependencies: 20 | object-assign "^4.1.1" 21 | util "0.10.3" 22 | 23 | base64-js@^1.0.2: 24 | version "1.5.1" 25 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 26 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 27 | 28 | bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: 29 | version "4.12.0" 30 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" 31 | integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== 32 | 33 | bn.js@^5.0.0, bn.js@^5.1.1: 34 | version "5.2.1" 35 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" 36 | integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== 37 | 38 | brorand@^1.0.1, brorand@^1.1.0: 39 | version "1.1.0" 40 | resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" 41 | integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== 42 | 43 | browserify-aes@^1.0.0, browserify-aes@^1.0.4: 44 | version "1.2.0" 45 | resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" 46 | integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== 47 | dependencies: 48 | buffer-xor "^1.0.3" 49 | cipher-base "^1.0.0" 50 | create-hash "^1.1.0" 51 | evp_bytestokey "^1.0.3" 52 | inherits "^2.0.1" 53 | safe-buffer "^5.0.1" 54 | 55 | browserify-cipher@^1.0.0: 56 | version "1.0.1" 57 | resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" 58 | integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== 59 | dependencies: 60 | browserify-aes "^1.0.4" 61 | browserify-des "^1.0.0" 62 | evp_bytestokey "^1.0.0" 63 | 64 | browserify-des@^1.0.0: 65 | version "1.0.2" 66 | resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" 67 | integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== 68 | dependencies: 69 | cipher-base "^1.0.1" 70 | des.js "^1.0.0" 71 | inherits "^2.0.1" 72 | safe-buffer "^5.1.2" 73 | 74 | browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: 75 | version "4.1.0" 76 | resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" 77 | integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== 78 | dependencies: 79 | bn.js "^5.0.0" 80 | randombytes "^2.0.1" 81 | 82 | browserify-sign@^4.0.0: 83 | version "4.2.1" 84 | resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" 85 | integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== 86 | dependencies: 87 | bn.js "^5.1.1" 88 | browserify-rsa "^4.0.1" 89 | create-hash "^1.2.0" 90 | create-hmac "^1.1.7" 91 | elliptic "^6.5.3" 92 | inherits "^2.0.4" 93 | parse-asn1 "^5.1.5" 94 | readable-stream "^3.6.0" 95 | safe-buffer "^5.2.0" 96 | 97 | browserify-zlib@^0.2.0: 98 | version "0.2.0" 99 | resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" 100 | integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== 101 | dependencies: 102 | pako "~1.0.5" 103 | 104 | buffer-from@^1.0.0: 105 | version "1.1.2" 106 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 107 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 108 | 109 | buffer-xor@^1.0.3: 110 | version "1.0.3" 111 | resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" 112 | integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== 113 | 114 | buffer@^4.3.0: 115 | version "4.9.2" 116 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" 117 | integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== 118 | dependencies: 119 | base64-js "^1.0.2" 120 | ieee754 "^1.1.4" 121 | isarray "^1.0.0" 122 | 123 | builtin-status-codes@^3.0.0: 124 | version "3.0.0" 125 | resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" 126 | integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== 127 | 128 | cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: 129 | version "1.0.4" 130 | resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" 131 | integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== 132 | dependencies: 133 | inherits "^2.0.1" 134 | safe-buffer "^5.0.1" 135 | 136 | console-browserify@^1.1.0: 137 | version "1.2.0" 138 | resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" 139 | integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== 140 | 141 | constants-browserify@^1.0.0: 142 | version "1.0.0" 143 | resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" 144 | integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== 145 | 146 | core-util-is@~1.0.0: 147 | version "1.0.3" 148 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" 149 | integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== 150 | 151 | create-ecdh@^4.0.0: 152 | version "4.0.4" 153 | resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" 154 | integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== 155 | dependencies: 156 | bn.js "^4.1.0" 157 | elliptic "^6.5.3" 158 | 159 | create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: 160 | version "1.2.0" 161 | resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" 162 | integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== 163 | dependencies: 164 | cipher-base "^1.0.1" 165 | inherits "^2.0.1" 166 | md5.js "^1.3.4" 167 | ripemd160 "^2.0.1" 168 | sha.js "^2.4.0" 169 | 170 | create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: 171 | version "1.1.7" 172 | resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" 173 | integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== 174 | dependencies: 175 | cipher-base "^1.0.3" 176 | create-hash "^1.1.0" 177 | inherits "^2.0.1" 178 | ripemd160 "^2.0.0" 179 | safe-buffer "^5.0.1" 180 | sha.js "^2.4.8" 181 | 182 | crypto-browserify@^3.11.0: 183 | version "3.12.0" 184 | resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" 185 | integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== 186 | dependencies: 187 | browserify-cipher "^1.0.0" 188 | browserify-sign "^4.0.0" 189 | create-ecdh "^4.0.0" 190 | create-hash "^1.1.0" 191 | create-hmac "^1.1.0" 192 | diffie-hellman "^5.0.0" 193 | inherits "^2.0.1" 194 | pbkdf2 "^3.0.3" 195 | public-encrypt "^4.0.0" 196 | randombytes "^2.0.0" 197 | randomfill "^1.0.3" 198 | 199 | des.js@^1.0.0: 200 | version "1.0.1" 201 | resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" 202 | integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== 203 | dependencies: 204 | inherits "^2.0.1" 205 | minimalistic-assert "^1.0.0" 206 | 207 | diffie-hellman@^5.0.0: 208 | version "5.0.3" 209 | resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" 210 | integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== 211 | dependencies: 212 | bn.js "^4.1.0" 213 | miller-rabin "^4.0.0" 214 | randombytes "^2.0.0" 215 | 216 | domain-browser@^1.1.1: 217 | version "1.2.0" 218 | resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" 219 | integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== 220 | 221 | elliptic@^6.5.3: 222 | version "6.5.4" 223 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" 224 | integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== 225 | dependencies: 226 | bn.js "^4.11.9" 227 | brorand "^1.1.0" 228 | hash.js "^1.0.0" 229 | hmac-drbg "^1.0.1" 230 | inherits "^2.0.4" 231 | minimalistic-assert "^1.0.1" 232 | minimalistic-crypto-utils "^1.0.1" 233 | 234 | events@^3.0.0: 235 | version "3.3.0" 236 | resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" 237 | integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== 238 | 239 | evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: 240 | version "1.0.3" 241 | resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" 242 | integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== 243 | dependencies: 244 | md5.js "^1.3.4" 245 | safe-buffer "^5.1.1" 246 | 247 | hash-base@^3.0.0: 248 | version "3.1.0" 249 | resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" 250 | integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== 251 | dependencies: 252 | inherits "^2.0.4" 253 | readable-stream "^3.6.0" 254 | safe-buffer "^5.2.0" 255 | 256 | hash.js@^1.0.0, hash.js@^1.0.3: 257 | version "1.1.7" 258 | resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" 259 | integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== 260 | dependencies: 261 | inherits "^2.0.3" 262 | minimalistic-assert "^1.0.1" 263 | 264 | hmac-drbg@^1.0.1: 265 | version "1.0.1" 266 | resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" 267 | integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== 268 | dependencies: 269 | hash.js "^1.0.3" 270 | minimalistic-assert "^1.0.0" 271 | minimalistic-crypto-utils "^1.0.1" 272 | 273 | https-browserify@^1.0.0: 274 | version "1.0.0" 275 | resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" 276 | integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== 277 | 278 | ieee754@^1.1.4: 279 | version "1.2.1" 280 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 281 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 282 | 283 | inherits@2.0.1: 284 | version "2.0.1" 285 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" 286 | integrity sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA== 287 | 288 | inherits@2.0.3: 289 | version "2.0.3" 290 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 291 | integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== 292 | 293 | inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: 294 | version "2.0.4" 295 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 296 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 297 | 298 | isarray@^1.0.0, isarray@~1.0.0: 299 | version "1.0.0" 300 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 301 | integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== 302 | 303 | isexe@^2.0.0: 304 | version "2.0.0" 305 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 306 | integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== 307 | 308 | md5.js@^1.3.4: 309 | version "1.3.5" 310 | resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" 311 | integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== 312 | dependencies: 313 | hash-base "^3.0.0" 314 | inherits "^2.0.1" 315 | safe-buffer "^5.1.2" 316 | 317 | miller-rabin@^4.0.0: 318 | version "4.0.1" 319 | resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" 320 | integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== 321 | dependencies: 322 | bn.js "^4.0.0" 323 | brorand "^1.0.1" 324 | 325 | minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: 326 | version "1.0.1" 327 | resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" 328 | integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== 329 | 330 | minimalistic-crypto-utils@^1.0.1: 331 | version "1.0.1" 332 | resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" 333 | integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== 334 | 335 | node-libs-browser@^2.2.1: 336 | version "2.2.1" 337 | resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" 338 | integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== 339 | dependencies: 340 | assert "^1.1.1" 341 | browserify-zlib "^0.2.0" 342 | buffer "^4.3.0" 343 | console-browserify "^1.1.0" 344 | constants-browserify "^1.0.0" 345 | crypto-browserify "^3.11.0" 346 | domain-browser "^1.1.1" 347 | events "^3.0.0" 348 | https-browserify "^1.0.0" 349 | os-browserify "^0.3.0" 350 | path-browserify "0.0.1" 351 | process "^0.11.10" 352 | punycode "^1.2.4" 353 | querystring-es3 "^0.2.0" 354 | readable-stream "^2.3.3" 355 | stream-browserify "^2.0.1" 356 | stream-http "^2.7.2" 357 | string_decoder "^1.0.0" 358 | timers-browserify "^2.0.4" 359 | tty-browserify "0.0.0" 360 | url "^0.11.0" 361 | util "^0.11.0" 362 | vm-browserify "^1.0.1" 363 | 364 | object-assign@^4.1.1: 365 | version "4.1.1" 366 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 367 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 368 | 369 | os-browserify@^0.3.0: 370 | version "0.3.0" 371 | resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" 372 | integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== 373 | 374 | pako@~1.0.5: 375 | version "1.0.11" 376 | resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" 377 | integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== 378 | 379 | parse-asn1@^5.0.0, parse-asn1@^5.1.5: 380 | version "5.1.6" 381 | resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" 382 | integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== 383 | dependencies: 384 | asn1.js "^5.2.0" 385 | browserify-aes "^1.0.0" 386 | evp_bytestokey "^1.0.0" 387 | pbkdf2 "^3.0.3" 388 | safe-buffer "^5.1.1" 389 | 390 | path-browserify@0.0.1: 391 | version "0.0.1" 392 | resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" 393 | integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== 394 | 395 | pbkdf2@^3.0.3: 396 | version "3.1.2" 397 | resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" 398 | integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== 399 | dependencies: 400 | create-hash "^1.1.2" 401 | create-hmac "^1.1.4" 402 | ripemd160 "^2.0.1" 403 | safe-buffer "^5.0.1" 404 | sha.js "^2.4.8" 405 | 406 | process-nextick-args@~2.0.0: 407 | version "2.0.1" 408 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 409 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 410 | 411 | process@^0.11.10: 412 | version "0.11.10" 413 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 414 | integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== 415 | 416 | public-encrypt@^4.0.0: 417 | version "4.0.3" 418 | resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" 419 | integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== 420 | dependencies: 421 | bn.js "^4.1.0" 422 | browserify-rsa "^4.0.0" 423 | create-hash "^1.1.0" 424 | parse-asn1 "^5.0.0" 425 | randombytes "^2.0.1" 426 | safe-buffer "^5.1.2" 427 | 428 | punycode@1.3.2: 429 | version "1.3.2" 430 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" 431 | integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== 432 | 433 | punycode@^1.2.4: 434 | version "1.4.1" 435 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 436 | integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== 437 | 438 | querystring-es3@^0.2.0: 439 | version "0.2.1" 440 | resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" 441 | integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== 442 | 443 | querystring@0.2.0: 444 | version "0.2.0" 445 | resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" 446 | integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== 447 | 448 | randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: 449 | version "2.1.0" 450 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 451 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 452 | dependencies: 453 | safe-buffer "^5.1.0" 454 | 455 | randomfill@^1.0.3: 456 | version "1.0.4" 457 | resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" 458 | integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== 459 | dependencies: 460 | randombytes "^2.0.5" 461 | safe-buffer "^5.1.0" 462 | 463 | readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6: 464 | version "2.3.7" 465 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 466 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 467 | dependencies: 468 | core-util-is "~1.0.0" 469 | inherits "~2.0.3" 470 | isarray "~1.0.0" 471 | process-nextick-args "~2.0.0" 472 | safe-buffer "~5.1.1" 473 | string_decoder "~1.1.1" 474 | util-deprecate "~1.0.1" 475 | 476 | readable-stream@^3.6.0: 477 | version "3.6.0" 478 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 479 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 480 | dependencies: 481 | inherits "^2.0.3" 482 | string_decoder "^1.1.1" 483 | util-deprecate "^1.0.1" 484 | 485 | readline-sync@^1.4.7: 486 | version "1.4.10" 487 | resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" 488 | integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== 489 | 490 | ripemd160@^2.0.0, ripemd160@^2.0.1: 491 | version "2.0.2" 492 | resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" 493 | integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== 494 | dependencies: 495 | hash-base "^3.0.0" 496 | inherits "^2.0.1" 497 | 498 | safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: 499 | version "5.2.1" 500 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 501 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 502 | 503 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 504 | version "5.1.2" 505 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 506 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 507 | 508 | safer-buffer@^2.1.0: 509 | version "2.1.2" 510 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 511 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 512 | 513 | setimmediate@^1.0.4: 514 | version "1.0.5" 515 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 516 | integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== 517 | 518 | sha.js@^2.4.0, sha.js@^2.4.8: 519 | version "2.4.11" 520 | resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" 521 | integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== 522 | dependencies: 523 | inherits "^2.0.1" 524 | safe-buffer "^5.0.1" 525 | 526 | shadow-cljs-jar@1.3.2: 527 | version "1.3.2" 528 | resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" 529 | integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== 530 | 531 | shadow-cljs@^2.20.12: 532 | version "2.20.12" 533 | resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.20.12.tgz#5de83fc507f181b7c8c6c76fa53e70f47720d737" 534 | integrity sha512-obFIENp1QgsMldDYye1sMRlIo++bYw+7UGor5cZvYgViXaegoeXBB+O5B5SI5Yg80CpKCGB7DIkAi9qbGjsLoQ== 535 | dependencies: 536 | node-libs-browser "^2.2.1" 537 | readline-sync "^1.4.7" 538 | shadow-cljs-jar "1.3.2" 539 | source-map-support "^0.4.15" 540 | which "^1.3.1" 541 | ws "^7.4.6" 542 | 543 | source-map-support@^0.4.15: 544 | version "0.4.18" 545 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" 546 | integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== 547 | dependencies: 548 | source-map "^0.5.6" 549 | 550 | source-map-support@^0.5.21: 551 | version "0.5.21" 552 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 553 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 554 | dependencies: 555 | buffer-from "^1.0.0" 556 | source-map "^0.6.0" 557 | 558 | source-map@^0.5.6: 559 | version "0.5.7" 560 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 561 | integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== 562 | 563 | source-map@^0.6.0: 564 | version "0.6.1" 565 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 566 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 567 | 568 | stream-browserify@^2.0.1: 569 | version "2.0.2" 570 | resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" 571 | integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== 572 | dependencies: 573 | inherits "~2.0.1" 574 | readable-stream "^2.0.2" 575 | 576 | stream-http@^2.7.2: 577 | version "2.8.3" 578 | resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" 579 | integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== 580 | dependencies: 581 | builtin-status-codes "^3.0.0" 582 | inherits "^2.0.1" 583 | readable-stream "^2.3.6" 584 | to-arraybuffer "^1.0.0" 585 | xtend "^4.0.0" 586 | 587 | string_decoder@^1.0.0, string_decoder@^1.1.1: 588 | version "1.3.0" 589 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 590 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 591 | dependencies: 592 | safe-buffer "~5.2.0" 593 | 594 | string_decoder@~1.1.1: 595 | version "1.1.1" 596 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 597 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 598 | dependencies: 599 | safe-buffer "~5.1.0" 600 | 601 | timers-browserify@^2.0.4: 602 | version "2.0.12" 603 | resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" 604 | integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== 605 | dependencies: 606 | setimmediate "^1.0.4" 607 | 608 | to-arraybuffer@^1.0.0: 609 | version "1.0.1" 610 | resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" 611 | integrity sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA== 612 | 613 | tty-browserify@0.0.0: 614 | version "0.0.0" 615 | resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" 616 | integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw== 617 | 618 | url@^0.11.0: 619 | version "0.11.0" 620 | resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" 621 | integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== 622 | dependencies: 623 | punycode "1.3.2" 624 | querystring "0.2.0" 625 | 626 | util-deprecate@^1.0.1, util-deprecate@~1.0.1: 627 | version "1.0.2" 628 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 629 | integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 630 | 631 | util@0.10.3: 632 | version "0.10.3" 633 | resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" 634 | integrity sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ== 635 | dependencies: 636 | inherits "2.0.1" 637 | 638 | util@^0.11.0: 639 | version "0.11.1" 640 | resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" 641 | integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== 642 | dependencies: 643 | inherits "2.0.3" 644 | 645 | vm-browserify@^1.0.1: 646 | version "1.1.2" 647 | resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" 648 | integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== 649 | 650 | which@^1.3.1: 651 | version "1.3.1" 652 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 653 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 654 | dependencies: 655 | isexe "^2.0.0" 656 | 657 | ws@^7.4.6: 658 | version "7.5.9" 659 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" 660 | integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== 661 | 662 | xtend@^4.0.0: 663 | version "4.0.2" 664 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 665 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 666 | -------------------------------------------------------------------------------- /src-clojure/me/tonsky/persistent_sorted_set.cljs: -------------------------------------------------------------------------------- 1 | (ns ^{:doc 2 | "A B-tree based persistent sorted set. Supports transients, custom comparators, fast iteration, efficient slices (iterator over a part of the set) and reverse slices. Almost a drop-in replacement for [[clojure.core/sorted-set]], the only difference being this one can’t store nil." 3 | :author "Nikita Prokopov"} 4 | me.tonsky.persistent-sorted-set 5 | (:refer-clojure :exclude [iter conj disj sorted-set sorted-set-by]) 6 | (:require 7 | [me.tonsky.persistent-sorted-set.arrays :as arrays]) 8 | (:require-macros 9 | [me.tonsky.persistent-sorted-set.arrays :as arrays])) 10 | 11 | ; B+ tree 12 | ; ------- 13 | 14 | ; Leaf: keys[] :: array of values 15 | 16 | ; Node: pointers[] :: links to children nodes 17 | ; keys[] :: max value for whole subtree 18 | ; node.keys[i] == max(node.pointers[i].keys) 19 | ; All arrays are 16..32 elements, inclusive 20 | 21 | ; BTSet: root :: Node or Leaf 22 | ; shift :: depth - 1 23 | ; cnt :: size of a set, integer, rolling 24 | ; comparator :: comparator used for ordering 25 | ; meta :: clojure meta map 26 | ; _hash :: hash code, same as for clojure collections, on-demand, cached 27 | 28 | ; Path: conceptually a vector of indexes from root to leaf value, but encoded in a single number. 29 | ; E.g. we have path [7 30 11] representing root.pointers[7].pointers[30].keys[11]. 30 | ; In our case level-shift is 5, meaning each index will take 5 bits: 31 | ; (7 << 10) | (30 << 5) | (11 << 0) = 8139 32 | ; 00111 11110 01011 33 | 34 | ; Iter: set :: Set this iterator belongs to 35 | ; left :: Current path 36 | ; right :: Right bound path (exclusive) 37 | ; keys :: Cached ref for keys array for a leaf 38 | ; idx :: Cached idx in keys array 39 | ; Keys and idx are cached for fast iteration inside a leaf" 40 | 41 | (def ^:const max-safe-path 42 | "js limitation for bit ops" 43 | (js/Math.pow 2 31)) 44 | 45 | (def ^:const bits-per-level 46 | "tunable param" 47 | 5) 48 | 49 | (def ^:const max-len 50 | (js/Math.pow 2 bits-per-level)) ;; 32 51 | 52 | (def ^:const min-len 53 | (/ max-len 2)) ;; 16 54 | 55 | (def ^:private ^:const avg-len 56 | (arrays/half (+ max-len min-len))) ;; 24 57 | 58 | (def ^:const max-safe-level 59 | (js/Math.floor (/ 31 bits-per-level))) ;; 6 60 | 61 | (def ^:const bit-mask 62 | (- max-len 1)) ;; 0b011111 = 5 bit 63 | 64 | (def factors 65 | (arrays/into-array (map #(js/Math.pow 2 %) (range 0 52 bits-per-level)))) 66 | 67 | (def ^:const empty-path 0) 68 | 69 | (defn- path-get ^number [^number path ^number level] 70 | (if (< level max-safe-level) 71 | (-> path 72 | (unsigned-bit-shift-right (* level bits-per-level)) 73 | (bit-and bit-mask)) 74 | (-> path 75 | (/ (arrays/aget factors level)) 76 | (js/Math.floor) 77 | (bit-and bit-mask)))) 78 | 79 | (defn- path-set ^number [^number path ^number level ^number idx] 80 | (let [smol? (and (< path max-safe-path) (< level max-safe-level)) 81 | old (path-get path level) 82 | minus (if smol? 83 | (bit-shift-left old (* level bits-per-level)) 84 | (* old (arrays/aget factors level))) 85 | plus (if smol? 86 | (bit-shift-left idx (* level bits-per-level)) 87 | (* idx (arrays/aget factors level)))] 88 | (-> path 89 | (- minus) 90 | (+ plus)))) 91 | 92 | (defn- path-inc ^number [^number path] 93 | (inc path)) 94 | 95 | (defn- path-dec ^number [^number path] 96 | (dec path)) 97 | 98 | (defn- path-cmp ^number [^number path1 ^number path2] 99 | (- path1 path2)) 100 | 101 | (defn- path-lt ^boolean [^number path1 ^number path2] 102 | (< path1 path2)) 103 | 104 | (defn- path-lte ^boolean [^number path1 ^number path2] 105 | (<= path1 path2)) 106 | 107 | (defn- path-eq ^boolean [^number path1 ^number path2] 108 | (== path1 path2)) 109 | 110 | (defn- path-same-leaf ^boolean [^number path1 ^number path2] 111 | (if (and 112 | (< path1 max-safe-path) 113 | (< path2 max-safe-path)) 114 | (== 115 | (unsigned-bit-shift-right path1 bits-per-level) 116 | (unsigned-bit-shift-right path2 bits-per-level)) 117 | (== 118 | (Math/floor (/ path1 max-len)) 119 | (Math/floor (/ path2 max-len))))) 120 | 121 | (defn- path-str [^number path] 122 | (loop [res () 123 | path path] 124 | (if (not= path 0) 125 | (recur (cljs.core/conj res (mod path max-len)) (Math/floor (/ path max-len))) 126 | (vec res)))) 127 | 128 | (defn- binary-search-l [cmp arr r k] 129 | (loop [l 0 130 | r (long r)] 131 | (if (<= l r) 132 | (let [m (arrays/half (+ l r)) 133 | mk (arrays/aget arr m)] 134 | (if (neg? (cmp mk k)) 135 | (recur (inc m) r) 136 | (recur l (dec m)))) 137 | l))) 138 | 139 | (defn- binary-search-r [cmp arr r k] 140 | (loop [l 0 141 | r (long r)] 142 | (if (<= l r) 143 | (let [m (arrays/half (+ l r)) 144 | mk (arrays/aget arr m)] 145 | (if (pos? (cmp mk k)) 146 | (recur l (dec m)) 147 | (recur (inc m) r))) 148 | l))) 149 | 150 | (defn- lookup-exact [cmp arr key] 151 | (let [arr-l (arrays/alength arr) 152 | idx (binary-search-l cmp arr (dec arr-l) key)] 153 | (if (and (< idx arr-l) 154 | (== 0 (cmp (arrays/aget arr idx) key))) 155 | idx 156 | -1))) 157 | 158 | (defn- lookup-range [cmp arr key] 159 | (let [arr-l (arrays/alength arr) 160 | idx (binary-search-l cmp arr (dec arr-l) key)] 161 | (if (== idx arr-l) 162 | -1 163 | idx))) 164 | 165 | ;; Array operations 166 | 167 | (defn- cut-n-splice [arr cut-from cut-to splice-from splice-to xs] 168 | (let [xs-l (arrays/alength xs) 169 | l1 (- splice-from cut-from) 170 | l2 (- cut-to splice-to) 171 | l1xs (+ l1 xs-l) 172 | new-arr (arrays/make-array (+ l1 xs-l l2))] 173 | (arrays/acopy arr cut-from splice-from new-arr 0) 174 | (arrays/acopy xs 0 xs-l new-arr l1) 175 | (arrays/acopy arr splice-to cut-to new-arr l1xs) 176 | new-arr)) 177 | 178 | (defn- splice [arr splice-from splice-to xs] 179 | (cut-n-splice arr 0 (arrays/alength arr) splice-from splice-to xs)) 180 | 181 | (defn- insert [arr idx xs] 182 | (cut-n-splice arr 0 (arrays/alength arr) idx idx xs)) 183 | 184 | (defn- merge-n-split [a1 a2] 185 | (let [a1-l (arrays/alength a1) 186 | a2-l (arrays/alength a2) 187 | total-l (+ a1-l a2-l) 188 | r1-l (arrays/half total-l) 189 | r2-l (- total-l r1-l) 190 | r1 (arrays/make-array r1-l) 191 | r2 (arrays/make-array r2-l)] 192 | (if (<= a1-l r1-l) 193 | (do 194 | (arrays/acopy a1 0 a1-l r1 0) 195 | (arrays/acopy a2 0 (- r1-l a1-l) r1 a1-l) 196 | (arrays/acopy a2 (- r1-l a1-l) a2-l r2 0)) 197 | (do 198 | (arrays/acopy a1 0 r1-l r1 0) 199 | (arrays/acopy a1 r1-l a1-l r2 0) 200 | (arrays/acopy a2 0 a2-l r2 (- a1-l r1-l)))) 201 | (arrays/array r1 r2))) 202 | 203 | (defn- ^boolean eq-arr [cmp a1 a1-from a1-to a2 a2-from a2-to] 204 | (let [len (- a1-to a1-from)] 205 | (and 206 | (== len (- a2-to a2-from)) 207 | (loop [i 0] 208 | (cond 209 | (== i len) 210 | true 211 | 212 | (not (== 0 (cmp 213 | (arrays/aget a1 (+ i a1-from)) 214 | (arrays/aget a2 (+ i a2-from))))) 215 | false 216 | 217 | :else 218 | (recur (inc i))))))) 219 | 220 | (defn- check-n-splice [cmp arr from to new-arr] 221 | (if (eq-arr cmp arr from to new-arr 0 (arrays/alength new-arr)) 222 | arr 223 | (splice arr from to new-arr))) 224 | 225 | (defn- return-array 226 | "Drop non-nil references and return array of arguments" 227 | ([a1] 228 | (arrays/array a1)) 229 | ([a1 a2] 230 | (if a1 231 | (if a2 232 | (arrays/array a1 a2) 233 | (arrays/array a1)) 234 | (arrays/array a2))) 235 | ([a1 a2 a3] 236 | (if a1 237 | (if a2 238 | (if a3 239 | (arrays/array a1 a2 a3) 240 | (arrays/array a1 a2)) 241 | (if a3 242 | (arrays/array a1 a3) 243 | (arrays/array a1))) 244 | (if a2 245 | (if a3 246 | (arrays/array a2 a3) 247 | (arrays/array a2)) 248 | (arrays/array a3))))) 249 | 250 | ;; 251 | 252 | (defprotocol INode 253 | (node-lim-key [_]) 254 | (node-len [_]) 255 | (node-merge [_ next]) 256 | (node-merge-n-split [_ next]) 257 | (node-lookup [_ cmp key]) 258 | (node-conj [_ cmp key]) 259 | (node-disj [_ cmp key root? left right])) 260 | 261 | (defn- rotate [node root? left right] 262 | (cond 263 | ;; root never merges 264 | root? 265 | (return-array node) 266 | 267 | ;; enough keys, nothing to merge 268 | (> (node-len node) min-len) 269 | (return-array left node right) 270 | 271 | ;; left and this can be merged to one 272 | (and left (<= (node-len left) min-len)) 273 | (return-array (node-merge left node) right) 274 | 275 | ;; right and this can be merged to one 276 | (and right (<= (node-len right) min-len)) 277 | (return-array left (node-merge node right)) 278 | 279 | ;; left has fewer nodes, redestribute with it 280 | (and left (or (nil? right) 281 | (< (node-len left) (node-len right)))) 282 | (let [nodes (node-merge-n-split left node)] 283 | (return-array (arrays/aget nodes 0) (arrays/aget nodes 1) right)) 284 | 285 | ;; right has fewer nodes, redestribute with it 286 | :else 287 | (let [nodes (node-merge-n-split node right)] 288 | (return-array left (arrays/aget nodes 0) (arrays/aget nodes 1))))) 289 | 290 | (deftype Node [keys pointers] 291 | INode 292 | (node-lim-key [_] 293 | (arrays/alast keys)) 294 | 295 | (node-len [_] 296 | (arrays/alength keys)) 297 | 298 | (node-merge [_ next] 299 | (Node. (arrays/aconcat keys (.-keys next)) 300 | (arrays/aconcat pointers (.-pointers next)))) 301 | 302 | (node-merge-n-split [_ next] 303 | (let [ks (merge-n-split keys (.-keys next)) 304 | ps (merge-n-split pointers (.-pointers next))] 305 | (return-array (Node. (arrays/aget ks 0) (arrays/aget ps 0)) 306 | (Node. (arrays/aget ks 1) (arrays/aget ps 1))))) 307 | 308 | (node-lookup [_ cmp key] 309 | (let [idx (lookup-range cmp keys key)] 310 | (when-not (== -1 idx) 311 | (node-lookup (arrays/aget pointers idx) cmp key)))) 312 | 313 | (node-conj [_ cmp key] 314 | (let [idx (binary-search-l cmp keys (- (arrays/alength keys) 2) key) 315 | nodes (node-conj (arrays/aget pointers idx) cmp key)] 316 | (when nodes 317 | (let [new-keys (check-n-splice cmp keys idx (inc idx) (arrays/amap node-lim-key nodes)) 318 | new-pointers (splice pointers idx (inc idx) nodes)] 319 | (if (<= (arrays/alength new-pointers) max-len) 320 | ;; ok as is 321 | (arrays/array (Node. new-keys new-pointers)) 322 | ;; gotta split it up 323 | (let [middle (arrays/half (arrays/alength new-pointers))] 324 | (arrays/array 325 | (Node. (.slice new-keys 0 middle) 326 | (.slice new-pointers 0 middle)) 327 | (Node. (.slice new-keys middle) 328 | (.slice new-pointers middle))))))))) 329 | 330 | (node-disj [_ cmp key root? left right] 331 | (let [idx (lookup-range cmp keys key)] 332 | (when-not (== -1 idx) ;; short-circuit, key not here 333 | (let [child (arrays/aget pointers idx) 334 | left-child (when (>= (dec idx) 0) 335 | (arrays/aget pointers (dec idx))) 336 | right-child (when (< (inc idx) (arrays/alength pointers)) 337 | (arrays/aget pointers (inc idx))) 338 | disjned (node-disj child cmp key false left-child right-child)] 339 | (when disjned ;; short-circuit, key not here 340 | (let [left-idx (if left-child (dec idx) idx) 341 | right-idx (if right-child (+ 2 idx) (+ 1 idx)) 342 | new-keys (check-n-splice cmp keys left-idx right-idx (arrays/amap node-lim-key disjned)) 343 | new-pointers (splice pointers left-idx right-idx disjned)] 344 | (rotate (Node. new-keys new-pointers) root? left right)))))))) 345 | 346 | (deftype Leaf [keys] 347 | INode 348 | (node-lim-key [_] 349 | (arrays/alast keys)) 350 | ;; Object 351 | ;; (toString [_] (pr-str* (vec keys))) 352 | 353 | (node-len [_] 354 | (arrays/alength keys)) 355 | 356 | (node-merge [_ next] 357 | (Leaf. (arrays/aconcat keys (.-keys next)))) 358 | 359 | (node-merge-n-split [_ next] 360 | (let [ks (merge-n-split keys (.-keys next))] 361 | (return-array (Leaf. (arrays/aget ks 0)) 362 | (Leaf. (arrays/aget ks 1))))) 363 | 364 | (node-lookup [_ cmp key] 365 | (let [idx (lookup-exact cmp keys key)] 366 | (when-not (== -1 idx) 367 | (arrays/aget keys idx)))) 368 | 369 | (node-conj [_ cmp key] 370 | (let [idx (binary-search-l cmp keys (dec (arrays/alength keys)) key) 371 | keys-l (arrays/alength keys)] 372 | (cond 373 | ;; element already here 374 | (and (< idx keys-l) 375 | (== 0 (cmp key (arrays/aget keys idx)))) 376 | nil 377 | 378 | ;; splitting 379 | (== keys-l max-len) 380 | (let [middle (arrays/half (inc keys-l))] 381 | (if (> idx middle) 382 | ;; new key goes to the second half 383 | (arrays/array 384 | (Leaf. (.slice keys 0 middle)) 385 | (Leaf. (cut-n-splice keys middle keys-l idx idx (arrays/array key)))) 386 | ;; new key goes to the first half 387 | (arrays/array 388 | (Leaf. (cut-n-splice keys 0 middle idx idx (arrays/array key))) 389 | (Leaf. (.slice keys middle keys-l))))) 390 | 391 | ;; ok as is 392 | :else 393 | (arrays/array (Leaf. (splice keys idx idx (arrays/array key))))))) 394 | 395 | (node-disj [_ cmp key root? left right] 396 | (let [idx (lookup-exact cmp keys key)] 397 | (when-not (== -1 idx) ;; key is here 398 | (let [new-keys (splice keys idx (inc idx) (arrays/array))] 399 | (rotate (Leaf. new-keys) root? left right)))))) 400 | 401 | ;; BTSet 402 | 403 | (declare conj disj btset-iter) 404 | 405 | (def ^:private ^:const uninitialized-hash nil) 406 | 407 | (deftype BTSet [root shift cnt comparator meta ^:mutable _hash] 408 | Object 409 | (toString [this] (pr-str* this)) 410 | 411 | ICloneable 412 | (-clone [_] (BTSet. root shift cnt comparator meta _hash)) 413 | 414 | IWithMeta 415 | (-with-meta [_ new-meta] (BTSet. root shift cnt comparator new-meta _hash)) 416 | 417 | IMeta 418 | (-meta [_] meta) 419 | 420 | IEmptyableCollection 421 | (-empty [_] (BTSet. (Leaf. (arrays/array)) 0 0 comparator meta uninitialized-hash)) 422 | 423 | IEquiv 424 | (-equiv [this other] 425 | (and 426 | (set? other) 427 | (== cnt (count other)) 428 | (every? #(contains? this %) other))) 429 | 430 | IHash 431 | (-hash [this] (caching-hash this hash-unordered-coll _hash)) 432 | 433 | ICollection 434 | (-conj [this key] (conj this key comparator)) 435 | 436 | ISet 437 | (-disjoin [this key] (disj this key comparator)) 438 | 439 | ILookup 440 | (-lookup [_ k] 441 | (node-lookup root comparator k)) 442 | (-lookup [_ k not-found] 443 | (or (node-lookup root comparator k) not-found)) 444 | 445 | ISeqable 446 | (-seq [this] (btset-iter this)) 447 | 448 | IReduce 449 | (-reduce [this f] 450 | (if-let [i (btset-iter this)] 451 | (-reduce i f) 452 | (f))) 453 | (-reduce [this f start] 454 | (if-let [i (btset-iter this)] 455 | (-reduce i f start) 456 | start)) 457 | 458 | IReversible 459 | (-rseq [this] 460 | (rseq (btset-iter this))) 461 | 462 | ; ISorted 463 | ; (-sorted-seq [this ascending?]) 464 | ; (-sorted-seq-from [this k ascending?]) 465 | ; (-entry-key [this entry] entry) 466 | ; (-comparator [this] comparator) 467 | 468 | ICounted 469 | (-count [_] cnt) 470 | 471 | IEditableCollection 472 | (-as-transient [this] this) 473 | 474 | ITransientCollection 475 | (-conj! [this key] (conj this key comparator)) 476 | (-persistent! [this] this) 477 | 478 | ITransientSet 479 | (-disjoin! [this key] (disj this key comparator)) 480 | 481 | IFn 482 | (-invoke [this k] (-lookup this k)) 483 | (-invoke [this k not-found] (-lookup this k not-found)) 484 | 485 | IPrintWithWriter 486 | (-pr-writer [this writer opts] 487 | (pr-sequential-writer writer pr-writer "#{" " " "}" opts (seq this)))) 488 | 489 | (defn- keys-for [set path] 490 | (loop [level (.-shift set) 491 | node (.-root set)] 492 | (if (pos? level) 493 | (recur 494 | (dec level) 495 | (arrays/aget (.-pointers node) (path-get path level))) 496 | (.-keys node)))) 497 | 498 | (defn- alter-btset [set root shift cnt] 499 | (BTSet. root shift cnt (.-comparator set) (.-meta set) uninitialized-hash)) 500 | 501 | 502 | ;; iteration 503 | 504 | (defn- -next-path [node ^number path ^number level] 505 | (let [idx (path-get path level)] 506 | (if (pos? level) 507 | ;; inner node 508 | (let [sub-path (-next-path (arrays/aget (.-pointers node) idx) path (dec level))] 509 | (if (nil? sub-path) 510 | ;; nested node overflow 511 | (if (< (inc idx) (arrays/alength (.-pointers node))) 512 | ;; advance current node idx, reset subsequent indexes 513 | (path-set empty-path level (inc idx)) 514 | ;; current node overflow 515 | nil) 516 | ;; keep current idx 517 | (path-set sub-path level idx))) 518 | ;; leaf 519 | (if (< (inc idx) (arrays/alength (.-keys node))) 520 | ;; advance leaf idx 521 | (path-set empty-path 0 (inc idx)) 522 | ;; leaf overflow 523 | nil)))) 524 | 525 | (defn- -rpath 526 | "Returns rightmost path possible starting from node and going deeper" 527 | [node ^number path ^number level] 528 | (loop [node node 529 | path path 530 | level level] 531 | (if (pos? level) 532 | ;; inner node 533 | (recur 534 | (arrays/alast (.-pointers node)) 535 | (path-set path level (dec (arrays/alength (.-pointers node)))) 536 | (dec level)) 537 | ;; leaf 538 | (path-set path 0 (dec (arrays/alength (.-keys node))))))) 539 | 540 | (defn- next-path 541 | "Returns path representing next item after `path` in natural traversal order. 542 | Will overflow at leaf if at the end of the tree" 543 | [set ^number path] 544 | (if (neg? path) 545 | empty-path 546 | (or 547 | (-next-path (.-root set) path (.-shift set)) 548 | (path-inc (-rpath (.-root set) empty-path (.-shift set)))))) 549 | 550 | (defn- -prev-path [node ^number path ^number level] 551 | (let [idx (path-get path level)] 552 | (cond 553 | ;; leaf overflow 554 | (and (== 0 level) (== 0 idx)) 555 | nil 556 | 557 | ;; leaf 558 | (== 0 level) 559 | (path-set empty-path 0 (dec idx)) 560 | 561 | ;; branch that was overflow before 562 | (>= idx (node-len node)) 563 | (-rpath node path level) 564 | 565 | :else 566 | (let [path' (-prev-path (arrays/aget (.-pointers node) idx) path (dec level))] 567 | (cond 568 | ;; no sub-overflow, keep current idx 569 | (some? path') 570 | (path-set path' level idx) 571 | 572 | ;; nested overflow + this node overflow 573 | (== 0 idx) 574 | nil 575 | 576 | ;; nested overflow, advance current idx, reset subsequent indexes 577 | :else 578 | (let [path' (-rpath (arrays/aget (.-pointers node) (dec idx)) path (dec level))] 579 | (path-set path' level (dec idx)))))))) 580 | 581 | (defn- prev-path 582 | "Returns path representing previous item before `path` in natural traversal order. 583 | Will overflow at leaf if at beginning of tree" 584 | [set ^number path] 585 | (if (> (path-get path (inc (.-shift set))) 0) ;; overflow 586 | (-rpath (.-root set) path (.-shift set)) 587 | (or 588 | (-prev-path (.-root set) path (.-shift set)) 589 | (path-dec empty-path)))) 590 | 591 | (declare iter riter) 592 | 593 | (defn- btset-iter 594 | "Iterator that represents the whole set" 595 | [set] 596 | (when (pos? (node-len (.-root set))) 597 | (let [left empty-path 598 | rpath (-rpath (.-root set) empty-path (.-shift set)) 599 | right (next-path set rpath)] 600 | (iter set left right)))) 601 | 602 | ;; replace with cljs.core/ArrayChunk after https://dev.clojure.org/jira/browse/CLJS-2470 603 | (deftype Chunk [arr off end] 604 | ICounted 605 | (-count [_] (- end off)) 606 | 607 | IIndexed 608 | (-nth [this i] 609 | (aget arr (+ off i))) 610 | 611 | (-nth [this i not-found] 612 | (if (and (>= i 0) (< i (- end off))) 613 | (aget arr (+ off i)) 614 | not-found)) 615 | 616 | IChunk 617 | (-drop-first [this] 618 | (if (== off end) 619 | (throw (js/Error. "-drop-first of empty chunk")) 620 | (ArrayChunk. arr (inc off) end))) 621 | 622 | IReduce 623 | (-reduce [this f] 624 | (if (== off end) 625 | (f) 626 | (-reduce (-drop-first this) f (aget arr off)))) 627 | 628 | (-reduce [this f start] 629 | (loop [val start, n off] 630 | (if (< n end) 631 | (let [val' (f val (aget arr n))] 632 | (if (reduced? val') 633 | @val' 634 | (recur val' (inc n)))) 635 | val)))) 636 | 637 | (defprotocol IIter 638 | (-copy [this left right])) 639 | 640 | (defprotocol ISeek 641 | (-seek 642 | [this key] 643 | [this key comparator])) 644 | 645 | (declare -seek* -rseek*) 646 | 647 | (deftype Iter [set left right keys idx] 648 | IIter 649 | (-copy [_ l r] 650 | (Iter. set l r (keys-for set l) (path-get l 0))) 651 | 652 | IEquiv 653 | (-equiv [this other] (equiv-sequential this other)) 654 | 655 | ISequential 656 | ISeqable 657 | (-seq [this] (when keys this)) 658 | 659 | ISeq 660 | (-first [this] 661 | (when keys 662 | (arrays/aget keys idx))) 663 | 664 | (-rest [this] 665 | (or (-next this) ())) 666 | 667 | INext 668 | (-next [this] 669 | (when keys 670 | (if (< (inc idx) (arrays/alength keys)) 671 | ;; can use cached array to move forward 672 | (let [left' (path-inc left)] 673 | (when (path-lt left' right) 674 | (Iter. set left' right keys (inc idx)))) 675 | (let [left' (next-path set left)] 676 | (when (path-lt left' right) 677 | (-copy this left' right)))))) 678 | 679 | IChunkedSeq 680 | (-chunked-first [this] 681 | (let [end-idx (if (path-same-leaf left right) 682 | ;; right is in the same node 683 | (path-get right 0) 684 | ;; right is in a different node 685 | (arrays/alength keys))] 686 | (Chunk. keys idx end-idx))) 687 | 688 | (-chunked-rest [this] 689 | (or (-chunked-next this) ())) 690 | 691 | IChunkedNext 692 | (-chunked-next [this] 693 | (let [last (path-set left 0 (dec (arrays/alength keys))) 694 | left' (next-path set last)] 695 | (when (path-lt left' right) 696 | (-copy this left' right)))) 697 | 698 | IReduce 699 | (-reduce [this f] 700 | (if (nil? keys) 701 | (f) 702 | (let [first (-first this)] 703 | (if-some [next (-next this)] 704 | (-reduce next f first) 705 | first)))) 706 | 707 | (-reduce [this f start] 708 | (loop [left left 709 | keys keys 710 | idx idx 711 | acc start] 712 | (if (nil? keys) 713 | acc 714 | (let [new-acc (f acc (arrays/aget keys idx))] 715 | (cond 716 | (reduced? new-acc) 717 | @new-acc 718 | 719 | (< (inc idx) (arrays/alength keys)) ;; can use cached array to move forward 720 | (let [left' (path-inc left)] 721 | (if (path-lt left' right) 722 | (recur left' keys (inc idx) new-acc) 723 | new-acc)) 724 | 725 | :else 726 | (let [left' (next-path set left)] 727 | (if (path-lt left' right) 728 | (recur left' (keys-for set left') (path-get left' 0) new-acc) 729 | new-acc))))))) 730 | 731 | IReversible 732 | (-rseq [this] 733 | (when keys 734 | (riter set (prev-path set left) (prev-path set right)))) 735 | 736 | ISeek 737 | (-seek [this key] 738 | (-seek this key (.-comparator set))) 739 | 740 | (-seek [this key cmp] 741 | (cond 742 | (nil? key) 743 | (throw (js/Error. "seek can't be called with a nil key!")) 744 | 745 | (nat-int? (cmp (arrays/aget keys idx) key)) 746 | this 747 | 748 | :else 749 | (when-some [left' (-seek* set key cmp)] 750 | (Iter. set left' right (keys-for set left') (path-get left' 0))))) 751 | 752 | Object 753 | (toString [this] (pr-str* this)) 754 | 755 | IPrintWithWriter 756 | (-pr-writer [this writer opts] 757 | (pr-sequential-writer writer pr-writer "(" " " ")" opts (seq this)))) 758 | 759 | (defn iter [set left right] 760 | (Iter. set left right (keys-for set left) (path-get left 0))) 761 | 762 | ;; reverse iteration 763 | 764 | (deftype ReverseIter [set left right keys idx] 765 | IIter 766 | (-copy [_ l r] 767 | (ReverseIter. set l r (keys-for set r) (path-get r 0))) 768 | 769 | IEquiv 770 | (-equiv [this other] (equiv-sequential this other)) 771 | 772 | ISequential 773 | ISeqable 774 | (-seq [this] (when keys this)) 775 | 776 | ISeq 777 | (-first [this] 778 | (when keys 779 | (arrays/aget keys idx))) 780 | 781 | (-rest [this] 782 | (or (-next this) ())) 783 | 784 | INext 785 | (-next [this] 786 | (when keys 787 | (if (> idx 0) 788 | ;; can use cached array to advance 789 | (let [right' (path-dec right)] 790 | (when (path-lt left right') 791 | (ReverseIter. set left right' keys (dec idx)))) 792 | (let [right' (prev-path set right)] 793 | (when (path-lt left right') 794 | (-copy this left right')))))) 795 | 796 | IReversible 797 | (-rseq [this] 798 | (when keys 799 | (iter set (next-path set left) (next-path set right)))) 800 | 801 | ISeek 802 | (-seek [this key] 803 | (-seek this key (.-comparator set))) 804 | 805 | (-seek [this key cmp] 806 | (cond 807 | (nil? key) 808 | (throw (js/Error. "seek can't be called with a nil key!")) 809 | 810 | (nat-int? (cmp key (arrays/aget keys idx))) 811 | this 812 | 813 | :else 814 | (let [right' (prev-path set (-rseek* set key cmp))] 815 | (when (and 816 | (nat-int? right') 817 | (path-lte left right') 818 | (path-lt right' right)) 819 | (ReverseIter. set left right' (keys-for set right') (path-get right' 0)))))) 820 | 821 | Object 822 | (toString [this] (pr-str* this)) 823 | 824 | IPrintWithWriter 825 | (-pr-writer [this writer opts] 826 | (pr-sequential-writer writer pr-writer "(" " " ")" opts (seq this)))) 827 | 828 | (defn riter [set left right] 829 | (ReverseIter. set left right (keys-for set right) (path-get right 0))) 830 | 831 | ;; distance 832 | 833 | (defn- -distance [node left right level] 834 | (let [idx-l (path-get left level) 835 | idx-r (path-get right level)] 836 | (if (pos? level) 837 | ;; inner node 838 | (if (== idx-l idx-r) 839 | (-distance (arrays/aget (.-pointers node) idx-l) left right (dec level)) 840 | (loop [level level 841 | res (- idx-r idx-l)] 842 | (if (== 0 level) 843 | res 844 | (recur (dec level) (* res avg-len))))) 845 | (- idx-r idx-l)))) 846 | 847 | (defn- distance [set path-l path-r] 848 | (cond 849 | (path-eq path-l path-r) 850 | 0 851 | 852 | (path-eq (path-inc path-l) path-r) 853 | 1 854 | 855 | (path-eq (next-path set path-l) path-r) 856 | 1 857 | 858 | :else 859 | (-distance (.-root set) path-l path-r (.-shift set)))) 860 | 861 | (defn est-count [iter] 862 | (distance (.-set iter) (.-left iter) (.-right iter))) 863 | 864 | 865 | ;; Slicing 866 | 867 | (defn- -seek* 868 | "Returns path to first element >= key, 869 | or -1 if all elements in a set < key" 870 | [set key comparator] 871 | (if (nil? key) 872 | empty-path 873 | (loop [node (.-root set) 874 | path empty-path 875 | level (.-shift set)] 876 | (let [keys-l (node-len node)] 877 | (if (== 0 level) 878 | (let [keys (.-keys node) 879 | idx (binary-search-l comparator keys (dec keys-l) key)] 880 | (if (== keys-l idx) 881 | nil 882 | (path-set path 0 idx))) 883 | (let [keys (.-keys node) 884 | idx (binary-search-l comparator keys (- keys-l 2) key)] 885 | (recur 886 | (arrays/aget (.-pointers node) idx) 887 | (path-set path level idx) 888 | (dec level)))))))) 889 | 890 | (defn- -rseek* 891 | "Returns path to the first element that is > key. 892 | If all elements in a set are <= key, returns `(-rpath set) + 1`. 893 | It’s a virtual path that is bigger than any path in a tree" 894 | [set key comparator] 895 | (if (nil? key) 896 | (path-inc (-rpath (.-root set) empty-path (.-shift set))) 897 | (loop [node (.-root set) 898 | path empty-path 899 | level (.-shift set)] 900 | (let [keys-l (node-len node)] 901 | (if (== 0 level) 902 | (let [keys (.-keys node) 903 | idx (binary-search-r comparator keys (dec keys-l) key) 904 | res (path-set path 0 idx)] 905 | res) 906 | (let [keys (.-keys node) 907 | idx (binary-search-r comparator keys (- keys-l 2) key) 908 | res (path-set path level idx)] 909 | (recur 910 | (arrays/aget (.-pointers node) idx) 911 | res 912 | (dec level)))))))) 913 | 914 | (defn- -slice [set key-from key-to comparator] 915 | (when-some [path (-seek* set key-from comparator)] 916 | (let [till-path (-rseek* set key-to comparator)] 917 | (when (path-lt path till-path) 918 | (Iter. set path till-path (keys-for set path) (path-get path 0)))))) 919 | 920 | (defn- arr-map-inplace [f arr] 921 | (let [len (arrays/alength arr)] 922 | (loop [i 0] 923 | (when (< i len) 924 | (arrays/aset arr i (f (arrays/aget arr i))) 925 | (recur (inc i)))) 926 | arr)) 927 | 928 | 929 | (defn- arr-partition-approx 930 | "Splits `arr` into arrays of size between min-len and max-len, 931 | trying to stick to (min+max)/2" 932 | [min-len max-len arr] 933 | (let [chunk-len avg-len 934 | len (arrays/alength arr) 935 | acc (transient [])] 936 | (when (pos? len) 937 | (loop [pos 0] 938 | (let [rest (- len pos)] 939 | (cond 940 | (<= rest max-len) 941 | (conj! acc (.slice arr pos)) 942 | (>= rest (+ chunk-len min-len)) 943 | (do 944 | (conj! acc (.slice arr pos (+ pos chunk-len))) 945 | (recur (+ pos chunk-len))) 946 | :else 947 | (let [piece-len (arrays/half rest)] 948 | (conj! acc (.slice arr pos (+ pos piece-len))) 949 | (recur (+ pos piece-len))))))) 950 | (to-array (persistent! acc)))) 951 | 952 | 953 | (defn- sorted-arr-distinct? [arr cmp] 954 | (let [al (arrays/alength arr)] 955 | (if (<= al 1) 956 | true 957 | (loop [i 1 958 | p (arrays/aget arr 0)] 959 | (if (>= i al) 960 | true 961 | (let [e (arrays/aget arr i)] 962 | (if (== 0 (cmp e p)) 963 | false 964 | (recur (inc i) e)))))))) 965 | 966 | 967 | (defn- sorted-arr-distinct 968 | "Filter out repetitive values in a sorted array. 969 | Optimized for no-duplicates case" 970 | [arr cmp] 971 | (if (sorted-arr-distinct? arr cmp) 972 | arr 973 | (let [al (arrays/alength arr)] 974 | (loop [acc (transient [(arrays/aget arr 0)]) 975 | i 1 976 | p (arrays/aget arr 0)] 977 | (if (>= i al) 978 | (into-array (persistent! acc)) 979 | (let [e (arrays/aget arr i)] 980 | (if (== 0 (cmp e p)) 981 | (recur acc (inc i) e) 982 | (recur (conj! acc e) (inc i) e)))))))) 983 | 984 | 985 | ;; Public interface 986 | 987 | (defn conj 988 | "Analogue to [[clojure.core/conj]] with comparator that overrides the one stored in set." 989 | [set key cmp] 990 | (let [roots (node-conj (.-root set) cmp key)] 991 | (cond 992 | ;; tree not changed 993 | (nil? roots) 994 | set 995 | 996 | ;; keeping single root 997 | (== (arrays/alength roots) 1) 998 | (alter-btset set 999 | (arrays/aget roots 0) 1000 | (.-shift set) 1001 | (inc (.-cnt set))) 1002 | 1003 | ;; introducing new root 1004 | :else 1005 | (alter-btset set 1006 | (Node. (arrays/amap node-lim-key roots) roots) 1007 | (inc (.-shift set)) 1008 | (inc (.-cnt set)))))) 1009 | 1010 | 1011 | (defn disj 1012 | "Analogue to [[clojure.core/disj]] with comparator that overrides the one stored in set." 1013 | [set key cmp] 1014 | (let [new-roots (node-disj (.-root set) cmp key true nil nil)] 1015 | (if (nil? new-roots) ;; nothing changed, key wasn't in the set 1016 | set 1017 | (let [new-root (arrays/aget new-roots 0)] 1018 | (if (and (instance? Node new-root) 1019 | (== 1 (arrays/alength (.-pointers new-root)))) 1020 | 1021 | ;; root has one child, make him new root 1022 | (alter-btset set 1023 | (arrays/aget (.-pointers new-root) 0) 1024 | (dec (.-shift set)) 1025 | (dec (.-cnt set))) 1026 | 1027 | ;; keeping root level 1028 | (alter-btset set 1029 | new-root 1030 | (.-shift set) 1031 | (dec (.-cnt set)))))))) 1032 | 1033 | 1034 | (defn slice 1035 | "An iterator for part of the set with provided boundaries. 1036 | `(slice set from to)` returns iterator for all Xs where from <= X <= to. 1037 | Optionally pass in comparator that will override the one that set uses. Supports efficient [[clojure.core/rseq]]." 1038 | ([set key-from key-to] 1039 | (-slice set key-from key-to (.-comparator set))) 1040 | ([set key-from key-to comparator] 1041 | (-slice set key-from key-to comparator))) 1042 | 1043 | 1044 | (defn rslice 1045 | "A reverse iterator for part of the set with provided boundaries. 1046 | `(rslice set from to)` returns backwards iterator for all Xs where from <= X <= to. 1047 | Optionally pass in comparator that will override the one that set uses. Supports efficient [[clojure.core/rseq]]." 1048 | ([set key] 1049 | (some-> (-slice set key key (.-comparator set)) rseq)) 1050 | ([set key-from key-to] 1051 | (some-> (-slice set key-to key-from (.-comparator set)) rseq)) 1052 | ([set key-from key-to comparator] 1053 | (some-> (-slice set key-to key-from comparator) rseq))) 1054 | 1055 | 1056 | (defn seek 1057 | "An efficient way to seek to a specific key in a seq (either returned by [[clojure.core.seq]] or a slice.) 1058 | `(seek (seq set) to)` returns iterator for all Xs where to <= X. 1059 | Optionally pass in comparator that will override the one that set uses." 1060 | ([seq to] 1061 | (-seek seq to)) 1062 | ([seq to cmp] 1063 | (-seek seq to cmp))) 1064 | 1065 | 1066 | (defn from-sorted-array 1067 | "Fast path to create a set if you already have a sorted array of elements on your hands." 1068 | ([cmp arr] 1069 | (from-sorted-array cmp arr (arrays/alength arr) {})) 1070 | ([cmp arr _len] 1071 | (from-sorted-array cmp arr _len {})) 1072 | ([cmp arr _len _opts] 1073 | (let [leaves (->> arr 1074 | (arr-partition-approx min-len max-len) 1075 | (arr-map-inplace #(Leaf. %)))] 1076 | (loop [current-level leaves 1077 | shift 0] 1078 | (case (count current-level) 1079 | 0 (BTSet. (Leaf. (arrays/array)) 0 0 cmp nil uninitialized-hash) 1080 | 1 (BTSet. (first current-level) shift (arrays/alength arr) cmp nil uninitialized-hash) 1081 | (recur 1082 | (->> current-level 1083 | (arr-partition-approx min-len max-len) 1084 | (arr-map-inplace #(Node. (arrays/amap node-lim-key %) %))) 1085 | (inc shift))))))) 1086 | 1087 | 1088 | (defn from-sequential 1089 | "Create a set with custom comparator and a collection of keys. Useful when you don’t want to call [[clojure.core/apply]] on [[sorted-set-by]]." 1090 | [cmp seq] 1091 | (let [arr (-> (into-array seq) (arrays/asort cmp) (sorted-arr-distinct cmp))] 1092 | (from-sorted-array cmp arr))) 1093 | 1094 | 1095 | (defn sorted-set* 1096 | "Create a set with custom comparator, metadata and settings" 1097 | [opts] 1098 | (BTSet. (Leaf. (arrays/array)) 0 0 (or (:cmp opts) compare) (:meta opts) uninitialized-hash)) 1099 | 1100 | 1101 | (defn sorted-set-by 1102 | ([cmp] (BTSet. (Leaf. (arrays/array)) 0 0 cmp nil uninitialized-hash)) 1103 | ([cmp & keys] (from-sequential cmp keys))) 1104 | 1105 | 1106 | (defn sorted-set 1107 | ([] (sorted-set-by compare)) 1108 | ([& keys] (from-sequential compare keys))) 1109 | 1110 | (defn settings [set] 1111 | {:branching-factor max-len 1112 | :ref-type :strong}) --------------------------------------------------------------------------------