Initial commit of split off of Utilities project
commit
a4c33b53d5
|
@ -0,0 +1,121 @@
|
|||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
.idea/**/codeStyles/
|
||||
.idea/**/codeStyleSettings.xml
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/shelf/
|
||||
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
######################
|
||||
# End JetBrains IDEs #
|
||||
######################
|
||||
|
||||
|
||||
# From https://github.com/github/gitignore/blob/master/Gradle.gitignore
|
||||
.gradle
|
||||
/build/
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
!gradle-wrapper.properties
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
|
||||
|
||||
|
||||
# From https://github.com/github/gitignore/blob/master/Java.gitignore
|
||||
*.class
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
|
||||
##########################################################
|
||||
# Specific to this module
|
||||
|
||||
# iml files are generated by intellij/gradle now
|
||||
**/*.iml
|
|
@ -0,0 +1,29 @@
|
|||
- OS - Information about the system, Java runtime, OS, Window Manager, and Desktop Environment.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/OS
|
||||
Copyright 2022
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
|
@ -0,0 +1,218 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,39 @@
|
|||
Niche collections to augment what is already available.
|
||||
|
||||
###### [![Dorkbox](https://badge.dorkbox.com/dorkbox.svg "Dorkbox")](https://git.dorkbox.com/dorkbox/Collections) [![Github](https://badge.dorkbox.com/github.svg "Github")](https://github.com/dorkbox/Collections) [![Gitlab](https://badge.dorkbox.com/gitlab.svg "Gitlab")](https://gitlab.com/dorkbox/Collections)
|
||||
|
||||
|
||||
* LockFree, performant collections/maps/sets/bi-maps
|
||||
* Thread-safe concurrent iterators
|
||||
* AhoCorasick finite state machine
|
||||
* Performant, Primative Maps (int/long/float)
|
||||
* TimSort
|
||||
|
||||
|
||||
|
||||
Maven Info
|
||||
---------
|
||||
```
|
||||
<dependencies>
|
||||
...
|
||||
<dependency>
|
||||
<groupId>com.dorkbox</groupId>
|
||||
<artifactId>Collections</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
Gradle Info
|
||||
---------
|
||||
```
|
||||
dependencies {
|
||||
...
|
||||
implementation("com.dorkbox:Collections:1.0")
|
||||
}
|
||||
```
|
||||
|
||||
License
|
||||
---------
|
||||
This project is © 2022 dorkbox llc, and is distributed under the terms of the Apache v2.0 License. See file "LICENSE" for further
|
||||
references.
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright 2020 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
///////////////////////////////
|
||||
////// PUBLISH TO SONATYPE / MAVEN CENTRAL
|
||||
////// TESTING : (to local maven repo) <'publish and release' - 'publishToMavenLocal'>
|
||||
////// RELEASE : (to sonatype/maven central), <'publish and release' - 'publishToSonatypeAndRelease'>
|
||||
///////////////////////////////
|
||||
|
||||
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace!
|
||||
|
||||
|
||||
plugins {
|
||||
id("com.dorkbox.GradleUtils") version "2.16"
|
||||
id("com.dorkbox.Licensing") version "2.12"
|
||||
id("com.dorkbox.VersionUpdate") version "2.4"
|
||||
id("com.dorkbox.GradlePublish") version "1.12"
|
||||
|
||||
kotlin("jvm") version "1.6.10"
|
||||
}
|
||||
|
||||
object Extras {
|
||||
// set for the project
|
||||
const val description = "Information about the system, Java runtime, OS, Window Manager, and Desktop Environment."
|
||||
const val group = "com.dorkbox"
|
||||
const val version = "1.0"
|
||||
|
||||
// set as project.ext
|
||||
const val name = "Collections"
|
||||
const val id = "Collections" // this is the maven ID!
|
||||
const val vendor = "Dorkbox LLC"
|
||||
const val vendorUrl = "https://dorkbox.com"
|
||||
const val url = "https://git.dorkbox.com/dorkbox/Collections"
|
||||
|
||||
val buildDate = Instant.now().toString()
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
///// assign 'Extras'
|
||||
///////////////////////////////
|
||||
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
|
||||
GradleUtils.defaults()
|
||||
GradleUtils.compileConfiguration(JavaVersion.VERSION_1_8)
|
||||
GradleUtils.jpms(JavaVersion.VERSION_1_9)
|
||||
|
||||
|
||||
licensing {
|
||||
license(License.APACHE_2) {
|
||||
description(Extras.description)
|
||||
author(Extras.vendor)
|
||||
url(Extras.url)
|
||||
|
||||
extra("AhoCorasickDoubleArrayTrie", License.APACHE_2) {
|
||||
description(Extras.description)
|
||||
copyright(2018)
|
||||
author("hankcs <me@hankcs.com>")
|
||||
url("https://github.com/hankcs/AhoCorasickDoubleArrayTrie")
|
||||
}
|
||||
extra("Bias, BinarySearch", License.MIT) {
|
||||
url(Extras.url)
|
||||
url("https://github.com/timboudreau/util")
|
||||
copyright(2013)
|
||||
author("Tim Boudreau")
|
||||
}
|
||||
extra("ConcurrentEntry", License.APACHE_2) {
|
||||
url(Extras.url)
|
||||
copyright(2016)
|
||||
author("bennidi")
|
||||
author("dorkbox")
|
||||
}
|
||||
extra("Collection Utilities (Array, ArrayMap, BooleanArray, ByteArray, CharArray, FloatArray, IdentityMap, IntArray, IntFloatMap, IntIntMap, IntMap, IntSet, LongArray, LongMap, ObjectFloatMap, ObjectIntMap, ObjectMap, ObjectSet, OrderedMap, OrderedSet)", License.APACHE_2) {
|
||||
url(Extras.url)
|
||||
url("https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils")
|
||||
copyright(2011)
|
||||
author("LibGDX")
|
||||
author("Mario Zechner (badlogicgames@gmail.com)")
|
||||
author("Nathan Sweet (nathan.sweet@gmail.com)")
|
||||
}
|
||||
extra("Predicate", License.APACHE_2) {
|
||||
url(Extras.url)
|
||||
url("https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils")
|
||||
copyright(2011)
|
||||
author("LibGDX")
|
||||
author("Mario Zechner (badlogicgames@gmail.com)")
|
||||
author("Nathan Sweet (nathan.sweet@gmail.com)")
|
||||
author("xoppa")
|
||||
}
|
||||
extra("Select, QuickSelect", License.APACHE_2) {
|
||||
url(Extras.url)
|
||||
url("https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils")
|
||||
copyright(2011)
|
||||
author("LibGDX")
|
||||
author("Mario Zechner (badlogicgames@gmail.com)")
|
||||
author("Nathan Sweet (nathan.sweet@gmail.com)")
|
||||
author("Jon Renner")
|
||||
}
|
||||
extra("TimSort, ComparableTimSort", License.APACHE_2) {
|
||||
url(Extras.url)
|
||||
url("https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils")
|
||||
copyright(2008)
|
||||
author("The Android Open Source Project")
|
||||
}
|
||||
extra("ConcurrentWeakIdentityHashMap", License.APACHE_2) {
|
||||
copyright(2016)
|
||||
description("Concurrent WeakIdentity HashMap")
|
||||
author("zhanhb")
|
||||
url("https://github.com/spring-projects/spring-loaded/blob/master/springloaded/src/main/java/org/springsource/loaded/support/ConcurrentWeakIdentityHashMap.java")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jar.get().apply {
|
||||
manifest {
|
||||
// https://docs.oracle.com/javase/tutorial/deployment/jar/packageman.html
|
||||
attributes["Name"] = Extras.name
|
||||
|
||||
attributes["Specification-Title"] = Extras.name
|
||||
attributes["Specification-Version"] = Extras.version
|
||||
attributes["Specification-Vendor"] = Extras.vendor
|
||||
|
||||
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
|
||||
attributes["Implementation-Version"] = Extras.buildDate
|
||||
attributes["Implementation-Vendor"] = Extras.vendor
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api("com.dorkbox:Updates:1.1")
|
||||
}
|
||||
|
||||
publishToSonatype {
|
||||
groupId = Extras.group
|
||||
artifactId = Extras.id
|
||||
version = Extras.version
|
||||
|
||||
name = Extras.name
|
||||
description = Extras.description
|
||||
url = Extras.url
|
||||
|
||||
vendor = Extras.vendor
|
||||
vendorUrl = Extras.vendorUrl
|
||||
|
||||
issueManagement {
|
||||
url = "${Extras.url}/issues"
|
||||
nickname = "Gitea Issues"
|
||||
}
|
||||
|
||||
developer {
|
||||
id = "dorkbox"
|
||||
name = Extras.vendor
|
||||
email = "email@dorkbox.com"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
|
||||
org.gradle.jvmargs=-Dfile.encoding=UTF-8
|
||||
|
||||
#org.gradle.warning.mode=(all,fail,none,summary)
|
||||
org.gradle.warning.mode=all
|
||||
|
||||
#org.gradle.daemon=false
|
||||
# default is 3 hours, this is 1 minute
|
||||
org.gradle.daemon.idletimeout=60000
|
||||
|
||||
#org.gradle.console=(auto,plain,rich,verbose)
|
||||
org.gradle.console=auto
|
||||
|
||||
#org.gradle.logging.level=(quiet,warn,lifecycle,info,debug)
|
||||
org.gradle.logging.level=lifecycle
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
|
@ -0,0 +1,662 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
|
||||
/** A resizable, ordered or unordered array of objects. If unordered, this class avoids a memory copy when removing elements (the
|
||||
* last element is moved to the removed element's position).
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"unchecked", "rawtypes", "SuspiciousSystemArraycopy", "unused", "NullableProblems", "DuplicatedCode"})
|
||||
public class Array<T> implements Iterable<T> {
|
||||
/** Provides direct access to the underlying array. If the Array's generic type is not Object, this field may only be accessed
|
||||
* if the {@link Array#Array(boolean, int, Class)} constructor was used. */
|
||||
public T[] items;
|
||||
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
private ArrayIterable iterable;
|
||||
private Predicate.PredicateIterable<T> predicateIterable;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public Array () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public Array (int capacity) {
|
||||
this(true, capacity);
|
||||
}
|
||||
|
||||
/** @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing array to be grown. */
|
||||
public Array (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = (T[])new Object[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new array with {@link #items} of the specified type.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing array to be grown. */
|
||||
public Array (boolean ordered, int capacity, Class arrayType) {
|
||||
this.ordered = ordered;
|
||||
items = (T[])java.lang.reflect.Array.newInstance(arrayType, capacity);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with {@link #items} of the specified type and a capacity of 16. */
|
||||
public Array (Class arrayType) {
|
||||
this(true, 16, arrayType);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The new array will have the same type of backing array
|
||||
* and will be ordered if the specified array is ordered. The capacity is set to the number of elements, so any subsequent
|
||||
* elements added will cause the backing array to be grown. */
|
||||
public Array (Array<? extends T> array) {
|
||||
this(array.ordered, array.size, array.items.getClass().getComponentType());
|
||||
size = array.size;
|
||||
System.arraycopy(array.items, 0, items, 0, size);
|
||||
}
|
||||
|
||||
/** Creates a new ordered array containing the elements in the specified array. The new array will have the same type of
|
||||
* backing array. The capacity is set to the number of elements, so any subsequent elements added will cause the backing array
|
||||
* to be grown. */
|
||||
public Array (T[] array) {
|
||||
this(true, array, 0, array.length);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The new array will have the same type of backing array.
|
||||
* The capacity is set to the number of elements, so any subsequent elements added will cause the backing array to be grown.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy. */
|
||||
public Array (boolean ordered, T[] array, int start, int count) {
|
||||
this(ordered, count, (Class)array.getClass().getComponentType());
|
||||
size = count;
|
||||
System.arraycopy(array, start, items, 0, size);
|
||||
}
|
||||
|
||||
public void add (T value) {
|
||||
T[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (T value1, T value2) {
|
||||
T[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (T value1, T value2, T value3) {
|
||||
T[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (T value1, T value2, T value3, T value4) {
|
||||
T[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (Array<? extends T> array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (Array<? extends T> array, int start, int count) {
|
||||
if (start + count > array.size)
|
||||
throw new IllegalArgumentException("start + count must be <= size: " + start + " + " + count + " <= " + array.size);
|
||||
addAll((T[])array.items, start, count);
|
||||
}
|
||||
|
||||
public void addAll (T... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (T[] array, int start, int count) {
|
||||
T[] items = this.items;
|
||||
int sizeNeeded = size + count;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, start, items, size, count);
|
||||
size += count;
|
||||
}
|
||||
|
||||
public T get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, T value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void insert (int index, T value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
T[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
T[] items = this.items;
|
||||
T firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
/** Returns if this array contains value.
|
||||
* @param value May be null.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used.
|
||||
* @return true if array contains value, false if it doesn't */
|
||||
public boolean contains (T value, boolean identity) {
|
||||
T[] items = this.items;
|
||||
int i = size - 1;
|
||||
if (identity || value == null) {
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
} else {
|
||||
while (i >= 0)
|
||||
if (value.equals(items[i--])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the index of first occurrence of value in the array, or -1 if no such value exists.
|
||||
* @param value May be null.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used.
|
||||
* @return An index of first occurrence of value in array or -1 if no such value exists */
|
||||
public int indexOf (T value, boolean identity) {
|
||||
T[] items = this.items;
|
||||
if (identity || value == null) {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (value.equals(items[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Returns an index of last occurrence of value in array or -1 if no such value exists. Search is started from the end of an
|
||||
* array.
|
||||
* @param value May be null.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used.
|
||||
* @return An index of last occurrence of value in array or -1 if no such value exists */
|
||||
public int lastIndexOf (T value, boolean identity) {
|
||||
T[] items = this.items;
|
||||
if (identity || value == null) {
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
} else {
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (value.equals(items[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Removes the first instance of the specified value in the array.
|
||||
* @param value May be null.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used.
|
||||
* @return true if value was found and removed, false otherwise */
|
||||
public boolean removeValue (T value, boolean identity) {
|
||||
T[] items = this.items;
|
||||
if (identity || value == null) {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (value.equals(items[i])) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public T removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
T[] items = this.items;
|
||||
T value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
items[size] = null;
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
T[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @param identity True to use ==, false to use .equals().
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (Array<? extends T> array, boolean identity) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
T[] items = this.items;
|
||||
if (identity) {
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
T item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item == items[ii]) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
T item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item.equals(items[ii])) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return size != startSize;
|
||||
}
|
||||
|
||||
/** Removes and returns the last item. */
|
||||
public T pop () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
--size;
|
||||
T item = items[size];
|
||||
items[size] = null;
|
||||
return item;
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public T peek () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public T first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
T[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
items[i] = null;
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing array to the size of the actual items. This is useful to release memory when many items
|
||||
* have been removed, or if it is known that more items will not be added.
|
||||
* @return {@link #items} */
|
||||
public T[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public T[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size null.
|
||||
* @return {@link #items} */
|
||||
public T[] setSize (int newSize) {
|
||||
truncate(newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Creates a new backing array with the specified size containing the current items. */
|
||||
protected T[] resize (int newSize) {
|
||||
T[] items = this.items;
|
||||
T[] newItems = (T[])java.lang.reflect.Array.newInstance(items.getClass().getComponentType(), newSize);
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
/** Sorts this array. The array elements must implement {@link Comparable}. This method is not thread safe (uses
|
||||
* {@link Sort#instance()}). */
|
||||
public void sort () {
|
||||
Sort.instance().sort(items, 0, size);
|
||||
}
|
||||
|
||||
/** Sorts the array. This method is not thread safe (uses {@link Sort#instance()}). */
|
||||
public void sort (Comparator<? super T> comparator) {
|
||||
Sort.instance().sort(items, comparator, 0, size);
|
||||
}
|
||||
|
||||
/** Selects the nth-lowest element from the Array according to Comparator ranking. This might partially sort the Array. The
|
||||
* array must have a size greater than 0, or a {@link RuntimeException} will be thrown.
|
||||
* @see Select
|
||||
* @param comparator used for comparison
|
||||
* @param kthLowest rank of desired object according to comparison, n is based on ordinal numbers, not array indices. for min
|
||||
* value use 1, for max value use size of array, using 0 results in runtime exception.
|
||||
* @return the value of the Nth lowest ranked object. */
|
||||
public T selectRanked (Comparator<T> comparator, int kthLowest) {
|
||||
if (kthLowest < 1) {
|
||||
throw new RuntimeException("nth_lowest must be greater than 0, 1 = first, 2 = second...");
|
||||
}
|
||||
return Select.instance().select(items, comparator, kthLowest, size);
|
||||
}
|
||||
|
||||
/** @see Array#selectRanked(java.util.Comparator, int)
|
||||
* @param comparator used for comparison
|
||||
* @param kthLowest rank of desired object according to comparison, n is based on ordinal numbers, not array indices. for min
|
||||
* value use 1, for max value use size of array, using 0 results in runtime exception.
|
||||
* @return the index of the Nth lowest ranked object. */
|
||||
public int selectRankedIndex (Comparator<T> comparator, int kthLowest) {
|
||||
if (kthLowest < 1) {
|
||||
throw new RuntimeException("nth_lowest must be greater than 0, 1 = first, 2 = second...");
|
||||
}
|
||||
return Select.instance().selectIndex(items, comparator, kthLowest, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
T[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
T temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
T[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = MathUtil.random(i);
|
||||
T temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns an iterator for the items in the array. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link ArrayIterator} constructor for nested or multithreaded iteration. */
|
||||
@Override
|
||||
public Iterator<T> iterator () {
|
||||
if (iterable == null) iterable = new ArrayIterable(this);
|
||||
return iterable.iterator();
|
||||
}
|
||||
|
||||
/** Returns an iterable for the selected items in the array. Remove is supported, but not between hasNext() and next(). Note
|
||||
* that the same iterable instance is returned each time this method is called. Use the {@link Predicate.PredicateIterable}
|
||||
* constructor for nested or multithreaded iteration. */
|
||||
public Iterable<T> select (Predicate<T> predicate) {
|
||||
if (predicateIterable == null)
|
||||
predicateIterable = new Predicate.PredicateIterable<T>(this, predicate);
|
||||
else
|
||||
predicateIterable.set(this, predicate);
|
||||
return predicateIterable;
|
||||
}
|
||||
|
||||
/** Reduces the size of the array to the specified size. If the array is already smaller than the specified size, no action is
|
||||
* taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (size <= newSize) return;
|
||||
for (int i = newSize; i < size; i++)
|
||||
items[i] = null;
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or null if the array is empty. */
|
||||
public T random () {
|
||||
if (size == 0) return null;
|
||||
return items[MathUtil.random(0, size - 1)];
|
||||
}
|
||||
|
||||
/** Returns the items as an array. Note the array is typed, so the {@link #Array(Class)} constructor must have been used.
|
||||
* Otherwise use {@link #toArray(Class)} to specify the array type. */
|
||||
public T[] toArray () {
|
||||
return (T[])toArray(items.getClass().getComponentType());
|
||||
}
|
||||
|
||||
public <V> V[] toArray (Class<V> type) {
|
||||
V[] result = (V[])java.lang.reflect.Array.newInstance(type, size);
|
||||
System.arraycopy(items, 0, result, 0, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
Object[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
h *= 31;
|
||||
Object item = items[i];
|
||||
if (item != null) h += item.hashCode();
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof Array)) return false;
|
||||
Array array = (Array)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
Object[] items1 = this.items;
|
||||
Object[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++) {
|
||||
Object o1 = items1[i];
|
||||
Object o2 = items2[i];
|
||||
if (!(o1 == null ? o2 == null : o1.equals(o2))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
T[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
T[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #Array(Class) */
|
||||
static public <T> Array<T> of (Class<T> arrayType) {
|
||||
return new Array(arrayType);
|
||||
}
|
||||
|
||||
/** @see #Array(boolean, int, Class) */
|
||||
static public <T> Array<T> of (boolean ordered, int capacity, Class<T> arrayType) {
|
||||
return new Array(ordered, capacity, arrayType);
|
||||
}
|
||||
|
||||
/** @see #Array(Object[]) */
|
||||
static public <T> Array<T> with (T... array) {
|
||||
return new Array(array);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
static public class ArrayIterator<T> implements Iterator<T>, Iterable<T> {
|
||||
private final Array<T> array;
|
||||
private final boolean allowRemove;
|
||||
int index;
|
||||
boolean valid = true;
|
||||
|
||||
// ArrayIterable<T> iterable;
|
||||
|
||||
public ArrayIterator (Array<T> array) {
|
||||
this(array, true);
|
||||
}
|
||||
|
||||
public ArrayIterator (Array<T> array, boolean allowRemove) {
|
||||
this.array = array;
|
||||
this.allowRemove = allowRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) {
|
||||
// System.out.println(iterable.lastAcquire);
|
||||
throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
}
|
||||
return index < array.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next () {
|
||||
if (index >= array.size) throw new NoSuchElementException(String.valueOf(index));
|
||||
if (!valid) {
|
||||
// System.out.println(iterable.lastAcquire);
|
||||
throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
}
|
||||
return array.items[index++];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (!allowRemove) throw new RuntimeException("Remove not allowed.");
|
||||
index--;
|
||||
array.removeIndex(index);
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator () {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "NullableProblems"})
|
||||
static public class ArrayIterable<T> implements Iterable<T> {
|
||||
private final Array<T> array;
|
||||
private final boolean allowRemove;
|
||||
private ArrayIterator iterator1, iterator2;
|
||||
|
||||
// java.io.StringWriter lastAcquire = new java.io.StringWriter();
|
||||
|
||||
public ArrayIterable (Array<T> array) {
|
||||
this(array, true);
|
||||
}
|
||||
|
||||
public ArrayIterable (Array<T> array, boolean allowRemove) {
|
||||
this.array = array;
|
||||
this.allowRemove = allowRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator () {
|
||||
// lastAcquire.getBuffer().setLength(0);
|
||||
// new Throwable().printStackTrace(new java.io.PrintWriter(lastAcquire));
|
||||
if (iterator1 == null) {
|
||||
iterator1 = new ArrayIterator(array, allowRemove);
|
||||
iterator2 = new ArrayIterator(array, allowRemove);
|
||||
// iterator1.iterable = this;
|
||||
// iterator2.iterable = this;
|
||||
}
|
||||
if (!iterator1.valid) {
|
||||
iterator1.index = 0;
|
||||
iterator1.valid = true;
|
||||
iterator2.valid = false;
|
||||
return iterator1;
|
||||
}
|
||||
iterator2.index = 0;
|
||||
iterator2.valid = true;
|
||||
iterator1.valid = false;
|
||||
return iterator2;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,660 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.collections.ObjectMap.Entry;
|
||||
|
||||
/** An ordered or unordered map of objects. This implementation uses arrays to store the keys and values, which means
|
||||
* {@link #getKey(Object, boolean) gets} do a comparison for each key in the map. This is slower than a typical hash map
|
||||
* implementation, but may be acceptable for small maps and has the benefits that keys and values can be accessed by index, which
|
||||
* makes iteration fast. Like {@link Array}, if ordered is false, this class avoids a memory copy when removing elements (the last
|
||||
* element is moved to the removed element's position).
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"unchecked", "rawtypes", "unused", "SuspiciousSystemArraycopy", "NullableProblems"})
|
||||
public class ArrayMap<K, V> implements Iterable<Entry<K, V>> {
|
||||
public K[] keys;
|
||||
public V[] values;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values valuesIter1, valuesIter2;
|
||||
private Keys keysIter1, keysIter2;
|
||||
|
||||
/** Creates an ordered map with a capacity of 16. */
|
||||
public ArrayMap () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered map with the specified capacity. */
|
||||
public ArrayMap (int capacity) {
|
||||
this(true, capacity);
|
||||
}
|
||||
|
||||
/** @param ordered If false, methods that remove elements may change the order of other elements in the arrays, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing arrays to be grown. */
|
||||
public ArrayMap (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
keys = (K[])new Object[capacity];
|
||||
values = (V[])new Object[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new map with {@link #keys} and {@link #values} of the specified type.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the arrays, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing arrays to be grown. */
|
||||
public ArrayMap (boolean ordered, int capacity, Class keyArrayType, Class valueArrayType) {
|
||||
this.ordered = ordered;
|
||||
keys = (K[])java.lang.reflect.Array.newInstance(keyArrayType, capacity);
|
||||
values = (V[])java.lang.reflect.Array.newInstance(valueArrayType, capacity);
|
||||
}
|
||||
|
||||
/** Creates an ordered map with {@link #keys} and {@link #values} of the specified type and a capacity of 16. */
|
||||
public ArrayMap (Class keyArrayType, Class valueArrayType) {
|
||||
this(false, 16, keyArrayType, valueArrayType);
|
||||
}
|
||||
|
||||
/** Creates a new map containing the elements in the specified map. The new map will have the same type of backing arrays and
|
||||
* will be ordered if the specified map is ordered. The capacity is set to the number of elements, so any subsequent elements
|
||||
* added will cause the backing arrays to be grown. */
|
||||
@SuppressWarnings("CopyConstructorMissesField")
|
||||
public ArrayMap (ArrayMap array) {
|
||||
this(array.ordered, array.size, array.keys.getClass().getComponentType(), array.values.getClass().getComponentType());
|
||||
size = array.size;
|
||||
System.arraycopy(array.keys, 0, keys, 0, size);
|
||||
System.arraycopy(array.values, 0, values, 0, size);
|
||||
}
|
||||
|
||||
public int put (K key, V value) {
|
||||
int index = indexOfKey(key);
|
||||
if (index == -1) {
|
||||
if (size == keys.length) resize(Math.max(8, (int)(size * 1.75f)));
|
||||
index = size++;
|
||||
}
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
return index;
|
||||
}
|
||||
|
||||
public int put (K key, V value, int index) {
|
||||
int existingIndex = indexOfKey(key);
|
||||
if (existingIndex != -1)
|
||||
removeIndex(existingIndex);
|
||||
else if (size == keys.length) //
|
||||
resize(Math.max(8, (int)(size * 1.75f)));
|
||||
System.arraycopy(keys, index, keys, index + 1, size - index);
|
||||
System.arraycopy(values, index, values, index + 1, size - index);
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
size++;
|
||||
return index;
|
||||
}
|
||||
|
||||
public void putAll (ArrayMap<? extends K, ? extends V> map) {
|
||||
putAll(map, 0, map.size);
|
||||
}
|
||||
|
||||
public void putAll (ArrayMap<? extends K, ? extends V> map, int offset, int length) {
|
||||
if (offset + length > map.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + map.size);
|
||||
int sizeNeeded = size + length - offset;
|
||||
if (sizeNeeded >= keys.length) resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(map.keys, offset, keys, size, length);
|
||||
System.arraycopy(map.values, offset, values, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
/** Returns the value for the specified key. Note this does a .equals() comparison of each key in reverse order until the
|
||||
* specified key is found. */
|
||||
public V get (K key) {
|
||||
Object[] keys = this.keys;
|
||||
int i = size - 1;
|
||||
if (key == null) {
|
||||
for (; i >= 0; i--)
|
||||
if (keys[i] == key) return values[i];
|
||||
} else {
|
||||
for (; i >= 0; i--)
|
||||
if (key.equals(keys[i])) return values[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value. Note this does a comparison of each value in reverse order until the specified
|
||||
* value is found.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used. */
|
||||
public K getKey (V value, boolean identity) {
|
||||
Object[] values = this.values;
|
||||
int i = size - 1;
|
||||
if (identity || value == null) {
|
||||
for (; i >= 0; i--)
|
||||
if (values[i] == value) return keys[i];
|
||||
} else {
|
||||
for (; i >= 0; i--)
|
||||
if (value.equals(values[i])) return keys[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public K getKeyAt (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
return keys[index];
|
||||
}
|
||||
|
||||
public V getValueAt (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
return values[index];
|
||||
}
|
||||
|
||||
public K firstKey () {
|
||||
if (size == 0) throw new IllegalStateException("Map is empty.");
|
||||
return keys[0];
|
||||
}
|
||||
|
||||
public V firstValue () {
|
||||
if (size == 0) throw new IllegalStateException("Map is empty.");
|
||||
return values[0];
|
||||
}
|
||||
|
||||
public void setKey (int index, K key) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
keys[index] = key;
|
||||
}
|
||||
|
||||
public void setValue (int index, V value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
values[index] = value;
|
||||
}
|
||||
|
||||
public void insert (int index, K key, V value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
if (size == keys.length) resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered) {
|
||||
System.arraycopy(keys, index, keys, index + 1, size - index);
|
||||
System.arraycopy(values, index, values, index + 1, size - index);
|
||||
} else {
|
||||
keys[size] = keys[index];
|
||||
values[size] = values[index];
|
||||
}
|
||||
size++;
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
}
|
||||
|
||||
public boolean containsKey (K key) {
|
||||
K[] keys = this.keys;
|
||||
int i = size - 1;
|
||||
if (key == null) {
|
||||
while (i >= 0)
|
||||
if (keys[i--] == key) return true;
|
||||
} else {
|
||||
while (i >= 0)
|
||||
if (key.equals(keys[i--])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @param identity If true, == comparison will be used. If false, .equals() comparison will be used. */
|
||||
public boolean containsValue (V value, boolean identity) {
|
||||
V[] values = this.values;
|
||||
int i = size - 1;
|
||||
if (identity || value == null) {
|
||||
while (i >= 0)
|
||||
if (values[i--] == value) return true;
|
||||
} else {
|
||||
while (i >= 0)
|
||||
if (value.equals(values[i--])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOfKey (K key) {
|
||||
Object[] keys = this.keys;
|
||||
if (key == null) {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (keys[i] == key) return i;
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (key.equals(keys[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int indexOfValue (V value, boolean identity) {
|
||||
Object[] values = this.values;
|
||||
if (identity || value == null) {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (values[i] == value) return i;
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (value.equals(values[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public V removeKey (K key) {
|
||||
Object[] keys = this.keys;
|
||||
if (key == null) {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (keys[i] == key) {
|
||||
V value = values[i];
|
||||
removeIndex(i);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (key.equals(keys[i])) {
|
||||
V value = values[i];
|
||||
removeIndex(i);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean removeValue (V value, boolean identity) {
|
||||
Object[] values = this.values;
|
||||
if (identity || value == null) {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (values[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (value.equals(values[i])) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the key/values pair at the specified index. */
|
||||
public void removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
Object[] keys = this.keys;
|
||||
size--;
|
||||
if (ordered) {
|
||||
System.arraycopy(keys, index + 1, keys, index, size - index);
|
||||
System.arraycopy(values, index + 1, values, index, size - index);
|
||||
} else {
|
||||
keys[index] = keys[size];
|
||||
values[index] = values[size];
|
||||
}
|
||||
keys[size] = null;
|
||||
values[size] = null;
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Returns the last key. */
|
||||
public K peekKey () {
|
||||
return keys[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the last value. */
|
||||
public V peekValue () {
|
||||
return values[size - 1];
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (keys.length <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
K[] keys = this.keys;
|
||||
V[] values = this.values;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
keys[i] = null;
|
||||
values[i] = null;
|
||||
}
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to the size of the actual number of entries. This is useful to release memory when
|
||||
* many items have been removed, or if it is known that more entries will not be added. */
|
||||
public void shrink () {
|
||||
if (keys.length == size) return;
|
||||
resize(size);
|
||||
}
|
||||
|
||||
/** Increases the size of the backing arrays to accommodate the specified number of additional entries. Useful before adding
|
||||
* many entries to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= keys.length) resize(Math.max(8, sizeNeeded));
|
||||
}
|
||||
|
||||
protected void resize (int newSize) {
|
||||
K[] newKeys = (K[])java.lang.reflect.Array.newInstance(keys.getClass().getComponentType(), newSize);
|
||||
System.arraycopy(keys, 0, newKeys, 0, Math.min(size, newKeys.length));
|
||||
this.keys = newKeys;
|
||||
|
||||
V[] newValues = (V[])java.lang.reflect.Array.newInstance(values.getClass().getComponentType(), newSize);
|
||||
System.arraycopy(values, 0, newValues, 0, Math.min(size, newValues.length));
|
||||
this.values = newValues;
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
K tempKey = keys[i];
|
||||
keys[i] = keys[ii];
|
||||
keys[ii] = tempKey;
|
||||
|
||||
V tempValue = values[i];
|
||||
values[i] = values[ii];
|
||||
values[ii] = tempValue;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = MathUtil.random(i);
|
||||
K tempKey = keys[i];
|
||||
keys[i] = keys[ii];
|
||||
keys[ii] = tempKey;
|
||||
|
||||
V tempValue = values[i];
|
||||
values[i] = values[ii];
|
||||
values[ii] = tempValue;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reduces the size of the arrays to the specified size. If the arrays are already smaller than the specified size, no action
|
||||
* is taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (size <= newSize) return;
|
||||
for (int i = newSize; i < size; i++) {
|
||||
keys[i] = null;
|
||||
values[i] = null;
|
||||
}
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
K[] keys = this.keys;
|
||||
V[] values = this.values;
|
||||
int h = 0;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
K key = keys[i];
|
||||
V value = values[i];
|
||||
if (key != null) h += key.hashCode() * 31;
|
||||
if (value != null) h += value.hashCode();
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof ArrayMap)) return false;
|
||||
ArrayMap<K, V> other = (ArrayMap)obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keys = this.keys;
|
||||
V[] values = this.values;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
K key = keys[i];
|
||||
V value = values[i];
|
||||
if (value == null) {
|
||||
if (!other.containsKey(key) || other.get(key) != null) return false;
|
||||
} else {
|
||||
if (!value.equals(other.get(key))) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
K[] keys = this.keys;
|
||||
V[] values = this.values;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
buffer.append(keys[0]);
|
||||
buffer.append('=');
|
||||
buffer.append(values[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(keys[i]);
|
||||
buffer.append('=');
|
||||
buffer.append(values[i]);
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Entries<K, V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.index = 0;
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.index = 0;
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Values<V> values () {
|
||||
if (valuesIter1 == null) {
|
||||
valuesIter1 = new Values(this);
|
||||
valuesIter2 = new Values(this);
|
||||
}
|
||||
if (!valuesIter1.valid) {
|
||||
valuesIter1.index = 0;
|
||||
valuesIter1.valid = true;
|
||||
valuesIter2.valid = false;
|
||||
return valuesIter1;
|
||||
}
|
||||
valuesIter2.index = 0;
|
||||
valuesIter2.valid = true;
|
||||
valuesIter1.valid = false;
|
||||
return valuesIter2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Keys<K> keys () {
|
||||
if (keysIter1 == null) {
|
||||
keysIter1 = new Keys(this);
|
||||
keysIter2 = new Keys(this);
|
||||
}
|
||||
if (!keysIter1.valid) {
|
||||
keysIter1.index = 0;
|
||||
keysIter1.valid = true;
|
||||
keysIter2.valid = false;
|
||||
return keysIter1;
|
||||
}
|
||||
keysIter2.index = 0;
|
||||
keysIter2.valid = true;
|
||||
keysIter1.valid = false;
|
||||
return keysIter2;
|
||||
}
|
||||
|
||||
static public class Entries<K, V> implements Iterable<Entry<K, V>>, Iterator<Entry<K, V>> {
|
||||
private final ArrayMap<K, V> map;
|
||||
Entry<K, V> entry = new Entry();
|
||||
int index;
|
||||
boolean valid = true;
|
||||
|
||||
public Entries (ArrayMap<K, V> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return index < map.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
@Override
|
||||
public Entry<K, V> next () {
|
||||
if (index >= map.size) throw new NoSuchElementException(String.valueOf(index));
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
entry.key = map.keys[index];
|
||||
entry.value = map.values[index++];
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
index--;
|
||||
map.removeIndex(index);
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values<V> implements Iterable<V>, Iterator<V> {
|
||||
private final ArrayMap<Object, V> map;
|
||||
int index;
|
||||
boolean valid = true;
|
||||
|
||||
public Values (ArrayMap<Object, V> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return index < map.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next () {
|
||||
if (index >= map.size) throw new NoSuchElementException(String.valueOf(index));
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return map.values[index++];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
index--;
|
||||
map.removeIndex(index);
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
public Array<V> toArray () {
|
||||
return new Array(true, map.values, index, map.size - index);
|
||||
}
|
||||
|
||||
public Array<V> toArray (Array array) {
|
||||
array.addAll(map.values, index, map.size - index);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys<K> implements Iterable<K>, Iterator<K> {
|
||||
private final ArrayMap<K, Object> map;
|
||||
int index;
|
||||
boolean valid = true;
|
||||
|
||||
public Keys (ArrayMap<K, Object> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return index < map.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next () {
|
||||
if (index >= map.size) throw new NoSuchElementException(String.valueOf(index));
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return map.keys[index++];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
index--;
|
||||
map.removeIndex(index);
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
public Array<K> toArray () {
|
||||
return new Array(true, map.keys, index, map.size - index);
|
||||
}
|
||||
|
||||
public Array<K> toArray (Array array) {
|
||||
array.addAll(map.keys, index, map.size - index);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2013 Tim Boudreau.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
/**
|
||||
* Bias used to decide how to resolve ambiguity, for example in a binary search
|
||||
* with a list of timestamps - if the requested timestamp lies between two
|
||||
* actual data snapshots, should we return the next, previous, nearest one, or
|
||||
* none unless there is an exact match.
|
||||
*
|
||||
* @author Tim Boudreau
|
||||
*/
|
||||
public enum Bias {
|
||||
|
||||
/**
|
||||
* If a search result falls between two elements, prefer the next element
|
||||
*/
|
||||
FORWARD,
|
||||
/**
|
||||
* If a search result falls between two elements, prefer the previous element
|
||||
*/
|
||||
BACKWARD,
|
||||
/**
|
||||
* If a search result falls between two elements, prefer the element with
|
||||
* the minimum distance
|
||||
*/
|
||||
NEAREST,
|
||||
/**
|
||||
* If a search result falls between two elements, return no element unless
|
||||
* there is an exact match
|
||||
*/
|
||||
NONE;
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2013 Tim Boudreau.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* General-purpose binary search algorithm; you pass in an array or list
|
||||
* wrapped in an instance of <code>Indexed</code>, and an
|
||||
* <code>Evaluator</code> which converts the contents of the list into
|
||||
* numbers used by the binary search algorithm. Note that the data
|
||||
* (as returned by the Indexed array/list) <b><i>must be in order from
|
||||
* low to high</i></b>. The indices need not be contiguous (presumably
|
||||
* they are not or you wouldn't be using this class), but they must be
|
||||
* sorted. If assertions are enabled, this is enforced; if not, very
|
||||
* bad things (endless loops, etc.) can happen as a consequence of passing
|
||||
* unsorted data in.
|
||||
* <p/>
|
||||
* This class is not thread-safe and the size and contents of the <code>Indexed</code>
|
||||
* should not change while a search is being performed.
|
||||
*
|
||||
* @author Tim Boudreau
|
||||
*/
|
||||
public class BinarySearch<T> {
|
||||
|
||||
private final Evaluator<T> eval;
|
||||
private final Indexed<T> indexed;
|
||||
|
||||
/**
|
||||
* Create a new binary search.
|
||||
*
|
||||
* @param eval The thing which converts elements into numbers
|
||||
* @param indexed A collection, list or array
|
||||
*/
|
||||
public BinarySearch(Evaluator<T> eval, Indexed<T> indexed) {
|
||||
this.eval = eval;
|
||||
this.indexed = indexed;
|
||||
assert checkSorted();
|
||||
}
|
||||
|
||||
public BinarySearch(Evaluator<T> eval, List<T> l) {
|
||||
this (eval, new ListWrap<T>(l));
|
||||
}
|
||||
|
||||
private boolean checkSorted() {
|
||||
long val = Long.MIN_VALUE;
|
||||
long sz = this.indexed.size();
|
||||
for (long i=0; i < sz; i++) {
|
||||
T t = this.indexed.get(i);
|
||||
long nue = this.eval.getValue(t);
|
||||
if (val != Long.MIN_VALUE) {
|
||||
if (nue < val) {
|
||||
throw new IllegalArgumentException("Collection is not sorted at " + i + " - " + this.indexed);
|
||||
}
|
||||
}
|
||||
val = nue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public long search(long value, Bias bias) {
|
||||
return search(0, this.indexed.size()-1, value, bias);
|
||||
}
|
||||
|
||||
public T match(T prototype, Bias bias) {
|
||||
long value = this.eval.getValue(prototype);
|
||||
long index = search(value, bias);
|
||||
return index == -1 ? null : this.indexed.get(index);
|
||||
}
|
||||
|
||||
public T searchFor(long value, Bias bias) {
|
||||
long index = search(value, bias);
|
||||
return index == -1 ? null : this.indexed.get(index);
|
||||
}
|
||||
|
||||
private long search(long start, long end, long value, Bias bias) {
|
||||
long range = end - start;
|
||||
if (range == 0) {
|
||||
return start;
|
||||
}
|
||||
if (range == 1) {
|
||||
T ahead = this.indexed.get(end);
|
||||
T behind = this.indexed.get(start);
|
||||
long v1 = this.eval.getValue(behind);
|
||||
long v2 = this.eval.getValue(ahead);
|
||||
switch (bias) {
|
||||
case BACKWARD:
|
||||
return start;
|
||||
case FORWARD:
|
||||
return end;
|
||||
case NEAREST:
|
||||
if (v1 == value) {
|
||||
return start;
|
||||
} else if (v2 == value) {
|
||||
return end;
|
||||
} else {
|
||||
if (Math.abs(v1 - value) < Math.abs(v2 - value)) {
|
||||
return start;
|
||||
} else {
|
||||
return end;
|
||||
}
|
||||
}
|
||||
case NONE:
|
||||
if (v1 == value) {
|
||||
return start;
|
||||
} else if (v2 == value) {
|
||||
return end;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
default:
|
||||
throw new AssertionError(bias);
|
||||
|
||||
}
|
||||
}
|
||||
long mid = start + range / 2;
|
||||
long vm = this.eval.getValue(this.indexed.get(mid));
|
||||
if (value >= vm) {
|
||||
return search(mid, end, value, bias);
|
||||
} else {
|
||||
return search(start, mid, value, bias);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object into a numeric value that is used to
|
||||
* perform binary search
|
||||
* @param <T>
|
||||
*/
|
||||
public interface Evaluator<T> {
|
||||
|
||||
public long getValue(T obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstraction for list-like things which have a length and indices
|
||||
* @param <T>
|
||||
*/
|
||||
public interface Indexed<T> {
|
||||
|
||||
public T get(long index);
|
||||
|
||||
public long size();
|
||||
}
|
||||
|
||||
private static final class ListWrap<T> implements Indexed<T> {
|
||||
|
||||
private final List<T> l;
|
||||
|
||||
ListWrap(List<T> l) {
|
||||
this.l = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long index) {
|
||||
return this.l.get((int) index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() {
|
||||
return this.l.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + '{' + this.l + '}';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,363 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
/** A resizable, ordered or unordered boolean array. Avoids the boxing that occurs with ArrayList<Boolean>. It is less memory
|
||||
* efficient than {@link BitSet}, except for very small sizes. It more CPU efficient than {@link BitSet}, except for very large
|
||||
* sizes or if BitSet functionality such as and, or, xor, etc are needed. If unordered, this class avoids a memory copy when
|
||||
* removing elements (the last element is moved to the removed element's position).
|
||||
* @author Nathan Sweet */
|
||||
public class BooleanArray {
|
||||
public boolean[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public BooleanArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public BooleanArray (int capacity) {
|
||||
this(true, capacity);
|
||||
}
|
||||
|
||||
/** @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing array to be grown. */
|
||||
public BooleanArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new boolean[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specific array. The new array will be ordered if the specific array is
|
||||
* ordered. The capacity is set to the number of elements, so any subsequent elements added will cause the backing array to be
|
||||
* grown. */
|
||||
public BooleanArray (BooleanArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new boolean[size];
|
||||
System.arraycopy(array.items, 0, items, 0, size);
|
||||
}
|
||||
|
||||
/** Creates a new ordered array containing the elements in the specified array. The capacity is set to the number of elements,
|
||||
* so any subsequent elements added will cause the backing array to be grown. */
|
||||
public BooleanArray (boolean[] array) {
|
||||
this(true, array, 0, array.length);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The capacity is set to the number of elements, so any
|
||||
* subsequent elements added will cause the backing array to be grown.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy. */
|
||||
public BooleanArray (boolean ordered, boolean[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (boolean value) {
|
||||
boolean[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (boolean value1, boolean value2) {
|
||||
boolean[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (boolean value1, boolean value2, boolean value3) {
|
||||
boolean[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (boolean value1, boolean value2, boolean value3, boolean value4) {
|
||||
boolean[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (BooleanArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (BooleanArray array, int offset, int length) {
|
||||
if (offset + length > array.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
|
||||
addAll(array.items, offset, length);
|
||||
}
|
||||
|
||||
public void addAll (boolean... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (boolean[] array, int offset, int length) {
|
||||
boolean[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public boolean get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, boolean value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void insert (int index, boolean value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
boolean[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
boolean[] items = this.items;
|
||||
boolean firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public boolean removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
boolean[] items = this.items;
|
||||
boolean value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
boolean[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (BooleanArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
boolean[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
boolean item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item == items[ii]) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return size != startSize;
|
||||
}
|
||||
|
||||
/** Removes and returns the last item. */
|
||||
public boolean pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public boolean peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public boolean first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing array to the size of the actual items. This is useful to release memory when many items
|
||||
* have been removed, or if it is known that more items will not be added.
|
||||
* @return {@link #items} */
|
||||
public boolean[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public boolean[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public boolean[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected boolean[] resize (int newSize) {
|
||||
boolean[] newItems = new boolean[newSize];
|
||||
boolean[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
boolean[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
boolean temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
boolean[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = MathUtil.random(i);
|
||||
boolean temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reduces the size of the array to the specified size. If the array is already smaller than the specified size, no action is
|
||||
* taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or false if the array is empty. */
|
||||
public boolean random () {
|
||||
if (size == 0) return false;
|
||||
return items[MathUtil.random(0, size - 1)];
|
||||
}
|
||||
|
||||
public boolean[] toArray () {
|
||||
boolean[] array = new boolean[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
boolean[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + (items[i] ? 1231 : 1237);
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof BooleanArray)) return false;
|
||||
BooleanArray array = (BooleanArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
boolean[] items1 = this.items;
|
||||
boolean[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items1[i] != items2[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
boolean[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
boolean[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #BooleanArray(boolean[]) */
|
||||
static public BooleanArray with (boolean... array) {
|
||||
return new BooleanArray(array);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,408 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/** A resizable, ordered or unordered byte array. Avoids the boxing that occurs with ArrayList<Byte>. If unordered, this class
|
||||
* avoids a memory copy when removing elements (the last element is moved to the removed element's position).
|
||||
* @author Nathan Sweet */
|
||||
public class ByteArray {
|
||||
public byte[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public ByteArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public ByteArray (int capacity) {
|
||||
this(true, capacity);
|
||||
}
|
||||
|
||||
/** @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing array to be grown. */
|
||||
public ByteArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new byte[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specific array. The new array will be ordered if the specific array is
|
||||
* ordered. The capacity is set to the number of elements, so any subsequent elements added will cause the backing array to be
|
||||
* grown. */
|
||||
public ByteArray (ByteArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new byte[size];
|
||||
System.arraycopy(array.items, 0, items, 0, size);
|
||||
}
|
||||
|
||||
/** Creates a new ordered array containing the elements in the specified array. The capacity is set to the number of elements,
|
||||
* so any subsequent elements added will cause the backing array to be grown. */
|
||||
public ByteArray (byte[] array) {
|
||||
this(true, array, 0, array.length);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The capacity is set to the number of elements, so any
|
||||
* subsequent elements added will cause the backing array to be grown.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy. */
|
||||
public ByteArray (boolean ordered, byte[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (byte value) {
|
||||
byte[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (byte value1, byte value2) {
|
||||
byte[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (byte value1, byte value2, byte value3) {
|
||||
byte[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (byte value1, byte value2, byte value3, byte value4) {
|
||||
byte[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (ByteArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (ByteArray array, int offset, int length) {
|
||||
if (offset + length > array.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
|
||||
addAll(array.items, offset, length);
|
||||
}
|
||||
|
||||
public void addAll (byte... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (byte[] array, int offset, int length) {
|
||||
byte[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public byte get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, byte value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, byte value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, byte value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, byte value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
byte[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
byte[] items = this.items;
|
||||
byte firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
public boolean contains (byte value) {
|
||||
int i = size - 1;
|
||||
byte[] items = this.items;
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOf (byte value) {
|
||||
byte[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int lastIndexOf (byte value) {
|
||||
byte[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean removeValue (byte value) {
|
||||
byte[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public int removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
byte[] items = this.items;
|
||||
int value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
byte[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (ByteArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
byte[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
int item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item == items[ii]) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return size != startSize;
|
||||
}
|
||||
|
||||
/** Removes and returns the last item. */
|
||||
public byte pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public byte peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public byte first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing array to the size of the actual items. This is useful to release memory when many items
|
||||
* have been removed, or if it is known that more items will not be added.
|
||||
* @return {@link #items} */
|
||||
public byte[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public byte[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public byte[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected byte[] resize (int newSize) {
|
||||
byte[] newItems = new byte[newSize];
|
||||
byte[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
byte[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
byte temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
byte[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = MathUtil.random(i);
|
||||
byte temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reduces the size of the array to the specified size. If the array is already smaller than the specified size, no action is
|
||||
* taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or zero if the array is empty. */
|
||||
public byte random () {
|
||||
if (size == 0) return 0;
|
||||
return items[MathUtil.random(0, size - 1)];
|
||||
}
|
||||
|
||||
public byte[] toArray () {
|
||||
byte[] array = new byte[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
byte[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + items[i];
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof ByteArray)) return false;
|
||||
ByteArray array = (ByteArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
byte[] items1 = this.items;
|
||||
byte[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items1[i] != items2[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
byte[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
byte[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #ByteArray(byte[]) */
|
||||
static public ByteArray with (byte... array) {
|
||||
return new ByteArray(array);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,408 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/** A resizable, ordered or unordered char array. Avoids the boxing that occurs with ArrayList<Character>. If unordered, this
|
||||
* class avoids a memory copy when removing elements (the last element is moved to the removed element's position).
|
||||
* @author Nathan Sweet */
|
||||
public class CharArray {
|
||||
public char[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public CharArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public CharArray (int capacity) {
|
||||
this(true, capacity);
|
||||
}
|
||||
|
||||
/** @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing array to be grown. */
|
||||
public CharArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new char[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specific array. The new array will be ordered if the specific array is
|
||||
* ordered. The capacity is set to the number of elements, so any subsequent elements added will cause the backing array to be
|
||||
* grown. */
|
||||
public CharArray (CharArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new char[size];
|
||||
System.arraycopy(array.items, 0, items, 0, size);
|
||||
}
|
||||
|
||||
/** Creates a new ordered array containing the elements in the specified array. The capacity is set to the number of elements,
|
||||
* so any subsequent elements added will cause the backing array to be grown. */
|
||||
public CharArray (char[] array) {
|
||||
this(true, array, 0, array.length);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The capacity is set to the number of elements, so any
|
||||
* subsequent elements added will cause the backing array to be grown.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy. */
|
||||
public CharArray (boolean ordered, char[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (char value) {
|
||||
char[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (char value1, char value2) {
|
||||
char[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (char value1, char value2, char value3) {
|
||||
char[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (char value1, char value2, char value3, char value4) {
|
||||
char[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (CharArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (CharArray array, int offset, int length) {
|
||||
if (offset + length > array.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
|
||||
addAll(array.items, offset, length);
|
||||
}
|
||||
|
||||
public void addAll (char... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (char[] array, int offset, int length) {
|
||||
char[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public char get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, char value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, char value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, char value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, char value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
char[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
char[] items = this.items;
|
||||
char firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
public boolean contains (char value) {
|
||||
int i = size - 1;
|
||||
char[] items = this.items;
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOf (char value) {
|
||||
char[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int lastIndexOf (char value) {
|
||||
char[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean removeValue (char value) {
|
||||
char[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public char removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
char[] items = this.items;
|
||||
char value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
char[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (CharArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
char[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
char item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item == items[ii]) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return size != startSize;
|
||||
}
|
||||
|
||||
/** Removes and returns the last item. */
|
||||
public char pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public char peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public char first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing array to the size of the actual items. This is useful to release memory when many items
|
||||
* have been removed, or if it is known that more items will not be added.
|
||||
* @return {@link #items} */
|
||||
public char[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public char[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public char[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected char[] resize (int newSize) {
|
||||
char[] newItems = new char[newSize];
|
||||
char[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
char[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
char temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
char[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = MathUtil.random(i);
|
||||
char temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reduces the size of the array to the specified size. If the array is already smaller than the specified size, no action is
|
||||
* taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or zero if the array is empty. */
|
||||
public char random () {
|
||||
if (size == 0) return 0;
|
||||
return items[MathUtil.random(0, size - 1)];
|
||||
}
|
||||
|
||||
public char[] toArray () {
|
||||
char[] array = new char[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
char[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + items[i];
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof CharArray)) return false;
|
||||
CharArray array = (CharArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
char[] items1 = this.items;
|
||||
char[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items1[i] != items2[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
char[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
char[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #CharArray(char[]) */
|
||||
static public CharArray with (char... array) {
|
||||
return new CharArray(array);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,805 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
|
||||
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
/** This is a near duplicate of {@link TimSort}, modified for use with arrays of objects that implement {@link Comparable}, instead
|
||||
* of using explicit comparators.
|
||||
*
|
||||
* <p>
|
||||
* If you are using an optimizing VM, you may find that ComparableTimSort offers no performance benefit over TimSort in
|
||||
* conjunction with a comparator that simply returns {@code ((Comparable)first).compareTo(Second)}. If this is the case, you are
|
||||
* better off deleting ComparableTimSort to eliminate the code duplication. (See Arrays.java for details.) */
|
||||
class ComparableTimSort {
|
||||
/** This is the minimum sized sequence that will be merged. Shorter sequences will be lengthened by calling binarySort. If the
|
||||
* entire array is less than this length, no merges will be performed.
|
||||
*
|
||||
* This constant should be a power of two. It was 64 in Tim Peter's C implementation, but 32 was empirically determined to work
|
||||
* better in this implementation. In the unlikely event that you set this constant to be a number that's not a power of two,
|
||||
* you'll need to change the {@link #minRunLength} computation.
|
||||
*
|
||||
* If you decrease this constant, you must change the stackLen computation in the TimSort constructor, or you risk an
|
||||
* ArrayOutOfBounds exception. See listsort.txt for a discussion of the minimum stack length required as a function of the
|
||||
* length of the array being sorted and the minimum merge sequence length. */
|
||||
private static final int MIN_MERGE = 32;
|
||||
|
||||
/** The array being sorted. */
|
||||
private Object[] a;
|
||||
|
||||
/** When we get into galloping mode, we stay there until both runs win less often than MIN_GALLOP consecutive times. */
|
||||
private static final int MIN_GALLOP = 7;
|
||||
|
||||
/** This controls when we get *into* galloping mode. It is initialized to MIN_GALLOP. The mergeLo and mergeHi methods nudge it
|
||||
* higher for random data, and lower for highly structured data. */
|
||||
private int minGallop = MIN_GALLOP;
|
||||
|
||||
/** Maximum initial size of tmp array, which is used for merging. The array can grow to accommodate demand.
|
||||
*
|
||||
* Unlike Tim's original C version, we do not allocate this much storage when sorting smaller arrays. This change was required
|
||||
* for performance. */
|
||||
private static final int INITIAL_TMP_STORAGE_LENGTH = 256;
|
||||
|
||||
/** Temp storage for merges. */
|
||||
private Object[] tmp;
|
||||
private int tmpCount;
|
||||
|
||||
/** A stack of pending runs yet to be merged. Run i starts at address base[i] and extends for len[i] elements. It's always true
|
||||
* (so long as the indices are in bounds) that:
|
||||
*
|
||||
* runBase[i] + runLen[i] == runBase[i + 1]
|
||||
*
|
||||
* so we could cut the storage for this, but it's a minor amount, and keeping all the info explicit simplifies the code. */
|
||||
private int stackSize = 0; // Number of pending runs on stack
|
||||
private final int[] runBase;
|
||||
private final int[] runLen;
|
||||
|
||||
/** Asserts have been placed in if-statements for performace. To enable them, set this field to true and enable them in VM with
|
||||
* a command line flag. If you modify this class, please do test the asserts! */
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
ComparableTimSort () {
|
||||
tmp = new Object[INITIAL_TMP_STORAGE_LENGTH];
|
||||
runBase = new int[40];
|
||||
runLen = new int[40];
|
||||
}
|
||||
|
||||
public void doSort (Object[] a, int lo, int hi) {
|
||||
stackSize = 0;
|
||||
rangeCheck(a.length, lo, hi);
|
||||
int nRemaining = hi - lo;
|
||||
if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted
|
||||
|
||||
// If array is small, do a "mini-TimSort" with no merges
|
||||
if (nRemaining < MIN_MERGE) {
|
||||
int initRunLen = countRunAndMakeAscending(a, lo, hi);
|
||||
binarySort(a, lo, hi, lo + initRunLen);
|
||||
return;
|
||||
}
|
||||
|
||||
this.a = a;
|
||||
tmpCount = 0;
|
||||
|
||||
/** March over the array once, left to right, finding natural runs, extending short natural runs to minRun elements, and
|
||||
* merging runs to maintain stack invariant. */
|
||||
int minRun = minRunLength(nRemaining);
|
||||
do {
|
||||
// Identify next run
|
||||
int runLen = countRunAndMakeAscending(a, lo, hi);
|
||||
|
||||
// If run is short, extend to min(minRun, nRemaining)
|
||||
if (runLen < minRun) {
|
||||
int force = nRemaining <= minRun ? nRemaining : minRun;
|
||||
binarySort(a, lo, lo + force, lo + runLen);
|
||||
runLen = force;
|
||||
}
|
||||
|
||||
// Push run onto pending-run stack, and maybe merge
|
||||
pushRun(lo, runLen);
|
||||
mergeCollapse();
|
||||
|
||||
// Advance to find next run
|
||||
lo += runLen;
|
||||
nRemaining -= runLen;
|
||||
} while (nRemaining != 0);
|
||||
|
||||
// Merge all remaining runs to complete sort
|
||||
if (DEBUG) assert lo == hi;
|
||||
mergeForceCollapse();
|
||||
if (DEBUG) assert stackSize == 1;
|
||||
|
||||
this.a = null;
|
||||
Object[] tmp = this.tmp;
|
||||
for (int i = 0, n = tmpCount; i < n; i++)
|
||||
tmp[i] = null;
|
||||
}
|
||||
|
||||
/** Creates a TimSort instance to maintain the state of an ongoing sort.
|
||||
*
|
||||
* @param a the array to be sorted */
|
||||
private ComparableTimSort (Object[] a) {
|
||||
this.a = a;
|
||||
|
||||
// Allocate temp storage (which may be increased later if necessary)
|
||||
int len = a.length;
|
||||
Object[] newArray = new Object[len < 2 * INITIAL_TMP_STORAGE_LENGTH ? len >>> 1 : INITIAL_TMP_STORAGE_LENGTH];
|
||||
tmp = newArray;
|
||||
|
||||
/*
|
||||
* Allocate runs-to-be-merged stack (which cannot be expanded). The stack length requirements are described in listsort.txt.
|
||||
* The C version always uses the same stack length (85), but this was measured to be too expensive when sorting "mid-sized"
|
||||
* arrays (e.g., 100 elements) in Java. Therefore, we use smaller (but sufficiently large) stack lengths for smaller arrays.
|
||||
* The "magic numbers" in the computation below must be changed if MIN_MERGE is decreased. See the MIN_MERGE declaration
|
||||
* above for more information.
|
||||
*/
|
||||
int stackLen = (len < 120 ? 5 : len < 1542 ? 10 : len < 119151 ? 19 : 40);
|
||||
runBase = new int[stackLen];
|
||||
runLen = new int[stackLen];
|
||||
}
|
||||
|
||||
/*
|
||||
* The next two methods (which are package private and static) constitute the entire API of this class. Each of these methods
|
||||
* obeys the contract of the public method with the same signature in java.util.Arrays.
|
||||
*/
|
||||
|
||||
static void sort (Object[] a) {
|
||||
sort(a, 0, a.length);
|
||||
}
|
||||
|
||||
static void sort (Object[] a, int lo, int hi) {
|
||||
rangeCheck(a.length, lo, hi);
|
||||
int nRemaining = hi - lo;
|
||||
if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted
|
||||
|
||||
// If array is small, do a "mini-TimSort" with no merges
|
||||
if (nRemaining < MIN_MERGE) {
|
||||
int initRunLen = countRunAndMakeAscending(a, lo, hi);
|
||||
binarySort(a, lo, hi, lo + initRunLen);
|
||||
return;
|
||||
}
|
||||
|
||||
/** March over the array once, left to right, finding natural runs, extending short natural runs to minRun elements, and
|
||||
* merging runs to maintain stack invariant. */
|
||||
ComparableTimSort ts = new ComparableTimSort(a);
|
||||
int minRun = minRunLength(nRemaining);
|
||||
do {
|
||||
// Identify next run
|
||||
int runLen = countRunAndMakeAscending(a, lo, hi);
|
||||
|
||||
// If run is short, extend to min(minRun, nRemaining)
|
||||
if (runLen < minRun) {
|
||||
int force = nRemaining <= minRun ? nRemaining : minRun;
|
||||
binarySort(a, lo, lo + force, lo + runLen);
|
||||
runLen = force;
|
||||
}
|
||||
|
||||
// Push run onto pending-run stack, and maybe merge
|
||||
ts.pushRun(lo, runLen);
|
||||
ts.mergeCollapse();
|
||||
|
||||
// Advance to find next run
|
||||
lo += runLen;
|
||||
nRemaining -= runLen;
|
||||
} while (nRemaining != 0);
|
||||
|
||||
// Merge all remaining runs to complete sort
|
||||
if (DEBUG) assert lo == hi;
|
||||
ts.mergeForceCollapse();
|
||||
if (DEBUG) assert ts.stackSize == 1;
|
||||
}
|
||||
|
||||
/** Sorts the specified portion of the specified array using a binary insertion sort. This is the best method for sorting small
|
||||
* numbers of elements. It requires O(n log n) compares, but O(n^2) data movement (worst case).
|
||||
*
|
||||
* If the initial part of the specified range is already sorted, this method can take advantage of it: the method assumes that
|
||||
* the elements from index {@code lo}, inclusive, to {@code start}, exclusive are already sorted.
|
||||
*
|
||||
* @param a the array in which a range is to be sorted
|
||||
* @param lo the index of the first element in the range to be sorted
|
||||
* @param hi the index after the last element in the range to be sorted
|
||||
* @param start the index of the first element in the range that is not already known to be sorted (@code lo <= start <= hi} */
|
||||
@SuppressWarnings("fallthrough")
|
||||
private static void binarySort (Object[] a, int lo, int hi, int start) {
|
||||
if (DEBUG) assert lo <= start && start <= hi;
|
||||
if (start == lo) start++;
|
||||
for (; start < hi; start++) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Comparable<Object> pivot = (Comparable)a[start];
|
||||
|
||||
// Set left (and right) to the index where a[start] (pivot) belongs
|
||||
int left = lo;
|
||||
int right = start;
|
||||
if (DEBUG) assert left <= right;
|
||||
/*
|
||||
* Invariants: pivot >= all in [lo, left). pivot < all in [right, start).
|
||||
*/
|
||||
while (left < right) {
|
||||
int mid = (left + right) >>> 1;
|
||||
if (pivot.compareTo(a[mid]) < 0)
|
||||
right = mid;
|
||||
else
|
||||
left = mid + 1;
|
||||
}
|
||||
if (DEBUG) assert left == right;
|
||||
|
||||
/*
|
||||
* The invariants still hold: pivot >= all in [lo, left) and pivot < all in [left, start), so pivot belongs at left. Note
|
||||
* that if there are elements equal to pivot, left points to the first slot after them -- that's why this sort is stable.
|
||||
* Slide elements over to make room to make room for pivot.
|
||||
*/
|
||||
int n = start - left; // The number of elements to move
|
||||
// Switch is just an optimization for arraycopy in default case
|
||||
switch (n) {
|
||||
case 2:
|
||||
a[left + 2] = a[left + 1];
|
||||
case 1:
|
||||
a[left + 1] = a[left];
|
||||
break;
|
||||
default:
|
||||
System.arraycopy(a, left, a, left + 1, n);
|
||||
}
|
||||
a[left] = pivot;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the length of the run beginning at the specified position in the specified array and reverses the run if it is
|
||||
* descending (ensuring that the run will always be ascending when the method returns).
|
||||
*
|
||||
* A run is the longest ascending sequence with:
|
||||
*
|
||||
* a[lo] <= a[lo + 1] <= a[lo + 2] <= ...
|
||||
*
|
||||
* or the longest descending sequence with:
|
||||
*
|
||||
* a[lo] > a[lo + 1] > a[lo + 2] > ...
|
||||
*
|
||||
* For its intended use in a stable mergesort, the strictness of the definition of "descending" is needed so that the call can
|
||||
* safely reverse a descending sequence without violating stability.
|
||||
*
|
||||
* @param a the array in which a run is to be counted and possibly reversed
|
||||
* @param lo index of the first element in the run
|
||||
* @param hi index after the last element that may be contained in the run. It is required that @code{lo < hi}.
|
||||
* @return the length of the run beginning at the specified position in the specified array */
|
||||
@SuppressWarnings("unchecked")
|
||||
private static int countRunAndMakeAscending (Object[] a, int lo, int hi) {
|
||||
if (DEBUG) assert lo < hi;
|
||||
int runHi = lo + 1;
|
||||
if (runHi == hi) return 1;
|
||||
|
||||
// Find end of run, and reverse range if descending
|
||||
if (((Comparable)a[runHi++]).compareTo(a[lo]) < 0) { // Descending
|
||||
while (runHi < hi && ((Comparable)a[runHi]).compareTo(a[runHi - 1]) < 0)
|
||||
runHi++;
|
||||
reverseRange(a, lo, runHi);
|
||||
} else { // Ascending
|
||||
while (runHi < hi && ((Comparable)a[runHi]).compareTo(a[runHi - 1]) >= 0)
|
||||
runHi++;
|
||||
}
|
||||
|
||||
return runHi - lo;
|
||||
}
|
||||
|
||||
/** Reverse the specified range of the specified array.
|
||||
*
|
||||
* @param a the array in which a range is to be reversed
|
||||
* @param lo the index of the first element in the range to be reversed
|
||||
* @param hi the index after the last element in the range to be reversed */
|
||||
private static void reverseRange (Object[] a, int lo, int hi) {
|
||||
hi--;
|
||||
while (lo < hi) {
|
||||
Object t = a[lo];
|
||||
a[lo++] = a[hi];
|
||||
a[hi--] = t;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the minimum acceptable run length for an array of the specified length. Natural runs shorter than this will be
|
||||
* extended with {@link #binarySort}.
|
||||
*
|
||||
* Roughly speaking, the computation is:
|
||||
*
|
||||
* If n < MIN_MERGE, return n (it's too small to bother with fancy stuff). Else if n is an exact power of 2, return
|
||||
* MIN_MERGE/2. Else return an int k, MIN_MERGE/2 <= k <= MIN_MERGE, such that n/k is close to, but strictly less than, an
|
||||
* exact power of 2.
|
||||
*
|
||||
* For the rationale, see listsort.txt.
|
||||
*
|
||||
* @param n the length of the array to be sorted
|
||||
* @return the length of the minimum run to be merged */
|
||||
private static int minRunLength (int n) {
|
||||
if (DEBUG) assert n >= 0;
|
||||
int r = 0; // Becomes 1 if any 1 bits are shifted off
|
||||
while (n >= MIN_MERGE) {
|
||||
r |= (n & 1);
|
||||
n >>= 1;
|
||||
}
|
||||
return n + r;
|
||||
}
|
||||
|
||||
/** Pushes the specified run onto the pending-run stack.
|
||||
*
|
||||
* @param runBase index of the first element in the run
|
||||
* @param runLen the number of elements in the run */
|
||||
private void pushRun (int runBase, int runLen) {
|
||||
this.runBase[stackSize] = runBase;
|
||||
this.runLen[stackSize] = runLen;
|
||||
stackSize++;
|
||||
}
|
||||
|
||||
/** Examines the stack of runs waiting to be merged and merges adjacent runs until the stack invariants are reestablished:
|
||||
*
|
||||
* 1. runLen[i - 3] > runLen[i - 2] + runLen[i - 1] 2. runLen[i - 2] > runLen[i - 1]
|
||||
*
|
||||
* This method is called each time a new run is pushed onto the stack, so the invariants are guaranteed to hold for i <
|
||||
* stackSize upon entry to the method. */
|
||||
private void mergeCollapse () {
|
||||
while (stackSize > 1) {
|
||||
int n = stackSize - 2;
|
||||
if (n > 0 && runLen[n - 1] <= runLen[n] + runLen[n + 1]) {
|
||||
if (runLen[n - 1] < runLen[n + 1]) n--;
|
||||
mergeAt(n);
|
||||
} else if (runLen[n] <= runLen[n + 1]) {
|
||||
mergeAt(n);
|
||||
} else {
|
||||
break; // Invariant is established
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges all runs on the stack until only one remains. This method is called once, to complete the sort. */
|
||||
private void mergeForceCollapse () {
|
||||
while (stackSize > 1) {
|
||||
int n = stackSize - 2;
|
||||
if (n > 0 && runLen[n - 1] < runLen[n + 1]) n--;
|
||||
mergeAt(n);
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges the two runs at stack indices i and i+1. Run i must be the penultimate or antepenultimate run on the stack. In other
|
||||
* words, i must be equal to stackSize-2 or stackSize-3.
|
||||
*
|
||||
* @param i stack index of the first of the two runs to merge */
|
||||
@SuppressWarnings("unchecked")
|
||||
private void mergeAt (int i) {
|
||||
if (DEBUG) assert stackSize >= 2;
|
||||
if (DEBUG) assert i >= 0;
|
||||
if (DEBUG) assert i == stackSize - 2 || i == stackSize - 3;
|
||||
|
||||
int base1 = runBase[i];
|
||||
int len1 = runLen[i];
|
||||
int base2 = runBase[i + 1];
|
||||
int len2 = runLen[i + 1];
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0;
|
||||
if (DEBUG) assert base1 + len1 == base2;
|
||||
|
||||
/*
|
||||
* Record the length of the combined runs; if i is the 3rd-last run now, also slide over the last run (which isn't involved
|
||||
* in this merge). The current run (i+1) goes away in any case.
|
||||
*/
|
||||
runLen[i] = len1 + len2;
|
||||
if (i == stackSize - 3) {
|
||||
runBase[i + 1] = runBase[i + 2];
|
||||
runLen[i + 1] = runLen[i + 2];
|
||||
}
|
||||
stackSize--;
|
||||
|
||||
/*
|
||||
* Find where the first element of run2 goes in run1. Prior elements in run1 can be ignored (because they're already in
|
||||
* place).
|
||||
*/
|
||||
int k = gallopRight((Comparable<Object>)a[base2], a, base1, len1, 0);
|
||||
if (DEBUG) assert k >= 0;
|
||||
base1 += k;
|
||||
len1 -= k;
|
||||
if (len1 == 0) return;
|
||||
|
||||
/*
|
||||
* Find where the last element of run1 goes in run2. Subsequent elements in run2 can be ignored (because they're already in
|
||||
* place).
|
||||
*/
|
||||
len2 = gallopLeft((Comparable<Object>)a[base1 + len1 - 1], a, base2, len2, len2 - 1);
|
||||
if (DEBUG) assert len2 >= 0;
|
||||
if (len2 == 0) return;
|
||||
|
||||
// Merge remaining runs, using tmp array with min(len1, len2) elements
|
||||
if (len1 <= len2)
|
||||
mergeLo(base1, len1, base2, len2);
|
||||
else
|
||||
mergeHi(base1, len1, base2, len2);
|
||||
}
|
||||
|
||||
/** Locates the position at which to insert the specified key into the specified sorted range; if the range contains an element
|
||||
* equal to key, returns the index of the leftmost equal element.
|
||||
*
|
||||
* @param key the key whose insertion point to search for
|
||||
* @param a the array in which to search
|
||||
* @param base the index of the first element in the range
|
||||
* @param len the length of the range; must be > 0
|
||||
* @param hint the index at which to begin the search, 0 <= hint < n. The closer hint is to the result, the faster this method
|
||||
* will run.
|
||||
* @return the int k, 0 <= k <= n such that a[b + k - 1] < key <= a[b + k], pretending that a[b - 1] is minus infinity and a[b
|
||||
* + n] is infinity. In other words, key belongs at index b + k; or in other words, the first k elements of a should
|
||||
* precede key, and the last n - k should follow it. */
|
||||
private static int gallopLeft (Comparable<Object> key, Object[] a, int base, int len, int hint) {
|
||||
if (DEBUG) assert len > 0 && hint >= 0 && hint < len;
|
||||
|
||||
int lastOfs = 0;
|
||||
int ofs = 1;
|
||||
if (key.compareTo(a[base + hint]) > 0) {
|
||||
// Gallop right until a[base+hint+lastOfs] < key <= a[base+hint+ofs]
|
||||
int maxOfs = len - hint;
|
||||
while (ofs < maxOfs && key.compareTo(a[base + hint + ofs]) > 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to base
|
||||
lastOfs += hint;
|
||||
ofs += hint;
|
||||
} else { // key <= a[base + hint]
|
||||
// Gallop left until a[base+hint-ofs] < key <= a[base+hint-lastOfs]
|
||||
final int maxOfs = hint + 1;
|
||||
while (ofs < maxOfs && key.compareTo(a[base + hint - ofs]) <= 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to base
|
||||
int tmp = lastOfs;
|
||||
lastOfs = hint - ofs;
|
||||
ofs = hint - tmp;
|
||||
}
|
||||
if (DEBUG) assert -1 <= lastOfs && lastOfs < ofs && ofs <= len;
|
||||
|
||||
/*
|
||||
* Now a[base+lastOfs] < key <= a[base+ofs], so key belongs somewhere to the right of lastOfs but no farther right than ofs.
|
||||
* Do a binary search, with invariant a[base + lastOfs - 1] < key <= a[base + ofs].
|
||||
*/
|
||||
lastOfs++;
|
||||
while (lastOfs < ofs) {
|
||||
int m = lastOfs + ((ofs - lastOfs) >>> 1);
|
||||
|
||||
if (key.compareTo(a[base + m]) > 0)
|
||||
lastOfs = m + 1; // a[base + m] < key
|
||||
else
|
||||
ofs = m; // key <= a[base + m]
|
||||
}
|
||||
if (DEBUG) assert lastOfs == ofs; // so a[base + ofs - 1] < key <= a[base + ofs]
|
||||
return ofs;
|
||||
}
|
||||
|
||||
/** Like gallopLeft, except that if the range contains an element equal to key, gallopRight returns the index after the
|
||||
* rightmost equal element.
|
||||
*
|
||||
* @param key the key whose insertion point to search for
|
||||
* @param a the array in which to search
|
||||
* @param base the index of the first element in the range
|
||||
* @param len the length of the range; must be > 0
|
||||
* @param hint the index at which to begin the search, 0 <= hint < n. The closer hint is to the result, the faster this method
|
||||
* will run.
|
||||
* @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] */
|
||||
private static int gallopRight (Comparable<Object> key, Object[] a, int base, int len, int hint) {
|
||||
if (DEBUG) assert len > 0 && hint >= 0 && hint < len;
|
||||
|
||||
int ofs = 1;
|
||||
int lastOfs = 0;
|
||||
if (key.compareTo(a[base + hint]) < 0) {
|
||||
// Gallop left until a[b+hint - ofs] <= key < a[b+hint - lastOfs]
|
||||
int maxOfs = hint + 1;
|
||||
while (ofs < maxOfs && key.compareTo(a[base + hint - ofs]) < 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to b
|
||||
int tmp = lastOfs;
|
||||
lastOfs = hint - ofs;
|
||||
ofs = hint - tmp;
|
||||
} else { // a[b + hint] <= key
|
||||
// Gallop right until a[b+hint + lastOfs] <= key < a[b+hint + ofs]
|
||||
int maxOfs = len - hint;
|
||||
while (ofs < maxOfs && key.compareTo(a[base + hint + ofs]) >= 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to b
|
||||
lastOfs += hint;
|
||||
ofs += hint;
|
||||
}
|
||||
if (DEBUG) assert -1 <= lastOfs && lastOfs < ofs && ofs <= len;
|
||||
|
||||
/*
|
||||
* Now a[b + lastOfs] <= key < a[b + ofs], so key belongs somewhere to the right of lastOfs but no farther right than ofs.
|
||||
* Do a binary search, with invariant a[b + lastOfs - 1] <= key < a[b + ofs].
|
||||
*/
|
||||
lastOfs++;
|
||||
while (lastOfs < ofs) {
|
||||
int m = lastOfs + ((ofs - lastOfs) >>> 1);
|
||||
|
||||
if (key.compareTo(a[base + m]) < 0)
|
||||
ofs = m; // key < a[b + m]
|
||||
else
|
||||
lastOfs = m + 1; // a[b + m] <= key
|
||||
}
|
||||
if (DEBUG) assert lastOfs == ofs; // so a[b + ofs - 1] <= key < a[b + ofs]
|
||||
return ofs;
|
||||
}
|
||||
|
||||
/** Merges two adjacent runs in place, in a stable fashion. The first element of the first run must be greater than the first
|
||||
* element of the second run (a[base1] > a[base2]), and the last element of the first run (a[base1 + len1-1]) must be greater
|
||||
* than all elements of the second run.
|
||||
*
|
||||
* For performance, this method should be called only when len1 <= len2; its twin, mergeHi should be called if len1 >= len2.
|
||||
* (Either method may be called if len1 == len2.)
|
||||
*
|
||||
* @param base1 index of first element in first run to be merged
|
||||
* @param len1 length of first run to be merged (must be > 0)
|
||||
* @param base2 index of first element in second run to be merged (must be aBase + aLen)
|
||||
* @param len2 length of second run to be merged (must be > 0) */
|
||||
@SuppressWarnings("unchecked")
|
||||
private void mergeLo (int base1, int len1, int base2, int len2) {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
|
||||
|
||||
// Copy first run into temp array
|
||||
Object[] a = this.a; // For performance
|
||||
Object[] tmp = ensureCapacity(len1);
|
||||
System.arraycopy(a, base1, tmp, 0, len1);
|
||||
|
||||
int cursor1 = 0; // Indexes into tmp array
|
||||
int cursor2 = base2; // Indexes int a
|
||||
int dest = base1; // Indexes int a
|
||||
|
||||
// Move first element of second run and deal with degenerate cases
|
||||
a[dest++] = a[cursor2++];
|
||||
if (--len2 == 0) {
|
||||
System.arraycopy(tmp, cursor1, a, dest, len1);
|
||||
return;
|
||||
}
|
||||
if (len1 == 1) {
|
||||
System.arraycopy(a, cursor2, a, dest, len2);
|
||||
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
|
||||
return;
|
||||
}
|
||||
|
||||
int minGallop = this.minGallop; // Use local variable for performance
|
||||
outer:
|
||||
while (true) {
|
||||
int count1 = 0; // Number of times in a row that first run won
|
||||
int count2 = 0; // Number of times in a row that second run won
|
||||
|
||||
/*
|
||||
* Do the straightforward thing until (if ever) one run starts winning consistently.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 1 && len2 > 0;
|
||||
if (((Comparable)a[cursor2]).compareTo(tmp[cursor1]) < 0) {
|
||||
a[dest++] = a[cursor2++];
|
||||
count2++;
|
||||
count1 = 0;
|
||||
if (--len2 == 0) break outer;
|
||||
} else {
|
||||
a[dest++] = tmp[cursor1++];
|
||||
count1++;
|
||||
count2 = 0;
|
||||
if (--len1 == 1) break outer;
|
||||
}
|
||||
} while ((count1 | count2) < minGallop);
|
||||
|
||||
/*
|
||||
* One run is winning so consistently that galloping may be a huge win. So try that, and continue galloping until (if
|
||||
* ever) neither run appears to be winning consistently anymore.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 1 && len2 > 0;
|
||||
count1 = gallopRight((Comparable)a[cursor2], tmp, cursor1, len1, 0);
|
||||
if (count1 != 0) {
|
||||
System.arraycopy(tmp, cursor1, a, dest, count1);
|
||||
dest += count1;
|
||||
cursor1 += count1;
|
||||
len1 -= count1;
|
||||
if (len1 <= 1) // len1 == 1 || len1 == 0
|
||||
break outer;
|
||||
}
|
||||
a[dest++] = a[cursor2++];
|
||||
if (--len2 == 0) break outer;
|
||||
|
||||
count2 = gallopLeft((Comparable)tmp[cursor1], a, cursor2, len2, 0);
|
||||
if (count2 != 0) {
|
||||
System.arraycopy(a, cursor2, a, dest, count2);
|
||||
dest += count2;
|
||||
cursor2 += count2;
|
||||
len2 -= count2;
|
||||
if (len2 == 0) break outer;
|
||||
}
|
||||
a[dest++] = tmp[cursor1++];
|
||||
if (--len1 == 1) break outer;
|
||||
minGallop--;
|
||||
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
|
||||
if (minGallop < 0) minGallop = 0;
|
||||
minGallop += 2; // Penalize for leaving gallop mode
|
||||
} // End of "outer" loop
|
||||
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
|
||||
|
||||
if (len1 == 1) {
|
||||
if (DEBUG) assert len2 > 0;
|
||||
System.arraycopy(a, cursor2, a, dest, len2);
|
||||
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
|
||||
} else if (len1 == 0) {
|
||||
throw new IllegalArgumentException("Comparison method violates its general contract!");
|
||||
} else {
|
||||
if (DEBUG) assert len2 == 0;
|
||||
if (DEBUG) assert len1 > 1;
|
||||
System.arraycopy(tmp, cursor1, a, dest, len1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Like mergeLo, except that this method should be called only if len1 >= len2; mergeLo should be called if len1 <= len2.
|
||||
* (Either method may be called if len1 == len2.)
|
||||
*
|
||||
* @param base1 index of first element in first run to be merged
|
||||
* @param len1 length of first run to be merged (must be > 0)
|
||||
* @param base2 index of first element in second run to be merged (must be aBase + aLen)
|
||||
* @param len2 length of second run to be merged (must be > 0) */
|
||||
@SuppressWarnings("unchecked")
|
||||
private void mergeHi (int base1, int len1, int base2, int len2) {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
|
||||
|
||||
// Copy second run into temp array
|
||||
Object[] a = this.a; // For performance
|
||||
Object[] tmp = ensureCapacity(len2);
|
||||
System.arraycopy(a, base2, tmp, 0, len2);
|
||||
|
||||
int cursor1 = base1 + len1 - 1; // Indexes into a
|
||||
int cursor2 = len2 - 1; // Indexes into tmp array
|
||||
int dest = base2 + len2 - 1; // Indexes into a
|
||||
|
||||
// Move last element of first run and deal with degenerate cases
|
||||
a[dest--] = a[cursor1--];
|
||||
if (--len1 == 0) {
|
||||
System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2);
|
||||
return;
|
||||
}
|
||||
if (len2 == 1) {
|
||||
dest -= len1;
|
||||
cursor1 -= len1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, len1);
|
||||
a[dest] = tmp[cursor2];
|
||||
return;
|
||||
}
|
||||
|
||||
int minGallop = this.minGallop; // Use local variable for performance
|
||||
outer:
|
||||
while (true) {
|
||||
int count1 = 0; // Number of times in a row that first run won
|
||||
int count2 = 0; // Number of times in a row that second run won
|
||||
|
||||
/*
|
||||
* Do the straightforward thing until (if ever) one run appears to win consistently.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 1;
|
||||
if (((Comparable)tmp[cursor2]).compareTo(a[cursor1]) < 0) {
|
||||
a[dest--] = a[cursor1--];
|
||||
count1++;
|
||||
count2 = 0;
|
||||
if (--len1 == 0) break outer;
|
||||
} else {
|
||||
a[dest--] = tmp[cursor2--];
|
||||
count2++;
|
||||
count1 = 0;
|
||||
if (--len2 == 1) break outer;
|
||||
}
|
||||
} while ((count1 | count2) < minGallop);
|
||||
|
||||
/*
|
||||
* One run is winning so consistently that galloping may be a huge win. So try that, and continue galloping until (if
|
||||
* ever) neither run appears to be winning consistently anymore.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 1;
|
||||
count1 = len1 - gallopRight((Comparable)tmp[cursor2], a, base1, len1, len1 - 1);
|
||||
if (count1 != 0) {
|
||||
dest -= count1;
|
||||
cursor1 -= count1;
|
||||
len1 -= count1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, count1);
|
||||
if (len1 == 0) break outer;
|
||||
}
|
||||
a[dest--] = tmp[cursor2--];
|
||||
if (--len2 == 1) break outer;
|
||||
|
||||
count2 = len2 - gallopLeft((Comparable)a[cursor1], tmp, 0, len2, len2 - 1);
|
||||
if (count2 != 0) {
|
||||
dest -= count2;
|
||||
cursor2 -= count2;
|
||||
len2 -= count2;
|
||||
System.arraycopy(tmp, cursor2 + 1, a, dest + 1, count2);
|
||||
if (len2 <= 1) break outer; // len2 == 1 || len2 == 0
|
||||
}
|
||||
a[dest--] = a[cursor1--];
|
||||
if (--len1 == 0) break outer;
|
||||
minGallop--;
|
||||
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
|
||||
if (minGallop < 0) minGallop = 0;
|
||||
minGallop += 2; // Penalize for leaving gallop mode
|
||||
} // End of "outer" loop
|
||||
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
|
||||
|
||||
if (len2 == 1) {
|
||||
if (DEBUG) assert len1 > 0;
|
||||
dest -= len1;
|
||||
cursor1 -= len1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, len1);
|
||||
a[dest] = tmp[cursor2]; // Move first elt of run2 to front of merge
|
||||
} else if (len2 == 0) {
|
||||
throw new IllegalArgumentException("Comparison method violates its general contract!");
|
||||
} else {
|
||||
if (DEBUG) assert len1 == 0;
|
||||
if (DEBUG) assert len2 > 0;
|
||||
System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensures that the external array tmp has at least the specified number of elements, increasing its size if necessary. The
|
||||
* size increases exponentially to ensure amortized linear time complexity.
|
||||
*
|
||||
* @param minCapacity the minimum required capacity of the tmp array
|
||||
* @return tmp, whether or not it grew */
|
||||
private Object[] ensureCapacity (int minCapacity) {
|
||||
tmpCount = Math.max(tmpCount, minCapacity);
|
||||
if (tmp.length < minCapacity) {
|
||||
// Compute smallest power of 2 > minCapacity
|
||||
int newSize = minCapacity;
|
||||
newSize |= newSize >> 1;
|
||||
newSize |= newSize >> 2;
|
||||
newSize |= newSize >> 4;
|
||||
newSize |= newSize >> 8;
|
||||
newSize |= newSize >> 16;
|
||||
newSize++;
|
||||
|
||||
if (newSize < 0) // Not bloody likely!
|
||||
newSize = minCapacity;
|
||||
else
|
||||
newSize = Math.min(newSize, a.length >>> 1);
|
||||
|
||||
Object[] newArray = new Object[newSize];
|
||||
tmp = newArray;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/** Checks that fromIndex and toIndex are in range, and throws an appropriate exception if they aren't.
|
||||
*
|
||||
* @param arrayLen the length of the array
|
||||
* @param fromIndex the index of the first element of the range
|
||||
* @param toIndex the index after the last element of the range
|
||||
* @throws IllegalArgumentException if fromIndex > toIndex
|
||||
* @throws ArrayIndexOutOfBoundsException if fromIndex < 0 or toIndex > arrayLen */
|
||||
private static void rangeCheck (int arrayLen, int fromIndex, int toIndex) {
|
||||
if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
|
||||
if (fromIndex < 0) throw new ArrayIndexOutOfBoundsException(fromIndex);
|
||||
if (toIndex > arrayLen) throw new ArrayIndexOutOfBoundsException(toIndex);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
//
|
||||
// not thread-safe!!!
|
||||
//
|
||||
|
||||
/**
|
||||
* @author bennidi
|
||||
* @author dorkbox, llc Date: 2/3/16
|
||||
*/
|
||||
public
|
||||
class ConcurrentEntry<T> {
|
||||
private final T value;
|
||||
|
||||
private volatile ConcurrentEntry<T> next;
|
||||
private volatile ConcurrentEntry<T> prev;
|
||||
|
||||
public
|
||||
ConcurrentEntry(T value, ConcurrentEntry<T> next) {
|
||||
if (next != null) {
|
||||
this.next = next;
|
||||
next.prev = this;
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public
|
||||
void remove() {
|
||||
if (this.prev != null) {
|
||||
this.prev.next = this.next;
|
||||
if (this.next != null) {
|
||||
this.next.prev = this.prev;
|
||||
}
|
||||
}
|
||||
else if (this.next != null) {
|
||||
this.next.prev = null;
|
||||
}
|
||||
|
||||
// can not nullify references to help GC since running iterators might not see the entire set
|
||||
// if this element is their current element
|
||||
//next = null;
|
||||
//prev = null;
|
||||
}
|
||||
|
||||
public
|
||||
ConcurrentEntry<T> next() {
|
||||
return this.next;
|
||||
}
|
||||
|
||||
public
|
||||
void clear() {
|
||||
this.next = null;
|
||||
}
|
||||
|
||||
|
||||
public
|
||||
T getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
/**
|
||||
* @author dorkbox, llc
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
class ConcurrentIterator<T> {
|
||||
/**
|
||||
* Specifies the load-factor for the IdentityMap used
|
||||
*/
|
||||
private float loadFactor = 0.8F;
|
||||
|
||||
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
||||
private final int ID = ID_COUNTER.getAndIncrement();
|
||||
|
||||
// This is only touched by a single thread, maintains a map of entries for FAST lookup during remove.
|
||||
private final IdentityMap<T, ConcurrentEntry> entries = new IdentityMap<T, ConcurrentEntry>(32, loadFactor);
|
||||
|
||||
// this is still inside the single-writer, and can use the same techniques as subscription manager (for thread safe publication)
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private volatile ConcurrentEntry<T> head = null; // reference to the first element
|
||||
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
public static final AtomicReferenceFieldUpdater<ConcurrentIterator, ConcurrentEntry> headREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
ConcurrentIterator.class,
|
||||
ConcurrentEntry.class,
|
||||
"head");
|
||||
|
||||
|
||||
public
|
||||
ConcurrentIterator() {
|
||||
}
|
||||
|
||||
public
|
||||
ConcurrentIterator(float loadFactor) {
|
||||
this.loadFactor = loadFactor;
|
||||
}
|
||||
|
||||
/**
|
||||
* single writer principle!
|
||||
* called from within SYNCHRONIZE
|
||||
*/
|
||||
public final
|
||||
void clear() {
|
||||
this.entries.clear();
|
||||
this.head = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* single writer principle!
|
||||
* called from within SYNCHRONIZE
|
||||
*
|
||||
* @param listener the object that will receive messages during publication
|
||||
*/
|
||||
public synchronized
|
||||
void add(final T listener) {
|
||||
ConcurrentEntry<T> head = headREF.get(this);
|
||||
|
||||
if (!entries.containsKey(listener)) {
|
||||
head = new ConcurrentEntry<T>(listener, head);
|
||||
|
||||
entries.put(listener, head);
|
||||
headREF.lazySet(this, head);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* single writer principle!
|
||||
* called from within SYNCHRONIZE
|
||||
*
|
||||
* @param listener the object that will NO LONGER receive messages during publication
|
||||
*/
|
||||
public synchronized
|
||||
boolean remove(final T listener) {
|
||||
ConcurrentEntry<T> concurrentEntry = entries.get(listener);
|
||||
|
||||
if (concurrentEntry != null) {
|
||||
ConcurrentEntry<T> head = headREF.get(this);
|
||||
|
||||
if (concurrentEntry == head) {
|
||||
// if it was second, now it's first
|
||||
head = head.next();
|
||||
//oldHead.clear(); // optimize for GC not possible because of potentially running iterators
|
||||
}
|
||||
else {
|
||||
concurrentEntry.remove();
|
||||
}
|
||||
|
||||
headREF.lazySet(this, head);
|
||||
this.entries.remove(listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* single writer principle!
|
||||
* called from within SYNCHRONIZE
|
||||
*/
|
||||
public synchronized
|
||||
int size() {
|
||||
return entries.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
int hashCode() {
|
||||
return this.ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ConcurrentIterator other = (ConcurrentIterator) obj;
|
||||
return this.ID == other.ID;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* Copyright 2016 zhanhb.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* @param <K>
|
||||
* @param <V>
|
||||
*
|
||||
* @author zhanhb
|
||||
*/
|
||||
class ConcurrentWeakIdentityHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
|
||||
|
||||
private final ConcurrentMap<Key<K>, V> map;
|
||||
private final ReferenceQueue<K> queue = new ReferenceQueue<>();
|
||||
private transient Set<Map.Entry<K, V>> es;
|
||||
|
||||
ConcurrentWeakIdentityHashMap(int initialCapacity) {
|
||||
this.map = new ConcurrentHashMap<>(initialCapacity);
|
||||
}
|
||||
|
||||
@SuppressWarnings("CollectionWithoutInitialCapacity")
|
||||
ConcurrentWeakIdentityHashMap() {
|
||||
this.map = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V get(Object key) {
|
||||
purgeKeys();
|
||||
return map.get(new Key<>(key, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V put(K key, V value) {
|
||||
purgeKeys();
|
||||
return map.put(new Key<>(key, queue), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int size() {
|
||||
purgeKeys();
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"NestedAssignment", "element-type-mismatch"})
|
||||
private
|
||||
void purgeKeys() {
|
||||
Reference<? extends K> reference;
|
||||
while ((reference = queue.poll()) != null) {
|
||||
map.remove(reference);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NestedAssignment")
|
||||
public
|
||||
Set<Map.Entry<K, V>> entrySet() {
|
||||
Set<Map.Entry<K, V>> entrySet;
|
||||
return ((entrySet = this.es) == null) ? es = new EntrySet() : entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V putIfAbsent(K key, V value) {
|
||||
purgeKeys();
|
||||
return map.putIfAbsent(new Key<>(key, queue), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V remove(Object key) {
|
||||
return map.remove(new Key<>(key, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean remove(Object key, Object value) {
|
||||
purgeKeys();
|
||||
return map.remove(new Key<>(key, null), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean replace(K key, V oldValue, V newValue) {
|
||||
purgeKeys();
|
||||
return map.replace(new Key<>(key, null), oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V replace(K key, V value) {
|
||||
purgeKeys();
|
||||
return map.replace(new Key<>(key, null), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean containsKey(Object key) {
|
||||
purgeKeys();
|
||||
return map.containsKey(new Key<>(key, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("empty-statement")
|
||||
public
|
||||
void clear() {
|
||||
while (queue.poll() != null) {
|
||||
}
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean containsValue(Object value) {
|
||||
purgeKeys();
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
private static
|
||||
class Key<T> extends WeakReference<T> {
|
||||
|
||||
private final int hash;
|
||||
|
||||
Key(T t, ReferenceQueue<T> queue) {
|
||||
super(t, queue);
|
||||
hash = System.identityHashCode(Objects.requireNonNull(t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(Object obj) {
|
||||
return this == obj || obj instanceof Key && ((Key<?>) obj).get() == get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
class Iter implements Iterator<Map.Entry<K, V>> {
|
||||
|
||||
private final Iterator<Map.Entry<Key<K>, V>> it;
|
||||
private Map.Entry<K, V> nextValue;
|
||||
|
||||
Iter(Iterator<Map.Entry<Key<K>, V>> it) {
|
||||
this.it = it;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasNext() {
|
||||
if (nextValue != null) {
|
||||
return true;
|
||||
}
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<Key<K>, V> entry = it.next();
|
||||
K key = entry.getKey().get();
|
||||
if (key != null) {
|
||||
nextValue = new Entry(key, entry.getValue());
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Map.Entry<K, V> next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Map.Entry<K, V> entry = nextValue;
|
||||
nextValue = null;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void remove() {
|
||||
it.remove();
|
||||
nextValue = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
class EntrySet extends AbstractSet<Map.Entry<K, V>> {
|
||||
|
||||
@Override
|
||||
public
|
||||
Iterator<Map.Entry<K, V>> iterator() {
|
||||
return new Iter(map.entrySet().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int size() {
|
||||
return ConcurrentWeakIdentityHashMap.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void clear() {
|
||||
ConcurrentWeakIdentityHashMap.this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("element-type-mismatch")
|
||||
public
|
||||
boolean contains(Object o) {
|
||||
if (!(o instanceof Map.Entry)) {
|
||||
return false;
|
||||
}
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
|
||||
return ConcurrentWeakIdentityHashMap.this.get(e.getKey()) == e.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean remove(Object o) {
|
||||
if (!(o instanceof Map.Entry)) {
|
||||
return false;
|
||||
}
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
|
||||
return ConcurrentWeakIdentityHashMap.this.remove(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private
|
||||
class Entry extends SimpleEntry<K, V> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
Entry(K key, V value) {
|
||||
super(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
V setValue(V value) {
|
||||
ConcurrentWeakIdentityHashMap.this.put(getKey(), value);
|
||||
return super.setValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(Object obj) {
|
||||
if (obj instanceof Map.Entry) {
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) obj;
|
||||
return getKey() == e.getKey() && getValue() == e.getValue();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return System.identityHashCode(getKey()) ^ System.identityHashCode(getValue());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,424 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
/** A resizable, ordered or unordered float array. Avoids the boxing that occurs with ArrayList<Float>. If unordered, this class
|
||||
* avoids a memory copy when removing elements (the last element is moved to the removed element's position).
|
||||
* @author Nathan Sweet */
|
||||
public class FloatArray {
|
||||
public float[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public FloatArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public FloatArray (int capacity) {
|
||||
this(true, capacity);
|
||||
}
|
||||
|
||||
/** @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing array to be grown. */
|
||||
public FloatArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new float[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specific array. The new array will be ordered if the specific array is
|
||||
* ordered. The capacity is set to the number of elements, so any subsequent elements added will cause the backing array to be
|
||||
* grown. */
|
||||
public FloatArray (FloatArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new float[size];
|
||||
System.arraycopy(array.items, 0, items, 0, size);
|
||||
}
|
||||
|
||||
/** Creates a new ordered array containing the elements in the specified array. The capacity is set to the number of elements,
|
||||
* so any subsequent elements added will cause the backing array to be grown. */
|
||||
public FloatArray (float[] array) {
|
||||
this(true, array, 0, array.length);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The capacity is set to the number of elements, so any
|
||||
* subsequent elements added will cause the backing array to be grown.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy. */
|
||||
public FloatArray (boolean ordered, float[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (float value) {
|
||||
float[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (float value1, float value2) {
|
||||
float[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (float value1, float value2, float value3) {
|
||||
float[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (float value1, float value2, float value3, float value4) {
|
||||
float[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (FloatArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (FloatArray array, int offset, int length) {
|
||||
if (offset + length > array.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
|
||||
addAll(array.items, offset, length);
|
||||
}
|
||||
|
||||
public void addAll (float... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (float[] array, int offset, int length) {
|
||||
float[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public float get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, float value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, float value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, float value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, float value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
float[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
float[] items = this.items;
|
||||
float firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
public boolean contains (float value) {
|
||||
int i = size - 1;
|
||||
float[] items = this.items;
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOf (float value) {
|
||||
float[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int lastIndexOf (char value) {
|
||||
float[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean removeValue (float value) {
|
||||
float[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public float removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
float[] items = this.items;
|
||||
float value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
float[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (FloatArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
float[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
float item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item == items[ii]) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return size != startSize;
|
||||
}
|
||||
|
||||
/** Removes and returns the last item. */
|
||||
public float pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public float peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public float first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing array to the size of the actual items. This is useful to release memory when many items
|
||||
* have been removed, or if it is known that more items will not be added.
|
||||
* @return {@link #items} */
|
||||
public float[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public float[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public float[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected float[] resize (int newSize) {
|
||||
float[] newItems = new float[newSize];
|
||||
float[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
float[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
float temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
float[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = MathUtil.random(i);
|
||||
float temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reduces the size of the array to the specified size. If the array is already smaller than the specified size, no action is
|
||||
* taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or zero if the array is empty. */
|
||||
public float random () {
|
||||
if (size == 0) return 0;
|
||||
return items[MathUtil.random(0, size - 1)];
|
||||
}
|
||||
|
||||
public float[] toArray () {
|
||||
float[] array = new float[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
float[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + Float.floatToIntBits(items[i]);
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof FloatArray)) return false;
|
||||
FloatArray array = (FloatArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
float[] items1 = this.items;
|
||||
float[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items1[i] != items2[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean equals (Object object, float epsilon) {
|
||||
if (object == this) return true;
|
||||
if (!(object instanceof FloatArray)) return false;
|
||||
FloatArray array = (FloatArray)object;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
if (!ordered) return false;
|
||||
if (!array.ordered) return false;
|
||||
float[] items1 = this.items;
|
||||
float[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (Math.abs(items1[i] - items2[i]) > epsilon) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
float[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
float[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #FloatArray(float[]) */
|
||||
static public FloatArray with (float... array) {
|
||||
return new FloatArray(array);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,812 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** An unordered map that uses identity comparison for keys. This implementation is a cuckoo hash map using 3 hashes, random
|
||||
* walking, and a small stash for problematic keys. Null keys are not allowed. Null values are allowed. No allocation is done
|
||||
* except when growing the table size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"unchecked", "rawtypes", "NullableProblems"})
|
||||
public class IdentityMap<K, V> implements Iterable<IdentityMap.Entry<K, V>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
||||
public int size;
|
||||
|
||||
K[] keyTable;
|
||||
V[] valueTable;
|
||||
int capacity, stashSize;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
/** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public IdentityMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IdentityMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IdentityMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = (K[])new Object[capacity + stashCapacity];
|
||||
valueTable = (V[])new Object[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public IdentityMap (IdentityMap map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
}
|
||||
|
||||
public V put (K key, V value) {
|
||||
if (key == null) throw new IllegalArgumentException("key cannot be null.");
|
||||
K[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == key) {
|
||||
V oldValue = valueTable[index1];
|
||||
valueTable[index1] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == key) {
|
||||
V oldValue = valueTable[index2];
|
||||
valueTable[index2] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == key) {
|
||||
V oldValue = valueTable[index3];
|
||||
valueTable[index3] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
valueTable[i] = value;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (K key, V value) {
|
||||
// Check for empty buckets.
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (K insertKey, V insertValue, int index1, K key1, int index2, K key2, int index3, K key3) {
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
K evictedKey;
|
||||
V evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random.nextInt(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
evictedValue = valueTable[index1];
|
||||
keyTable[index1] = insertKey;
|
||||
valueTable[index1] = insertValue;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
evictedValue = valueTable[index2];
|
||||
keyTable[index2] = insertKey;
|
||||
valueTable[index2] = insertValue;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
evictedValue = valueTable[index3];
|
||||
keyTable[index3] = insertKey;
|
||||
valueTable[index3] = insertValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
int hashCode = System.identityHashCode(evictedKey);
|
||||
index1 = hashCode & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(hashCode);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(hashCode);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (K key, V value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
public V get (K key) {
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index = hashCode & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(hashCode);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(hashCode);
|
||||
if (key != keyTable[index]) return getStash(key, null);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
public V get (K key, V defaultValue) {
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index = hashCode & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(hashCode);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(hashCode);
|
||||
if (key != keyTable[index]) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private V getStash (K key, V defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public V remove (K key) {
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index = hashCode & mask;
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
V removeStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
valueTable[lastIndex] = null;
|
||||
} else
|
||||
valueTable[index] = null;
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;) {
|
||||
keyTable[i] = null;
|
||||
valueTable[i] = null;
|
||||
}
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
}
|
||||
|
||||
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
|
||||
* an expensive operation.
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}. */
|
||||
public boolean containsValue (Object value, boolean identity) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == null) return true;
|
||||
} else if (identity) {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return true;
|
||||
} else {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (K key) {
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index = hashCode & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(hashCode);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(hashCode);
|
||||
if (key != keyTable[index]) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares
|
||||
* every value, which may be an expensive operation.
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}. */
|
||||
public K findKey (Object value, boolean identity) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == null) return keyTable[i];
|
||||
} else if (identity) {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return keyTable[i];
|
||||
} else {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return keyTable[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
K[] oldKeyTable = keyTable;
|
||||
V[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = (K[])new Object[newSize + stashCapacity];
|
||||
valueTable = (V[])new Object[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
K key = oldKeyTable[i];
|
||||
if (key != null) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
h += key.hashCode() * 31;
|
||||
|
||||
V value = valueTable[i];
|
||||
if (value != null) {
|
||||
h += value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof IdentityMap)) return false;
|
||||
IdentityMap<K, V> other = (IdentityMap) obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
V value = valueTable[i];
|
||||
if (value == null) {
|
||||
if (!other.containsKey(key) || other.get(key) != null) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!value.equals(other.get(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
break;
|
||||
}
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Entries<K, V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Values<V> values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return values2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each time
|
||||
* this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Keys<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<K, V> {
|
||||
public K key;
|
||||
public V value;
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
static private abstract class MapIterator<K, V, I> implements Iterable<I>, Iterator<I> {
|
||||
public boolean hasNext;
|
||||
|
||||
final IdentityMap<K, V> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (IdentityMap<K, V> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = null;
|
||||
map.valueTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"NullableProblems", "DuplicatedCode", "unchecked", "FieldMayBeFinal"})
|
||||
static public class Entries<K, V> extends MapIterator<K, V, Entry<K, V>> {
|
||||
private Entry<K, V> entry = new Entry();
|
||||
|
||||
public Entries (IdentityMap<K, V> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
@Override
|
||||
public Entry<K, V> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K[] keyTable = map.keyTable;
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator () {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "NullableProblems"})
|
||||
static public class Values<V> extends MapIterator<Object, V, V> {
|
||||
public Values (IdentityMap<?, V> map) {
|
||||
super((IdentityMap<Object, V>)map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
V value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public Array<V> toArray () {
|
||||
Array array = new Array(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
/** Adds the remaining values to the specified array. */
|
||||
public void toArray (Array<V> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static public class Keys<K> extends MapIterator<K, Object, K> {
|
||||
public Keys (IdentityMap<K, ?> map) {
|
||||
super((IdentityMap<K, Object>)map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public Array<K> toArray () {
|
||||
Array array = new Array(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,408 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/** A resizable, ordered or unordered int array. Avoids the boxing that occurs with ArrayList<Integer>. If unordered, this class
|
||||
* avoids a memory copy when removing elements (the last element is moved to the removed element's position).
|
||||
* @author Nathan Sweet */
|
||||
public class IntArray {
|
||||
public int[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public IntArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public IntArray (int capacity) {
|
||||
this(true, capacity);
|
||||
}
|
||||
|
||||
/** @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing array to be grown. */
|
||||
public IntArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new int[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specific array. The new array will be ordered if the specific array is
|
||||
* ordered. The capacity is set to the number of elements, so any subsequent elements added will cause the backing array to be
|
||||
* grown. */
|
||||
public IntArray (IntArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new int[size];
|
||||
System.arraycopy(array.items, 0, items, 0, size);
|
||||
}
|
||||
|
||||
/** Creates a new ordered array containing the elements in the specified array. The capacity is set to the number of elements,
|
||||
* so any subsequent elements added will cause the backing array to be grown. */
|
||||
public IntArray (int[] array) {
|
||||
this(true, array, 0, array.length);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The capacity is set to the number of elements, so any
|
||||
* subsequent elements added will cause the backing array to be grown.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy. */
|
||||
public IntArray (boolean ordered, int[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (int value) {
|
||||
int[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (int value1, int value2) {
|
||||
int[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (int value1, int value2, int value3) {
|
||||
int[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (int value1, int value2, int value3, int value4) {
|
||||
int[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (IntArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (IntArray array, int offset, int length) {
|
||||
if (offset + length > array.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
|
||||
addAll(array.items, offset, length);
|
||||
}
|
||||
|
||||
public void addAll (int... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (int[] array, int offset, int length) {
|
||||
int[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public int get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, int value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, int value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, int value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, int value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
int[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
int[] items = this.items;
|
||||
int firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
public boolean contains (int value) {
|
||||
int i = size - 1;
|
||||
int[] items = this.items;
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOf (int value) {
|
||||
int[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int lastIndexOf (int value) {
|
||||
int[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean removeValue (int value) {
|
||||
int[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public int removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
int[] items = this.items;
|
||||
int value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
int[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (IntArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
int[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
int item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item == items[ii]) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return size != startSize;
|
||||
}
|
||||
|
||||
/** Removes and returns the last item. */
|
||||
public int pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public int peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public int first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing array to the size of the actual items. This is useful to release memory when many items
|
||||
* have been removed, or if it is known that more items will not be added.
|
||||
* @return {@link #items} */
|
||||
public int[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public int[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public int[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected int[] resize (int newSize) {
|
||||
int[] newItems = new int[newSize];
|
||||
int[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
int[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
int temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
int[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = MathUtil.random(i);
|
||||
int temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reduces the size of the array to the specified size. If the array is already smaller than the specified size, no action is
|
||||
* taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or zero if the array is empty. */
|
||||
public int random () {
|
||||
if (size == 0) return 0;
|
||||
return items[MathUtil.random(0, size - 1)];
|
||||
}
|
||||
|
||||
public int[] toArray () {
|
||||
int[] array = new int[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
int[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + items[i];
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof IntArray)) return false;
|
||||
IntArray array = (IntArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
int[] items1 = this.items;
|
||||
int[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items[i] != array.items[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
int[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
int[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #IntArray(int[]) */
|
||||
static public IntArray with (int... array) {
|
||||
return new IntArray(array);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,842 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** An unordered map where the keys are ints and values are floats. This implementation is a cuckoo hash map using 3 hashes, random
|
||||
* walking, and a small stash for problematic keys. Null keys are not allowed. No allocation is done except when growing the table
|
||||
* size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.
|
||||
* @author Nathan Sweet */
|
||||
public class IntFloatMap implements Iterable<IntFloatMap.Entry> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
private static final int EMPTY = 0;
|
||||
|
||||
public int size;
|
||||
|
||||
int[] keyTable;
|
||||
float[] valueTable;
|
||||
int capacity, stashSize;
|
||||
float zeroValue;
|
||||
boolean hasZeroValue;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
/** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public IntFloatMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntFloatMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntFloatMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = new int[capacity + stashCapacity];
|
||||
valueTable = new float[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public IntFloatMap (IntFloatMap map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
zeroValue = map.zeroValue;
|
||||
hasZeroValue = map.hasZeroValue;
|
||||
}
|
||||
|
||||
public void put (int key, float value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
if (!hasZeroValue) {
|
||||
hasZeroValue = true;
|
||||
size++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key == key1) {
|
||||
valueTable[index1] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key == key2) {
|
||||
valueTable[index2] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key == key3) {
|
||||
valueTable[index3] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key == keyTable[i]) {
|
||||
valueTable[i] = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
public void putAll (IntFloatMap map) {
|
||||
for (Entry entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (int key, float value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
hasZeroValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (int insertKey, float insertValue, int index1, int key1, int index2, int key2, int index3, int key3) {
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
int evictedKey;
|
||||
float evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
evictedValue = valueTable[index1];
|
||||
keyTable[index1] = insertKey;
|
||||
valueTable[index1] = insertValue;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
evictedValue = valueTable[index2];
|
||||
keyTable[index2] = insertKey;
|
||||
valueTable[index2] = insertValue;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
evictedValue = valueTable[index3];
|
||||
keyTable[index3] = insertKey;
|
||||
valueTable[index3] = insertValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
index1 = evictedKey & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(evictedKey);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(evictedKey);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (int key, float value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** @param defaultValue Returned if the key was not associated with a value. */
|
||||
public float get (int key, float defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private float getStash (int key, float defaultValue) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** Returns the key's current value and increments the stored value. If the key is not in the map, defaultValue + increment is
|
||||
* put into the map. */
|
||||
public float getAndIncrement (int key, float defaultValue, float increment) {
|
||||
if (key == 0) {
|
||||
if (hasZeroValue) {
|
||||
float value = zeroValue;
|
||||
zeroValue += increment;
|
||||
return value;
|
||||
} else {
|
||||
hasZeroValue = true;
|
||||
zeroValue = defaultValue + increment;
|
||||
++size;
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
int index = key & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(key);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(key);
|
||||
if (key != keyTable[index]) return getAndIncrementStash(key, defaultValue, increment);
|
||||
}
|
||||
}
|
||||
float value = valueTable[index];
|
||||
valueTable[index] = value + increment;
|
||||
return value;
|
||||
}
|
||||
|
||||
private float getAndIncrementStash (int key, float defaultValue, float increment) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) {
|
||||
float value = valueTable[i];
|
||||
valueTable[i] = value + increment;
|
||||
return value;
|
||||
}
|
||||
put(key, defaultValue + increment);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public float remove (int key, float defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
hasZeroValue = false;
|
||||
size--;
|
||||
return zeroValue;
|
||||
}
|
||||
|
||||
int index = key & mask;
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(key);
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(key);
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key, defaultValue);
|
||||
}
|
||||
|
||||
float removeStash (int key, float defaultValue) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key == keyTable[i]) {
|
||||
float oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = EMPTY;
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
}
|
||||
|
||||
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
|
||||
* an expensive operation. */
|
||||
public boolean containsValue (float value) {
|
||||
if (hasZeroValue && zeroValue == value) return true;
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != 0 && valueTable[i] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
|
||||
* an expensive operation. */
|
||||
public boolean containsValue (float value, float epsilon) {
|
||||
if (hasZeroValue && Math.abs(zeroValue - value) <= epsilon) return true;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (Math.abs(valueTable[i] - value) <= epsilon) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (int key) {
|
||||
if (key == 0) return hasZeroValue;
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares
|
||||
* every value, which may be an expensive operation. */
|
||||
public int findKey (float value, int notFound) {
|
||||
if (hasZeroValue && zeroValue == value) return 0;
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != 0 && valueTable[i] == value) return keyTable[i];
|
||||
return notFound;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
int[] oldKeyTable = keyTable;
|
||||
float[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = new int[newSize + stashCapacity];
|
||||
valueTable = new float[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = hasZeroValue ? 1 : 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
int key = oldKeyTable[i];
|
||||
if (key != EMPTY) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
if (hasZeroValue) {
|
||||
h += Float.floatToIntBits(zeroValue);
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
h += key * 31;
|
||||
|
||||
float value = valueTable[i];
|
||||
h += Float.floatToIntBits(value);
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof IntFloatMap)) return false;
|
||||
IntFloatMap other = (IntFloatMap)obj;
|
||||
if (other.size != size) return false;
|
||||
if (other.hasZeroValue != hasZeroValue) return false;
|
||||
if (hasZeroValue && other.zeroValue != zeroValue) {
|
||||
return false;
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
float otherValue = other.get(key, 0f);
|
||||
if (otherValue == 0f && !other.containsKey(key)) return false;
|
||||
float value = valueTable[i];
|
||||
if (otherValue != value) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
if (hasZeroValue) {
|
||||
buffer.append("0=");
|
||||
buffer.append(zeroValue);
|
||||
} else {
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Entries entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Values values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return values2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each time
|
||||
* this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Keys keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry {
|
||||
public int key;
|
||||
public float value;
|
||||
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator {
|
||||
static final int INDEX_ILLEGAL = -2;
|
||||
static final int INDEX_ZERO = -1;
|
||||
|
||||
public boolean hasNext;
|
||||
|
||||
final IntFloatMap map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (IntFloatMap map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
nextIndex = INDEX_ZERO;
|
||||
if (map.hasZeroValue)
|
||||
hasNext = true;
|
||||
else
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
int[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != EMPTY) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex == INDEX_ZERO && map.hasZeroValue) {
|
||||
map.hasZeroValue = false;
|
||||
} else if (currentIndex < 0) {
|
||||
throw new IllegalStateException("next must be called before remove.");
|
||||
} else if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = EMPTY;
|
||||
}
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries extends MapIterator implements Iterable<Entry>, Iterator<Entry> {
|
||||
private Entry entry = new Entry();
|
||||
|
||||
public Entries (IntFloatMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int[] keyTable = map.keyTable;
|
||||
if (nextIndex == INDEX_ZERO) {
|
||||
entry.key = 0;
|
||||
entry.value = map.zeroValue;
|
||||
} else {
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
}
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values extends MapIterator {
|
||||
public Values (IntFloatMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public float next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
float value;
|
||||
if (nextIndex == INDEX_ZERO)
|
||||
value = map.zeroValue;
|
||||
else
|
||||
value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public FloatArray toArray () {
|
||||
FloatArray array = new FloatArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys extends MapIterator {
|
||||
public Keys (IntFloatMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int key = nextIndex == INDEX_ZERO ? 0 : map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,828 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** An unordered map where the keys and values are ints. This implementation is a cuckoo hash map using 3 hashes, random walking,
|
||||
* and a small stash for problematic keys. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.
|
||||
* @author Nathan Sweet */
|
||||
public class IntIntMap implements Iterable<IntIntMap.Entry> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
private static final int EMPTY = 0;
|
||||
|
||||
public int size;
|
||||
|
||||
int[] keyTable, valueTable;
|
||||
int capacity, stashSize;
|
||||
int zeroValue;
|
||||
boolean hasZeroValue;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
/** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public IntIntMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntIntMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntIntMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = new int[capacity + stashCapacity];
|
||||
valueTable = new int[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public IntIntMap (IntIntMap map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
zeroValue = map.zeroValue;
|
||||
hasZeroValue = map.hasZeroValue;
|
||||
}
|
||||
|
||||
public void put (int key, int value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
if (!hasZeroValue) {
|
||||
hasZeroValue = true;
|
||||
size++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key == key1) {
|
||||
valueTable[index1] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key == key2) {
|
||||
valueTable[index2] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key == key3) {
|
||||
valueTable[index3] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key == keyTable[i]) {
|
||||
valueTable[i] = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
public void putAll (IntIntMap map) {
|
||||
for (Entry entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (int key, int value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
hasZeroValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (int insertKey, int insertValue, int index1, int key1, int index2, int key2, int index3, int key3) {
|
||||
int[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
int evictedKey;
|
||||
int evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
evictedValue = valueTable[index1];
|
||||
keyTable[index1] = insertKey;
|
||||
valueTable[index1] = insertValue;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
evictedValue = valueTable[index2];
|
||||
keyTable[index2] = insertKey;
|
||||
valueTable[index2] = insertValue;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
evictedValue = valueTable[index3];
|
||||
keyTable[index3] = insertKey;
|
||||
valueTable[index3] = insertValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
index1 = evictedKey & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(evictedKey);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(evictedKey);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (int key, int value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** @param defaultValue Returned if the key was not associated with a value. */
|
||||
public int get (int key, int defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private int getStash (int key, int defaultValue) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** Returns the key's current value and increments the stored value. If the key is not in the map, defaultValue + increment is
|
||||
* put into the map. */
|
||||
public int getAndIncrement (int key, int defaultValue, int increment) {
|
||||
if (key == 0) {
|
||||
if (hasZeroValue) {
|
||||
int value = zeroValue;
|
||||
zeroValue += increment;
|
||||
return value;
|
||||
} else {
|
||||
hasZeroValue = true;
|
||||
zeroValue = defaultValue + increment;
|
||||
++size;
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
int index = key & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(key);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(key);
|
||||
if (key != keyTable[index]) return getAndIncrementStash(key, defaultValue, increment);
|
||||
}
|
||||
}
|
||||
int value = valueTable[index];
|
||||
valueTable[index] = value + increment;
|
||||
return value;
|
||||
}
|
||||
|
||||
private int getAndIncrementStash (int key, int defaultValue, int increment) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) {
|
||||
int value = valueTable[i];
|
||||
valueTable[i] = value + increment;
|
||||
return value;
|
||||
}
|
||||
put(key, defaultValue + increment);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public int remove (int key, int defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
hasZeroValue = false;
|
||||
size--;
|
||||
return zeroValue;
|
||||
}
|
||||
|
||||
int index = key & mask;
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(key);
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(key);
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key, defaultValue);
|
||||
}
|
||||
|
||||
int removeStash (int key, int defaultValue) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key == keyTable[i]) {
|
||||
int oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = EMPTY;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
hasZeroValue = false;
|
||||
}
|
||||
|
||||
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
|
||||
* an expensive operation. */
|
||||
public boolean containsValue (int value) {
|
||||
if (hasZeroValue && zeroValue == value) return true;
|
||||
int[] keyTable = this.keyTable, valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != 0 && valueTable[i] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (int key) {
|
||||
if (key == 0) return hasZeroValue;
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares
|
||||
* every value, which may be an expensive operation. */
|
||||
public int findKey (int value, int notFound) {
|
||||
if (hasZeroValue && zeroValue == value) return 0;
|
||||
int[] keyTable = this.keyTable, valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != 0 && valueTable[i] == value) return keyTable[i];
|
||||
return notFound;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
int[] oldKeyTable = keyTable;
|
||||
int[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = new int[newSize + stashCapacity];
|
||||
valueTable = new int[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = hasZeroValue ? 1 : 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
int key = oldKeyTable[i];
|
||||
if (key != EMPTY) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
if (hasZeroValue) {
|
||||
h += Float.floatToIntBits(zeroValue);
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
h += key * 31;
|
||||
|
||||
int value = valueTable[i];
|
||||
h += value;
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof IntIntMap)) return false;
|
||||
IntIntMap other = (IntIntMap)obj;
|
||||
if (other.size != size) return false;
|
||||
if (other.hasZeroValue != hasZeroValue) return false;
|
||||
if (hasZeroValue && other.zeroValue != zeroValue) {
|
||||
return false;
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
int otherValue = other.get(key, 0);
|
||||
if (otherValue == 0 && !other.containsKey(key)) return false;
|
||||
int value = valueTable[i];
|
||||
if (otherValue != value) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
int[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
if (hasZeroValue) {
|
||||
buffer.append("0=");
|
||||
buffer.append(zeroValue);
|
||||
} else {
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Entries entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Values values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return values2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each time
|
||||
* this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Keys keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry {
|
||||
public int key;
|
||||
public int value;
|
||||
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator {
|
||||
static final int INDEX_ILLEGAL = -2;
|
||||
static final int INDEX_ZERO = -1;
|
||||
|
||||
public boolean hasNext;
|
||||
|
||||
final IntIntMap map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (IntIntMap map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
nextIndex = INDEX_ZERO;
|
||||
if (map.hasZeroValue)
|
||||
hasNext = true;
|
||||
else
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
int[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != EMPTY) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex == INDEX_ZERO && map.hasZeroValue) {
|
||||
map.hasZeroValue = false;
|
||||
} else if (currentIndex < 0) {
|
||||
throw new IllegalStateException("next must be called before remove.");
|
||||
} else if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = EMPTY;
|
||||
}
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries extends MapIterator implements Iterable<Entry>, Iterator<Entry> {
|
||||
private Entry entry = new Entry();
|
||||
|
||||
public Entries (IntIntMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int[] keyTable = map.keyTable;
|
||||
if (nextIndex == INDEX_ZERO) {
|
||||
entry.key = 0;
|
||||
entry.value = map.zeroValue;
|
||||
} else {
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
}
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values extends MapIterator {
|
||||
public Values (IntIntMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int value;
|
||||
if (nextIndex == INDEX_ZERO)
|
||||
value = map.zeroValue;
|
||||
else
|
||||
value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys extends MapIterator {
|
||||
public Keys (IntIntMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int key = nextIndex == INDEX_ZERO ? 0 : map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,881 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
/** An unordered map that uses int keys. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small
|
||||
* stash for problematic keys. Null values are allowed. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class IntMap<V> implements Iterable<IntMap.Entry<V>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
private static final int EMPTY = 0;
|
||||
|
||||
public int size;
|
||||
|
||||
int[] keyTable;
|
||||
V[] valueTable;
|
||||
int capacity, stashSize;
|
||||
V zeroValue;
|
||||
boolean hasZeroValue;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
/** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public IntMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = new int[capacity + stashCapacity];
|
||||
valueTable = (V[])new Object[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public IntMap (IntMap<? extends V> map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
zeroValue = map.zeroValue;
|
||||
hasZeroValue = map.hasZeroValue;
|
||||
}
|
||||
|
||||
public V put (int key, V value) {
|
||||
if (key == 0) {
|
||||
V oldValue = zeroValue;
|
||||
zeroValue = value;
|
||||
if (!hasZeroValue) {
|
||||
hasZeroValue = true;
|
||||
size++;
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == key) {
|
||||
V oldValue = valueTable[index1];
|
||||
valueTable[index1] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == key) {
|
||||
V oldValue = valueTable[index2];
|
||||
valueTable[index2] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == key) {
|
||||
V oldValue = valueTable[index3];
|
||||
valueTable[index3] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
valueTable[i] = value;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void putAll (IntMap<? extends V> map) {
|
||||
for (Entry<? extends V> entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (int key, V value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
hasZeroValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (int insertKey, V insertValue, int index1, int key1, int index2, int key2, int index3, int key3) {
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
V[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
int evictedKey;
|
||||
V evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random.nextInt(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
evictedValue = valueTable[index1];
|
||||
keyTable[index1] = insertKey;
|
||||
valueTable[index1] = insertValue;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
evictedValue = valueTable[index2];
|
||||
keyTable[index2] = insertKey;
|
||||
valueTable[index2] = insertValue;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
evictedValue = valueTable[index3];
|
||||
keyTable[index3] = insertKey;
|
||||
valueTable[index3] = insertValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
index1 = evictedKey & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(evictedKey);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(evictedKey);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (int key, V value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
public V get (int key) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return null;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, null);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
public V get (int key, V defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private V getStash (int key, V defaultValue) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public V remove (int key) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return null;
|
||||
V oldValue = zeroValue;
|
||||
zeroValue = null;
|
||||
hasZeroValue = false;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index = key & mask;
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
V removeStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
valueTable[lastIndex] = null;
|
||||
} else
|
||||
valueTable[index] = null;
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
zeroValue = null;
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
int[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;) {
|
||||
keyTable[i] = EMPTY;
|
||||
valueTable[i] = null;
|
||||
}
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
zeroValue = null;
|
||||
hasZeroValue = false;
|
||||
}
|
||||
|
||||
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may
|
||||
* be an expensive operation.
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}. */
|
||||
public boolean containsValue (Object value, boolean identity) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
if (hasZeroValue && zeroValue == null) return true;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != EMPTY && valueTable[i] == null) return true;
|
||||
} else if (identity) {
|
||||
if (value == zeroValue) return true;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return true;
|
||||
} else {
|
||||
if (hasZeroValue && value.equals(zeroValue)) return true;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (int key) {
|
||||
if (key == 0) return hasZeroValue;
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value, or <tt>notFound</tt> if it is not in the map. Note this traverses the entire map
|
||||
* and compares every value, which may be an expensive operation.
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}. */
|
||||
public int findKey (Object value, boolean identity, int notFound) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
if (hasZeroValue && zeroValue == null) return 0;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != EMPTY && valueTable[i] == null) return keyTable[i];
|
||||
} else if (identity) {
|
||||
if (value == zeroValue) return 0;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return keyTable[i];
|
||||
} else {
|
||||
if (hasZeroValue && value.equals(zeroValue)) return 0;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return keyTable[i];
|
||||
}
|
||||
return notFound;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
int[] oldKeyTable = keyTable;
|
||||
V[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = new int[newSize + stashCapacity];
|
||||
valueTable = (V[])new Object[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = hasZeroValue ? 1 : 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
int key = oldKeyTable[i];
|
||||
if (key != EMPTY) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
if (hasZeroValue && zeroValue != null) {
|
||||
h += zeroValue.hashCode();
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
h += key * 31;
|
||||
|
||||
V value = valueTable[i];
|
||||
if (value != null) {
|
||||
h += value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof IntMap)) return false;
|
||||
IntMap<V> other = (IntMap)obj;
|
||||
if (other.size != size) return false;
|
||||
if (other.hasZeroValue != hasZeroValue) return false;
|
||||
if (hasZeroValue) {
|
||||
if (other.zeroValue == null) {
|
||||
if (zeroValue != null) return false;
|
||||
} else {
|
||||
if (!other.zeroValue.equals(zeroValue)) return false;
|
||||
}
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
V value = valueTable[i];
|
||||
if (value == null) {
|
||||
if (!other.containsKey(key) || other.get(key) != null) return false;
|
||||
} else {
|
||||
if (!value.equals(other.get(key))) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
int[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
if (hasZeroValue) {
|
||||
buffer.append("0=");
|
||||
buffer.append(zeroValue);
|
||||
} else {
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<V>> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Entries<V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Values<V> values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return values2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Keys keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<V> {
|
||||
public int key;
|
||||
public V value;
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator<V> {
|
||||
static final int INDEX_ILLEGAL = -2;
|
||||
static final int INDEX_ZERO = -1;
|
||||
|
||||
public boolean hasNext;
|
||||
|
||||
final IntMap<V> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (IntMap<V> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
nextIndex = INDEX_ZERO;
|
||||
if (map.hasZeroValue)
|
||||
hasNext = true;
|
||||
else
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
int[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != EMPTY) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex == INDEX_ZERO && map.hasZeroValue) {
|
||||
map.zeroValue = null;
|
||||
map.hasZeroValue = false;
|
||||
} else if (currentIndex < 0) {
|
||||
throw new IllegalStateException("next must be called before remove.");
|
||||
} else if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = EMPTY;
|
||||
map.valueTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static public class Entries<V> extends MapIterator<V> implements Iterable<Entry<V>>, Iterator<Entry<V>> {
|
||||
private Entry<V> entry = new Entry();
|
||||
|
||||
public Entries (IntMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
@Override
|
||||
public Entry<V> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int[] keyTable = map.keyTable;
|
||||
if (nextIndex == INDEX_ZERO) {
|
||||
entry.key = 0;
|
||||
entry.value = map.zeroValue;
|
||||
} else {
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
}
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<V>> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static public class Values<V> extends MapIterator<V> implements Iterable<V>, Iterator<V> {
|
||||
public Values (IntMap<V> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
V value;
|
||||
if (nextIndex == INDEX_ZERO)
|
||||
value = map.zeroValue;
|
||||
else
|
||||
value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public Array<V> toArray () {
|
||||
Array array = new Array(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static public class Keys extends MapIterator {
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Keys (IntMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int key = nextIndex == INDEX_ZERO ? 0 : map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,571 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
/** An unordered set that uses int keys. This implementation uses cuckoo hashing using 3 hashes, random walking, and a small stash
|
||||
* for problematic keys. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This set performs very fast contains and remove (typically O(1), worst case O(log(n))). Add may be a bit slower, depending on
|
||||
* hash collisions. Load factors greater than 0.91 greatly increase the chances the set will have to rehash to the next higher POT
|
||||
* size.
|
||||
* @author Nathan Sweet */
|
||||
public class IntSet {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
private static final int EMPTY = 0;
|
||||
|
||||
public int size;
|
||||
|
||||
int[] keyTable;
|
||||
int capacity, stashSize;
|
||||
boolean hasZeroValue;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private IntSetIterator iterator1, iterator2;
|
||||
|
||||
/** Creates a new set with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public IntSet () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new set with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntSet (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new set with the specified initial capacity and load factor. This set will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntSet (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = new int[capacity + stashCapacity];
|
||||
}
|
||||
|
||||
/** Creates a new set identical to the specified set. */
|
||||
public IntSet (IntSet set) {
|
||||
this((int)Math.floor(set.capacity * set.loadFactor), set.loadFactor);
|
||||
stashSize = set.stashSize;
|
||||
System.arraycopy(set.keyTable, 0, keyTable, 0, set.keyTable.length);
|
||||
size = set.size;
|
||||
hasZeroValue = set.hasZeroValue;
|
||||
}
|
||||
|
||||
/** Returns true if the key was not already in the set. */
|
||||
public boolean add (int key) {
|
||||
if (key == 0) {
|
||||
if (hasZeroValue) return false;
|
||||
hasZeroValue = true;
|
||||
size++;
|
||||
return true;
|
||||
}
|
||||
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == key) return false;
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == key) return false;
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == key) return false;
|
||||
|
||||
// Find key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return false;
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
push(key, index1, key1, index2, key2, index3, key3);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addAll (IntArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (IntArray array, int offset, int length) {
|
||||
if (offset + length > array.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
|
||||
addAll(array.items, offset, length);
|
||||
}
|
||||
|
||||
public void addAll (int... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (int[] array, int offset, int length) {
|
||||
ensureCapacity(length);
|
||||
for (int i = offset, n = i + length; i < n; i++)
|
||||
add(array[i]);
|
||||
}
|
||||
|
||||
public void addAll (IntSet set) {
|
||||
ensureCapacity(set.size);
|
||||
IntSetIterator iterator = set.iterator();
|
||||
while (iterator.hasNext)
|
||||
add(iterator.next());
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void addResize (int key) {
|
||||
if (key == 0) {
|
||||
hasZeroValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (int insertKey, int index1, int key1, int index2, int key2, int index3, int key3) {
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
int evictedKey;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random.nextInt(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
keyTable[index1] = insertKey;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
keyTable[index2] = insertKey;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
keyTable[index3] = insertKey;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
index1 = evictedKey & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(evictedKey);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(evictedKey);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
} while (true);
|
||||
|
||||
addStash(evictedKey);
|
||||
}
|
||||
|
||||
private void addStash (int key) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
addResize(key);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** Returns true if the key was removed. */
|
||||
public boolean remove (int key) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return false;
|
||||
hasZeroValue = false;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
int index = key & mask;
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = hash2(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = hash3(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
boolean removeStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) keyTable[index] = keyTable[lastIndex];
|
||||
}
|
||||
|
||||
/** Returns true if the set is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the set contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the set and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = EMPTY;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
hasZeroValue = false;
|
||||
}
|
||||
|
||||
public boolean contains (int key) {
|
||||
if (key == 0) return hasZeroValue;
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int first () {
|
||||
if (hasZeroValue) return 0;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != EMPTY) return keyTable[i];
|
||||
throw new IllegalStateException("IntSet is empty.");
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
int[] oldKeyTable = keyTable;
|
||||
|
||||
keyTable = new int[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = hasZeroValue ? 1 : 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
int key = oldKeyTable[i];
|
||||
if (key != EMPTY) addResize(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != EMPTY) h += keyTable[i];
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (!(obj instanceof IntSet)) return false;
|
||||
IntSet other = (IntSet)obj;
|
||||
if (other.size != size) return false;
|
||||
if (other.hasZeroValue != hasZeroValue) return false;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != EMPTY && !other.contains(keyTable[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
int[] keyTable = this.keyTable;
|
||||
int i = keyTable.length;
|
||||
if (hasZeroValue)
|
||||
buffer.append("0");
|
||||
else {
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the set. Remove is supported. Note that the same iterator instance is returned each time
|
||||
* this method is called. Use the {@link IntSetIterator} constructor for nested or multithreaded iteration. */
|
||||
public IntSetIterator iterator () {
|
||||
if (iterator1 == null) {
|
||||
iterator1 = new IntSetIterator(this);
|
||||
iterator2 = new IntSetIterator(this);
|
||||
}
|
||||
if (!iterator1.valid) {
|
||||
iterator1.reset();
|
||||
iterator1.valid = true;
|
||||
iterator2.valid = false;
|
||||
return iterator1;
|
||||
}
|
||||
iterator2.reset();
|
||||
iterator2.valid = true;
|
||||
iterator1.valid = false;
|
||||
return iterator2;
|
||||
}
|
||||
|
||||
static public IntSet with (int... array) {
|
||||
IntSet set = new IntSet();
|
||||
set.addAll(array);
|
||||
return set;
|
||||
}
|
||||
|
||||
static public class IntSetIterator {
|
||||
static final int INDEX_ILLEGAL = -2;
|
||||
static final int INDEX_ZERO = -1;
|
||||
|
||||
public boolean hasNext;
|
||||
|
||||
final IntSet set;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public IntSetIterator (IntSet set) {
|
||||
this.set = set;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
nextIndex = INDEX_ZERO;
|
||||
if (set.hasZeroValue)
|
||||
hasNext = true;
|
||||
else
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
int[] keyTable = set.keyTable;
|
||||
for (int n = set.capacity + set.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != EMPTY) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex == INDEX_ZERO && set.hasZeroValue) {
|
||||
set.hasZeroValue = false;
|
||||
} else if (currentIndex < 0) {
|
||||
throw new IllegalStateException("next must be called before remove.");
|
||||
} else if (currentIndex >= set.capacity) {
|
||||
set.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
set.keyTable[currentIndex] = EMPTY;
|
||||
}
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
set.size--;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int key = nextIndex == INDEX_ZERO ? 0 : set.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, set.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.RandomAccess;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
/**
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
*
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
*
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
*
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*/
|
||||
public final
|
||||
class LockFreeArrayList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeArrayList, ArrayList> listRef =
|
||||
AtomicReferenceFieldUpdater.newUpdater(LockFreeArrayList.class,
|
||||
ArrayList.class, "arrayList");
|
||||
private volatile ArrayList<E> arrayList = new ArrayList<>();
|
||||
|
||||
|
||||
public
|
||||
LockFreeArrayList(){}
|
||||
|
||||
|
||||
public
|
||||
LockFreeArrayList(Collection<E> elements) {
|
||||
arrayList.addAll(elements);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
public synchronized
|
||||
void clear() {
|
||||
arrayList.clear();
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
public synchronized
|
||||
boolean add(final E element) {
|
||||
return arrayList.add(element);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
public synchronized
|
||||
boolean addAll(final Collection<? extends E> elements) {
|
||||
return arrayList.addAll(elements);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
@Override
|
||||
public synchronized
|
||||
boolean addAll(final int i, final Collection<? extends E> collection) {
|
||||
return arrayList.addAll(i, collection);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
@Override
|
||||
public
|
||||
boolean removeAll(final Collection<?> collection) {
|
||||
return arrayList.removeAll(collection);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
@Override
|
||||
public synchronized
|
||||
boolean retainAll(final Collection<?> collection) {
|
||||
return retainAll(collection);
|
||||
}
|
||||
|
||||
|
||||
public
|
||||
E get(int index) {
|
||||
//noinspection unchecked
|
||||
return (E) listRef.get(this).get(index);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
@Override
|
||||
public synchronized
|
||||
E set(final int index, final E element) {
|
||||
return arrayList.set(index, element);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
@Override
|
||||
public synchronized
|
||||
void add(final int index, final E element) {
|
||||
arrayList.add(index, element);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
@Override
|
||||
public synchronized
|
||||
E remove(final int index) {
|
||||
return arrayList.remove(index);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
int indexOf(final Object object) {
|
||||
return listRef.get(this).indexOf(object);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
int lastIndexOf(final Object object) {
|
||||
return listRef.get(this).lastIndexOf(object);
|
||||
}
|
||||
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
ListIterator<E> listIterator() {
|
||||
return listRef.get(this).listIterator();
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
ListIterator<E> listIterator(final int index) {
|
||||
return listRef.get(this).listIterator(index);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
List<E> subList(final int startIndex, final int endIndex) {
|
||||
return listRef.get(this).subList(startIndex, endIndex);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
public synchronized
|
||||
boolean remove(final Object element) {
|
||||
return arrayList.remove(element);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
boolean containsAll(final Collection<?> collection) {
|
||||
//noinspection unchecked
|
||||
return listRef.get(this).containsAll(collection);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
public
|
||||
int size() {
|
||||
return listRef.get(this).size();
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
boolean isEmpty() {
|
||||
return listRef.get(this).isEmpty();
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
public
|
||||
boolean contains(final Object element) {
|
||||
// use the SWP to get the value
|
||||
return listRef.get(this).contains(element);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
Iterator<E> iterator() {
|
||||
return listRef.get(this).iterator();
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
Object[] toArray() {
|
||||
return listRef.get(this).toArray();
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
<T> T[] toArray(final T[] targetArray) {
|
||||
//noinspection unchecked
|
||||
return (T[]) listRef.get(this).toArray(targetArray);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
ArrayList<E> elements() {
|
||||
return listRef.get(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
/**
|
||||
* A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as that of its keys. This constraint
|
||||
* enables bimaps to support an "inverse view", which is another bimap containing the same entries as this bimap but with reversed keys and values.
|
||||
*
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
*
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
*
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
*
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public final
|
||||
class LockFreeBiMap<K, V> {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeBiMap, HashMap> forwardREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(LockFreeBiMap.class,
|
||||
HashMap.class,
|
||||
"forwardHashMap");
|
||||
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeBiMap, HashMap> reverseREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(LockFreeBiMap.class,
|
||||
HashMap.class,
|
||||
"reverseHashMap");
|
||||
|
||||
private volatile HashMap<K, V> forwardHashMap;
|
||||
private volatile HashMap<V, K> reverseHashMap;
|
||||
private final LockFreeBiMap<V, K> inverse;
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
|
||||
public
|
||||
LockFreeBiMap() {
|
||||
forwardHashMap = new HashMap<K, V>();
|
||||
reverseHashMap = new HashMap<V, K>();
|
||||
this.inverse = new LockFreeBiMap<V, K>(reverseHashMap, forwardHashMap, this);
|
||||
}
|
||||
|
||||
private
|
||||
LockFreeBiMap(final HashMap<K, V> forwardHashMap, final HashMap<V, K> reverseHashMap, final LockFreeBiMap<V, K> inverse) {
|
||||
this.forwardHashMap = forwardHashMap;
|
||||
this.reverseHashMap = reverseHashMap;
|
||||
this.inverse = inverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all of the mappings from this bimap.
|
||||
* The bimap will be empty after this call returns.
|
||||
*/
|
||||
public synchronized
|
||||
void clear() {
|
||||
forwardHashMap.clear();
|
||||
reverseHashMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the inverse view of this bimap, which maps each of this bimap's values to its associated key.
|
||||
*/
|
||||
public
|
||||
LockFreeBiMap<V, K> inverse() {
|
||||
return inverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all of the mappings from the specified map to this bimap.
|
||||
* These mappings will replace any mappings that this map had for
|
||||
* any of the keys currently in the specified map.
|
||||
*
|
||||
* @param hashMap mappings to be stored in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*
|
||||
* @throws IllegalArgumentException if a given value in the map is already bound to a different key in this bimap. The bimap will remain
|
||||
* unmodified in this event. To avoid this exception, call {@link #replaceAllForce(Map)} replaceAllForce(map) instead.
|
||||
*/
|
||||
public synchronized
|
||||
void replaceAll(final Map<K, V> hashMap) throws IllegalArgumentException {
|
||||
if (hashMap == null) {
|
||||
throw new NullPointerException("hashMap");
|
||||
}
|
||||
|
||||
LockFreeBiMap<K, V> biMap = new LockFreeBiMap<K, V>();
|
||||
|
||||
try {
|
||||
biMap.putAll(hashMap);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// do nothing if there is an exception
|
||||
throw e;
|
||||
}
|
||||
|
||||
// only if there are no problems with the creation of the new bimap.
|
||||
this.forwardHashMap.clear();
|
||||
this.reverseHashMap.clear();
|
||||
|
||||
this.forwardHashMap.putAll(biMap.forwardHashMap);
|
||||
this.reverseHashMap.putAll(biMap.reverseHashMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all of the mappings from the specified map to this bimap.
|
||||
* These mappings will replace any mappings that this map had for
|
||||
* any of the keys currently in the specified map. This is an alternate
|
||||
* form of {@link #replaceAll(Map)} replaceAll(K, V) that will silently
|
||||
* ignore duplicates
|
||||
*
|
||||
* @param hashMap mappings to be stored in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*/
|
||||
public synchronized
|
||||
void replaceAllForce(final Map<K, V> hashMap) {
|
||||
if (hashMap == null) {
|
||||
throw new NullPointerException("hashMap");
|
||||
}
|
||||
|
||||
// only if there are no problems with the creation of the new bimap.
|
||||
this.forwardHashMap.clear();
|
||||
this.reverseHashMap.clear();
|
||||
|
||||
putAllForce(hashMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified key in this bimap.
|
||||
* If the bimap previously contained a mapping for the key, the old
|
||||
* value is replaced. If the given value is already bound to a different
|
||||
* key in this bimap, the bimap will remain unmodified. To avoid throwing
|
||||
* an exception, call {@link #putForce(Object, Object)} putForce(K, V) instead.
|
||||
*
|
||||
* @param key key with which the specified value is to be associated
|
||||
* @param value value to be associated with the specified key
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>null</tt> return can also indicate that the map
|
||||
* previously associated <tt>null</tt> with <tt>key</tt>.)
|
||||
*
|
||||
* @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain
|
||||
* unmodified in this event. To avoid this exception, call {@link #putForce(Object, Object)} putForce(K, V) instead.
|
||||
*/
|
||||
public synchronized
|
||||
V put(final K key, final V value) throws IllegalArgumentException {
|
||||
V prevForwardValue = this.forwardHashMap.put(key, value);
|
||||
if (prevForwardValue != null) {
|
||||
reverseHashMap.remove(prevForwardValue);
|
||||
}
|
||||
|
||||
K prevReverseValue = this.reverseHashMap.put(value, key);
|
||||
if (prevReverseValue != null) {
|
||||
// put the old value back
|
||||
if (prevForwardValue != null) {
|
||||
this.forwardHashMap.put(key, prevForwardValue);
|
||||
}
|
||||
else {
|
||||
this.forwardHashMap.remove(key);
|
||||
}
|
||||
this.reverseHashMap.put(value, prevReverseValue);
|
||||
|
||||
throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!");
|
||||
}
|
||||
|
||||
return prevForwardValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified key in this bimap.
|
||||
* If the bimap previously contained a mapping for the key, the old
|
||||
* value is replaced. This is an alternate form of {@link #put(Object, Object)} put(K, V)
|
||||
* that will silently ignore duplicates
|
||||
*
|
||||
* @param key key with which the specified value is to be associated
|
||||
* @param value value to be associated with the specified key
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>null</tt> return can also indicate that the map
|
||||
* previously associated <tt>null</tt> with <tt>key</tt>.)
|
||||
*/
|
||||
public synchronized
|
||||
V putForce(final K key, final V value) {
|
||||
V prevForwardValue = this.forwardHashMap.put(key, value);
|
||||
if (prevForwardValue != null) {
|
||||
reverseHashMap.remove(prevForwardValue);
|
||||
}
|
||||
|
||||
K prevReverseValue = this.reverseHashMap.put(value, key);
|
||||
if (prevReverseValue != null) {
|
||||
forwardHashMap.remove(prevReverseValue);
|
||||
}
|
||||
|
||||
return prevForwardValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the specified map to this map.
|
||||
* These mappings will replace any mappings that this map had for
|
||||
* any of the keys currently in the specified map.
|
||||
*
|
||||
* @param hashMap mappings to be stored in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*
|
||||
* @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain
|
||||
* unmodified in this event. To avoid this exception, call {@link #putAllForce(Map)} putAllForce(K, V) instead.
|
||||
*/
|
||||
public synchronized
|
||||
void putAll(final Map<K, V> hashMap) throws IllegalArgumentException {
|
||||
LockFreeBiMap<K, V> biMap = new LockFreeBiMap<K, V>();
|
||||
|
||||
try {
|
||||
for (Map.Entry<K, V> entry : hashMap.entrySet()) {
|
||||
K key = entry.getKey();
|
||||
V value = entry.getValue();
|
||||
|
||||
biMap.put(key, value);
|
||||
|
||||
// we have to verify that the keys/values between the bimaps are unique
|
||||
if (this.forwardHashMap.containsKey(key)) {
|
||||
throw new IllegalArgumentException("Key already exists. Keys and values must both be unique!");
|
||||
}
|
||||
|
||||
if (this.reverseHashMap.containsKey(value)) {
|
||||
throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!");
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// do nothing if there is an exception
|
||||
throw e;
|
||||
}
|
||||
|
||||
// only if there are no problems with the creation of the new bimap AND the uniqueness constrain is guaranteed
|
||||
this.forwardHashMap.putAll(biMap.forwardHashMap);
|
||||
this.reverseHashMap.putAll(biMap.reverseHashMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the specified map to this map.
|
||||
* These mappings will replace any mappings that this map had for
|
||||
* any of the keys currently in the specified map. This is an alternate
|
||||
* form of {@link #putAll(Map)} putAll(K, V) that will silently
|
||||
* ignore duplicates
|
||||
*
|
||||
* @param hashMap mappings to be stored in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*/
|
||||
public synchronized
|
||||
void putAllForce(final Map<K, V> hashMap) {
|
||||
for (Map.Entry<K, V> entry : hashMap.entrySet()) {
|
||||
K key = entry.getKey();
|
||||
V value = entry.getValue();
|
||||
|
||||
putForce(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for the specified key from this map if present.
|
||||
*
|
||||
* @param key key whose mapping is to be removed from the map
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>null</tt> return can also indicate that the map
|
||||
* previously associated <tt>null</tt> with <tt>key</tt>.)
|
||||
*/
|
||||
public synchronized
|
||||
V remove(final K key) {
|
||||
V value = forwardHashMap.remove(key);
|
||||
reverseHashMap.remove(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped,
|
||||
* or {@code null} if this map contains no mapping for the key.
|
||||
* <p>
|
||||
* <p>More formally, if this map contains a mapping from a key
|
||||
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
|
||||
* key.equals(k))}, then this method returns {@code v}; otherwise
|
||||
* it returns {@code null}. (There can be at most one such mapping.)
|
||||
* <p>
|
||||
* <p>A return value of {@code null} does not <i>necessarily</i>
|
||||
* indicate that the map contains no mapping for the key; it's also
|
||||
* possible that the map explicitly maps the key to {@code null}.
|
||||
* The {@link HashMap#containsKey containsKey} operation may be used to
|
||||
* distinguish these two cases.
|
||||
*
|
||||
* @see #put(Object, Object)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
V get(final K key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return (V) forwardREF.get(this).get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reverse key to which the specified key is mapped,
|
||||
* or {@code null} if this map contains no mapping for the key.
|
||||
* <p>
|
||||
* <p>More formally, if this map contains a mapping from a key
|
||||
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
|
||||
* key.equals(k))}, then this method returns {@code v}; otherwise
|
||||
* it returns {@code null}. (There can be at most one such mapping.)
|
||||
* <p>
|
||||
* <p>A return value of {@code null} does not <i>necessarily</i>
|
||||
* indicate that the map contains no mapping for the key; it's also
|
||||
* possible that the map explicitly maps the key to {@code null}.
|
||||
* The {@link HashMap#containsKey containsKey} operation may be used to
|
||||
* distinguish these two cases.
|
||||
*
|
||||
* @see #put(Object, Object)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
K getReverse(final V key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return (K) reverseREF.get(this).get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Collection} view of the values contained in this map.
|
||||
* The collection is backed by the map, so changes to the map are
|
||||
* reflected in the collection, and vice-versa. If the map is
|
||||
* modified while an iteration over the collection is in progress
|
||||
* (except through the iterator's own <tt>remove</tt> operation),
|
||||
* the results of the iteration are undefined. The collection
|
||||
* supports element removal, which removes the corresponding
|
||||
* mapping from the map, via the <tt>Iterator.remove</tt>,
|
||||
* <tt>Collection.remove</tt>, <tt>removeAll</tt>,
|
||||
* <tt>retainAll</tt> and <tt>clear</tt> operations. It does not
|
||||
* support the <tt>add</tt> or <tt>addAll</tt> operations.
|
||||
*
|
||||
* @return a view of the values contained in this map
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Collection<V> values() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return forwardREF.get(this).values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <tt>true</tt> if this bimap contains no key-value mappings.
|
||||
*
|
||||
* @return <tt>true</tt> if this bimap contains no key-value mappings
|
||||
*/
|
||||
public
|
||||
boolean isEmpty() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return forwardREF.get(this)
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a {@link Collection} view of the values contained in this map.
|
||||
* The collection is backed by the map, so changes to the map are
|
||||
* reflected in the collection, and vice-versa. If the map is
|
||||
* modified while an iteration over the collection is in progress
|
||||
* (except through the iterator's own <tt>remove</tt> operation),
|
||||
* the results of the iteration are undefined. The collection
|
||||
* supports element removal, which removes the corresponding
|
||||
* mapping from the map, via the <tt>Iterator.remove</tt>,
|
||||
* <tt>Collection.remove</tt>, <tt>removeAll</tt>,
|
||||
* <tt>retainAll</tt> and <tt>clear</tt> operations. It does not
|
||||
* support the <tt>add</tt> or <tt>addAll</tt> operations.
|
||||
*
|
||||
* @return a view of the values contained in this map
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Collection<K> reverseValues() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return reverseREF.get(this).values();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
/**
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
* <p>
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
* <p>
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
* <p>
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*/
|
||||
public final
|
||||
class LockFreeHashMap<K, V> implements Map<K, V>, Cloneable, Serializable {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeHashMap, HashMap> mapREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
LockFreeHashMap.class,
|
||||
HashMap.class,
|
||||
"hashMap");
|
||||
|
||||
private volatile HashMap<K, V> hashMap;
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
|
||||
* (16) and the default load factor (0.75).
|
||||
*/
|
||||
public
|
||||
LockFreeHashMap() {
|
||||
hashMap = new HashMap<K, V>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>HashMap</tt> with the specified initial
|
||||
* capacity and the default load factor (0.75).
|
||||
*
|
||||
* @param initialCapacity the initial capacity.
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is negative.
|
||||
*/
|
||||
public
|
||||
LockFreeHashMap(int initialCapacity) {
|
||||
hashMap = new HashMap<K, V>(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>HashMap</tt> with the same mappings as the
|
||||
* specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
|
||||
* default load factor (0.75) and an initial capacity sufficient to
|
||||
* hold the mappings in the specified <tt>Map</tt>.
|
||||
*
|
||||
* @param map the map whose mappings are to be placed in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*/
|
||||
public
|
||||
LockFreeHashMap(Map<K, V> map) {
|
||||
this.hashMap = new HashMap<K, V>(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>HashMap</tt> with the specified initial
|
||||
* capacity and load factor.
|
||||
*
|
||||
* @param initialCapacity the initial capacity
|
||||
* @param loadFactor the load factor
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is negative
|
||||
* or the load factor is nonpositive
|
||||
*/
|
||||
public
|
||||
LockFreeHashMap(int initialCapacity, float loadFactor) {
|
||||
this.hashMap = new HashMap<K, V>(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Map<K, V> getMap() {
|
||||
// use the SWP to get a lock-free get of the map. It's values are only valid at the moment this method is called.
|
||||
return Collections.unmodifiableMap(mapREF.get(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int size() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean isEmpty() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean containsKey(final Object key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean containsValue(final Object value) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.containsValue(value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public
|
||||
V get(final Object key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return (V) mapREF.get(this)
|
||||
.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
V put(final K key, final V value) {
|
||||
return hashMap.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
V remove(final Object key) {
|
||||
return hashMap.remove(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("Java8CollectionRemoveIf")
|
||||
public synchronized
|
||||
void removeAllValues(final V value) {
|
||||
for (Iterator<Entry<K, V>> iterator = hashMap.entrySet().iterator(); iterator.hasNext(); ) {
|
||||
final Map.Entry<K, V> kvEntry = iterator.next();
|
||||
if (kvEntry.getValue().equals(value)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
void putAll(final Map<? extends K, ? extends V> map) {
|
||||
this.hashMap.putAll(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* This uses equals to update values. At first glance, this seems like a waste (since if it's equal, why update it?). This is because
|
||||
* the ONLY location this is used (in the Database, for updating all DeviceUser in the map), equals compares ONLY the DB ID. In only
|
||||
* this situation, this makes sense (since anything with the same DB ID, we should replace/update the value)
|
||||
*/
|
||||
public synchronized
|
||||
void updateAllWithValue(final V value) {
|
||||
for (Map.Entry<K, V> entry : hashMap.entrySet()) {
|
||||
if (value.equals(entry.getValue())) {
|
||||
// get's all device IDs that have this user assigned, and reassign the value
|
||||
entry.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized
|
||||
void replaceAll(Map<K,V> hashMap) {
|
||||
this.hashMap.clear();
|
||||
this.hashMap.putAll(hashMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
void clear() {
|
||||
hashMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Set<K> keySet() {
|
||||
return getMap().keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Collection<V> values() {
|
||||
return getMap().values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Set<Entry<K, V>> entrySet() {
|
||||
return getMap().entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
return mapREF.get(this)
|
||||
.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return mapREF.get(this)
|
||||
.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return mapREF.get(this)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Collection<K> keys() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this).keySet();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Map<K,V> elements() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
HashMap<K, V> backingMap() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
|
||||
/**
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
*
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
*
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
*
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*/
|
||||
public final
|
||||
class LockFreeHashSet<E> implements Set<E>, Cloneable, Serializable {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeHashSet, Set> setREF =
|
||||
AtomicReferenceFieldUpdater.newUpdater(LockFreeHashSet.class,
|
||||
Set.class,
|
||||
"hashSet");
|
||||
private volatile Set<E> hashSet = new HashSet<>();
|
||||
|
||||
|
||||
public
|
||||
LockFreeHashSet(){}
|
||||
|
||||
|
||||
public
|
||||
LockFreeHashSet(Collection<E> elements) {
|
||||
hashSet.addAll(elements);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
public synchronized
|
||||
void clear() {
|
||||
hashSet.clear();
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
public synchronized
|
||||
boolean add(final E element) {
|
||||
return hashSet.add(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean containsAll(final Collection<?> collection) {
|
||||
return setREF.get(this).containsAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
boolean retainAll(final Collection<?> collection) {
|
||||
return hashSet.retainAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
boolean removeAll(final Collection<?> collection) {
|
||||
return hashSet.removeAll(collection);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
public synchronized
|
||||
boolean addAll(final Collection<? extends E> collection) {
|
||||
return hashSet.addAll(collection);
|
||||
}
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
public synchronized
|
||||
boolean remove(final Object element) {
|
||||
return hashSet.remove(element);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
boolean contains(final Object element) {
|
||||
return setREF.get(this).contains(element);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Set<E> elements() {
|
||||
return setREF.get(this);
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
public
|
||||
int size() {
|
||||
return setREF.get(this).size();
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
boolean isEmpty() {
|
||||
return setREF.get(this).isEmpty();
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
Iterator<E> iterator() {
|
||||
//noinspection unchecked
|
||||
return setREF.get(this).iterator();
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
Object[] toArray() {
|
||||
return setREF.get(this).toArray();
|
||||
}
|
||||
|
||||
// lock-free get
|
||||
@Override
|
||||
public
|
||||
<T> T[] toArray(final T[] targetArray) {
|
||||
//noinspection unchecked
|
||||
return (T[]) setREF.get(this).toArray(targetArray);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,438 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import static dorkbox.collections.IntMap.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
/**
|
||||
* A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as that of its keys. This constraint
|
||||
* enables bimaps to support an "inverse view", which is another bimap containing the same entries as this bimap but with reversed keys and values.
|
||||
*
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
*
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
*
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
*
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*/
|
||||
public
|
||||
class LockFreeIntBiMap<V> {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeIntBiMap, IntMap> forwardREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
LockFreeIntBiMap.class,
|
||||
IntMap.class,
|
||||
"forwardHashMap");
|
||||
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeIntBiMap, ObjectIntMap> reverseREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
LockFreeIntBiMap.class,
|
||||
ObjectIntMap.class,
|
||||
"reverseHashMap");
|
||||
|
||||
private volatile IntMap<V> forwardHashMap;
|
||||
private volatile ObjectIntMap<V> reverseHashMap;
|
||||
|
||||
private final int defaultReturnValue;
|
||||
private final LockFreeObjectIntBiMap<V> inverse;
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
|
||||
// public static
|
||||
// void main(String[] args) {
|
||||
// LockFreeIntBiMap<String> test = new LockFreeIntBiMap<String>();
|
||||
// String one = "One";
|
||||
// String four = "Four";
|
||||
//
|
||||
// test.put(1, one);
|
||||
// test.put(2, "Two");
|
||||
// test.put(3, "Three");
|
||||
// test.put(4, four);
|
||||
// // try {
|
||||
// // test.put(1, four);
|
||||
// // } catch (IllegalArgumentException e) {
|
||||
// // }
|
||||
// test.putForce(1, four);
|
||||
// test.put(5, one);
|
||||
//
|
||||
// System.out.println(test.toString());
|
||||
//
|
||||
// System.out.println("Reverse");
|
||||
// System.out.println(test.inverse().toString());
|
||||
// }
|
||||
|
||||
/**
|
||||
* Creates a new bimap using @{link Integer#MIN_VALUE}.
|
||||
*/
|
||||
public
|
||||
LockFreeIntBiMap() {
|
||||
this(Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default return value is used for various get/put operations on the IntMap/ObjectIntMap.
|
||||
*
|
||||
* @param defaultReturnValue value used for various get/put operations on the IntMap/ObjectIntMap.
|
||||
*/
|
||||
public
|
||||
LockFreeIntBiMap(int defaultReturnValue) {
|
||||
this(new IntMap<V>(), new ObjectIntMap<V>(), defaultReturnValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default return value is used for various get/put operations on the IntMap/ObjectIntMap.
|
||||
*
|
||||
* @param defaultReturnValue value used for various get/put operations on the IntMap/ObjectIntMap.
|
||||
*/
|
||||
public
|
||||
LockFreeIntBiMap(IntMap<V> forwardHashMap, ObjectIntMap<V> reverseHashMap, int defaultReturnValue) {
|
||||
this.forwardHashMap = forwardHashMap;
|
||||
this.reverseHashMap = reverseHashMap;
|
||||
this.defaultReturnValue = defaultReturnValue;
|
||||
|
||||
this.inverse = new LockFreeObjectIntBiMap<V>(reverseHashMap, forwardHashMap, defaultReturnValue, this);
|
||||
}
|
||||
|
||||
LockFreeIntBiMap(final IntMap<V> forwardHashMap,
|
||||
final ObjectIntMap<V> reverseHashMap,
|
||||
final int defaultReturnValue,
|
||||
final LockFreeObjectIntBiMap<V> inverse) {
|
||||
|
||||
this.forwardHashMap = forwardHashMap;
|
||||
this.reverseHashMap = reverseHashMap;
|
||||
this.defaultReturnValue = defaultReturnValue;
|
||||
this.inverse = inverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all of the mappings from this bimap.
|
||||
* The bimap will be empty after this call returns.
|
||||
*/
|
||||
public synchronized
|
||||
void clear() {
|
||||
forwardHashMap.clear();
|
||||
reverseHashMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the inverse view of this bimap, which maps each of this bimap's values to its associated key.
|
||||
*/
|
||||
public
|
||||
LockFreeObjectIntBiMap<V> inverse() {
|
||||
return inverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified key in this bimap.
|
||||
* If the bimap previously contained a mapping for the key, the old
|
||||
* value is replaced. If the given value is already bound to a different
|
||||
* key in this bimap, the bimap will remain unmodified. To avoid throwing
|
||||
* an exception, call {@link #putForce(int, Object)} putForce(K, V) instead.
|
||||
*
|
||||
* @param key key with which the specified value is to be associated
|
||||
* @param value value to be associated with the specified key
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>null</tt> return can also indicate that the map
|
||||
* previously associated <tt>null</tt> with <tt>key</tt>.)
|
||||
*
|
||||
* @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain
|
||||
* unmodified in this event. To avoid this exception, call {@link #putForce(int, Object)} putForce(K, V) instead.
|
||||
*/
|
||||
public synchronized
|
||||
V put(final int key, final V value) throws IllegalArgumentException {
|
||||
V prevForwardValue = this.forwardHashMap.put(key, value);
|
||||
if (prevForwardValue != null) {
|
||||
reverseHashMap.remove(prevForwardValue, defaultReturnValue);
|
||||
}
|
||||
|
||||
int prevReverseValue = this.reverseHashMap.get(value, defaultReturnValue);
|
||||
this.reverseHashMap.put(value, key);
|
||||
if (prevReverseValue != defaultReturnValue) {
|
||||
// put the old value back
|
||||
if (prevForwardValue != null) {
|
||||
this.forwardHashMap.put(key, prevForwardValue);
|
||||
}
|
||||
else {
|
||||
this.forwardHashMap.remove(key);
|
||||
}
|
||||
|
||||
this.reverseHashMap.put(value, prevReverseValue);
|
||||
|
||||
throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!");
|
||||
}
|
||||
|
||||
return prevForwardValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified key in this bimap.
|
||||
* If the bimap previously contained a mapping for the key, the old
|
||||
* value is replaced. This is an alternate form of {@link #put(int, Object)}
|
||||
* that will silently ignore duplicates
|
||||
*
|
||||
* @param key key with which the specified value is to be associated
|
||||
* @param value value to be associated with the specified key
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>null</tt> return can also indicate that the map
|
||||
* previously associated <tt>null</tt> with <tt>key</tt>.)
|
||||
*/
|
||||
public synchronized
|
||||
V putForce(final int key, final V value) {
|
||||
V prevForwardValue = this.forwardHashMap.put(key, value);
|
||||
if (prevForwardValue != null) {
|
||||
reverseHashMap.remove(prevForwardValue, defaultReturnValue);
|
||||
}
|
||||
|
||||
|
||||
int prevReverseValue = this.reverseHashMap.get(value, defaultReturnValue);
|
||||
this.reverseHashMap.put(value, key);
|
||||
|
||||
if (prevReverseValue != defaultReturnValue) {
|
||||
forwardHashMap.remove(prevReverseValue);
|
||||
}
|
||||
|
||||
return prevForwardValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the specified map to this map.
|
||||
* These mappings will replace any mappings that this map had for
|
||||
* any of the keys currently in the specified map.
|
||||
*
|
||||
* @param hashMap mappings to be stored in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*
|
||||
* @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain
|
||||
* unmodified in this event. To avoid this exception, call {@link #putAllForce(Map)} instead.
|
||||
*/
|
||||
public synchronized
|
||||
void putAll(final Map<Integer, V> hashMap) throws IllegalArgumentException {
|
||||
LockFreeIntBiMap<V> biMap = new LockFreeIntBiMap<V>();
|
||||
|
||||
try {
|
||||
for (Map.Entry<Integer, V> entry : hashMap.entrySet()) {
|
||||
Integer key = entry.getKey();
|
||||
V value = entry.getValue();
|
||||
|
||||
biMap.put(key, value);
|
||||
|
||||
// we have to verify that the keys/values between the bimaps are unique
|
||||
if (this.forwardHashMap.containsKey(key)) {
|
||||
throw new IllegalArgumentException("Key already exists. Keys and values must both be unique!");
|
||||
}
|
||||
|
||||
if (this.reverseHashMap.containsKey(value)) {
|
||||
throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!");
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// do nothing if there is an exception
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
// we have checked to make sure that the bimap is unique, AND have checked that we don't already have any of the key/values in ourselves
|
||||
this.forwardHashMap.putAll(biMap.forwardHashMap);
|
||||
this.reverseHashMap.putAll(biMap.reverseHashMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the specified map to this map.
|
||||
* These mappings will replace any mappings that this map had for
|
||||
* any of the keys currently in the specified map. This is an alternate
|
||||
* form of {@link #putAll(Map)} putAll(K, V) that will silently
|
||||
* ignore duplicates
|
||||
*
|
||||
* @param hashMap mappings to be stored in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*/
|
||||
public synchronized
|
||||
void putAllForce(final Map<Integer, V> hashMap) {
|
||||
for (Map.Entry<Integer, V> entry : hashMap.entrySet()) {
|
||||
Integer key = entry.getKey();
|
||||
V value = entry.getValue();
|
||||
|
||||
putForce(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for the specified key from this map if present.
|
||||
*
|
||||
* @param key key whose mapping is to be removed from the map
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>null</tt> return can also indicate that the map
|
||||
* previously associated <tt>null</tt> with <tt>key</tt>.)
|
||||
*/
|
||||
public synchronized
|
||||
V remove(final int key) {
|
||||
V value = forwardHashMap.remove(key);
|
||||
if (value != null) {
|
||||
reverseHashMap.remove(value, defaultReturnValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped,
|
||||
* or {@code null} if this map contains no mapping for the key.
|
||||
* <p>
|
||||
* <p>More formally, if this map contains a mapping from a key
|
||||
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
|
||||
* key.equals(k))}, then this method returns {@code v}; otherwise
|
||||
* it returns {@code null}. (There can be at most one such mapping.)
|
||||
* <p>
|
||||
* <p>A return value of {@code null} does not <i>necessarily</i>
|
||||
* indicate that the map contains no mapping for the key; it's also
|
||||
* possible that the map explicitly maps the key to {@code null}.
|
||||
* The {@link HashMap#containsKey containsKey} operation may be used to
|
||||
* distinguish these two cases.
|
||||
*
|
||||
* @see #put(int, Object)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
V get(final int key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return (V) forwardREF.get(this).get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <tt>true</tt> if this bimap contains no key-value mappings.
|
||||
*
|
||||
* @return <tt>true</tt> if this bimap contains no key-value mappings
|
||||
*/
|
||||
public
|
||||
boolean isEmpty() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return forwardREF.get(this)
|
||||
.size == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value mappings in this map. If the
|
||||
* map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
|
||||
* <tt>Integer.MAX_VALUE</tt>.
|
||||
*
|
||||
* @return the number of key-value mappings in this map
|
||||
*/
|
||||
public
|
||||
int size() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return forwardREF.get(this)
|
||||
.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
public
|
||||
Keys keys() {
|
||||
return forwardREF.get(this)
|
||||
.keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Values<V> values() {
|
||||
return forwardREF.get(this)
|
||||
.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
public
|
||||
Entries entries() {
|
||||
return forwardREF.get(this)
|
||||
.entries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity equals only!
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
return this == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
int result = forwardREF.get(this).hashCode();
|
||||
result = 31 * result + reverseREF.get(this).hashCode();
|
||||
result = 31 * result + defaultReturnValue;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
StringBuilder builder = new StringBuilder("LockFreeIntBiMap {");
|
||||
|
||||
Keys keys = keys();
|
||||
Iterator<V> values = values();
|
||||
|
||||
while (keys.hasNext) {
|
||||
builder.append(keys.next());
|
||||
builder.append(" (")
|
||||
.append(values.next())
|
||||
.append("), ");
|
||||
}
|
||||
|
||||
int length = builder.length();
|
||||
if (length > 1) {
|
||||
// delete the ', '
|
||||
builder.delete(length - 2, length);
|
||||
}
|
||||
|
||||
builder.append('}');
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
import dorkbox.collections.IntMap.*;
|
||||
|
||||
|
||||
/**
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
* <p>
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
* <p>
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
* <p>
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*
|
||||
* This is an unordered map that uses int keys. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small stash
|
||||
* for problematic keys. Null values are allowed. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.
|
||||
* @author Nathan Sweet
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final
|
||||
class LockFreeIntMap<V> implements Cloneable, Serializable {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeIntMap, IntMap> mapREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
LockFreeIntMap.class,
|
||||
IntMap.class,
|
||||
"map");
|
||||
|
||||
private volatile IntMap<V> map;
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>IntMap</tt> with the default initial capacity
|
||||
* (16) and the default load factor (0.75).
|
||||
*/
|
||||
public
|
||||
LockFreeIntMap() {
|
||||
map = new IntMap<V>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>IntMap</tt> with the specified initial
|
||||
* capacity and the default load factor (0.75).
|
||||
*
|
||||
* @param initialCapacity the initial capacity.
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is negative.
|
||||
*/
|
||||
public
|
||||
LockFreeIntMap(int initialCapacity) {
|
||||
map = new IntMap<V>(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>IntMap</tt> with the specified initial
|
||||
* capacity and load factor.
|
||||
*
|
||||
* @param initialCapacity the initial capacity
|
||||
* @param loadFactor the load factor
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is negative
|
||||
* or the load factor is nonpositive
|
||||
*/
|
||||
public
|
||||
LockFreeIntMap(int initialCapacity, float loadFactor) {
|
||||
this.map = new IntMap<V>(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
|
||||
public
|
||||
int size() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.size;
|
||||
}
|
||||
|
||||
public
|
||||
boolean isEmpty() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.size == 0;
|
||||
}
|
||||
|
||||
public
|
||||
boolean containsKey(final int key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
|
||||
* an expensive operation.
|
||||
*
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}.
|
||||
*/
|
||||
public
|
||||
boolean containsValue(final Object value, boolean identity) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.containsValue(value, identity);
|
||||
}
|
||||
|
||||
public
|
||||
V get(final int key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return (V) mapREF.get(this)
|
||||
.get(key);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
V put(final int key, final V value) {
|
||||
return map.put(key, value);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
V remove(final int key) {
|
||||
return map.remove(key);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
void putAll(final IntMap<V> map) {
|
||||
this.map.putAll(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
public
|
||||
Keys keys() {
|
||||
return mapREF.get(this)
|
||||
.keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
public
|
||||
Values<V> values() {
|
||||
return mapREF.get(this)
|
||||
.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
public
|
||||
Entries entries() {
|
||||
return mapREF.get(this)
|
||||
.entries();
|
||||
}
|
||||
|
||||
public synchronized
|
||||
void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity equals only!
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
return this == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return mapREF.get(this)
|
||||
.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return mapREF.get(this)
|
||||
.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package dorkbox.collections;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
/**
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
* <p>
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
* <p>
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
* <p>
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*
|
||||
* This is an unordered map that uses int keys. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small stash
|
||||
* for problematic keys. Null values are allowed. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
class LockFreeIntStringMap<V> {
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeIntStringMap, IntMap> mapREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
LockFreeIntStringMap.class,
|
||||
IntMap.class,
|
||||
"map");
|
||||
|
||||
private volatile IntMap<V> map;
|
||||
|
||||
|
||||
public LockFreeIntStringMap() {
|
||||
this.map = new IntMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>IntMap</tt> with the specified initial
|
||||
* capacity and the default load factor (0.75).
|
||||
*
|
||||
* @param initialCapacity the initial capacity.
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is negative.
|
||||
*/
|
||||
public
|
||||
LockFreeIntStringMap(int initialCapacity) {
|
||||
map = new IntMap<>(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>IntMap</tt> with the specified initial
|
||||
* capacity and load factor.
|
||||
*
|
||||
* @param initialCapacity the initial capacity
|
||||
* @param loadFactor the load factor
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is negative
|
||||
* or the load factor is nonpositive
|
||||
*/
|
||||
public
|
||||
LockFreeIntStringMap(int initialCapacity, float loadFactor) {
|
||||
this.map = new IntMap(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
|
||||
public
|
||||
int size() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.size;
|
||||
}
|
||||
|
||||
public
|
||||
boolean isEmpty() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.size == 0;
|
||||
}
|
||||
|
||||
public
|
||||
boolean containsKey(final int key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.containsKey(key);
|
||||
}
|
||||
|
||||
public
|
||||
boolean containsKey(final String key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.containsKey(key.hashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
|
||||
* an expensive operation.
|
||||
*
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}.
|
||||
*/
|
||||
public
|
||||
boolean containsValue(final Object value, boolean identity) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.containsValue(value, identity);
|
||||
}
|
||||
|
||||
public
|
||||
V get(final int key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return (V) mapREF.get(this)
|
||||
.get(key);
|
||||
}
|
||||
|
||||
public
|
||||
V get(final String key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return (V) mapREF.get(this)
|
||||
.get(key.hashCode());
|
||||
}
|
||||
|
||||
public synchronized
|
||||
V put(final int key, final V value) {
|
||||
return map.put(key, value);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
V put(final String key, final V value) {
|
||||
return map.put(key.hashCode(), value);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
V remove(final int key) {
|
||||
return map.remove(key);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
V remove(final String key) {
|
||||
return map.remove(key.hashCode());
|
||||
}
|
||||
|
||||
public synchronized
|
||||
void putAll(final IntMap<V> map) {
|
||||
this.map.putAll(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link IntMap.Entries} constructor for nested or multi-threaded iteration.
|
||||
*/
|
||||
public
|
||||
IntMap.Keys keys() {
|
||||
return mapREF.get(this)
|
||||
.keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link IntMap.Entries} constructor for nested or multi-threaded iteration.
|
||||
*/
|
||||
public
|
||||
IntMap.Values<V> values() {
|
||||
return mapREF.get(this)
|
||||
.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link IntMap.Entries} constructor for nested or multi-threaded iteration.
|
||||
*/
|
||||
public
|
||||
IntMap.Entries<V> entries() {
|
||||
return mapREF.get(this)
|
||||
.entries();
|
||||
}
|
||||
|
||||
public synchronized
|
||||
void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity equals only!
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
return this == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return mapREF.get(this)
|
||||
.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return mapREF.get(this)
|
||||
.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,400 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
import dorkbox.collections.IntMap.*;
|
||||
|
||||
/**
|
||||
* A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as that of its keys. This constraint
|
||||
* enables bimaps to support an "inverse view", which is another bimap containing the same entries as this bimap but with reversed keys and values.
|
||||
*
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
*
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
*
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
*
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*/
|
||||
public
|
||||
class LockFreeObjectIntBiMap<V> {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeObjectIntBiMap, ObjectIntMap> forwardREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
LockFreeObjectIntBiMap.class,
|
||||
ObjectIntMap.class,
|
||||
"forwardHashMap");
|
||||
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeObjectIntBiMap, IntMap> reverseREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
LockFreeObjectIntBiMap.class,
|
||||
IntMap.class,
|
||||
"reverseHashMap");
|
||||
|
||||
private volatile ObjectIntMap<V> forwardHashMap;
|
||||
private volatile IntMap<V> reverseHashMap;
|
||||
|
||||
private final int defaultReturnValue;
|
||||
private final LockFreeIntBiMap<V> inverse;
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
|
||||
/**
|
||||
* Creates a new bimap using @{link Integer#MIN_VALUE}.
|
||||
*/
|
||||
public
|
||||
LockFreeObjectIntBiMap() {
|
||||
this(Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default return value is used for various get/put operations on the IntMap/ObjectIntMap.
|
||||
*
|
||||
* @param defaultReturnValue value used for various get/put operations on the IntMap/ObjectIntMap.
|
||||
*/
|
||||
public
|
||||
LockFreeObjectIntBiMap(int defaultReturnValue) {
|
||||
this(new ObjectIntMap<V>(), new IntMap<V>(), defaultReturnValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default return value is used for various get/put operations on the IntMap/ObjectIntMap.
|
||||
*
|
||||
* @param defaultReturnValue value used for various get/put operations on the IntMap/ObjectIntMap.
|
||||
*/
|
||||
LockFreeObjectIntBiMap(ObjectIntMap<V> forwardHashMap, IntMap<V> reverseHashMap, int defaultReturnValue) {
|
||||
this.forwardHashMap = forwardHashMap;
|
||||
this.reverseHashMap = reverseHashMap;
|
||||
this.defaultReturnValue = defaultReturnValue;
|
||||
|
||||
this.inverse = new LockFreeIntBiMap<V>(reverseHashMap, forwardHashMap, defaultReturnValue, this);
|
||||
}
|
||||
|
||||
LockFreeObjectIntBiMap(final ObjectIntMap<V> forwardHashMap,
|
||||
final IntMap<V> reverseHashMap,
|
||||
final int defaultReturnValue,
|
||||
final LockFreeIntBiMap<V> inverse) {
|
||||
|
||||
this.forwardHashMap = forwardHashMap;
|
||||
this.reverseHashMap = reverseHashMap;
|
||||
this.defaultReturnValue = defaultReturnValue;
|
||||
this.inverse = inverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all of the mappings from this bimap.
|
||||
*
|
||||
* The bimap will be empty after this call returns.
|
||||
*/
|
||||
public synchronized
|
||||
void clear() {
|
||||
forwardHashMap.clear();
|
||||
reverseHashMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the inverse view of this bimap, which maps each of this bimap's values to its associated key.
|
||||
*/
|
||||
public
|
||||
LockFreeIntBiMap<V> inverse() {
|
||||
return inverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified key in this bimap.
|
||||
* If the bimap previously contained a mapping for the key, the old
|
||||
* value is replaced. If the given value is already bound to a different
|
||||
* key in this bimap, the bimap will remain unmodified. To avoid throwing
|
||||
* an exception, call {@link #putForce(Object, int)} instead.
|
||||
*
|
||||
* @param key key with which the specified value is to be associated
|
||||
* @param value value to be associated with the specified key
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>null</tt> return can also indicate that the map
|
||||
* previously associated <tt>null</tt> with <tt>key</tt>.)
|
||||
*
|
||||
* @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain
|
||||
* unmodified in this event. To avoid this exception, call {@link #putForce(Object, int)} instead.
|
||||
*/
|
||||
public synchronized
|
||||
int put(final V key, final int value) throws IllegalArgumentException {
|
||||
int prevForwardValue = this.forwardHashMap.get(key, defaultReturnValue);
|
||||
this.forwardHashMap.put(key, value);
|
||||
if (prevForwardValue != defaultReturnValue) {
|
||||
reverseHashMap.remove(prevForwardValue);
|
||||
}
|
||||
|
||||
V prevReverseValue = this.reverseHashMap.put(value, key);
|
||||
if (prevReverseValue != null) {
|
||||
// put the old value back
|
||||
if (prevForwardValue != defaultReturnValue) {
|
||||
this.forwardHashMap.put(key, prevForwardValue);
|
||||
}
|
||||
else {
|
||||
this.forwardHashMap.remove(key, defaultReturnValue);
|
||||
}
|
||||
|
||||
this.reverseHashMap.put(value, prevReverseValue);
|
||||
|
||||
throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!");
|
||||
}
|
||||
|
||||
return prevForwardValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified key in this bimap.
|
||||
* If the bimap previously contained a mapping for the key, the old
|
||||
* value is replaced. This is an alternate form of {@link #put(Object, int)}
|
||||
* that will silently ignore duplicates
|
||||
*
|
||||
* @param key key with which the specified value is to be associated
|
||||
* @param value value to be associated with the specified key
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>null</tt> return can also indicate that the map
|
||||
* previously associated <tt>null</tt> with <tt>key</tt>.)
|
||||
*/
|
||||
public synchronized
|
||||
int putForce(final V key, final int value) {
|
||||
int prevForwardValue = this.forwardHashMap.get(key, defaultReturnValue);
|
||||
this.forwardHashMap.put(key, value);
|
||||
if (prevForwardValue != defaultReturnValue) {
|
||||
reverseHashMap.remove(prevForwardValue);
|
||||
}
|
||||
|
||||
|
||||
V prevReverseValue = this.reverseHashMap.get(value);
|
||||
this.reverseHashMap.put(value, key);
|
||||
|
||||
if (prevReverseValue != null) {
|
||||
forwardHashMap.remove(prevReverseValue, defaultReturnValue);
|
||||
}
|
||||
|
||||
return prevForwardValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the specified map to this map.
|
||||
* These mappings will replace any mappings that this map had for
|
||||
* any of the keys currently in the specified map.
|
||||
*
|
||||
* @param hashMap mappings to be stored in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*
|
||||
* @throws IllegalArgumentException if the given value is already bound to a different key in this bimap. The bimap will remain
|
||||
* unmodified in this event. To avoid this exception, call {@link #putAllForce(Map)} instead.
|
||||
*/
|
||||
public synchronized
|
||||
void putAll(final Map<V, Integer> hashMap) throws IllegalArgumentException {
|
||||
LockFreeObjectIntBiMap<V> biMap = new LockFreeObjectIntBiMap<V>();
|
||||
|
||||
try {
|
||||
for (Map.Entry<V, Integer> entry : hashMap.entrySet()) {
|
||||
V key = entry.getKey();
|
||||
Integer value = entry.getValue();
|
||||
|
||||
biMap.put(key, value);
|
||||
|
||||
// we have to verify that the keys/values between the bimaps are unique
|
||||
if (this.forwardHashMap.containsKey(key)) {
|
||||
throw new IllegalArgumentException("Key already exists. Keys and values must both be unique!");
|
||||
}
|
||||
|
||||
if (this.reverseHashMap.containsKey(value)) {
|
||||
throw new IllegalArgumentException("Value already exists. Keys and values must both be unique!");
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// do nothing if there is an exception
|
||||
throw e;
|
||||
}
|
||||
|
||||
// we have checked to make sure that the bimap is unique, AND have checked that we don't already have any of the key/values in ourselves
|
||||
this.forwardHashMap.putAll(biMap.forwardHashMap);
|
||||
this.reverseHashMap.putAll(biMap.reverseHashMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the specified map to this map.
|
||||
* These mappings will replace any mappings that this map had for
|
||||
* any of the keys currently in the specified map. This is an alternate
|
||||
* form of {@link #putAll(Map)} putAll(K, V) that will silently
|
||||
* ignore duplicates
|
||||
*
|
||||
* @param hashMap mappings to be stored in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*/
|
||||
public synchronized
|
||||
void putAllForce(final Map<V, Integer> hashMap) {
|
||||
for (Map.Entry<V, Integer> entry : hashMap.entrySet()) {
|
||||
V key = entry.getKey();
|
||||
Integer value = entry.getValue();
|
||||
|
||||
putForce(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for the specified key from this map if present.
|
||||
*
|
||||
* @param key key whose mapping is to be removed from the map
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>defaultReturnValue</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>defaultReturnValue</tt> return can also indicate that the map
|
||||
* previously associated <tt>defaultReturnValue</tt> with <tt>key</tt>.)
|
||||
*/
|
||||
public synchronized
|
||||
int remove(final V key) {
|
||||
int value = forwardHashMap.remove(key, defaultReturnValue);
|
||||
if (value != defaultReturnValue) {
|
||||
reverseHashMap.remove(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped,
|
||||
* or {@code defaultReturnValue} if this map contains no mapping for the key.
|
||||
* <p>
|
||||
* <p>More formally, if this map contains a mapping from a key
|
||||
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
|
||||
* key.equals(k))}, then this method returns {@code v}; otherwise
|
||||
* it returns {@code defaultReturnValue}. (There can be at most one such mapping.)
|
||||
* <p>
|
||||
* <p>A return value of {@code defaultReturnValue} does not <i>necessarily</i>
|
||||
* indicate that the map contains no mapping for the key; it's also
|
||||
* possible that the map explicitly maps the key to {@code null}.
|
||||
* The {@link HashMap#containsKey containsKey} operation may be used to
|
||||
* distinguish these two cases.
|
||||
*
|
||||
* @see #put(Object, int)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
int get(final V key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return forwardREF.get(this).get(key, defaultReturnValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Iterator<V> keys() {
|
||||
// the ObjectIntMap doesn't have iterators, but the IntMap does
|
||||
return inverse.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Keys values() {
|
||||
// the ObjectIntMap doesn't have iterators, but the IntMap does
|
||||
return inverse.keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <tt>true</tt> if this bimap contains no key-value mappings.
|
||||
*
|
||||
* @return <tt>true</tt> if this bimap contains no key-value mappings
|
||||
*/
|
||||
public
|
||||
boolean isEmpty() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return forwardREF.get(this)
|
||||
.size == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value mappings in this map. If the
|
||||
* map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
|
||||
* <tt>Integer.MAX_VALUE</tt>.
|
||||
*
|
||||
* @return the number of key-value mappings in this map
|
||||
*/
|
||||
public
|
||||
int size() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return forwardREF.get(this)
|
||||
.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity equals only!
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
return this == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
int result = forwardREF.get(this).hashCode();
|
||||
result = 31 * result + reverseREF.get(this).hashCode();
|
||||
result = 31 * result + defaultReturnValue;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
StringBuilder builder = new StringBuilder("LockFreeObjectIntBiMap {");
|
||||
|
||||
Iterator<V> keys = keys();
|
||||
Keys values = values();
|
||||
|
||||
while (keys.hasNext()) {
|
||||
builder.append(keys.next());
|
||||
builder.append(" (")
|
||||
.append(values.next())
|
||||
.append("), ");
|
||||
}
|
||||
|
||||
int length = builder.length();
|
||||
if (length > 1) {
|
||||
// delete the ', '
|
||||
builder.delete(length - 2, length);
|
||||
}
|
||||
|
||||
builder.append('}');
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
/**
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
*
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
*
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
*
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*/
|
||||
public
|
||||
class LockFreeObjectIntMap<V> {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeObjectIntMap, ObjectIntMap> mapREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
LockFreeObjectIntMap.class,
|
||||
ObjectIntMap.class,
|
||||
"map");
|
||||
|
||||
private volatile ObjectIntMap<V> map;
|
||||
|
||||
private final int defaultReturnValue;
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
|
||||
/**
|
||||
* Creates a new map using @{link Integer#MIN_VALUE}.
|
||||
*/
|
||||
public
|
||||
LockFreeObjectIntMap() {
|
||||
this(Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default return value is used for various get/put operations on the ObjectIntMap.
|
||||
*
|
||||
* @param defaultReturnValue value used for various get/put operations on the ObjectIntMap.
|
||||
*/
|
||||
public
|
||||
LockFreeObjectIntMap(int defaultReturnValue) {
|
||||
this(new ObjectIntMap<V>(), defaultReturnValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default return value is used for various get/put operations on the ObjectIntMap.
|
||||
*
|
||||
* @param defaultReturnValue value used for various get/put operations on the ObjectIntMap.
|
||||
*/
|
||||
LockFreeObjectIntMap(ObjectIntMap<V> forwardHashMap, int defaultReturnValue) {
|
||||
this.map = forwardHashMap;
|
||||
this.defaultReturnValue = defaultReturnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all of the mappings from this map.
|
||||
*
|
||||
* The map will be empty after this call returns.
|
||||
*/
|
||||
public synchronized
|
||||
void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
public synchronized
|
||||
int put(final V key, final int value) {
|
||||
int prevForwardValue = this.map.get(key, defaultReturnValue);
|
||||
this.map.put(key, value);
|
||||
|
||||
return prevForwardValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the specified map to this map.
|
||||
* These mappings will replace any mappings that this map had for
|
||||
* any of the keys currently in the specified map.
|
||||
*
|
||||
* @param hashMap mappings to be stored in this map
|
||||
*
|
||||
* @throws NullPointerException if the specified map is null
|
||||
*/
|
||||
public synchronized
|
||||
void putAll(final Map<V, Integer> hashMap) throws IllegalArgumentException {
|
||||
try {
|
||||
ObjectIntMap<V> map = this.map;
|
||||
for (Map.Entry<V, Integer> entry : hashMap.entrySet()) {
|
||||
V key = entry.getKey();
|
||||
Integer value = entry.getValue();
|
||||
|
||||
map.put(key, value);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// do nothing if there is an exception
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for the specified key from this map if present.
|
||||
*
|
||||
* @param key key whose mapping is to be removed from the map
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>defaultReturnValue</tt> if there was no mapping for <tt>key</tt>.
|
||||
* (A <tt>defaultReturnValue</tt> return can also indicate that the map
|
||||
* previously associated <tt>defaultReturnValue</tt> with <tt>key</tt>.)
|
||||
*/
|
||||
public synchronized
|
||||
int remove(final V key) {
|
||||
int value = map.remove(key, defaultReturnValue);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped,
|
||||
* or {@code defaultReturnValue} if this map contains no mapping for the key.
|
||||
* <p>
|
||||
* <p>More formally, if this map contains a mapping from a key
|
||||
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
|
||||
* key.equals(k))}, then this method returns {@code v}; otherwise
|
||||
* it returns {@code defaultReturnValue}. (There can be at most one such mapping.)
|
||||
* <p>
|
||||
* <p>A return value of {@code defaultReturnValue} does not <i>necessarily</i>
|
||||
* indicate that the map contains no mapping for the key; it's also
|
||||
* possible that the map explicitly maps the key to {@code null}.
|
||||
* The {@link HashMap#containsKey containsKey} operation may be used to
|
||||
* distinguish these two cases.
|
||||
*
|
||||
* @see #put(Object, int)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
int get(final V key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this).get(key, defaultReturnValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <tt>true</tt> if this map contains no key-value mappings.
|
||||
*
|
||||
* @return <tt>true</tt> if this map contains no key-value mappings
|
||||
*/
|
||||
public
|
||||
boolean isEmpty() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.size == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value mappings in this map. If the
|
||||
* map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
|
||||
* <tt>Integer.MAX_VALUE</tt>.
|
||||
*
|
||||
* @return the number of key-value mappings in this map
|
||||
*/
|
||||
public
|
||||
int size() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity equals only!
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
return this == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return mapREF.get(this).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return mapREF.get(this)
|
||||
.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Copyright 2018 dorkbox, llc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
/**
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
* <p>
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
* <p>
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
* <p>
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final
|
||||
class LockFreeObjectMap<K, V> implements Cloneable, Serializable {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeObjectMap, ObjectMap> mapREF = AtomicReferenceFieldUpdater.newUpdater(
|
||||
LockFreeObjectMap.class,
|
||||
ObjectMap.class,
|
||||
"hashMap");
|
||||
|
||||
private volatile ObjectMap<K, V> hashMap;
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
|
||||
* (16) and the default load factor (0.75).
|
||||
*/
|
||||
public
|
||||
LockFreeObjectMap() {
|
||||
hashMap = new ObjectMap<K, V>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>HashMap</tt> with the specified initial
|
||||
* capacity and the default load factor (0.75).
|
||||
*
|
||||
* @param initialCapacity the initial capacity.
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is negative.
|
||||
*/
|
||||
public
|
||||
LockFreeObjectMap(int initialCapacity) {
|
||||
hashMap = new ObjectMap<K, V>(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty <tt>HashMap</tt> with the specified initial
|
||||
* capacity and load factor.
|
||||
*
|
||||
* @param initialCapacity the initial capacity
|
||||
* @param loadFactor the load factor
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is negative
|
||||
* or the load factor is nonpositive
|
||||
*/
|
||||
public
|
||||
LockFreeObjectMap(int initialCapacity, float loadFactor) {
|
||||
this.hashMap = new ObjectMap<K, V>(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
public
|
||||
int size() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.size;
|
||||
}
|
||||
|
||||
public
|
||||
boolean isEmpty() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.size == 0;
|
||||
}
|
||||
|
||||
public
|
||||
boolean containsKey(final K key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.containsKey(key);
|
||||
}
|
||||
|
||||
public
|
||||
boolean containsValue(final V value, boolean identity) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return mapREF.get(this)
|
||||
.containsValue(value, identity);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
V get(final K key) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return (V) mapREF.get(this)
|
||||
.get(key);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
V put(final K key, final V value) {
|
||||
return hashMap.put(key, value);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
V remove(final K key) {
|
||||
return hashMap.remove(key);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
void putAll(final ObjectMap<K, V> map) {
|
||||
this.hashMap.putAll(map);
|
||||
}
|
||||
|
||||
public synchronized
|
||||
void clear() {
|
||||
hashMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link ObjectMap.Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
public
|
||||
ObjectMap.Keys keys() {
|
||||
return mapREF.get(this)
|
||||
.keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link ObjectMap.Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
public
|
||||
ObjectMap.Values values() {
|
||||
return mapREF.get(this)
|
||||
.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT MODIFY THE MAP VIA THIS (unless you synchronize around it!) It will result in unknown object visibility!
|
||||
*
|
||||
* Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link ObjectMap.Entries} constructor for nested or multithreaded iteration.
|
||||
*/
|
||||
public
|
||||
ObjectMap.Entries entries() {
|
||||
return mapREF.get(this)
|
||||
.entries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity equals only!
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
return this == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return mapREF.get(this)
|
||||
.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return mapREF.get(this)
|
||||
.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
/**
|
||||
* This class uses the "single-writer-principle" for lock-free publication.
|
||||
* <p>
|
||||
* Since there are only 2 methods to guarantee that modifications can only be called one-at-a-time (either it is only called by
|
||||
* one thread, or only one thread can access it at a time) -- we chose the 2nd option -- and use 'synchronized' to make sure that only
|
||||
* one thread can access this modification methods at a time. Getting or checking the presence of values can then happen in a lock-free
|
||||
* manner.
|
||||
* <p>
|
||||
* According to my benchmarks, this is approximately 25% faster than ConcurrentHashMap for (all types of) reads, and a lot slower for
|
||||
* contended writes.
|
||||
* <p>
|
||||
* This data structure is for many-read/few-write scenarios
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final
|
||||
class LockFreeSet<E> implements Set<E>, Cloneable, java.io.Serializable {
|
||||
// Recommended for best performance while adhering to the "single writer principle". Must be static-final
|
||||
private static final AtomicReferenceFieldUpdater<LockFreeSet, Set> setREF = AtomicReferenceFieldUpdater.newUpdater(LockFreeSet.class,
|
||||
Set.class,
|
||||
"hashSet");
|
||||
|
||||
private volatile Set<E> hashSet;
|
||||
|
||||
// synchronized is used here to ensure the "single writer principle", and make sure that ONLY one thread at a time can enter this
|
||||
// section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our
|
||||
// use-case 99% of the time)
|
||||
|
||||
/**
|
||||
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
|
||||
* default initial capacity (16) and load factor (0.75).
|
||||
*/
|
||||
public
|
||||
LockFreeSet() {
|
||||
hashSet = new HashSet<E>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
|
||||
* the specified initial capacity and the specified load factor.
|
||||
*
|
||||
* @param initialCapacity the initial capacity of the hash map
|
||||
* @param loadFactor the load factor of the hash map
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is less
|
||||
* than zero, or if the load factor is nonpositive
|
||||
*/
|
||||
public
|
||||
LockFreeSet(int initialCapacity, float loadFactor) {
|
||||
hashSet = new HashSet<E>(initialCapacity, loadFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
|
||||
* the specified initial capacity and default load factor (0.75).
|
||||
*
|
||||
* @param initialCapacity the initial capacity of the hash table
|
||||
*
|
||||
* @throws IllegalArgumentException if the initial capacity is less
|
||||
* than zero
|
||||
*/
|
||||
public
|
||||
LockFreeSet(int initialCapacity) {
|
||||
hashSet = new HashSet<E>(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new set containing the elements in the specified
|
||||
* collection. The <tt>HashMap</tt> is created with default load factor
|
||||
* (0.75) and an initial capacity sufficient to contain the elements in
|
||||
* the specified collection.
|
||||
*
|
||||
* @param collection the collection whose elements are to be placed into this set
|
||||
*
|
||||
* @throws NullPointerException if the specified collection is null
|
||||
*/
|
||||
public
|
||||
LockFreeSet(final Collection<E> collection) {
|
||||
hashSet = new HashSet<E>(collection);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
Set<E> elements() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return Collections.unmodifiableSet(setREF.get(this));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public
|
||||
int size() {
|
||||
return setREF.get(this)
|
||||
.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean isEmpty() {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return setREF.get(this)
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean contains(final Object element) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return setREF.get(this)
|
||||
.contains(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Iterator<E> iterator() {
|
||||
return elements().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
Object[] toArray() {
|
||||
return setREF.get(this)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
<T> T[] toArray(final T[] a) {
|
||||
return (T[]) setREF.get(this)
|
||||
.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
boolean add(final E element) {
|
||||
return hashSet.add(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
boolean remove(final Object element) {
|
||||
return hashSet.remove(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean containsAll(final Collection<?> collection) {
|
||||
// use the SWP to get a lock-free get of the value
|
||||
return setREF.get(this)
|
||||
.containsAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
boolean addAll(final Collection<? extends E> elements) {
|
||||
return hashSet.addAll(elements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
boolean retainAll(final Collection<?> collection) {
|
||||
return hashSet.retainAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
boolean removeAll(final Collection<?> collection) {
|
||||
return hashSet.removeAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized
|
||||
void clear() {
|
||||
hashSet.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
return setREF.get(this).equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return setREF.get(this)
|
||||
.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String toString() {
|
||||
return setREF.get(this)
|
||||
.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,408 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/** A resizable, ordered or unordered long array. Avoids the boxing that occurs with ArrayList<Long>. If unordered, this class
|
||||
* avoids a memory copy when removing elements (the last element is moved to the removed element's position).
|
||||
* @author Nathan Sweet */
|
||||
public class LongArray {
|
||||
public long[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public LongArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public LongArray (int capacity) {
|
||||
this(true, capacity);
|
||||
}
|
||||
|
||||
/** @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing array to be grown. */
|
||||
public LongArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new long[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specific array. The new array will be ordered if the specific array is
|
||||
* ordered. The capacity is set to the number of elements, so any subsequent elements added will cause the backing array to be
|
||||
* grown. */
|
||||
public LongArray (LongArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new long[size];
|
||||
System.arraycopy(array.items, 0, items, 0, size);
|
||||
}
|
||||
|
||||
/** Creates a new ordered array containing the elements in the specified array. The capacity is set to the number of elements,
|
||||
* so any subsequent elements added will cause the backing array to be grown. */
|
||||
public LongArray (long[] array) {
|
||||
this(true, array, 0, array.length);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The capacity is set to the number of elements, so any
|
||||
* subsequent elements added will cause the backing array to be grown.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
|
||||
* memory copy. */
|
||||
public LongArray (boolean ordered, long[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (long value) {
|
||||
long[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (long value1, long value2) {
|
||||
long[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (long value1, long value2, long value3) {
|
||||
long[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (long value1, long value2, long value3, long value4) {
|
||||
long[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (LongArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (LongArray array, int offset, int length) {
|
||||
if (offset + length > array.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
|
||||
addAll(array.items, offset, length);
|
||||
}
|
||||
|
||||
public void addAll (long... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (long[] array, int offset, int length) {
|
||||
long[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public long get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, long value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, long value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, long value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, long value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
long[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
long[] items = this.items;
|
||||
long firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
public boolean contains (long value) {
|
||||
int i = size - 1;
|
||||
long[] items = this.items;
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOf (long value) {
|
||||
long[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int lastIndexOf (char value) {
|
||||
long[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean removeValue (long value) {
|
||||
long[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public long removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
long[] items = this.items;
|
||||
long value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
long[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (LongArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
long[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
long item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item == items[ii]) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return size != startSize;
|
||||
}
|
||||
|
||||
/** Removes and returns the last item. */
|
||||
public long pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public long peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public long first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing array to the size of the actual items. This is useful to release memory when many items
|
||||
* have been removed, or if it is known that more items will not be added.
|
||||
* @return {@link #items} */
|
||||
public long[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public long[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public long[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected long[] resize (int newSize) {
|
||||
long[] newItems = new long[newSize];
|
||||
long[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
long[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
long temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
long[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = MathUtil.random(i);
|
||||
long temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reduces the size of the array to the specified size. If the array is already smaller than the specified size, no action is
|
||||
* taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or zero if the array is empty. */
|
||||
public long random () {
|
||||
if (size == 0) return 0;
|
||||
return items[MathUtil.random(0,size - 1)];
|
||||
}
|
||||
|
||||
public long[] toArray () {
|
||||
long[] array = new long[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
long[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + (int)(items[i] ^ (items[i] >>> 32));
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof LongArray)) return false;
|
||||
LongArray array = (LongArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
long[] items1 = this.items;
|
||||
long[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items[i] != array.items[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
long[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
long[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #LongArray(long[]) */
|
||||
static public LongArray with (long... array) {
|
||||
return new LongArray(array);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,871 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** An unordered map that uses long keys. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small
|
||||
* stash for problematic keys. Null values are allowed. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"NullableProblems", "rawtypes", "unchecked"})
|
||||
public class LongMap<V> implements Iterable<LongMap.Entry<V>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
private static final int EMPTY = 0;
|
||||
|
||||
public int size;
|
||||
|
||||
long[] keyTable;
|
||||
V[] valueTable;
|
||||
int capacity, stashSize;
|
||||
V zeroValue;
|
||||
boolean hasZeroValue;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
/** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public LongMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public LongMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public LongMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 63 - Long.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = new long[capacity + stashCapacity];
|
||||
valueTable = (V[])new Object[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public LongMap (LongMap<? extends V> map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
zeroValue = map.zeroValue;
|
||||
hasZeroValue = map.hasZeroValue;
|
||||
}
|
||||
|
||||
public V put (long key, V value) {
|
||||
if (key == 0) {
|
||||
V oldValue = zeroValue;
|
||||
zeroValue = value;
|
||||
if (!hasZeroValue) {
|
||||
hasZeroValue = true;
|
||||
size++;
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
long[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int index1 = (int)(key & mask);
|
||||
long key1 = keyTable[index1];
|
||||
if (key1 == key) {
|
||||
V oldValue = valueTable[index1];
|
||||
valueTable[index1] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
long key2 = keyTable[index2];
|
||||
if (key2 == key) {
|
||||
V oldValue = valueTable[index2];
|
||||
valueTable[index2] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
long key3 = keyTable[index3];
|
||||
if (key3 == key) {
|
||||
V oldValue = valueTable[index3];
|
||||
valueTable[index3] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
valueTable[i] = value;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void putAll (LongMap<? extends V> map) {
|
||||
for (Entry<? extends V> entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (long key, V value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
hasZeroValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
int index1 = (int)(key & mask);
|
||||
long key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
long key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
long key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (long insertKey, V insertValue, int index1, long key1, int index2, long key2, int index3, long key3) {
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
long evictedKey;
|
||||
V evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
evictedValue = valueTable[index1];
|
||||
keyTable[index1] = insertKey;
|
||||
valueTable[index1] = insertValue;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
evictedValue = valueTable[index2];
|
||||
keyTable[index2] = insertKey;
|
||||
valueTable[index2] = insertValue;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
evictedValue = valueTable[index3];
|
||||
keyTable[index3] = insertKey;
|
||||
valueTable[index3] = insertValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
index1 = (int)(evictedKey & mask);
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(evictedKey);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(evictedKey);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (long key, V value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
public V get (long key) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return null;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = (int)(key & mask);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, null);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
public V get (long key, V defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = (int)(key & mask);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private V getStash (long key, V defaultValue) {
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public V remove (long key) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return null;
|
||||
V oldValue = zeroValue;
|
||||
zeroValue = null;
|
||||
hasZeroValue = false;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index = (int)(key & mask);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
V removeStash (long key) {
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
valueTable[lastIndex] = null;
|
||||
} else
|
||||
valueTable[index] = null;
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
zeroValue = null;
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;) {
|
||||
keyTable[i] = EMPTY;
|
||||
valueTable[i] = null;
|
||||
}
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
zeroValue = null;
|
||||
hasZeroValue = false;
|
||||
}
|
||||
|
||||
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may
|
||||
* be an expensive operation. */
|
||||
public boolean containsValue (Object value, boolean identity) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
if (hasZeroValue && zeroValue == null) return true;
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != EMPTY && valueTable[i] == null) return true;
|
||||
} else if (identity) {
|
||||
if (value == zeroValue) return true;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return true;
|
||||
} else {
|
||||
if (hasZeroValue && value.equals(zeroValue)) return true;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (long key) {
|
||||
if (key == 0) return hasZeroValue;
|
||||
int index = (int)(key & mask);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (long key) {
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value, or <tt>notFound</tt> if it is not in the map. Note this traverses the entire map
|
||||
* and compares every value, which may be an expensive operation.
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}. */
|
||||
public long findKey (Object value, boolean identity, long notFound) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
if (hasZeroValue && zeroValue == null) return 0;
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != EMPTY && valueTable[i] == null) return keyTable[i];
|
||||
} else if (identity) {
|
||||
if (value == zeroValue) return 0;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return keyTable[i];
|
||||
} else {
|
||||
if (hasZeroValue && value.equals(zeroValue)) return 0;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return keyTable[i];
|
||||
}
|
||||
return notFound;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 63 - Long.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
long[] oldKeyTable = keyTable;
|
||||
V[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = new long[newSize + stashCapacity];
|
||||
valueTable = (V[])new Object[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = hasZeroValue ? 1 : 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
long key = oldKeyTable[i];
|
||||
if (key != EMPTY) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (long h) {
|
||||
h *= PRIME2;
|
||||
return (int)((h ^ h >>> hashShift) & mask);
|
||||
}
|
||||
|
||||
private int hash3 (long h) {
|
||||
h *= PRIME3;
|
||||
return (int)((h ^ h >>> hashShift) & mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
if (hasZeroValue && zeroValue != null) {
|
||||
h += zeroValue.hashCode();
|
||||
}
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
long key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
h += (int)(key ^ (key >>> 32)) * 31;
|
||||
|
||||
V value = valueTable[i];
|
||||
if (value != null) {
|
||||
h += value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof LongMap)) return false;
|
||||
LongMap<V> other = (LongMap)obj;
|
||||
if (other.size != size) return false;
|
||||
if (other.hasZeroValue != hasZeroValue) return false;
|
||||
if (hasZeroValue) {
|
||||
if (other.zeroValue == null) {
|
||||
if (zeroValue != null) return false;
|
||||
} else {
|
||||
if (!other.zeroValue.equals(zeroValue)) return false;
|
||||
}
|
||||
}
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
long key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
V value = valueTable[i];
|
||||
if (value == null) {
|
||||
if (!other.containsKey(key) || other.get(key) != null) return false;
|
||||
} else {
|
||||
if (!value.equals(other.get(key))) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
while (i-- > 0) {
|
||||
long key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
break;
|
||||
}
|
||||
while (i-- > 0) {
|
||||
long key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<V>> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Entries<V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Values<V> values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return values2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Keys keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<V> {
|
||||
public long key;
|
||||
public V value;
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator<V> {
|
||||
static final int INDEX_ILLEGAL = -2;
|
||||
static final int INDEX_ZERO = -1;
|
||||
|
||||
public boolean hasNext;
|
||||
|
||||
final LongMap<V> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (LongMap<V> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
nextIndex = INDEX_ZERO;
|
||||
if (map.hasZeroValue)
|
||||
hasNext = true;
|
||||
else
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
long[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != EMPTY) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex == INDEX_ZERO && map.hasZeroValue) {
|
||||
map.zeroValue = null;
|
||||
map.hasZeroValue = false;
|
||||
} else if (currentIndex < 0) {
|
||||
throw new IllegalStateException("next must be called before remove.");
|
||||
} else if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = EMPTY;
|
||||
map.valueTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
static public class Entries<V> extends MapIterator<V> implements Iterable<Entry<V>>, Iterator<Entry<V>> {
|
||||
private Entry<V> entry = new Entry();
|
||||
|
||||
public Entries (LongMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
@Override
|
||||
public Entry<V> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
long[] keyTable = map.keyTable;
|
||||
if (nextIndex == INDEX_ZERO) {
|
||||
entry.key = 0;
|
||||
entry.value = map.zeroValue;
|
||||
} else {
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
}
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<V>> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static public class Values<V> extends MapIterator<V> implements Iterable<V>, Iterator<V> {
|
||||
public Values (LongMap<V> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
V value;
|
||||
if (nextIndex == INDEX_ZERO)
|
||||
value = map.zeroValue;
|
||||
else
|
||||
value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public Array<V> toArray () {
|
||||
Array array = new Array(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static public class Keys extends MapIterator {
|
||||
public Keys (LongMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public long next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
long key = nextIndex == INDEX_ZERO ? 0 : map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public LongArray toArray () {
|
||||
LongArray array = new LongArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.collections;
|
||||
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public
|
||||
class MathUtil {
|
||||
public static final Random random = new Random();
|
||||
|
||||
/** Returns a random number between 0 (inclusive) and the specified value (inclusive). */
|
||||
static public int random (int range) {
|
||||
return random.nextInt(range + 1);
|
||||
}
|
||||
|
||||
/** Returns a random number between start (inclusive) and end (inclusive). */
|
||||
static public int random (int start, int end) {
|
||||
return start + random.nextInt(end - start + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next power of two. Returns the specified value if the value is already a power of two.
|
||||
*/
|
||||
public static
|
||||
int nextPowerOfTwo(int value) {
|
||||
return 1 << (32 - Integer.numberOfLeadingZeros(value - 1));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,795 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/** An unordered map where the values are floats. This implementation is a cuckoo hash map using 3 hashes, random walking, and a
|
||||
* small stash for problematic keys. Null keys are not allowed. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"unchecked", "NullableProblems", "rawtypes"})
|
||||
public class ObjectFloatMap<K> implements Iterable<ObjectFloatMap.Entry<K>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
||||
public int size;
|
||||
|
||||
K[] keyTable;
|
||||
float[] valueTable;
|
||||
int capacity, stashSize;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
/** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public ObjectFloatMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectFloatMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public ObjectFloatMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = (K[])new Object[capacity + stashCapacity];
|
||||
valueTable = new float[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public ObjectFloatMap (ObjectFloatMap<? extends K> map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
}
|
||||
|
||||
public void put (K key, float value) {
|
||||
if (key == null) throw new IllegalArgumentException("key cannot be null.");
|
||||
K[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key.equals(key1)) {
|
||||
valueTable[index1] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key.equals(key2)) {
|
||||
valueTable[index2] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key.equals(key3)) {
|
||||
valueTable[index3] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
valueTable[i] = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
public void putAll (ObjectFloatMap<? extends K> map) {
|
||||
for (Entry<? extends K> entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (K key, float value) {
|
||||
// Check for empty buckets.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (K insertKey, float insertValue, int index1, K key1, int index2, K key2, int index3, K key3) {
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
K evictedKey;
|
||||
float evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
evictedValue = valueTable[index1];
|
||||
keyTable[index1] = insertKey;
|
||||
valueTable[index1] = insertValue;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
evictedValue = valueTable[index2];
|
||||
keyTable[index2] = insertKey;
|
||||
valueTable[index2] = insertValue;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
evictedValue = valueTable[index3];
|
||||
keyTable[index3] = insertKey;
|
||||
valueTable[index3] = insertValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
int hashCode = evictedKey.hashCode();
|
||||
index1 = hashCode & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(hashCode);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(hashCode);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (K key, float value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** @param defaultValue Returned if the key was not associated with a value. */
|
||||
public float get (K key, float defaultValue) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private float getStash (K key, float defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** Returns the key's current value and increments the stored value. If the key is not in the map, defaultValue + increment is
|
||||
* put into the map. */
|
||||
public float getAndIncrement (K key, float defaultValue, float increment) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getAndIncrementStash(key, defaultValue, increment);
|
||||
}
|
||||
}
|
||||
float value = valueTable[index];
|
||||
valueTable[index] = value + increment;
|
||||
return value;
|
||||
}
|
||||
|
||||
private float getAndIncrementStash (K key, float defaultValue, float increment) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) {
|
||||
float value = valueTable[i];
|
||||
valueTable[i] = value + increment;
|
||||
return value;
|
||||
}
|
||||
put(key, defaultValue + increment);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public float remove (K key, float defaultValue) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key, defaultValue);
|
||||
}
|
||||
|
||||
float removeStash (K key, float defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
float oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
keyTable[lastIndex] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = null;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
}
|
||||
|
||||
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
|
||||
* an expensive operation. */
|
||||
public boolean containsValue (float value) {
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (K key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares
|
||||
* every value, which may be an expensive operation. */
|
||||
public K findKey (float value) {
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == value) return keyTable[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
K[] oldKeyTable = keyTable;
|
||||
float[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = (K[])new Object[newSize + stashCapacity];
|
||||
valueTable = new float[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
K key = oldKeyTable[i];
|
||||
if (key != null) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
h += key.hashCode() * 31;
|
||||
|
||||
float value = valueTable[i];
|
||||
h += Float.floatToIntBits(value);
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof ObjectFloatMap)) return false;
|
||||
ObjectFloatMap<K> other = (ObjectFloatMap) obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
float otherValue = other.get(key, 0f);
|
||||
if (otherValue == 0f && !other.containsKey(key)) return false;
|
||||
float value = valueTable[i];
|
||||
if (otherValue != value) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
break;
|
||||
}
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entries<K> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Entries<K> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Values values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return values2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each time
|
||||
* this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Keys<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<K> {
|
||||
public K key;
|
||||
public float value;
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator<K> {
|
||||
public boolean hasNext;
|
||||
|
||||
final ObjectFloatMap<K> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (ObjectFloatMap<K> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries<K> extends MapIterator<K> implements Iterable<Entry<K>>, Iterator<Entry<K>> {
|
||||
private Entry<K> entry = new Entry();
|
||||
|
||||
public Entries (ObjectFloatMap<K> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
@Override
|
||||
public Entry<K> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K[] keyTable = map.keyTable;
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entries<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values extends MapIterator<Object> {
|
||||
public Values (ObjectFloatMap<?> map) {
|
||||
super((ObjectFloatMap<Object>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public float next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
float value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public FloatArray toArray () {
|
||||
FloatArray array = new FloatArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static public class Keys<K> extends MapIterator<K> implements Iterable<K>, Iterator<K> {
|
||||
public Keys (ObjectFloatMap<K> map) {
|
||||
super((ObjectFloatMap<K>)map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keys<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public Array<K> toArray () {
|
||||
Array array = new Array(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
/** Adds the remaining keys to the array. */
|
||||
public Array<K> toArray (Array<K> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,794 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/** An unordered map where the values are ints. This implementation is a cuckoo hash map using 3 hashes, random walking, and a
|
||||
* small stash for problematic keys. Null keys are not allowed. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"unchecked", "NullableProblems"})
|
||||
public class ObjectIntMap<K> implements Iterable<ObjectIntMap.Entry<K>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
||||
public int size;
|
||||
|
||||
K[] keyTable;
|
||||
int[] valueTable;
|
||||
int capacity, stashSize;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
/** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public ObjectIntMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectIntMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectIntMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = (K[])new Object[capacity + stashCapacity];
|
||||
valueTable = new int[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public ObjectIntMap (ObjectIntMap<? extends K> map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
}
|
||||
|
||||
public void put (K key, int value) {
|
||||
if (key == null) throw new IllegalArgumentException("key cannot be null.");
|
||||
K[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key.equals(key1)) {
|
||||
valueTable[index1] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key.equals(key2)) {
|
||||
valueTable[index2] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key.equals(key3)) {
|
||||
valueTable[index3] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
valueTable[i] = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
public void putAll (ObjectIntMap<? extends K> map) {
|
||||
for (Entry<? extends K> entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (K key, int value) {
|
||||
// Check for empty buckets.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (K insertKey, int insertValue, int index1, K key1, int index2, K key2, int index3, K key3) {
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
K evictedKey;
|
||||
int evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
evictedValue = valueTable[index1];
|
||||
keyTable[index1] = insertKey;
|
||||
valueTable[index1] = insertValue;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
evictedValue = valueTable[index2];
|
||||
keyTable[index2] = insertKey;
|
||||
valueTable[index2] = insertValue;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
evictedValue = valueTable[index3];
|
||||
keyTable[index3] = insertKey;
|
||||
valueTable[index3] = insertValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
int hashCode = evictedKey.hashCode();
|
||||
index1 = hashCode & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(hashCode);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(hashCode);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (K key, int value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** @param defaultValue Returned if the key was not associated with a value. */
|
||||
public int get (K key, int defaultValue) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private int getStash (K key, int defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** Returns the key's current value and increments the stored value. If the key is not in the map, defaultValue + increment is
|
||||
* put into the map. */
|
||||
public int getAndIncrement (K key, int defaultValue, int increment) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getAndIncrementStash(key, defaultValue, increment);
|
||||
}
|
||||
}
|
||||
int value = valueTable[index];
|
||||
valueTable[index] = value + increment;
|
||||
return value;
|
||||
}
|
||||
|
||||
private int getAndIncrementStash (K key, int defaultValue, int increment) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) {
|
||||
int value = valueTable[i];
|
||||
valueTable[i] = value + increment;
|
||||
return value;
|
||||
}
|
||||
put(key, defaultValue + increment);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public int remove (K key, int defaultValue) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key, defaultValue);
|
||||
}
|
||||
|
||||
int removeStash (K key, int defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
int oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
keyTable[lastIndex] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = null;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
}
|
||||
|
||||
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
|
||||
* an expensive operation. */
|
||||
public boolean containsValue (int value) {
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == value) return true;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public boolean containsKey (K key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares
|
||||
* every value, which may be an expensive operation. */
|
||||
public K findKey (int value) {
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == value) return keyTable[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
K[] oldKeyTable = keyTable;
|
||||
int[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = (K[])new Object[newSize + stashCapacity];
|
||||
valueTable = new int[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
K key = oldKeyTable[i];
|
||||
if (key != null) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
h += key.hashCode() * 31;
|
||||
|
||||
int value = valueTable[i];
|
||||
h += value;
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof ObjectIntMap)) return false;
|
||||
ObjectIntMap<K> other = (ObjectIntMap)obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
int otherValue = other.get(key, 0);
|
||||
if (otherValue == 0 && !other.containsKey(key)) return false;
|
||||
int value = valueTable[i];
|
||||
if (otherValue != value) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
break;
|
||||
}
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entries<K> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Entries<K> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Values values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return values2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each time
|
||||
* this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Keys<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<K> {
|
||||
public K key;
|
||||
public int value;
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator<K> {
|
||||
public boolean hasNext;
|
||||
|
||||
final ObjectIntMap<K> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (ObjectIntMap<K> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries<K> extends MapIterator<K> implements Iterable<Entry<K>>, Iterator<Entry<K>> {
|
||||
private Entry<K> entry = new Entry();
|
||||
|
||||
public Entries (ObjectIntMap<K> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
@Override
|
||||
public Entry<K> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K[] keyTable = map.keyTable;
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entries<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values extends MapIterator<Object> {
|
||||
public Values (ObjectIntMap<?> map) {
|
||||
super((ObjectIntMap<Object>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys<K> extends MapIterator<K> implements Iterable<K>, Iterator<K> {
|
||||
public Keys (ObjectIntMap<K> map) {
|
||||
super((ObjectIntMap<K>)map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keys<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public Array<K> toArray () {
|
||||
Array array = new Array(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
/** Adds the remaining keys to the array. */
|
||||
public Array<K> toArray (Array<K> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,836 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
/** An unordered map. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small stash for problematic
|
||||
* keys. Null keys are not allowed. Null values are allowed. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
|
||||
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
|
||||
* next higher POT size.<br>
|
||||
* <br>
|
||||
* Iteration can be very slow for a map with a large capacity. {@link #clear(int)} and {@link #shrink(int)} can be used to reduce
|
||||
* the capacity. {@link OrderedMap} provides much faster iteration.
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ObjectMap<K, V> implements Iterable<ObjectMap.Entry<K, V>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
||||
public int size;
|
||||
|
||||
K[] keyTable;
|
||||
V[] valueTable;
|
||||
int capacity, stashSize;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
/** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public ObjectMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = (K[])new Object[capacity + stashCapacity];
|
||||
valueTable = (V[])new Object[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public ObjectMap (ObjectMap<? extends K, ? extends V> map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
}
|
||||
|
||||
/** Returns the old value associated with the specified key, or null. */
|
||||
public V put (K key, V value) {
|
||||
if (key == null) throw new IllegalArgumentException("key cannot be null.");
|
||||
K[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key.equals(key1)) {
|
||||
V oldValue = valueTable[index1];
|
||||
valueTable[index1] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key.equals(key2)) {
|
||||
V oldValue = valueTable[index2];
|
||||
valueTable[index2] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key.equals(key3)) {
|
||||
V oldValue = valueTable[index3];
|
||||
valueTable[index3] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
V oldValue = valueTable[i];
|
||||
valueTable[i] = value;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void putAll (ObjectMap<? extends K, ? extends V> map) {
|
||||
ensureCapacity(map.size);
|
||||
for (Entry<? extends K, ? extends V> entry : map)
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (K key, V value) {
|
||||
// Check for empty buckets.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (K insertKey, V insertValue, int index1, K key1, int index2, K key2, int index3, K key3) {
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
K evictedKey;
|
||||
V evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random.nextInt(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
evictedValue = valueTable[index1];
|
||||
keyTable[index1] = insertKey;
|
||||
valueTable[index1] = insertValue;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
evictedValue = valueTable[index2];
|
||||
keyTable[index2] = insertKey;
|
||||
valueTable[index2] = insertValue;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
evictedValue = valueTable[index3];
|
||||
keyTable[index3] = insertKey;
|
||||
valueTable[index3] = insertValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
int hashCode = evictedKey.hashCode();
|
||||
index1 = hashCode & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(hashCode);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(hashCode);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (K key, V value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** Returns the value for the specified key, or null if the key is not in the map. */
|
||||
public V get (K key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getStash(key, null);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
/** Returns the value for the specified key, or the default value if the key is not in the map. */
|
||||
public V get (K key, V defaultValue) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private V getStash (K key, V defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** Returns the value associated with the key, or null. */
|
||||
public V remove (K key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
V removeStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
V oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
keyTable[lastIndex] = null;
|
||||
valueTable[lastIndex] = null;
|
||||
} else {
|
||||
keyTable[index] = null;
|
||||
valueTable[index] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity, if they are larger. The reduction
|
||||
* is done by allocating new arrays, though for large arrays this can be faster than clearing the existing array. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map, leaving the backing arrays at the current capacity. When the capacity is high and the population is low,
|
||||
* iteration can be unnecessarily slow. {@link #clear(int)} can be used to reduce the capacity. */
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;) {
|
||||
keyTable[i] = null;
|
||||
valueTable[i] = null;
|
||||
}
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
}
|
||||
|
||||
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may
|
||||
* be an expensive operation.
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}. */
|
||||
public boolean containsValue (Object value, boolean identity) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == null) return true;
|
||||
} else if (identity) {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return true;
|
||||
} else {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (K key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares
|
||||
* every value, which may be an expensive operation.
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}. */
|
||||
public K findKey (Object value, boolean identity) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == null) return keyTable[i];
|
||||
} else if (identity) {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return keyTable[i];
|
||||
} else {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return keyTable[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
K[] oldKeyTable = keyTable;
|
||||
V[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = (K[])new Object[newSize + stashCapacity];
|
||||
valueTable = (V[])new Object[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
K key = oldKeyTable[i];
|
||||
if (key != null) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
h += key.hashCode() * 31;
|
||||
|
||||
V value = valueTable[i];
|
||||
if (value != null) {
|
||||
h += value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof ObjectMap)) return false;
|
||||
ObjectMap<K, V> other = (ObjectMap)obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
V value = valueTable[i];
|
||||
if (value == null) {
|
||||
if (!other.containsKey(key) || other.get(key) != null) return false;
|
||||
} else {
|
||||
if (!value.equals(other.get(key))) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
return toString(separator, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return toString(", ", true);
|
||||
}
|
||||
|
||||
private String toString (String separator, boolean braces) {
|
||||
if (size == 0) return braces ? "{}" : "";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
if (braces) buffer.append('{');
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
break;
|
||||
}
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(separator);
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
if (braces) buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entries<K, V> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
|
||||
public Entries<K, V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Values} constructor for nested or multithreaded iteration. */
|
||||
public Values<V> values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return values2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link Keys} constructor for nested or multithreaded iteration. */
|
||||
public Keys<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<K, V> {
|
||||
public K key;
|
||||
public V value;
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private abstract class MapIterator<K, V, I> implements Iterable<I>, Iterator<I> {
|
||||
public boolean hasNext;
|
||||
|
||||
final ObjectMap<K, V> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (ObjectMap<K, V> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = null;
|
||||
map.valueTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
static public class Entries<K, V> extends MapIterator<K, V, Entry<K, V>> {
|
||||
Entry<K, V> entry = new Entry();
|
||||
|
||||
public Entries (ObjectMap<K, V> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
@Override
|
||||
public Entry<K, V> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K[] keyTable = map.keyTable;
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entries<K, V> iterator () {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"NullableProblems", "unchecked", "rawtypes"})
|
||||
static public class Values<V> extends MapIterator<Object, V, V> {
|
||||
public Values (ObjectMap<?, V> map) {
|
||||
super((ObjectMap<Object, V>)map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
V value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Values<V> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public Array<V> toArray () {
|
||||
return toArray(new Array(true, map.size));
|
||||
}
|
||||
|
||||
/** Adds the remaining values to the specified array. */
|
||||
public Array<V> toArray (Array<V> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "NullableProblems", "rawtypes"})
|
||||
static public class Keys<K> extends MapIterator<K, Object, K> {
|
||||
public Keys (ObjectMap<K, ?> map) {
|
||||
super((ObjectMap<K, Object>)map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keys<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public Array<K> toArray () {
|
||||
return toArray(new Array(true, map.size));
|
||||
}
|
||||
|
||||
/** Adds the remaining keys to the array. */
|
||||
public Array<K> toArray (Array<K> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,589 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
/** An unordered set where the keys are objects. This implementation uses cuckoo hashing using 3 hashes, random walking, and a
|
||||
* small stash for problematic keys. Null keys are not allowed. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This set performs very fast contains and remove (typically O(1), worst case O(log(n))). Add may be a bit slower, depending on
|
||||
* hash collisions. Load factors greater than 0.91 greatly increase the chances the set will have to rehash to the next higher POT
|
||||
* size.<br>
|
||||
* <br>
|
||||
* Iteration can be very slow for a set with a large capacity. {@link #clear(int)} and {@link #shrink(int)} can be used to reduce
|
||||
* the capacity. {@link OrderedSet} provides much faster iteration.
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"unchecked", "rawtypes", "NullableProblems", "SuspiciousSystemArraycopy"})
|
||||
public class ObjectSet<T> implements Iterable<T> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
||||
public int size;
|
||||
|
||||
T[] keyTable;
|
||||
int capacity, stashSize;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private ObjectSetIterator iterator1, iterator2;
|
||||
|
||||
/** Creates a new set with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public ObjectSet () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new set with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectSet (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new set with the specified initial capacity and load factor. This set will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectSet (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = (T[])new Object[capacity + stashCapacity];
|
||||
}
|
||||
|
||||
/** Creates a new set identical to the specified set. */
|
||||
public ObjectSet (ObjectSet set) {
|
||||
this((int)Math.floor(set.capacity * set.loadFactor), set.loadFactor);
|
||||
stashSize = set.stashSize;
|
||||
System.arraycopy(set.keyTable, 0, keyTable, 0, set.keyTable.length);
|
||||
size = set.size;
|
||||
}
|
||||
|
||||
/** Returns true if the key was not already in the set. If this set already contains the key, the call leaves the set unchanged
|
||||
* and returns false. */
|
||||
public boolean add (T key) {
|
||||
if (key == null) throw new IllegalArgumentException("key cannot be null.");
|
||||
T[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
T key1 = keyTable[index1];
|
||||
if (key.equals(key1)) return false;
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
T key2 = keyTable[index2];
|
||||
if (key.equals(key2)) return false;
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
T key3 = keyTable[index3];
|
||||
if (key.equals(key3)) return false;
|
||||
|
||||
// Find key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return false;
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
push(key, index1, key1, index2, key2, index3, key3);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addAll (Array<? extends T> array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (Array<? extends T> array, int offset, int length) {
|
||||
if (offset + length > array.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
|
||||
addAll((T[])array.items, offset, length);
|
||||
}
|
||||
|
||||
public void addAll (T... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (T[] array, int offset, int length) {
|
||||
ensureCapacity(length);
|
||||
for (int i = offset, n = i + length; i < n; i++)
|
||||
add(array[i]);
|
||||
}
|
||||
|
||||
public void addAll (ObjectSet<T> set) {
|
||||
ensureCapacity(set.size);
|
||||
for (T key : set)
|
||||
add(key);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void addResize (T key) {
|
||||
// Check for empty buckets.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
T key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
T key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
T key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (T insertKey, int index1, T key1, int index2, T key2, int index3, T key3) {
|
||||
T[] keyTable = this.keyTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
T evictedKey;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (MathUtil.random.nextInt(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
keyTable[index1] = insertKey;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
keyTable[index2] = insertKey;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
keyTable[index3] = insertKey;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the evicted key hashes to an empty bucket, put it there and stop.
|
||||
int hashCode = evictedKey.hashCode();
|
||||
index1 = hashCode & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(hashCode);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(hashCode);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
} while (true);
|
||||
|
||||
addStash(evictedKey);
|
||||
}
|
||||
|
||||
private void addStash (T key) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
addResize(key);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** Returns true if the key was removed. */
|
||||
public boolean remove (T key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
boolean removeStash (T key) {
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
keyTable[lastIndex] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the set is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
|
||||
* done. If the set contains more items than the specified capacity, the next highest power of two capacity is used instead. */
|
||||
public void shrink (int maximumCapacity) {
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the set and reduces the size of the backing arrays to be the specified capacity, if they are larger. The reduction
|
||||
* is done by allocating new arrays, though for large arrays this can be faster than clearing the existing array. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the set, leaving the backing arrays at the current capacity. When the capacity is high and the population is low,
|
||||
* iteration can be unnecessarily slow. {@link #clear(int)} can be used to reduce the capacity. */
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = null;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
}
|
||||
|
||||
public boolean contains (T key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getKeyStash(key) != null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return May be null. */
|
||||
public T get (T key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
T found = keyTable[index];
|
||||
if (!key.equals(found)) {
|
||||
index = hash2(hashCode);
|
||||
found = keyTable[index];
|
||||
if (!key.equals(found)) {
|
||||
index = hash3(hashCode);
|
||||
found = keyTable[index];
|
||||
if (!key.equals(found)) return getKeyStash(key);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private T getKeyStash (T key) {
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return keyTable[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
public T first () {
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != null) return keyTable[i];
|
||||
throw new IllegalStateException("ObjectSet is empty.");
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
T[] oldKeyTable = keyTable;
|
||||
|
||||
keyTable = (T[])new Object[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
T key = oldKeyTable[i];
|
||||
if (key != null) addResize(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != null) h += keyTable[i].hashCode();
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object obj) {
|
||||
if (!(obj instanceof ObjectSet)) return false;
|
||||
ObjectSet other = (ObjectSet)obj;
|
||||
if (other.size != size) return false;
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != null && !other.contains(keyTable[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return '{' + toString(", ") + '}';
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
T[] keyTable = this.keyTable;
|
||||
int i = keyTable.length;
|
||||
while (i-- > 0) {
|
||||
T key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(key);
|
||||
break;
|
||||
}
|
||||
while (i-- > 0) {
|
||||
T key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(separator);
|
||||
buffer.append(key);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the set. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link ObjectSetIterator} constructor for nested or multithreaded iteration. */
|
||||
@Override
|
||||
public ObjectSetIterator<T> iterator () {
|
||||
if (iterator1 == null) {
|
||||
iterator1 = new ObjectSetIterator(this);
|
||||
iterator2 = new ObjectSetIterator(this);
|
||||
}
|
||||
if (!iterator1.valid) {
|
||||
iterator1.reset();
|
||||
iterator1.valid = true;
|
||||
iterator2.valid = false;
|
||||
return iterator1;
|
||||
}
|
||||
iterator2.reset();
|
||||
iterator2.valid = true;
|
||||
iterator1.valid = false;
|
||||
return iterator2;
|
||||
}
|
||||
|
||||
static public <T> ObjectSet<T> with (T... array) {
|
||||
ObjectSet set = new ObjectSet();
|
||||
set.addAll(array);
|
||||
return set;
|
||||
}
|
||||
|
||||
static public class ObjectSetIterator<K> implements Iterable<K>, Iterator<K> {
|
||||
public boolean hasNext;
|
||||
|
||||
final ObjectSet<K> set;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public ObjectSetIterator (ObjectSet<K> set) {
|
||||
this.set = set;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
private void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = set.keyTable;
|
||||
for (int n = set.capacity + set.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= set.capacity) {
|
||||
set.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
set.keyTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
set.size--;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = set.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectSetIterator<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds the remaining values to the array. */
|
||||
public Array<K> toArray (Array<K> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public Array<K> toArray () {
|
||||
return toArray(new Array(true, set.size));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** An {@link ObjectMap} that also stores keys in an {@link Array} using the insertion order. Iteration over the
|
||||
* {@link #entries()}, {@link #keys()}, and {@link #values()} is ordered and faster than an unordered map. Keys can also be
|
||||
* accessed and the order changed using {@link #orderedKeys()}. There is some additional overhead for put and remove. When used
|
||||
* for faster iteration versus ObjectMap and the order does not actually matter, copying during remove can be greatly reduced by
|
||||
* setting {@link Array#ordered} to false for {@link OrderedMap#orderedKeys()}.
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"unchecked", "NullableProblems"})
|
||||
public class OrderedMap<K, V> extends ObjectMap<K, V> {
|
||||
final Array<K> keys;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
public OrderedMap () {
|
||||
keys = new Array();
|
||||
}
|
||||
|
||||
public OrderedMap (int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
keys = new Array(capacity);
|
||||
}
|
||||
|
||||
public OrderedMap (int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
keys = new Array(capacity);
|
||||
}
|
||||
|
||||
public OrderedMap (OrderedMap<? extends K, ? extends V> map) {
|
||||
super(map);
|
||||
keys = new Array(map.keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put (K key, V value) {
|
||||
if (!containsKey(key)) keys.add(key);
|
||||
return super.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove (K key) {
|
||||
keys.removeValue(key, false);
|
||||
return super.remove(key);
|
||||
}
|
||||
|
||||
public V removeIndex (int index) {
|
||||
return super.remove(keys.removeIndex(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear (int maximumCapacity) {
|
||||
keys.clear();
|
||||
super.clear(maximumCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear () {
|
||||
keys.clear();
|
||||
super.clear();
|
||||
}
|
||||
|
||||
public Array<K> orderedKeys () {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entries<K, V> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link OrderedMapEntries} constructor for nested or multithreaded iteration. */
|
||||
@Override
|
||||
public Entries<K, V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new OrderedMapEntries(this);
|
||||
entries2 = new OrderedMapEntries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return entries2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link OrderedMapValues} constructor for nested or multithreaded iteration. */
|
||||
@Override
|
||||
public Values<V> values () {
|
||||
if (values1 == null) {
|
||||
values1 = new OrderedMapValues(this);
|
||||
values2 = new OrderedMapValues(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return values2;
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link OrderedMapKeys} constructor for nested or multithreaded iteration. */
|
||||
@Override
|
||||
public Keys<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new OrderedMapKeys(this);
|
||||
keys2 = new OrderedMapKeys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
Array<K> keys = this.keys;
|
||||
for (int i = 0, n = keys.size; i < n; i++) {
|
||||
K key = keys.get(i);
|
||||
if (i > 0) buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(get(key));
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static public class OrderedMapEntries<K, V> extends Entries<K, V> {
|
||||
private final Array<K> keys;
|
||||
|
||||
public OrderedMapEntries (OrderedMap<K, V> map) {
|
||||
super(map);
|
||||
keys = map.keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset () {
|
||||
nextIndex = 0;
|
||||
hasNext = map.size > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
entry.key = keys.get(nextIndex);
|
||||
entry.value = map.get(entry.key);
|
||||
nextIndex++;
|
||||
hasNext = nextIndex < map.size;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
map.remove(entry.key);
|
||||
nextIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class OrderedMapKeys<K> extends Keys<K> {
|
||||
private final Array<K> keys;
|
||||
|
||||
public OrderedMapKeys (OrderedMap<K, ?> map) {
|
||||
super(map);
|
||||
keys = map.keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset () {
|
||||
nextIndex = 0;
|
||||
hasNext = map.size > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = keys.get(nextIndex);
|
||||
currentIndex = nextIndex;
|
||||
nextIndex++;
|
||||
hasNext = nextIndex < map.size;
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
((OrderedMap)map).removeIndex(nextIndex - 1);
|
||||
nextIndex = currentIndex;
|
||||
currentIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static public class OrderedMapValues<V> extends Values<V> {
|
||||
private final Array keys;
|
||||
|
||||
public OrderedMapValues (OrderedMap<?, V> map) {
|
||||
super(map);
|
||||
keys = map.keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset () {
|
||||
nextIndex = 0;
|
||||
hasNext = map.size > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
V value = (V)map.get(keys.get(nextIndex));
|
||||
currentIndex = nextIndex;
|
||||
nextIndex++;
|
||||
hasNext = nextIndex < map.size;
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
((OrderedMap)map).removeIndex(currentIndex);
|
||||
nextIndex = currentIndex;
|
||||
currentIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** An {@link ObjectSet} that also stores keys in an {@link Array} using the insertion order. {@link #iterator() Iteration} is
|
||||
* ordered and faster than an unordered set. Keys can also be accessed and the order changed using {@link #orderedItems()}. There
|
||||
* is some additional overhead for put and remove. When used for faster iteration versus ObjectSet and the order does not actually
|
||||
* matter, copying during remove can be greatly reduced by setting {@link Array#ordered} to false for
|
||||
* {@link OrderedSet#orderedItems()}.
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class OrderedSet<T> extends ObjectSet<T> {
|
||||
final Array<T> items;
|
||||
OrderedSetIterator iterator1, iterator2;
|
||||
|
||||
public OrderedSet () {
|
||||
items = new Array();
|
||||
}
|
||||
|
||||
public OrderedSet (int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
items = new Array(capacity);
|
||||
}
|
||||
|
||||
public OrderedSet (int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
items = new Array(capacity);
|
||||
}
|
||||
|
||||
public OrderedSet (OrderedSet set) {
|
||||
super(set);
|
||||
items = new Array(capacity);
|
||||
items.addAll(set.items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add (T key) {
|
||||
if (!super.add(key)) return false;
|
||||
items.add(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean add (T key, int index) {
|
||||
if (!super.add(key)) {
|
||||
items.removeValue(key, true);
|
||||
items.insert(index, key);
|
||||
return false;
|
||||
}
|
||||
items.insert(index, key);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove (T key) {
|
||||
if (!super.remove(key)) return false;
|
||||
items.removeValue(key, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public T removeIndex (int index) {
|
||||
T key = items.removeIndex(index);
|
||||
super.remove(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear (int maximumCapacity) {
|
||||
items.clear();
|
||||
super.clear(maximumCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear () {
|
||||
items.clear();
|
||||
super.clear();
|
||||
}
|
||||
|
||||
public Array<T> orderedItems () {
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderedSetIterator<T> iterator () {
|
||||
if (iterator1 == null) {
|
||||
iterator1 = new OrderedSetIterator(this);
|
||||
iterator2 = new OrderedSetIterator(this);
|
||||
}
|
||||
if (!iterator1.valid) {
|
||||
iterator1.reset();
|
||||
iterator1.valid = true;
|
||||
iterator2.valid = false;
|
||||
return iterator1;
|
||||
}
|
||||
iterator2.reset();
|
||||
iterator2.valid = true;
|
||||
iterator1.valid = false;
|
||||
return iterator2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
T[] items = this.items.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString (String separator) {
|
||||
return items.toString(separator);
|
||||
}
|
||||
|
||||
static public class OrderedSetIterator<T> extends ObjectSetIterator<T> {
|
||||
private Array<T> items;
|
||||
|
||||
public OrderedSetIterator (OrderedSet<T> set) {
|
||||
super(set);
|
||||
items = set.items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset () {
|
||||
nextIndex = 0;
|
||||
hasNext = set.size > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
T key = items.get(nextIndex);
|
||||
nextIndex++;
|
||||
hasNext = nextIndex < set.size;
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (nextIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
nextIndex--;
|
||||
((OrderedSet)set).removeIndex(nextIndex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/** Interface used to select items within an iterator against a predicate.
|
||||
* @author Xoppa */
|
||||
public interface Predicate<T> {
|
||||
|
||||
/** @return true if the item matches the criteria and should be included in the iterator's items */
|
||||
boolean evaluate (T arg0);
|
||||
|
||||
public class PredicateIterator<T> implements Iterator<T> {
|
||||
public Iterator<T> iterator;
|
||||
public Predicate<T> predicate;
|
||||
public boolean end = false;
|
||||
public boolean peeked = false;
|
||||
public T next = null;
|
||||
|
||||
public PredicateIterator (final Iterable<T> iterable, final Predicate<T> predicate) {
|
||||
this(iterable.iterator(), predicate);
|
||||
}
|
||||
|
||||
public PredicateIterator (final Iterator<T> iterator, final Predicate<T> predicate) {
|
||||
set(iterator, predicate);
|
||||
}
|
||||
|
||||
public void set (final Iterable<T> iterable, final Predicate<T> predicate) {
|
||||
set(iterable.iterator(), predicate);
|
||||
}
|
||||
|
||||
public void set (final Iterator<T> iterator, final Predicate<T> predicate) {
|
||||
this.iterator = iterator;
|
||||
this.predicate = predicate;
|
||||
end = peeked = false;
|
||||
next = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (end) return false;
|
||||
if (next != null) return true;
|
||||
peeked = true;
|
||||
while (iterator.hasNext()) {
|
||||
final T n = iterator.next();
|
||||
if (predicate.evaluate(n)) {
|
||||
next = n;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
end = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next () {
|
||||
if (next == null && !hasNext()) return null;
|
||||
final T result = next;
|
||||
next = null;
|
||||
peeked = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (peeked) throw new RuntimeException("Cannot remove between a call to hasNext() and next().");
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PredicateIterable<T> implements Iterable<T> {
|
||||
public Iterable<T> iterable;
|
||||
public Predicate<T> predicate;
|
||||
public PredicateIterator<T> iterator = null;
|
||||
|
||||
public PredicateIterable (Iterable<T> iterable, Predicate<T> predicate) {
|
||||
set(iterable, predicate);
|
||||
}
|
||||
|
||||
public void set (Iterable<T> iterable, Predicate<T> predicate) {
|
||||
this.iterable = iterable;
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
/** Returns an iterator. Note that the same iterator instance is returned each time this method is called. Use the
|
||||
* {@link Predicate.PredicateIterator} constructor for nested or multithreaded iteration. */
|
||||
@Override
|
||||
public Iterator<T> iterator () {
|
||||
if (iterator == null)
|
||||
iterator = new PredicateIterator<T>(iterable.iterator(), predicate);
|
||||
else
|
||||
iterator.set(iterable.iterator(), predicate);
|
||||
return iterator;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/** Implementation of Tony Hoare's quickselect algorithm. Running time is generally O(n), but worst case is O(n^2) Pivot choice is
|
||||
* median of three method, providing better performance than a random pivot for partially sorted data.
|
||||
* http://en.wikipedia.org/wiki/Quickselect
|
||||
* @author Jon Renner */
|
||||
public class QuickSelect<T> {
|
||||
private T[] array;
|
||||
private Comparator<? super T> comp;
|
||||
|
||||
public int select (T[] items, Comparator<T> comp, int n, int size) {
|
||||
this.array = items;
|
||||
this.comp = comp;
|
||||
return recursiveSelect(0, size - 1, n);
|
||||
}
|
||||
|
||||
private int partition (int left, int right, int pivot) {
|
||||
T pivotValue = array[pivot];
|
||||
swap(right, pivot);
|
||||
int storage = left;
|
||||
for (int i = left; i < right; i++) {
|
||||
if (comp.compare(array[i], pivotValue) < 0) {
|
||||
swap(storage, i);
|
||||
storage++;
|
||||
}
|
||||
}
|
||||
swap(right, storage);
|
||||
return storage;
|
||||
}
|
||||
|
||||
private int recursiveSelect (int left, int right, int k) {
|
||||
if (left == right) return left;
|
||||
int pivotIndex = medianOfThreePivot(left, right);
|
||||
int pivotNewIndex = partition(left, right, pivotIndex);
|
||||
int pivotDist = (pivotNewIndex - left) + 1;
|
||||
int result;
|
||||
if (pivotDist == k) {
|
||||
result = pivotNewIndex;
|
||||
} else if (k < pivotDist) {
|
||||
result = recursiveSelect(left, pivotNewIndex - 1, k);
|
||||
} else {
|
||||
result = recursiveSelect(pivotNewIndex + 1, right, k - pivotDist);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Median of Three has the potential to outperform a random pivot, especially for partially sorted arrays */
|
||||
private int medianOfThreePivot (int leftIdx, int rightIdx) {
|
||||
T left = array[leftIdx];
|
||||
int midIdx = (leftIdx + rightIdx) / 2;
|
||||
T mid = array[midIdx];
|
||||
T right = array[rightIdx];
|
||||
|
||||
// spaghetti median of three algorithm
|
||||
// does at most 3 comparisons
|
||||
if (comp.compare(left, mid) > 0) {
|
||||
if (comp.compare(mid, right) > 0) {
|
||||
return midIdx;
|
||||
} else if (comp.compare(left, right) > 0) {
|
||||
return rightIdx;
|
||||
} else {
|
||||
return leftIdx;
|
||||
}
|
||||
} else {
|
||||
if (comp.compare(left, right) > 0) {
|
||||
return leftIdx;
|
||||
} else if (comp.compare(mid, right) > 0) {
|
||||
return rightIdx;
|
||||
} else {
|
||||
return midIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void swap (int left, int right) {
|
||||
T tmp = array[left];
|
||||
array[left] = array[right];
|
||||
array[right] = tmp;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/** This class is for selecting a ranked element (kth ordered statistic) from an unordered list in faster time than sorting the
|
||||
* whole array. Typical applications include finding the nearest enemy unit(s), and other operations which are likely to run as
|
||||
* often as every x frames. Certain values of k will result in a partial sorting of the Array.
|
||||
* <p>
|
||||
* The lowest ranking element starts at 1, not 0. 1 = first, 2 = second, 3 = third, etc. calling with a value of zero will result
|
||||
* in a {@link RuntimeException}
|
||||
* </p>
|
||||
* <p>
|
||||
* This class uses very minimal extra memory, as it makes no copies of the array. The underlying algorithms used are a naive
|
||||
* single-pass for k=min and k=max, and Hoare's quickselect for values in between.
|
||||
* </p>
|
||||
* @author Jon Renner */
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Select {
|
||||
private static Select instance;
|
||||
private QuickSelect quickSelect;
|
||||
|
||||
/** Provided for convenience */
|
||||
public static Select instance () {
|
||||
if (instance == null) instance = new Select();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public <T> T select (T[] items, Comparator<T> comp, int kthLowest, int size) {
|
||||
int idx = selectIndex(items, comp, kthLowest, size);
|
||||
return items[idx];
|
||||
}
|
||||
|
||||
public <T> int selectIndex (T[] items, Comparator<T> comp, int kthLowest, int size) {
|
||||
if (size < 1) {
|
||||
throw new RuntimeException("cannot select from empty array (size < 1)");
|
||||
} else if (kthLowest > size) {
|
||||
throw new RuntimeException("Kth rank is larger than size. k: " + kthLowest + ", size: " + size);
|
||||
}
|
||||
int idx;
|
||||
// naive partial selection sort almost certain to outperform quickselect where n is min or max
|
||||
if (kthLowest == 1) {
|
||||
// find min
|
||||
idx = fastMin(items, comp, size);
|
||||
} else if (kthLowest == size) {
|
||||
// find max
|
||||
idx = fastMax(items, comp, size);
|
||||
} else {
|
||||
// quickselect a better choice for cases of k between min and max
|
||||
if (quickSelect == null) quickSelect = new QuickSelect();
|
||||
idx = quickSelect.select(items, comp, kthLowest, size);
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/** Faster than quickselect for n = min */
|
||||
private <T> int fastMin (T[] items, Comparator<T> comp, int size) {
|
||||
int lowestIdx = 0;
|
||||
for (int i = 1; i < size; i++) {
|
||||
int comparison = comp.compare(items[i], items[lowestIdx]);
|
||||
if (comparison < 0) {
|
||||
lowestIdx = i;
|
||||
}
|
||||
}
|
||||
return lowestIdx;
|
||||
}
|
||||
|
||||
/** Faster than quickselect for n = max */
|
||||
private <T> int fastMax (T[] items, Comparator<T> comp, int size) {
|
||||
int highestIdx = 0;
|
||||
for (int i = 1; i < size; i++) {
|
||||
int comparison = comp.compare(items[i], items[highestIdx]);
|
||||
if (comparison > 0) {
|
||||
highestIdx = i;
|
||||
}
|
||||
}
|
||||
return highestIdx;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
|
||||
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/** Provides methods to sort arrays of objects. Sorting requires working memory and this class allows that memory to be reused to
|
||||
* avoid allocation. The sorting is otherwise identical to the Arrays.sort methods (uses timsort).<br>
|
||||
* <br>
|
||||
* Note that sorting primitive arrays with the Arrays.sort methods does not allocate memory (unless sorting large arrays of char,
|
||||
* short, or byte).
|
||||
* @author Nathan Sweet */
|
||||
@SuppressWarnings({"RedundantCast", "unchecked", "rawtypes"})
|
||||
public class Sort {
|
||||
static private Sort instance;
|
||||
|
||||
private TimSort timSort;
|
||||
private ComparableTimSort comparableTimSort;
|
||||
|
||||
public <T> void sort (Array<T> a) {
|
||||
if (comparableTimSort == null) comparableTimSort = new ComparableTimSort();
|
||||
comparableTimSort.doSort((Object[])a.items, 0, a.size);
|
||||
}
|
||||
|
||||
public <T> void sort (T[] a) {
|
||||
if (comparableTimSort == null) comparableTimSort = new ComparableTimSort();
|
||||
comparableTimSort.doSort(a, 0, a.length);
|
||||
}
|
||||
|
||||
public <T> void sort (T[] a, int fromIndex, int toIndex) {
|
||||
if (comparableTimSort == null) comparableTimSort = new ComparableTimSort();
|
||||
comparableTimSort.doSort(a, fromIndex, toIndex);
|
||||
}
|
||||
|
||||
public <T> void sort (Array<T> a, Comparator<? super T> c) {
|
||||
if (timSort == null) timSort = new TimSort();
|
||||
timSort.doSort((Object[])a.items, (Comparator)c, 0, a.size);
|
||||
}
|
||||
|
||||
public <T> void sort (T[] a, Comparator<? super T> c) {
|
||||
if (timSort == null) timSort = new TimSort();
|
||||
timSort.doSort(a, c, 0, a.length);
|
||||
}
|
||||
|
||||
public <T> void sort (T[] a, Comparator<? super T> c, int fromIndex, int toIndex) {
|
||||
if (timSort == null) timSort = new TimSort();
|
||||
timSort.doSort(a, c, fromIndex, toIndex);
|
||||
}
|
||||
|
||||
/** Returns a Sort instance for convenience. Multiple threads must not use this instance at the same time. */
|
||||
static public Sort instance () {
|
||||
if (instance == null) instance = new Sort();
|
||||
return instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,840 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
|
||||
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
/** A stable, adaptive, iterative mergesort that requires far fewer than n lg(n) comparisons when running on partially sorted
|
||||
* arrays, while offering performance comparable to a traditional mergesort when run on random arrays. Like all proper mergesorts,
|
||||
* this sort is stable and runs O(n log n) time (worst case). In the worst case, this sort requires temporary storage space for
|
||||
* n/2 object references; in the best case, it requires only a small constant amount of space.
|
||||
*
|
||||
* This implementation was adapted from Tim Peters's list sort for Python, which is described in detail here:
|
||||
*
|
||||
* http://svn.python.org/projects/python/trunk/Objects/listsort.txt
|
||||
*
|
||||
* Tim's C code may be found here:
|
||||
*
|
||||
* http://svn.python.org/projects/python/trunk/Objects/listobject.c
|
||||
*
|
||||
* The underlying techniques are described in this paper (and may have even earlier origins):
|
||||
*
|
||||
* "Optimistic Sorting and Information Theoretic Complexity" Peter McIlroy SODA (Fourth Annual ACM-SIAM Symposium on Discrete
|
||||
* Algorithms), pp 467-474, Austin, Texas, 25-27 January 1993.
|
||||
*
|
||||
* While the API to this class consists solely of static methods, it is (privately) instantiable; a TimSort instance holds the
|
||||
* state of an ongoing sort, assuming the input array is large enough to warrant the full-blown TimSort. Small arrays are sorted
|
||||
* in place, using a binary insertion sort. */
|
||||
@SuppressWarnings("unchecked")
|
||||
class TimSort<T> {
|
||||
/** This is the minimum sized sequence that will be merged. Shorter sequences will be lengthened by calling binarySort. If the
|
||||
* entire array is less than this length, no merges will be performed.
|
||||
*
|
||||
* This constant should be a power of two. It was 64 in Tim Peter's C implementation, but 32 was empirically determined to work
|
||||
* better in this implementation. In the unlikely event that you set this constant to be a number that's not a power of two,
|
||||
* you'll need to change the {@link #minRunLength} computation.
|
||||
*
|
||||
* If you decrease this constant, you must change the stackLen computation in the TimSort constructor, or you risk an
|
||||
* ArrayOutOfBounds exception. See listsort.txt for a discussion of the minimum stack length required as a function of the
|
||||
* length of the array being sorted and the minimum merge sequence length. */
|
||||
private static final int MIN_MERGE = 32;
|
||||
|
||||
/** The array being sorted. */
|
||||
private T[] a;
|
||||
|
||||
/** The comparator for this sort. */
|
||||
private Comparator<? super T> c;
|
||||
|
||||
/** When we get into galloping mode, we stay there until both runs win less often than MIN_GALLOP consecutive times. */
|
||||
private static final int MIN_GALLOP = 7;
|
||||
|
||||
/** This controls when we get *into* galloping mode. It is initialized to MIN_GALLOP. The mergeLo and mergeHi methods nudge it
|
||||
* higher for random data, and lower for highly structured data. */
|
||||
private int minGallop = MIN_GALLOP;
|
||||
|
||||
/** Maximum initial size of tmp array, which is used for merging. The array can grow to accommodate demand.
|
||||
*
|
||||
* Unlike Tim's original C version, we do not allocate this much storage when sorting smaller arrays. This change was required
|
||||
* for performance. */
|
||||
private static final int INITIAL_TMP_STORAGE_LENGTH = 256;
|
||||
|
||||
/** Temp storage for merges. */
|
||||
private T[] tmp; // Actual runtime type will be Object[], regardless of T
|
||||
private int tmpCount;
|
||||
|
||||
/** A stack of pending runs yet to be merged. Run i starts at address base[i] and extends for len[i] elements. It's always true
|
||||
* (so long as the indices are in bounds) that:
|
||||
*
|
||||
* runBase[i] + runLen[i] == runBase[i + 1]
|
||||
*
|
||||
* so we could cut the storage for this, but it's a minor amount, and keeping all the info explicit simplifies the code. */
|
||||
private int stackSize = 0; // Number of pending runs on stack
|
||||
private final int[] runBase;
|
||||
private final int[] runLen;
|
||||
|
||||
/** Asserts have been placed in if-statements for performance. To enable them, set this field to true and enable them in VM with
|
||||
* a command line flag. If you modify this class, please do test the asserts! */
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
TimSort () {
|
||||
tmp = (T[])new Object[INITIAL_TMP_STORAGE_LENGTH];
|
||||
runBase = new int[40];
|
||||
runLen = new int[40];
|
||||
}
|
||||
|
||||
public void doSort (T[] a, Comparator<T> c, int lo, int hi) {
|
||||
stackSize = 0;
|
||||
rangeCheck(a.length, lo, hi);
|
||||
int nRemaining = hi - lo;
|
||||
if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted
|
||||
|
||||
// If array is small, do a "mini-TimSort" with no merges
|
||||
if (nRemaining < MIN_MERGE) {
|
||||
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
|
||||
binarySort(a, lo, hi, lo + initRunLen, c);
|
||||
return;
|
||||
}
|
||||
|
||||
this.a = a;
|
||||
this.c = c;
|
||||
tmpCount = 0;
|
||||
|
||||
/** March over the array once, left to right, finding natural runs, extending short natural runs to minRun elements, and
|
||||
* merging runs to maintain stack invariant. */
|
||||
int minRun = minRunLength(nRemaining);
|
||||
do {
|
||||
// Identify next run
|
||||
int runLen = countRunAndMakeAscending(a, lo, hi, c);
|
||||
|
||||
// If run is short, extend to min(minRun, nRemaining)
|
||||
if (runLen < minRun) {
|
||||
int force = nRemaining <= minRun ? nRemaining : minRun;
|
||||
binarySort(a, lo, lo + force, lo + runLen, c);
|
||||
runLen = force;
|
||||
}
|
||||
|
||||
// Push run onto pending-run stack, and maybe merge
|
||||
pushRun(lo, runLen);
|
||||
mergeCollapse();
|
||||
|
||||
// Advance to find next run
|
||||
lo += runLen;
|
||||
nRemaining -= runLen;
|
||||
} while (nRemaining != 0);
|
||||
|
||||
// Merge all remaining runs to complete sort
|
||||
if (DEBUG) assert lo == hi;
|
||||
mergeForceCollapse();
|
||||
if (DEBUG) assert stackSize == 1;
|
||||
|
||||
this.a = null;
|
||||
this.c = null;
|
||||
T[] tmp = this.tmp;
|
||||
for (int i = 0, n = tmpCount; i < n; i++)
|
||||
tmp[i] = null;
|
||||
}
|
||||
|
||||
/** Creates a TimSort instance to maintain the state of an ongoing sort.
|
||||
*
|
||||
* @param a the array to be sorted
|
||||
* @param c the comparator to determine the order of the sort */
|
||||
private TimSort (T[] a, Comparator<? super T> c) {
|
||||
this.a = a;
|
||||
this.c = c;
|
||||
|
||||
// Allocate temp storage (which may be increased later if necessary)
|
||||
int len = a.length;
|
||||
T[] newArray = (T[])new Object[len < 2 * INITIAL_TMP_STORAGE_LENGTH ? len >>> 1 : INITIAL_TMP_STORAGE_LENGTH];
|
||||
tmp = newArray;
|
||||
|
||||
/*
|
||||
* Allocate runs-to-be-merged stack (which cannot be expanded). The stack length requirements are described in listsort.txt.
|
||||
* The C version always uses the same stack length (85), but this was measured to be too expensive when sorting "mid-sized"
|
||||
* arrays (e.g., 100 elements) in Java. Therefore, we use smaller (but sufficiently large) stack lengths for smaller arrays.
|
||||
* The "magic numbers" in the computation below must be changed if MIN_MERGE is decreased. See the MIN_MERGE declaration
|
||||
* above for more information.
|
||||
*/
|
||||
int stackLen = (len < 120 ? 5 : len < 1542 ? 10 : len < 119151 ? 19 : 40);
|
||||
runBase = new int[stackLen];
|
||||
runLen = new int[stackLen];
|
||||
}
|
||||
|
||||
/*
|
||||
* The next two methods (which are package private and static) constitute the entire API of this class. Each of these methods
|
||||
* obeys the contract of the public method with the same signature in java.util.Arrays.
|
||||
*/
|
||||
|
||||
static <T> void sort (T[] a, Comparator<? super T> c) {
|
||||
sort(a, 0, a.length, c);
|
||||
}
|
||||
|
||||
static <T> void sort (T[] a, int lo, int hi, Comparator<? super T> c) {
|
||||
if (c == null) {
|
||||
Arrays.sort(a, lo, hi);
|
||||
return;
|
||||
}
|
||||
|
||||
rangeCheck(a.length, lo, hi);
|
||||
int nRemaining = hi - lo;
|
||||
if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted
|
||||
|
||||
// If array is small, do a "mini-TimSort" with no merges
|
||||
if (nRemaining < MIN_MERGE) {
|
||||
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
|
||||
binarySort(a, lo, hi, lo + initRunLen, c);
|
||||
return;
|
||||
}
|
||||
|
||||
/** March over the array once, left to right, finding natural runs, extending short natural runs to minRun elements, and
|
||||
* merging runs to maintain stack invariant. */
|
||||
TimSort<T> ts = new TimSort<T>(a, c);
|
||||
int minRun = minRunLength(nRemaining);
|
||||
do {
|
||||
// Identify next run
|
||||
int runLen = countRunAndMakeAscending(a, lo, hi, c);
|
||||
|
||||
// If run is short, extend to min(minRun, nRemaining)
|
||||
if (runLen < minRun) {
|
||||
int force = nRemaining <= minRun ? nRemaining : minRun;
|
||||
binarySort(a, lo, lo + force, lo + runLen, c);
|
||||
runLen = force;
|
||||
}
|
||||
|
||||
// Push run onto pending-run stack, and maybe merge
|
||||
ts.pushRun(lo, runLen);
|
||||
ts.mergeCollapse();
|
||||
|
||||
// Advance to find next run
|
||||
lo += runLen;
|
||||
nRemaining -= runLen;
|
||||
} while (nRemaining != 0);
|
||||
|
||||
// Merge all remaining runs to complete sort
|
||||
if (DEBUG) assert lo == hi;
|
||||
ts.mergeForceCollapse();
|
||||
if (DEBUG) assert ts.stackSize == 1;
|
||||
}
|
||||
|
||||
/** Sorts the specified portion of the specified array using a binary insertion sort. This is the best method for sorting small
|
||||
* numbers of elements. It requires O(n log n) compares, but O(n^2) data movement (worst case).
|
||||
*
|
||||
* If the initial part of the specified range is already sorted, this method can take advantage of it: the method assumes that
|
||||
* the elements from index {@code lo}, inclusive, to {@code start}, exclusive are already sorted.
|
||||
*
|
||||
* @param a the array in which a range is to be sorted
|
||||
* @param lo the index of the first element in the range to be sorted
|
||||
* @param hi the index after the last element in the range to be sorted
|
||||
* @param start the index of the first element in the range that is not already known to be sorted (@code lo <= start <= hi}
|
||||
* @param c comparator to used for the sort */
|
||||
@SuppressWarnings("fallthrough")
|
||||
private static <T> void binarySort (T[] a, int lo, int hi, int start, Comparator<? super T> c) {
|
||||
if (DEBUG) assert lo <= start && start <= hi;
|
||||
if (start == lo) start++;
|
||||
for (; start < hi; start++) {
|
||||
T pivot = a[start];
|
||||
|
||||
// Set left (and right) to the index where a[start] (pivot) belongs
|
||||
int left = lo;
|
||||
int right = start;
|
||||
if (DEBUG) assert left <= right;
|
||||
/*
|
||||
* Invariants: pivot >= all in [lo, left). pivot < all in [right, start).
|
||||
*/
|
||||
while (left < right) {
|
||||
int mid = (left + right) >>> 1;
|
||||
if (c.compare(pivot, a[mid]) < 0)
|
||||
right = mid;
|
||||
else
|
||||
left = mid + 1;
|
||||
}
|
||||
if (DEBUG) assert left == right;
|
||||
|
||||
/*
|
||||
* The invariants still hold: pivot >= all in [lo, left) and pivot < all in [left, start), so pivot belongs at left. Note
|
||||
* that if there are elements equal to pivot, left points to the first slot after them -- that's why this sort is stable.
|
||||
* Slide elements over to make room for pivot.
|
||||
*/
|
||||
int n = start - left; // The number of elements to move
|
||||
// Switch is just an optimization for arraycopy in default case
|
||||
switch (n) {
|
||||
case 2:
|
||||
a[left + 2] = a[left + 1];
|
||||
case 1:
|
||||
a[left + 1] = a[left];
|
||||
break;
|
||||
default:
|
||||
System.arraycopy(a, left, a, left + 1, n);
|
||||
}
|
||||
a[left] = pivot;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the length of the run beginning at the specified position in the specified array and reverses the run if it is
|
||||
* descending (ensuring that the run will always be ascending when the method returns).
|
||||
*
|
||||
* A run is the longest ascending sequence with:
|
||||
*
|
||||
* a[lo] <= a[lo + 1] <= a[lo + 2] <= ...
|
||||
*
|
||||
* or the longest descending sequence with:
|
||||
*
|
||||
* a[lo] > a[lo + 1] > a[lo + 2] > ...
|
||||
*
|
||||
* For its intended use in a stable mergesort, the strictness of the definition of "descending" is needed so that the call can
|
||||
* safely reverse a descending sequence without violating stability.
|
||||
*
|
||||
* @param a the array in which a run is to be counted and possibly reversed
|
||||
* @param lo index of the first element in the run
|
||||
* @param hi index after the last element that may be contained in the run. It is required that @code{lo < hi}.
|
||||
* @param c the comparator to used for the sort
|
||||
* @return the length of the run beginning at the specified position in the specified array */
|
||||
private static <T> int countRunAndMakeAscending (T[] a, int lo, int hi, Comparator<? super T> c) {
|
||||
if (DEBUG) assert lo < hi;
|
||||
int runHi = lo + 1;
|
||||
if (runHi == hi) return 1;
|
||||
|
||||
// Find end of run, and reverse range if descending
|
||||
if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
|
||||
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
|
||||
runHi++;
|
||||
reverseRange(a, lo, runHi);
|
||||
} else { // Ascending
|
||||
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
|
||||
runHi++;
|
||||
}
|
||||
|
||||
return runHi - lo;
|
||||
}
|
||||
|
||||
/** Reverse the specified range of the specified array.
|
||||
*
|
||||
* @param a the array in which a range is to be reversed
|
||||
* @param lo the index of the first element in the range to be reversed
|
||||
* @param hi the index after the last element in the range to be reversed */
|
||||
private static void reverseRange (Object[] a, int lo, int hi) {
|
||||
hi--;
|
||||
while (lo < hi) {
|
||||
Object t = a[lo];
|
||||
a[lo++] = a[hi];
|
||||
a[hi--] = t;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the minimum acceptable run length for an array of the specified length. Natural runs shorter than this will be
|
||||
* extended with {@link #binarySort}.
|
||||
*
|
||||
* Roughly speaking, the computation is:
|
||||
*
|
||||
* If n < MIN_MERGE, return n (it's too small to bother with fancy stuff). Else if n is an exact power of 2, return
|
||||
* MIN_MERGE/2. Else return an int k, MIN_MERGE/2 <= k <= MIN_MERGE, such that n/k is close to, but strictly less than, an
|
||||
* exact power of 2.
|
||||
*
|
||||
* For the rationale, see listsort.txt.
|
||||
*
|
||||
* @param n the length of the array to be sorted
|
||||
* @return the length of the minimum run to be merged */
|
||||
private static int minRunLength (int n) {
|
||||
if (DEBUG) assert n >= 0;
|
||||
int r = 0; // Becomes 1 if any 1 bits are shifted off
|
||||
while (n >= MIN_MERGE) {
|
||||
r |= (n & 1);
|
||||
n >>= 1;
|
||||
}
|
||||
return n + r;
|
||||
}
|
||||
|
||||
/** Pushes the specified run onto the pending-run stack.
|
||||
*
|
||||
* @param runBase index of the first element in the run
|
||||
* @param runLen the number of elements in the run */
|
||||
private void pushRun (int runBase, int runLen) {
|
||||
this.runBase[stackSize] = runBase;
|
||||
this.runLen[stackSize] = runLen;
|
||||
stackSize++;
|
||||
}
|
||||
|
||||
/** Examines the stack of runs waiting to be merged and merges adjacent runs until the stack invariants are reestablished:
|
||||
*
|
||||
* 1. runLen[n - 2] > runLen[n - 1] + runLen[n] 2. runLen[n - 1] > runLen[n]
|
||||
*
|
||||
* where n is the index of the last run in runLen.
|
||||
*
|
||||
* This method has been formally verified to be correct after checking the last 4 runs.
|
||||
* Checking for 3 runs results in an exception for large arrays.
|
||||
* (Source: http://envisage-project.eu/proving-android-java-and-python-sorting-algorithm-is-broken-and-how-to-fix-it/)
|
||||
*
|
||||
* This method is called each time a new run is pushed onto the stack, so the invariants are guaranteed to hold for i <
|
||||
* stackSize upon entry to the method. */
|
||||
private void mergeCollapse () {
|
||||
while (stackSize > 1) {
|
||||
int n = stackSize - 2;
|
||||
if ((n >= 1 && runLen[n - 1] <= runLen[n] + runLen[n + 1]) || (n >= 2 && runLen[n - 2] <= runLen[n] + runLen[n - 1])) {
|
||||
if (runLen[n - 1] < runLen[n + 1]) n--;
|
||||
} else if (runLen[n] > runLen[n + 1]) {
|
||||
break; // Invariant is established
|
||||
}
|
||||
mergeAt(n);
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges all runs on the stack until only one remains. This method is called once, to complete the sort. */
|
||||
private void mergeForceCollapse () {
|
||||
while (stackSize > 1) {
|
||||
int n = stackSize - 2;
|
||||
if (n > 0 && runLen[n - 1] < runLen[n + 1]) n--;
|
||||
mergeAt(n);
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges the two runs at stack indices i and i+1. Run i must be the penultimate or antepenultimate run on the stack. In other
|
||||
* words, i must be equal to stackSize-2 or stackSize-3.
|
||||
*
|
||||
* @param i stack index of the first of the two runs to merge */
|
||||
private void mergeAt (int i) {
|
||||
if (DEBUG) assert stackSize >= 2;
|
||||
if (DEBUG) assert i >= 0;
|
||||
if (DEBUG) assert i == stackSize - 2 || i == stackSize - 3;
|
||||
|
||||
int base1 = runBase[i];
|
||||
int len1 = runLen[i];
|
||||
int base2 = runBase[i + 1];
|
||||
int len2 = runLen[i + 1];
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0;
|
||||
if (DEBUG) assert base1 + len1 == base2;
|
||||
|
||||
/*
|
||||
* Record the length of the combined runs; if i is the 3rd-last run now, also slide over the last run (which isn't involved
|
||||
* in this merge). The current run (i+1) goes away in any case.
|
||||
*/
|
||||
runLen[i] = len1 + len2;
|
||||
if (i == stackSize - 3) {
|
||||
runBase[i + 1] = runBase[i + 2];
|
||||
runLen[i + 1] = runLen[i + 2];
|
||||
}
|
||||
stackSize--;
|
||||
|
||||
/*
|
||||
* Find where the first element of run2 goes in run1. Prior elements in run1 can be ignored (because they're already in
|
||||
* place).
|
||||
*/
|
||||
int k = gallopRight(a[base2], a, base1, len1, 0, c);
|
||||
if (DEBUG) assert k >= 0;
|
||||
base1 += k;
|
||||
len1 -= k;
|
||||
if (len1 == 0) return;
|
||||
|
||||
/*
|
||||
* Find where the last element of run1 goes in run2. Subsequent elements in run2 can be ignored (because they're already in
|
||||
* place).
|
||||
*/
|
||||
len2 = gallopLeft(a[base1 + len1 - 1], a, base2, len2, len2 - 1, c);
|
||||
if (DEBUG) assert len2 >= 0;
|
||||
if (len2 == 0) return;
|
||||
|
||||
// Merge remaining runs, using tmp array with min(len1, len2) elements
|
||||
if (len1 <= len2)
|
||||
mergeLo(base1, len1, base2, len2);
|
||||
else
|
||||
mergeHi(base1, len1, base2, len2);
|
||||
}
|
||||
|
||||
/** Locates the position at which to insert the specified key into the specified sorted range; if the range contains an element
|
||||
* equal to key, returns the index of the leftmost equal element.
|
||||
*
|
||||
* @param key the key whose insertion point to search for
|
||||
* @param a the array in which to search
|
||||
* @param base the index of the first element in the range
|
||||
* @param len the length of the range; must be > 0
|
||||
* @param hint the index at which to begin the search, 0 <= hint < n. The closer hint is to the result, the faster this method
|
||||
* will run.
|
||||
* @param c the comparator used to order the range, and to search
|
||||
* @return the int k, 0 <= k <= n such that a[b + k - 1] < key <= a[b + k], pretending that a[b - 1] is minus infinity and a[b
|
||||
* + n] is infinity. In other words, key belongs at index b + k; or in other words, the first k elements of a should
|
||||
* precede key, and the last n - k should follow it. */
|
||||
private static <T> int gallopLeft (T key, T[] a, int base, int len, int hint, Comparator<? super T> c) {
|
||||
if (DEBUG) assert len > 0 && hint >= 0 && hint < len;
|
||||
int lastOfs = 0;
|
||||
int ofs = 1;
|
||||
if (c.compare(key, a[base + hint]) > 0) {
|
||||
// Gallop right until a[base+hint+lastOfs] < key <= a[base+hint+ofs]
|
||||
int maxOfs = len - hint;
|
||||
while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) > 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to base
|
||||
lastOfs += hint;
|
||||
ofs += hint;
|
||||
} else { // key <= a[base + hint]
|
||||
// Gallop left until a[base+hint-ofs] < key <= a[base+hint-lastOfs]
|
||||
final int maxOfs = hint + 1;
|
||||
while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) <= 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to base
|
||||
int tmp = lastOfs;
|
||||
lastOfs = hint - ofs;
|
||||
ofs = hint - tmp;
|
||||
}
|
||||
if (DEBUG) assert -1 <= lastOfs && lastOfs < ofs && ofs <= len;
|
||||
|
||||
/*
|
||||
* Now a[base+lastOfs] < key <= a[base+ofs], so key belongs somewhere to the right of lastOfs but no farther right than ofs.
|
||||
* Do a binary search, with invariant a[base + lastOfs - 1] < key <= a[base + ofs].
|
||||
*/
|
||||
lastOfs++;
|
||||
while (lastOfs < ofs) {
|
||||
int m = lastOfs + ((ofs - lastOfs) >>> 1);
|
||||
|
||||
if (c.compare(key, a[base + m]) > 0)
|
||||
lastOfs = m + 1; // a[base + m] < key
|
||||
else
|
||||
ofs = m; // key <= a[base + m]
|
||||
}
|
||||
if (DEBUG) assert lastOfs == ofs; // so a[base + ofs - 1] < key <= a[base + ofs]
|
||||
return ofs;
|
||||
}
|
||||
|
||||
/** Like gallopLeft, except that if the range contains an element equal to key, gallopRight returns the index after the
|
||||
* rightmost equal element.
|
||||
*
|
||||
* @param key the key whose insertion point to search for
|
||||
* @param a the array in which to search
|
||||
* @param base the index of the first element in the range
|
||||
* @param len the length of the range; must be > 0
|
||||
* @param hint the index at which to begin the search, 0 <= hint < n. The closer hint is to the result, the faster this method
|
||||
* will run.
|
||||
* @param c the comparator used to order the range, and to search
|
||||
* @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] */
|
||||
private static <T> int gallopRight (T key, T[] a, int base, int len, int hint, Comparator<? super T> c) {
|
||||
if (DEBUG) assert len > 0 && hint >= 0 && hint < len;
|
||||
|
||||
int ofs = 1;
|
||||
int lastOfs = 0;
|
||||
if (c.compare(key, a[base + hint]) < 0) {
|
||||
// Gallop left until a[b+hint - ofs] <= key < a[b+hint - lastOfs]
|
||||
int maxOfs = hint + 1;
|
||||
while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) < 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to b
|
||||
int tmp = lastOfs;
|
||||
lastOfs = hint - ofs;
|
||||
ofs = hint - tmp;
|
||||
} else { // a[b + hint] <= key
|
||||
// Gallop right until a[b+hint + lastOfs] <= key < a[b+hint + ofs]
|
||||
int maxOfs = len - hint;
|
||||
while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) >= 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to b
|
||||
lastOfs += hint;
|
||||
ofs += hint;
|
||||
}
|
||||
if (DEBUG) assert -1 <= lastOfs && lastOfs < ofs && ofs <= len;
|
||||
|
||||
/*
|
||||
* Now a[b + lastOfs] <= key < a[b + ofs], so key belongs somewhere to the right of lastOfs but no farther right than ofs.
|
||||
* Do a binary search, with invariant a[b + lastOfs - 1] <= key < a[b + ofs].
|
||||
*/
|
||||
lastOfs++;
|
||||
while (lastOfs < ofs) {
|
||||
int m = lastOfs + ((ofs - lastOfs) >>> 1);
|
||||
|
||||
if (c.compare(key, a[base + m]) < 0)
|
||||
ofs = m; // key < a[b + m]
|
||||
else
|
||||
lastOfs = m + 1; // a[b + m] <= key
|
||||
}
|
||||
if (DEBUG) assert lastOfs == ofs; // so a[b + ofs - 1] <= key < a[b + ofs]
|
||||
return ofs;
|
||||
}
|
||||
|
||||
/** Merges two adjacent runs in place, in a stable fashion. The first element of the first run must be greater than the first
|
||||
* element of the second run (a[base1] > a[base2]), and the last element of the first run (a[base1 + len1-1]) must be greater
|
||||
* than all elements of the second run.
|
||||
*
|
||||
* For performance, this method should be called only when len1 <= len2; its twin, mergeHi should be called if len1 >= len2.
|
||||
* (Either method may be called if len1 == len2.)
|
||||
*
|
||||
* @param base1 index of first element in first run to be merged
|
||||
* @param len1 length of first run to be merged (must be > 0)
|
||||
* @param base2 index of first element in second run to be merged (must be aBase + aLen)
|
||||
* @param len2 length of second run to be merged (must be > 0) */
|
||||
private void mergeLo (int base1, int len1, int base2, int len2) {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
|
||||
|
||||
// Copy first run into temp array
|
||||
T[] a = this.a; // For performance
|
||||
T[] tmp = ensureCapacity(len1);
|
||||
System.arraycopy(a, base1, tmp, 0, len1);
|
||||
|
||||
int cursor1 = 0; // Indexes into tmp array
|
||||
int cursor2 = base2; // Indexes int a
|
||||
int dest = base1; // Indexes int a
|
||||
|
||||
// Move first element of second run and deal with degenerate cases
|
||||
a[dest++] = a[cursor2++];
|
||||
if (--len2 == 0) {
|
||||
System.arraycopy(tmp, cursor1, a, dest, len1);
|
||||
return;
|
||||
}
|
||||
if (len1 == 1) {
|
||||
System.arraycopy(a, cursor2, a, dest, len2);
|
||||
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
|
||||
return;
|
||||
}
|
||||
|
||||
Comparator<? super T> c = this.c; // Use local variable for performance
|
||||
int minGallop = this.minGallop; // " " " " "
|
||||
outer:
|
||||
while (true) {
|
||||
int count1 = 0; // Number of times in a row that first run won
|
||||
int count2 = 0; // Number of times in a row that second run won
|
||||
|
||||
/*
|
||||
* Do the straightforward thing until (if ever) one run starts winning consistently.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 1 && len2 > 0;
|
||||
if (c.compare(a[cursor2], tmp[cursor1]) < 0) {
|
||||
a[dest++] = a[cursor2++];
|
||||
count2++;
|
||||
count1 = 0;
|
||||
if (--len2 == 0) break outer;
|
||||
} else {
|
||||
a[dest++] = tmp[cursor1++];
|
||||
count1++;
|
||||
count2 = 0;
|
||||
if (--len1 == 1) break outer;
|
||||
}
|
||||
} while ((count1 | count2) < minGallop);
|
||||
|
||||
/*
|
||||
* One run is winning so consistently that galloping may be a huge win. So try that, and continue galloping until (if
|
||||
* ever) neither run appears to be winning consistently anymore.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 1 && len2 > 0;
|
||||
count1 = gallopRight(a[cursor2], tmp, cursor1, len1, 0, c);
|
||||
if (count1 != 0) {
|
||||
System.arraycopy(tmp, cursor1, a, dest, count1);
|
||||
dest += count1;
|
||||
cursor1 += count1;
|
||||
len1 -= count1;
|
||||
if (len1 <= 1) // len1 == 1 || len1 == 0
|
||||
break outer;
|
||||
}
|
||||
a[dest++] = a[cursor2++];
|
||||
if (--len2 == 0) break outer;
|
||||
|
||||
count2 = gallopLeft(tmp[cursor1], a, cursor2, len2, 0, c);
|
||||
if (count2 != 0) {
|
||||
System.arraycopy(a, cursor2, a, dest, count2);
|
||||
dest += count2;
|
||||
cursor2 += count2;
|
||||
len2 -= count2;
|
||||
if (len2 == 0) break outer;
|
||||
}
|
||||
a[dest++] = tmp[cursor1++];
|
||||
if (--len1 == 1) break outer;
|
||||
minGallop--;
|
||||
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
|
||||
if (minGallop < 0) minGallop = 0;
|
||||
minGallop += 2; // Penalize for leaving gallop mode
|
||||
} // End of "outer" loop
|
||||
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
|
||||
|
||||
if (len1 == 1) {
|
||||
if (DEBUG) assert len2 > 0;
|
||||
System.arraycopy(a, cursor2, a, dest, len2);
|
||||
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
|
||||
} else if (len1 == 0) {
|
||||
throw new IllegalArgumentException("Comparison method violates its general contract!");
|
||||
} else {
|
||||
if (DEBUG) assert len2 == 0;
|
||||
if (DEBUG) assert len1 > 1;
|
||||
System.arraycopy(tmp, cursor1, a, dest, len1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Like mergeLo, except that this method should be called only if len1 >= len2; mergeLo should be called if len1 <= len2.
|
||||
* (Either method may be called if len1 == len2.)
|
||||
*
|
||||
* @param base1 index of first element in first run to be merged
|
||||
* @param len1 length of first run to be merged (must be > 0)
|
||||
* @param base2 index of first element in second run to be merged (must be aBase + aLen)
|
||||
* @param len2 length of second run to be merged (must be > 0) */
|
||||
private void mergeHi (int base1, int len1, int base2, int len2) {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
|
||||
|
||||
// Copy second run into temp array
|
||||
T[] a = this.a; // For performance
|
||||
T[] tmp = ensureCapacity(len2);
|
||||
System.arraycopy(a, base2, tmp, 0, len2);
|
||||
|
||||
int cursor1 = base1 + len1 - 1; // Indexes into a
|
||||
int cursor2 = len2 - 1; // Indexes into tmp array
|
||||
int dest = base2 + len2 - 1; // Indexes into a
|
||||
|
||||
// Move last element of first run and deal with degenerate cases
|
||||
a[dest--] = a[cursor1--];
|
||||
if (--len1 == 0) {
|
||||
System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2);
|
||||
return;
|
||||
}
|
||||
if (len2 == 1) {
|
||||
dest -= len1;
|
||||
cursor1 -= len1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, len1);
|
||||
a[dest] = tmp[cursor2];
|
||||
return;
|
||||
}
|
||||
|
||||
Comparator<? super T> c = this.c; // Use local variable for performance
|
||||
int minGallop = this.minGallop; // " " " " "
|
||||
outer:
|
||||
while (true) {
|
||||
int count1 = 0; // Number of times in a row that first run won
|
||||
int count2 = 0; // Number of times in a row that second run won
|
||||
|
||||
/*
|
||||
* Do the straightforward thing until (if ever) one run appears to win consistently.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 1;
|
||||
if (c.compare(tmp[cursor2], a[cursor1]) < 0) {
|
||||
a[dest--] = a[cursor1--];
|
||||
count1++;
|
||||
count2 = 0;
|
||||
if (--len1 == 0) break outer;
|
||||
} else {
|
||||
a[dest--] = tmp[cursor2--];
|
||||
count2++;
|
||||
count1 = 0;
|
||||
if (--len2 == 1) break outer;
|
||||
}
|
||||
} while ((count1 | count2) < minGallop);
|
||||
|
||||
/*
|
||||
* One run is winning so consistently that galloping may be a huge win. So try that, and continue galloping until (if
|
||||
* ever) neither run appears to be winning consistently anymore.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 1;
|
||||
count1 = len1 - gallopRight(tmp[cursor2], a, base1, len1, len1 - 1, c);
|
||||
if (count1 != 0) {
|
||||
dest -= count1;
|
||||
cursor1 -= count1;
|
||||
len1 -= count1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, count1);
|
||||
if (len1 == 0) break outer;
|
||||
}
|
||||
a[dest--] = tmp[cursor2--];
|
||||
if (--len2 == 1) break outer;
|
||||
|
||||
count2 = len2 - gallopLeft(a[cursor1], tmp, 0, len2, len2 - 1, c);
|
||||
if (count2 != 0) {
|
||||
dest -= count2;
|
||||
cursor2 -= count2;
|
||||
len2 -= count2;
|
||||
System.arraycopy(tmp, cursor2 + 1, a, dest + 1, count2);
|
||||
if (len2 <= 1) // len2 == 1 || len2 == 0
|
||||
break outer;
|
||||
}
|
||||
a[dest--] = a[cursor1--];
|
||||
if (--len1 == 0) break outer;
|
||||
minGallop--;
|
||||
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
|
||||
if (minGallop < 0) minGallop = 0;
|
||||
minGallop += 2; // Penalize for leaving gallop mode
|
||||
} // End of "outer" loop
|
||||
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
|
||||
|
||||
if (len2 == 1) {
|
||||
if (DEBUG) assert len1 > 0;
|
||||
dest -= len1;
|
||||
cursor1 -= len1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, len1);
|
||||
a[dest] = tmp[cursor2]; // Move first elt of run2 to front of merge
|
||||
} else if (len2 == 0) {
|
||||
throw new IllegalArgumentException("Comparison method violates its general contract!");
|
||||
} else {
|
||||
if (DEBUG) assert len1 == 0;
|
||||
if (DEBUG) assert len2 > 0;
|
||||
System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensures that the external array tmp has at least the specified number of elements, increasing its size if necessary. The
|
||||
* size increases exponentially to ensure amortized linear time complexity.
|
||||
*
|
||||
* @param minCapacity the minimum required capacity of the tmp array
|
||||
* @return tmp, whether or not it grew */
|
||||
private T[] ensureCapacity (int minCapacity) {
|
||||
tmpCount = Math.max(tmpCount, minCapacity);
|
||||
if (tmp.length < minCapacity) {
|
||||
// Compute smallest power of 2 > minCapacity
|
||||
int newSize = minCapacity;
|
||||
newSize |= newSize >> 1;
|
||||
newSize |= newSize >> 2;
|
||||
newSize |= newSize >> 4;
|
||||
newSize |= newSize >> 8;
|
||||
newSize |= newSize >> 16;
|
||||
newSize++;
|
||||
|
||||
if (newSize < 0) // Not bloody likely!
|
||||
newSize = minCapacity;
|
||||
else
|
||||
newSize = Math.min(newSize, a.length >>> 1);
|
||||
|
||||
T[] newArray = (T[])new Object[newSize];
|
||||
tmp = newArray;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/** Checks that fromIndex and toIndex are in range, and throws an appropriate exception if they aren't.
|
||||
*
|
||||
* @param arrayLen the length of the array
|
||||
* @param fromIndex the index of the first element of the range
|
||||
* @param toIndex the index after the last element of the range
|
||||
* @throws IllegalArgumentException if fromIndex > toIndex
|
||||
* @throws ArrayIndexOutOfBoundsException if fromIndex < 0 or toIndex > arrayLen */
|
||||
private static void rangeCheck (int arrayLen, int fromIndex, int toIndex) {
|
||||
if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
|
||||
if (fromIndex < 0) throw new ArrayIndexOutOfBoundsException(fromIndex);
|
||||
if (toIndex > arrayLen) throw new ArrayIndexOutOfBoundsException(toIndex);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,145 @@
|
|||
package dorkbox.collections.ahoCorasick
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Creates a Finite State Machine for very fast string matching.
|
||||
*
|
||||
* This is a wrapper for DoubleArrayTrie, since that class is awkward to use
|
||||
*/
|
||||
class FiniteStateMachine<V>(private val trie: DoubleArrayTrie<V>) {
|
||||
companion object {
|
||||
fun <V> build(map: Map<String, V>): FiniteStateMachine<V> {
|
||||
return FiniteStateMachine(DoubleArrayTrie(map))
|
||||
}
|
||||
|
||||
fun build(strings: List<String>): FiniteStateMachine<Boolean> {
|
||||
if (strings.isEmpty()) {
|
||||
throw IllegalArgumentException("strings cannot be empty")
|
||||
}
|
||||
|
||||
val map = TreeMap<String, Boolean>()
|
||||
for (key in strings) {
|
||||
map[key] = java.lang.Boolean.TRUE
|
||||
}
|
||||
|
||||
return build(map)
|
||||
}
|
||||
|
||||
fun build(vararg strings: String): FiniteStateMachine<Boolean> {
|
||||
if (strings.isEmpty()) {
|
||||
throw IllegalArgumentException("strings cannot be empty")
|
||||
}
|
||||
|
||||
val map = TreeMap<String, Boolean>()
|
||||
for (key in strings) {
|
||||
map[key] = java.lang.Boolean.TRUE
|
||||
}
|
||||
|
||||
return build(map)
|
||||
}
|
||||
|
||||
// @JvmStatic
|
||||
// fun main(args: Array<String>) {
|
||||
// val strings = arrayOf("khanacademy.com", "cnn.com", "google.com", "fun.reddit.com", "reddit.com")
|
||||
// val keys = Arrays.asList(*strings)
|
||||
// var text: String
|
||||
// run {
|
||||
// val map = TreeMap<String, String>()
|
||||
// for (key in keys) {
|
||||
// map[key] = key
|
||||
// }
|
||||
// val fsm: FiniteStateMachine<*> = build(map)
|
||||
// text = "reddit.google.com"
|
||||
// println("Searching : $text")
|
||||
// println(fsm.partialMatch(text))
|
||||
// println("Found: " + fsm.matches(text))
|
||||
// println()
|
||||
// text = "reddit.com"
|
||||
// println("Searching : $text")
|
||||
// println(fsm.partialMatch(text))
|
||||
// println("Found: " + fsm.matches(text))
|
||||
// println()
|
||||
// text = "fun.reddit.com"
|
||||
// println("Searching : $text")
|
||||
// println(fsm.partialMatch(text))
|
||||
// println("Found: " + fsm.matches(text))
|
||||
// }
|
||||
// println("\n\nTrying with new type\n\n")
|
||||
// run {
|
||||
// val fsm: FiniteStateMachine<*> = build(keys)
|
||||
// text = "reddit.google.com"
|
||||
// println("Searching : $text")
|
||||
// println(fsm.partialMatch(text))
|
||||
// println("Found: " + fsm.matches(text))
|
||||
// println()
|
||||
// text = "reddit.com"
|
||||
// println("Searching : $text")
|
||||
// println(fsm.partialMatch(text))
|
||||
// println("Found: " + fsm.matches(text))
|
||||
// println()
|
||||
// text = "fun.reddit.com"
|
||||
// println("Searching : $text")
|
||||
// println(fsm.partialMatch(text))
|
||||
// println("Found: " + fsm.matches(text))
|
||||
// }
|
||||
// println("\n\nTrying with new type\n\n")
|
||||
// run {
|
||||
// val fsm: FiniteStateMachine<*> = build(*strings)
|
||||
// text = "reddit.google.com"
|
||||
// println("Searching : $text")
|
||||
// println(fsm.partialMatch(text))
|
||||
// println("Found: " + fsm.matches(text))
|
||||
// println()
|
||||
// text = "reddit.com"
|
||||
// println("Searching : $text")
|
||||
// println(fsm.partialMatch(text))
|
||||
// println("Found: " + fsm.matches(text))
|
||||
// println()
|
||||
// text = "fun.reddit.com"
|
||||
// println("Searching : $text")
|
||||
// println(fsm.partialMatch(text))
|
||||
// println("Found: " + fsm.matches(text))
|
||||
// }
|
||||
// val fsm: FiniteStateMachine<*> = build(*strings)
|
||||
// run {
|
||||
// println("Keywords Orig: " + Arrays.toString(strings))
|
||||
// println("Keywords FSM : " + Arrays.toString(fsm.getKeywords()))
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this string is exactly contained. False otherwise
|
||||
*/
|
||||
fun matches(text: String): Boolean {
|
||||
return (trie.exactMatchSearch(text) > -1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses text and finds PARTIALLY matching results. For exact matches only it is better to use `matches`
|
||||
*
|
||||
* @return a list of outputs that contain matches or partial matches. The returned list will specify HOW MUCH of the text matches (A full match would be from 0 (the start), to N (the length of the text).
|
||||
*/
|
||||
fun partialMatch(text: String): List<DoubleArrayTrie.Hit<V>> {
|
||||
return trie.parseText(text)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses text and returns true if there are PARTIALLY matching results. For exact matches only it is better to use `matches`
|
||||
*
|
||||
* @return true if there is a match or partial match. "fun.reddit.com" will partially match to "reddit.com"
|
||||
*/
|
||||
fun hasPartialMatch(text: String): Boolean {
|
||||
return trie.parseText(text).isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the backing keywords IN THEIR NATURAL ORDER, in the case that you need access to the original FSM data.
|
||||
*
|
||||
* @return for example, if the FSM was populated with [reddit.com, cnn.com], this will return [cnn.com, reddit.com]
|
||||
*/
|
||||
fun getKeywords(): Array<V> {
|
||||
return trie.v
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* AhoCorasickDoubleArrayTrie Project
|
||||
* https://github.com/hankcs/AhoCorasickDoubleArrayTrie
|
||||
*
|
||||
* Copyright 2008-2018 hankcs <me@hankcs.com>
|
||||
* You may modify and redistribute as long as this attribution remains.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.collections.ahoCorasick
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* A state has the following functions
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* * success; successfully transferred to another state
|
||||
* * failure; if you cannot jump along the string, jump to a shallow node
|
||||
* * emits; hit a pattern string
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* The root node is slightly different. The root node has no failure function. Its "failure" refers to moving to the next state according to the string path. Other nodes have a failure state.
|
||||
*
|
||||
*
|
||||
* @author Robert Bor
|
||||
*/
|
||||
class State
|
||||
/**
|
||||
* Construct a node with a depth of depth
|
||||
*/
|
||||
@JvmOverloads constructor(
|
||||
/**
|
||||
* The length of the pattern string is also the depth of this state
|
||||
*/
|
||||
/**
|
||||
* Get node depth
|
||||
*/
|
||||
val depth: Int = 0) {
|
||||
|
||||
/**
|
||||
* The fail function, if there is no match, jumps to this state.
|
||||
*/
|
||||
private var failure: State? = null
|
||||
|
||||
/**
|
||||
* Record mode string as long as this state is reachable
|
||||
*/
|
||||
private var emits: MutableSet<Int>? = null
|
||||
/**
|
||||
* The goto table, also known as the transfer function. Move to the next state based on the next character of the string
|
||||
*/
|
||||
private val success = TreeMap<Char, State>()
|
||||
|
||||
/**
|
||||
* Corresponding subscript in double array
|
||||
*/
|
||||
var index: Int = 0
|
||||
|
||||
/**
|
||||
* Get the largest value
|
||||
*/
|
||||
val largestValueId: Int?
|
||||
get() = if (emits == null || emits!!.size == 0) {
|
||||
null
|
||||
}
|
||||
else emits!!.iterator().next()
|
||||
|
||||
/**
|
||||
* Whether it is the termination status
|
||||
*/
|
||||
val isAcceptable: Boolean
|
||||
get() = this.depth > 0 && this.emits != null
|
||||
|
||||
val states: Collection<State>
|
||||
get() = this.success.values
|
||||
|
||||
val transitions: Collection<Char>
|
||||
get() = this.success.keys
|
||||
|
||||
/**
|
||||
* Add a matching pattern string (this state corresponds to this pattern string)
|
||||
*/
|
||||
fun addEmit(keyword: Int) {
|
||||
if (this.emits == null) {
|
||||
this.emits = TreeSet(Collections.reverseOrder())
|
||||
}
|
||||
this.emits!!.add(keyword)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some matching pattern strings
|
||||
*/
|
||||
fun addEmit(emits: Collection<Int>) {
|
||||
for (emit in emits) {
|
||||
addEmit(emit)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pattern string represented by this node (we)
|
||||
*/
|
||||
fun emit(): Collection<Int> {
|
||||
return this.emits ?: emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the failure status
|
||||
*/
|
||||
fun failure(): State? {
|
||||
return this.failure
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the failure status
|
||||
*/
|
||||
fun setFailure(failState: State,
|
||||
fail: IntArray) {
|
||||
this.failure = failState
|
||||
fail[index] = failState.index
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to the next state
|
||||
*
|
||||
* @param character wants to transfer by this character
|
||||
* @param ignoreRootState Whether to ignore the root node, it should be true if the root node calls itself, otherwise it is false
|
||||
*
|
||||
* @return transfer result
|
||||
*/
|
||||
private fun nextState(character: Char,
|
||||
ignoreRootState: Boolean): State? {
|
||||
var nextState: State? = this.success[character]
|
||||
if (!ignoreRootState && nextState == null && this.depth == 0) {
|
||||
nextState = this
|
||||
}
|
||||
return nextState
|
||||
}
|
||||
|
||||
/**
|
||||
* According to the character transfer, the root node transfer failure will return itself (never return null)
|
||||
*/
|
||||
fun nextState(character: Char): State? {
|
||||
return nextState(character, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* According to character transfer, any node transfer failure will return null
|
||||
*/
|
||||
fun nextStateIgnoreRootState(character: Char): State? {
|
||||
return nextState(character, true)
|
||||
}
|
||||
|
||||
fun addState(character: Char): State {
|
||||
var nextState = nextStateIgnoreRootState(character)
|
||||
if (nextState == null) {
|
||||
nextState = State(this.depth + 1)
|
||||
this.success[character] = nextState
|
||||
}
|
||||
return nextState
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val sb = StringBuilder("State{")
|
||||
sb.append("depth=").append(depth)
|
||||
sb.append(", ID=").append(index)
|
||||
sb.append(", emits=").append(emits)
|
||||
sb.append(", success=").append(success.keys)
|
||||
sb.append(", failureID=").append(if (failure == null) "-1" else failure!!.index)
|
||||
sb.append(", failure=").append(failure)
|
||||
sb.append('}')
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get goto table
|
||||
*/
|
||||
fun getSuccess(): Map<Char, State> {
|
||||
return success
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Construct a node with a depth of 0
|
||||
*/
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.collections;
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.collections;
|
||||
|
||||
/**
|
||||
* Required for intellij to not complain regarding `module-info` for a multi-release jar.
|
||||
* This file is completely ignored by the gradle build process
|
||||
*/
|
||||
public
|
||||
class EmptyClass {}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dorkbox.collections.ahoCorasick;
|
||||
|
||||
/**
|
||||
* Required for intellij to not complain regarding `module-info` for a multi-release jar.
|
||||
* This file is completely ignored by the gradle build process
|
||||
*/
|
||||
public
|
||||
class EmptyClass {}
|
|
@ -0,0 +1,6 @@
|
|||
module dorkbox.collections {
|
||||
exports dorkbox.collections;
|
||||
exports dorkbox.collections.ahoCorasick;
|
||||
|
||||
requires java.base;
|
||||
}
|
Loading…
Reference in New Issue