├── .gitignore ├── .github └── workflows │ ├── test.yml │ ├── snapshot.yml │ ├── doc-build.yml │ └── release.yml ├── CONTRIBUTING.md ├── project.clj ├── CHANGELOG.md ├── src ├── main │ ├── java │ │ └── clojure │ │ │ └── data │ │ │ └── int_map │ │ │ ├── ISet.java │ │ │ ├── INode.java │ │ │ ├── IntSet.java │ │ │ └── Nodes.java │ └── clojure │ │ └── clojure │ │ └── data │ │ └── int_map.clj └── test │ └── clojure │ └── clojure │ └── data │ ├── benchmark.clj │ └── int_map_test.clj ├── pom.xml ├── README.md ├── LICENSE └── epl.html /.gitignore: -------------------------------------------------------------------------------- 1 | .nrepl* 2 | target 3 | .lein* 4 | .idea* 5 | *.iml 6 | .clj-kondo/ 7 | .lsp/ 8 | .calva/ 9 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | call-test: 7 | uses: clojure/build.ci/.github/workflows/test.yml@master 8 | -------------------------------------------------------------------------------- /.github/workflows/snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Snapshot on demand 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | call-snapshot: 7 | uses: clojure/build.ci/.github/workflows/snapshot.yml@master 8 | secrets: inherit 9 | -------------------------------------------------------------------------------- /.github/workflows/doc-build.yml: -------------------------------------------------------------------------------- 1 | name: Build API Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | call-doc-build-workflow: 8 | uses: clojure/build.ci/.github/workflows/doc-build.yml@master 9 | with: 10 | project: clojure/data.int-map 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This is a [Clojure contrib] project. 2 | 3 | Under the Clojure contrib [guidelines], this project cannot accept 4 | pull requests. All patches must be submitted via [JIRA]. 5 | 6 | See [Contributing] on the Clojure website for 7 | more information on how to contribute. 8 | 9 | [Clojure contrib]: https://clojure.org/community/contrib_libs 10 | [Contributing]: https://clojure.org/community/contributing 11 | [JIRA]: https://clojure.atlassian.net/browse/DIMAP 12 | [guidelines]: https://clojure.org/community/contrib_howto 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release on demand 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseVersion: 7 | description: "Version to release" 8 | required: true 9 | snapshotVersion: 10 | description: "Snapshot version after release" 11 | required: true 12 | 13 | jobs: 14 | call-release: 15 | uses: clojure/build.ci/.github/workflows/release.yml@master 16 | with: 17 | releaseVersion: ${{ github.event.inputs.releaseVersion }} 18 | snapshotVersion: ${{ github.event.inputs.snapshotVersion }} 19 | secrets: inherit -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject org.clojure/data.int-map "0.2.2-SNAPSHOT" 2 | :description "Set and map data structures optimized for integer keys and elements." 3 | :url "https://github.com/clojure/data.int-map" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :source-paths ["src/main/clojure"] 7 | :test-paths ["src/test/clojure"] 8 | :dependencies [] 9 | :profiles {:dev {:dependencies [[org.clojure/clojure "1.9.0"] 10 | [collection-check "0.1.6"] 11 | [criterium "0.4.6"] 12 | [rhizome "0.2.6"]]}} 13 | :test-selectors {:default (complement :benchmark) 14 | :benchmark :benchmark} 15 | :java-source-paths ["src/main/java"] 16 | :jvm-opts ^:replace ["-server" "-Xmx10g" "-XX:-OmitStackTraceInFastThrow" "-Xss750k"] 17 | :global-vars {*warn-on-reflection* true}) 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | =========== 3 | 4 | * 1.2.1 on Mar 23, 2023 5 | * DIMAP-22 - Performance improvement to int-map range function 6 | * 1.2.0 on Jan 20, 2023 7 | * Made int maps and sets work correctly on contains? and get for non-int keys 8 | * DIMAP-6 - fixed incorrect docstring on int-map 9 | * 1.1.0 on Jan 20, 2023 10 | * DIMAP-17 - use singletons for empty int maps and sets 11 | * DIMAP-16 - fix equals and equiv don't compare equals with Java maps/sets 12 | * DIMAP-18 - fix empty doesn't preserve meta 13 | * DIMAP-7 - fix minor typos in README and docstring 14 | * Remove unused ArrayList allocation 15 | * 1.0.0 on Aug 19, 2020 16 | * DIMAP-15 - fix PersistentIntMap equals/equiv gives wrong result 17 | * DIMAP-14 - support element lookup on transient sets 18 | * 0.2.4 on Sep 28, 2016 19 | * 0.2.3 on Jul 10, 2016 20 | * Fix iteration over empty nodes 21 | * Improve perf of equality checks 22 | * 0.2.2 on Dec 11, 2015 23 | * 0.2.1 on Aug i7, 2015 24 | * 0.2.0 on May 26, 2015 25 | * 0.1.0 on Aug 28, 2014 26 | * Initial release 27 | -------------------------------------------------------------------------------- /src/main/java/clojure/data/int_map/ISet.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Zach Tellman, Rich Hickey and contributors. All rights reserved. 2 | // The use and distribution terms for this software are covered by the 3 | // Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | // which can be found in the file epl-v10.html at the root of this distribution. 5 | // By using this software in any fashion, you are agreeing to be bound by 6 | // the terms of this license. 7 | // You must not remove this notice, or any other, from this software. 8 | 9 | package clojure.data.int_map; 10 | 11 | import java.util.BitSet; 12 | import java.util.Iterator; 13 | 14 | public interface ISet { 15 | ISet add(long epoch, long val); 16 | ISet remove(long epoch, long val); 17 | boolean contains(long val); 18 | 19 | ISet range(long epoch, long min, long max); 20 | Iterator elements(long offset, boolean reverse); 21 | long count(); 22 | 23 | BitSet toBitSet(); 24 | 25 | ISet intersection(long epoch, ISet sv); 26 | ISet union(long epoch, ISet sv); 27 | ISet difference(long epoch, ISet sv); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/clojure/data/int_map/INode.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Zach Tellman, Rich Hickey and contributors. All rights reserved. 2 | // The use and distribution terms for this software are covered by the 3 | // Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | // which can be found in the file epl-v10.html at the root of this distribution. 5 | // By using this software in any fashion, you are agreeing to be bound by 6 | // the terms of this license. 7 | // You must not remove this notice, or any other, from this software. 8 | 9 | package clojure.data.int_map; 10 | 11 | import java.util.Iterator; 12 | import clojure.lang.IFn; 13 | 14 | public interface INode { 15 | 16 | public enum IterationType { 17 | KEYS, 18 | VALS, 19 | ENTRIES 20 | } 21 | 22 | long count(); 23 | Iterator iterator(IterationType type, boolean reverse); 24 | INode range(long min, long max); 25 | 26 | INode merge(INode node, long epoch, IFn f); 27 | INode assoc(long k, long epoch, IFn f, Object v); 28 | INode dissoc(long k, long epoch); 29 | INode update(long k, long epoch, IFn f); 30 | Object get(long k, Object defaultVal); 31 | 32 | Object kvreduce(IFn f, Object init); 33 | Object reduce(IFn f, Object init); 34 | Object fold(long n, IFn combiner, IFn reducer, IFn fjtask, IFn fjfork, IFn fjjoin); 35 | } 36 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | data.int-map 5 | 1.3.1-SNAPSHOT 6 | data.int-map 7 | A map optimized for integer keys 8 | 9 | 10 | 11 | Eclipse Public License 1.0 12 | https://opensource.org/license/epl-1-0/ 13 | repo 14 | 15 | 16 | 17 | 18 | org.clojure 19 | pom.contrib 20 | 1.3.0 21 | 22 | 23 | 24 | 25 | Zach Tellman 26 | https://github.com/ztellman 27 | 28 | 29 | 30 | 31 | scm:git:git://github.com/clojure/data.int-map.git 32 | scm:git:git://github.com/clojure/data.int-map.git 33 | https://github.com/clojure/data.int-map 34 | HEAD 35 | 36 | 37 | 38 | 1.9.0 39 | true 40 | 41 | 42 | 43 | 44 | 45 | com.theoryinpractise 46 | clojure-maven-plugin 47 | 1.7.1 48 | true 49 | 50 | 51 | clojure-compile 52 | compile 53 | 54 | compile 55 | 56 | 57 | true 58 | true 59 | 60 | 61 | 62 | clojure-test 63 | test 64 | 65 | test 66 | 67 | 68 | true 69 | true 70 | 71 | !clojure.data.benchmark 72 | 73 | 74 | 75 | 76 | 77 | true 78 | true 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.clojure 87 | clojure 88 | ${clojure.version} 89 | provided 90 | 91 | 92 | collection-check 93 | collection-check 94 | 0.1.6 95 | test 96 | 97 | 98 | rhizome 99 | rhizome 100 | 0.2.1 101 | test 102 | 103 | 104 | criterium 105 | criterium 106 | 0.4.6 107 | test 108 | 109 | 110 | 111 | 112 | 113 | clojars 114 | https://clojars.org/repo/ 115 | 116 | true 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/data/benchmark.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.data.benchmark 2 | (:use 3 | [clojure.test]) 4 | (:require 5 | [clojure.core.reducers :as r] 6 | [clojure.data.int-map :as i] 7 | [criterium.core :as c]) 8 | (:import 9 | [java.util 10 | BitSet] 11 | [clojure.data.int_map 12 | PersistentIntMap])) 13 | 14 | (def n (long 1e6)) 15 | 16 | (def ordered-entries (vec (map vector (range n) (range n)))) 17 | (def entries (vec (shuffle ordered-entries))) 18 | 19 | (deftest ^:benchmark benchmark-maps 20 | 21 | (println "\ninto {} unordered") 22 | (c/quick-bench 23 | (into {} entries)) 24 | 25 | (println "\ninto {} ordered") 26 | (c/quick-bench 27 | (into {} ordered-entries)) 28 | 29 | (println "\ninto (sorted-map) unordered") 30 | (c/quick-bench 31 | (into (sorted-map) entries)) 32 | 33 | (println "\ninto (sorted-map) ordered") 34 | (c/quick-bench 35 | (into (sorted-map) ordered-entries)) 36 | 37 | (println "\ninto (int-map) unordered") 38 | (c/quick-bench 39 | (into (i/int-map) entries)) 40 | 41 | (println "\ninto (int-map) ordered") 42 | (c/quick-bench 43 | (into (i/int-map) ordered-entries)) 44 | 45 | (println "\ninto (int-map) fold/merge unordered") 46 | (c/quick-bench 47 | (r/fold i/merge conj entries)) 48 | 49 | (println "\ninto (int-map) fold/merge ordered") 50 | (c/quick-bench 51 | (r/fold i/merge conj ordered-entries)) 52 | 53 | (let [m (into {} entries) 54 | r (java.util.Random.)] 55 | (println "\nget {}") 56 | (c/quick-bench 57 | (get m 1000))) 58 | 59 | (let [m (into (i/int-map) entries) 60 | r (java.util.Random.)] 61 | (println "\nget (int-map)") 62 | (c/quick-bench 63 | (get m 1000))) 64 | 65 | (let [m (into (sorted-map) entries) 66 | r (java.util.Random.)] 67 | (println "\nget (sorted-map)") 68 | (c/quick-bench 69 | (get m 1000))) 70 | 71 | (let [m1 (into (i/int-map) entries) 72 | m2 (into (i/int-map) entries) 73 | r (java.util.Random.)] 74 | (println "\n= (int-map, int-map)") 75 | (c/quick-bench 76 | (= m1 m2))) 77 | 78 | (let [m1 (into (i/int-map) entries) 79 | m2 (into (hash-map) entries) 80 | r (java.util.Random.)] 81 | (println "\n= (int-map, hash-map)") 82 | (c/quick-bench 83 | (= m1 m2))) 84 | 85 | (let [m1 (into (hash-map) entries) 86 | m2 (into (hash-map) entries) 87 | r (java.util.Random.)] 88 | (println "\n= (hash-map, hash-map)") 89 | (c/quick-bench 90 | (= m1 m2)))) 91 | 92 | ;;; 93 | 94 | (deftest ^:benchmark benchmark-modify-set 95 | (println "\nsparse bitset into 1e3") 96 | (c/quick-bench 97 | (into (i/int-set) (range 1e3))) 98 | (println "\ndense bitset into 1e3") 99 | (c/quick-bench 100 | (into (i/dense-int-set) (range 1e3))) 101 | (println "\nnormal set into 1e3") 102 | (c/quick-bench 103 | (into #{} (range 1e3))) 104 | (println "\nmutable bitset add 1e3") 105 | (c/quick-bench 106 | (let [^BitSet bitset (BitSet. 1e3)] 107 | (dotimes [idx 1e3] 108 | (.set bitset idx true))))) 109 | 110 | (deftest ^:benchmark benchmark-check-set 111 | (println "\ncheck sparse bitset") 112 | (let [b (into (i/int-set) (range 1e3))] 113 | (c/quick-bench 114 | (contains? b 123))) 115 | (println "\ncheck dense bitset") 116 | (let [b (into (i/dense-int-set) (range 1e3))] 117 | (c/quick-bench 118 | (contains? b 123))) 119 | (println "\ncheck normal set") 120 | (let [s (into #{} (range 1e3))] 121 | (c/quick-bench 122 | (contains? s 123))) 123 | (println "\nmutable bitset lookup") 124 | (let [b (BitSet. 1e3)] 125 | (c/quick-bench 126 | (.get b 123)))) 127 | 128 | (deftest ^:benchmark benchmark-int-map-range 129 | (println "\ncheck int-map range") 130 | (let [^PersistentIntMap im (->> (range 0 50000) 131 | (map (fn [x] [x x])) 132 | (into (i/int-map)))] 133 | (c/quick-bench 134 | (do 135 | (.range im -20000 20000) 136 | (.range im 20000 20100) 137 | (.range im -10000 -100) 138 | (.range im 20000 100000) 139 | (.range im -1000000 100000))) 140 | )) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [CLI/`deps.edn`](https://clojure.org/reference/deps_edn) dependency information: 2 | ```clojure 3 | org.clojure/data.int-map {:mvn/version "1.3.0"} 4 | ``` 5 | [Leiningen](https://github.com/technomancy/leiningen): 6 | ```clj 7 | [org.clojure/data.int-map "1.3.0"] 8 | ``` 9 | 10 | This library has special implementations of immutable maps and sets, optimized for integer keys. They are both faster in both updates and lookups than normal Clojure data structures, but also more memory efficient, sometimes significantly. 11 | 12 | ## Maps 13 | 14 | ```clj 15 | > (require '[clojure.data.int-map :as i]) 16 | nil 17 | > (i/int-map) 18 | {} 19 | ``` 20 | 21 | These maps support transient/persistent semantics, and also provide special `merge`, `merge-with`, `update`, and `update!` methods that provide significantly faster performance than their normal Clojure counterparts. The elements must be in the range `[Long/MIN_VALUE, Long/MAX_VALUE]`. They can be used to represent normal maps which have integral keys, or sparse vectors. 22 | 23 | The fact that int-maps are mergeable means that they can be used very effectively with Clojure's [reducer](https://clojure.org/news/2012/05/08/reducers) mechanism. For instance, consider populating a data structure. Typically, we'd use `into`: 24 | 25 | ```clj 26 | > (into {} [[1 2] [3 4]]) 27 | {1 2, 3 4} 28 | ``` 29 | 30 | Under the covers, `into` looks something like this: 31 | 32 | ```clj 33 | > (persistent! 34 | (reduce conj! 35 | (transient {}) 36 | [[1 2] [3 4]])) 37 | ``` 38 | 39 | This makes use of Clojure's transients, but is still inherently sequential. This is because merging together standard Clojure maps is an O(N) operation, so any parallel work would still result in a linear walk of the map's entries. However, int-maps merges are typically much faster, which means we can build sub-maps on parallel threads, and then cheaply merge them together. We can use the `fold` method in `clojure.core.reducers` to easily express this: 40 | 41 | ```clj 42 | > (require '[clojure.core.reducers :as r]) 43 | nil 44 | > (r/fold i/merge conj entries) 45 | ... 46 | ``` 47 | 48 | If `entries` is a data structure that `fold` can split, such as a vector or hash-map, the performance benefits of this are significant. Consider this table, which gives the times on a four-core system for populating a map with a million entries, with all values in milliseconds: 49 | 50 | | | unsorted entries | sorted entries | 51 | |----|------------------|-------------| 52 | | `(into {} ...)` | 630 | 500 | 53 | | `(into (sorted-map) ...)` | 2035 | 1080 | 54 | | `(into (i/int-map) ...)` | 529 | 187 | 55 | | `(fold i/merge conj ...)` | 273 | 53 | 56 | 57 | As we can see, the int-map implementation is faster in all cases, and an entire order of magnitude faster when using `fold` on ordered entries. 58 | 59 | ## Sets 60 | 61 | ```clj 62 | > (require '[clojure.data.int-map :as i]) 63 | nil 64 | > (i/int-set) 65 | #{} 66 | > (i/dense-int-set) 67 | #{} 68 | ``` 69 | 70 | There are special `union`, `intersection` and `difference` operators for these sets, which are significantly faster than those in `clojure.set`. The elements must be in the range `[Long/MIN_VALUE, Long/MAX_VALUE]`. 71 | 72 | `dense-int-set` behaves the same as `int-set`, the difference is only in their memory efficiency. Consider a case where we create a set of all numbers between one and one million: 73 | 74 | ```clj 75 | (def s (range 1e6)) 76 | 77 | (into #{} s) ; ~100mb 78 | (into (int-set) s) ; ~1mb 79 | (into (dense-int-set) s) ; ~150kb 80 | ``` 81 | 82 | Both of these are significantly smaller than the standard set, but the dense int-set is almost an order of magnitude smaller than the normal int-set. This is because the dense int-set allocates larger contiguous chunks, which is great if the numbers are densely clustered. However, if the numbers are sparse: 83 | 84 | ```clj 85 | (def s (map (partial * 1e6) (range 1e6))) 86 | 87 | (into #{} s) ; ~100mb 88 | (into (int-set) s) ; ~130mb 89 | (into (dense-int-set) s) ; ~670mb 90 | ``` 91 | 92 | In this case, the dense int-set is much less efficient than the standard set, while the normal int-set is equivalently large. So as a rule of thumb, use `dense-int-set` where the elements are densely clustered (each element has multiple elements within +/- 1000), and `int-set` for everything else. 93 | 94 | ## Developer information 95 | 96 | data.int-map is being developed as a Clojure Contrib project, see the 97 | [What is Clojure Contrib](https://clojure.org/dev/contrib_libs) 98 | page for details. Patches will only be accepted from developers who 99 | have signed the Clojure Contributor Agreement. 100 | 101 | * [API docs](https://clojure.github.io/data.int-map/) 102 | * [GitHub project](https://github.com/clojure/data.int-map) 103 | * [Bug Tracker](https://clojure.atlassian.net/browse/DIMAP) 104 | * [Continuous Integration](https://github.com/clojure/data.int-map/actions/workflows/test.yml) 105 | 106 | ## License 107 | 108 | Copyright © Zach Tellman, Rich Hickey and contributors 109 | 110 | Distributed under the Eclipse Public License, the same as Clojure. 111 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/data/int_map_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.data.int-map-test 2 | (:use 3 | [clojure.test]) 4 | (:require 5 | #_[rhizome.viz :as v] 6 | [clojure.set :as set] 7 | [clojure.core.reducers :as r] 8 | [clojure.data.int-map :as i] 9 | [collection-check :as check] 10 | [clojure.test.check.generators :as gen] 11 | [clojure.test.check.properties :as prop] 12 | [clojure.test.check.clojure-test :as ct :refer (defspec)])) 13 | 14 | (set! *warn-on-reflection* false) 15 | 16 | (deftest empty-retains-meta 17 | (let [mm {:hi :there}] 18 | (is (= mm (meta (empty (with-meta (i/int-map 1 2) mm))))) 19 | (is (= mm (meta (empty (with-meta (i/int-set [1 2]) mm))))) 20 | (is (= mm (meta (empty (with-meta (i/dense-int-set [1 2]) mm))))))) 21 | 22 | ;;; 23 | 24 | (defn is-same-collection [a b] 25 | (let [msg (format "(class a)=%s (class b)=%s a=%s b=%s" 26 | (.getName (class a)) (.getName (class b)) a b)] 27 | (is (= (count a) (count b) (.size a) (.size b)) msg) 28 | (is (= a b) msg) 29 | (is (= b a) msg) 30 | (is (.equals ^Object a b) msg) 31 | (is (.equals ^Object b a) msg) 32 | (is (= (.hashCode ^Object a) (.hashCode ^Object b)) msg))) 33 | 34 | (defn is-same-clj-collection [a b] 35 | (let [msg (format "(class a)=%s (class b)=%s a=%s b=%s" 36 | (.getName (class a)) (.getName (class b)) a b)] 37 | (is-same-collection a b) 38 | (is (= (hash a) (hash b)) msg))) 39 | 40 | (deftest set-collection-tests 41 | (let [clj-sets [(set [11 13 17]) 42 | (hash-set 11 13 17) 43 | (sorted-set 11 13 17) 44 | (sorted-set-by < 11 13 17) 45 | (i/int-set [11 13 17]) 46 | (i/dense-int-set [11 13 17])] 47 | non-clj-sets [(java.util.HashSet. [11 13 17])]] 48 | (doseq [c1 (concat clj-sets non-clj-sets), 49 | c2 (concat clj-sets non-clj-sets)] 50 | (is-same-collection c1 c2)) 51 | (doseq [c1 clj-sets, c2 clj-sets] 52 | (is-same-clj-collection c1 c2)))) 53 | 54 | (deftest map-collection-tests 55 | (let [clj-maps [(hash-map 11 13, 17 19) 56 | (array-map 11 13, 17 19) 57 | (sorted-map 11 13, 17 19) 58 | (sorted-map-by < 11 13, 17 19) 59 | (i/int-map 11 13, 17 19)] 60 | non-clj-maps [(java.util.HashMap. {11 13, 17 19})]] 61 | (doseq [c1 (concat clj-maps non-clj-maps), 62 | c2 (concat clj-maps non-clj-maps)] 63 | (is-same-collection c1 c2)) 64 | (doseq [c1 clj-maps, c2 clj-maps] 65 | (is-same-clj-collection c1 c2)))) 66 | 67 | (def map-int 68 | (->> (gen/tuple gen/int (gen/choose 0 63)) 69 | (gen/fmap (fn [[x y]] (bit-shift-left x y))))) 70 | 71 | (deftest test-map-like 72 | (check/assert-map-like 1e3 (i/int-map) map-int gen/int)) 73 | 74 | (deftest test-set-like 75 | (check/assert-set-like 1e3 (i/int-set) map-int)) 76 | 77 | (def int-map-generator 78 | (gen/fmap 79 | (fn [ks] 80 | (into (i/int-map) ks)) 81 | (gen/list (gen/tuple map-int gen/int)))) 82 | 83 | (def int-set-generator 84 | (gen/fmap 85 | (fn [ks] 86 | (into (i/int-set) ks)) 87 | (gen/list map-int))) 88 | 89 | (defspec equivalent-update 1e3 90 | (let [f #(if % (inc %) 1)] 91 | (prop/for-all [m int-map-generator k gen/int] 92 | (= (i/update m k f) 93 | (assoc m k (f (get m k))))))) 94 | 95 | (defspec equivalent-update! 1e3 96 | (prop/for-all [ks (gen/list gen/int)] 97 | (persistent! (reduce #(i/update! %1 %2 (fn [_])) (transient (i/int-map)) ks)))) 98 | 99 | (defspec equivalent-merge 1e3 100 | (prop/for-all [a (gen/list (gen/tuple gen/int gen/int)) 101 | b (gen/list (gen/tuple gen/int gen/int))] 102 | (let [a (into (i/int-map) a) 103 | b (into (i/int-map) b)] 104 | (= (merge-with - a b) (i/merge-with - a b))))) 105 | 106 | (defspec equivalent-fold 1e3 107 | (prop/for-all [m int-map-generator] 108 | (= (reduce-kv (fn [n _ v] (+ n v)) 0 m) 109 | (r/fold 8 + (fn [n _ v] (+ n v)) m)))) 110 | 111 | (defspec equivalent-map-order 1e4 112 | (prop/for-all [ks (gen/list gen/int)] 113 | (= (keys (reduce #(assoc %1 %2 nil) (i/int-map) ks)) 114 | (seq (sort (distinct ks)))))) 115 | 116 | (defspec equivalent-reverse-map-order 1e4 117 | (prop/for-all [m int-map-generator] 118 | (= (seq (reverse m)) (rseq m)))) 119 | 120 | (defspec equivalent-reverse-set-order 1e4 121 | (prop/for-all [s int-set-generator] 122 | (= (seq (reverse s)) (rseq s)))) 123 | 124 | (defspec equivalent-set-order 1e4 125 | (prop/for-all [ks (gen/list gen/int)] 126 | (= (seq (reduce #(conj %1 %2) (i/int-set) ks)) 127 | (seq (sort (distinct ks)))))) 128 | 129 | (defspec equivalent-map-range 1e4 130 | (prop/for-all [m int-map-generator 131 | min gen/int 132 | max gen/int] 133 | (= (i/range m min max) 134 | (select-keys m (->> m keys (filter #(<= min % max))))))) 135 | 136 | (defspec equivalent-set-range 1e4 137 | (prop/for-all [s int-set-generator 138 | min gen/int 139 | max gen/int] 140 | (= (i/range s min max) 141 | (->> s (filter #(<= min % max)) set)))) 142 | 143 | (deftest test-contiguous-keys 144 | (is (== 1e7 (count (persistent! (reduce #(assoc! %1 %2 nil) (transient (i/int-map)) (range 1e7))))))) 145 | ;;; 146 | 147 | #_(import 148 | [clojure.data.int_map 149 | INode 150 | Nodes 151 | Nodes$Leaf 152 | Nodes$Empty 153 | Nodes$BinaryBranch 154 | Nodes$Branch]) 155 | 156 | #_(defn view-tree [m] 157 | (let [r (.root m)] 158 | (v/view-tree 159 | #(or (instance? Nodes$BinaryBranch %) (instance? Nodes$Branch %)) 160 | #(if (instance? Nodes$BinaryBranch %) 161 | [(.a %) (.b %)] 162 | (remove nil? (.children %))) 163 | r 164 | :node->descriptor (fn [n] 165 | {:label (cond 166 | (instance? Nodes$Leaf n) 167 | (str (.key n) "," (.value n)) 168 | 169 | (instance? Nodes$Branch n) 170 | (str (.offset n)) 171 | 172 | :else 173 | "")})))) 174 | 175 | #_(defn view-set [s] 176 | (let [r (-> s .int-set .map)] 177 | (v/view-tree 178 | #(or (instance? Nodes$BinaryBranch %) (instance? Nodes$Branch %)) 179 | #(if (instance? Nodes$BinaryBranch %) 180 | [(.a %) (.b %)] 181 | (remove nil? (.children %))) 182 | r 183 | :node->descriptor (fn [n] 184 | {:label (cond 185 | (instance? Nodes$Leaf n) 186 | (str (.key n) "," (.value n)) 187 | 188 | (instance? Nodes$Branch n) 189 | (str (.offset n)) 190 | 191 | :else 192 | "")})))) 193 | 194 | (defn get-field [field x] 195 | (let [field (name field)] 196 | (-> x 197 | class 198 | (.getDeclaredField field) 199 | (doto (.setAccessible true)) 200 | (.get x)))) 201 | 202 | (defn tree-hierarchy [m] 203 | (condp instance? m 204 | clojure.lang.PersistentHashMap 205 | (->> m 206 | (get-field :root) 207 | tree-hierarchy) 208 | 209 | clojure.lang.PersistentHashMap$BitmapIndexedNode 210 | (let [ary (get-field :array m)] 211 | (concat 212 | (->> ary 213 | (partition 2) 214 | (map first) 215 | (remove nil?)) 216 | (->> ary 217 | (partition 2) 218 | (filter #(nil? (first %))) 219 | (map second) 220 | (remove nil?) 221 | (map tree-hierarchy)))) 222 | 223 | clojure.lang.PersistentHashMap$ArrayNode 224 | (->> m 225 | (get-field :array) 226 | (remove nil?) 227 | (map tree-hierarchy)) 228 | 229 | clojure.lang.PersistentHashMap$HashCollisionNode 230 | (->> m 231 | (get-field :array) 232 | (partition 2) 233 | (map first)) 234 | 235 | clojure.data.int_map.PersistentIntMap 236 | (->> m 237 | (get-field :root) 238 | tree-hierarchy) 239 | 240 | clojure.data.int_map.Nodes$BinaryBranch 241 | (->> [(get-field :left m) (get-field :right m)] 242 | (remove nil?) 243 | (map tree-hierarchy)) 244 | 245 | clojure.data.int_map.Nodes$Branch 246 | (->> m 247 | (get-field :children) 248 | (remove nil?) 249 | (map tree-hierarchy)) 250 | 251 | clojure.data.int_map.Nodes$Leaf 252 | (get-field :key m) 253 | 254 | m)) 255 | 256 | (defn singly-linked-nodes [tree] 257 | (concat 258 | (->> tree 259 | (filter #(and (seq? %) (= 1 (count %))))) 260 | (->> tree 261 | (filter #(and (seq? %) (not= 1 (count %)))) 262 | (mapcat singly-linked-nodes)))) 263 | 264 | (defn depths 265 | ([tree] 266 | (depths 0 tree)) 267 | ([d tree] 268 | (concat 269 | (->> tree 270 | (remove seq?) 271 | (map (constantly d))) 272 | (->> tree 273 | (filter seq?) 274 | (mapcat #(depths (inc d) %)))))) 275 | 276 | (defn mean [s] 277 | (double (/ (reduce + s) (count s)))) 278 | 279 | ;;; 280 | 281 | (defn diff-equals? [set0 set1] 282 | (prn (i/difference (i/int-set set0) 283 | (i/int-set set1)) 284 | 285 | (set/difference set0 286 | set1))) 287 | 288 | (defn all-set-algebra-operators-equivalent? 289 | [generator] 290 | (prop/for-all [a (gen/vector map-int) b (gen/vector map-int)] 291 | (let [sa (set a) 292 | sb (set b) 293 | isa (generator a) 294 | isb (generator b)] 295 | (and 296 | (= (set/difference sa sb) (i/difference isa isb)) 297 | (= (set/difference sb sa) (i/difference isb isa)) 298 | (= (set/union sa sb) (i/union isa isb) (i/union isb isa)) 299 | (= (set/intersection sa sb) (i/intersection isa isb) (i/intersection isb isa)))))) 300 | 301 | (defspec prop-sparse-all-set-algebra-operators-equivalent 1e5 302 | (all-set-algebra-operators-equivalent? i/int-set)) 303 | 304 | (defspec prop-dense-all-set-algebra-operators-equivalent 1e5 305 | (all-set-algebra-operators-equivalent? i/dense-int-set)) 306 | 307 | (deftest int-map-empty-singleton 308 | (is (identical? (i/int-map) (i/int-map)) "(int-map) should always return the same instance")) 309 | (deftest int-set-empty-singleton 310 | (is (identical? (i/int-set) (i/int-set)) "(int-set) should always return the same instance")) 311 | (deftest dense-int-set-empty-singleton 312 | (is (identical? (i/dense-int-set) (i/dense-int-set)) "(dense-int-set) should always return the same instance")) 313 | 314 | (defspec int-map-range 1e5 315 | (prop/for-all [im int-map-generator 316 | min gen/int 317 | max gen/int] 318 | (is (= (.range im min max) (->> im 319 | (filter (fn [[k _]] (<= min k max))) 320 | (into {})))))) 321 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' 19 | from a Contributor if it was added to the Program by such Contributor 20 | itself or anyone acting on such Contributor's behalf. Contributions do not 21 | include additions to the Program which: (i) are separate modules of 22 | software distributed in conjunction with the Program under their own 23 | license agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this 32 | Agreement. 33 | 34 | "Recipient" means anyone who receives the Program under this Agreement, 35 | including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | a) Subject to the terms of this Agreement, each Contributor hereby grants 39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 40 | reproduce, prepare derivative works of, publicly display, publicly 41 | perform, distribute and sublicense the Contribution of such Contributor, 42 | if any, and such derivative works, in source code and object code form. 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | c) Recipient understands that although each Contributor grants the licenses 54 | to its Contributions set forth herein, no assurances are provided by any 55 | Contributor that the Program does not infringe the patent or other 56 | intellectual property rights of any other entity. Each Contributor 57 | disclaims any liability to Recipient for claims brought by any other 58 | entity based on infringement of intellectual property rights or 59 | otherwise. As a condition to exercising the rights and licenses granted 60 | hereunder, each Recipient hereby assumes sole responsibility to secure 61 | any other intellectual property rights needed, if any. For example, if a 62 | third party patent license is required to allow Recipient to distribute 63 | the Program, it is Recipient's responsibility to acquire that license 64 | before distributing the Program. 65 | d) Each Contributor represents that to its knowledge it has sufficient 66 | copyright rights in its Contribution, if any, to grant the copyright 67 | license set forth in this Agreement. 68 | 69 | 3. REQUIREMENTS 70 | 71 | A Contributor may choose to distribute the Program in object code form under 72 | its own license agreement, provided that: 73 | 74 | a) it complies with the terms and conditions of this Agreement; and 75 | b) its license agreement: 76 | i) effectively disclaims on behalf of all Contributors all warranties 77 | and conditions, express and implied, including warranties or 78 | conditions of title and non-infringement, and implied warranties or 79 | conditions of merchantability and fitness for a particular purpose; 80 | ii) effectively excludes on behalf of all Contributors all liability for 81 | damages, including direct, indirect, special, incidental and 82 | consequential damages, such as lost profits; 83 | iii) states that any provisions which differ from this Agreement are 84 | offered by that Contributor alone and not by any other party; and 85 | iv) states that source code for the Program is available from such 86 | Contributor, and informs licensees how to obtain it in a reasonable 87 | manner on or through a medium customarily used for software exchange. 88 | 89 | When the Program is made available in source code form: 90 | 91 | a) it must be made available under this Agreement; and 92 | b) a copy of this Agreement must be included with each copy of the Program. 93 | Contributors may not remove or alter any copyright notices contained 94 | within the Program. 95 | 96 | Each Contributor must identify itself as the originator of its Contribution, 97 | if 98 | any, in a manner that reasonably allows subsequent Recipients to identify the 99 | originator of the Contribution. 100 | 101 | 4. COMMERCIAL DISTRIBUTION 102 | 103 | Commercial distributors of software may accept certain responsibilities with 104 | respect to end users, business partners and the like. While this license is 105 | intended to facilitate the commercial use of the Program, the Contributor who 106 | includes the Program in a commercial product offering should do so in a manner 107 | which does not create potential liability for other Contributors. Therefore, 108 | if a Contributor includes the Program in a commercial product offering, such 109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 110 | every other Contributor ("Indemnified Contributor") against any losses, 111 | damages and costs (collectively "Losses") arising from claims, lawsuits and 112 | other legal actions brought by a third party against the Indemnified 113 | Contributor to the extent caused by the acts or omissions of such Commercial 114 | Contributor in connection with its distribution of the Program in a commercial 115 | product offering. The obligations in this section do not apply to any claims 116 | or Losses relating to any actual or alleged intellectual property 117 | infringement. In order to qualify, an Indemnified Contributor must: 118 | a) promptly notify the Commercial Contributor in writing of such claim, and 119 | b) allow the Commercial Contributor to control, and cooperate with the 120 | Commercial Contributor in, the defense and any related settlement 121 | negotiations. The Indemnified Contributor may participate in any such claim at 122 | its own expense. 123 | 124 | For example, a Contributor might include the Program in a commercial product 125 | offering, Product X. That Contributor is then a Commercial Contributor. If 126 | that Commercial Contributor then makes performance claims, or offers 127 | warranties related to Product X, those performance claims and warranties are 128 | such Commercial Contributor's responsibility alone. Under this section, the 129 | Commercial Contributor would have to defend claims against the other 130 | Contributors related to those performance claims and warranties, and if a 131 | court requires any other Contributor to pay any damages as a result, the 132 | Commercial Contributor must pay those damages. 133 | 134 | 5. NO WARRANTY 135 | 136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 140 | Recipient is solely responsible for determining the appropriateness of using 141 | and distributing the Program and assumes all risks associated with its 142 | exercise of rights under this Agreement , including but not limited to the 143 | risks and costs of program errors, compliance with applicable laws, damage to 144 | or loss of data, programs or equipment, and unavailability or interruption of 145 | operations. 146 | 147 | 6. DISCLAIMER OF LIABILITY 148 | 149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 156 | OF SUCH DAMAGES. 157 | 158 | 7. GENERAL 159 | 160 | If any provision of this Agreement is invalid or unenforceable under 161 | applicable law, it shall not affect the validity or enforceability of the 162 | remainder of the terms of this Agreement, and without further action by the 163 | parties hereto, such provision shall be reformed to the minimum extent 164 | necessary to make such provision valid and enforceable. 165 | 166 | If Recipient institutes patent litigation against any entity (including a 167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 168 | (excluding combinations of the Program with other software or hardware) 169 | infringes such Recipient's patent(s), then such Recipient's rights granted 170 | under Section 2(b) shall terminate as of the date such litigation is filed. 171 | 172 | All Recipient's rights under this Agreement shall terminate if it fails to 173 | comply with any of the material terms or conditions of this Agreement and does 174 | not cure such failure in a reasonable period of time after becoming aware of 175 | such noncompliance. If all Recipient's rights under this Agreement terminate, 176 | Recipient agrees to cease use and distribution of the Program as soon as 177 | reasonably practicable. However, Recipient's obligations under this Agreement 178 | and any licenses granted by Recipient relating to the Program shall continue 179 | and survive. 180 | 181 | Everyone is permitted to copy and distribute copies of this Agreement, but in 182 | order to avoid inconsistency the Agreement is copyrighted and may only be 183 | modified in the following manner. The Agreement Steward reserves the right to 184 | publish new versions (including revisions) of this Agreement from time to 185 | time. No one other than the Agreement Steward has the right to modify this 186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 187 | Eclipse Foundation may assign the responsibility to serve as the Agreement 188 | Steward to a suitable separate entity. Each new version of the Agreement will 189 | be given a distinguishing version number. The Program (including 190 | Contributions) may always be distributed subject to the version of the 191 | Agreement under which it was received. In addition, after a new version of the 192 | Agreement is published, Contributor may elect to distribute the Program 193 | (including its Contributions) under the new version. Except as expressly 194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 195 | licenses to the intellectual property of any Contributor under this Agreement, 196 | whether expressly, by implication, estoppel or otherwise. All rights in the 197 | Program not expressly granted under this Agreement are reserved. 198 | 199 | This Agreement is governed by the laws of the State of New York and the 200 | intellectual property laws of the United States of America. No party to this 201 | Agreement will bring a legal action under this Agreement more than one year 202 | after the cause of action arose. Each party waives its rights to a jury trial in 203 | any resulting litigation. 204 | 205 | 206 | -------------------------------------------------------------------------------- /epl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /src/main/java/clojure/data/int_map/IntSet.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Zach Tellman, Rich Hickey and contributors. All rights reserved. 2 | // The use and distribution terms for this software are covered by the 3 | // Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | // which can be found in the file epl-v10.html at the root of this distribution. 5 | // By using this software in any fashion, you are agreeing to be bound by 6 | // the terms of this license. 7 | // You must not remove this notice, or any other, from this software. 8 | 9 | package clojure.data.int_map; 10 | 11 | import clojure.lang.AFn; 12 | import clojure.lang.MapEntry; 13 | 14 | import java.util.*; 15 | 16 | public class IntSet implements ISet { 17 | 18 | public class BitSetContainer implements ISet { 19 | public final long epoch; 20 | public final BitSet bitSet; 21 | 22 | public BitSetContainer(long epoch, BitSet bitSet) { 23 | this.epoch = epoch; 24 | this.bitSet = bitSet; 25 | } 26 | 27 | public ISet add(long epoch, long val) { 28 | if (epoch == this.epoch) { 29 | bitSet.set((short) val); 30 | return this; 31 | } else { 32 | BitSet bitSet = (BitSet) this.bitSet.clone(); 33 | bitSet.set((short) val); 34 | return new BitSetContainer(epoch, bitSet); 35 | } 36 | } 37 | 38 | public ISet remove(long epoch, long val) { 39 | if (epoch == this.epoch) { 40 | bitSet.set((short) val, false); 41 | return this; 42 | } else { 43 | BitSet bitSet = (BitSet) this.bitSet.clone(); 44 | bitSet.set((short) val, false); 45 | return new BitSetContainer(epoch, bitSet); 46 | } 47 | } 48 | 49 | public boolean contains(long val) { 50 | return bitSet.get((short) val); 51 | } 52 | 53 | public ISet range(long epoch, long min, long max) { 54 | BitSet bitSet = (BitSet) this.bitSet.clone(); 55 | 56 | int size = bitSet.size(); 57 | bitSet.set(0, Math.max((short)min, 0), false); 58 | if (max < size) { 59 | bitSet.set(Math.min((short)max+1, size), size, false); 60 | } 61 | return new BitSetContainer(epoch, bitSet); 62 | } 63 | 64 | public Iterator elements(long offset, boolean reverse) { 65 | List ns = new ArrayList(bitSet.cardinality()); 66 | int idx = 0; 67 | while (idx < bitSet.length()) { 68 | idx = bitSet.nextSetBit(idx); 69 | ns.add(offset + idx); 70 | idx++; 71 | } 72 | if (reverse) { 73 | Collections.reverse(ns); 74 | } 75 | return ns.iterator(); 76 | } 77 | 78 | public long count() { 79 | return bitSet.cardinality(); 80 | } 81 | 82 | public BitSet toBitSet() { 83 | return bitSet; 84 | } 85 | 86 | public ISet intersection(long epoch, ISet val) { 87 | BitSet bitSet = (BitSet) this.bitSet.clone(); 88 | bitSet.and(val.toBitSet()); 89 | return new BitSetContainer(epoch, bitSet); 90 | } 91 | 92 | public ISet union(long epoch, ISet val) { 93 | BitSet bitSet = (BitSet) this.bitSet.clone(); 94 | bitSet.or(val.toBitSet()); 95 | return new BitSetContainer(epoch, bitSet); 96 | } 97 | 98 | public ISet difference(long epoch, ISet val) { 99 | BitSet bitSet = (BitSet) this.bitSet.clone(); 100 | bitSet.andNot(val.toBitSet()); 101 | return new BitSetContainer(epoch, bitSet); 102 | } 103 | } 104 | 105 | public class SingleContainer implements ISet { 106 | public final short val; 107 | 108 | public SingleContainer(short val) { 109 | this.val = val; 110 | } 111 | 112 | public ISet add(long epoch, long val) { 113 | if (val == this.val) { 114 | return this; 115 | } else { 116 | BitSet bitSet = new BitSet(Math.max((short) val, this.val)); 117 | bitSet.set((short) val); 118 | bitSet.set(this.val); 119 | return new BitSetContainer(epoch, bitSet); 120 | } 121 | } 122 | 123 | public ISet remove(long epoch, long val) { 124 | return val == this.val ? null : this; 125 | } 126 | 127 | public boolean contains(long val) { 128 | return val == this.val; 129 | } 130 | 131 | public ISet range(long epoch, long min, long max) { 132 | return (min <= val && max >= val) ? this : null; 133 | } 134 | 135 | public long count() { 136 | return 1; 137 | } 138 | 139 | public Iterator elements(long offset, boolean reverse) { 140 | final long val = this.val + offset; 141 | return new Iterator() { 142 | 143 | private boolean isDone = false; 144 | 145 | public boolean hasNext() { 146 | return !isDone; 147 | } 148 | 149 | public Object next() { 150 | if (isDone) throw new NoSuchElementException(); 151 | isDone = true; 152 | return val; 153 | } 154 | 155 | public void remove() { 156 | throw new UnsupportedOperationException(); 157 | } 158 | }; 159 | } 160 | 161 | public BitSet toBitSet() { 162 | BitSet bitSet = new BitSet(val); 163 | bitSet.set(val); 164 | return bitSet; 165 | } 166 | 167 | public ISet intersection(long epoch, ISet sv) { 168 | return sv == null 169 | ? null 170 | : sv.contains(val) 171 | ? this 172 | : null; 173 | } 174 | 175 | public ISet union(long epoch, ISet sv) { 176 | return sv == null 177 | ? this 178 | : sv.contains(val) 179 | ? sv 180 | : sv.add(epoch, val); 181 | } 182 | 183 | public ISet difference(long epoch, ISet sv) { 184 | return sv == null 185 | ? this 186 | : sv.contains(val) 187 | ? null 188 | : this; 189 | } 190 | 191 | } 192 | 193 | public final INode map; 194 | public final short leafSize, log2LeafSize; 195 | public volatile int count = -1; 196 | 197 | public IntSet(short leafSize) { 198 | this.leafSize = leafSize; 199 | this.log2LeafSize = (short) Nodes.bitLog2(leafSize); 200 | map = Nodes.Empty.EMPTY; 201 | } 202 | 203 | IntSet(short leafSize, short log2LeafSize, INode map) { 204 | this.leafSize = leafSize; 205 | this.log2LeafSize = log2LeafSize; 206 | this.map = map; 207 | } 208 | 209 | public int leafSize() { 210 | return this.leafSize; 211 | } 212 | 213 | private long mapKey(long val) { 214 | return val >> log2LeafSize; 215 | } 216 | 217 | private short leafOffset(long val) { 218 | return (short) (val & (leafSize - 1)); 219 | } 220 | 221 | public ISet add(final long epoch, final long val) { 222 | INode mapPrime = map.update(mapKey(val), epoch, 223 | new AFn() { 224 | public Object invoke(Object v) { 225 | ISet s = (ISet) v; 226 | return s == null ? new SingleContainer(leafOffset(val)) : s.add(epoch, leafOffset(val)); 227 | } 228 | }); 229 | if (mapPrime == map) { 230 | count = -1; 231 | return this; 232 | } else { 233 | return new IntSet(leafSize, log2LeafSize, mapPrime); 234 | } 235 | } 236 | 237 | public ISet remove(final long epoch, final long val) { 238 | INode mapPrime = map.update(mapKey(val), epoch, 239 | new AFn() { 240 | public Object invoke(Object v) { 241 | ISet s = (ISet) v; 242 | return s == null ? null : s.remove(epoch, leafOffset(val)); 243 | } 244 | }); 245 | if (mapPrime == map) { 246 | count = -1; 247 | return this; 248 | } else { 249 | return new IntSet(leafSize, log2LeafSize, mapPrime); 250 | } 251 | } 252 | 253 | public boolean contains(long val) { 254 | ISet s = (ISet) map.get(mapKey(val), null); 255 | return s != null && s.contains(leafOffset(val)); 256 | } 257 | 258 | public ISet range(final long epoch, final long min, final long max) { 259 | 260 | if (max < min) { 261 | return new IntSet(leafSize); 262 | } 263 | 264 | if (mapKey(min) == mapKey(max)) { 265 | ISet set = (ISet) map.get(mapKey(min), null); 266 | set = set == null ? null : set.range(epoch, leafOffset(min), leafOffset(max)); 267 | 268 | return set == null 269 | ? new IntSet(leafSize) 270 | : new IntSet(leafSize, log2LeafSize, Nodes.Empty.EMPTY.assoc(mapKey(min), epoch, null, set)); 271 | } 272 | 273 | INode mapPrime = map.range(mapKey(min), mapKey(max)); 274 | mapPrime = mapPrime == null 275 | ? Nodes.Empty.EMPTY 276 | : mapPrime 277 | .update(mapKey(min), epoch, 278 | new AFn() { 279 | public Object invoke(Object v) { 280 | return v != null ? ((ISet) v).range(epoch, leafOffset(min), leafSize) : null; 281 | } 282 | }) 283 | .update(mapKey(max), epoch, 284 | new AFn() { 285 | public Object invoke(Object v) { 286 | return v != null ? ((ISet)v).range(epoch, 0, leafOffset(max)) : null; 287 | } 288 | }); 289 | 290 | return new IntSet(leafSize, log2LeafSize, mapPrime); 291 | } 292 | 293 | public Iterator elements(final long offset, final boolean reverse) { 294 | final Iterator it = map.iterator(INode.IterationType.ENTRIES, reverse); 295 | return new Iterator() { 296 | 297 | private Iterator parentIterator = it; 298 | private Iterator iterator = null; 299 | 300 | private void tryAdvance() { 301 | while ((iterator == null || !iterator.hasNext()) && parentIterator.hasNext()) { 302 | MapEntry entry = (MapEntry) parentIterator.next(); 303 | ISet set = (ISet) entry.val(); 304 | long fullOffset = offset + ((Long)entry.key()) << log2LeafSize; 305 | iterator = set == null ? null : set.elements(fullOffset, reverse); 306 | } 307 | } 308 | 309 | public boolean hasNext() { 310 | tryAdvance(); 311 | return iterator == null ? false : iterator.hasNext(); 312 | } 313 | 314 | public Object next() { 315 | tryAdvance(); 316 | return iterator.next(); 317 | } 318 | 319 | public void remove() { 320 | throw new UnsupportedOperationException(); 321 | } 322 | }; 323 | } 324 | 325 | public long count() { 326 | if (count >= 0) { 327 | return count; 328 | } 329 | 330 | long cnt = 0; 331 | Iterator i = map.iterator(INode.IterationType.VALS, false); 332 | while (i.hasNext()) { 333 | ISet s = (ISet) i.next(); 334 | if (s != null) cnt += s.count(); 335 | } 336 | return cnt; 337 | } 338 | 339 | public BitSet toBitSet() { 340 | throw new UnsupportedOperationException(); 341 | } 342 | 343 | public ISet intersection(final long epoch, ISet sv) { 344 | IntSet s = (IntSet) sv; 345 | Iterator i1 = map.iterator(INode.IterationType.ENTRIES, false); 346 | Iterator i2 = s.map.iterator(INode.IterationType.ENTRIES, false); 347 | 348 | // one is empty, so is the intersection 349 | if (!i1.hasNext() || !i2.hasNext()) { 350 | return new IntSet(leafSize); 351 | } 352 | 353 | INode node = Nodes.Empty.EMPTY; 354 | 355 | MapEntry e1 = (MapEntry) i1.next(); 356 | MapEntry e2 = (MapEntry) i2.next(); 357 | while (true) { 358 | long k1 = (Long) e1.key(); 359 | long k2 = (Long) e2.key(); 360 | if (k1 == k2 && e1.val() != null && e2.val() != null) { 361 | node = node.assoc(k1, epoch, null, ((ISet)e1.val()).intersection(epoch, (ISet)e2.val())); 362 | if (!i1.hasNext() || !i2.hasNext()) break; 363 | e1 = (MapEntry) i1.next(); 364 | e2 = (MapEntry) i2.next(); 365 | } else if (k1 < k2) { 366 | if (!i1.hasNext()) break; 367 | e1 = (MapEntry) i1.next(); 368 | } else { 369 | if (!i2.hasNext()) break; 370 | e2 = (MapEntry) i2.next(); 371 | } 372 | } 373 | 374 | return new IntSet(leafSize, log2LeafSize, node); 375 | } 376 | 377 | public ISet union(final long epoch, ISet sv) { 378 | IntSet s = (IntSet) sv; 379 | if (s.leafSize != leafSize) { 380 | throw new IllegalArgumentException("Cannot merge int-sets of different density."); 381 | } 382 | return new IntSet(leafSize, log2LeafSize, 383 | map.merge(s.map, epoch, 384 | new AFn() { 385 | public Object invoke(Object a, Object b) { 386 | if (a == null) return b; 387 | if (b == null) return a; 388 | return ((ISet) a).union(epoch, (ISet) b); 389 | } 390 | })); 391 | } 392 | 393 | public ISet difference(final long epoch, ISet sv) { 394 | IntSet s = (IntSet) sv; 395 | Iterator i1 = map.iterator(INode.IterationType.ENTRIES, false); 396 | Iterator i2 = s.map.iterator(INode.IterationType.ENTRIES, false); 397 | 398 | if (!i1.hasNext() || !i2.hasNext()) { 399 | return this; 400 | } 401 | 402 | INode node = Nodes.Empty.EMPTY; 403 | 404 | MapEntry e1 = (MapEntry) i1.next(); 405 | MapEntry e2 = (MapEntry) i2.next(); 406 | while (true) { 407 | long k1 = (Long) e1.key(); 408 | long k2 = (Long) e2.key(); 409 | 410 | if (k1 == k2 && e1.val() != null && e2.val() != null) { 411 | node = node.assoc(k1, epoch, null, ((ISet)e1.val()).difference(epoch, (ISet) e2.val())); 412 | if (!i1.hasNext() || !i2.hasNext()) break; 413 | e1 = (MapEntry) i1.next(); 414 | e2 = (MapEntry) i2.next(); 415 | } else if (k1 <= k2 && e1.val() != null) { 416 | node = node.assoc(k1, epoch, null, e1.val()); 417 | if (!i1.hasNext()) break; 418 | e1 = (MapEntry) i1.next(); 419 | } else { 420 | if (!i2.hasNext()) { 421 | node = node.assoc(k1, epoch, null, e1.val()); 422 | break; 423 | } 424 | e2 = (MapEntry) i2.next(); 425 | } 426 | } 427 | 428 | while (i1.hasNext()) { 429 | e1 = (MapEntry) i1.next(); 430 | node = node.assoc((Long)e1.key(), epoch, null, e1.val()); 431 | } 432 | 433 | return new IntSet(leafSize, log2LeafSize, node); 434 | } 435 | 436 | } 437 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/data/int_map.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Zach Tellman, Rich Hickey and contributors. All rights reserved. 2 | ; The use and distribution terms for this software are covered by the 3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ; which can be found in the file epl-v10.html at the root of this distribution. 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; You must not remove this notice, or any other, from this software. 8 | 9 | (ns 10 | ^{:doc "An adaptation of Okasaki and Gill's \"Fast Mergeable Integer Maps`\", 11 | which can be found at http://ittc.ku.edu/~andygill/papers/IntMap98.pdf"} 12 | clojure.data.int-map 13 | (:refer-clojure 14 | :exclude [merge merge-with update range]) 15 | (:require 16 | [clojure.core.reducers :as r]) 17 | (:import 18 | [java.util 19 | Map 20 | Map$Entry] 21 | [clojure.data.int_map 22 | INode 23 | IntSet 24 | Nodes$Empty 25 | INode$IterationType] 26 | [clojure.lang Util])) 27 | 28 | (set! *warn-on-reflection* true) 29 | (set! *unchecked-math* true) 30 | 31 | ;;; 32 | 33 | (defn ^:private default-merge [_ x] 34 | x) 35 | 36 | (definterface IRange 37 | (range [^long min ^long max])) 38 | 39 | (definterface IRadix 40 | (mergeWith [b f]) 41 | (update [k f])) 42 | 43 | (defmacro ^:private compile-if [test then else] 44 | (if (eval test) 45 | then 46 | else)) 47 | 48 | (declare ->transient-int-map 49 | ^:private EMPTY-INT-MAP) 50 | 51 | (deftype PersistentIntMap 52 | [^INode root 53 | ^long epoch 54 | meta] 55 | 56 | IRange 57 | (range [_ min max] 58 | (PersistentIntMap. 59 | (or (.range root min max) Nodes$Empty/EMPTY) 60 | epoch 61 | meta)) 62 | 63 | IRadix 64 | (mergeWith [_ b f] 65 | (let [^PersistentIntMap b b 66 | epoch' (inc (Math/max (.epoch b) epoch))] 67 | (PersistentIntMap. 68 | (.merge root (.root b) epoch' f) 69 | epoch' 70 | meta))) 71 | 72 | (update [_ k f] 73 | (let [epoch' (inc epoch) 74 | root' (.update root k epoch' f)] 75 | (PersistentIntMap. root' epoch' meta))) 76 | 77 | clojure.lang.IObj 78 | (meta [_] meta) 79 | (withMeta [_ m] (PersistentIntMap. root epoch m)) 80 | 81 | clojure.lang.MapEquivalence 82 | 83 | clojure.lang.Counted 84 | (count [this] 85 | (.count root)) 86 | 87 | clojure.lang.IPersistentCollection 88 | 89 | (equiv [this x] 90 | (cond 91 | (not (instance? java.util.Map x)) 92 | false 93 | 94 | (and (instance? clojure.lang.IPersistentMap x) 95 | (not (instance? clojure.lang.MapEquivalence x))) 96 | false 97 | 98 | (not= (count this) (.size ^Map x)) 99 | false 100 | 101 | (instance? PersistentIntMap x) 102 | (loop [it1 (.iterator this) 103 | it2 (.iterator ^PersistentIntMap x)] 104 | (if (.hasNext it1) 105 | (let [^Map$Entry e1 (.next it1) 106 | ^Map$Entry e2 (.next it2)] 107 | (if (and (Util/equiv (.getKey e1) (.getKey e2)) 108 | (Util/equiv (.getValue e1) (.getValue e2))) 109 | (recur it1 it2) 110 | false)) 111 | true)) 112 | 113 | :else 114 | (loop [it (.iterator this)] 115 | (if (.hasNext it) 116 | (let [^Map$Entry e (.next it)] 117 | (if (and (.containsKey ^Map x (.getKey e)) 118 | (Util/equiv (.getValue e) (.get ^Map x (.getKey e)))) 119 | (recur it) 120 | false)) 121 | true)))) 122 | 123 | (cons [this o] 124 | (if (map? o) 125 | (reduce #(apply assoc %1 %2) this o) 126 | (.assoc this (nth o 0) (nth o 1)))) 127 | 128 | clojure.lang.Seqable 129 | (seq [this] 130 | (iterator-seq (.iterator this))) 131 | 132 | clojure.lang.Reversible 133 | (rseq [_] 134 | (iterator-seq (.iterator root INode$IterationType/ENTRIES true))) 135 | 136 | r/CollFold 137 | 138 | (coll-fold [this n combinef reducef] 139 | (#'r/fjinvoke #(try (.fold root n combinef reducef #'r/fjtask #'r/fjfork #'r/fjjoin) (catch Throwable e (.printStackTrace e))))) 140 | 141 | clojure.core.protocols.CollReduce 142 | 143 | (coll-reduce 144 | [this f] 145 | (let [x (.reduce root f (f))] 146 | (if (reduced? x) 147 | @x 148 | x))) 149 | 150 | (coll-reduce 151 | [this f val] 152 | (let [x (.reduce root f val)] 153 | (if (reduced? x) 154 | @x 155 | x))) 156 | 157 | clojure.core.protocols.IKVReduce 158 | (kv-reduce 159 | [this f val] 160 | (let [x (.kvreduce root f val)] 161 | (if (reduced? x) 162 | @x 163 | x))) 164 | 165 | Object 166 | (hashCode [this] 167 | (reduce 168 | (fn [acc [k v]] 169 | (unchecked-add acc (bit-xor (.hashCode k) (.hashCode v)))) 170 | 0 171 | (seq this))) 172 | 173 | clojure.lang.IHashEq 174 | (hasheq [this] 175 | (compile-if (resolve 'clojure.core/hash-unordered-coll) 176 | (hash-unordered-coll this) 177 | (.hashCode this))) 178 | 179 | (equals [this x] 180 | (cond 181 | (identical? this x) 182 | true 183 | 184 | (not (instance? java.util.Map x)) 185 | false 186 | 187 | (not= (count this) (.size ^Map x)) 188 | false 189 | 190 | (instance? PersistentIntMap x) 191 | (loop [it1 (.iterator this) 192 | it2 (.iterator ^PersistentIntMap x)] 193 | (if (and (.hasNext it1) (.hasNext it2)) 194 | (let [^Map$Entry e1 (.next it1) 195 | ^Map$Entry e2 (.next it2)] 196 | (if (and (Util/equals (.getKey e1) (.getKey e2)) 197 | (Util/equals (.getValue e1) (.getValue e2))) 198 | (recur it1 it2) 199 | false)) 200 | true)) 201 | 202 | :else 203 | (loop [it (.iterator this)] 204 | (if (.hasNext it) 205 | (let [^Map$Entry e (.next it)] 206 | (if e 207 | (if (and (.containsKey ^Map x (.getKey e)) 208 | (Util/equals (.getValue e) (.get ^Map x (.getKey e)))) 209 | (recur it) 210 | false) 211 | false)) 212 | true)))) 213 | 214 | (toString [this] 215 | (str (into {} this))) 216 | 217 | clojure.lang.ILookup 218 | (valAt [this k] 219 | (.valAt this k nil)) 220 | (valAt [this k default] 221 | (try 222 | (.get root (long k) default) 223 | (catch ClassCastException _ default))) 224 | 225 | clojure.lang.Associative 226 | (containsKey [this k] 227 | (not (identical? ::not-found (.valAt this k ::not-found)))) 228 | 229 | (entryAt [this k] 230 | (let [v (.valAt this k ::not-found)] 231 | (when (not= v ::not-found) 232 | (clojure.lang.MapEntry. k v)))) 233 | 234 | (assoc [this k v] 235 | (let [k (long k) 236 | epoch' (inc epoch)] 237 | (PersistentIntMap. 238 | (.assoc root (long k) epoch' default-merge v) 239 | epoch' 240 | meta))) 241 | 242 | (empty [this] 243 | (cond-> (PersistentIntMap. Nodes$Empty/EMPTY 0 nil) 244 | meta (with-meta meta))) 245 | 246 | clojure.lang.IEditableCollection 247 | (asTransient [this] 248 | (->transient-int-map root (inc epoch) meta)) 249 | 250 | java.util.Map 251 | (get [this k] 252 | (.valAt this k)) 253 | (isEmpty [this] 254 | (empty? (seq this))) 255 | (size [this] 256 | (count this)) 257 | (keySet [this] 258 | (->> this 259 | seq 260 | (map key) 261 | set)) 262 | (put [_ _ _] 263 | (throw (UnsupportedOperationException.))) 264 | (putAll [_ _] 265 | (throw (UnsupportedOperationException.))) 266 | (clear [_] 267 | (throw (UnsupportedOperationException.))) 268 | (remove [_ _] 269 | (throw (UnsupportedOperationException.))) 270 | (values [this] 271 | (->> this seq (map second))) 272 | (entrySet [this] 273 | (->> this seq set)) 274 | (iterator [this] 275 | (.iterator root INode$IterationType/ENTRIES false)) 276 | 277 | clojure.lang.IPersistentMap 278 | (assocEx [this k v] 279 | (if (contains? this k) 280 | (throw (Exception. "Key or value already present")) 281 | (assoc this k v))) 282 | (without [this k] 283 | (let [k (long k) 284 | epoch' (inc epoch)] 285 | (PersistentIntMap. 286 | (or (.dissoc root k epoch') Nodes$Empty/EMPTY) 287 | epoch' 288 | meta))) 289 | 290 | clojure.lang.IFn 291 | 292 | (invoke [this k] 293 | (.valAt this k)) 294 | 295 | (invoke [this k default] 296 | (.valAt this k default))) 297 | 298 | (def ^:private ^PersistentIntMap EMPTY-INT-MAP (PersistentIntMap. Nodes$Empty/EMPTY 0 nil)) 299 | 300 | (deftype TransientIntMap 301 | [^INode root 302 | ^long epoch 303 | meta] 304 | 305 | IRadix 306 | (mergeWith [this b f] 307 | (throw (IllegalArgumentException. "Cannot call `merge-with` on transient int-map."))) 308 | 309 | (update [this k f] 310 | (let [root' (.update root k epoch f)] 311 | (if (identical? root root') 312 | this 313 | (TransientIntMap. root' epoch meta)))) 314 | 315 | clojure.lang.IObj 316 | (meta [_] meta) 317 | (withMeta [_ m] (TransientIntMap. root (inc epoch) meta)) 318 | 319 | clojure.lang.Counted 320 | (count [this] 321 | (.count root)) 322 | 323 | clojure.lang.MapEquivalence 324 | 325 | (equiv [this x] 326 | (and (map? x) (= x (into {} this)))) 327 | 328 | clojure.lang.Seqable 329 | (seq [this] 330 | (iterator-seq (.iterator root INode$IterationType/ENTRIES false))) 331 | 332 | Object 333 | (hashCode [this] 334 | (reduce 335 | (fn [acc [k v]] 336 | (unchecked-add acc (bit-xor (hash k) (hash v)))) 337 | 0 338 | (seq this))) 339 | 340 | (equals [this x] 341 | (or (identical? this x) 342 | (and 343 | (map? x) 344 | (= x (into {} this))))) 345 | 346 | (toString [this] 347 | (str (into {} this))) 348 | 349 | clojure.lang.ILookup 350 | (valAt [this k] 351 | (.valAt this k nil)) 352 | (valAt [this k default] 353 | (try 354 | (.get root k default) 355 | (catch ClassCastException _ default))) 356 | 357 | clojure.lang.Associative 358 | (containsKey [this k] 359 | (not (identical? ::not-found (.valAt this k ::not-found)))) 360 | 361 | (entryAt [this k] 362 | (let [v (.valAt this k ::not-found)] 363 | (when (not= v ::not-found) 364 | (clojure.lang.MapEntry. k v)))) 365 | 366 | clojure.lang.ITransientMap 367 | 368 | (assoc [this k v] 369 | (let [k (long k) 370 | root' (.assoc root (long k) epoch default-merge v)] 371 | (if (identical? root' root) 372 | this 373 | (TransientIntMap. root' epoch meta)))) 374 | 375 | (conj [this o] 376 | (if (map? o) 377 | (reduce #(apply assoc! %1 %2) this o) 378 | (.assoc this (nth o 0) (nth o 1)))) 379 | 380 | (persistent [_] 381 | (PersistentIntMap. root (inc epoch) meta)) 382 | 383 | (without [this k] 384 | (let [root' (or (.dissoc root (long k) epoch) Nodes$Empty/EMPTY)] 385 | (if (identical? root' root) 386 | this 387 | (TransientIntMap. root' epoch meta)))) 388 | 389 | clojure.lang.IFn 390 | 391 | (invoke [this k] 392 | (.valAt this k)) 393 | 394 | (invoke [this k default] 395 | (.valAt this k default))) 396 | 397 | (defn- ->transient-int-map [root ^long epoch meta] 398 | (TransientIntMap. root epoch meta)) 399 | 400 | ;;; 401 | 402 | (defn int-map 403 | "Given alternating keys and values, creates an integer map that can only 404 | have integers as keys." 405 | ([] 406 | EMPTY-INT-MAP) 407 | ([a b] 408 | (assoc EMPTY-INT-MAP a b)) 409 | ([a b & rest] 410 | (apply assoc EMPTY-INT-MAP a b rest))) 411 | 412 | (defn merge-with 413 | "Merges together two int-maps, using `f` to resolve value conflicts." 414 | ([f] 415 | EMPTY-INT-MAP) 416 | ([f a b] 417 | (let [a' (if (instance? TransientIntMap a) 418 | (persistent! a) 419 | a) 420 | b' (if (instance? TransientIntMap b) 421 | (persistent! b) 422 | b)] 423 | (.mergeWith ^IRadix a' b' f))) 424 | ([f a b & rest] 425 | (reduce #(merge-with f %1 %2) (list* a b rest)))) 426 | 427 | (defn merge 428 | "Merges together two int-maps, giving precedence to values from the right-most map." 429 | ([] 430 | EMPTY-INT-MAP) 431 | ([a b] 432 | (merge-with (fn [_ b] b) a b)) 433 | ([a b & rest] 434 | (apply merge-with (fn [_ b] b) a b rest))) 435 | 436 | (defn update 437 | "Updates the value associated with the given key. If no such key exists, `f` is invoked 438 | with `nil`." 439 | ([m k f] 440 | (.update ^PersistentIntMap m k f)) 441 | ([m k f & args] 442 | (update m k #(apply f % args)))) 443 | 444 | (defn update! 445 | "A transient variant of `update`." 446 | ([m k f] 447 | (.update ^TransientIntMap m k f)) 448 | ([m k f & args] 449 | (update! m k #(apply f % args)))) 450 | 451 | (defn range 452 | "Returns a map or set representing all elements within [min, max], inclusive." 453 | [x ^long min ^long max] 454 | (.range ^IRange x min max)) 455 | 456 | ;;; 457 | 458 | (declare ->transient-int-set ->persistent-int-set) 459 | 460 | (deftype PersistentIntSet 461 | [^IntSet int-set 462 | ^long epoch 463 | meta] 464 | 465 | IRange 466 | (range [this min max] 467 | (let [epoch' (inc epoch)] 468 | (PersistentIntSet. (.range int-set epoch' min max) epoch' meta))) 469 | 470 | clojure.lang.Reversible 471 | (rseq [_] 472 | (iterator-seq (.elements int-set 0 true))) 473 | 474 | java.lang.Object 475 | (hashCode [this] 476 | (->> this 477 | (map #(bit-xor (long %) (unsigned-bit-shift-right (long %) 32))) 478 | (reduce +'))) 479 | 480 | (equals [this x] 481 | (.equiv this x)) 482 | 483 | clojure.lang.IHashEq 484 | (hasheq [this] 485 | (compile-if (resolve 'clojure.core/hash-unordered-coll) 486 | (hash-unordered-coll this) 487 | (.hashCode this))) 488 | 489 | java.util.Set 490 | (size [this] (count this)) 491 | (isEmpty [this] (zero? (count this))) 492 | (iterator [this] (clojure.lang.SeqIterator. (seq this))) 493 | (containsAll [this s] (every? #(contains? this %) s)) 494 | 495 | clojure.lang.IObj 496 | (meta [_] meta) 497 | (withMeta [this meta'] 498 | (PersistentIntSet. int-set epoch meta')) 499 | 500 | clojure.lang.IEditableCollection 501 | (asTransient [this] (->transient-int-set this)) 502 | 503 | clojure.lang.Seqable 504 | (seq [_] 505 | (iterator-seq (.elements int-set 0 false))) 506 | 507 | clojure.lang.IFn 508 | (invoke [this idx] 509 | (when (.contains this idx) 510 | idx)) 511 | 512 | clojure.lang.IPersistentSet 513 | (equiv [this x] 514 | (and 515 | (instance? java.util.Set x) 516 | (= (count this) (count x)) 517 | (every? 518 | #(contains? x %) 519 | (seq this)))) 520 | (count [_] 521 | (.count int-set)) 522 | (empty [_] 523 | (cond-> (PersistentIntSet. (IntSet. (.leafSize int-set)) 0 nil) 524 | meta (with-meta meta))) 525 | (contains [_ n] 526 | (try 527 | (.contains int-set n) 528 | (catch ClassCastException _ false))) 529 | (get [this n] 530 | (when (.contains this n) n)) 531 | (disjoin [this n] 532 | (let [epoch' (inc epoch) 533 | int-set' (.remove int-set epoch' n)] 534 | (if (identical? int-set int-set') 535 | this 536 | (PersistentIntSet. int-set' epoch' meta)))) 537 | (cons [this n] 538 | (let [epoch' (inc epoch) 539 | int-set' (.add int-set epoch' n)] 540 | (if (identical? int-set int-set') 541 | this 542 | (PersistentIntSet. int-set' epoch' meta))))) 543 | 544 | (deftype TransientIntSet 545 | [^IntSet int-set 546 | ^int epoch 547 | meta] 548 | 549 | clojure.lang.IObj 550 | (meta [_] meta) 551 | (withMeta [this meta'] 552 | (TransientIntSet. int-set epoch meta')) 553 | 554 | clojure.lang.IFn 555 | (invoke [this idx] 556 | (when (.contains this idx) 557 | idx)) 558 | 559 | clojure.lang.ITransientSet 560 | (count [_] 561 | (.count int-set)) 562 | (persistent [this] (->persistent-int-set this)) 563 | (contains [_ n] 564 | (.contains int-set n)) 565 | (disjoin [this n] 566 | (let [int-set' (.remove int-set epoch n)] 567 | (if (identical? int-set int-set') 568 | this 569 | (TransientIntSet. int-set' epoch meta)))) 570 | (conj [this n] 571 | (let [int-set' (.add int-set epoch n)] 572 | (if (identical? int-set int-set') 573 | this 574 | (TransientIntSet. int-set' epoch meta))))) 575 | 576 | (defn- ->persistent-int-set [^TransientIntSet set] 577 | (PersistentIntSet. 578 | (.int-set set) 579 | (.epoch set) 580 | (.meta set))) 581 | 582 | (defn- ->transient-int-set [^PersistentIntSet set] 583 | (TransientIntSet. 584 | (.int-set set) 585 | (inc (.epoch set)) 586 | (.meta set))) 587 | 588 | ;;; 589 | 590 | (def ^:private ^PersistentIntSet EMPTY-INT-SET (PersistentIntSet. (IntSet. 128) 0 nil)) 591 | (def ^:private ^PersistentIntSet EMPTY-DENSE-INT-SET (PersistentIntSet. (IntSet. 4096) 0 nil)) 592 | 593 | (defn int-set 594 | "Given a collection, creates an immutable set which can only store integral values. 595 | This should be used unless elements are densely clustered (each element has multiple 596 | elements within +/- 1000)." 597 | ([] 598 | EMPTY-INT-SET) 599 | ([s] 600 | (into EMPTY-INT-SET s))) 601 | 602 | (defn dense-int-set 603 | "Given a collection, creates an immutable set which can only store integral values. 604 | This should be used only if elements are densely clustered (each element has multiple 605 | elements within +/- 1000)." 606 | ([] 607 | EMPTY-DENSE-INT-SET) 608 | ([s] 609 | (into EMPTY-DENSE-INT-SET s))) 610 | 611 | (defn union 612 | "Returns the union of two bitsets." 613 | [^PersistentIntSet a ^PersistentIntSet b] 614 | (let [epoch (inc (long (Math/max (.epoch a) (.epoch b))))] 615 | (PersistentIntSet. 616 | (.union ^IntSet (.int-set a) epoch (.int-set b)) 617 | epoch 618 | nil))) 619 | 620 | (defn intersection 621 | "Returns the intersection of two bitsets." 622 | [^PersistentIntSet a ^PersistentIntSet b] 623 | (let [epoch (inc (long (Math/max (.epoch a) (.epoch b))))] 624 | (PersistentIntSet. 625 | (.intersection ^IntSet (.int-set a) epoch (.int-set b)) 626 | epoch 627 | nil))) 628 | 629 | (defn difference 630 | "Returns the difference between two bitsets." 631 | [^PersistentIntSet a ^PersistentIntSet b] 632 | (let [epoch (inc (long (Math/max (.epoch a) (.epoch b))))] 633 | (PersistentIntSet. 634 | (.difference ^IntSet (.int-set a) epoch (.int-set b)) 635 | epoch 636 | nil))) 637 | -------------------------------------------------------------------------------- /src/main/java/clojure/data/int_map/Nodes.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Zach Tellman, Rich Hickey and contributors. All rights reserved. 2 | // The use and distribution terms for this software are covered by the 3 | // Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | // which can be found in the file epl-v10.html at the root of this distribution. 5 | // By using this software in any fashion, you are agreeing to be bound by 6 | // the terms of this license. 7 | // You must not remove this notice, or any other, from this software. 8 | 9 | package clojure.data.int_map; 10 | 11 | import clojure.lang.IFn; 12 | import clojure.lang.AFn; 13 | import clojure.lang.RT; 14 | import clojure.lang.Util; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Iterator; 18 | import java.util.List; 19 | import java.util.NoSuchElementException; 20 | import java.util.concurrent.Callable; 21 | 22 | public class Nodes { 23 | 24 | static class InvertFn extends AFn { 25 | IFn f; 26 | 27 | public InvertFn(IFn f) { 28 | this.f = f; 29 | } 30 | 31 | public Object invoke(Object x, Object y) { 32 | return f.invoke(y, x); 33 | } 34 | } 35 | 36 | static public IFn invert(IFn f) { 37 | if (f instanceof InvertFn) { 38 | return ((InvertFn) f).f; 39 | } 40 | return new InvertFn(f); 41 | } 42 | 43 | // bitwise helper functions 44 | 45 | public static long lowestBit(long n) { 46 | return n & -n; 47 | } 48 | 49 | private static final byte deBruijnIndex[] = 50 | new byte[]{0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, 51 | 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, 52 | 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 53 | 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12}; 54 | 55 | public static int bitLog2(long n) { 56 | return deBruijnIndex[0xFF & (int) ((n * 0x022fdd63cc95386dL) >>> 58)]; 57 | } 58 | 59 | public static int offset(long a, long b) { 60 | return bitLog2(highestBit(a ^ b, 1)) & ~0x3; 61 | } 62 | 63 | public static long highestBit(long n, long estimate) { 64 | long x = n & ~(estimate - 1); 65 | long m; 66 | while (true) { 67 | m = lowestBit(x); 68 | if (x == m) return m; 69 | x -= m; 70 | } 71 | } 72 | 73 | // 2-way top-level branch 74 | public static class BinaryBranch implements INode { 75 | 76 | public INode a, b; 77 | 78 | public BinaryBranch(INode a, INode b) { 79 | this.a = a; 80 | this.b = b; 81 | } 82 | 83 | public long count() { 84 | return a.count() + b.count(); 85 | } 86 | 87 | public Iterator iterator(final IterationType type, final boolean reverse) { 88 | return new Iterator() { 89 | boolean first = true; 90 | Iterator iterator = reverse ? b.iterator(type, reverse) : a.iterator(type, reverse); 91 | 92 | public boolean hasNext() { 93 | if (iterator.hasNext()) { 94 | return true; 95 | } 96 | 97 | if (first) { 98 | first = false; 99 | iterator = reverse ? a.iterator(type, reverse) : b.iterator(type, reverse); 100 | } 101 | 102 | return iterator.hasNext(); 103 | } 104 | 105 | public Object next() { 106 | if (hasNext()) { 107 | return iterator.next(); 108 | } else { 109 | throw new NoSuchElementException(); 110 | } 111 | } 112 | 113 | public void remove() { 114 | throw new UnsupportedOperationException(); 115 | } 116 | }; 117 | } 118 | 119 | public INode range(long min, long max) { 120 | if (max < 0) { 121 | return a.range(min, max); 122 | } else if (min >= 0) { 123 | return b.range(min, max); 124 | } else { 125 | INode aPrime = a.range(min,max); 126 | INode bPrime = b.range(min, max); 127 | 128 | if (aPrime == null && bPrime == null) { 129 | return Empty.EMPTY; 130 | } else if (aPrime == null) { 131 | return bPrime; 132 | } else if (bPrime == null) { 133 | return aPrime; 134 | } else { 135 | return new BinaryBranch(aPrime, bPrime); 136 | } 137 | } 138 | } 139 | 140 | public INode merge(INode node, long epoch, IFn f) { 141 | if (node instanceof BinaryBranch) { 142 | BinaryBranch bin = (BinaryBranch) node; 143 | return new BinaryBranch(a.merge(bin.a, epoch, f), b.merge(bin.b, epoch, f)); 144 | } else if (node instanceof Branch) { 145 | Branch branch = (Branch) node; 146 | return branch.prefix < 0 ? new BinaryBranch(a.merge(node, epoch, f), b) : new BinaryBranch(a, b.merge(node, epoch, f)); 147 | } else { 148 | return node.merge(this, epoch, invert(f)); 149 | } 150 | } 151 | 152 | public INode assoc(long k, long epoch, IFn f, Object v) { 153 | if (k < 0) { 154 | INode aPrime = a.assoc(k, epoch, f, v); 155 | return a == aPrime ? this : new BinaryBranch(aPrime, b); 156 | } else { 157 | INode bPrime = b.assoc(k, epoch, f, v); 158 | return b == bPrime ? this : new BinaryBranch(a, bPrime); 159 | } 160 | } 161 | 162 | public INode dissoc(long k, long epoch) { 163 | if (k < 0) { 164 | INode aPrime = a.dissoc(k, epoch); 165 | return aPrime == null 166 | ? b 167 | : (a == aPrime) 168 | ? this 169 | : new BinaryBranch(aPrime, b); 170 | } else { 171 | INode bPrime = b.dissoc(k, epoch); 172 | return bPrime == null 173 | ? a 174 | : (b == bPrime) 175 | ? this 176 | : new BinaryBranch(a, bPrime); 177 | } 178 | } 179 | 180 | public INode update(long k, long epoch, IFn f) { 181 | if (k < 0) { 182 | INode aPrime = a.update(k, epoch, f); 183 | return a == aPrime ? this : new BinaryBranch(aPrime, b); 184 | } else { 185 | INode bPrime = b.update(k, epoch, f); 186 | return b == bPrime ? this : new BinaryBranch(a, bPrime); 187 | } 188 | } 189 | 190 | public Object get(long k, Object defaultVal) { 191 | return k < 0 ? a.get(k, defaultVal) : b.get(k, defaultVal); 192 | } 193 | 194 | public Object kvreduce(IFn f, Object init) { 195 | init = a.kvreduce(f, init); 196 | if (RT.isReduced(init)) return init; 197 | return b.kvreduce(f, init); 198 | } 199 | 200 | public Object reduce(IFn f, Object init) { 201 | init = a.reduce(f, init); 202 | if (RT.isReduced(init)) return init; 203 | return b.reduce(f, init); 204 | } 205 | 206 | public Object fold(final long n, final IFn combiner, final IFn reducer, final IFn fjtask, final IFn fjfork, final IFn fjjoin) { 207 | if (count() > n) { 208 | Object forked = new Callable() { 209 | public Object call() throws Exception { 210 | return b.fold(n, combiner, reducer, fjtask, fjfork, fjjoin); 211 | } 212 | }; 213 | return combiner.invoke(a.fold(n, combiner, reducer, fjtask, fjfork, fjjoin), fjjoin.invoke(fjfork.invoke(fjtask.invoke(forked)))); 214 | } else { 215 | return kvreduce(reducer, combiner.invoke()); 216 | } 217 | } 218 | } 219 | 220 | // 16-way branch node 221 | 222 | public static class Branch implements INode { 223 | public final long prefix, mask, epoch; 224 | public final int offset; 225 | long count; 226 | public final INode[] children; 227 | 228 | public Branch(long prefix, int offset, long epoch, long count, INode[] children) { 229 | this.prefix = prefix; 230 | this.offset = offset; 231 | this.epoch = epoch; 232 | this.mask = 0xfL << offset; 233 | this.count = count; 234 | this.children = children; 235 | } 236 | 237 | public Branch(long prefix, int offset, long epoch, INode[] children) { 238 | this.prefix = prefix; 239 | this.offset = offset; 240 | this.epoch = epoch; 241 | this.mask = 0xfL << offset; 242 | this.count = -1; 243 | this.children = children; 244 | } 245 | 246 | public int indexOf(long key) { 247 | return (int) ((key & mask) >>> offset); 248 | } 249 | 250 | private INode[] arraycopy() { 251 | INode[] copy = new INode[16]; 252 | System.arraycopy(children, 0, copy, 0, 16); 253 | return copy; 254 | } 255 | 256 | // returns 0 for no overlap, 1 if there's some overlap, 2 if the left completely covers the right 257 | private static int overlap(long min0, long max0, long min1, long max1) { 258 | if (min0 <= min1 && max1 <= max0) { 259 | return 2; 260 | } 261 | if (min0 <= max1 && min1 <= max0) { 262 | return 1; 263 | } 264 | return 0; 265 | } 266 | 267 | public INode range(long min, long max) { 268 | long nodeMask = offset < 60 ? ((1L << (offset + 4)) - 1) : ~(1L<<63); 269 | long nodeMin = prefix & ~nodeMask; 270 | long nodeMax = prefix | nodeMask; 271 | switch (overlap(min, max, nodeMin, nodeMax)) { 272 | case 2: 273 | return this; 274 | case 0: 275 | return null; 276 | default: 277 | } 278 | 279 | INode[] children = null; 280 | // true if already known that at least one child of this node should be removed in the result 281 | boolean atLeastOneDropped = false; 282 | 283 | int minI = min <= nodeMin ? -1 : indexOf(Math.min(nodeMax, min)); 284 | int maxI = max >= nodeMax ? 16 : indexOf(Math.max(nodeMin, max)); 285 | 286 | INode onlyChild = null; 287 | int numChildren = 0; 288 | 289 | for (int i = 0; i < 16; i++) { 290 | INode c = this.children[i]; 291 | if (c != null) { 292 | 293 | // if i is outside [minI, maxI], it must have no intersection with the range 294 | // if it is strictly inside (minI, maxI) exclusive, it must be fully in the range 295 | // otherwise it is minI or maxI, so it may be partially in the range so we have to recurse 296 | INode child = 297 | (i < minI || maxI < i) ? null : 298 | (minI < i && i < maxI) ? c : 299 | c.range(min, max); 300 | 301 | if (children != null) { 302 | children[i] = child; 303 | } else if (child == null) { 304 | if (numChildren != 0) { 305 | // dropping `child`, but the children at previous indices are all identical to this.children, so clone 306 | // indices 0..(i-1) 307 | children = this.children.clone(); 308 | children[i] = null; 309 | } else { 310 | atLeastOneDropped = true; 311 | } 312 | } else if (child != c) { 313 | children = numChildren != 0 ? this.children.clone() : new INode[16]; 314 | children[i] = child; 315 | // we could set atLeastOneChild = true here, but it will never be used since now children != null 316 | } else if (atLeastOneDropped) { 317 | children = new INode[16]; 318 | children[i] = child; 319 | // we could set atLeastOneChild = true here, but it will never be used since now children != null 320 | } 321 | if (child != null) { 322 | numChildren += 1; 323 | onlyChild = child; 324 | } 325 | } 326 | } 327 | 328 | return numChildren == 0 ? null : 329 | numChildren == 1 ? onlyChild : 330 | children == null ? this : 331 | new Branch(prefix, offset, epoch, children); 332 | } 333 | 334 | public Iterator iterator(final IterationType type, final boolean reverse) { 335 | return new Iterator() { 336 | 337 | private byte idx = (byte)(reverse ? 16 : -1); 338 | private Iterator iterator = null; 339 | 340 | private void advanceToNext() { 341 | while (reverse ? --idx >= 0 : ++idx < 16) { 342 | INode c = children[idx]; 343 | if (c != null) { 344 | iterator = children[idx].iterator(type, reverse); 345 | if (iterator.hasNext()) { 346 | return; 347 | } 348 | } 349 | } 350 | iterator = null; 351 | } 352 | 353 | public boolean hasNext() { 354 | if (iterator != null && iterator.hasNext()) { 355 | return true; 356 | } 357 | advanceToNext(); 358 | return iterator != null; 359 | } 360 | 361 | public Object next() { 362 | if (iterator != null && iterator.hasNext()) { 363 | return iterator.next(); 364 | } else { 365 | advanceToNext(); 366 | if (iterator != null) return iterator.next(); 367 | throw new NoSuchElementException(); 368 | } 369 | } 370 | 371 | public void remove() { 372 | throw new UnsupportedOperationException(); 373 | } 374 | }; 375 | } 376 | 377 | public Object get(long k, Object defaultVal) { 378 | INode n = children[indexOf(k)]; 379 | return n == null ? defaultVal : n.get(k, defaultVal); 380 | } 381 | 382 | public long count() { 383 | int count = 0; 384 | for (int i = 0; i < 16; i++) { 385 | INode n = children[i]; 386 | if (n != null) count += n.count(); 387 | } 388 | this.count = count; 389 | return count; 390 | } 391 | 392 | public INode merge(INode node, long epoch, IFn f) { 393 | if (node instanceof Branch) { 394 | Branch branch = (Branch) node; 395 | int offsetPrime = offset(prefix, branch.prefix); 396 | 397 | if (branch.prefix < 0 && this.prefix >= 0) { 398 | return new BinaryBranch(branch, this); 399 | } else if (branch.prefix >= 0 && this.prefix < 0) { 400 | return new BinaryBranch(this, branch); 401 | } 402 | 403 | if (offsetPrime > offset && offsetPrime > branch.offset) { 404 | return new Branch(prefix, offset(prefix, branch.prefix), epoch, new INode[16]) 405 | .merge(this, epoch, f) 406 | .merge(node, epoch, f); 407 | } 408 | 409 | // we contain the other node 410 | if (offset > branch.offset) { 411 | int idx = indexOf(branch.prefix); 412 | INode[] children = arraycopy(); 413 | INode n = children[idx]; 414 | children[idx] = n != null ? n.merge(node, epoch, f) : node; 415 | return new Branch(prefix, offset, epoch, children); 416 | 417 | } 418 | 419 | if (offset < branch.offset) { 420 | return branch.merge(this, epoch, invert(f)); 421 | } 422 | 423 | INode[] children = new INode[16]; 424 | INode[] branchChildren = branch.children; 425 | int offset = this.offset; 426 | 427 | for (int i = 0; i < 16; i++) { 428 | INode n = this.children[i]; 429 | INode nPrime = branchChildren[i]; 430 | if (n == null) { 431 | children[i] = nPrime; 432 | } else if (nPrime == null) { 433 | children[i] = n; 434 | } else { 435 | children[i] = n.merge(nPrime, epoch, f); 436 | } 437 | } 438 | return new Branch(prefix, offset, epoch, children); 439 | 440 | } else { 441 | return node.merge(this, epoch, invert(f)); 442 | } 443 | } 444 | 445 | public INode assoc(long k, long epoch, IFn f, Object v) { 446 | int offsetPrime = offset(k, prefix); 447 | 448 | // need a new branch above us both 449 | if (prefix < 0 && k >= 0) { 450 | return new BinaryBranch(this, new Leaf(k, v)); 451 | } else if (k < 0 && prefix >= 0) { 452 | return new BinaryBranch(new Leaf(k, v), this); 453 | } else if (offsetPrime > this.offset) { 454 | return new Branch(k, offsetPrime, epoch, new INode[16]) 455 | .merge(this, epoch, null) 456 | .assoc(k, epoch, f, v); 457 | 458 | // somewhere at or below our level 459 | } else { 460 | int idx = indexOf(k); 461 | INode n = children[idx]; 462 | if (n == null) { 463 | if (epoch == this.epoch) { 464 | children[idx] = new Leaf(k, v); 465 | count = -1; 466 | return this; 467 | } else { 468 | INode[] children = arraycopy(); 469 | children[idx] = new Leaf(k, v); 470 | return new Branch(prefix, offset, epoch, count, children); 471 | } 472 | } else { 473 | INode nPrime = n.assoc(k, epoch, f, v); 474 | if (nPrime == n) { 475 | count = -1; 476 | return this; 477 | } else { 478 | INode[] children = arraycopy(); 479 | children[idx] = nPrime; 480 | return new Branch(prefix, offset, epoch, count, children); 481 | } 482 | } 483 | } 484 | } 485 | 486 | public INode dissoc(long k, long epoch) { 487 | int idx = indexOf(k); 488 | INode n = children[idx]; 489 | if (n == null) { 490 | return this; 491 | } else { 492 | INode nPrime = n.dissoc(k, epoch); 493 | if (nPrime == n) { 494 | count = -1; 495 | return this; 496 | } else { 497 | INode[] children = arraycopy(); 498 | children[idx] = nPrime; 499 | for (int i = 0; i < 16; i++) { 500 | if (children[i] != null) { 501 | return new Branch(prefix, offset, epoch, count, children); 502 | } 503 | } 504 | return null; 505 | } 506 | } 507 | } 508 | 509 | public INode update(long k, long epoch, IFn f) { 510 | int offsetPrime = offset(k, prefix); 511 | 512 | // need a new branch above us both 513 | if (prefix < 0 && k >= 0) { 514 | return new BinaryBranch(this, new Leaf(k, f.invoke(null))); 515 | } else if (k < 0 && prefix >= 0) { 516 | return new BinaryBranch(new Leaf(k, f.invoke(null)), this); 517 | } else if (offsetPrime > this.offset) { 518 | return new Branch(k, offsetPrime, epoch, new INode[16]) 519 | .merge(this, epoch, null) 520 | .update(k, epoch, f); 521 | } 522 | 523 | int idx = indexOf(k); 524 | INode n = children[idx]; 525 | if (n == null) { 526 | if (epoch == this.epoch) { 527 | children[idx] = new Leaf(k, f.invoke(null)); 528 | count = -1; 529 | return this; 530 | } else { 531 | INode[] children = arraycopy(); 532 | children[idx] = new Leaf(k, f.invoke(null)); 533 | return new Branch(prefix, offset, epoch, count, children); 534 | } 535 | } else { 536 | INode nPrime = n.update(k, epoch, f); 537 | if (nPrime == n) { 538 | count = -1; 539 | return this; 540 | } else { 541 | INode[] children = arraycopy(); 542 | children[idx] = nPrime; 543 | return new Branch(prefix, offset, epoch, count, children); 544 | } 545 | } 546 | } 547 | 548 | public Object kvreduce(IFn f, Object init) { 549 | for (int i = 0; i < 16; i++) { 550 | INode n = children[i]; 551 | if (n != null) init = n.kvreduce(f, init); 552 | if (RT.isReduced(init)) break; 553 | } 554 | return init; 555 | } 556 | 557 | public Object reduce(IFn f, Object init) { 558 | for (int i = 0; i < 16; i++) { 559 | INode n = children[i]; 560 | if (n != null) init = n.reduce(f, init); 561 | if (RT.isReduced(init)) break; 562 | } 563 | return init; 564 | } 565 | 566 | // adapted from the PersistentHashMap.ArrayNode implementation 567 | static public Object foldTasks(List tasks, final IFn combiner, final IFn fjtask, final IFn fjfork, final IFn fjjoin) { 568 | 569 | if (tasks.isEmpty()) { 570 | return combiner.invoke(); 571 | 572 | // just wait on the one value 573 | } else if (tasks.size() == 1) { 574 | try { 575 | return tasks.get(0).call(); 576 | } catch (Exception e) { 577 | throw Util.sneakyThrow(e); 578 | } 579 | 580 | // divide and conquer 581 | } else { 582 | List t1 = tasks.subList(0, tasks.size() / 2); 583 | final List t2 = tasks.subList(tasks.size() / 2, tasks.size()); 584 | 585 | Object forked = fjfork.invoke(fjtask.invoke(new Callable() { 586 | public Object call() throws Exception { 587 | return foldTasks(t2, combiner, fjtask, fjfork, fjjoin); 588 | } 589 | })); 590 | 591 | return combiner.invoke(foldTasks(t1, combiner, fjtask, fjfork, fjjoin), fjjoin.invoke(forked)); 592 | } 593 | } 594 | 595 | public Object fold(final long n, final IFn combiner, final IFn reducer, final IFn fjtask, final IFn fjfork, final IFn fjjoin) { 596 | if (n > count()) { 597 | List tasks = new ArrayList(); 598 | for (int i = 0; i < 16; i++) { 599 | final INode node = children[i]; 600 | if (node != null) { 601 | tasks.add(new Callable() { 602 | public Object call() throws Exception { 603 | return node.fold(n, combiner, reducer, fjtask, fjfork, fjjoin); 604 | } 605 | }); 606 | } 607 | } 608 | return foldTasks(tasks, combiner, fjtask, fjfork, fjjoin); 609 | } else { 610 | return kvreduce(reducer, combiner.invoke()); 611 | } 612 | } 613 | } 614 | 615 | // leaf node 616 | public static class Leaf implements INode { 617 | public final long key; 618 | public final Object value; 619 | 620 | public Leaf(long key, Object value) { 621 | this.key = key; 622 | this.value = value; 623 | } 624 | 625 | public Iterator iterator(final IterationType type, boolean reverse) { 626 | return new Iterator() { 627 | 628 | boolean iterated = false; 629 | 630 | public boolean hasNext() { 631 | return !iterated; 632 | } 633 | 634 | public Object next() { 635 | if (iterated) { 636 | throw new NoSuchElementException(); 637 | } else { 638 | iterated = true; 639 | switch(type) { 640 | case KEYS: 641 | return key; 642 | case VALS: 643 | return value; 644 | case ENTRIES: 645 | return new clojure.lang.MapEntry(key, value); 646 | default: 647 | throw new IllegalStateException(); 648 | } 649 | } 650 | } 651 | 652 | public void remove() { 653 | throw new UnsupportedOperationException(); 654 | } 655 | }; 656 | } 657 | 658 | public INode range(long min, long max) { 659 | return (min <= key && key <= max) ? this : null; 660 | } 661 | 662 | public Object reduce(IFn f, Object init) { 663 | return f.invoke(init, new clojure.lang.MapEntry(key, value)); 664 | } 665 | 666 | public Object kvreduce(IFn f, Object init) { 667 | return f.invoke(init, key, value); 668 | } 669 | 670 | public Object fold(long n, IFn combiner, IFn reducer, IFn fjtask, IFn fjfork, IFn fjjoin) { 671 | return kvreduce(reducer, combiner.invoke()); 672 | } 673 | 674 | public long count() { 675 | return 1; 676 | } 677 | 678 | public INode merge(INode node, long epoch, IFn f) { 679 | return node.assoc(key, epoch, invert(f), value); 680 | } 681 | 682 | public INode assoc(long k, long epoch, IFn f, Object v) { 683 | if (k == key) { 684 | v = f == null ? v : f.invoke(value, v); 685 | return new Leaf(k, v); 686 | } else if (key < 0 && k >= 0) { 687 | return new BinaryBranch(this, new Leaf(k, v)); 688 | } else if (k < 0 && key >= 0) { 689 | return new BinaryBranch(new Leaf(k, v), this); 690 | } else { 691 | return new Branch(k, offset(k, key), epoch, new INode[16]) 692 | .assoc(key, epoch, f, value) 693 | .assoc(k, epoch, f, v); 694 | } 695 | } 696 | 697 | public INode dissoc(long k, long epoch) { 698 | if (key == k) { 699 | return null; 700 | } else { 701 | return this; 702 | } 703 | } 704 | 705 | public INode update(long k, long epoch, IFn f) { 706 | if (k == key) { 707 | Object v = f.invoke(value); 708 | return new Leaf(k, v); 709 | } else { 710 | return this.assoc(k, epoch, null, f.invoke(null)); 711 | } 712 | } 713 | 714 | public Object get(long k, Object defaultVal) { 715 | if (k == key) return value; 716 | return defaultVal; 717 | } 718 | } 719 | 720 | // empty node 721 | public static class Empty implements INode { 722 | 723 | public static Empty EMPTY = new Empty(); 724 | 725 | Empty() { 726 | } 727 | 728 | public INode range(long min, long max) { 729 | return this; 730 | } 731 | 732 | public Iterator iterator(IterationType type, boolean reverse) { 733 | return new Iterator() { 734 | 735 | public boolean hasNext() { 736 | return false; 737 | } 738 | 739 | public Object next() { 740 | throw new NoSuchElementException(); 741 | } 742 | 743 | public void remove() { 744 | throw new UnsupportedOperationException(); 745 | } 746 | }; 747 | } 748 | 749 | public Object reduce(IFn f, Object init) { 750 | return init; 751 | } 752 | 753 | public Object kvreduce(IFn f, Object init) { 754 | return init; 755 | } 756 | 757 | public Object fold(long n, IFn combiner, IFn reducer, IFn fjtask, IFn fjfork, IFn fjjoin) { 758 | return combiner.invoke(); 759 | } 760 | 761 | public long count() { 762 | return 0; 763 | } 764 | 765 | public INode merge(INode node, long epoch, IFn f) { 766 | return node; 767 | } 768 | 769 | public INode assoc(long k, long epoch, IFn f, Object v) { 770 | return new Leaf(k, v); 771 | } 772 | 773 | public INode dissoc(long k, long epoch) { 774 | return this; 775 | } 776 | 777 | public INode update(long k, long epoch, IFn f) { 778 | return new Leaf(k, f.invoke(null)); 779 | } 780 | 781 | public Object get(long k, Object defaultVal) { 782 | return defaultVal; 783 | } 784 | } 785 | } 786 | --------------------------------------------------------------------------------