├── deps.edn
├── .github
├── workflows
│ ├── test.yml
│ ├── snapshot.yml
│ ├── doc-build.yml
│ └── release.yml
└── PULL_REQUEST_TEMPLATE
├── .gitignore
├── CONTRIBUTING.md
├── pom.xml
├── README.md
├── src
├── test
│ └── clojure
│ │ └── clojure
│ │ └── data
│ │ └── test_priority_map.clj
└── main
│ └── clojure
│ └── clojure
│ └── data
│ └── priority_map.clj
├── LICENSE
└── epl.html
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src/main/clojure"]}
2 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.jar
2 | .classpath
3 | .project
4 | .settings
5 | bin
6 | classes
7 | clojure-src.jar
8 | clojure-contrib.jar
9 | clojure.jar
10 | clojure-contrib-src.jar
11 | target
12 | .cpcache/
13 |
--------------------------------------------------------------------------------
/.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.priority-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/DPRIMAP
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
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE:
--------------------------------------------------------------------------------
1 | Hi! Thanks for your interest in contributing to this project.
2 |
3 | Clojure contrib projects do not use GitHub issues or pull requests, and
4 | require a signed Contributor Agreement. If you would like to contribute,
5 | please read more about the CA and sign that first (this can be done online).
6 |
7 | Then go to this project's issue tracker in JIRA to create tickets, update
8 | tickets, or submit patches. For help in creating tickets and patches,
9 | please see:
10 |
11 | - Contributing FAQ: https://clojure.org/dev
12 | - Signing the CA: https://clojure.org/dev/contributor_agreement
13 | - Creating Tickets: https://clojure.org/dev/creating_tickets
14 | - Developing Patches: https://clojure.org/dev/developing_patches
15 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
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/clojure/clojure/data/priority_map.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Mark Engelberg, 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 | ;; A priority map is a map from items to priorities, 10 | ;; offering queue-like peek/pop as well as the map-like ability to 11 | ;; easily reassign priorities and other conveniences. 12 | ;; by Mark Engelberg (mark.engelberg@gmail.com) 13 | ;; Last update - September 19, 2021 14 | 15 | (ns 16 | ^{:author "Mark Engelberg", 17 | :doc "A priority map is very similar to a sorted map, but whereas a sorted map produces a 18 | sequence of the entries sorted by key, a priority map produces the entries sorted by value. 19 | In addition to supporting all the functions a sorted map supports, a priority map 20 | can also be thought of as a queue of [item priority] pairs. To support usage as 21 | a versatile priority queue, priority maps also support conj/peek/pop operations. 22 | 23 | The standard way to construct a priority map is with priority-map: 24 | user=> (def p (priority-map :a 2 :b 1 :c 3 :d 5 :e 4 :f 3)) 25 | #'user/p 26 | user=> p 27 | {:b 1, :a 2, :c 3, :f 3, :e 4, :d 5} 28 | 29 | So :b has priority 1, :a has priority 2, and so on. 30 | Notice how the priority map prints in an order sorted by its priorities (i.e., the map's values) 31 | 32 | We can use assoc to assign a priority to a new item: 33 | user=> (assoc p :g 1) 34 | {:b 1, :g 1, :a 2, :c 3, :f 3, :e 4, :d 5} 35 | 36 | or to assign a new priority to an extant item: 37 | user=> (assoc p :c 4) 38 | {:b 1, :a 2, :f 3, :c 4, :e 4, :d 5} 39 | 40 | We can remove an item from the priority map: 41 | user=> (dissoc p :e) 42 | {:b 1, :a 2, :c 3, :f 3, :d 5} 43 | 44 | An alternative way to add to the priority map is to conj a [item priority] pair: 45 | user=> (conj p [:g 0]) 46 | {:g 0, :b 1, :a 2, :c 3, :f 3, :e 4, :d 5} 47 | 48 | or use into: 49 | user=> (into p [[:g 0] [:h 1] [:i 2]]) 50 | {:g 0, :b 1, :h 1, :a 2, :i 2, :c 3, :f 3, :e 4, :d 5} 51 | 52 | Priority maps are countable: 53 | user=> (count p) 54 | 6 55 | 56 | Like other maps, equivalence is based not on type, but on contents. 57 | In other words, just as a sorted-map can be equal to a hash-map, 58 | so can a priority-map. 59 | user=> (= p {:b 1, :a 2, :c 3, :f 3, :e 4, :d 5}) 60 | true 61 | 62 | You can test them for emptiness: 63 | user=> (empty? (priority-map)) 64 | true 65 | user=> (empty? p) 66 | false 67 | 68 | You can test whether an item is in the priority map: 69 | user=> (contains? p :a) 70 | true 71 | user=> (contains? p :g) 72 | false 73 | 74 | It is easy to look up the priority of a given item, using any of the standard map mechanisms: 75 | user=> (get p :a) 76 | 2 77 | user=> (get p :g 10) 78 | 10 79 | user=> (p :a) 80 | 2 81 | user=> (:a p) 82 | 2 83 | 84 | Priority maps derive much of their utility by providing priority-based seq. 85 | Note that no guarantees are made about the order in which items of the same priority appear. 86 | user=> (seq p) 87 | ([:b 1] [:a 2] [:c 3] [:f 3] [:e 4] [:d 5]) 88 | Because no guarantees are made about the order of same-priority items, note that 89 | rseq might not be an exact reverse of the seq. It is only guaranteed to be in 90 | descending order. 91 | user=> (rseq p) 92 | ([:d 5] [:e 4] [:c 3] [:f 3] [:a 2] [:b 1]) 93 | 94 | This means first/rest/next/for/map/etc. all operate in priority order. 95 | user=> (first p) 96 | [:b 1] 97 | user=> (rest p) 98 | ([:a 2] [:c 3] [:f 3] [:e 4] [:d 5]) 99 | 100 | Priority maps also support subseq and rsubseq, however, *you must use the subseq and rsubseq 101 | defined in the clojure.data.priority-map namespace*, which patches longstanding JIRA issue 102 | [CLJ-428](https://clojure.atlassian.net/browse/CLJ-428). These patched versions 103 | of subseq and rsubseq will work on Clojure's other sorted collections as well, so you can 104 | use them as a drop-in replacement for the subseq and rsubseq found in core. 105 | user=> (subseq p < 3) 106 | ([:b 1] [:a 2]) 107 | user=> (subseq p >= 3) 108 | ([:c 3] [:f 3] [:e 4] [:d 5]) 109 | user=> (subseq p >= 2 < 4) 110 | ([:a 2] [:c 3] [:f 3]) 111 | user=> (rsubseq p < 4) 112 | ([:c 3] [:f 3] [:a 2] [:b 1]) 113 | user=> (rsubseq p >= 4) 114 | ([:d 5] [:e 4]) 115 | 116 | Priority maps support metadata: 117 | user=> (meta (with-meta p {:extra :info})) 118 | {:extra :info} 119 | 120 | But perhaps most importantly, priority maps can also function as priority queues. 121 | peek, like first, gives you the first [item priority] pair in the collection. 122 | pop removes the first [item priority] from the collection. 123 | (Note that unlike rest, which returns a seq, pop returns a priority map). 124 | 125 | user=> (peek p) 126 | [:b 1] 127 | user=> (pop p) 128 | {:a 2, :c 3, :f 3, :e 4, :d 5} 129 | 130 | It is also possible to use a custom comparator: 131 | user=> (priority-map-by > :a 1 :b 2 :c 3) 132 | {:c 3, :b 2, :a 1} 133 | 134 | Sometimes, it is desirable to have a map where the values contain more information 135 | than just the priority. For example, let's say you want a map like: 136 | {:a [2 :apple], :b [1 :banana], :c [3 :carrot]} 137 | and you want to sort the map by the numeric priority found in the pair. 138 | 139 | A common mistake is to try to solve this with a custom comparator: 140 | (priority-map-by 141 | (fn [[priority1 _] [priority2 _]] (< priority1 priority2)) 142 | :a [2 :apple], :b [1 :banana], :c [3 :carrot]) 143 | 144 | This will not work! Although it may appear to work with these particular values, it is not safe. 145 | In Clojure, like Java, all comparators must be *total orders*, 146 | meaning that you can't have a tie unless the objects you are comparing are 147 | in fact equal. The above comparator breaks that rule because objects such as 148 | `[2 :apple]` and `[2 :apricot]` would tie, but are not equal. 149 | 150 | The correct way to construct such a priority map is by specifying a keyfn, which is used 151 | to extract the true priority from the priority map's vals. (Note: It might seem a little odd 152 | that the priority-extraction function is called a *key*fn, even though it is applied to the 153 | map's values. This terminology is based on the docstring of clojure.core/sort-by, which 154 | uses `keyfn` for the function which extracts the sort order.) 155 | 156 | In the above example, 157 | 158 | user=> (priority-map-keyfn first :a [2 :apple], :b [1 :banana], :c [3 :carrot]) 159 | {:b [1 :banana], :a [2 :apple], :c [3 :carrot]} 160 | 161 | You can also combine a keyfn with a comparator that operates on the extracted priorities: 162 | 163 | user=> (priority-map-keyfn-by 164 | first > 165 | :a [2 :apple], :b [1 :banana], :c [3 :carrot]) 166 | {:c [3 :carrot], :a [2 :apple], :b [1 :banana]} 167 | 168 | 169 | 170 | All of these operations are efficient. Generally speaking, most operations 171 | are O(log n) where n is the number of distinct priorities. Some operations 172 | (for example, straightforward lookup of an item's priority, or testing 173 | whether a given item is in the priority map) are as efficient 174 | as Clojure's built-in map. 175 | 176 | The key to this efficiency is that internally, not only does the priority map store 177 | an ordinary hash map of items to priority, but it also stores a sorted map that 178 | maps priorities to sets of items with that priority. 179 | 180 | A typical textbook priority queue data structure supports at the ability to add 181 | a [item priority] pair to the queue, and to pop/peek the next [item priority] pair. 182 | But many real-world applications of priority queues require more features, such 183 | as the ability to test whether something is already in the queue, or to reassign 184 | a priority. For example, a standard formulation of Dijkstra's algorithm requires the 185 | ability to reduce the priority number associated with a given item. Once you 186 | throw persistence into the mix with the desire to adjust priorities, the traditional 187 | structures just don't work that well. 188 | 189 | This particular blend of Clojure's built-in hash sets, hash maps, and sorted maps 190 | proved to be a great way to implement an especially flexible persistent priority queue. 191 | 192 | Connoisseurs of algorithms will note that this structure's peek operation is not O(1) as 193 | it would be if based upon a heap data structure, but I feel this is a small concession for 194 | the blend of persistence, priority reassignment, and priority-sorted seq, which can be 195 | quite expensive to achieve with a heap (I did actually try this for comparison). Furthermore, 196 | this peek's logarithmic behavior is quite good (on my computer I can do a million 197 | peeks at a priority map with a million items in 750ms). Also, consider that peek and pop 198 | usually follow one another, and even with a heap, pop is logarithmic. So the net combination 199 | of peek and pop is not much different between this versatile formulation of a priority map and 200 | a more limited heap-based one. In a nutshell, peek, although not O(1), is unlikely to be the 201 | bottleneck in your program. 202 | 203 | All in all, I hope you will find priority maps to be an easy-to-use and useful addition 204 | to Clojure's assortment of built-in maps (hash-map and sorted-map). 205 | "} 206 | clojure.data.priority-map 207 | (:refer-clojure :exclude [subseq rsubseq]) 208 | (:import clojure.lang.MapEntry java.util.Map clojure.lang.PersistentTreeMap)) 209 | 210 | (declare pm-empty) 211 | 212 | (defmacro apply-keyfn [x] 213 | `(if ~'keyfn (~'keyfn ~x) ~x)) 214 | 215 | (defmacro ^:private compile-if [test then else] 216 | (if (eval test) 217 | then 218 | else)) 219 | 220 | ;; We create a patched version of subseq and rsubseq from core, that works on ordinary sorted collections, as well as priority maps 221 | ;; See https://dev.clojure.org/jira/browse/CLJ-428 222 | 223 | (defn mk-bound-fn 224 | {:private true} 225 | [^clojure.lang.Sorted sc test key] 226 | (fn [e] (test (.. sc comparator (compare (. sc entryKey e) key)) 0))) 227 | 228 | (defn subseq 229 | "sc must be a sorted collection, test(s) one of <, <=, > or 230 | >=. Returns a seq of those entries with keys ek for 231 | which (test (.. sc comparator (compare ek key)) 0) is true" 232 | ([^clojure.lang.Sorted sc test key] 233 | (let [include (mk-bound-fn sc test key)] 234 | (if (#{> >=} test) 235 | (when-let [[e :as s] (. sc seqFrom key true)] 236 | (seq (drop-while #(not (include %)) s))) 237 | (seq (take-while include (. sc seq true)))))) 238 | ([^clojure.lang.Sorted sc start-test start-key end-test end-key] 239 | (when-let [[e :as s] (. sc seqFrom start-key true)] 240 | (seq (take-while (mk-bound-fn sc end-test end-key) 241 | (drop-while (complement (mk-bound-fn sc start-test start-key)) s)))))) 242 | 243 | (defn rsubseq 244 | "sc must be a sorted collection, test(s) one of <, <=, > or 245 | >=. Returns a reverse seq of those entries with keys ek for 246 | which (test (.. sc comparator (compare ek key)) 0) is true" 247 | ([^clojure.lang.Sorted sc test key] 248 | (let [include (mk-bound-fn sc test key)] 249 | (if (#{< <=} test) 250 | (when-let [[e :as s] (. sc seqFrom key false)] 251 | (seq (drop-while #(not (include %)) s))) 252 | (seq (take-while include (. sc seq false)))))) 253 | ([^clojure.lang.Sorted sc start-test start-key end-test end-key] 254 | (when-let [[e :as s] (. sc seqFrom end-key false)] 255 | (seq (take-while (mk-bound-fn sc start-test start-key) 256 | (drop-while (complement (mk-bound-fn sc end-test end-key)) s)))))) 257 | 258 | ;; A Priority Map is comprised of a sorted map that maps priorities to hash sets of items 259 | ;; with that priority (priority->set-of-items), 260 | ;; as well as a hash map that maps items to priorities (item->priority) 261 | ;; Priority maps may also have metadata 262 | ;; Priority maps can also have a keyfn which is applied to the "priorities" found as values in 263 | ;; the item->priority map to get the actual sortable priority keys used in priority->set-of-items. 264 | 265 | (deftype PersistentPriorityMap [priority->set-of-items item->priority _meta keyfn] 266 | Object 267 | (toString [this] (str (.seq this))) 268 | 269 | clojure.lang.ILookup 270 | ;; valAt gives (get pm key) and (get pm key not-found) behavior 271 | (valAt [this item] (get item->priority item)) 272 | (valAt [this item not-found] (get item->priority item not-found)) 273 | 274 | clojure.lang.IPersistentMap 275 | (count [this] (count item->priority)) 276 | 277 | (assoc [this item priority] 278 | (let [current-priority (get item->priority item nil)] 279 | (if current-priority 280 | ;;Case 1 - item is already in priority map, so this is a reassignment 281 | (if (= current-priority priority) 282 | ;;Subcase 1 - no change in priority, do nothing 283 | this 284 | (let [priority-key (apply-keyfn priority) 285 | current-priority-key (apply-keyfn current-priority) 286 | item-set (get priority->set-of-items current-priority-key)] 287 | (if (= (count item-set) 1) 288 | ;;Subcase 2 - it was the only item of this priority 289 | ;;so remove old priority entirely 290 | ;;and conj item onto new priority's set 291 | (PersistentPriorityMap. 292 | (assoc (dissoc priority->set-of-items current-priority-key) 293 | priority-key (conj (get priority->set-of-items priority-key #{}) item)) 294 | (assoc item->priority item priority) 295 | (meta this) 296 | keyfn) 297 | ;;Subcase 3 - there were many items associated with the item's original priority, 298 | ;;so remove it from the old set and conj it onto the new one. 299 | (PersistentPriorityMap. 300 | (assoc priority->set-of-items 301 | current-priority-key (disj (get priority->set-of-items current-priority-key) item) 302 | priority-key (conj (get priority->set-of-items priority-key #{}) item)) 303 | (assoc item->priority item priority) 304 | (meta this) 305 | keyfn)))) 306 | ;; Case 2: Item is new to the priority map, so just add it. 307 | (let [priority-key (apply-keyfn priority)] 308 | (PersistentPriorityMap. 309 | (assoc priority->set-of-items 310 | priority-key (conj (get priority->set-of-items priority-key #{}) item)) 311 | (assoc item->priority item priority) 312 | (meta this) 313 | keyfn))))) 314 | 315 | (empty [this] (PersistentPriorityMap. (empty priority->set-of-items) {} _meta keyfn)) 316 | 317 | ;; cons defines conj behavior 318 | (cons [this e] 319 | (if (map? e) 320 | (into this e) 321 | (let [[item priority] e] (.assoc this item priority)))) 322 | 323 | ;; Like sorted maps, priority maps are equal to other maps provided 324 | ;; their key-value pairs are the same. 325 | (equiv [this o] (= item->priority o)) 326 | (hashCode [this] (.hashCode item->priority)) 327 | (equals [this o] (or (identical? this o) (.equals item->priority o))) 328 | 329 | ;;containsKey implements (contains? pm k) behavior 330 | (containsKey [this item] (contains? item->priority item)) 331 | 332 | (entryAt [this k] 333 | (let [v (.valAt this k this)] 334 | (when-not (identical? v this) 335 | (MapEntry. k v)))) 336 | 337 | (seq [this] 338 | (if keyfn 339 | (seq (for [[priority item-set] priority->set-of-items, item item-set] 340 | (MapEntry. item (item->priority item)))) 341 | (seq (for [[priority item-set] priority->set-of-items, item item-set] 342 | (MapEntry. item priority))))) 343 | 344 | ;;without implements (dissoc pm k) behavior 345 | (without 346 | [this item] 347 | (let [priority (item->priority item ::not-found)] 348 | (if (= priority ::not-found) 349 | ;; If item is not in map, return the map unchanged. 350 | this 351 | (let [priority-key (apply-keyfn priority) 352 | item-set (priority->set-of-items priority-key)] 353 | (if (= (count item-set) 1) 354 | ;;If it is the only item with this priority, remove that priority's set completely 355 | (PersistentPriorityMap. (dissoc priority->set-of-items priority-key) 356 | (dissoc item->priority item) 357 | (meta this) 358 | keyfn) 359 | ;;Otherwise, just remove the item from the priority's set. 360 | (PersistentPriorityMap. 361 | (assoc priority->set-of-items priority-key (disj item-set item)), 362 | (dissoc item->priority item) 363 | (meta this) 364 | keyfn)))))) 365 | 366 | clojure.lang.IHashEq 367 | (hasheq [this] 368 | (compile-if (resolve 'clojure.core/hash-unordered-coll) 369 | (hash-unordered-coll this) 370 | (.hashCode this))) 371 | 372 | java.io.Serializable ;Serialization comes for free with the other things implemented 373 | clojure.lang.MapEquivalence 374 | Map ;Makes this compatible with java's map 375 | (size [this] (count item->priority)) 376 | (isEmpty [this] (zero? (count item->priority))) 377 | (containsValue [this v] 378 | (if keyfn 379 | (some (partial = v) (vals this)) ; no shortcut if there is a keyfn 380 | (contains? priority->set-of-items v))) 381 | (get [this k] (.valAt this k)) 382 | (put [this k v] (throw (UnsupportedOperationException.))) 383 | (remove [this k] (throw (UnsupportedOperationException.))) 384 | (putAll [this m] (throw (UnsupportedOperationException.))) 385 | (clear [this] (throw (UnsupportedOperationException.))) 386 | (keySet [this] (set (keys this))) 387 | (values [this] (vals this)) 388 | (entrySet [this] (set this)) 389 | 390 | Iterable 391 | (iterator [this] (clojure.lang.SeqIterator. (seq this))) 392 | 393 | clojure.core.protocols/IKVReduce 394 | (kv-reduce [this f init] 395 | (if keyfn 396 | (reduce-kv (fn [a k v] 397 | (reduce (fn [a v] (f a v (item->priority v))) a v)) 398 | init priority->set-of-items) 399 | (reduce-kv (fn [a k v] 400 | (reduce (fn [a v] (f a v k)) a v)) 401 | init priority->set-of-items))) 402 | 403 | clojure.lang.IPersistentStack 404 | (peek [this] 405 | (when-not (.isEmpty this) 406 | (let [f (first priority->set-of-items) 407 | item (first (val f))] 408 | (if keyfn 409 | (MapEntry. item (item->priority item)) 410 | (MapEntry. item (key f)))))) 411 | 412 | (pop [this] 413 | (if (.isEmpty this) (throw (IllegalStateException. "Can't pop empty priority map")) 414 | (let [f (first priority->set-of-items), 415 | item-set (val f) 416 | item (first item-set), 417 | priority-key (key f)] 418 | (if (= (count item-set) 1) 419 | ;;If the first item is the only item with its priority, remove that priority's set completely 420 | (PersistentPriorityMap. 421 | (dissoc priority->set-of-items priority-key) 422 | (dissoc item->priority item) 423 | (meta this) 424 | keyfn) 425 | ;;Otherwise, just remove the item from the priority's set. 426 | (PersistentPriorityMap. 427 | (assoc priority->set-of-items priority-key (disj item-set item)), 428 | (dissoc item->priority item) 429 | (meta this) 430 | keyfn))))) 431 | 432 | clojure.lang.IFn 433 | ;;makes priority map usable as a function 434 | (invoke [this k] (.valAt this k)) 435 | (invoke [this k not-found] (.valAt this k not-found)) 436 | 437 | clojure.lang.IObj 438 | ;;adds metadata support 439 | (meta [this] _meta) 440 | (withMeta [this m] (PersistentPriorityMap. priority->set-of-items item->priority m keyfn)) 441 | 442 | clojure.lang.Reversible 443 | (rseq [this] 444 | (if keyfn 445 | (seq (for [[priority item-set] (rseq priority->set-of-items), item item-set] 446 | (MapEntry. item (item->priority item)))) 447 | (seq (for [[priority item-set] (rseq priority->set-of-items), item item-set] 448 | (MapEntry. item priority))))) 449 | 450 | clojure.lang.Sorted 451 | ;; These methods provide support for subseq and rsubseq 452 | (comparator [this] (.comparator ^PersistentTreeMap priority->set-of-items)) 453 | (entryKey [this entry] (if keyfn (keyfn (val entry)) (val entry))) 454 | (seqFrom [this k ascending] 455 | (let [sets (if ascending (subseq priority->set-of-items >= k) (rsubseq priority->set-of-items <= k))] 456 | (if keyfn 457 | (seq (for [[priority item-set] sets, item item-set] 458 | (MapEntry. item (item->priority item)))) 459 | (seq (for [[priority item-set] sets, item item-set] 460 | (MapEntry. item priority)))))) 461 | (seq [this ascending] 462 | (if ascending (seq this) (rseq this)))) 463 | 464 | (def ^:private pm-empty (PersistentPriorityMap. (sorted-map) {} {} nil)) 465 | (defn- pm-empty-by [comparator] (PersistentPriorityMap. (sorted-map-by comparator) {} {} nil)) 466 | (defn- pm-empty-keyfn 467 | ([keyfn] (PersistentPriorityMap. (sorted-map) {} {} keyfn)) 468 | ([keyfn comparator] (PersistentPriorityMap. (sorted-map-by comparator) {} {} keyfn))) 469 | 470 | 471 | ;; The main way to build priority maps 472 | (defn priority-map 473 | "Usage: (priority-map key val key val ...) 474 | Returns a new priority map with optional supplied mappings. 475 | (priority-map) returns an empty priority map." 476 | [& keyvals] 477 | {:pre [(even? (count keyvals))]} 478 | (reduce conj pm-empty (partition 2 keyvals))) 479 | 480 | (defn priority-map-by 481 | "Usage: (priority-map comparator key val key val ...) 482 | Returns a new priority map with custom comparator and optional supplied mappings. 483 | (priority-map-by comparator) yields an empty priority map with custom comparator." 484 | [comparator & keyvals] 485 | {:pre [(even? (count keyvals))]} 486 | (reduce conj (pm-empty-by comparator) (partition 2 keyvals))) 487 | 488 | (defn priority-map-keyfn 489 | "Usage: (priority-map-keyfn keyfn key val key val ...) 490 | Returns a new priority map with custom keyfn and optional supplied mappings. 491 | The priority is determined by comparing (keyfn val). 492 | (priority-map-keyfn keyfn) yields an empty priority map with custom keyfn." 493 | [keyfn & keyvals] 494 | {:pre [(even? (count keyvals))]} 495 | (reduce conj (pm-empty-keyfn keyfn) (partition 2 keyvals))) 496 | 497 | (defn priority-map-keyfn-by 498 | "Usage: (priority-map-keyfn-by keyfn comparator key val key val ...) 499 | Returns a new priority map with custom keyfn, custom comparator, and optional supplied mappings. 500 | The priority is determined by comparing (keyfn val). 501 | (priority-map-keyfn-by keyfn comparator) yields an empty priority map with custom keyfn and comparator." 502 | [keyfn comparator & keyvals] 503 | {:pre [(even? (count keyvals))]} 504 | (reduce conj (pm-empty-keyfn keyfn comparator) (partition 2 keyvals))) 505 | 506 | (defn priority->set-of-items 507 | "Takes a priority map p, and returns a sorted map from each priority 508 | to the set of items with that priority in p" 509 | [^PersistentPriorityMap p] 510 | (.priority->set-of-items p)) 511 | --------------------------------------------------------------------------------