diff --git a/packages/OrderedCollections/.codecov.yml b/packages/OrderedCollections/.codecov.yml new file mode 100644 index 0000000..69cb760 --- /dev/null +++ b/packages/OrderedCollections/.codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/packages/OrderedCollections/.github/workflows/CI.yml b/packages/OrderedCollections/.github/workflows/CI.yml new file mode 100644 index 0000000..8d43117 --- /dev/null +++ b/packages/OrderedCollections/.github/workflows/CI.yml @@ -0,0 +1,46 @@ +name: CI +on: + pull_request: + push: + branches: + - master + tags: '*' +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1.0' + - '1' +# - 'nightly' + os: + - ubuntu-latest + - macOS-latest + - windows-latest + arch: + - x64 + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v1 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v1 + with: + file: lcov.info diff --git a/packages/OrderedCollections/.github/workflows/Documenter.yml b/packages/OrderedCollections/.github/workflows/Documenter.yml new file mode 100644 index 0000000..586e7ef --- /dev/null +++ b/packages/OrderedCollections/.github/workflows/Documenter.yml @@ -0,0 +1,18 @@ +name: Documenter +on: + push: + branches: [master] + tags: [v*] + pull_request: + +jobs: + Documenter: + name: Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/julia-buildpkg@latest + - uses: julia-actions/julia-docdeploy@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/packages/OrderedCollections/.github/workflows/TagBot.yml b/packages/OrderedCollections/.github/workflows/TagBot.yml new file mode 100644 index 0000000..f49313b --- /dev/null +++ b/packages/OrderedCollections/.github/workflows/TagBot.yml @@ -0,0 +1,15 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/packages/OrderedCollections/.gitignore b/packages/OrderedCollections/.gitignore new file mode 100644 index 0000000..49b1c24 --- /dev/null +++ b/packages/OrderedCollections/.gitignore @@ -0,0 +1,4 @@ +doc/build +docs/build/ +docs/site/ +Manifest.toml diff --git a/packages/OrderedCollections/Changelog_DataStructures.md b/packages/OrderedCollections/Changelog_DataStructures.md new file mode 100644 index 0000000..3f3d3dc --- /dev/null +++ b/packages/OrderedCollections/Changelog_DataStructures.md @@ -0,0 +1,292 @@ + +0.7.0 / 2017-09-02 +================== + + * Drop support for Julia v0.5 (and update to v0.6/v0.7 syntax) + * Add some missing things to docs (#317) + * Remove additional v0.6 deprecations + * Fix a "formal" ambiguity on 0.6+ and enable ambiguity tests + * Remove Compat (not needed/used right now) + * Move all tests to testsets + +v0.6.1 / 2017-07-26 +================== + * Fix most of 0.7 depwarns + +v0.6.0 / 2017-07-09 +================== + * Fix depwarn on 0.7 + * Update CI URLs to point to new caching infrastructure + * Re-fix 0.6 depwarns + +v0.5.3 / 2017-02-21 +================== + * Julia v0.6 depwarn, ambiguity, and other misc fixes + * Fix 0.6 typealias depwarn + * Fix 0.6 abstract type declaration depwarn + * Fix 0.6 misc other depwarns + +v0.5.2 / 2017-01-18 +================== + * Julia 0.6 fixes + * Remove recently introduced TypeVars. + * Don't allow failure on nightly + +v0.5.1 / 2017-01-07 +================== + * Temporarily revert removal of HashDict (broke gadfly) + +v0.5.0 / 2017-01-05 +================== + * Changed OrderedDict implementation to Jeff Bezanson's version (from Julia #10116) + * Remove HashDict (no longer needed), refactor Dict-related classes + * Added more Dict-related tests + * Allow OrderedDicts to be sorted + * Fix xor deprecations + +v0.4.6 / 2016-07-28 +================== + * isdefined -> isassigned + +v0.4.5 / 2016-07-28 +================== + * Fixes for Julia v0.5 + * Exception type updates for + * Export complement if not available in Base + * Fix ASCIIString, UTF8String -> String deprecations + * Fix getfield deprecation + * Add RTD badge to Readme + +v0.4.4 / 2016-04-10 +================== + * rename files with underscores for consistency + * OrderedDict: use type parameters for constructor, rather than as parameters + * add various docstrings + * Remove spaces between {} and () in function/constructor definitions + +v0.4.3 / 2016-02-10 +================== + * Many deprecation warnings were deleted (https://github.com/JuliaLang/DataStructures.jl/pull/161) + * Ordered sets now have indexing + * Performance improvements to OrderedDict + + +v0.4.2 / 2016-01-13 +================== + + * Fix OrderedDict constructors (with tests) + * Dead code, tree.jl removal + +v0.4.1 / 2015-12-29 +=================== + + * Updated Changelog + * Merge pull request #156 from JuliaLang/kms/remove-v0.3-part2 + * Replace tuple_or_pair with Pair() or Pair{} + * More thorough removal of v0.3 support + * Updated Changelog.md + +v0.4.1 / 2015-12-29 +================== + + * More thorough removal of v0.3 support + * Replace tuple_or_pair with Pair() or Pair{} + +v0.4.0 / 2015-12-28 +=================== + + * Remove support for Julia 0.3 + +v0.3.14 / 2015-11-14 +==================== + + * OrderedDict: + * Implement merge for OrderedDict + * Serialize and deserialize + * Remove invalid rst and align elements + * Fix #34, implement `==` instead of `isequal` in places + * Define ==(x::Nil, y::Nil) and ==(x::Cons, y::Cons) + +v0.3.13 / 2015-09-18 +==================== + + * Julia v0.4 updates + * Union() -> Union{} + * 0.4 bindings deprecation + * Add operator imports to fix deprecation warnings + * Travis + * Run tests on 0.3, 0.4, and nightly (0.5) + * Enable osx + * (Re)enable codecov + * Add precompile directive + * Switched setindex! to insert! + * Fix Pair usage for OrderedDict + +v0.3.11 / 2015-07-14 +==================== + + * Fix deprecated syntax in OrderedSet test + * Updated README with extra DefaultDict examples + * More formatting updates to README.rst + * Remove syntax deprecation warnings on 0.4 + +v0.3.10 / 2015-06-29 +==================== + + * REQUIRE: bump Julia version to v0.3 + * Fix serialization ambiguity warnings + +v0.3.9 / 2015-05-03 +=================== + + * Fix error on 0.4-dev, allow running tests without installing + +v0.3.8 / 2015-04-18 +=================== + + * Add special OrderedDict deprection for Numbers + * Fix warning about {A, B...} + +v0.3.7 / 2015-04-17 +=================== + + * 0.4 Compat fixes + * Implement nlargest and nsmallest + +v0.3.6 / 2015-03-05 +=================== + + * Updated OrderedSet, OrderedDict tests + * Update OrderedDict, OrderedSet constructors to take iterables + * Use Julia 0.4 syntax + * Added compat support for Julia v0.3 + * Rewrite README in rst format (instead of md) + * Get coverage data generation back up for Coveralls + * Update Travis to use Julia Language Support + * use Base.warn_once() instead of warn() + * Support v0.4 style association construction via Pair operator + * Update syntax to avoid deprecation warnings on Julia 0.4 + * Consistent whitespace + +v0.3.4 / 2014-10-14 +=================== + + * Fix #60 + * Update Dict construction to use new syntax + * Fix signed/unsigned issue in hashindex + * Modernize Travis, Pkg.test compat, coverage, badges + +v0.3.2 / 2014-08-31 +=================== + + * Remove trailing whitespace + * Add more constructors for Trie + * Remove trailing whitespace + +v0.3.1 / 2014-07-14 +=================== + + * Update README + * Deprecate add\! in favor of push\! + +v0.3.0 / 2014-06-10 +=================== + + * Bump REQUIRE to v0.3, for incompatible change in test_throws + +v0.2.15 / 2014-06-10 +==================== + + * Revert "fix `@test_throw` warnings" + +v0.2.14 / 2014-06-02 +==================== + + * Import serialize_type in hashdict.jl + * Add some clarification on code examples + * fix `@test_throw` warnings + * use SVG logo for travis status + * rename run_tests.jl to runtests.jl + +v0.2.13 / 2014-05-08 +==================== + + * Revert "Remove unused code" + * Fix broken tests + +v0.2.12 / 2014-04-26 +==================== + + * Import Base.reverse + * Inserted missing comma + * Avoid stack overflow in length method. Use iterator in show method + * Changed name from add_singleton! to push! + * Update README.md + +v0.2.11 / 2014-04-10 +==================== + + * Update README.md (closes #24) + * Changed the name make_set to add_singleton + * import serialize, deserialize + * Clean up code. Follow Dict interface more closely. + * Added working test of make_set! + * Added make_set! to exports in DataStructures.jl + * Changed length(s.parents) to length(s) + * Added version of make_set! which automatically chooses the new element as the next available one + * Added ! to the name of the make_set function, since it modifies the structure + * Added make_set to add single element as a new disjoint set, with its parent equal to itself + * Implemented list iterator functions + * add list and binary tree. closes #17 + +v0.2.10 / 2014-03-02 +==================== + + * Revert "Update REQUIRE to julia v0.3" + +v0.2.9 / 2014-02-26 +=================== + + * Update REQUIRE to julia v0.3 + * Update README.md + * Fix travis config. Enable testing with releases. + * Change Travis badge url to JuliaLang + * README.md: OrderedDefaultDict -> DefaultOrderedDict + * fix C++ template syntax in README + * Added/updated various dictionary, set variants + * update travis.yml (disable apt-get upgrade) + * add classified counters + * add classified collections + +v0.2.5 / 2013-10-08 +=================== + + * improved benchmark scripts + +0.2.4 / 2013-07-27 +================== + + * add travis logo to readme + * add travis.yml + * use run_tests.jl in the place of test/test_all.jl + * Added 1 missing API call to the documentation + +0.2.3 / 2013-04-21 +================== + + * export in_same_set + +0.2.0 / 2013-04-15 +================== + + * add julia version requirement + * Test ==> Base.Test & add test_all.jl + * add empty REQUIRE file + * Update README.md + * add license + * add readme + * improved interface and added test + * renamed to DataStructures + * add stack and queue (tested) + * add Dequeue (tested) + * Initial commit diff --git a/packages/OrderedCollections/License.md b/packages/OrderedCollections/License.md new file mode 100644 index 0000000..34299fe --- /dev/null +++ b/packages/OrderedCollections/License.md @@ -0,0 +1,7 @@ +Copyright (c) 2013 Dahua Lin + +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. \ No newline at end of file diff --git a/packages/OrderedCollections/Project.toml b/packages/OrderedCollections/Project.toml new file mode 100644 index 0000000..9599d63 --- /dev/null +++ b/packages/OrderedCollections/Project.toml @@ -0,0 +1,14 @@ +name = "OrderedCollections" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.4.1" + +[compat] +julia = "0.7, 1" + +[extras] +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Serialization", "Test", "Random"] diff --git a/packages/OrderedCollections/README.md b/packages/OrderedCollections/README.md new file mode 100644 index 0000000..e04baf3 --- /dev/null +++ b/packages/OrderedCollections/README.md @@ -0,0 +1,20 @@ +[![Travis Build Status](https://travis-ci.org/JuliaCollections/OrderedCollections.jl.svg?branch=master)](https://travis-ci.org/JuliaCollections/OrderedCollections.jl) + +[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/5gw9xok4e58aixsv?svg=true)](https://ci.appveyor.com/project/kmsquire/datastructures-jl) +[![Test Coverage](https://codecov.io/github/JuliaCollections/OrderedCollections.jl/coverage.svg?branch=master)](https://codecov.io/github/JuliaCollections/OrderedCollections.jl?branch=master) + +[![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://juliacollections.github.io/OrderedCollections.jl/latest) + +OrderedCollections.jl +===================== + +This package implements OrderedDicts and OrderedSets, which are similar to containers in base Julia. +However, during iteration the Ordered* containers return items in the order in which they were added to the collection. +It also implements `LittleDict` which is a ordered dictionary, that is much faster than any other `AbstractDict` (ordered or not) for small collections. + +This package was split out from [DataStructures.jl](https://github.com/JuliaCollections/DataStructures.jl). + +Resources +--------- + +- **Documentation**: https://juliacollections.github.io/OrderedCollections.jl/latest diff --git a/packages/OrderedCollections/docs/make.jl b/packages/OrderedCollections/docs/make.jl new file mode 100644 index 0000000..a5d17d8 --- /dev/null +++ b/packages/OrderedCollections/docs/make.jl @@ -0,0 +1,21 @@ +using Documenter +using OrderedCollections + + +makedocs( + format = :html, + sitename = "OrderedCollections.jl", + pages = [ + "index.md", + "ordered_containers.md", + ] +) + +deploydocs( + repo = "github.com/JuliaCollections/OrderedCollections.jl.git", + julia = "0.7", + latest = "master", + target = "build", + deps = nothing, # we use the `format = :html`, without `mkdocs` + make = nothing, # we use the `format = :html`, without `mkdocs` +) diff --git a/packages/OrderedCollections/docs/mkdocs.yml b/packages/OrderedCollections/docs/mkdocs.yml new file mode 100644 index 0000000..4dc9753 --- /dev/null +++ b/packages/OrderedCollections/docs/mkdocs.yml @@ -0,0 +1,24 @@ +site_name: OrderedCollections.jl +repo_url: https://github.com/JuliaCollections/OrderedCollections.jl +site_description: Documentation for the Julia OrderedCollections package +site_author: JuliaCollections + +theme: readthedocs + +extra_css: + - assets/Documenter.css + +extra_javascript: + - https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS_HTML + - assets/mathjaxhelper.js + +markdown_extensions: + - extra + - tables + - fenced_code + - mdx_math + +docs_dir: 'build' + +pages: + - Home: index.md diff --git a/packages/OrderedCollections/docs/src/index.md b/packages/OrderedCollections/docs/src/index.md new file mode 100644 index 0000000..fe1341d --- /dev/null +++ b/packages/OrderedCollections/docs/src/index.md @@ -0,0 +1,16 @@ +# OrderedCollections.jl + +This package implements associative containers that preserve the order of insertion: + +- OrderedDict +- OrderedSet +- LittleDict + +## Contents + +```@contents +Pages = [ + "index.md", + "ordered_containers.md", +] +``` diff --git a/packages/OrderedCollections/docs/src/ordered_containers.md b/packages/OrderedCollections/docs/src/ordered_containers.md new file mode 100644 index 0000000..204a353 --- /dev/null +++ b/packages/OrderedCollections/docs/src/ordered_containers.md @@ -0,0 +1,69 @@ +# OrderedSets + +`OrderedSets` are sets whose entries have a particular order. +Order refers to *insertion order*, which allows deterministic +iteration over the set: + +```julia +using Base.MathConstants +s = OrderedSet((π,e,γ,catalan,φ)) +for x in s + println(x) +end +#> π = 3.1415926535897... +#> ℯ = 2.7182818284590... +#> γ = 0.5772156649015... +#> catalan = 0.9159655941772... +#> φ = 1.6180339887498... +``` +All `Set` operations are available for OrderedSets. + +Note that to create an OrderedSet of a particular type, you must +specify the type in curly-braces: + +```julia +# create an OrderedSet of Strings +strs = OrderedSet{AbstractString}() +``` +# OrderedDicts +Similarly, `OrderedDict` are simply dictionaries whose entries have a particular +order. +```julia +d = OrderedDict{Char,Int}() +for c in 'a':'d' + d[c] = c-'a'+1 +end +for x in d + println(x) +end +#> 'a' => 1 +#> 'b' => 2 +#> 'c' => 3 +#> 'd' => 4 +``` +The insertion order is conserved when iterating on the dictionary itself, +its keys (through `keys(d)`), or its values (through `values(d)`). +All standard `Associative` and `Dict` functions are available for `OrderedDicts` + +# LittleDict +```julia +d = LittleDict{Char,Int}() +for c in 'a':'d' + d[c] = c-'a'+1 +end +for x in d + println(x) +end +#> 'a' => 1 +#> 'b' => 2 +#> 'c' => 3 +#> 'd' => 4 +``` +The `LittleDict` acts similarly to the `OrderedDict`. +However for small collections it is much faster. +Indeed the preceeding example (with the io redirected to `devnull`), runs 4x faster in the `LittleDict` version as the earlier `OrderedDict` version. + +```@docs +LittleDict +freeze +``` diff --git a/packages/OrderedCollections/src/OrderedCollections.jl b/packages/OrderedCollections/src/OrderedCollections.jl new file mode 100644 index 0000000..47e1128 --- /dev/null +++ b/packages/OrderedCollections/src/OrderedCollections.jl @@ -0,0 +1,32 @@ +module OrderedCollections + + import Base: <, <=, ==, convert, length, isempty, iterate, delete!, + show, dump, empty!, getindex, setindex!, get, get!, + in, haskey, keys, merge, copy, cat, + push!, pop!, popfirst!, insert!, + union!, delete!, empty, sizehint!, + isequal, hash, + map, map!, reverse, + first, last, eltype, getkey, values, sum, + merge, merge!, lt, Ordering, ForwardOrdering, Forward, + ReverseOrdering, Reverse, Lt, + isless, + union, intersect, symdiff, setdiff, setdiff!, issubset, + searchsortedfirst, searchsortedlast, in, + filter, filter!, ValueIterator, eachindex, keytype, + valtype, lastindex, nextind, + copymutable, emptymutable, dict_with_eltype + + export OrderedDict, OrderedSet, LittleDict + export freeze + + include("dict_support.jl") + include("ordered_dict.jl") + include("little_dict.jl") + include("ordered_set.jl") + include("dict_sorting.jl") + + import Base: similar + @deprecate similar(d::OrderedDict) empty(d) + @deprecate similar(s::OrderedSet) empty(s) +end diff --git a/packages/OrderedCollections/src/dict_sorting.jl b/packages/OrderedCollections/src/dict_sorting.jl new file mode 100644 index 0000000..cfcf918 --- /dev/null +++ b/packages/OrderedCollections/src/dict_sorting.jl @@ -0,0 +1,31 @@ +# Sort for dicts +import Base: sort, sort! + +function sort!(d::OrderedDict; byvalue::Bool=false, args...) + if d.ndel > 0 + rehash!(d) + end + + if byvalue + p = sortperm(d.vals; args...) + else + p = sortperm(d.keys; args...) + end + d.keys = d.keys[p] + d.vals = d.vals[p] + rehash!(d) + return d +end + +sort(d::OrderedDict; args...) = sort!(copy(d); args...) +@deprecate sort(d::Dict; args...) sort!(OrderedDict(d); args...) + +function sort(d::LittleDict; byvalue::Bool=false, args...) + if byvalue + p = sortperm(d.vals; args...) + else + p = sortperm(d.keys; args...) + end + return LittleDict(d.keys[p], d.vals[p]) +end + diff --git a/packages/OrderedCollections/src/dict_support.jl b/packages/OrderedCollections/src/dict_support.jl new file mode 100644 index 0000000..b44f514 --- /dev/null +++ b/packages/OrderedCollections/src/dict_support.jl @@ -0,0 +1,6 @@ +# support functions + +# _tablesz and hashindex are defined in Base, but are not exported, +# so they are redefined here. +_tablesz(x::Integer) = x < 16 ? 16 : one(x)<<((sizeof(x)<<3)-leading_zeros(x-1)) +hashindex(key, sz) = (reinterpret(Int,(hash(key))) & (sz-1)) + 1 diff --git a/packages/OrderedCollections/src/little_dict.jl b/packages/OrderedCollections/src/little_dict.jl new file mode 100644 index 0000000..25e99e6 --- /dev/null +++ b/packages/OrderedCollections/src/little_dict.jl @@ -0,0 +1,268 @@ +const StoreType = Union{<:Tuple, <:Vector} + +""" + LittleDict(keys, vals)<:AbstractDict + +An ordered dictionary type for small numbers of keys. +Rather than using `hash` or some other sophisticated measure +to store the vals in a clever arrangement, +it just keeps everything in a pair of lists. + +While theoretically this has expected time complexity _O(n)_ +(vs the hash-based [`OrderedDict`](@ref)/`Dict`'s expected time complexity _O(1)_, +and the search-tree-based `SortedDict`'s expected time complexity _O(log(n))_), +in practice it is really fast, because it is cache & SIMD friendly. + +It is reasonable to expect it to outperform an `OrderedDict`, +with up to around 30 elements in general; +or with up to around 50 elements if using a `LittleDict` backed by `Tuples` +(see [`freeze`](@ref)) +However, this depends on exactly how long `isequal` and `hash` take, +as well as on how many hash collisions occur etc. + +!!! note + When constructing a `LittleDict` it is faster to pass in the keys and + values each as seperate lists. So if you have them seperately already, + do `LittleDict(ks, vs)` not `LittleDict(zip(ks, vs))`. + Furthermore, key and value lists that are passed as `Tuple`s will not require any + copies to create the `LittleDict`, so `LittleDict(ks::Tuple, vs::Tuple)` + is the fastest constructor of all. +""" +struct LittleDict{K,V,KS<:StoreType,VS<:StoreType} <: AbstractDict{K, V} + keys::KS + vals::VS + + function LittleDict{K,V,KS,VS}(keys,vals) where {K,V,KS,VS} + if length(keys) != length(vals) + throw(ArgumentError( + "Number of keys ($(length(keys))) differs from " * + "number of values ($(length(vals))" + )) + end + K<:eltype(KS) || ArgumentError("Invalid store type $KS, for key type $K") + V<:eltype(VS) || ArgumentError("Invalid store type $VS, for value type $K") + + return new(keys,vals) + end +end + +function LittleDict{K,V}(ks::KS, vs::VS) where {K,V, KS<:StoreType,VS<:StoreType} + return LittleDict{K, V, KS, VS}(ks, vs) +end + +function LittleDict(ks::KS, vs::VS) where {KS<:StoreType,VS<:StoreType} + return LittleDict{eltype(KS), eltype(VS)}(ks, vs) +end + + +# Other iterators should be copied to a Vector +LittleDict(ks, vs) = LittleDict(collect(ks), collect(vs)) + + +function LittleDict{K,V}(itr) where {K,V} + ks = K[] + vs = V[] + for val in itr + if !(val isa Union{Tuple{<:Any, <:Any}, Pair}) + throw(ArgumentError( + "LittleDict(kv): kv needs to be an iterator of tuples or pairs") + ) + end + k, v = val + push!(ks, k) + push!(vs, v) + end + return LittleDict(ks, vs) +end + +LittleDict{K,V}(itr...) where {K,V} = LittleDict{K,V}(itr) +LittleDict(itr...) = LittleDict(itr) +LittleDict(itr::T) where T = LittleDict{kvtype(eltype(T))...}(itr) + +# Avoid contention between the core constructor, and the list of elements +LittleDict(itr1::Pair, itr2::Pair) = LittleDict(first.([itr1, itr2]), last.([itr1,itr2])) +LittleDict(itr1::Pair) = LittleDict([first(itr1)], [last(itr1)]) +LittleDict{K, V}(itr::Pair) where {K, V} = LittleDict{K,V}(K[first(itr)], V[last(itr)]) + +kvtype(::Any) = (Any, Any) +kvtype(::Type{Union{}}) = (Any,Any) + +kvtype(::Type{Pair{K,V}}) where {K,V} = (K,V) +kvtype(::Type{Pair{<:Any,V}}) where {V} = (Any,V) +kvtype(::Type{Pair{K,<:Any}}) where {K} = (K,Any) + +kvtype(::Type{Tuple{K,V}}) where {K,V} = (K,V) + +""" + freeze(dd::AbstractDict) + +Render a dictionary immutable by converting it to a `Tuple`-backed +`LittleDict`. +The `Tuple`-backed `LittleDict` is faster than the `Vector`-backed `LittleDict`, +particularly when the keys are all concretely typed. +""" +function freeze(dd::AbstractDict) + ks = Tuple(keys(dd)) + vs = Tuple(values(dd)) + return LittleDict(ks, vs) +end + +isordered(::Type{<:LittleDict}) = true + +# For now these are internal UnionAlls for dispatch purposes +const UnfrozenLittleDict{K,V} = LittleDict{K,V, Vector{K}, Vector{V}} +const FrozenLittleDict{K,V} = LittleDict{K,V, <:Tuple, <:Tuple} + +##### Methods that all AbstractDicts should implement + +Base.length(dd::LittleDict) = length(dd.keys) + +function Base.getkey(dd::LittleDict, key, default) + if key ∈ dd.keys + return key + else + return default + end +end + +function Base.map!(f, iter::Base.ValueIterator{<:LittleDict}) + dict = iter.dict + vals = dict.vals + for i in 1:length(vals) + @inbounds vals[i] = f(vals[i]) + end + return iter +end + +struct NotFoundSentinel end # Struct to mark not not found +function Base.get(dd::LittleDict, key, default) + @assert length(dd.keys) == length(dd.vals) + for ii in 1:length(dd.keys) + cand = @inbounds dd.keys[ii] + isequal(cand, key) && return @inbounds(dd.vals[ii]) + end + return default +end +function get(default::Base.Callable, dd::LittleDict, key) + got = get(dd, key, NotFoundSentinel()) + if got isa NotFoundSentinel # not found + return default() + else + return got + end +end + +function Base.iterate(dd::LittleDict, ii=1) + ii > length(dd.keys) && return nothing + return (dd.keys[ii] => dd.vals[ii], ii+1) +end + +function merge(d1::LittleDict, others::AbstractDict...) + return merge((x,y)->y, d1, others...) +end + +function merge( + combine::Function, + d::LittleDict, + others::AbstractDict... + ) + K,V = _merge_kvtypes(d, others...) + dc = LittleDict{K,V}(d) + for d2 in others + for (k2,v2) in d2 + got = get(dc, k2, NotFoundSentinel()) + if got isa NotFoundSentinel + add_new!(dc, k2, v2) + else + # GOLDPLATE: ideally we would avoid iterating this twice + # once for get and once for setindex! + dc[k2]=combine(got, v2) + end + end + end + return dc +end + + +Base.empty(dd::LittleDict{K,V}) where {K,V} = LittleDict{K,V}() + +######## Methods that all mutable AbstractDict's should implement + +function Base.sizehint!(dd::UnfrozenLittleDict, sz) + sizehint!(dd.keys, sz) + sizehint!(dd.vals,sz) + return dd +end + +function add_new!(dd::UnfrozenLittleDict{K, V}, key, value) where {K, V} + kk = convert(K, key) + vv = convert(V, value) + + # if we can convert it to the right type, and the dict is unfrozen + # then neither push can fail, so the dict length will remain in sync + push!(dd.keys, kk) + push!(dd.vals, vv) + + return dd +end + + +function Base.setindex!(dd::LittleDict{K,V, <:Any, <:Vector}, value, key) where {K,V} + # Note we only care if the Value store is mutable (<:Vector) + # As we can have immutable keys, if we are setting the value of an existing key + + # Assertion below commented out as by standards of carefully optimised + # setindex! it has huge code (26%), this does mean that if someone has messed + # with the fields of the LittleDict directly, then the @inbounds could be invalid + #@assert length(dd.keys) == length(dd.vals) + + kk = convert(K, key) + vv = convert(V, value) + for ii in 1:length(dd.keys) + cand = @inbounds dd.keys[ii] + if isequal(cand, kk) + @inbounds(dd.vals[ii] = vv) + return dd + end + end + add_new!(dd, key, value) + return dd +end + +function Base.pop!(dd::UnfrozenLittleDict) + pop!(dd.keys) + return pop!(dd.vals) +end + +function Base.pop!(dd::UnfrozenLittleDict, key) + @assert length(dd.keys) == length(dd.vals) + + for ii in 1:length(dd.keys) + cand = @inbounds dd.keys[ii] + if isequal(cand, key) + deleteat!(dd.keys, ii) + val = @inbounds dd.vals[ii] + deleteat!(dd.vals, ii) + return val + end + end +end + +function Base.delete!(dd::UnfrozenLittleDict, key) + pop!(dd, key) + return dd +end + +Base.empty!(dd::UnfrozenLittleDict) = (empty!(dd.keys); empty!(dd.vals); dd) + +function get!(default::Base.Callable, dd::UnfrozenLittleDict, key) + got = get(dd, key, NotFoundSentinel()) + if got isa NotFoundSentinel # not found + val = default() + add_new!(dd, key, val) + return val + else + return got + end +end +get!(dd::UnfrozenLittleDict, key, default) = get!(()->default, dd, key) diff --git a/packages/OrderedCollections/src/ordered_dict.jl b/packages/OrderedCollections/src/ordered_dict.jl new file mode 100644 index 0000000..7da2445 --- /dev/null +++ b/packages/OrderedCollections/src/ordered_dict.jl @@ -0,0 +1,491 @@ +# These can be changed, to trade off better performance for space +const global maxallowedprobe = isdefined(Base, :maxallowedprobe) ? Base.maxallowedprobe : 16 +const global maxprobeshift = isdefined(Base, :maxprobeshift) ? Base.maxprobeshift : 6 + +# OrderedDict + +""" + OrderedDict + +`OrderedDict`s are simply dictionaries whose entries have a particular order. The order +refers to insertion order, which allows deterministic iteration over the dictionary or set. +""" +mutable struct OrderedDict{K,V} <: AbstractDict{K,V} + slots::Array{Int32,1} + keys::Array{K,1} + vals::Array{V,1} + ndel::Int + maxprobe::Int + dirty::Bool + + function OrderedDict{K,V}() where {K,V} + new{K,V}(zeros(Int32,16), Vector{K}(), Vector{V}(), 0, 0, false) + end + function OrderedDict{K,V}(kv) where {K,V} + h = OrderedDict{K,V}() + for (k,v) in kv + h[k] = v + end + return h + end + OrderedDict{K,V}(p::Pair) where {K,V} = setindex!(OrderedDict{K,V}(), p.second, p.first) + function OrderedDict{K,V}(ps::Pair...) where {K,V} + h = OrderedDict{K,V}() + sizehint!(h, length(ps)) + for p in ps + h[p.first] = p.second + end + return h + end + function OrderedDict{K,V}(d::OrderedDict{K,V}) where {K,V} + if d.ndel > 0 + rehash!(d) + end + @assert d.ndel == 0 + new{K,V}(copy(d.slots), copy(d.keys), copy(d.vals), 0, d.maxprobe, false) + end +end +OrderedDict() = OrderedDict{Any,Any}() +OrderedDict(kv::Tuple{}) = OrderedDict() +copy(d::OrderedDict) = OrderedDict(d) + + +# TODO: this can probably be simplified using `eltype` as a THT (Tim Holy trait) +# OrderedDict{K,V}(kv::Tuple{Vararg{Tuple{K,V}}}) = OrderedDict{K,V}(kv) +# OrderedDict{K }(kv::Tuple{Vararg{Tuple{K,Any}}}) = OrderedDict{K,Any}(kv) +# OrderedDict{V }(kv::Tuple{Vararg{Tuple{Any,V}}}) = OrderedDict{Any,V}(kv) +OrderedDict(kv::Tuple{Vararg{Pair{K,V}}}) where {K,V} = OrderedDict{K,V}(kv) + +OrderedDict(kv::AbstractArray{Tuple{K,V}}) where {K,V} = OrderedDict{K,V}(kv) +OrderedDict(kv::AbstractArray{Pair{K,V}}) where {K,V} = OrderedDict{K,V}(kv) +OrderedDict(kv::AbstractDict{K,V}) where {K,V} = OrderedDict{K,V}(kv) + +OrderedDict(ps::Pair{K,V}...) where {K,V} = OrderedDict{K,V}(ps) +OrderedDict(ps::Pair...) = OrderedDict(ps) + +function OrderedDict(kv) + try + dict_with_eltype((K, V) -> OrderedDict{K, V}, kv, eltype(kv)) + catch e + if isempty(methods(iterate, (typeof(kv),))) || + !all(x->isa(x, Union{Tuple,Pair}), kv) + throw(ArgumentError("OrderedDict(kv): kv needs to be an iterator of tuples or pairs")) + else + rethrow(e) + end + end +end + +empty(d::OrderedDict{K,V}) where {K,V} = OrderedDict{K,V}() +empty(d::OrderedDict, ::Type{K}, ::Type{V}) where {K, V} = OrderedDict{K, V}() + +length(d::OrderedDict) = length(d.keys) - d.ndel +isempty(d::OrderedDict) = (length(d) == 0) + +""" + isordered(::Type) + +Property of associative containers, that is `true` if the container type has a +defined order (such as `OrderedDict` and `SortedDict`), and `false` otherwise. +""" +isordered(::Type{T}) where {T<:AbstractDict} = false +isordered(::Type{T}) where {T<:OrderedDict} = true + +# conversion between OrderedDict types +function convert(::Type{OrderedDict{K,V}}, d::AbstractDict) where {K,V} + if !isordered(typeof(d)) + Base.depwarn("Conversion to OrderedDict is deprecated for unordered associative containers (in this case, $(typeof(d))). Use an ordered or sorted associative type, such as SortedDict and OrderedDict.", :convert) + end + h = OrderedDict{K,V}() + for (k,v) in d + ck = convert(K,k) + if !haskey(h,ck) + h[ck] = convert(V,v) + else + error("key collision during dictionary conversion") + end + end + return h +end +convert(::Type{OrderedDict{K,V}},d::OrderedDict{K,V}) where {K,V} = d + +isslotempty(slot_value::Integer) = slot_value == 0 +isslotfilled(slot_value::Integer) = slot_value > 0 +isslotmissing(slot_value::Integer) = slot_value < 0 + +function rehash!(h::OrderedDict{K,V}, newsz = length(h.slots)) where {K,V} + olds = h.slots + keys = h.keys + vals = h.vals + sz = length(olds) + newsz = _tablesz(newsz) + h.dirty = true + count0 = length(h) + if count0 == 0 + resize!(h.slots, newsz) + fill!(h.slots, 0) + resize!(h.keys, 0) + resize!(h.vals, 0) + h.ndel = 0 + return h + end + + slots = zeros(Int32, newsz) + maxprobe = 0 + + if h.ndel > 0 + ndel0 = h.ndel + ptrs = !isbitstype(K) + to = 1 + # TODO: to get the best performance we need to avoid reallocating these. + # This algorithm actually works in place, unless the dict is modified + # due to GC during this process. + newkeys = similar(keys, count0) + newvals = similar(vals, count0) + @inbounds for from = 1:length(keys) + if !ptrs || isassigned(keys, from) + k = keys[from] + hashk = hash(k)%Int + isdeleted = false + if !ptrs + iter = 0 + index = (hashk & (sz-1)) + 1 + while iter <= h.maxprobe + si = olds[index] + si == from && break + # if we find si == 0, then the key was deleted and it's slot was reused/overwritten + (si == -from || si == 0) && (isdeleted = true; break) + index = (index & (sz-1)) + 1 + iter += 1 + end + iter > h.maxprobe && (isdeleted = true) # Another case where the slot was reused/overwritten + end + if !isdeleted + index0 = index = (hashk & (newsz-1)) + 1 + while slots[index] != 0 + index = (index & (newsz-1)) + 1 + end + probe = (index - index0) & (newsz-1) + probe > maxprobe && (maxprobe = probe) + slots[index] = to + newkeys[to] = k + newvals[to] = vals[from] + to += 1 + end + if h.ndel != ndel0 + # if items are removed by finalizers, retry + return rehash!(h, newsz) + end + end + end + h.keys = newkeys + h.vals = newvals + h.ndel = 0 + else + @inbounds for i = 1:count0 + k = keys[i] + index0 = index = hashindex(k, newsz) + while slots[index] != 0 + index = (index & (newsz-1)) + 1 + end + probe = (index - index0) & (newsz-1) + probe > maxprobe && (maxprobe = probe) + slots[index] = i + if h.ndel > 0 + # if items are removed by finalizers, retry + return rehash!(h, newsz) + end + end + end + + h.slots = slots + h.maxprobe = maxprobe + return h +end + +function sizehint!(d::OrderedDict, newsz) + slotsz = (newsz*3)>>1 + oldsz = length(d.slots) + if slotsz <= oldsz + # todo: shrink + # be careful: rehash!() assumes everything fits. it was only designed + # for growing. + return d + end + # grow at least 25% + slotsz = max(slotsz, (oldsz*5)>>2) + rehash!(d, slotsz) +end + +function empty!(h::OrderedDict{K,V}) where {K,V} + fill!(h.slots, 0) + empty!(h.keys) + empty!(h.vals) + h.ndel = 0 + h.dirty = true + return h +end + +# get the index where a key is stored, or -1 if not present +function ht_keyindex(h::OrderedDict{K,V}, key, direct) where {K,V} + slots = h.slots + sz = length(slots) + iter = 0 + maxprobe = h.maxprobe + index = hashindex(key, sz) + keys = h.keys + + @inbounds while iter <= maxprobe + si = slots[index] + isslotempty(si) && break + if isslotfilled(si) && isequal(key, keys[si]) + return ifelse(direct, oftype(index, si), index) + end + + index = (index & (sz-1)) + 1 + iter += 1 + end + + return -1 +end + +# get the index where a key is stored, or -pos if not present +# and the key would be inserted at pos +# This version is for use by setindex! and get! +function ht_keyindex2(h::OrderedDict{K,V}, key) where {K,V} + slots = h.slots + sz = length(slots) + iter = 0 + maxprobe = h.maxprobe + index = hashindex(key, sz) + keys = h.keys + avail = 0 + + @inbounds while iter <= maxprobe + si = slots[index] + if isslotempty(si) + avail < 0 && return avail + return -index + end + + if isslotmissing(si) + avail == 0 && (avail = -index) + elseif isequal(key, keys[si]) + return oftype(index, si) + end + + index = (index & (sz-1)) + 1 + iter += 1 + end + + avail < 0 && return avail + + # If key is not present, may need to keep searching to find slot + maxallowed = max(maxallowedprobe, sz>>maxprobeshift) + @inbounds while iter < maxallowed + if !isslotfilled(slots[index]) + h.maxprobe = iter + return -index + end + index = (index & (sz-1)) + 1 + iter += 1 + end + + rehash!(h, length(h) > 64000 ? sz*2 : sz*4) + + return ht_keyindex2(h, key) +end + +function _setindex!(h::OrderedDict, v, key, index) + hk, hv = h.keys, h.vals + #push!(h.keys, key) + ccall(:jl_array_grow_end, Cvoid, (Any, UInt), hk, 1) + nk = length(hk) + @inbounds hk[nk] = key + #push!(h.vals, v) + ccall(:jl_array_grow_end, Cvoid, (Any, UInt), hv, 1) + @inbounds hv[nk] = v + @inbounds h.slots[index] = nk + h.dirty = true + + sz = length(h.slots) + cnt = nk - h.ndel + # Rehash now if necessary + if h.ndel >= ((3*nk)>>2) || cnt*3 > sz*2 + # > 3/4 deleted or > 2/3 full + rehash!(h, cnt > 64000 ? cnt*2 : cnt*4) + end +end + +function setindex!(h::OrderedDict{K,V}, v0, key0) where {K,V} + key = convert(K, key0) + if !isequal(key, key0) + throw(ArgumentError("$key0 is not a valid key for type $K")) + end + v = convert(V, v0) + + index = ht_keyindex2(h, key) + + if index > 0 + @inbounds h.keys[index] = key + @inbounds h.vals[index] = v + else + _setindex!(h, v, key, -index) + end + + return h +end + +function get!(h::OrderedDict{K,V}, key0, default) where {K,V} + key = convert(K, key0) + if !isequal(key, key0) + throw(ArgumentError("$key0 is not a valid key for type $K")) + end + + index = ht_keyindex2(h, key) + + index > 0 && return h.vals[index] + + v = convert(V, default) + _setindex!(h, v, key, -index) + return v +end + +function get!(default::Base.Callable, h::OrderedDict{K,V}, key0) where {K,V} + key = convert(K, key0) + if !isequal(key, key0) + throw(ArgumentError("$key0 is not a valid key for type $K")) + end + + index = ht_keyindex2(h, key) + + index > 0 && return h.vals[index] + + h.dirty = false + v = convert(V, default()) + if h.dirty # calling default could have dirtied h + index = ht_keyindex2(h, key) + end + if index > 0 + h.keys[index] = key + h.vals[index] = v + else + _setindex!(h, v, key, -index) + end + return v +end + +function getindex(h::OrderedDict{K,V}, key) where {K,V} + index = ht_keyindex(h, key, true) + return (index<0) ? throw(KeyError(key)) : h.vals[index]::V +end + +function get(h::OrderedDict{K,V}, key, default) where {K,V} + index = ht_keyindex(h, key, true) + return (index<0) ? default : h.vals[index]::V +end + +function get(default::Base.Callable, h::OrderedDict{K,V}, key) where {K,V} + index = ht_keyindex(h, key, true) + return (index<0) ? default() : h.vals[index]::V +end + +haskey(h::OrderedDict, key) = (ht_keyindex(h, key, true) >= 0) +in(key, v::Base.KeySet{K,T}) where {K,T<:OrderedDict{K}} = (ht_keyindex(v.dict, key, true) >= 0) + +function getkey(h::OrderedDict{K,V}, key, default) where {K,V} + index = ht_keyindex(h, key, true) + return (index<0) ? default : h.keys[index]::K +end + +function _pop!(h::OrderedDict, index) + @inbounds val = h.vals[h.slots[index]] + _delete!(h, index) + return val +end + +function pop!(h::OrderedDict) + h.ndel > 0 && rehash!(h) + key = h.keys[end] + index = ht_keyindex(h, key, false) + return key => _pop!(h, index) +end + +function popfirst!(h::OrderedDict) + h.ndel > 0 && rehash!(h) + key = h.keys[1] + index = ht_keyindex(h, key, false) + key => _pop!(h, index) +end + +function pop!(h::OrderedDict, key) + index = ht_keyindex(h, key, false) + index > 0 ? _pop!(h, index) : throw(KeyError(key)) +end + +function pop!(h::OrderedDict, key, default) + index = ht_keyindex(h, key, false) + index > 0 ? _pop!(h, index) : default +end + +function _delete!(h::OrderedDict, index) + @inbounds ki = h.slots[index] + @inbounds h.slots[index] = -ki + ccall(:jl_arrayunset, Cvoid, (Any, UInt), h.keys, ki-1) + ccall(:jl_arrayunset, Cvoid, (Any, UInt), h.vals, ki-1) + h.ndel += 1 + h.dirty = true + return h +end + +function delete!(h::OrderedDict, key) + index = ht_keyindex(h, key, false) + if index > 0; _delete!(h, index); end + return h +end + +function iterate(t::OrderedDict) + t.ndel > 0 && rehash!(t) + length(t.keys) < 1 && return nothing + return (Pair(t.keys[1], t.vals[1]), 2) +end +function iterate(t::OrderedDict, i) + length(t.keys) < i && return nothing + return (Pair(t.keys[i], t.vals[i]), i+1) +end + +function _merge_kvtypes(d, others...) + K, V = keytype(d), valtype(d) + for other in others + K = promote_type(K, keytype(other)) + V = promote_type(V, valtype(other)) + end + return (K,V) +end + +function merge(d::OrderedDict, others::AbstractDict...) + K,V = _merge_kvtypes(d, others...) + merge!(OrderedDict{K,V}(), d, others...) +end + +function merge(combine::Function, d::OrderedDict, others::AbstractDict...) + K,V = _merge_kvtypes(d, others...) + merge!(combine, OrderedDict{K,V}(), d, others...) +end + +function Base.map!(f, iter::Base.ValueIterator{<:OrderedDict}) + dict = iter.dict + vals = dict.vals + elements = length(vals) - dict.ndel + elements == 0 && return iter + for i in dict.slots + if i > 0 + @inbounds vals[i] = f(vals[i]) + elements -= 1 + elements == 0 && break + end + end + return iter +end + +last(h::OrderedDict) = h.keys[end] => h.vals[end] \ No newline at end of file diff --git a/packages/OrderedCollections/src/ordered_set.jl b/packages/OrderedCollections/src/ordered_set.jl new file mode 100644 index 0000000..3d3eada --- /dev/null +++ b/packages/OrderedCollections/src/ordered_set.jl @@ -0,0 +1,99 @@ +# ordered sets + +# This was largely copied and modified from Base + + +struct OrderedSet{T} <: AbstractSet{T} + dict::OrderedDict{T,Nothing} + + OrderedSet{T}() where {T} = new{T}(OrderedDict{T,Nothing}()) + OrderedSet{T}(xs) where {T} = union!(new{T}(OrderedDict{T,Nothing}()), xs) +end +OrderedSet() = OrderedSet{Any}() +OrderedSet(xs) = OrderedSet{eltype(xs)}(xs) + + +show(io::IO, s::OrderedSet) = (show(io, typeof(s)); print(io, "("); !isempty(s) && Base.show_vector(io, s,'[',']'); print(io, ")")) + +isempty(s::OrderedSet) = isempty(s.dict) +length(s::OrderedSet) = length(s.dict) + +sizehint!(s::OrderedSet, sz::Integer) = (sizehint!(s.dict, sz); s) + +in(x, s::OrderedSet) = haskey(s.dict, x) + +push!(s::OrderedSet, x) = (s.dict[x] = nothing; s) +pop!(s::OrderedSet, x) = (pop!(s.dict, x); x) +pop!(s::OrderedSet, x, deflt) = pop!(s.dict, x, deflt) == deflt ? deflt : x +delete!(s::OrderedSet, x) = (delete!(s.dict, x); s) + +empty(s::OrderedSet{T}) where {T} = OrderedSet{T}() +copy(s::OrderedSet) = union!(empty(s), s) + +empty!(s::OrderedSet{T}) where {T} = (empty!(s.dict); s) + +emptymutable(s::OrderedSet{T}, ::Type{U}=T) where {T,U} = OrderedSet{U}() +copymutable(s::OrderedSet) = copy(s) + +# NOTE: manually optimized to take advantage of OrderedDict representation +function iterate(s::OrderedSet) + s.dict.ndel > 0 && rehash!(s.dict) + length(s.dict.keys) < 1 && return nothing + return (s.dict.keys[1], 2) +end +function iterate(s::OrderedSet, i) + length(s.dict.keys) < i && return nothing + return (s.dict.keys[i], i+1) +end + +pop!(s::OrderedSet) = pop!(s.dict)[1] +popfirst!(s::OrderedSet) = popfirst!(s.dict)[1] + + + +==(l::OrderedSet, r::OrderedSet) = (length(l) == length(r)) && (l <= r) +<(l::OrderedSet, r::OrderedSet) = (length(l) < length(r)) && (l <= r) +<=(l::OrderedSet, r::OrderedSet) = issubset(l, r) + +function filter!(f::Function, s::OrderedSet) + for x in s + if !f(x) + delete!(s, x) + end + end + return s +end + +const orderedset_seed = UInt === UInt64 ? 0x2114638a942a91a5 : 0xd86bdbf1 +function hash(s::OrderedSet, h::UInt) + h = hash(orderedset_seed, h) + s.dict.ndel > 0 && rehash!(s.dict) + hash(s.dict.keys, h) +end + + +# Deprecated functionality, see +# https://github.com/JuliaCollections/DataStructures.jl/pull/180#issuecomment-400269803 + +function getindex(s::OrderedSet, i::Int) + Base.depwarn("indexing is deprecated for OrderedSet, please rewrite your code to use iteration", :getindex) + s.dict.ndel > 0 && rehash!(s.dict) + return s.dict.keys[i] +end + +function lastindex(s::OrderedSet) + Base.depwarn("indexing is deprecated for OrderedSet, please rewrite your code to use iteration", :lastindex) + s.dict.ndel > 0 && rehash!(s.dict) + return lastindex(s.dict.keys) +end + +function nextind(::OrderedSet, i::Int) + Base.depwarn("indexing is deprecated for OrderedSet, please rewrite your code to use iteration", :lastindex) + return i + 1 # Needed on 0.7 to mimic array indexing. +end + +function keys(s::OrderedSet) + Base.depwarn("indexing is deprecated for OrderedSet, please rewrite your code to use iteration", :lastindex) + s.dict.ndel > 0 && rehash!(s.dict) + return 1:length(s) +end diff --git a/packages/OrderedCollections/test/README b/packages/OrderedCollections/test/README new file mode 100644 index 0000000..27ad8b4 --- /dev/null +++ b/packages/OrderedCollections/test/README @@ -0,0 +1,3 @@ +To run a specific test file, such as test_xxx.jl, from the command line, use + + julia runtests.jl xxx diff --git a/packages/OrderedCollections/test/runtests.jl b/packages/OrderedCollections/test/runtests.jl new file mode 100644 index 0000000..07a9879 --- /dev/null +++ b/packages/OrderedCollections/test/runtests.jl @@ -0,0 +1,25 @@ +using OrderedCollections +using Test +using Random, Serialization + +@test isempty(detect_ambiguities(Base, Core, OrderedCollections)) + +tests = [ + "little_dict", + "ordered_dict", + "ordered_set", + ] + +if length(ARGS) > 0 + tests = ARGS +end + +@testset "OrderedCollections" begin + +for t in tests + fp = joinpath(dirname(@__FILE__), "test_$t.jl") + println("$fp ...") + include(fp) +end + +end # @testset diff --git a/packages/OrderedCollections/test/test_little_dict.jl b/packages/OrderedCollections/test/test_little_dict.jl new file mode 100644 index 0000000..d6edd99 --- /dev/null +++ b/packages/OrderedCollections/test/test_little_dict.jl @@ -0,0 +1,562 @@ +using OrderedCollections, Test +using OrderedCollections: FrozenLittleDict, UnfrozenLittleDict + +@testset "LittleDict" begin + @testset "Type Aliases" begin + FF1 = LittleDict{Int,Int, NTuple{10, Int}, NTuple{10, Int}} + @test FF1 <: FrozenLittleDict{<:Any, <:Any} + @test FF1 <: FrozenLittleDict + @test FF1 <: FrozenLittleDict{Int, Int} + @test !(FF1 <: UnfrozenLittleDict{<:Any, <:Any}) + @test !(FF1 <: UnfrozenLittleDict) + @test !(FF1 <: UnfrozenLittleDict{Int, Int}) + + + UU1 = LittleDict{Int,Int,Vector{Int},Vector{Int}} + @test !(UU1 <: FrozenLittleDict{<:Any, <:Any}) + @test !(UU1 <: FrozenLittleDict) + @test !(UU1 <: FrozenLittleDict{Int, Int}) + @test (UU1 <: UnfrozenLittleDict{<:Any, <:Any}) + @test (UU1 <: UnfrozenLittleDict) + @test (UU1 <: UnfrozenLittleDict{Int, Int}) + + + FU1 = LittleDict{Int,Int,NTuple{10, Int},Vector{Int}} + @test !(FU1 <: FrozenLittleDict{<:Any, <:Any}) + @test !(FU1 <: FrozenLittleDict) + @test !(FU1 <: FrozenLittleDict{Int, Int}) + @test !(FU1 <: UnfrozenLittleDict{<:Any, <:Any}) + @test !(FU1 <: UnfrozenLittleDict) + @test !(FU1 <: UnfrozenLittleDict{Int, Int}) + + UF1 = LittleDict{Int,Int,Vector{Int},NTuple{10,Int}} + @test !(UF1 <: FrozenLittleDict{<:Any, <:Any}) + @test !(UF1 <: FrozenLittleDict) + @test !(UF1 <: FrozenLittleDict{Int, Int}) + @test !(UF1 <: UnfrozenLittleDict{<:Any, <:Any}) + @test !(UF1 <: UnfrozenLittleDict) + @test !(UF1 <: UnfrozenLittleDict{Int, Int}) + end + + @testset "Constructors" begin + @test isa(@inferred(LittleDict()), LittleDict{Any,Any}) + @test isa(@inferred(LittleDict([(1,2.0)])), LittleDict{Int,Float64}) + + @test isa(@inferred(LittleDict([("a",1),("b",2)])), LittleDict{String,Int}) + @test isa(@inferred(LittleDict(Pair(1, 1.0))), LittleDict{Int,Float64}) + @test isa(@inferred(LittleDict(Pair(1, 1.0), Pair(2, 2.0))), + LittleDict{Int,Float64}) + + @test isa(@inferred(LittleDict{Int,Float64}(2=>2.0, 3=>3.0)), + LittleDict{Int,Float64}) + @test isa(@inferred(LittleDict{Int,Float64}(Pair(1, 1), Pair(2, 2))), LittleDict{Int,Float64}) + @test isa(@inferred(LittleDict(Pair(1, 1.0), Pair(2, 2.0), Pair(3, 3.0))), LittleDict{Int,Float64}) + @test LittleDict(()) == LittleDict{Any,Any}() + + @test isa(@inferred(LittleDict([Pair(1, 1.0), Pair(2, 2.0)])), LittleDict{Int,Float64}) + @test_throws ArgumentError LittleDict([1,2,3,4]) + + iter = Iterators.filter(x->x.first>1, [Pair(1, 1.0), Pair(2, 2.0), Pair(3, 3.0)]) + @test @inferred(LittleDict(iter)) == LittleDict{Int,Float64}(2=>2.0, 3=>3.0) + + iter = Iterators.drop(1:10, 1) + @test_throws ArgumentError LittleDict(iter) + + k_iter = Iterators.filter(x->x>1, [1,2,3,4]) + v_iter = Iterators.filter(x->x>1, [1.0,2.0,3.0,4.0]) + @test @inferred(LittleDict(k_iter, v_iter)) isa + LittleDict{Int,Float64, Vector{Int}, Vector{Float64}} + + @test @inferred(LittleDict{Int, Char}(rand(1:100,20), rand('a':'z', 20))) isa + LittleDict{Int,Char,Array{Int,1},Array{Char,1}} + + # Different number of keys and values + @test_throws ArgumentError LittleDict{Int, Char, Vector{Int}, Vector{Char}}([1,2,3], ['a','b']) + end + + + @testset "empty dictionary" begin + d = LittleDict{Char, Int}() + @test length(d) == 0 + @test isempty(d) + @test_throws KeyError d['c'] == 1 + d['c'] = 1 + @test !isempty(d) + @test_throws KeyError d[0.01] + @test isempty(empty(d)) + empty!(d) + @test isempty(d) + @test delete!(d, "foo") == empty(d) # Make sure this does't throw an error + + # access, modification + for c in 'a':'z' + d[c] = c - 'a' + 1 + end + + @test (d['a'] += 1) == 2 + @test 'a' in keys(d) + @test haskey(d, 'a') + @test get(d, 'B', 0) == 0 + @test getkey(d, 'b', nothing) == 'b' + @test getkey(d, 'B', nothing) == nothing + @test !('B' in keys(d)) + @test !haskey(d, 'B') + @test pop!(d, 'a') == 2 + + @test collect(keys(d)) == collect('b':'z') + @test collect(values(d)) == collect(2:26) + @test collect(d) == [Pair(a,i) for (a,i) in zip('b':'z', 2:26)] + end + + @testset "convert" begin + d = LittleDict{Int,Float32}(i=>Float32(i) for i = 1:10) + @test convert(LittleDict{Int,Float32}, d) === d + dc = convert(LittleDict{Int,Float64}, d) + @test dc !== d + @test keytype(dc) == Int + @test valtype(dc) == Float64 + @test keys(dc) == keys(d) + @test collect(values(dc)) == collect(values(d)) + end + + @testset "Issue #60" begin + od60 = LittleDict{Int,Int}() + od60[1] = 2 + + ranges = [2:5, 6:9, 10:13] + for range in ranges + for i = range + od60[i] = i+1 + end + for i = range + delete!( od60, i ) + end + end + od60[14]=15 + + @test od60[14] == 15 + end + + + ############################## + # Copied and modified from Base/test/dict.jl + + # LittleDict + + @testset "LittleDict{Int,Int}" begin + h = LittleDict{Int,Int}() + for i=1:100 + h[i] = i+1 + end + + @test collect(h) == [Pair(x,y) for (x,y) in zip(1:100, 2:101)] + + for i=1:2:100 + delete!(h, i) + end + for i=1:2:100 + h[i] = i+1 + end + + for i=1:100 + @test h[i]==i+1 + end + + for i=1:100 + delete!(h, i) + end + @test isempty(h) + + h[77] = 100 + @test h[77]==100 + @test length(h) == 1 + + for i=1:100 + h[i] = i+1 + end + @test length(h) == 100 + + for i=1:2:50 + delete!(h, i) + end + @test length(h) == 75 + + for i=51:100 + h[i] = i+1 + end + @test length(h) == 75 + + for i=2:2:100 + @test h[i]==i+1 + end + for i=75:100 + @test h[i]==i+1 + end + end + + @testset "LittleDict{Any,Any}" begin + h = LittleDict{Any,Any}([("a", 3)]) + @test h["a"] == 3 + h["a","b"] = 4 + @test h["a","b"] == h[("a","b")] == 4 + h["a","b","c"] = 4 + @test h["a","b","c"] == h[("a","b","c")] == 4 + end + + @testset "KeyError" begin + z = LittleDict() + get_KeyError = false + try + z["a"] + catch _e123_ + get_KeyError = isa(_e123_, KeyError) + end + @test get_KeyError + end + + @testset "filter" begin + _d = LittleDict([("a", 0)]) + v = [k for k in filter(x->length(x)==1, collect(keys(_d)))] + @test isa(v, Vector{String}) + end + + @testset "from tuple/vector/pairs/tuple of pair 1" begin + d = LittleDict(((1, 2), (3, 4))) + d2 = LittleDict([(1, 2), (3, 4)]) + d3 = LittleDict(1 => 2, 3 => 4) + d4 = LittleDict((1 => 2, 3 => 4)) + + @test d[1] === 2 + @test d[3] === 4 + + @test d == d2 == d3 == d4 + @test isa(d, LittleDict{Int,Int}) + @test isa(d2, LittleDict{Int,Int}) + @test isa(d3, LittleDict{Int,Int}) + @test isa(d4, LittleDict{Int,Int}) + end + + @testset "from tuple/vector/pairs/tuple of pair 2" begin + d = LittleDict(((1, 2), (3, "b"))) + d2 = LittleDict([(1, 2), (3, "b")]) + d3 = LittleDict(1 => 2, 3 => "b") + d4 = LittleDict((1 => 2, 3 => "b")) + + @test d2[1] === 2 + @test d2[3] == "b" + + @test d == d2 == d3 == d4 + @test isa(d, LittleDict{Int,Any}) + @test isa(d2, LittleDict{Int,Any}) + @test isa(d3, LittleDict{Int,Any}) + @test isa(d4, LittleDict{Int,Any}) + end + + @testset "from tuple/vector/pairs/tuple of pair 3" begin + d = LittleDict(((1, 2), ("a", 4))) + d2 = LittleDict([(1, 2), ("a", 4)]) + d3 = LittleDict(1 => 2, "a" => 4) + d4 = LittleDict((1 => 2, "a" => 4)) + + @test d2[1] === 2 + @test d2["a"] === 4 + + ## TODO: tuple of tuples doesn't work for mixed tuple types + # @test d == d2 == d3 == d4 + @test d2 == d3 == d4 + # @test isa(d, LittleDict{Any,Int}) + @test isa(d2, LittleDict{Any,Int}) + @test isa(d3, LittleDict{Any,Int}) + @test isa(d4, LittleDict{Any,Int}) + end + + @testset "from tuple/vector/pairs/tuple of pair 4" begin + d = LittleDict(((1, 2), ("a", "b"))) + d2 = LittleDict([(1, 2), ("a", "b")]) + d3 = LittleDict(1 => 2, "a" => "b") + d4 = LittleDict((1 => 2, "a" => "b")) + + @test d[1] === 2 + @test d["a"] == "b" + + @test d == d2 == d3 == d4 + @test isa(d, LittleDict{Any,Any}) + @test isa(d2, LittleDict{Any,Any}) + @test isa(d3, LittleDict{Any,Any}) + @test isa(d4, LittleDict{Any,Any}) + end + + @testset "first" begin + @test_throws ArgumentError first(LittleDict()) + @test first(LittleDict([(:f, 2)])) == Pair(:f,2) + end + + + @testset "iterate" begin + d = LittleDict("a" => [1, 2]) + val1, state1 = iterate(d) + @test val1 == ("a" => [1, 2]) + @test iterate(d, state1) === nothing + end + + + @testset "Failing to add a value but being able to add a key (cf: Issue #1821)" begin + d = LittleDict{String, Vector{Int}}() + d["a"] = [1, 2] + @test_throws MethodError d["b"] = 1 + @test isa(repr(d), AbstractString) # check that printable without error + end + + @testset "Issue #2344" begin + bestkey(d, key) = key + bestkey(d::AbstractDict{K,V}, key) where {K<:AbstractString,V} = string(key) + bar(x) = bestkey(x, :y) + @test bar(LittleDict([(:x, [1,2,5])])) == :y + @test bar(LittleDict([("x", [1,2,5])])) == "y" + end + + @testset "isequal" begin + @test isequal(LittleDict(), LittleDict()) + @test isequal(LittleDict([(1, 1)]), LittleDict([(1, 1)])) + @test !isequal(LittleDict([(1, 1)]), LittleDict()) + @test !isequal(LittleDict([(1, 1)]), LittleDict([(1, 2)])) + @test !isequal(LittleDict([(1, 1)]), LittleDict([(2, 1)])) + + @test isequal(LittleDict(), sizehint!(LittleDict(),96)) + + # Here is what currently happens when dictionaries of different types + # are compared. This is not necessarily desirable. These tests are + # descriptive rather than proscriptive. + @test !isequal(LittleDict([(1, 2)]), LittleDict([("dog", "bone")])) + @test isequal(LittleDict{Int,Int}(), LittleDict{AbstractString,AbstractString}()) + end + + + @testset "data_in" begin + # Generate some data to populate dicts to be compared + data_in = [ (rand(1:1000), randstring(2)) for _ in 1:1001 ] + + # Populate the first dict + d1 = LittleDict{Int, String}() + for (k,v) in data_in + d1[k] = v + end + data_in = collect(d1) + # shuffle the data + for i in 1:length(data_in) + j = rand(1:length(data_in)) + data_in[i], data_in[j] = data_in[j], data_in[i] + end + # Inserting data in different (shuffled) order should result in + # equivalent dict. + d2 = LittleDict{Int, AbstractString}() + for (k,v) in data_in + d2[k] = v + end + + @test isequal(d1, d2) + d3 = copy(d2) + d4 = copy(d2) + # Removing an item gives different dict + delete!(d1, data_in[rand(1:length(data_in))][1]) + @test !isequal(d1, d2) + # Changing a value gives different dict + d3[data_in[rand(1:length(data_in))][1]] = randstring(3) + !isequal(d1, d3) + # Adding a pair gives different dict + d4[1001] = randstring(3) + @test !isequal(d1, d4) + end + + @testset "get!" begin + # get! (get with default values assigned to the given location) + f(x) = x^2 + d = LittleDict(8 => 19) + + @test get!(d, 8, 5) == 19 + @test get!(d, 19, 2) == 2 + + @test get!(d, 42) do # d is updated with f(2) + f(2) + end == 4 + + @test get!(d, 42) do # d is not updated + f(200) + end == 4 + + @test get(d, 13) do # d is not updated + f(4) + end == 16 + + @test d == LittleDict(8=>19, 19=>2, 42=>4) + end + + @testset "Issue #5886" begin + d5886 = LittleDict() + for k5886 in 1:11 + d5886[k5886] = 1 + end + for k5886 in keys(d5886) + # undefined ref if not fixed + d5886[k5886] += 1 + end + end + + @testset "isordered (Issue #216)1" begin + @test OrderedCollections.isordered(LittleDict{Int, String}) + @test !OrderedCollections.isordered(Dict{Int, String}) + end + + @testset "Test merging" begin + a = LittleDict("foo" => 0.0, "bar" => 42.0) + b = LittleDict("フー" => 17, "バー" => 4711) + result = merge(a, b) + @test isa(result, LittleDict{String,Float64}) + + expected = LittleDict("foo" => 0.0, "bar" => 42.0, "フー" => 17, "バー" => 4711) + @test result == expected + + c = LittleDict("a" => 1, "b" => 2, "c" => 3) + result = merge(a, b, c) + @test isa(result, LittleDict{String,Float64}) + + expected = LittleDict( + "foo" => 0.0, "bar" => 42.0, + "フー" => 17, "バー" => 4711, + "a" => 1, "b" => 2, "c" => 3, + ) + @test result == expected + + c = LittleDict("a" => 1, "b" => 2, "foo" => 3) + result = merge(a, b, c) + @test isa(result, LittleDict{String,Float64}) + + expected = LittleDict( + "foo" => 3, "bar" => 42.0, + "フー" => 17, "バー" => 4711, + "a" => 1, "b" => 2, + ) + @test result == expected + end + + @testset "Issue #9295" begin + d = LittleDict() + @test push!(d, 'a'=> 1) === d + @test d['a'] == 1 + @test push!(d, 'b' => 2, 'c' => 3) === d + @test d['b'] == 2 + @test d['c'] == 3 + @test push!(d, 'd' => 4, 'e' => 5, 'f' => 6) === d + @test d['d'] == 4 + @test d['e'] == 5 + @test d['f'] == 6 + @test length(d) == 6 + end + + @testset "Serialization" begin + s = IOBuffer() + od = LittleDict{Char,Int64}() + for c in 'a':'e' + od[c] = c-'a'+1 + end + serialize(s, od) + seek(s, 0) + dd = deserialize(s) + @test isa(dd, OrderedCollections.LittleDict{Char,Int64}) + @test dd == od + close(s) + end + + @testset "Issue #148" begin + d148 = LittleDict( + :gps => [], + :direction => 1:8, + :weather => 1:10 + ) + + d148_2 = LittleDict( + :time => 1:10, + :features => LittleDict( + :gps => 1:5, + :direction => 1:8, + :weather => 1:10 + ) + ) + end + + @testset "Issue #400" begin + @test filter(p->first(p) > 1, LittleDict(1=>2, 3=>4)) isa LittleDict + end + + @testset "Sorting" begin + d = LittleDict(i=>Char(123-i) for i in [4, 8, 1, 7, 9, 3, 10, 2, 6, 5]) + + @test collect(keys(d)) != 1:10 + sd = sort(d) + @test collect(keys(sd)) == 1:10 + @test collect(values(sd)) == collect('z':-1:'q') + @test sort(sd) == sd + sdv = sort(d; byvalue=true) + @test collect(keys(sdv)) == 10:-1:1 + @test collect(values(sdv)) == collect('q':'z') + end + + @testset "Test that LittleDict merge with combiner returns type LittleDict" begin + @test merge(+, LittleDict(:a=>1, :b=>2), LittleDict(:b=>7, :c=>4)) == LittleDict(:a=>1, :b=>9, :c=>4) + @test merge(+, LittleDict(:a=>1, :b=>2), Dict(:b=>7, :c=>4)) isa LittleDict + end + + @testset "issue #27" begin + d = LittleDict{Symbol, Int}(:x=>1) + d1 = LittleDict(:x=>1) + d_wide = LittleDict{Symbol, Number}(:x=>1) + @test d == d1 == d_wide + @test d isa LittleDict{Symbol, Int} + @test d1 isa LittleDict{Symbol, Int} + @test d_wide isa LittleDict{Symbol, Number} + + @test_throws MethodError LittleDict{Char,Char}(:x => 1) + end +end # @testset LittleDict + + +@testset "Frozen LittleDict" begin + + @testset "types" begin + base_dict = LittleDict((10,20,30),("a", "b", "c")) + @test base_dict isa LittleDict{Int, String, <:Tuple, <:Tuple} + + nonfrozen = LittleDict(10=>"a", 20=>"b", 30=>"c") + @test nonfrozen isa LittleDict{Int, String, <:Vector, <:Vector} + + @test base_dict == nonfrozen + + frozen = freeze(nonfrozen) + @test frozen isa LittleDict{Int, String, <:Tuple, <:Tuple} + @test frozen == base_dict + @test frozen === base_dict + end + + @testset "get" begin + fd = LittleDict((10,20,30),("a", "b", "c")) + @test fd[10] == "a" + @test fd[20] == "b" + @test fd[30] == "c" + @test_throws KeyError fd[-1] + end + + @testset "set" begin + fd = LittleDict((10,20,30),("a", "b", "c")) + @test_throws MethodError fd[10] = "ab" + @test_throws MethodError fd[20] = "bb" + @test_throws MethodError fd[30] = "cc" + @test_throws MethodError fd[-1] = "dd" + end + + @testset "map!(f, values(LittleDict))" begin + testdict = LittleDict(:a=>1, :b=>2) + map!(v->v-1, values(testdict)) + @test testdict[:a] == 0 + @test testdict[:b] == 1 +end +end diff --git a/packages/OrderedCollections/test/test_ordered_dict.jl b/packages/OrderedCollections/test/test_ordered_dict.jl new file mode 100644 index 0000000..da8de16 --- /dev/null +++ b/packages/OrderedCollections/test/test_ordered_dict.jl @@ -0,0 +1,464 @@ +using OrderedCollections, Test + +@testset "OrderedDict" begin + + @testset "Constructors" begin + @test isa(@inferred(OrderedDict()), OrderedDict{Any,Any}) + @test isa(@inferred(OrderedDict([(1,2.0)])), OrderedDict{Int,Float64}) + @test isa(@inferred(OrderedDict([("a",1),("b",2)])), OrderedDict{String,Int}) + @test isa(@inferred(OrderedDict(Pair(1, 1.0))), OrderedDict{Int,Float64}) + @test isa(@inferred(OrderedDict(Pair(1, 1.0), Pair(2, 2.0))), OrderedDict{Int,Float64}) + @test isa(@inferred(OrderedDict{Int,Float64}(Pair(1, 1), Pair(2, 2))), OrderedDict{Int,Float64}) + @test isa(@inferred(OrderedDict(Pair(1, 1.0), Pair(2, 2.0), Pair(3, 3.0))), OrderedDict{Int,Float64}) + @test OrderedDict(()) == OrderedDict{Any,Any}() + @test isa(@inferred(OrderedDict([Pair(1, 1.0), Pair(2, 2.0)])), OrderedDict{Int,Float64}) + @test_throws ArgumentError OrderedDict([1,2,3,4]) + iter = Iterators.filter(x->x.first>1, [Pair(1, 1.0), Pair(2, 2.0), Pair(3, 3.0)]) + @test @inferred(OrderedDict(iter)) == OrderedDict{Int,Float64}(2=>2.0, 3=>3.0) + iter = Iterators.drop(1:10, 1) + @test_throws ArgumentError OrderedDict(iter) + end + + @testset "empty dictionary" begin + d = OrderedDict{Char, Int}() + @test length(d) == 0 + @test isempty(d) + @test_throws KeyError d['c'] == 1 + d['c'] = 1 + @test !isempty(d) + @test_throws KeyError d[0.01] + @test isempty(empty(d)) + empty!(d) + @test isempty(d) + + # access, modification + for c in 'a':'z' + d[c] = c - 'a' + 1 + end + + @test (d['a'] += 1) == 2 + @test 'a' in keys(d) + @test haskey(d, 'a') + @test get(d, 'B', 0) == 0 + @test getkey(d, 'b', nothing) == 'b' + @test getkey(d, 'B', nothing) == nothing + @test !('B' in keys(d)) + @test !haskey(d, 'B') + @test pop!(d, 'a') == 2 + + @test collect(keys(d)) == collect('b':'z') + @test collect(values(d)) == collect(2:26) + @test collect(d) == [Pair(a,i) for (a,i) in zip('b':'z', 2:26)] + end + + @testset "convert" begin + d = OrderedDict{Int,Float32}(i=>Float32(i) for i = 1:10) + @test convert(OrderedDict{Int,Float32}, d) === d + dc = convert(OrderedDict{Int,Float64}, d) + @test dc !== d + @test keytype(dc) == Int + @test valtype(dc) == Float64 + @test keys(dc) == keys(d) + @test collect(values(dc)) == collect(values(d)) + end + + @testset "Issue #60" begin + od60 = OrderedDict{Int,Int}() + od60[1] = 2 + + ranges = [2:5, 6:9, 10:13] + for range in ranges + for i = range + od60[i] = i+1 + end + for i = range + delete!( od60, i ) + end + end + od60[14]=15 + + @test od60[14] == 15 + end + + + ############################## + # Copied and modified from Base/test/dict.jl + + # OrderedDict + + @testset "OrderedDict{Int,Int}" begin + h = OrderedDict{Int,Int}() + for i=1:10000 + h[i] = i+1 + end + + @test collect(h) == [Pair(x,y) for (x,y) in zip(1:10000, 2:10001)] + + for i=1:2:10000 + delete!(h, i) + end + for i=1:2:10000 + h[i] = i+1 + end + + for i=1:10000 + @test h[i]==i+1 + end + + for i=1:10000 + delete!(h, i) + end + @test isempty(h) + + h[77] = 100 + @test h[77]==100 + + for i=1:10000 + h[i] = i+1 + end + + for i=1:2:10000 + delete!(h, i) + end + + for i=10001:20000 + h[i] = i+1 + end + + for i=2:2:10000 + @test h[i]==i+1 + end + + for i=10000:20000 + @test h[i]==i+1 + end + end + + @testset "OrderedDict{Any,Any}" begin + h = OrderedDict{Any,Any}([("a", 3)]) + @test h["a"] == 3 + h["a","b"] = 4 + @test h["a","b"] == h[("a","b")] == 4 + h["a","b","c"] = 4 + @test h["a","b","c"] == h[("a","b","c")] == 4 + end + + @testset "KeyError" begin + z = OrderedDict() + get_KeyError = false + try + z["a"] + catch _e123_ + get_KeyError = isa(_e123_, KeyError) + end + @test get_KeyError + end + + @testset "filter" begin + _d = OrderedDict([("a", 0)]) + v = [k for k in filter(x->length(x)==1, collect(keys(_d)))] + @test isa(v, Vector{String}) + end + + @testset "from tuple/vector/pairs/tuple of pair 1" begin + d = OrderedDict(((1, 2), (3, 4))) + d2 = OrderedDict([(1, 2), (3, 4)]) + d3 = OrderedDict(1 => 2, 3 => 4) + d4 = OrderedDict((1 => 2, 3 => 4)) + + @test d[1] === 2 + @test d[3] === 4 + + @test d == d2 == d3 == d4 + @test isa(d, OrderedDict{Int,Int}) + @test isa(d2, OrderedDict{Int,Int}) + @test isa(d3, OrderedDict{Int,Int}) + @test isa(d4, OrderedDict{Int,Int}) + end + + @testset "from tuple/vector/pairs/tuple of pair 2" begin + d = OrderedDict(((1, 2), (3, "b"))) + d2 = OrderedDict([(1, 2), (3, "b")]) + d3 = OrderedDict(1 => 2, 3 => "b") + d4 = OrderedDict((1 => 2, 3 => "b")) + + @test d2[1] === 2 + @test d2[3] == "b" + + ## TODO: tuple of tuples doesn't work for mixed tuple types + # @test d == d2 == d3 == d4 + # @test isa(d, OrderedDict{Int,Any}) + @test d2 == d3 == d4 + @test isa(d2, OrderedDict{Int,Any}) + @test isa(d3, OrderedDict{Int,Any}) + @test isa(d4, OrderedDict{Int,Any}) + end + + @testset "from tuple/vector/pairs/tuple of pair 3" begin + d = OrderedDict(((1, 2), ("a", 4))) + d2 = OrderedDict([(1, 2), ("a", 4)]) + d3 = OrderedDict(1 => 2, "a" => 4) + d4 = OrderedDict((1 => 2, "a" => 4)) + + @test d2[1] === 2 + @test d2["a"] === 4 + + ## TODO: tuple of tuples doesn't work for mixed tuple types + # @test d == d2 == d3 == d4 + @test d2 == d3 == d4 + # @test isa(d, OrderedDict{Any,Int}) + @test isa(d2, OrderedDict{Any,Int}) + @test isa(d3, OrderedDict{Any,Int}) + @test isa(d4, OrderedDict{Any,Int}) + end + + @testset "from tuple/vector/pairs/tuple of pair 4" begin + d = OrderedDict(((1, 2), ("a", "b"))) + d2 = OrderedDict([(1, 2), ("a", "b")]) + d3 = OrderedDict(1 => 2, "a" => "b") + d4 = OrderedDict((1 => 2, "a" => "b")) + + @test d[1] === 2 + @test d["a"] == "b" + + @test d == d2 == d3 == d4 + @test isa(d, OrderedDict{Any,Any}) + @test isa(d2, OrderedDict{Any,Any}) + @test isa(d3, OrderedDict{Any,Any}) + @test isa(d4, OrderedDict{Any,Any}) + end + + @testset "first" begin + @test_throws ArgumentError first(OrderedDict()) + @test first(OrderedDict([(:f, 2)])) == Pair(:f,2) + end + + @testset "last" begin + @test last(OrderedDict([(:f, 2)])) == Pair(:f,2) + end + + @testset "Issue #1821" begin + d = OrderedDict{String, Vector{Int}}() + d["a"] = [1, 2] + @test_throws MethodError d["b"] = 1 + @test isa(repr(d), AbstractString) # check that printable without error + end + + @testset "Issue #2344" begin + bestkey(d, key) = key + bestkey(d::AbstractDict{K,V}, key) where {K<:AbstractString,V} = string(key) + bar(x) = bestkey(x, :y) + @test bar(OrderedDict([(:x, [1,2,5])])) == :y + @test bar(OrderedDict([("x", [1,2,5])])) == "y" + end + + @testset "isequal" begin + @test isequal(OrderedDict(), OrderedDict()) + @test isequal(OrderedDict([(1, 1)]), OrderedDict([(1, 1)])) + @test !isequal(OrderedDict([(1, 1)]), OrderedDict()) + @test !isequal(OrderedDict([(1, 1)]), OrderedDict([(1, 2)])) + @test !isequal(OrderedDict([(1, 1)]), OrderedDict([(2, 1)])) + + @test isequal(OrderedDict(), sizehint!(OrderedDict(),96)) + + # Here is what currently happens when dictionaries of different types + # are compared. This is not necessarily desirable. These tests are + # descriptive rather than proscriptive. + @test !isequal(OrderedDict([(1, 2)]), OrderedDict([("dog", "bone")])) + @test isequal(OrderedDict{Int,Int}(), OrderedDict{AbstractString,AbstractString}()) + end + + @testset "data_in" begin + # Generate some data to populate dicts to be compared + data_in = [ (rand(1:1000), randstring(2)) for _ in 1:1001 ] + + # Populate the first dict + d1 = OrderedDict{Int, String}() + for (k,v) in data_in + d1[k] = v + end + data_in = collect(d1) + # shuffle the data + for i in 1:length(data_in) + j = rand(1:length(data_in)) + data_in[i], data_in[j] = data_in[j], data_in[i] + end + # Inserting data in different (shuffled) order should result in + # equivalent dict. + d2 = OrderedDict{Int, AbstractString}() + for (k,v) in data_in + d2[k] = v + end + + @test isequal(d1, d2) + d3 = copy(d2) + d4 = copy(d2) + # Removing an item gives different dict + delete!(d1, data_in[rand(1:length(data_in))][1]) + @test !isequal(d1, d2) + # Changing a value gives different dict + d3[data_in[rand(1:length(data_in))][1]] = randstring(3) + !isequal(d1, d3) + # Adding a pair gives different dict + d4[1001] = randstring(3) + @test !isequal(d1, d4) + end + + @testset "get!" begin + # get! (get with default values assigned to the given location) + f(x) = x^2 + d = OrderedDict(8 => 19) + + @test get!(d, 8, 5) == 19 + @test get!(d, 19, 2) == 2 + + @test get!(d, 42) do # d is updated with f(2) + f(2) + end == 4 + + @test get!(d, 42) do # d is not updated + f(200) + end == 4 + + @test get(d, 13) do # d is not updated + f(4) + end == 16 + + @test d == OrderedDict(8=>19, 19=>2, 42=>4) + end + + @testset "Issue #5886" begin + d5886 = OrderedDict() + for k5886 in 1:11 + d5886[k5886] = 1 + end + for k5886 in keys(d5886) + # undefined ref if not fixed + d5886[k5886] += 1 + end + end + + @testset "Issue #216" begin + @test OrderedCollections.isordered(OrderedDict{Int, String}) + @test !OrderedCollections.isordered(Dict{Int, String}) + end + + @testset "Test merging" begin + a = OrderedDict("foo" => 0.0, "bar" => 42.0) + b = OrderedDict("フー" => 17, "バー" => 4711) + @test isa(merge(a, b), OrderedDict{String,Float64}) + end + + @testset "Issue #9295" begin + d = OrderedDict() + @test push!(d, 'a'=> 1) === d + @test d['a'] == 1 + @test push!(d, 'b' => 2, 'c' => 3) === d + @test d['b'] == 2 + @test d['c'] == 3 + @test push!(d, 'd' => 4, 'e' => 5, 'f' => 6) === d + @test d['d'] == 4 + @test d['e'] == 5 + @test d['f'] == 6 + @test length(d) == 6 + end + + @testset "Serialization" begin + s = IOBuffer() + od = OrderedDict{Char,Int64}() + for c in 'a':'e' + od[c] = c-'a'+1 + end + serialize(s, od) + seek(s, 0) + dd = deserialize(s) + @test isa(dd, OrderedCollections.OrderedDict{Char,Int64}) + @test dd == od + close(s) + end + + @testset "Issue #148" begin + d148 = OrderedDict( + :gps => [], + :direction => 1:8, + :weather => 1:10 + ) + + d148_2 = OrderedDict( + :time => 1:10, + :features => OrderedDict( + :gps => 1:5, + :direction => 1:8, + :weather => 1:10 + ) + ) + end + + @testset "Issue #400" begin + @test filter(p->first(p) > 1, OrderedDict(1=>2, 3=>4)) isa OrderedDict + end + + @testset "Issue #30" begin + d = OrderedDict(:a=>1, :b=>2) + d1 = OrderedDict(k=>v for (k,v) in d) + @test keytype(d1) == keytype(d) + @test valtype(d1) == valtype(d) + end + + @testset "Sorting" begin + d = Dict(i=>Char(123-i) for i = 1:10) + @test collect(keys(d)) != 1:10 + sd = sort!(OrderedDict(d)) + @test collect(keys(sd)) == 1:10 + @test collect(values(sd)) == collect('z':-1:'q') + @test sort(sd) == sd + sdv = sort!(OrderedDict(d); byvalue=true) + @test collect(keys(sdv)) == 10:-1:1 + @test collect(values(sdv)) == collect('q':'z') + end + + @testset "Test that OrderedDict merge with combiner returns type OrderedDict" begin + @test merge(+, OrderedDict(:a=>1, :b=>2), OrderedDict(:b=>7, :c=>4)) == OrderedDict(:a=>1, :b=>9, :c=>4) + @test merge(+, OrderedDict(:a=>1, :b=>2), Dict(:b=>7, :c=>4)) isa OrderedDict + end + + @testset "map!(f, values(OrderedDict))" begin + testdict = OrderedDict(:a=>1, :b=>2) + map!(v->v-1, values(testdict)) + @test testdict[:a] == 0 + @test testdict[:b] == 1 + end + + @testset "Issue #47" begin + @test eltype(OrderedDict(String => :string, SubString => :substring)) == Pair{Type,Symbol} + @test eltype(OrderedDict(:string => String, :substring => SubString)) == Pair{Symbol,Type} + @test eltype(OrderedDict(String => String, SubString => SubString)) == Pair{Type,Type} + + @test eltype(OrderedDict(tuple(String => :string, SubString => :substring))) == Pair{Type,Symbol} + @test eltype(OrderedDict(tuple(:string => String, :substring => SubString))) == Pair{Symbol,Type} + @test eltype(OrderedDict(tuple(String => String, SubString => SubString))) == Pair{Type,Type} + end + + @testset "Issue #71" begin + od = OrderedDict(Dict(i=>0 for i=1:158)) + sort!(od) + @test od[158] == 0 + end + + @testset "Issue #71b" begin + # This is actually a simplified version of #60, which was triggered while fixing #71 + # It doesn't actually fail on previous versions of OrderedCollections + od = OrderedDict{Int,Int}(13=>13) + delete!( od, 13 ) + od[14]=14 + @test od[14] == 14 + end + + @testset "ordered access" begin + od = OrderedDict(:a=>1, :b=>2, :c=>3) + @test popfirst!(od) == (:a => 1) + @test :a ∉ keys(od) + @test pop!(od) == (:c => 3) + @test :c ∉ keys(od) + end +end # @testset OrderedDict diff --git a/packages/OrderedCollections/test/test_ordered_set.jl b/packages/OrderedCollections/test/test_ordered_set.jl new file mode 100644 index 0000000..5dd6964 --- /dev/null +++ b/packages/OrderedCollections/test/test_ordered_set.jl @@ -0,0 +1,242 @@ +using OrderedCollections, Test + +@testset "OrderedSet" begin + + @testset "Constructors" begin + @test isa(OrderedSet(), OrderedSet{Any}) + @test isa(OrderedSet([1,2,3]), OrderedSet{Int}) + @test isa(OrderedSet{Int}([3]), OrderedSet{Int}) + data_in = (1, "banana", ()) + s = OrderedSet(data_in) + data_out = collect(s) + @test isa(data_out, Array{Any,1}) + @test tuple(data_out...) === data_in + @test tuple(data_in...) === tuple(s...) + @test length(data_out) == length(data_in) + end + + @testset "hash" begin + s1 = OrderedSet{String}(["bar", "foo"]) + s2 = OrderedSet{String}(["foo", "bar"]) + s3 = OrderedSet{String}(["baz"]) + @test hash(s1) != hash(s2) + @test hash(s1) != hash(s3) + end + + @testset "isequal" begin + @test isequal(OrderedSet(), OrderedSet()) + @test !isequal(OrderedSet(), OrderedSet([1])) + @test isequal(OrderedSet{Any}(Any[1,2]), OrderedSet{Int}([1,2])) + @test !isequal(OrderedSet{Any}(Any[1,2]), OrderedSet{Int}([1,2,3])) + + @test isequal(OrderedSet{Int}(), OrderedSet{AbstractString}()) + @test !isequal(OrderedSet{Int}(), OrderedSet{AbstractString}([""])) + @test !isequal(OrderedSet{AbstractString}(), OrderedSet{Int}([0])) + @test !isequal(OrderedSet{Int}([1]), OrderedSet{AbstractString}()) + @test isequal(OrderedSet{Any}([1,2,3]), OrderedSet{Int}([1,2,3])) + @test isequal(OrderedSet{Int}([1,2,3]), OrderedSet{Any}([1,2,3])) + @test !isequal(OrderedSet{Any}([1,2,3]), OrderedSet{Int}([1,2,3,4])) + @test !isequal(OrderedSet{Int}([1,2,3]), OrderedSet{Any}([1,2,3,4])) + @test !isequal(OrderedSet{Any}([1,2,3,4]), OrderedSet{Int}([1,2,3])) + @test !isequal(OrderedSet{Int}([1,2,3,4]), OrderedSet{Any}([1,2,3])) + end + + @testset "eltype, empty" begin + s1 = empty(OrderedSet([1,"hello"])) + @test isequal(s1, OrderedSet()) + @test eltype(s1) === Any + s2 = empty(OrderedSet{Float32}([2.0f0,3.0f0,4.0f0])) + @test isequal(s2, OrderedSet()) + @test eltype(s2) === Float32 + end + + @testset "show" begin + @test endswith(sprint(show, OrderedSet()), "OrderedSet{Any}()") + @test endswith(sprint(show, OrderedSet(['a'])), "OrderedSet{Char}(['a'])") + end + + @testset "Core Functionality" begin + s = OrderedSet(); push!(s,1); push!(s,2); push!(s,3) + @test !isempty(s) + @test in(1,s) + @test in(2,s) + @test length(s) == 3 + push!(s,1); push!(s,2); push!(s,3) + @test length(s) == 3 + @test pop!(s,1) == 1 + @test !in(1,s) + @test in(2,s) + @test length(s) == 2 + @test_throws KeyError pop!(s,1) + @test pop!(s,1,:foo) == :foo + @test length(delete!(s,2)) == 1 + @test !in(1,s) + @test !in(2,s) + @test pop!(s) == 3 + @test length(s) == 0 + @test isempty(s) + end + + @testset "copy" begin + data_in = (1,2,9,8,4) + s = OrderedSet(data_in) + c = copy(s) + @test isequal(s,c) + v = pop!(s) + @test !in(v,s) + @test in(v,c) + push!(s,100) + push!(c,200) + @test !in(100,c) + @test !in(200,s) + end + + @testset "sizehint!, empty" begin + s = OrderedSet([1]) + @test isequal(sizehint!(s, 10), OrderedSet([1])) + @test isequal(empty!(s), OrderedSet()) + # TODO: rehash + end + + @testset "iterate" begin + for data_in in ((7,8,4,5), + ("hello", 23, 2.7, (), [], (1,8))) + s = OrderedSet(data_in) + + s_new = OrderedSet() + for el in s + push!(s_new, el) + end + @test isequal(s, s_new) + + t = tuple(s...) + + @test t === data_in + @test length(t) == length(s) + for (e,f) in zip(t,s) + @test e === f + end + end + end + + @testset "union" begin + @test isequal(union(OrderedSet([1])),OrderedSet([1])) + s = ∪(OrderedSet([1,2]), OrderedSet([3,4])) + @test isequal(s, OrderedSet([1,2,3,4])) + s = union(OrderedSet([5,6,7,8]), OrderedSet([7,8,9])) + @test isequal(s, OrderedSet([5,6,7,8,9])) + s = OrderedSet([1,3,5,7]) + union!(s,(2,3,4,5)) + # TODO: order is not the same, so isequal should return false... + @test isequal(s,OrderedSet([1,2,3,4,5,7])) + end + + @testset "intersect" begin + @test isequal(intersect(OrderedSet([1])),OrderedSet([1])) + s = ∩(OrderedSet([1,2]), OrderedSet([3,4])) + @test isequal(s, OrderedSet()) + s = intersect(OrderedSet([5,6,7,8]), OrderedSet([7,8,9])) + @test isequal(s, OrderedSet([7,8])) + @test isequal(intersect(OrderedSet([2,3,1]), OrderedSet([4,2,3]), OrderedSet([5,4,3,2])), OrderedSet([2,3])) + end + + @testset "setdiff" begin + @test isequal(setdiff(OrderedSet([1,2,3]), OrderedSet()), OrderedSet([1,2,3])) + @test isequal(setdiff(OrderedSet([1,2,3]), OrderedSet([1])), OrderedSet([2,3])) + @test isequal(setdiff(OrderedSet([1,2,3]), Set([1])), OrderedSet([2,3])) + @test isequal(setdiff(OrderedSet([1,2,3]), OrderedSet([1,2])), OrderedSet([3])) + @test isequal(setdiff(OrderedSet([1,2,3]), Set([1,2])), OrderedSet([3])) + @test isequal(setdiff(OrderedSet([1,2,3]), OrderedSet([1,2,3])), OrderedSet()) + @test isequal(setdiff(OrderedSet([1,2,3]), OrderedSet([4])), OrderedSet([1,2,3])) + @test isequal(setdiff(OrderedSet([1,2,3]), OrderedSet([4,1])), OrderedSet([2,3])) + s = OrderedSet([1,3,5,7]) + setdiff!(s,(3,5)) + @test isequal(s,OrderedSet([1,7])) + s = OrderedSet([1,2,3,4]) + setdiff!(s, OrderedSet([2,4,5,6])) + @test isequal(s,OrderedSet([1,3])) + end + + @testset "ordering" begin + @test OrderedSet() < OrderedSet([1]) + @test OrderedSet([1]) < OrderedSet([1,2]) + @test !(OrderedSet([3]) < OrderedSet([1,2])) + @test !(OrderedSet([3]) > OrderedSet([1,2])) + @test OrderedSet([1,2,3]) > OrderedSet([1,2]) + @test !(OrderedSet([3]) <= OrderedSet([1,2])) + @test !(OrderedSet([3]) >= OrderedSet([1,2])) + @test OrderedSet([1]) <= OrderedSet([1,2]) + @test OrderedSet([1,2]) <= OrderedSet([1,2]) + @test OrderedSet([1,2]) >= OrderedSet([1,2]) + @test OrderedSet([1,2,3]) >= OrderedSet([1,2]) + @test !(OrderedSet([1,2,3]) >= OrderedSet([1,2,4])) + @test !(OrderedSet([1,2,3]) <= OrderedSet([1,2,4])) + end + + @testset "issubset, symdiff" begin + for (l,r) in ((OrderedSet([1,2]), OrderedSet([3,4])), + (OrderedSet([5,6,7,8]), OrderedSet([7,8,9])), + (OrderedSet([1,2]), OrderedSet([3,4])), + (OrderedSet([5,6,7,8]), OrderedSet([7,8,9])), + (OrderedSet([1,2,3]), OrderedSet()), + (OrderedSet([1,2,3]), OrderedSet([1])), + (OrderedSet([1,2,3]), OrderedSet([1,2])), + (OrderedSet([1,2,3]), OrderedSet([1,2,3])), + (OrderedSet([1,2,3]), OrderedSet([4])), + (OrderedSet([1,2,3]), OrderedSet([4,1]))) + @test issubset(intersect(l,r), l) + @test issubset(intersect(l,r), r) + @test issubset(l, union(l,r)) + @test issubset(r, union(l,r)) + @test isequal(union(intersect(l,r),symdiff(l,r)), union(l,r)) + end + @test ⊆(OrderedSet([1]), OrderedSet([1,2])) + + @test ⊊(OrderedSet([1]), OrderedSet([1,2])) + @test !⊊(OrderedSet([1]), OrderedSet([1])) + @test ⊈(OrderedSet([1]), OrderedSet([2])) + + @test symdiff(OrderedSet([1,2,3,4]), OrderedSet([2,4,5,6])) == OrderedSet([1,3,5,6]) + + @test isequal(symdiff(OrderedSet([1,2,3,4]), OrderedSet([2,4,5,6])), OrderedSet([1,3,5,6])) + + end + + @testset "filter" begin + s = OrderedSet([1,2,3,4]) + @test isequal(filter(isodd,s), OrderedSet([1,3])) + filter!(isodd, s) + @test isequal(s, OrderedSet([1,3])) + end + + @testset "first" begin + @test_throws ArgumentError first(OrderedSet()) + @test first(OrderedSet([2])) == 2 + end + + @testset "empty set" begin + d = OrderedSet{Char}() + @test length(d) == 0 + @test isempty(d) + @test !('c' in d) + push!(d, 'c') + @test !isempty(d) + empty!(d) + @test isempty(d) + end + + @testset "access, modification" begin + d = OrderedSet{Char}() + + for c in 'a':'z' + push!(d, c) + end + + for c in 'a':'z' + @test c in d + end + + @test collect(d) == collect('a':'z') + end + +end # @testset OrderedSet