├── .circleci └── config.yml ├── .github └── CODEOWNERS ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── ORIGINATOR ├── README.md ├── bin └── kaocha ├── deps.edn ├── project.clj ├── src ├── data_readers.cljc └── flatland │ └── ordered │ ├── common.clj │ ├── map.clj │ ├── map.cljs │ └── set.clj ├── tasks.clj ├── test └── flatland │ └── ordered │ ├── map_test.cljc │ ├── performance_test.clj │ └── set_test.clj └── tests.edn /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | workflows: 4 | version: 2 5 | default: 6 | jobs: 7 | - reflection: 8 | filters: 9 | tags: 10 | only: /.*/ 11 | 12 | - openjdk-8: 13 | filters: 14 | tags: 15 | only: /.*/ 16 | 17 | - openjdk-11: 18 | filters: 19 | tags: 20 | only: /.*/ 21 | 22 | - deploy: 23 | requires: [openjdk-8, openjdk-11, reflection] 24 | filters: 25 | tags: 26 | only: /Release-.*/ 27 | context: 28 | - CLOJARS_DEPLOY 29 | 30 | commands: 31 | setup-project: 32 | description: "Setup project" 33 | steps: 34 | - checkout 35 | 36 | # Download and cache dependencies 37 | - restore_cache: 38 | keys: 39 | - v1-dependencies-{{ checksum "project.clj" }} 40 | 41 | - run: 42 | name: Fetch dependencies 43 | command: | 44 | lein depsall 45 | 46 | - save_cache: 47 | paths: 48 | - ~/.m2 49 | key: v1-dependencies-{{ checksum "project.clj" }} 50 | 51 | run-tests: 52 | description: "Run tests" 53 | steps: 54 | - run: 55 | name: Run JVM tests with clojure 1.8 56 | command: | 57 | lein with-profile +1.8 test 58 | - run: 59 | name: Run JVM tests with clojure 1.9 60 | command: | 61 | lein with-profile +1.9 test 62 | - run: 63 | name: Run JVM tests with clojure 1.10.0 64 | command: | 65 | lein with-profile +1.10.0 test 66 | - run: 67 | name: Run JVM tests with clojure 1.10.1 68 | command: | 69 | lein with-profile +1.10.1 test 70 | 71 | run-reflection: 72 | description: "Ensure no reflection warnings" 73 | steps: 74 | - run: "! lein check 2>&1 | grep 'Reflection warning'" 75 | 76 | jobs: 77 | reflection: 78 | docker: 79 | - image: circleci/clojure:openjdk-11-lein-2.9.1-node 80 | working_directory: ~/repo 81 | 82 | steps: 83 | - setup-project 84 | - run-reflection 85 | 86 | openjdk-8: 87 | docker: 88 | - image: circleci/clojure:openjdk-8-lein-2.9.1-node 89 | working_directory: ~/repo 90 | 91 | environment: 92 | JVM_OPTS: -Xmx3200m 93 | 94 | steps: 95 | - setup-project 96 | - run-tests 97 | 98 | openjdk-11: 99 | docker: 100 | - image: circleci/clojure:openjdk-11-lein-2.9.1-node 101 | working_directory: ~/repo 102 | 103 | environment: 104 | JVM_OPTS: -Xmx3200m 105 | 106 | steps: 107 | - setup-project 108 | - run-tests 109 | 110 | deploy: 111 | docker: 112 | # specify the version you desire here 113 | - image: circleci/clojure:openjdk-8-lein-2.9.1 114 | # Specify service dependencies here if necessary 115 | # CircleCI maintains a library of pre-built images 116 | # documented at https://circleci.com/docs/2.0/circleci-images/ 117 | # - image: circleci/postgres:9.4 118 | 119 | working_directory: ~/repo 120 | 121 | environment: 122 | LEIN_ROOT: "true" 123 | # Customize the JVM maximum heap limit 124 | JVM_OPTS: -Xmx3200m 125 | 126 | steps: 127 | - checkout 128 | 129 | # Download and cache dependencies 130 | - restore_cache: 131 | keys: 132 | - v1-dependencies-{{ checksum "project.clj" }} 133 | # fallback to using the latest cache if no exact match is found 134 | - v1-dependencies- 135 | 136 | # Download and cache dependencies 137 | - restore_cache: 138 | keys: 139 | - v1-dependencies-{{ checksum "project.clj" }} 140 | # fallback to using the latest cache if no exact match is found 141 | - v1-dependencies- 142 | 143 | - run: 144 | name: Install babashka 145 | command: | 146 | curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install -o install.sh 147 | sudo bash install.sh 148 | rm install.sh 149 | - run: 150 | name: Install deployment-script 151 | command: | 152 | curl -s https://raw.githubusercontent.com/clj-commons/infra/main/deployment/circle-maybe-deploy.bb -o circle-maybe-deploy.bb 153 | chmod a+x circle-maybe-deploy.bb 154 | 155 | - run: lein deps 156 | 157 | - run: 158 | name: Setup GPG signing key 159 | command: | 160 | GNUPGHOME="$HOME/.gnupg" 161 | export GNUPGHOME 162 | mkdir -p "$GNUPGHOME" 163 | chmod 0700 "$GNUPGHOME" 164 | 165 | echo "$GPG_KEY" \ 166 | | base64 --decode --ignore-garbage \ 167 | | gpg --batch --allow-secret-key-import --import 168 | 169 | gpg --keyid-format LONG --list-secret-keys 170 | 171 | - save_cache: 172 | paths: 173 | - ~/.m2 174 | key: v1-dependencies-{{ checksum "project.clj" }} 175 | - run: 176 | name: Deploy 177 | command: | 178 | GPG_TTY=$(tty) 179 | export GPG_TTY 180 | echo $GPG_TTY 181 | ./circle-maybe-deploy.bb lein deploy clojars 182 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @danielcompton 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *.jar 4 | *.war 5 | target 6 | .lein* 7 | docs 8 | tests.edn 9 | .cljs_node_repl 10 | .cpcache 11 | .dir-locals.el 12 | .nrepl-port 13 | node_modules 14 | out 15 | package-lock.json 16 | package.json 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 1.15.12 - 2024-05-13 10 | 11 | * Fix NPE when hashing ordered-set that contain `nil`. 12 | 13 | ## 1.15.11 - 2023-03-26 14 | 15 | * Solve native image size problem by removing runtime `resolve` [#71](https://github.com/clj-commons/ordered/issues/71) 16 | 17 | ## 1.15.10 - 2021-10-09 18 | 19 | ### Added 20 | 21 | * Added support for ClojureScript: [#59](https://github.com/clj-commons/ordered/pull/59) 22 | * Automatic deploys from CI are now enabled for `Release-` tags: [#65](https://github.com/clj-commons/ordered/pull/65) 23 | 24 | ### Fixed 25 | 26 | * Fixed data reader importing issues: [#56](https://github.com/clj-commons/ordered/pull/56) 27 | 28 | ## 1.5.9 - 2020-04-14 29 | 30 | ### Fixed 31 | 32 | * Fixed reflection warnings: [#51](https://github.com/clj-commons/ordered/pull/51) 33 | 34 | ## 1.5.8 - 2020-04-13 35 | 36 | ### Changed 37 | 38 | * Updated README, CircleCI configuration. 39 | 40 | ## 1.5.7 - 2018-12-07 41 | 42 | ### Fixed 43 | 44 | * Type hint toArray to be compatible with JDK 11: [#37](https://github.com/clj-commons/ordered/pull/37) 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF 5 | THE PROGRAM 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 12 | documentation distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | 16 | i) changes to the Program, and 17 | 18 | ii) additions to the Program; 19 | 20 | where such changes and/or additions to the Program originate from and 21 | are distributed by that particular Contributor. A Contribution 22 | 'originates' from a Contributor if it was added to the Program by such 23 | Contributor itself or anyone acting on such Contributor's 24 | behalf. Contributions do not include additions to the Program which: 25 | (i) are separate modules of software distributed in conjunction with 26 | the Program under their own license agreement, and (ii) are not 27 | derivative works of the Program. 28 | 29 | "Contributor" means any person or entity that distributes the Program. 30 | 31 | "Licensed Patents" mean patent claims licensable by a Contributor 32 | which are necessarily infringed by the use or sale of its Contribution 33 | alone or when combined with the Program. 34 | 35 | "Program" means the Contributions distributed in accordance with this 36 | Agreement. 37 | 38 | "Recipient" means anyone who receives the Program under this 39 | Agreement, including all Contributors. 40 | 41 | 2. GRANT OF RIGHTS 42 | 43 | a) Subject to the terms of this Agreement, each Contributor hereby 44 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 45 | license to reproduce, prepare derivative works of, publicly display, 46 | publicly perform, distribute and sublicense the Contribution of such 47 | Contributor, if any, and such derivative works, in source code and 48 | object code form. 49 | 50 | b) Subject to the terms of this Agreement, each Contributor hereby 51 | grants Recipient a non-exclusive, worldwide, royalty-free patent 52 | license under Licensed Patents to make, use, sell, offer to sell, 53 | import and otherwise transfer the Contribution of such Contributor, if 54 | any, in source code and object code form. This patent license shall 55 | apply to the combination of the Contribution and the Program if, at 56 | the time the Contribution is added by the Contributor, such addition 57 | of the Contribution causes such combination to be covered by the 58 | Licensed Patents. The patent license shall not apply to any other 59 | combinations which include the Contribution. No hardware per se is 60 | licensed hereunder. 61 | 62 | c) Recipient understands that although each Contributor grants the 63 | licenses to its Contributions set forth herein, no assurances are 64 | provided by any Contributor that the Program does not infringe the 65 | patent or other intellectual property rights of any other entity. Each 66 | Contributor disclaims any liability to Recipient for claims brought by 67 | any other entity based on infringement of intellectual property rights 68 | or otherwise. As a condition to exercising the rights and licenses 69 | granted hereunder, each Recipient hereby assumes sole responsibility 70 | to secure any other intellectual property rights needed, if any. For 71 | example, if a third party patent license is required to allow 72 | Recipient to distribute the Program, it is Recipient's responsibility 73 | to acquire that license before distributing the Program. 74 | 75 | d) Each Contributor represents that to its knowledge it has sufficient 76 | copyright rights in its Contribution, if any, to grant the copyright 77 | license set forth in this Agreement. 78 | 79 | 3. REQUIREMENTS 80 | 81 | A Contributor may choose to distribute the Program in object code form 82 | under its own license agreement, provided that: 83 | 84 | a) it complies with the terms and conditions of this Agreement; and 85 | 86 | b) its license agreement: 87 | 88 | i) effectively disclaims on behalf of all Contributors all warranties 89 | and conditions, express and implied, including warranties or 90 | conditions of title and non-infringement, and implied warranties or 91 | conditions of merchantability and fitness for a particular purpose; 92 | 93 | ii) effectively excludes on behalf of all Contributors all liability 94 | for damages, including direct, indirect, special, incidental and 95 | consequential damages, such as lost profits; 96 | 97 | iii) states that any provisions which differ from this Agreement are 98 | offered by that Contributor alone and not by any other party; and 99 | 100 | iv) states that source code for the Program is available from such 101 | Contributor, and informs licensees how to obtain it in a reasonable 102 | manner on or through a medium customarily used for software exchange. 103 | 104 | When the Program is made available in source code form: 105 | 106 | a) it must be made available under this Agreement; and 107 | 108 | b) a copy of this Agreement must be included with each copy of the Program. 109 | 110 | Contributors may not remove or alter any copyright notices contained 111 | within the Program. 112 | 113 | Each Contributor must identify itself as the originator of its 114 | Contribution, if any, in a manner that reasonably allows subsequent 115 | Recipients to identify the originator of the Contribution. 116 | 117 | 4. COMMERCIAL DISTRIBUTION 118 | 119 | Commercial distributors of software may accept certain 120 | responsibilities with respect to end users, business partners and the 121 | like. While this license is intended to facilitate the commercial use 122 | of the Program, the Contributor who includes the Program in a 123 | commercial product offering should do so in a manner which does not 124 | create potential liability for other Contributors. Therefore, if a 125 | Contributor includes the Program in a commercial product offering, 126 | such Contributor ("Commercial Contributor") hereby agrees to defend 127 | and indemnify every other Contributor ("Indemnified Contributor") 128 | against any losses, damages and costs (collectively "Losses") arising 129 | from claims, lawsuits and other legal actions brought by a third party 130 | against the Indemnified Contributor to the extent caused by the acts 131 | or omissions of such Commercial Contributor in connection with its 132 | distribution of the Program in a commercial product offering. The 133 | obligations in this section do not apply to any claims or Losses 134 | relating to any actual or alleged intellectual property 135 | infringement. In order to qualify, an Indemnified Contributor must: a) 136 | promptly notify the Commercial Contributor in writing of such claim, 137 | and b) allow the Commercial Contributor tocontrol, and cooperate with 138 | the Commercial Contributor in, the defense and any related settlement 139 | negotiations. The Indemnified Contributor may participate in any such 140 | claim at its own expense. 141 | 142 | For example, a Contributor might include the Program in a commercial 143 | product offering, Product X. That Contributor is then a Commercial 144 | Contributor. If that Commercial Contributor then makes performance 145 | claims, or offers warranties related to Product X, those performance 146 | claims and warranties are such Commercial Contributor's responsibility 147 | alone. Under this section, the Commercial Contributor would have to 148 | defend claims against the other Contributors related to those 149 | performance claims and warranties, and if a court requires any other 150 | Contributor to pay any damages as a result, the Commercial Contributor 151 | must pay those damages. 152 | 153 | 5. NO WARRANTY 154 | 155 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 156 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 157 | KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY 158 | WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 159 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 160 | responsible for determining the appropriateness of using and 161 | distributing the Program and assumes all risks associated with its 162 | exercise of rights under this Agreement , including but not limited to 163 | the risks and costs of program errors, compliance with applicable 164 | laws, damage to or loss of data, programs or equipment, and 165 | unavailability or interruption of operations. 166 | 167 | 6. DISCLAIMER OF LIABILITY 168 | 169 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR 170 | ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 171 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 172 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 173 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 174 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 175 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 176 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 177 | 178 | 7. GENERAL 179 | 180 | If any provision of this Agreement is invalid or unenforceable under 181 | applicable law, it shall not affect the validity or enforceability of 182 | the remainder of the terms of this Agreement, and without further 183 | action by the parties hereto, such provision shall be reformed to the 184 | minimum extent necessary to make such provision valid and enforceable. 185 | 186 | If Recipient institutes patent litigation against any entity 187 | (including a cross-claim or counterclaim in a lawsuit) alleging that 188 | the Program itself (excluding combinations of the Program with other 189 | software or hardware) infringes such Recipient's patent(s), then such 190 | Recipient's rights granted under Section 2(b) shall terminate as of 191 | the date such litigation is filed. 192 | 193 | All Recipient's rights under this Agreement shall terminate if it 194 | fails to comply with any of the material terms or conditions of this 195 | Agreement and does not cure such failure in a reasonable period of 196 | time after becoming aware of such noncompliance. If all Recipient's 197 | rights under this Agreement terminate, Recipient agrees to cease use 198 | and distribution of the Program as soon as reasonably 199 | practicable. However, Recipient's obligations under this Agreement and 200 | any licenses granted by Recipient relating to the Program shall 201 | continue and survive. 202 | 203 | Everyone is permitted to copy and distribute copies of this Agreement, 204 | but in order to avoid inconsistency the Agreement is copyrighted and 205 | may only be modified in the following manner. The Agreement Steward 206 | reserves the right to publish new versions (including revisions) of 207 | this Agreement from time to time. No one other than the Agreement 208 | Steward has the right to modify this Agreement. The Eclipse Foundation 209 | is the initial Agreement Steward. The Eclipse Foundation may assign 210 | the responsibility to serve as the Agreement Steward to a suitable 211 | separate entity. Each new version of the Agreement will be given a 212 | distinguishing version number. The Program (including Contributions) 213 | may always be distributed subject to the version of the Agreement 214 | under which it was received. In addition, after a new version of the 215 | Agreement is published, Contributor may elect to distribute the 216 | Program (including its Contributions) under the new version. Except as 217 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives 218 | no rights or licenses to the intellectual property of any Contributor 219 | under this Agreement, whether expressly, by implication, estoppel or 220 | otherwise. All rights in the Program not expressly granted under this 221 | Agreement are reserved. 222 | 223 | This Agreement is governed by the laws of the State of Washington and 224 | the intellectual property laws of the United States of America. No 225 | party to this Agreement will bring a legal action under this Agreement 226 | more than one year after the cause of action arose. Each party waives 227 | its rights to a jury trial in any resulting litigation. 228 | -------------------------------------------------------------------------------- /ORIGINATOR: -------------------------------------------------------------------------------- 1 | @amalloy 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Clojars Project](https://img.shields.io/clojars/v/org.flatland/ordered.svg)](https://clojars.org/org.flatland/ordered) 2 | [![cljdoc badge](https://cljdoc.org/badge/org.flatland/ordered)](https://cljdoc.org/d/org.flatland/ordered/CURRENT) 3 | [![CircleCI Status](https://circleci.com/gh/clj-commons/ordered.svg?style=svg)](https://circleci.com/gh/clj-commons/ordered) 4 | 5 | ordered provides sets and maps that maintain the insertion order of their contents. 6 | 7 | ## Sets 8 | 9 | ```clojure 10 | (use 'flatland.ordered.set) 11 | 12 | (ordered-set 4 3 1 8 2) 13 | => #ordered/set (4 3 1 8 2) 14 | 15 | (conj (ordered-set 9 10) 1 2 3) 16 | => #ordered/set (9 10 1 2 3) 17 | 18 | (into (ordered-set) [7 6 1 5 6]) 19 | => #ordered/set (7 6 1 5) 20 | 21 | (disj (ordered-set 8 1 7 2 6) 7) 22 | => #ordered/set (8 1 2 6) 23 | 24 | ;; Adding an element already in an ordered set does not change its 25 | ;; position in the order. 26 | (conj (ordered-set 4 3 1 8 2) 8) 27 | => #ordered/set (4 3 1 8 2) 28 | 29 | ;; Removing an element, then adding it back in, puts it at the end, 30 | ;; just as it would if it were never part of the set. 31 | (-> (ordered-set 4 3 1 8 2) (disj 8) (conj 8)) 32 | => #ordered/set (4 3 1 2 8) 33 | ``` 34 | 35 | ## Maps 36 | 37 | ```clojure 38 | (use 'flatland.ordered.map) 39 | 40 | (ordered-map :b 2 :a 1 :d 4) 41 | => #ordered/map ([:b 2] [:a 1] [:d 4]) 42 | 43 | (assoc (ordered-map :b 2 :a 1 :d 4) :c 3) 44 | => #ordered/map ([:b 2] [:a 1] [:d 4] [:c 3]) 45 | 46 | (into (ordered-map) [[:c 3] [:a 1] [:d 4]]) 47 | => #ordered/map ([:c 3] [:a 1] [:d 4]) 48 | 49 | (dissoc (ordered-map :c 3 :a 1 :d 4) :a) 50 | => #ordered/map ([:c 3] [:d 4]) 51 | 52 | ;; Adding a key already in an ordered map does not change its position 53 | ;; in the order. 54 | (assoc (ordered-map :b 2 :a 1 :d 4) :b 7) 55 | => #ordered/map ([:b 7] [:a 1] [:d 4]) 56 | 57 | ;; Removing a key, then adding it back in, puts it at the end, just as 58 | ;; it would if it were never part of the map. 59 | (-> (ordered-map :b 2 :a 1 :d 4) (dissoc :b) (assoc :b 7)) 60 | => #ordered/map ([:a 1] [:d 4] [:b 7]) 61 | ``` 62 | 63 | ## History 64 | 65 | ordered was originally created by [Alan Malloy](https://github.com/amalloy) and was part of the [flatland](https://github.com/flatland) organisation. In December 2018 it was moved to CLJ Commons for continued maintenance. 66 | 67 | It could previously be found at [amalloy/ordered](https://github.com/amalloy/ordered) and [flatland/ordered](https://github.com/flatland/ordered). [clj-commons/ordered](https://github.com/clj-commons/ordered) is the canonical repository now. 68 | -------------------------------------------------------------------------------- /bin/kaocha: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -d node_modules/ws ] || npm install ws 4 | 5 | clojure -A:test -M -m kaocha.runner "$@" -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.10.3"}} 3 | :aliases 4 | {:test {:extra-paths ["test"] 5 | :extra-deps {lambdaisland/kaocha {:mvn/version "RELEASE"} 6 | lambdaisland/kaocha-cljs {:mvn/version "RELEASE"} 7 | org.clojure/clojurescript {:mvn/version "RELEASE"}}}}} 8 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject org.flatland/ordered (or (System/getenv "PROJECT_VERSION") "1.5.10") 2 | :url "https://github.com/clj-commons/ordered" 3 | :license {:name "Eclipse Public License - v 1.0" 4 | :url "http://www.eclipse.org/legal/epl-v10.html"} 5 | :description "Pure-clojure implementation of ruby's ordered hash and set types - instead of sorting by key, these collections retain insertion order." 6 | :dependencies [[org.clojure/clojure "1.10.1"]] 7 | :aliases {"testall" ["with-profile" "+1.8:+1.9:+1.10.0:+1.10.1" "test"] 8 | "depsall" ["with-profile" "+1.8:+1.9:+1.10.0:+1.10.1" "deps"]} 9 | :deploy-repositories [["clojars" {:url "https://repo.clojars.org" 10 | :username :env/clojars_username 11 | :password :env/clojars_ordered_password 12 | :sign-releases true}]] 13 | :profiles {:1.10.1 {:dependencies [[org.clojure/clojure "1.10.1"]]} 14 | :1.10.0 {:dependencies [[org.clojure/clojure "1.10.0"]]} 15 | :1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]} 16 | :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} 17 | ;; Not required except for benchmarks. 18 | :dev {:dependencies [[ordered-collections "0.4.2"]]} }) 19 | -------------------------------------------------------------------------------- /src/data_readers.cljc: -------------------------------------------------------------------------------- 1 | {ordered/set flatland.ordered.set/into-ordered-set 2 | ordered/map #?(:clj flatland.ordered.map/ordered-map-reader-clj 3 | :cljs flatland.ordered.map/ordered-map-reader-cljs)} 4 | -------------------------------------------------------------------------------- /src/flatland/ordered/common.clj: -------------------------------------------------------------------------------- 1 | (ns flatland.ordered.common) 2 | 3 | (defmacro change! [field f & args] 4 | `(set! ~field (~f ~field ~@args))) 5 | 6 | (defprotocol Compactable 7 | (compact [this])) 8 | -------------------------------------------------------------------------------- /src/flatland/ordered/map.clj: -------------------------------------------------------------------------------- 1 | (ns flatland.ordered.map 2 | (:require [flatland.ordered.common :refer [change! Compactable compact]] 3 | [flatland.ordered.set :refer [ordered-set]]) 4 | (:require [clojure.string :as s]) 5 | (:import (clojure.lang APersistentMap 6 | IPersistentMap 7 | IPersistentVector 8 | IEditableCollection 9 | ITransientMap 10 | ITransientVector 11 | IHashEq 12 | IObj 13 | IFn 14 | MapEquivalence 15 | Reversible 16 | MapEntry 17 | ;; Indexed, maybe add later? 18 | ;; Sorted almost certainly not accurate 19 | ) 20 | (java.util Map Map$Entry))) 21 | 22 | (set! *warn-on-reflection* true) 23 | 24 | (defn entry [k v i] 25 | (MapEntry. k (MapEntry. i v))) 26 | 27 | (declare transient-ordered-map) 28 | 29 | (deftype OrderedMap [^clojure.lang.IPersistentMap backing-map 30 | ^clojure.lang.IPersistentVector order] 31 | 32 | ;; tagging interfaces 33 | MapEquivalence 34 | 35 | IPersistentMap 36 | (equiv [this other] 37 | (and (instance? Map other) 38 | (= (.count this) (.size ^Map other)) 39 | (every? (fn [^MapEntry e] 40 | (let [k (.key e)] 41 | (and (.containsKey ^Map other k) 42 | (= (.val e) (.get ^Map other k))))) 43 | (.seq this)))) 44 | (entryAt [this k] 45 | (let [v (get this k ::not-found)] 46 | (when (not= v ::not-found) 47 | (MapEntry. k v)))) 48 | (valAt [this k] 49 | (.valAt this k nil)) 50 | (valAt [this k not-found] 51 | (if-let [^MapEntry e (.get ^Map backing-map k)] 52 | (.val e) 53 | not-found)) 54 | (count [this] 55 | (.count backing-map)) 56 | (empty [this] 57 | (OrderedMap. (-> {} (with-meta (meta backing-map))) [])) 58 | (cons [this obj] 59 | (condp instance? obj 60 | Map$Entry (let [^Map$Entry e obj] 61 | (.assoc this (.getKey e) (.getValue e))) 62 | IPersistentVector (if (= 2 (count obj)) 63 | (.assoc this (nth obj 0) (nth obj 1)) 64 | (throw (IllegalArgumentException. 65 | "Vector arg to map conj must be a pair"))) 66 | (persistent! (reduce (fn [^ITransientMap m ^Map$Entry e] 67 | (.assoc m (.getKey e) (.getValue e))) 68 | (transient this) 69 | obj)))) 70 | 71 | (assoc [this k v] 72 | (if-let [^MapEntry e (.get ^Map backing-map k)] 73 | (let [old-v (.val e)] 74 | (if (identical? old-v v) 75 | this 76 | (let [i (.key e)] 77 | (OrderedMap. (.cons backing-map (entry k v i)) 78 | (.assoc order i (MapEntry. k v)))))) 79 | (OrderedMap. (.cons backing-map (entry k v (.count order))) 80 | (.cons order (MapEntry. k v))))) 81 | (without [this k] 82 | (if-let [^MapEntry e (.get ^Map backing-map k)] 83 | (OrderedMap. (.without backing-map k) 84 | (.assoc order (.key e) nil)) 85 | this)) 86 | (seq [this] 87 | (seq (keep identity order))) 88 | (iterator [this] 89 | (clojure.lang.SeqIterator. (.seq this))) 90 | (entrySet [this] 91 | ;; not performant, but i'm not going to implement another whole java interface from scratch just 92 | ;; because rich won't let us inherit from AbstractSet 93 | (apply ordered-set this)) 94 | 95 | IFn 96 | (invoke [this k] 97 | (.valAt this k)) 98 | (invoke [this k not-found] 99 | (.valAt this k not-found)) 100 | 101 | Map 102 | (size [this] 103 | (.size ^Map backing-map)) 104 | (containsKey [this k] 105 | (.containsKey backing-map k)) 106 | (isEmpty [this] 107 | (.isEmpty ^Map backing-map)) 108 | (keySet [this] 109 | (.keySet ^Map backing-map)) 110 | (get [this k] 111 | (.valAt this k)) 112 | (containsValue [this v] 113 | (boolean (seq (filter #(= % v) (.values this))))) 114 | (values [this] 115 | (map (comp val val) (.seq this))) 116 | 117 | Object 118 | (toString [this] 119 | (str "{" (s/join ", " (for [[k v] this] (str k " " v))) "}")) 120 | (equals [this other] 121 | (.equiv this other)) 122 | (hashCode [this] 123 | (APersistentMap/mapHash this)) 124 | IHashEq 125 | (hasheq [this] 126 | (hash-unordered-coll this)) 127 | 128 | IObj 129 | (meta [this] 130 | (.meta ^IObj backing-map)) 131 | (withMeta [this m] 132 | (OrderedMap. (.withMeta ^IObj backing-map m) order)) 133 | 134 | IEditableCollection 135 | (asTransient [this] 136 | (transient-ordered-map this)) 137 | 138 | Reversible 139 | (rseq [this] 140 | (seq (keep identity (rseq order)))) 141 | 142 | Compactable 143 | (compact [this] 144 | (into (empty this) this))) 145 | 146 | (def ^{:private true, 147 | :tag OrderedMap} empty-ordered-map (empty (OrderedMap. nil nil))) 148 | 149 | (defn ordered-map 150 | "Return a map with the given keys and values, whose entries are 151 | sorted in the order that keys are added. assoc'ing a key that is 152 | already in an ordered map leaves its order unchanged. dissoc'ing a 153 | key and then later assoc'ing it puts it at the end, as if it were 154 | assoc'ed for the first time. Supports transient." 155 | ([] empty-ordered-map) 156 | ([coll] 157 | (into empty-ordered-map coll)) 158 | ([k v & more] 159 | (apply assoc empty-ordered-map k v more))) 160 | 161 | ;; contains? is broken for transients. we could define a closure around a gensym 162 | ;; to use as the not-found argument to a get, but deftype can't be a closure. 163 | ;; instead, we pass `this` as the not-found argument and hope nobody makes a 164 | ;; transient contain itself. 165 | 166 | (deftype TransientOrderedMap [^{:unsynchronized-mutable true, :tag ITransientMap} backing-map, 167 | ^{:unsynchronized-mutable true, :tag ITransientVector} order] 168 | ITransientMap 169 | (count [this] 170 | (. backing-map (count))) 171 | (valAt [this k] 172 | (.valAt this k nil)) 173 | (valAt [this k not-found] 174 | (if-let [^MapEntry e (.valAt backing-map k)] 175 | (.val e) 176 | not-found)) 177 | (assoc [this k v] 178 | (let [^MapEntry e (.valAt backing-map k this) 179 | vector-entry (MapEntry. k v) 180 | i (if (identical? e this) 181 | (do (change! order .conj vector-entry) 182 | (dec (.count order))) 183 | (let [idx (.key e)] 184 | (change! order .assoc idx vector-entry) 185 | idx))] 186 | (change! backing-map .conj (entry k v i)) 187 | this)) 188 | (conj [this e] 189 | (let [[k v] e] 190 | (.assoc this k v))) 191 | (without [this k] 192 | (let [^MapEntry e (.valAt backing-map k this)] 193 | (when-not (identical? e this) 194 | (let [i (.key e)] 195 | (change! backing-map dissoc! k) 196 | (change! order assoc! i nil))) 197 | this)) 198 | (persistent [this] 199 | (OrderedMap. (.persistent backing-map) 200 | (.persistent order)))) 201 | 202 | (defn transient-ordered-map [^OrderedMap om] 203 | (TransientOrderedMap. (.asTransient ^IEditableCollection (.backing-map om)) 204 | (.asTransient ^IEditableCollection (.order om)))) 205 | 206 | (defmethod print-method OrderedMap [o ^java.io.Writer w] 207 | (.write w "#ordered/map ") 208 | (print-method (seq o) w)) 209 | 210 | (defn ordered-map-reader-clj [coll] 211 | (ordered-map coll)) 212 | 213 | (defn ordered-map-reader-cljs [coll] 214 | `(ordered-map ~(vec coll))) 215 | -------------------------------------------------------------------------------- /src/flatland/ordered/map.cljs: -------------------------------------------------------------------------------- 1 | (ns flatland.ordered.map) 2 | 3 | (declare equiv-impl) 4 | 5 | (defn print-ordered-map [writer kvs ks opts] 6 | (pr-sequential-writer 7 | writer 8 | (fn [k w opts] 9 | (-write w \[) 10 | (-write w (pr-str k)) 11 | (-write w \space) 12 | (-write w (pr-str (get kvs k))) 13 | (-write w \])) 14 | "(" " " ")" 15 | opts 16 | ks)) 17 | 18 | (deftype OrderedMap [kvs ks] 19 | Object 20 | (toString [this] (pr-str* this)) 21 | (equiv [this that] (equiv-impl kvs that)) 22 | 23 | ;; js/map interface 24 | (keys [this] (es6-iterator ks)) 25 | (entries [this] (es6-entries-iterator (seq kvs))) 26 | (values [this] (es6-iterator (vals kvs))) 27 | (has [this k] (not (nil? (.get kvs k)))) 28 | (get [this k] (.get kvs k)) 29 | (forEach [this f] 30 | (doseq [k ks] 31 | (f k (get kvs k) this))) 32 | (forEach [this f use-as-this] 33 | (doseq [k ks] 34 | (.call f use-as-this k (get kvs k) this))) 35 | 36 | ;; js fallbacks 37 | (key_set [this] (to-array (keys kvs))) 38 | (entry_set [this] (to-array (map to-array kvs))) 39 | (value_set [this] (to-array (map val kvs))) 40 | 41 | ICloneable 42 | (-clone [_] (OrderedMap. kvs ks)) 43 | 44 | ;; IIterable 45 | ;; (-iterator [_] ) 46 | 47 | IWithMeta 48 | (-with-meta [this new-meta] 49 | (if (identical? (meta kvs) new-meta) 50 | this 51 | (OrderedMap. (with-meta kvs new-meta) ks))) 52 | 53 | IMeta 54 | (-meta [this] (meta kvs)) 55 | 56 | ICollection 57 | (-conj [coll entry] 58 | (if (vector? entry) 59 | (OrderedMap. (conj kvs entry) (if (contains? kvs (-nth entry 0)) 60 | ks 61 | (conj ks (-nth entry 0)))) 62 | (OrderedMap. (conj kvs entry) (into ks 63 | (comp (map #(-nth % 0)) 64 | (remove #(contains? kvs %))) 65 | entry)))) 66 | 67 | IEmptyableCollection 68 | (-empty [this] 69 | (if (seq ks) 70 | (OrderedMap. (-empty kvs) []) 71 | this)) 72 | 73 | IEquiv 74 | (-equiv [this that] (equiv-impl kvs that)) 75 | 76 | IHash 77 | (-hash [_] (hash kvs)) 78 | 79 | ISeqable 80 | (-seq [this] 81 | (when (seq ks) 82 | (map #(-find kvs %) ks))) 83 | 84 | IReversible 85 | (-rseq [this] 86 | (when (seq ks) 87 | (map #(-find kvs %) (rseq ks)))) 88 | 89 | ICounted 90 | (-count [this] (count kvs)) 91 | 92 | ILookup 93 | (-lookup [this attr] (-lookup kvs attr)) 94 | (-lookup [this attr not-found] (-lookup kvs attr not-found)) 95 | 96 | IAssociative 97 | (-assoc [coll k v] 98 | (OrderedMap. (assoc kvs k v) (if (contains? kvs k) 99 | ks 100 | (conj ks k)))) 101 | (-contains-key? [this k] 102 | (contains? kvs k)) 103 | 104 | IFind 105 | (-find [this k] 106 | (-find kvs k)) 107 | 108 | IMap 109 | (-dissoc [this k] 110 | (if (contains? kvs k) 111 | (OrderedMap. (dissoc kvs k) (into [] (remove #{k}) ks)) 112 | this)) 113 | 114 | IKVReduce 115 | (-kv-reduce [coll f init] 116 | (reduce 117 | (fn [acc k] 118 | (f acc k (get kvs k))) 119 | init 120 | ks)) 121 | 122 | IFn 123 | (-invoke [this k] (kvs k)) 124 | (-invoke [this k not-found] (kvs k not-found)) 125 | 126 | IPrintWithWriter 127 | (-pr-writer [_ writer opts] 128 | (-write writer "#ordered/map ") 129 | (print-ordered-map writer kvs ks opts))) 130 | 131 | (defn equiv-impl [kvs that] 132 | (= kvs (if (instance? OrderedMap that) 133 | (.-kvs that) 134 | that))) 135 | 136 | (def ^:private empty-ordered-map (OrderedMap. {} [])) 137 | 138 | (defn ordered-map 139 | ([] 140 | empty-ordered-map) 141 | ([coll] 142 | (into empty-ordered-map coll)) 143 | ([k v & kvs] 144 | (apply assoc empty-ordered-map k v kvs))) 145 | 146 | (comment 147 | (ordered-map :foo 123 :bar 456) 148 | ;; => #ordered/map [:foo 123, :bar 456] 149 | 150 | (conj (ordered-map :foo 123 :bar 456) [:baz 123]) 151 | ;; => #ordered/map [:foo 123, :bar 456, :baz 123] 152 | 153 | (assoc (ordered-map :foo 123 :bar 456) 154 | :baz 123 155 | :baq 999) 156 | ;; => #ordered/map [:foo 123, :bar 456, :baz 123, :baq 999] 157 | 158 | (merge (ordered-map :foo 123 :bar 456) 159 | {:baz 123 160 | :baq 999}) 161 | ;; => #ordered/map [:foo 123, :bar 456, :baz 123, :baq 999] 162 | 163 | (= (ordered-map :foo 123 :bar 456 :baz 123) 164 | {:foo 123 :bar 456 :baz 123}) 165 | ;; => true 166 | 167 | (= {:foo 123 :bar 456 :baz 123} 168 | (ordered-map :foo 123 :bar 456 :baz 123)) 169 | ;; => true 170 | 171 | (map? (ordered-map :foo 123 :bar 456 :baz 123)) 172 | ;; => true 173 | 174 | (empty (ordered-map :foo 123 :bar 456 :baz 123)) 175 | ;; => #ordered/map [] 176 | 177 | (ordered-map) 178 | ;; => #ordered/map [] 179 | 180 | (seq (ordered-map)) 181 | ;; => nil 182 | 183 | (reduce conj [] (ordered-map :foo 123 :bar 456 :baz 123)) 184 | ;; => [[:foo 123] [:bar 456] [:baz 123]] 185 | 186 | (keys (ordered-map :foo 123 :bar 456 :baz 123)) 187 | ;; => (:foo :bar :baz) 188 | 189 | (vals (ordered-map :foo 123 :bar 456 :baz 789)) 190 | ;; => (123 456 789) 191 | 192 | (meta (with-meta (ordered-map) {:foo :bar})) 193 | ;; => {:foo :bar} 194 | 195 | (-> (ordered-map) 196 | (assoc-in [:foo :bar] 1) 197 | (assoc-in [:foo :baz] 2)) 198 | 199 | (into (ordered-map) [[:foo 1] [:bar 2] [:foo 3]]) 200 | ;; #ordered/map [:foo 3, :bar 2] 201 | 202 | ) 203 | -------------------------------------------------------------------------------- /src/flatland/ordered/set.clj: -------------------------------------------------------------------------------- 1 | (ns flatland.ordered.set 2 | (:use [flatland.ordered.common :only [Compactable compact change!]]) 3 | (:require [clojure.string :as s]) 4 | (:import (clojure.lang IPersistentSet ITransientSet IEditableCollection 5 | IPersistentMap ITransientMap ITransientAssociative 6 | IPersistentVector ITransientVector IHashEq 7 | Associative Seqable SeqIterator Reversible IFn IObj) 8 | (java.util Set Collection))) 9 | 10 | (declare transient-ordered-set) 11 | 12 | ;; We could use compile-if technique here, but hoping to avoid 13 | ;; an AOT issue using this way instead. 14 | (def hasheq-ordered-set 15 | (or (resolve 'clojure.core/hash-unordered-coll) 16 | (fn old-hasheq-ordered-set [^Seqable s] 17 | (reduce + (map hash (.seq s)))))) 18 | 19 | (deftype OrderedSet [^clojure.lang.IPersistentMap k->i 20 | ^clojure.lang.IPersistentVector i->k] 21 | IPersistentSet 22 | (disjoin [this k] 23 | (if-let [i (.valAt k->i k)] 24 | (OrderedSet. (dissoc k->i k) 25 | (assoc i->k i ::empty)) 26 | this)) 27 | (cons [this k] 28 | (if-let [i (.valAt k->i k)] 29 | this 30 | (OrderedSet. (.assoc ^Associative k->i k (.count i->k)) 31 | (.cons i->k k)))) 32 | (seq [this] 33 | (seq (remove #(identical? ::empty %) i->k))) 34 | (empty [this] 35 | (OrderedSet. (-> {} (with-meta (meta k->i))) 36 | [])) 37 | (equiv [this other] 38 | (.equals this other)) 39 | (get [this k] 40 | (when (.valAt k->i k) k)) 41 | (count [this] 42 | (.count k->i)) 43 | 44 | IObj 45 | (meta [this] 46 | (.meta ^IObj k->i)) 47 | (withMeta [this m] 48 | (OrderedSet. (.withMeta ^IObj k->i m) 49 | i->k)) 50 | 51 | Compactable 52 | (compact [this] 53 | (into (empty this) this)) 54 | 55 | Object 56 | (toString [this] 57 | (str "#{" (clojure.string/join " " (map str this)) "}")) 58 | (hashCode [this] 59 | (reduce + (keep #(when (some? %) (.hashCode ^Object %)) (.seq this)))) 60 | (equals [this other] 61 | (or (identical? this other) 62 | (and (instance? Set other) 63 | (let [^Set s other] 64 | (and (= (.size this) (.size s)) 65 | (every? #(.contains s %) (.seq this))))))) 66 | 67 | IHashEq 68 | (hasheq [this] 69 | (hasheq-ordered-set this)) 70 | 71 | Set 72 | (iterator [this] 73 | (SeqIterator. (.seq this))) 74 | (contains [this k] 75 | (.containsKey k->i k)) 76 | (containsAll [this ks] 77 | (every? #(.contains this %) ks)) 78 | (size [this] 79 | (.count this)) 80 | (isEmpty [this] 81 | (zero? (.count this))) 82 | (^objects toArray [this ^objects dest] 83 | (reduce (fn [idx item] 84 | (aset dest idx item) 85 | (inc idx)) 86 | 0, (.seq this)) 87 | dest) 88 | (toArray [this] 89 | (.toArray this (object-array (.count this)))) 90 | 91 | Reversible 92 | (rseq [this] 93 | (seq (remove #(identical? ::empty %) (rseq i->k)))) 94 | 95 | IEditableCollection 96 | (asTransient [this] 97 | (transient-ordered-set this)) 98 | IFn 99 | (invoke [this k] (when (.contains this k) k))) 100 | 101 | (def ^{:private true, 102 | :tag OrderedSet} empty-ordered-set (empty (OrderedSet. nil nil))) 103 | 104 | (defn ordered-set 105 | "Return a set with the given items, whose items are sorted in the 106 | order that they are added. conj'ing an item that was already in the 107 | set leaves its order unchanged. disj'ing an item and then later 108 | conj'ing it puts it at the end, as if it were being added for the 109 | first time. Supports transient. 110 | 111 | Note that clojure.set functions like union, intersection, and 112 | difference can change the order of their input sets for efficiency 113 | purposes, so may not return the order you expect given ordered sets 114 | as input." 115 | ([] empty-ordered-set) 116 | ([& xs] (into empty-ordered-set xs))) 117 | 118 | (deftype TransientOrderedSet [^{:unsynchronized-mutable true 119 | :tag ITransientMap} k->i, 120 | ^{:unsynchronized-mutable true 121 | :tag ITransientVector} i->k] 122 | ITransientSet 123 | (count [this] 124 | (.count k->i)) 125 | (get [this k] 126 | (when (.valAt k->i k) k)) 127 | (disjoin [this k] 128 | (let [i (.valAt k->i k)] 129 | (when i 130 | (change! k->i .without k) 131 | (change! i->k .assocN i ::empty))) 132 | this) 133 | (conj [this k] 134 | (let [i (.valAt k->i k)] 135 | (when-not i 136 | (change! ^ITransientAssociative k->i .assoc k (.count i->k)) 137 | (change! i->k conj! k))) 138 | this) 139 | (contains [this k] 140 | (boolean (.valAt k->i k))) 141 | (persistent [this] 142 | (OrderedSet. (.persistent k->i) 143 | (.persistent i->k)))) 144 | 145 | (defn transient-ordered-set [^OrderedSet os] 146 | (TransientOrderedSet. (transient (.k->i os)) 147 | (transient (.i->k os)))) 148 | 149 | (defn into-ordered-set 150 | [items] 151 | (into empty-ordered-set items)) 152 | 153 | (defmethod print-method OrderedSet [o ^java.io.Writer w] 154 | (.write w "#ordered/set ") 155 | (print-method (seq o) w)) 156 | -------------------------------------------------------------------------------- /tasks.clj: -------------------------------------------------------------------------------- 1 | (deftask test #{check}) 2 | -------------------------------------------------------------------------------- /test/flatland/ordered/map_test.cljc: -------------------------------------------------------------------------------- 1 | (ns flatland.ordered.map-test 2 | (:require [clojure.test :refer [deftest testing is are]] 3 | [flatland.ordered.map :refer [#?(:cljs OrderedMap) ordered-map]] 4 | #?(:clj [flatland.ordered.common :refer [compact]] 5 | :cljs [cljs.reader :as reader])) 6 | #?(:clj (:import flatland.ordered.map.OrderedMap))) 7 | 8 | #?(:cljs 9 | (defn read-string [s] 10 | (reader/read-string {:readers {'ordered/map ordered-map}} s))) 11 | 12 | (deftest implementations 13 | (let [basic (ordered-map)] 14 | #?(:clj 15 | (testing "Interfaces marked as implemented" 16 | (are [class] (instance? class basic) 17 | clojure.lang.IPersistentMap 18 | clojure.lang.IPersistentCollection 19 | clojure.lang.Counted 20 | clojure.lang.Associative 21 | java.util.Map))) 22 | (testing "Behavior smoke testing" 23 | (testing "Most operations don't change type" 24 | (are [object] (= (type object) (type basic)) 25 | (conj basic [1 2]) 26 | (assoc basic 1 2) 27 | (into basic {1 2}))) 28 | (testing "Seq-oriented operations return nil when empty" 29 | (are [object] (nil? object) 30 | (seq basic) 31 | (rseq basic))) 32 | (testing "Metadata" 33 | (is (nil? (seq (meta basic)))) 34 | (is (= 10 (-> basic 35 | (with-meta {:size 10}) 36 | meta 37 | :size))) 38 | (is (= {:succeeded true} 39 | (-> basic 40 | (vary-meta assoc :succeeded true) 41 | meta))) 42 | (is (= {:meta :here} 43 | (-> basic 44 | (with-meta {:meta :here}) 45 | (assoc :a :b) 46 | (empty) 47 | (meta)))) 48 | (testing "Metadata doesn't affect other properties" 49 | (let [m (with-meta basic {:a 1})] 50 | (is (instance? OrderedMap m)) 51 | (is (= m basic)))) 52 | (testing "Metadata behaves like map's metadata" 53 | (let [meta-map {:meta 1} 54 | m1 (with-meta {} meta-map) 55 | m2 (with-meta basic meta-map)] 56 | (is (= (meta (assoc m1 1 2)) 57 | (meta (assoc m2 1 2)))))))))) 58 | 59 | (deftest equality 60 | (let [empty (ordered-map) 61 | one-item (assoc empty 1 2)] 62 | (testing "Basic symmetric equality" 63 | (is (= {} empty)) 64 | (is (= empty {})) 65 | (is (= {1 2} one-item)) 66 | (is (= one-item {1 2}))) 67 | (testing "Order-insensitive comparisons" 68 | (let [one-way (into empty {1 2 3 4}) 69 | other-way (into empty {3 4 1 2}) 70 | unsorted {1 2 3 4}] 71 | (is (= one-way other-way)) 72 | (is (= one-way unsorted)) 73 | (is (= other-way unsorted)))) 74 | (testing "Hash code sanity" 75 | (is (integer? (hash one-item))) 76 | (is (= #{one-item} (into #{} [one-item {1 2}])))) 77 | (testing "nil values don't break .equiv" 78 | (is (not= (ordered-map :x nil) {:y 0}))))) 79 | 80 | (deftest ordering 81 | (let [values [[:first 10] 82 | [:second 20] 83 | [:third 30]] 84 | m (ordered-map values)] 85 | (testing "Seq behaves like on a seq of vectors" 86 | (is (= (seq values) (seq m)))) 87 | (testing "New values get added at the end" 88 | (let [entry [:fourth 40]] 89 | (is (= (seq (conj values entry)) 90 | (seq (conj m entry)))))) 91 | (testing "Changing old mappings leaves them at the same location" 92 | (let [vec-index [1] 93 | vec-key (conj vec-index 1) 94 | map-key (get-in values (conj vec-index 0)) 95 | new-value 5] 96 | (is (= (seq (assoc-in values vec-key new-value)) 97 | (seq (assoc m map-key new-value)))))) 98 | (testing "Large number of keys still sorted" 99 | (let [kvs (for [n (range 5000)] 100 | [(str n) n]) 101 | expected (into values kvs) 102 | ordered (into m kvs)] 103 | (is (= (seq expected) (seq ordered))))))) 104 | 105 | (deftest reversing 106 | (let [source (vec (for [n (range 10)] 107 | [n n])) 108 | m (into (ordered-map) source)] 109 | (is (= (rseq m) (rseq source))))) 110 | 111 | (deftest map-features 112 | (let [m (ordered-map :a 1 :b 2 :c 3)] 113 | (testing "Keyword lookup" 114 | (is (= 1 (:a m)))) 115 | (testing "Sequence views" 116 | (is (= [:a :b :c] (keys m))) 117 | (is (= [1 2 3] (vals m)))) 118 | (testing "IFn support" 119 | (is (= 2 (m :b))) 120 | (is (= 'not-here (m :nothing 'not-here))) 121 | (is (= nil ((ordered-map {:x nil}) :x 'not-here))) ) 122 | (testing "Get out Map.Entry" 123 | (is (= [:a 1] (find m :a)))) 124 | (testing "Get out Map.Entry with falsy value" 125 | (is (= [:a nil] (find (ordered-map {:a nil}) :a)))) 126 | (testing "Ordered dissoc" 127 | (let [m (dissoc m :b)] 128 | (is (= [:a :c] (keys m))) 129 | (is (= [1 3] (vals m))))) 130 | (testing "Can conj a map" 131 | (is (= {:a 1 :b 2 :c 3 :d 4} (conj m {:d 4})))) 132 | (testing "(conj m nil) returns m" 133 | (are [x] (= m x) 134 | (conj m nil) 135 | (merge m ()) 136 | (into m ())))) 137 | (let [m (ordered-map :a '("spark") :b '("flare" "bolt"))] 138 | (testing "assoc replaces values if not identical?" 139 | (is (vector? (-> m 140 | (update-in [:a] vec) 141 | (get :a))))))) 142 | 143 | #?(:clj 144 | (deftest object-features 145 | (let [m (ordered-map 'a 1 :b 2)] 146 | (is (= "{a 1, :b 2}" (str m)))))) 147 | 148 | #?(:clj 149 | (deftest transient-support 150 | (let [m (ordered-map {1 2 7 8})] 151 | (testing "Basic transient conj!" 152 | (let [t (transient m) 153 | t (conj! t [3 4]) 154 | t (conj! t [3 4]) 155 | p (persistent! t)] 156 | (is (= p (conj m [3 4]))))) 157 | (testing "Transients still keep order" 158 | (let [t (transient m) 159 | t (assoc! t 0 1) 160 | p (persistent! t)] 161 | (is (= (concat (seq m) '([0 1])) 162 | (seq p))))) 163 | (testing "Transients can overwrite existing entries" 164 | (let [t (transient m) 165 | t (assoc! t 1 5) 166 | p (persistent! t)] 167 | (is (= p (assoc m 1 5))))) 168 | (testing "Transients can dissoc!" 169 | (let [k (key (first m)) 170 | t (transient m) 171 | t (dissoc! t k)] 172 | (is (= (persistent! t) 173 | (dissoc m k))))) 174 | (testing "Can't edit transient after calling persistent!" 175 | (let [more [[:a 1] [:b 2]] 176 | t (transient m) 177 | t (reduce conj! t more) 178 | p (persistent! t)] 179 | (is (thrown? Throwable (assoc! t :c 3))) 180 | (is (= (into m more) p)))) 181 | (testing "Transients are never equal to other objects" 182 | (let [[t1 t2 :as ts] (repeatedly 2 #(transient m)) 183 | holder (apply hash-set ts)] 184 | (is (not= t1 t2)) 185 | (is (= (count ts) (count holder))) 186 | (are [t] (= t (holder t)) 187 | t1 t2)))))) 188 | 189 | (deftest print-and-read-ordered 190 | (let [s (ordered-map 1 2, 3 4, 5 6, 1 9, 7 8)] 191 | (is (= "#ordered/map ([1 9] [3 4] [5 6] [7 8])" 192 | (pr-str s))) 193 | (let [o (read-string (pr-str s))] 194 | (is (= OrderedMap (type o))) 195 | (is (= '([1 9] [3 4] [5 6] [7 8]) 196 | (seq o)))))) 197 | 198 | #?(:clj 199 | (deftest print-read-eval-ordered 200 | (is (= (pr-str (eval (read-string "#ordered/map[[:a 1] [:b 2]]"))) 201 | "#ordered/map ([:a 1] [:b 2])")) 202 | (is (= (pr-str (eval (read-string "#ordered/map[[1 2] [3 4] [5 6] [1 9] [7 8]]"))) 203 | "#ordered/map ([1 9] [3 4] [5 6] [7 8])")))) 204 | 205 | #?(:clj 206 | (deftest compacting 207 | (let [m1 (ordered-map :a 1 :b 2 :c 3) 208 | m2 (dissoc m1 :b) 209 | m3 (compact m2) 210 | m4 (dissoc m3 :c)] 211 | (is (= m2 (ordered-map :a 1 :c 3))) 212 | (is (= m3 m2)) 213 | (is (= m4 (ordered-map :a 1)))))) 214 | 215 | #?(:clj 216 | (deftest same-hash 217 | (let [m1 (ordered-map :a 1 :b 2 :c 3) 218 | m2 (hash-map :a 1 :b 2 :c 3) 219 | m3 (array-map :a 1 :b 2 :c 3)] 220 | (is (= (hash m1) (hash m2) (hash m3))) 221 | (is (= (.hashCode m1) (.hashCode m2) (.hashCode m3))) 222 | (is (= (hash (ordered-map)) (hash (hash-map)) (hash (array-map)))) 223 | (is (= (.hashCode (ordered-map)) (.hashCode (hash-map)) (.hashCode (array-map))))))) 224 | 225 | #?(:clj 226 | (deftest nil-hash-code-npe 227 | ;; No assertions here; just check that it doesn't NPE 228 | ;; See: https://github.com/amalloy/ordered/issues/27 229 | (are [contents] (.hashCode (ordered-map contents)) 230 | [[nil :a]] 231 | [[:a nil]] 232 | [[nil nil]]))) 233 | -------------------------------------------------------------------------------- /test/flatland/ordered/performance_test.clj: -------------------------------------------------------------------------------- 1 | (ns flatland.ordered.performance-test 2 | (:use clojure.test)) 3 | 4 | (deftest ^:kaocha/pending reflection 5 | #_(binding [*warn-on-reflection* true] 6 | (are [ns-sym] (= "" 7 | (with-out-str 8 | (binding [*err* *out*] 9 | (require :reload ns-sym)))) 10 | ;; Order of the below is IMPORTANT. set depends on map, and if you 11 | ;; reload map *after* reloading set, then set refers to classes that 12 | ;; don't exist anymore, and all kinds of bad stuff happens 13 | ;; (in this test and others) 14 | 'ordered.map 'ordered.set))) 15 | -------------------------------------------------------------------------------- /test/flatland/ordered/set_test.clj: -------------------------------------------------------------------------------- 1 | (ns flatland.ordered.set-test 2 | (:use clojure.test 3 | [flatland.ordered.set :only [ordered-set]] 4 | [flatland.ordered.common :only [compact]]) 5 | (:import (flatland.ordered.set OrderedSet))) 6 | 7 | (deftest implementations 8 | (let [s (ordered-set)] 9 | (testing "Interfaces marked as implemented" 10 | (are [class] (instance? class s) 11 | clojure.lang.IPersistentSet 12 | clojure.lang.IPersistentCollection 13 | clojure.lang.Counted 14 | java.util.Set)) 15 | (testing "Behavior smoke testing" 16 | (testing "Most operations don't change type" 17 | (are [object] (= (class object) (class s)) 18 | (conj s 1 2) 19 | (disj s 1) 20 | (into s #{1 2}))) 21 | (testing "Seq-oriented operations return nil when empty" 22 | (are [object] (nil? object) 23 | (seq s) 24 | (rseq s))) 25 | (testing "Metadata" 26 | (is (nil? (seq (meta s)))) 27 | (is (= 10 (-> s 28 | (with-meta {:size 10}) 29 | meta 30 | :size))) 31 | (is (= {:succeeded true} 32 | (-> s 33 | (vary-meta assoc :succeeded true) 34 | meta))) 35 | (is (= {:meta :here} 36 | (-> s 37 | (with-meta {:meta :here}) 38 | (conj :a) 39 | (empty) 40 | (meta)))) 41 | (testing "Metadata doesn't affect other properties" 42 | (let [m (with-meta s {:a 1})] 43 | (is (instance? OrderedSet m)) 44 | (is (= m s)))) 45 | (testing "Metadata behaves like set's metadata" 46 | (let [meta-map {:meta 1} 47 | s1 (with-meta #{} meta-map) 48 | s2 (with-meta s meta-map)] 49 | (is (= (meta (conj s1 1 2)) 50 | (meta (conj s2 1 2)))))))))) 51 | 52 | (deftest equality 53 | (let [empty (ordered-set) 54 | one-item (conj empty 1)] 55 | (testing "Basic symmetric equality" 56 | (is (= #{} empty)) 57 | (is (= empty #{})) 58 | (is (= #{1} one-item)) 59 | (is (= one-item #{1}))) 60 | (testing "Order-insensitive comparisons" 61 | (let [one-way (into empty [1 2 3 4]) 62 | other-way (into empty [3 4 1 2]) 63 | unsorted #{1 2 3 4}] 64 | (is (= one-way other-way)) 65 | (is (= one-way unsorted)) 66 | (is (= other-way unsorted)))))) 67 | 68 | (deftest ordering 69 | (let [values [[:first 10] 70 | [:second 20] 71 | [:third 30]] 72 | s (into (ordered-set) values)] 73 | (testing "Seq behaves like seq of a vector" 74 | (is (= (seq values) (seq s)))) 75 | (testing "New values get added at the end" 76 | (let [entry [:fourth 40]] 77 | (is (= (seq (conj values entry)) 78 | (seq (conj s entry)))))) 79 | (testing "Re-adding keys leaves them in the same place" 80 | (is (= (seq s) 81 | (seq (conj s [:second 20]))))) 82 | (testing "Large number of keys still sorted" 83 | (let [ints (range 5000) 84 | ordered (into s ints)] 85 | (= (seq ints) (seq ordered)))))) 86 | 87 | (deftest reversing 88 | (let [source (vec (range 1000)) 89 | s (into (sorted-set) source)] 90 | (is (= (rseq s) (rseq source))))) 91 | 92 | (deftest set-features 93 | (let [s (ordered-set :a 1 :b 2 :c 3)] 94 | (testing "Keyword lookup" 95 | (is (= :a (:a s)))) 96 | (testing "IFn support" 97 | (is (= :b (s :b)))) 98 | (testing "Falsy lookup support" 99 | (is (= false (#{false 1} false)))) 100 | (testing "Ordered disj" 101 | (is (= #{:a 1 2 3} (disj s :b :c)))))) 102 | 103 | (deftest object-features 104 | (let [s (ordered-set 'a 1 :b 2)] 105 | (is (= "#{a 1 :b 2}" (str s))))) 106 | 107 | (deftest transient-support 108 | (let [s (ordered-set 1 2 7 8)] 109 | (testing "Basic transient conj!" 110 | (let [t (transient s) 111 | t (conj! t 4) ; add 4 112 | t (conj! t 4) ; do nothing, 4's already there 113 | t (conj! t 1) ; should do nothing 114 | p (persistent! t)] 115 | (is (= p (conj s 4))))) 116 | (testing "Transients still keep order" 117 | (let [t (transient s) 118 | t (conj! t 0) 119 | t (conj! t 1) 120 | p (persistent! t)] 121 | (is (= (concat (seq s) '(0)) ; adding 0 (at the end) but not 1 122 | (seq p))))) 123 | (testing "Transients can disj!" 124 | (let [k (first s) 125 | t (transient s) 126 | t (disj! t k)] 127 | (is (= (persistent! t) 128 | (disj s k))))) 129 | (testing "Can lookup in transients" 130 | (let [t (transient s)] 131 | (is (.contains t (first s))))))) 132 | 133 | (deftest print-and-read-ordered 134 | (let [s (ordered-set 1 2 9 8 7 5)] 135 | (is (= "#ordered/set (1 2 9 8 7 5)" 136 | (pr-str s))) 137 | (let [o (read-string (pr-str s))] 138 | (is (= OrderedSet (type o))) 139 | (is (= '(1 2 9 8 7 5) 140 | (seq o)))))) 141 | 142 | (deftest print-read-eval-ordered 143 | (is (= (seq (eval (read-string "#ordered/set (1 2 9 8 7 5)"))) 144 | '(1 2 9 8 7 5))) 145 | (is (= (seq (eval (read-string "#ordered/set ([1 2] [3 4] [5 6] [1 9] [7 8])"))) 146 | '([1 2] [3 4] [5 6] [1 9] [7 8])))) 147 | 148 | (deftest compacting 149 | (let [s1 (ordered-set :a :b :c) 150 | s2 (disj s1 :b) 151 | s3 (compact s2) 152 | s4 (disj s3 :c)] 153 | (is (= s2 (ordered-set :a :c))) 154 | (is (= s3 s2)) 155 | (is (= s4 (ordered-set :a))))) 156 | 157 | (deftest same-hash 158 | (let [m1 (ordered-set :a :b :c) 159 | m2 (hash-set :a :b :c)] 160 | (is (= (hash m1) (hash m2))) 161 | (is (= (.hashCode m1) (.hashCode m2))) 162 | (is (= (hash (ordered-set)) (hash (hash-set)))) 163 | (is (= (.hashCode (ordered-set)) (.hashCode (hash-set)))) 164 | (is (= (hash (ordered-set nil)) (hash (hash-set nil)))) 165 | (is (= (.hashCode (ordered-set nil)) (.hashCode (hash-set nil)))) 166 | (is (= (.hashCode (ordered-set nil :a {:b nil})) (.hashCode (hash-set nil :a {:b nil})))))) 167 | 168 | (deftest nil-and-false-hashes 169 | (is (not= (.hashCode (ordered-set nil)) (.hashCode (hash-set false)))) 170 | (is (not= (.hashCode (ordered-set false)) (.hashCode (hash-set nil)))) 171 | (is (= (.hashCode (ordered-set false nil)) (.hashCode (hash-set nil false))))) 172 | 173 | (deftest nil-hash-code-npe 174 | ;; No assertions here; just check that it doesn't NPE 175 | ;; See: https://github.com/amalloy/ordered/issues/27 176 | (are [contents] (.hashCode (apply ordered-set contents)) 177 | [nil] 178 | [nil :a])) 179 | -------------------------------------------------------------------------------- /tests.edn: -------------------------------------------------------------------------------- 1 | #kaocha/v1 2 | {:tests [{:id :clj} 3 | {:id :cljs 4 | :type :kaocha.type/cljs}] 5 | :plugins [:notifier :print-invocations :hooks] 6 | :kaocha.hooks/pre-load [(fn [t] (require (quote flatland.ordered.map)) t)]} 7 | --------------------------------------------------------------------------------