├── .github ├── DOCS.md ├── codecov.yml ├── dependabot.yml └── workflows │ ├── check.yml │ ├── scheduled.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── lib.rs └── serde.rs /.github/DOCS.md: -------------------------------------------------------------------------------- 1 | # Github config and workflows 2 | 3 | In this folder there is configuration for codecoverage, dependabot, and ci 4 | workflows that check the library more deeply than the default configurations. 5 | 6 | This folder can be or was merged using a --allow-unrelated-histories merge 7 | strategy from which provides a 8 | reasonably sensible base for writing your own ci on. By using this strategy 9 | the history of the CI repo is included in your repo, and future updates to 10 | the CI can be merged later. 11 | 12 | To perform this merge run: 13 | 14 | ```shell 15 | git remote add ci https://github.com/jonhoo/rust-ci-conf.git 16 | git fetch ci 17 | git merge --allow-unrelated-histories ci/main 18 | ``` 19 | 20 | An overview of the files in this project is available at: 21 | , which contains some 22 | rationale for decisions and runs through an example of solving minimal version 23 | and OpenSSL issues. 24 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.codecov.com/docs/codecovyml-reference 2 | coverage: 3 | # Hold ourselves to a high bar 4 | range: 85..100 5 | round: down 6 | precision: 1 7 | status: 8 | # ref: https://docs.codecov.com/docs/commit-status 9 | project: 10 | default: 11 | # Avoid false negatives 12 | threshold: 1% 13 | 14 | # Test files aren't important for coverage 15 | ignore: 16 | - "tests" 17 | 18 | # Make comments less noisy 19 | comment: 20 | layout: "files" 21 | require_changes: true 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: daily 11 | ignore: 12 | - dependency-name: "*" 13 | # patch and minor updates don't matter for libraries as consumers of this library build 14 | # with their own lockfile, rather than the version specified in this library's lockfile 15 | # remove this ignore rule if your package has binaries to ensure that the binaries are 16 | # built with the exact set of dependencies and those are up to date. 17 | update-types: 18 | - "version-update:semver-patch" 19 | - "version-update:semver-minor" 20 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs whenever a PR is opened or updated, or a commit is pushed to main. It runs 2 | # several checks: 3 | # - fmt: checks that the code is formatted according to rustfmt 4 | # - clippy: checks that the code does not contain any clippy warnings 5 | # - doc: checks that the code can be documented without errors 6 | # - hack: check combinations of feature flags 7 | # - msrv: check that the msrv specified in the crate is correct 8 | permissions: 9 | contents: read 10 | # This configuration allows maintainers of this repo to create a branch and pull request based on 11 | # the new branch. Restricting the push trigger to the main branch ensures that the PR only gets 12 | # built once. 13 | on: 14 | push: 15 | branches: [main] 16 | pull_request: 17 | # If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that 18 | # we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 21 | cancel-in-progress: true 22 | name: check 23 | jobs: 24 | fmt: 25 | runs-on: ubuntu-latest 26 | name: stable / fmt 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | submodules: true 31 | - name: Install stable 32 | uses: dtolnay/rust-toolchain@stable 33 | with: 34 | components: rustfmt 35 | - name: cargo fmt --check 36 | run: cargo fmt --check 37 | clippy: 38 | runs-on: ubuntu-latest 39 | name: ${{ matrix.toolchain }} / clippy 40 | permissions: 41 | contents: read 42 | checks: write 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | # Get early warning of new lints which are regularly introduced in beta channels. 47 | toolchain: [stable, beta] 48 | steps: 49 | - uses: actions/checkout@v4 50 | with: 51 | submodules: true 52 | - name: Install ${{ matrix.toolchain }} 53 | uses: dtolnay/rust-toolchain@master 54 | with: 55 | toolchain: ${{ matrix.toolchain }} 56 | components: clippy 57 | - name: cargo clippy 58 | uses: giraffate/clippy-action@v1 59 | with: 60 | reporter: 'github-pr-check' 61 | github_token: ${{ secrets.GITHUB_TOKEN }} 62 | semver: 63 | runs-on: ubuntu-latest 64 | name: semver 65 | steps: 66 | - uses: actions/checkout@v4 67 | with: 68 | submodules: true 69 | - name: Install stable 70 | uses: dtolnay/rust-toolchain@stable 71 | with: 72 | components: rustfmt 73 | - name: cargo-semver-checks 74 | uses: obi1kenobi/cargo-semver-checks-action@v2 75 | doc: 76 | # run docs generation on nightly rather than stable. This enables features like 77 | # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an 78 | # API be documented as only available in some specific platforms. 79 | runs-on: ubuntu-latest 80 | name: nightly / doc 81 | steps: 82 | - uses: actions/checkout@v4 83 | with: 84 | submodules: true 85 | - name: Install nightly 86 | uses: dtolnay/rust-toolchain@nightly 87 | - name: Install cargo-docs-rs 88 | uses: dtolnay/install@cargo-docs-rs 89 | - name: cargo docs-rs 90 | run: cargo docs-rs 91 | hack: 92 | # cargo-hack checks combinations of feature flags to ensure that features are all additive 93 | # which is required for feature unification 94 | runs-on: ubuntu-latest 95 | name: ubuntu / stable / features 96 | steps: 97 | - uses: actions/checkout@v4 98 | with: 99 | submodules: true 100 | - name: Install stable 101 | uses: dtolnay/rust-toolchain@stable 102 | - name: cargo install cargo-hack 103 | uses: taiki-e/install-action@cargo-hack 104 | # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 105 | # --feature-powerset runs for every combination of features 106 | - name: cargo hack 107 | run: cargo hack --feature-powerset check 108 | msrv: 109 | # check that we can build using the minimal rust version that is specified by this crate 110 | runs-on: ubuntu-latest 111 | # we use a matrix here just because env can't be used in job names 112 | # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability 113 | strategy: 114 | matrix: 115 | msrv: ["1.40.0"] # map_get_key_value 116 | name: ubuntu / ${{ matrix.msrv }} 117 | steps: 118 | - uses: actions/checkout@v4 119 | with: 120 | submodules: true 121 | - name: Install ${{ matrix.msrv }} 122 | uses: dtolnay/rust-toolchain@master 123 | with: 124 | toolchain: ${{ matrix.msrv }} 125 | - name: cargo +${{ matrix.msrv }} check 126 | run: cargo check 127 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yml: -------------------------------------------------------------------------------- 1 | # Run scheduled (rolling) jobs on a nightly basis, as your crate may break independently of any 2 | # given PR. E.g., updates to rust nightly and updates to this crates dependencies. See check.yml for 3 | # information about how the concurrency cancellation and workflow triggering works 4 | permissions: 5 | contents: read 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | schedule: 11 | - cron: '7 7 * * *' 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 14 | cancel-in-progress: true 15 | name: rolling 16 | jobs: 17 | # https://twitter.com/mycoliza/status/1571295690063753218 18 | nightly: 19 | runs-on: ubuntu-latest 20 | name: ubuntu / nightly 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | submodules: true 25 | - name: Install nightly 26 | uses: dtolnay/rust-toolchain@nightly 27 | - name: cargo generate-lockfile 28 | if: hashFiles('Cargo.lock') == '' 29 | run: cargo generate-lockfile 30 | - name: cargo test --locked 31 | run: cargo test --locked --all-features --all-targets 32 | # https://twitter.com/alcuadrado/status/1571291687837732873 33 | update: 34 | # This action checks that updating the dependencies of this crate to the latest available that 35 | # satisfy the versions in Cargo.toml does not break this crate. This is important as consumers 36 | # of this crate will generally use the latest available crates. This is subject to the standard 37 | # Cargo semver rules (i.e cargo does not update to a new major version unless explicitly told 38 | # to). 39 | runs-on: ubuntu-latest 40 | name: ubuntu / beta / updated 41 | # There's no point running this if no Cargo.lock was checked in in the first place, since we'd 42 | # just redo what happened in the regular test job. Unfortunately, hashFiles only works in if on 43 | # steps, so we repeat it. 44 | steps: 45 | - uses: actions/checkout@v4 46 | with: 47 | submodules: true 48 | - name: Install beta 49 | if: hashFiles('Cargo.lock') != '' 50 | uses: dtolnay/rust-toolchain@beta 51 | - name: cargo update 52 | if: hashFiles('Cargo.lock') != '' 53 | run: cargo update 54 | - name: cargo test 55 | if: hashFiles('Cargo.lock') != '' 56 | run: cargo test --locked --all-features --all-targets 57 | env: 58 | RUSTFLAGS: -D deprecated 59 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This is the main CI workflow that runs the test suite on all pushes to main and all pull requests. 2 | # It runs the following jobs: 3 | # - required: runs the test suite on ubuntu with stable and beta rust toolchains 4 | # - minimal: runs the test suite with the minimal versions of the dependencies that satisfy the 5 | # requirements of this crate, and its dependencies 6 | # - os-check: runs the test suite on mac and windows 7 | # - coverage: runs the test suite and collects coverage information 8 | # See check.yml for information about how the concurrency cancellation and workflow triggering works 9 | permissions: 10 | contents: read 11 | on: 12 | push: 13 | branches: [main] 14 | pull_request: 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 17 | cancel-in-progress: true 18 | name: test 19 | jobs: 20 | required: 21 | runs-on: ubuntu-latest 22 | name: ubuntu / ${{ matrix.toolchain }} 23 | strategy: 24 | matrix: 25 | # run on stable and beta to ensure that tests won't break on the next version of the rust 26 | # toolchain 27 | toolchain: [stable, beta] 28 | steps: 29 | - uses: actions/checkout@v4 30 | with: 31 | submodules: true 32 | - name: Install ${{ matrix.toolchain }} 33 | uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: ${{ matrix.toolchain }} 36 | - name: cargo generate-lockfile 37 | # enable this ci template to run regardless of whether the lockfile is checked in or not 38 | if: hashFiles('Cargo.lock') == '' 39 | run: cargo generate-lockfile 40 | # https://twitter.com/jonhoo/status/1571290371124260865 41 | - name: cargo test --locked 42 | run: cargo test --locked --all-features --all-targets 43 | # https://github.com/rust-lang/cargo/issues/6669 44 | - name: cargo test --doc 45 | run: cargo test --locked --all-features --doc 46 | minimal: 47 | # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure 48 | # that this crate is compatible with the minimal version that this crate and its dependencies 49 | # require. This will pickup issues where this create relies on functionality that was introduced 50 | # later than the actual version specified (e.g., when we choose just a major version, but a 51 | # method was added after this version). 52 | # 53 | # This particular check can be difficult to get to succeed as often transitive dependencies may 54 | # be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There 55 | # is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for 56 | # direct dependencies of this crate, while selecting the maximal versions for the transitive 57 | # dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase 58 | # the minimal dependency, which you do with e.g.: 59 | # ```toml 60 | # # for minimal-versions 61 | # [target.'cfg(any())'.dependencies] 62 | # openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions 63 | # ``` 64 | # The optional = true is necessary in case that dependency isn't otherwise transitively required 65 | # by your library, and the target bit is so that this dependency edge never actually affects 66 | # Cargo build order. See also 67 | # https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49. 68 | # This action is run on ubuntu with the stable toolchain, as it is not expected to fail 69 | runs-on: ubuntu-latest 70 | name: ubuntu / stable / minimal-versions 71 | steps: 72 | - uses: actions/checkout@v4 73 | with: 74 | submodules: true 75 | - name: Install stable 76 | uses: dtolnay/rust-toolchain@stable 77 | - name: Install nightly for -Zminimal-versions 78 | uses: dtolnay/rust-toolchain@nightly 79 | - name: rustup default stable 80 | run: rustup default stable 81 | - name: cargo update -Zminimal-versions 82 | run: cargo +nightly update -Zminimal-versions 83 | - name: cargo test 84 | run: cargo test --locked --all-features --all-targets 85 | os-check: 86 | # run cargo test on mac and windows 87 | runs-on: ${{ matrix.os }} 88 | name: ${{ matrix.os }} / stable 89 | strategy: 90 | fail-fast: false 91 | matrix: 92 | os: [macos-latest, windows-latest] 93 | steps: 94 | # if your project needs OpenSSL, uncomment this to fix Windows builds. 95 | # it's commented out by default as the install command takes 5-10m. 96 | # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append 97 | # if: runner.os == 'Windows' 98 | # - run: vcpkg install openssl:x64-windows-static-md 99 | # if: runner.os == 'Windows' 100 | - uses: actions/checkout@v4 101 | with: 102 | submodules: true 103 | - name: Install stable 104 | uses: dtolnay/rust-toolchain@stable 105 | - name: cargo generate-lockfile 106 | if: hashFiles('Cargo.lock') == '' 107 | run: cargo generate-lockfile 108 | - name: cargo test 109 | run: cargo test --locked --all-features --all-targets 110 | coverage: 111 | # use llvm-cov to build and collect coverage and outputs in a format that 112 | # is compatible with codecov.io 113 | # 114 | # note that codecov as of v4 requires that CODECOV_TOKEN from 115 | # 116 | # https://app.codecov.io/gh///settings 117 | # 118 | # is set in two places on your repo: 119 | # 120 | # - https://github.com/jonhoo/guardian/settings/secrets/actions 121 | # - https://github.com/jonhoo/guardian/settings/secrets/dependabot 122 | # 123 | # (the former is needed for codecov uploads to work with Dependabot PRs) 124 | # 125 | # PRs coming from forks of your repo will not have access to the token, but 126 | # for those, codecov allows uploading coverage reports without a token. 127 | # it's all a little weird and inconvenient. see 128 | # 129 | # https://github.com/codecov/feedback/issues/112 130 | # 131 | # for lots of more discussion 132 | runs-on: ubuntu-latest 133 | name: ubuntu / stable / coverage 134 | steps: 135 | - uses: actions/checkout@v4 136 | with: 137 | submodules: true 138 | - name: Install stable 139 | uses: dtolnay/rust-toolchain@stable 140 | with: 141 | components: llvm-tools-preview 142 | - name: cargo install cargo-llvm-cov 143 | uses: taiki-e/install-action@cargo-llvm-cov 144 | - name: cargo generate-lockfile 145 | if: hashFiles('Cargo.lock') == '' 146 | run: cargo generate-lockfile 147 | - name: cargo llvm-cov 148 | run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info 149 | - name: Record Rust version 150 | run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" 151 | - name: Upload to codecov.io 152 | uses: codecov/codecov-action@v5 153 | with: 154 | fail_ci_if_error: true 155 | token: ${{ secrets.CODECOV_TOKEN }} 156 | env_vars: OS,RUST 157 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.8" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "bincode" 18 | version = "1.3.3" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 21 | dependencies = [ 22 | "serde", 23 | ] 24 | 25 | [[package]] 26 | name = "cfg-if" 27 | version = "1.0.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 30 | 31 | [[package]] 32 | name = "getrandom" 33 | version = "0.2.12" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 36 | dependencies = [ 37 | "cfg-if", 38 | "libc", 39 | "wasi", 40 | ] 41 | 42 | [[package]] 43 | name = "griddle" 44 | version = "0.5.2" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "6bb81d22191b89b117cd12d6549544bfcba0da741efdcec7c7d2fd06a0f56363" 47 | dependencies = [ 48 | "ahash", 49 | "hashbrown", 50 | ] 51 | 52 | [[package]] 53 | name = "hashbag" 54 | version = "0.1.12" 55 | dependencies = [ 56 | "bincode", 57 | "griddle", 58 | "serde", 59 | "serde_json", 60 | ] 61 | 62 | [[package]] 63 | name = "hashbrown" 64 | version = "0.11.2" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 67 | dependencies = [ 68 | "ahash", 69 | ] 70 | 71 | [[package]] 72 | name = "itoa" 73 | version = "1.0.10" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 76 | 77 | [[package]] 78 | name = "libc" 79 | version = "0.2.153" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 82 | 83 | [[package]] 84 | name = "once_cell" 85 | version = "1.19.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 88 | 89 | [[package]] 90 | name = "proc-macro2" 91 | version = "1.0.78" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 94 | dependencies = [ 95 | "unicode-ident", 96 | ] 97 | 98 | [[package]] 99 | name = "quote" 100 | version = "1.0.35" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 103 | dependencies = [ 104 | "proc-macro2", 105 | ] 106 | 107 | [[package]] 108 | name = "ryu" 109 | version = "1.0.16" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 112 | 113 | [[package]] 114 | name = "serde" 115 | version = "1.0.196" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" 118 | dependencies = [ 119 | "serde_derive", 120 | ] 121 | 122 | [[package]] 123 | name = "serde_derive" 124 | version = "1.0.196" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" 127 | dependencies = [ 128 | "proc-macro2", 129 | "quote", 130 | "syn", 131 | ] 132 | 133 | [[package]] 134 | name = "serde_json" 135 | version = "1.0.113" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" 138 | dependencies = [ 139 | "itoa", 140 | "ryu", 141 | "serde", 142 | ] 143 | 144 | [[package]] 145 | name = "syn" 146 | version = "2.0.49" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" 149 | dependencies = [ 150 | "proc-macro2", 151 | "quote", 152 | "unicode-ident", 153 | ] 154 | 155 | [[package]] 156 | name = "unicode-ident" 157 | version = "1.0.12" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 160 | 161 | [[package]] 162 | name = "version_check" 163 | version = "0.9.4" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 166 | 167 | [[package]] 168 | name = "wasi" 169 | version = "0.11.0+wasi-snapshot-preview1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 172 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hashbag" 3 | version = "0.1.12" 4 | authors = ["Jon Gjengset "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | readme = "README.md" 9 | description = "An unordered multiset implementation using a hash bag" 10 | repository = "https://github.com/jonhoo/hashbag.git" 11 | 12 | keywords = ["bag","multiset","set"] 13 | categories = ["data-structures"] 14 | 15 | [dependencies] 16 | griddle = { version = "0.5", optional = true } 17 | serde = { version = "1.0", optional = true } 18 | 19 | [dev-dependencies] 20 | serde_json = { version = "1.0" } 21 | serde = { version = "1.0", features = ["derive"] } 22 | bincode = { version = "1.3" } 23 | 24 | [features] 25 | amortize = ["griddle"] 26 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Jon Gjengset 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jon Gjengset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/hashbag.svg)](https://crates.io/crates/hashbag) 2 | [![Documentation](https://docs.rs/hashbag/badge.svg)](https://docs.rs/hashbag/) 3 | [![codecov](https://codecov.io/gh/jonhoo/hashbag/graph/badge.svg?token=ld3GTa1Fqq)](https://codecov.io/gh/jonhoo/hashbag) 4 | [![Dependency status](https://deps.rs/repo/github/jonhoo/hashbag/status.svg)](https://deps.rs/repo/github/jonhoo/hashbag) 5 | 6 | An unordered multiset/bag implementation backed by `HashMap`. 7 | 8 | A bag, unlike a set, allows duplicate values, and keeps track of how many 9 | duplicates each value holds. This type of collection is often referred to 10 | as an unordered multiset (see also C++'s [`std::unordered_multiset`]). 11 | 12 | [`std::unordered_multiset`]: http://www.cplusplus.com/reference/unordered_set/unordered_multiset/ 13 | 14 | ## License 15 | 16 | Licensed under either of 17 | 18 | * Apache License, Version 2.0 19 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 20 | * MIT license 21 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 22 | 23 | at your option. 24 | 25 | ## Contribution 26 | 27 | Unless you explicitly state otherwise, any contribution intentionally submitted 28 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 29 | dual licensed as above, without any additional terms or conditions. 30 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An unordered multiset/bag implementation backed by `HashMap`. 2 | //! 3 | //! A bag, unlike a set, allows duplicate values, and keeps track of how many 4 | //! duplicates each value holds. This type of collection is often referred to 5 | //! as an unordered multiset (see also C++'s [`std::unordered_multiset`]). 6 | //! 7 | //! This multiset/bag is implemented using a `HashMap` and so requires 8 | //! that the stored type implements `Hash + Eq`. 9 | //! 10 | //! For usage examples, see the primary type [`HashBag`]. 11 | //! 12 | //! If you want to use a hash table with [amortized resizes](https://github.com/jonhoo/griddle/), 13 | //! set the `amortize` feature. 14 | //! 15 | //! (De)serialization via serde is also available with the `serde` feature. 16 | //! Deserialization note: if the incoming data contains two instances of `T` that are the same, the resulting `HashBag` will merge 17 | //! the counts of those instances. 18 | //! 19 | //! [`std::unordered_multiset`]: http://www.cplusplus.com/reference/unordered_set/unordered_multiset/ 20 | #![deny(missing_docs, missing_debug_implementations, unreachable_pub)] 21 | #![cfg_attr(doc, deny(rustdoc::broken_intra_doc_links))] 22 | #![warn(rust_2018_idioms)] 23 | 24 | #[cfg(feature = "amortize")] 25 | use griddle::HashMap; 26 | use std::borrow::Borrow; 27 | use std::collections::hash_map::RandomState; 28 | #[cfg(not(feature = "amortize"))] 29 | use std::collections::HashMap; 30 | use std::convert::TryFrom; 31 | use std::hash::{BuildHasher, Hash}; 32 | 33 | #[cfg(feature = "serde")] 34 | mod serde; 35 | 36 | /// A hash bag implemented as a `HashMap` where the value is `usize`. 37 | /// 38 | /// A bag, unlike a set, allows duplicate values, and keeps track of how many 39 | /// duplicates each value holds. This type of collection is often referred to 40 | /// as an unordered multiset. 41 | /// 42 | /// As with the [`HashMap`] type, a `HashBag` requires that the elements 43 | /// implement the [`Eq`] and [`Hash`] traits. This can frequently be achieved by 44 | /// using `#[derive(PartialEq, Eq, Hash)]`. If you implement these yourself, 45 | /// it is important that the following property holds: 46 | /// 47 | /// ```text 48 | /// k1 == k2 -> hash(k1) == hash(k2) 49 | /// ``` 50 | /// 51 | /// In other words, if two keys are equal, their hashes must be equal. 52 | /// 53 | /// It is a logic error for an item to be modified in such a way that the 54 | /// item's hash, as determined by the [`Hash`] trait, or its equality, as 55 | /// determined by the [`Eq`] trait, changes while it is in the bag. 56 | /// 57 | /// # Examples 58 | /// 59 | /// ``` 60 | /// use hashbag::HashBag; 61 | /// // Type inference lets us omit an explicit type signature (which 62 | /// // would be `HashBag` in this example). 63 | /// let mut books = HashBag::new(); 64 | /// 65 | /// // Add some books. 66 | /// // Since we are a library, we have many copies. 67 | /// books.insert("A Dance With Dragons".to_string()); 68 | /// books.insert("To Kill a Mockingbird".to_string()); 69 | /// books.insert("To Kill a Mockingbird".to_string()); 70 | /// books.insert("The Odyssey".to_string()); 71 | /// books.insert("The Odyssey".to_string()); 72 | /// books.insert("The Odyssey".to_string()); 73 | /// books.insert("The Great Gatsby".to_string()); 74 | /// books.insert("The Great Gatsby".to_string()); 75 | /// books.insert("The Great Gatsby".to_string()); 76 | /// books.insert("The Great Gatsby".to_string()); 77 | /// 78 | /// // When we count the number of books, duplicates are included. 79 | /// assert_eq!(books.len(), 10); 80 | /// 81 | /// // Check for a specific one. 82 | /// if books.contains("The Winds of Winter") == 0 { 83 | /// println!("We have {} books, but The Winds of Winter ain't one.", 84 | /// books.len()); 85 | /// } 86 | /// 87 | /// // Remove a book. 88 | /// let had_copies = books.remove("The Odyssey"); 89 | /// // Remove returns how many copies of that book we had. 90 | /// assert_eq!(had_copies, 3); 91 | /// 92 | /// // Iterate over everything. 93 | /// // Duplicates will be listed multiple times. 94 | /// for book in &books { 95 | /// println!("{}", book); 96 | /// } 97 | /// 98 | /// // Iterate over each distinct book. 99 | /// for (book, copies) in books.set_iter() { 100 | /// println!("{} ({} copies)", book, copies); 101 | /// } 102 | /// 103 | /// // Extract the books and their counts. 104 | /// for (book, copies) in books { 105 | /// println!("{} ({} copies)", book, copies); 106 | /// } 107 | /// ``` 108 | /// 109 | /// The easiest way to use `HashBag` with a custom type is to derive 110 | /// [`Eq`] and [`Hash`]. We must also derive [`PartialEq`], this will in the 111 | /// future be implied by [`Eq`]. 112 | /// 113 | /// ``` 114 | /// use hashbag::HashBag; 115 | /// #[derive(Hash, Eq, PartialEq, Debug, Clone)] 116 | /// struct Viking { 117 | /// name: String, 118 | /// power: usize, 119 | /// } 120 | /// 121 | /// let mut vikings = HashBag::new(); 122 | /// 123 | /// vikings.insert(Viking { name: "Einar".to_string(), power: 9 }); 124 | /// vikings.insert(Viking { name: "Einar".to_string(), power: 9 }); 125 | /// vikings.insert(Viking { name: "Olaf".to_string(), power: 4 }); 126 | /// vikings.insert(Viking { name: "Olaf".to_string(), power: 5 }); 127 | /// vikings.insert(Viking { name: "Harald".to_string(), power: 8 }); 128 | /// 129 | /// // Use derived implementation to print the vikings. 130 | /// // Notice that all duplicates are printed. 131 | /// for v in &vikings { 132 | /// println!("{:?}", v); 133 | /// } 134 | /// 135 | /// // Since the derived implementation compares all the fields, 136 | /// // vikings that share a name but not a power are not duplicates. 137 | /// for (v, n) in vikings.set_iter() { 138 | /// println!("{:?} ({} of them!)", v, n); 139 | /// } 140 | /// 141 | /// // HashBags themselves can also be compared for equality, 142 | /// // and will do so by considering both the values and their counts. 143 | /// let mut vikings2 = vikings.clone(); 144 | /// assert_eq!(vikings, vikings2); 145 | /// let fallen = vikings.iter().next().unwrap(); 146 | /// vikings2.remove(fallen); 147 | /// assert_ne!(vikings, vikings2); 148 | /// vikings2.insert(Viking { name: "Snorre".to_string(), power: 1 }); 149 | /// assert_ne!(vikings, vikings2); 150 | /// ``` 151 | /// 152 | /// A `HashBag` with fixed list of elements can be initialized from an array: 153 | /// 154 | /// ``` 155 | /// use hashbag::HashBag; 156 | /// 157 | /// let mut viking_names: HashBag<&'static str> = 158 | /// [ "Einar", "Olaf", "Harald" ].iter().cloned().collect(); 159 | /// // use the values stored in the bag 160 | /// ``` 161 | /// 162 | /// You can also extend the bag easily: 163 | /// 164 | /// ``` 165 | /// use hashbag::HashBag; 166 | /// 167 | /// let mut vikings: HashBag = HashBag::new(); 168 | /// vikings.extend(std::iter::once("Snorre".to_string())); 169 | /// assert_eq!(vikings.contains("Snorre"), 1); 170 | /// 171 | /// // You can extend with many instances at once: 172 | /// vikings.extend(std::iter::once(("Snorre".to_string(), 4))); 173 | /// assert_eq!(vikings.contains("Snorre"), 5); 174 | /// 175 | /// // Extension also works with reference iterators if the type is Clone: 176 | /// let einar = String::from("Einar"); 177 | /// vikings.extend(std::iter::once(&einar)); 178 | /// assert_eq!(vikings.contains(&einar), 1); 179 | /// 180 | /// // And extend with many instances at once: 181 | /// vikings.extend(std::iter::once((&einar, 4))); 182 | /// assert_eq!(vikings.contains(&einar), 5); 183 | /// ``` 184 | pub struct HashBag { 185 | items: HashMap, 186 | count: usize, 187 | } 188 | 189 | impl Clone for HashBag { 190 | fn clone(&self) -> Self { 191 | Self { 192 | items: self.items.clone(), 193 | count: self.count, 194 | } 195 | } 196 | 197 | fn clone_from(&mut self, source: &Self) { 198 | self.items.clone_from(&source.items); 199 | self.count = source.count; 200 | } 201 | } 202 | 203 | impl HashBag { 204 | /// Creates an empty `HashBag`. 205 | /// 206 | /// The hash bag is initially created with a capacity of 0, so it will not allocate until it 207 | /// is first inserted into. 208 | /// 209 | /// # Examples 210 | /// 211 | /// ``` 212 | /// use hashbag::HashBag; 213 | /// let bag: HashBag = HashBag::new(); 214 | /// ``` 215 | #[inline] 216 | pub fn new() -> HashBag { 217 | Self::with_hasher(RandomState::new()) 218 | } 219 | 220 | /// Creates an empty `HashBag` with the specified capacity. 221 | /// 222 | /// The hash bag will be able to hold at least `capacity` distinct values without 223 | /// reallocating. If `capacity` is 0, the hash bag will not allocate. 224 | /// 225 | /// # Examples 226 | /// 227 | /// ``` 228 | /// use hashbag::HashBag; 229 | /// let bag: HashBag = HashBag::with_capacity(10); 230 | /// assert!(bag.capacity() >= 10); 231 | /// ``` 232 | #[inline] 233 | pub fn with_capacity(capacity: usize) -> HashBag { 234 | Self::with_capacity_and_hasher(capacity, RandomState::new()) 235 | } 236 | } 237 | 238 | impl HashBag { 239 | /// Returns the number of distinct values the bag can hold without reallocating. 240 | /// 241 | /// # Examples 242 | /// 243 | /// ``` 244 | /// use hashbag::HashBag; 245 | /// let bag: HashBag = HashBag::with_capacity(100); 246 | /// assert!(bag.capacity() >= 100); 247 | /// ``` 248 | #[inline] 249 | pub fn capacity(&self) -> usize { 250 | self.items.capacity() 251 | } 252 | 253 | /// An iterator visiting all elements in arbitrary order. 254 | /// 255 | /// The iterator element type is `&'a T`. 256 | /// Duplicates are yielded as many times as they appear in the bag. 257 | /// 258 | /// # Examples 259 | /// 260 | /// ``` 261 | /// use hashbag::HashBag; 262 | /// let mut bag = HashBag::new(); 263 | /// bag.insert("a"); 264 | /// bag.insert("b"); 265 | /// bag.insert("b"); 266 | /// 267 | /// // Will print in an arbitrary order. 268 | /// // b will be printed twice. 269 | /// for x in bag.iter() { 270 | /// println!("{}", x); 271 | /// } 272 | /// ``` 273 | #[inline] 274 | pub fn iter(&self) -> Iter<'_, T> { 275 | Iter::new(self.items.iter(), self.count) 276 | } 277 | 278 | /// An iterator visiting all distinct elements in arbitrary order. 279 | /// 280 | /// The iterator element type is `(&'a T, usize)`. 281 | /// Duplicated values are yielded once along with a count of the number of occurrences. 282 | /// 283 | /// # Examples 284 | /// 285 | /// ``` 286 | /// use hashbag::HashBag; 287 | /// let mut bag = HashBag::new(); 288 | /// bag.insert("a"); 289 | /// bag.insert("b"); 290 | /// bag.insert("b"); 291 | /// 292 | /// // Will print in an arbitrary order. 293 | /// for (x, n) in bag.set_iter() { 294 | /// println!("{} {}", x, n); 295 | /// } 296 | /// ``` 297 | #[inline] 298 | pub fn set_iter(&self) -> SetIter<'_, T> { 299 | SetIter(self.items.iter()) 300 | } 301 | 302 | /// Returns the number of elements in the bag. 303 | /// 304 | /// Duplicates are counted. 305 | /// 306 | /// # Examples 307 | /// 308 | /// ``` 309 | /// use hashbag::HashBag; 310 | /// 311 | /// let mut bag = HashBag::new(); 312 | /// assert_eq!(bag.len(), 0); 313 | /// bag.insert(1); 314 | /// assert_eq!(bag.len(), 1); 315 | /// bag.insert(1); 316 | /// assert_eq!(bag.len(), 2); 317 | /// ``` 318 | #[inline] 319 | pub fn len(&self) -> usize { 320 | self.count 321 | } 322 | 323 | /// Returns the number of elements in the bag. 324 | /// 325 | /// Duplicates are not counted. 326 | /// 327 | /// # Examples 328 | /// 329 | /// ``` 330 | /// use hashbag::HashBag; 331 | /// 332 | /// let mut bag = HashBag::new(); 333 | /// assert_eq!(bag.set_len(), 0); 334 | /// bag.insert(1); 335 | /// assert_eq!(bag.set_len(), 1); 336 | /// bag.insert(1); 337 | /// assert_eq!(bag.set_len(), 1); 338 | /// ``` 339 | #[inline] 340 | pub fn set_len(&self) -> usize { 341 | self.items.len() 342 | } 343 | 344 | /// Returns `true` if the bag contains no elements. 345 | /// 346 | /// # Examples 347 | /// 348 | /// ``` 349 | /// use hashbag::HashBag; 350 | /// 351 | /// let mut bag = HashBag::new(); 352 | /// assert!(bag.is_empty()); 353 | /// bag.insert(1); 354 | /// assert!(!bag.is_empty()); 355 | /// ``` 356 | #[inline] 357 | pub fn is_empty(&self) -> bool { 358 | self.count == 0 359 | } 360 | 361 | /// Clears the bag, returning all elements in an iterator. 362 | /// 363 | /// Duplicates appear only in the count yielded for each element. 364 | /// 365 | /// # Examples 366 | /// 367 | /// ``` 368 | /// use hashbag::HashBag; 369 | /// 370 | /// let mut bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); 371 | /// assert!(!bag.is_empty()); 372 | /// 373 | /// // prints 374 | /// // 1 1 375 | /// // 2 1 376 | /// // 3 2 377 | /// // in an arbitrary order 378 | /// for (i, n) in bag.drain() { 379 | /// println!("{} {}", i, n); 380 | /// } 381 | /// 382 | /// assert!(bag.is_empty()); 383 | /// ``` 384 | #[inline] 385 | pub fn drain(&mut self) -> Drain<'_, T> { 386 | self.count = 0; 387 | Drain(self.items.drain()) 388 | } 389 | 390 | /// Clears the bag, removing all values. 391 | /// 392 | /// # Examples 393 | /// 394 | /// ``` 395 | /// use hashbag::HashBag; 396 | /// 397 | /// let mut bag = HashBag::new(); 398 | /// bag.insert(1); 399 | /// bag.clear(); 400 | /// assert!(bag.is_empty()); 401 | /// ``` 402 | #[inline] 403 | pub fn clear(&mut self) { 404 | self.count = 0; 405 | self.items.clear(); 406 | } 407 | } 408 | 409 | impl HashBag 410 | where 411 | T: Eq + Hash, 412 | S: BuildHasher, 413 | { 414 | /// Creates a new empty hash bag which will use the given hasher to hash 415 | /// keys. 416 | /// 417 | /// The hash bag is also created with the default initial capacity. 418 | /// 419 | /// Warning: `hasher` is normally randomly generated, and 420 | /// is designed to allow `HashBag`s to be resistant to attacks that 421 | /// cause many collisions and very poor performance. Setting it 422 | /// manually using this function can expose a DoS attack vector. 423 | /// 424 | /// # Examples 425 | /// 426 | /// ``` 427 | /// use hashbag::HashBag; 428 | /// use std::collections::hash_map::RandomState; 429 | /// 430 | /// let s = RandomState::new(); 431 | /// let mut bag = HashBag::with_hasher(s); 432 | /// bag.insert(2); 433 | /// ``` 434 | #[inline] 435 | pub fn with_hasher(hash_builder: S) -> HashBag { 436 | HashBag { 437 | items: HashMap::with_hasher(hash_builder), 438 | count: 0, 439 | } 440 | } 441 | 442 | /// Creates an empty `HashBag` with the specified capacity, using 443 | /// `hasher` to hash the keys. 444 | /// 445 | /// The hash bag will be able to hold at least `capacity` distinct values 446 | /// without reallocating. If `capacity` is 0, the hash bag will not allocate. 447 | /// 448 | /// Warning: `hasher` is normally randomly generated, and 449 | /// is designed to allow `HashBag`s to be resistant to attacks that 450 | /// cause many collisions and very poor performance. Setting it 451 | /// manually using this function can expose a DoS attack vector. 452 | /// 453 | /// # Examples 454 | /// 455 | /// ``` 456 | /// use hashbag::HashBag; 457 | /// use std::collections::hash_map::RandomState; 458 | /// 459 | /// let s = RandomState::new(); 460 | /// let mut bag = HashBag::with_capacity_and_hasher(10, s); 461 | /// bag.insert(1); 462 | /// ``` 463 | #[inline] 464 | pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> HashBag { 465 | HashBag { 466 | items: HashMap::with_capacity_and_hasher(capacity, hash_builder), 467 | count: 0, 468 | } 469 | } 470 | 471 | /// Returns a reference to the bag's [`BuildHasher`]. 472 | /// 473 | /// # Examples 474 | /// 475 | /// ``` 476 | /// use hashbag::HashBag; 477 | /// use std::collections::hash_map::RandomState; 478 | /// 479 | /// let hasher = RandomState::new(); 480 | /// let bag: HashBag = HashBag::with_hasher(hasher); 481 | /// let hasher: &RandomState = bag.hasher(); 482 | /// ``` 483 | #[inline] 484 | pub fn hasher(&self) -> &S { 485 | self.items.hasher() 486 | } 487 | 488 | /// Reserves capacity for at least `additional` more distinct values 489 | /// to be inserted in the `HashBag`. The collection may reserve more 490 | /// space to avoid frequent reallocations. 491 | /// 492 | /// # Panics 493 | /// 494 | /// Panics if the new allocation size overflows `usize`. 495 | /// 496 | /// # Examples 497 | /// 498 | /// ``` 499 | /// use hashbag::HashBag; 500 | /// let mut bag: HashBag = HashBag::new(); 501 | /// bag.reserve(10); 502 | /// assert!(bag.capacity() >= 10); 503 | /// ``` 504 | #[inline] 505 | pub fn reserve(&mut self, additional: usize) { 506 | self.items.reserve(additional) 507 | } 508 | 509 | /// Shrinks the capacity of the ba as much as possible. It will drop 510 | /// down as much as possible while maintaining the internal rules 511 | /// and possibly leaving some space in accordance with the resize policy. 512 | /// 513 | /// # Examples 514 | /// 515 | /// ``` 516 | /// use hashbag::HashBag; 517 | /// 518 | /// let mut bag = HashBag::with_capacity(100); 519 | /// bag.insert(1); 520 | /// bag.insert(2); 521 | /// assert!(bag.capacity() >= 100); 522 | /// bag.shrink_to_fit(); 523 | /// assert!(bag.capacity() >= 2); 524 | /// ``` 525 | #[inline] 526 | pub fn shrink_to_fit(&mut self) { 527 | self.items.shrink_to_fit() 528 | } 529 | 530 | /// Returns the number of instances of `value` in the bag. 531 | /// 532 | /// The value may be any borrowed form of the bag's value type, but 533 | /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for 534 | /// the value type. 535 | /// 536 | /// # Examples 537 | /// 538 | /// ``` 539 | /// use hashbag::HashBag; 540 | /// 541 | /// let bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); 542 | /// assert_eq!(bag.contains(&1), 1); 543 | /// assert_eq!(bag.contains(&3), 2); 544 | /// assert_eq!(bag.contains(&4), 0); 545 | /// ``` 546 | #[inline] 547 | pub fn contains(&self, value: &Q) -> usize 548 | where 549 | T: Borrow, 550 | Q: Hash + Eq, 551 | { 552 | self.items.get(value).cloned().unwrap_or(0) 553 | } 554 | 555 | /// Returns a reference to the value in the bag, if any, that is equal to the given value, 556 | /// along with its number of occurrences. 557 | /// 558 | /// The value may be any borrowed form of the bag's value type, but 559 | /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for 560 | /// the value type. 561 | /// 562 | /// # Examples 563 | /// 564 | /// ``` 565 | /// use hashbag::HashBag; 566 | /// 567 | /// let bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); 568 | /// assert_eq!(bag.get(&2), Some((&2, 1))); 569 | /// assert_eq!(bag.get(&3), Some((&3, 2))); 570 | /// assert_eq!(bag.get(&4), None); 571 | /// ``` 572 | #[inline] 573 | pub fn get(&self, value: &Q) -> Option<(&T, usize)> 574 | where 575 | T: Borrow, 576 | Q: Hash + Eq, 577 | { 578 | self.items 579 | .get_key_value(value) 580 | .map(|(t, count)| (t, *count)) 581 | } 582 | 583 | /// Gets a given value's corresponding entry in the bag for in-place manipulation. 584 | /// 585 | /// # Examples 586 | /// 587 | /// ``` 588 | /// use hashbag::HashBag; 589 | /// 590 | /// let mut bag: HashBag = ['a'].iter().cloned().collect(); 591 | /// let entry = bag.entry('a').and_modify(|n| *n += 1).or_insert(); 592 | /// assert_eq!(bag.get(&'a'), Some((&'a', 2))); 593 | /// let entry = bag.entry('b').and_modify(|n| *n += 1).or_insert(); 594 | /// assert_eq!(bag.get(&'b'), Some((&'b', 1))); 595 | /// let entry = bag.entry('c').and_modify(|n| *n += 1).or_insert_many(7); 596 | /// assert_eq!(bag.get(&'c'), Some((&'c', 7))); 597 | /// ``` 598 | #[inline] 599 | pub fn entry(&mut self, value: T) -> Entry<'_, T, S> { 600 | Entry(( 601 | ForiegnEntry::new(self.items.entry(value)), 602 | &mut self.count, 603 | PhantomData, 604 | )) 605 | } 606 | 607 | /// Adds a value to the bag. 608 | /// 609 | /// The number of occurrences of the value previously in the bag is returned. 610 | /// 611 | /// # Examples 612 | /// 613 | /// ``` 614 | /// use hashbag::HashBag; 615 | /// 616 | /// let mut bag = HashBag::new(); 617 | /// 618 | /// assert_eq!(bag.insert(2), 0); 619 | /// assert_eq!(bag.insert(2), 1); 620 | /// assert_eq!(bag.insert(2), 2); 621 | /// assert_eq!(bag.set_len(), 1); 622 | /// assert_eq!(bag.len(), 3); 623 | /// ``` 624 | #[inline] 625 | pub fn insert(&mut self, value: T) -> usize { 626 | self.insert_many(value, 1) 627 | } 628 | 629 | /// Adds multiple occurrences of a value to the bag. 630 | /// 631 | /// The number of occurrences of the value previously in the bag is returned. 632 | /// 633 | /// # Examples 634 | /// 635 | /// ``` 636 | /// use hashbag::HashBag; 637 | /// 638 | /// let mut bag = HashBag::new(); 639 | /// 640 | /// assert_eq!(bag.insert_many(2, 1), 0); 641 | /// assert_eq!(bag.insert_many(2, 2), 1); 642 | /// assert_eq!(bag.insert_many(2, 4), 3); 643 | /// assert_eq!(bag.set_len(), 1); 644 | /// assert_eq!(bag.len(), 7); 645 | /// ``` 646 | #[inline] 647 | pub fn insert_many(&mut self, value: T, count: usize) -> usize { 648 | if count == 0 { 649 | return self.contains(&value); 650 | } 651 | self.count += count; 652 | let n = self.items.entry(value).or_insert(0); 653 | let was_there = *n; 654 | *n += count; 655 | was_there 656 | } 657 | 658 | /// Adds a value to the bag, replacing all existing occurrences, if any, that equal the given 659 | /// one. 660 | /// 661 | /// The number of occurrences of the value previously in the bag is returned. 662 | /// 663 | /// # Examples 664 | /// 665 | /// ``` 666 | /// use hashbag::HashBag; 667 | /// 668 | /// let mut bag = HashBag::new(); 669 | /// bag.insert(Vec::::new()); 670 | /// bag.insert(Vec::::new()); 671 | /// assert_eq!(bag.contains(&[][..]), 2); 672 | /// assert_eq!(bag.get(&[][..]).unwrap().0.capacity(), 0); 673 | /// 674 | /// bag.replace(Vec::with_capacity(10)); 675 | /// assert_eq!(bag.contains(&[][..]), 1); 676 | /// assert_eq!(bag.get(&[][..]).unwrap().0.capacity(), 10); 677 | /// ``` 678 | #[inline] 679 | pub fn replace(&mut self, value: T) -> usize { 680 | let n = self.items.remove(&value).unwrap_or(0); 681 | self.count -= n; 682 | self.items.insert(value, 1); 683 | self.count += 1; 684 | n 685 | } 686 | 687 | /// Removes a value from the bag. 688 | /// 689 | /// The number of occurrences of the value previously in the bag is returned. 690 | /// 691 | /// The value may be any borrowed form of the bag's value type, but 692 | /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for 693 | /// the value type. 694 | /// 695 | /// # Examples 696 | /// 697 | /// ``` 698 | /// use hashbag::HashBag; 699 | /// 700 | /// let mut bag = HashBag::new(); 701 | /// 702 | /// bag.insert_many('x', 2); 703 | /// assert_eq!(bag.contains(&'x'), 2); 704 | /// assert_eq!(bag.remove(&'x'), 2); 705 | /// assert_eq!(bag.contains(&'x'), 1); 706 | /// assert_eq!(bag.remove(&'x'), 1); 707 | /// assert_eq!(bag.contains(&'x'), 0); 708 | /// assert_eq!(bag.remove(&'x'), 0); 709 | /// ``` 710 | #[inline] 711 | pub fn remove(&mut self, value: &Q) -> usize 712 | where 713 | T: Borrow, 714 | Q: Hash + Eq, 715 | { 716 | match self.items.get_mut(value) { 717 | None => 0, 718 | #[cfg(debug_assertions)] 719 | Some(n) if *n == 0 => unreachable!(), 720 | Some(n) if *n == 1 => { 721 | self.count -= 1; 722 | self.items.remove(value); 723 | 1 724 | } 725 | Some(n) => { 726 | self.count -= 1; 727 | *n -= 1; 728 | *n + 1 729 | } 730 | } 731 | } 732 | 733 | /// Removes multiple of a value from the bag. If `quantity` is greater than the number of 734 | /// occurences, zero occurances will remain. 735 | /// 736 | /// The number of occurrences of the value currently in the bag is returned. 737 | /// 738 | /// The value may be any borrowed form of the bag's value type, but 739 | /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for 740 | /// the value type. 741 | /// 742 | /// # Examples 743 | /// 744 | /// ``` 745 | /// use hashbag::HashBag; 746 | /// 747 | /// let mut bag = HashBag::new(); 748 | /// 749 | /// bag.insert_many('x', 10); 750 | /// assert_eq!(bag.contains(&'x'), 10); 751 | /// assert_eq!(bag.remove_up_to(&'x', 3), 7); 752 | /// assert_eq!(bag.contains(&'x'), 7); 753 | /// assert_eq!(bag.remove_up_to(&'x', 10), 0); 754 | /// ``` 755 | #[inline] 756 | pub fn remove_up_to(&mut self, value: &Q, quantity: usize) -> usize 757 | where 758 | T: Borrow, 759 | Q: Hash + Eq, 760 | { 761 | match self.items.get_mut(value) { 762 | None => 0, 763 | Some(&mut n) if n <= quantity => { 764 | self.count -= n; 765 | self.items.remove(value); 766 | 0 767 | } 768 | Some(n) => { 769 | self.count -= quantity; 770 | *n -= quantity; 771 | *n 772 | } 773 | } 774 | } 775 | 776 | /// Returns an iterator over all of the elements that are in `self` or `other`. 777 | /// The iterator also yields the respective counts in `self` and `other` in that order. 778 | /// Elements that are in `self` are yielded before any elements that are exclusively in `other`. 779 | /// Each distinct element is yielded only once. 780 | /// 781 | /// # Examples 782 | /// ``` 783 | /// use hashbag::HashBag; 784 | /// use std::collections::HashSet; 785 | /// use std::iter::FromIterator; 786 | /// 787 | /// let a: HashBag<_> = "hash".chars().collect(); 788 | /// let b: HashBag<_> = "math".chars().collect(); 789 | /// let expected: HashSet<_> = HashSet::from_iter([(&'h', 2, 1), (&'a', 1, 1), (&'s', 1, 0), (&'m', 0, 1), (&'t', 0, 1)]); 790 | /// let actual: HashSet<_> = a.outer_join(&b).collect(); 791 | /// assert_eq!(expected, actual); 792 | /// ``` 793 | pub fn outer_join<'a, OtherS>( 794 | &'a self, 795 | other: &'a HashBag, 796 | ) -> impl Iterator 797 | where 798 | OtherS: BuildHasher, 799 | { 800 | self.items 801 | .iter() 802 | .map(move |(x, &self_count)| (x, self_count, other.contains(x))) 803 | .chain(other.items.iter().filter_map(move |(x, &other_count)| { 804 | let self_count = self.contains(x); 805 | if self_count == 0 { 806 | Some((x, self_count, other_count)) 807 | } else { 808 | None 809 | } 810 | })) 811 | } 812 | 813 | /// Returns an iterator over all the elements that are in `self` with a 814 | /// higher occurrence count than in `other`. The count in the returned 815 | /// iterator represents how many more of a given element are in `self` than 816 | /// `other`. 817 | /// 818 | /// # Examples 819 | /// 820 | /// ``` 821 | /// use hashbag::HashBag; 822 | /// use std::collections::HashSet; 823 | /// use std::iter::FromIterator; 824 | /// 825 | /// let a: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); 826 | /// let b: HashBag<_> = [2, 3].iter().cloned().collect(); 827 | /// let expected: HashSet<_> = HashSet::from_iter([(&1, 1), (&3, 1)]); 828 | /// let actual: HashSet<_> = a.difference(&b).collect(); 829 | /// assert_eq!(expected, actual); 830 | /// ``` 831 | pub fn difference<'a, OtherS>( 832 | &'a self, 833 | other: &'a HashBag, 834 | ) -> impl Iterator 835 | where 836 | OtherS: BuildHasher, 837 | { 838 | self.outer_join(other) 839 | .take_while(|(_, self_count, _)| self_count > &0) 840 | .filter(|(_x, self_count, other_count)| self_count > other_count) 841 | .map(|(x, self_count, other_count)| (x, self_count - other_count)) 842 | } 843 | 844 | /// Returns an iterator over all the elements that are in `self` or `other`. 845 | /// The iterator also yields the difference in counts between `self` and `other`. 846 | /// 847 | /// Unlike 'difference' which only yields elements that have a higher count in `self` than in `other`, 848 | /// this iterator yields all elements that are in either of the `HashBag`s. Elements that have a higher 849 | /// count in `other` than in self (including elements that are not in `self`) will have a negative count. 850 | /// 851 | /// If the difference can be represented as an `isize`, then it will be. Otherwise, the difference will be 852 | /// clamped to `isize::MIN`/`isize::MAX`, thus keeping the sign of the difference, and as much of the 853 | /// magnitude as possible. 854 | /// 855 | /// # Examples 856 | /// 857 | /// ``` 858 | /// use hashbag::HashBag; 859 | /// use std::collections::HashSet; 860 | /// use std::iter::FromIterator; 861 | /// 862 | /// let a: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); 863 | /// let b: HashBag<_> = [2, 3, 4, 4].iter().cloned().collect(); 864 | /// let expected: HashSet<_> = HashSet::from_iter([(&1, 1), (&2, 0), (&3, 1), (&4, -2)]); 865 | /// let actual: HashSet<_> = a.signed_difference(&b).collect(); 866 | /// assert_eq!(expected, actual); 867 | /// ``` 868 | pub fn signed_difference<'a, OtherS>( 869 | &'a self, 870 | other: &'a HashBag, 871 | ) -> impl Iterator 872 | where 873 | OtherS: BuildHasher, 874 | { 875 | self.outer_join(other).map(|(x, self_count, other_count)| { 876 | let diff = if self_count >= other_count { 877 | isize::try_from(self_count - other_count).unwrap_or(std::isize::MAX) 878 | } else { 879 | isize::try_from(other_count - self_count) 880 | .map(|x| -x) 881 | .unwrap_or(std::isize::MIN) 882 | }; 883 | (x, diff) 884 | }) 885 | } 886 | 887 | /// Returns an iterator over all of the elements that are in `self` but not in `other`. 888 | /// 889 | /// # Examples 890 | /// ``` 891 | /// use hashbag::HashBag; 892 | /// use std::collections::HashSet; 893 | /// use std::iter::FromIterator; 894 | /// 895 | /// let a: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); 896 | /// let b: HashBag<_> = [2, 3].iter().cloned().collect(); 897 | /// let expected: HashSet<_> = HashSet::from_iter([(&1, 1)]); 898 | /// let actual: HashSet<_> = a.not_in(&b).collect(); 899 | /// assert_eq!(expected, actual); 900 | /// ``` 901 | pub fn not_in<'a, OtherS>( 902 | &'a self, 903 | other: &'a HashBag, 904 | ) -> impl Iterator 905 | where 906 | OtherS: BuildHasher, 907 | { 908 | self.outer_join(other) 909 | .take_while(|(_, self_count, _)| self_count > &0) 910 | .filter_map(|(k, self_count, other_count)| { 911 | if other_count == 0 { 912 | Some((k, self_count)) 913 | } else { 914 | None 915 | } 916 | }) 917 | } 918 | 919 | /// Removes a value that is equal to the given one, and returns it if it was the last. 920 | /// 921 | /// If the matching value is not the last, a reference to the remainder is given, along with 922 | /// the number of occurrences prior to the removal. 923 | /// 924 | /// The value may be any borrowed form of the bag's value type, but 925 | /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for 926 | /// the value type. 927 | /// 928 | /// # Examples 929 | /// 930 | /// ``` 931 | /// use hashbag::HashBag; 932 | /// 933 | /// let mut bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); 934 | /// assert_eq!(bag.try_take(&2), Ok(2)); 935 | /// assert_eq!(bag.try_take(&3), Err(Some((&3, 2)))); 936 | /// assert_eq!(bag.try_take(&3), Ok(3)); 937 | /// assert_eq!(bag.try_take(&4), Err(None)); 938 | /// ``` 939 | #[inline] 940 | pub fn try_take(&mut self, value: &Q) -> Result> 941 | where 942 | T: Borrow, 943 | Q: Hash + Eq, 944 | { 945 | // TODO: it should be possible to make this more efficient 946 | match self.items.remove_entry(value) { 947 | Some((t, 1)) => { 948 | self.count -= 1; 949 | Ok(t) 950 | } 951 | Some((t, n)) => { 952 | self.count -= 1; 953 | self.items.insert(t, n - 1); 954 | Err(Some( 955 | self.items 956 | .get_key_value(value) 957 | .map(|(t, n)| (t, *n + 1)) 958 | .unwrap(), 959 | )) 960 | } 961 | None => Err(None), 962 | } 963 | } 964 | 965 | /// Removes and returns all occurrences of the value, if any, that is equal to the given one. 966 | /// 967 | /// The value may be any borrowed form of the bag's value type, but 968 | /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for 969 | /// the value type. 970 | /// 971 | /// # Examples 972 | /// 973 | /// ``` 974 | /// use hashbag::HashBag; 975 | /// 976 | /// let mut bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); 977 | /// assert_eq!(bag.take_all(&2), Some((2, 1))); 978 | /// assert_eq!(bag.take_all(&3), Some((3, 2))); 979 | /// assert_eq!(bag.take_all(&2), None); 980 | /// assert_eq!(bag.take_all(&3), None); 981 | /// ``` 982 | #[inline] 983 | pub fn take_all(&mut self, value: &Q) -> Option<(T, usize)> 984 | where 985 | T: Borrow, 986 | Q: Hash + Eq, 987 | { 988 | let (t, n) = self.items.remove_entry(value)?; 989 | self.count -= n; 990 | Some((t, n)) 991 | } 992 | 993 | /// Retains only the values specified by the predicate. 994 | /// 995 | /// In other words, for each value `v` retain only `f(&v)` occurrences. 996 | /// 997 | /// # Examples 998 | /// 999 | /// ``` 1000 | /// use hashbag::HashBag; 1001 | /// 1002 | /// let xs = [0,0,0,0,0,1,1,1,1,2,2,2,3,3,4]; 1003 | /// let mut bag: HashBag = xs.iter().cloned().collect(); 1004 | /// bag.retain(|&k, _| k as usize); 1005 | /// assert_eq!(bag.set_len(), 4); // >= 1 of all but value 0 1006 | /// assert_eq!(bag.len(), 6); 1007 | /// assert_eq!(bag.contains(&0), 0); 1008 | /// assert_eq!(bag.contains(&1), 1); 1009 | /// assert_eq!(bag.contains(&2), 2); 1010 | /// assert_eq!(bag.contains(&3), 2); 1011 | /// assert_eq!(bag.contains(&4), 1); 1012 | /// ``` 1013 | pub fn retain(&mut self, mut f: F) 1014 | where 1015 | F: FnMut(&T, usize) -> usize, 1016 | { 1017 | let count = &mut self.count; 1018 | self.items.retain(|t, n| { 1019 | let keep = std::cmp::min(*n, f(t, *n)); 1020 | *count -= *n - keep; 1021 | if keep == 0 { 1022 | false 1023 | } else { 1024 | *n = keep; 1025 | true 1026 | } 1027 | }); 1028 | } 1029 | } 1030 | 1031 | // ======== standard traits 1032 | 1033 | use std::fmt; 1034 | use std::marker::PhantomData; 1035 | 1036 | impl fmt::Debug for HashBag 1037 | where 1038 | T: fmt::Debug, 1039 | { 1040 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 1041 | fmt.debug_set().entries(self.iter()).finish() 1042 | } 1043 | } 1044 | 1045 | impl Default for HashBag 1046 | where 1047 | T: Eq + Hash, 1048 | S: BuildHasher + Default, 1049 | { 1050 | fn default() -> Self { 1051 | Self::with_hasher(S::default()) 1052 | } 1053 | } 1054 | 1055 | impl PartialEq> for HashBag 1056 | where 1057 | T: Eq + Hash, 1058 | S: BuildHasher, 1059 | { 1060 | fn eq(&self, other: &Self) -> bool { 1061 | self.count == other.count && self.items == other.items 1062 | } 1063 | } 1064 | 1065 | impl Eq for HashBag 1066 | where 1067 | T: Eq + Hash, 1068 | S: BuildHasher, 1069 | { 1070 | } 1071 | 1072 | impl<'a, T, S> Extend<&'a T> for HashBag 1073 | where 1074 | T: 'a + Eq + Hash + Clone, 1075 | S: BuildHasher, 1076 | { 1077 | fn extend>(&mut self, iter: I) { 1078 | for e in iter { 1079 | self.insert(e.clone()); 1080 | } 1081 | } 1082 | } 1083 | 1084 | impl<'a, T, S> Extend<(&'a T, usize)> for HashBag 1085 | where 1086 | T: 'a + Eq + Hash + Clone, 1087 | S: BuildHasher, 1088 | { 1089 | fn extend>(&mut self, iter: I) { 1090 | for (e, n) in iter { 1091 | self.count += n; 1092 | *self.items.entry(e.clone()).or_insert(0) += n; 1093 | } 1094 | } 1095 | } 1096 | 1097 | impl Extend for HashBag 1098 | where 1099 | T: Eq + Hash, 1100 | S: BuildHasher, 1101 | { 1102 | fn extend>(&mut self, iter: I) { 1103 | for e in iter { 1104 | self.insert(e); 1105 | } 1106 | } 1107 | } 1108 | 1109 | impl Extend<(T, usize)> for HashBag 1110 | where 1111 | T: Eq + Hash, 1112 | S: BuildHasher, 1113 | { 1114 | fn extend>(&mut self, iter: I) { 1115 | for (e, n) in iter { 1116 | self.count += n; 1117 | if n != 0 { 1118 | *self.items.entry(e).or_insert(0) += n; 1119 | } 1120 | } 1121 | } 1122 | } 1123 | 1124 | impl std::iter::FromIterator for HashBag 1125 | where 1126 | T: Eq + Hash, 1127 | S: BuildHasher + Default, 1128 | { 1129 | fn from_iter>(iter: I) -> Self { 1130 | let mut bag = Self::default(); 1131 | bag.extend(iter); 1132 | bag 1133 | } 1134 | } 1135 | 1136 | impl std::iter::FromIterator<(T, usize)> for HashBag 1137 | where 1138 | T: Eq + Hash, 1139 | S: BuildHasher + Default, 1140 | { 1141 | fn from_iter>(iter: I) -> Self { 1142 | let mut bag = Self::default(); 1143 | bag.extend(iter); 1144 | bag 1145 | } 1146 | } 1147 | 1148 | impl<'a, T, S> IntoIterator for &'a HashBag { 1149 | type Item = &'a T; 1150 | type IntoIter = Iter<'a, T>; 1151 | fn into_iter(self) -> Iter<'a, T> { 1152 | self.iter() 1153 | } 1154 | } 1155 | 1156 | impl IntoIterator for HashBag { 1157 | type Item = (T, usize); 1158 | type IntoIter = IntoIter; 1159 | fn into_iter(self) -> IntoIter { 1160 | IntoIter(self.items.into_iter()) 1161 | } 1162 | } 1163 | 1164 | // ======== entry type 1165 | #[cfg(feature = "amortize")] 1166 | pub(crate) mod entry { 1167 | use griddle::hash_map::Entry; 1168 | 1169 | #[derive(Debug)] 1170 | pub(crate) struct ForiegnEntry<'a, T, S> { 1171 | pub(crate) entry: Entry<'a, T, usize, S>, 1172 | } 1173 | 1174 | impl<'a, T, S> ForiegnEntry<'a, T, S> { 1175 | pub(crate) fn new(entry: Entry<'a, T, usize, S>) -> Self { 1176 | Self { entry } 1177 | } 1178 | 1179 | pub(crate) fn get_mut(&mut self) -> Option<&mut usize> { 1180 | match &mut self.entry { 1181 | Entry::Occupied(entry) => Some(entry.get_mut()), 1182 | Entry::Vacant(_) => None, 1183 | } 1184 | } 1185 | } 1186 | } 1187 | #[cfg(not(feature = "amortize"))] 1188 | pub(crate) mod entry { 1189 | use std::{collections::hash_map::Entry, marker::PhantomData}; 1190 | 1191 | #[derive(Debug)] 1192 | pub(crate) struct ForiegnEntry<'a, T, S> { 1193 | pub(crate) entry: Entry<'a, T, usize>, 1194 | data: PhantomData, 1195 | } 1196 | 1197 | impl<'a, T, S> ForiegnEntry<'a, T, S> { 1198 | pub(crate) fn new(entry: Entry<'a, T, usize>) -> Self { 1199 | Self { 1200 | entry, 1201 | data: PhantomData, 1202 | } 1203 | } 1204 | 1205 | pub(crate) fn get_mut(&mut self) -> Option<&mut usize> { 1206 | match &mut self.entry { 1207 | Entry::Occupied(entry) => Some(entry.get_mut()), 1208 | Entry::Vacant(_) => None, 1209 | } 1210 | } 1211 | } 1212 | } 1213 | 1214 | use entry::ForiegnEntry; 1215 | 1216 | type EntryInner<'a, T, S> = (ForiegnEntry<'a, T, S>, &'a mut usize, PhantomData); 1217 | 1218 | #[derive(Debug)] 1219 | /// A view into a single entry in the bag, which may either be vacant or occupied. 1220 | /// This `enum` is constructed from the [`entry`](HashBag::entry) method on [`HashBag`] 1221 | pub struct Entry<'a, T, S>(EntryInner<'a, T, S>); 1222 | 1223 | impl<'a, T, S> Entry<'a, T, S> 1224 | where 1225 | T: Hash + Eq, 1226 | S: BuildHasher, 1227 | { 1228 | /// Provides in-place mutable access to an occupied entry before potential inserts into the 1229 | /// map. 1230 | pub fn and_modify(mut self, f: F) -> Self 1231 | where 1232 | F: FnOnce(&mut usize), 1233 | { 1234 | if let Some(n) = self.0 .0.get_mut() { 1235 | let init = *n; 1236 | f(n); 1237 | *self.0 .1 += *n; 1238 | *self.0 .1 -= init; 1239 | } 1240 | Self((self.0 .0, self.0 .1, PhantomData)) 1241 | } 1242 | 1243 | /// Returns a reference to the entry's value. 1244 | pub fn value(&self) -> &T { 1245 | self.0 .0.entry.key() 1246 | } 1247 | 1248 | /// Ensures there is at least one instance of the value before returning a mutable reference 1249 | /// to the value's count 1250 | pub fn or_insert(mut self) -> usize { 1251 | if self.0 .0.get_mut().is_none() { 1252 | *self.0 .1 += 1; 1253 | } 1254 | *self.0 .0.entry.or_insert(1) 1255 | } 1256 | 1257 | /// Ensures there is at least `quantity` instances of the value before returning a mutable reference 1258 | /// to the value's count 1259 | pub fn or_insert_many(mut self, quantity: usize) -> usize { 1260 | if self.0 .0.get_mut().is_none() { 1261 | *self.0 .1 += quantity; 1262 | } 1263 | *self.0 .0.entry.or_insert(quantity) 1264 | } 1265 | } 1266 | 1267 | // ======== iterators 1268 | 1269 | #[cfg(feature = "amortize")] 1270 | type IterInner<'a, T> = griddle::hash_map::Iter<'a, T, usize>; 1271 | #[cfg(not(feature = "amortize"))] 1272 | type IterInner<'a, T> = std::collections::hash_map::Iter<'a, T, usize>; 1273 | 1274 | /// An iterator over the items of a `HashBag`. 1275 | /// 1276 | /// Each value is repeated as many times as it occurs in the bag. 1277 | /// 1278 | /// This `struct` is created by [`HashBag::iter`]. 1279 | /// See its documentation for more. 1280 | pub struct Iter<'a, T> { 1281 | iter: IterInner<'a, T>, 1282 | repeat: Option<(&'a T, usize)>, 1283 | left: usize, 1284 | } 1285 | 1286 | impl<'a, T> std::iter::FusedIterator for Iter<'a, T> where IterInner<'a, T>: std::iter::FusedIterator 1287 | {} 1288 | 1289 | impl<'a, T> ExactSizeIterator for Iter<'a, T> where IterInner<'a, T>: ExactSizeIterator {} 1290 | 1291 | impl<'a, T> Clone for Iter<'a, T> { 1292 | fn clone(&self) -> Self { 1293 | Iter { 1294 | iter: self.iter.clone(), 1295 | repeat: self.repeat, 1296 | left: self.left, 1297 | } 1298 | } 1299 | } 1300 | 1301 | impl fmt::Debug for Iter<'_, T> { 1302 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 1303 | fmt.debug_set().entries(self.clone()).finish() 1304 | } 1305 | } 1306 | 1307 | impl<'a, T> Iter<'a, T> { 1308 | fn new(it: IterInner<'a, T>, n: usize) -> Self { 1309 | Self { 1310 | iter: it, 1311 | repeat: None, 1312 | left: n, 1313 | } 1314 | } 1315 | } 1316 | 1317 | impl<'a, T> Iterator for Iter<'a, T> { 1318 | type Item = &'a T; 1319 | fn next(&mut self) -> Option { 1320 | if let Some((t, ref mut n)) = self.repeat { 1321 | if *n == 0 { 1322 | self.repeat = None; 1323 | } else { 1324 | *n -= 1; 1325 | self.left -= 1; 1326 | return Some(t); 1327 | } 1328 | } 1329 | 1330 | let (next, n) = self.iter.next()?; 1331 | if *n > 1 { 1332 | self.repeat = Some((next, *n - 1)); 1333 | } 1334 | self.left -= 1; 1335 | Some(next) 1336 | } 1337 | 1338 | fn size_hint(&self) -> (usize, Option) { 1339 | (self.left, Some(self.left)) 1340 | } 1341 | } 1342 | 1343 | /// An iterator over the distinct items of a `HashBag` and their occurrence counts. 1344 | /// 1345 | /// This `struct` is created by [`HashBag::set_iter`]. 1346 | /// See its documentation for more. 1347 | pub struct SetIter<'a, T>(IterInner<'a, T>); 1348 | 1349 | impl<'a, T> std::iter::FusedIterator for SetIter<'a, T> where 1350 | IterInner<'a, T>: std::iter::FusedIterator 1351 | { 1352 | } 1353 | 1354 | impl<'a, T> ExactSizeIterator for SetIter<'a, T> where IterInner<'a, T>: ExactSizeIterator {} 1355 | 1356 | impl<'a, T> Clone for SetIter<'a, T> { 1357 | fn clone(&self) -> Self { 1358 | SetIter(self.0.clone()) 1359 | } 1360 | } 1361 | 1362 | impl fmt::Debug for SetIter<'_, T> { 1363 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 1364 | fmt.debug_set().entries(self.clone()).finish() 1365 | } 1366 | } 1367 | 1368 | impl<'a, T> Iterator for SetIter<'a, T> { 1369 | type Item = (&'a T, usize); 1370 | fn next(&mut self) -> Option { 1371 | self.0.next().map(|(t, n)| (t, *n)) 1372 | } 1373 | 1374 | fn size_hint(&self) -> (usize, Option) { 1375 | self.0.size_hint() 1376 | } 1377 | } 1378 | 1379 | #[cfg(feature = "amortize")] 1380 | type IntoIterInner = griddle::hash_map::IntoIter; 1381 | #[cfg(not(feature = "amortize"))] 1382 | type IntoIterInner = std::collections::hash_map::IntoIter; 1383 | 1384 | /// An owning iterator over the distinct items of a `HashBag` and their occurrence counts. 1385 | /// 1386 | /// This `struct` is created by using the implementation of [`IntoIterator`] for [`HashBag`]. 1387 | pub struct IntoIter(IntoIterInner); 1388 | 1389 | impl std::iter::FusedIterator for IntoIter where IntoIterInner: std::iter::FusedIterator {} 1390 | 1391 | impl ExactSizeIterator for IntoIter where IntoIterInner: ExactSizeIterator {} 1392 | 1393 | impl fmt::Debug for IntoIter { 1394 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 1395 | self.0.fmt(fmt) 1396 | } 1397 | } 1398 | 1399 | impl Iterator for IntoIter { 1400 | type Item = (T, usize); 1401 | fn next(&mut self) -> Option { 1402 | self.0.next() 1403 | } 1404 | 1405 | fn size_hint(&self) -> (usize, Option) { 1406 | self.0.size_hint() 1407 | } 1408 | } 1409 | 1410 | #[cfg(feature = "amortize")] 1411 | type DrainInner<'a, T> = griddle::hash_map::Drain<'a, T, usize>; 1412 | #[cfg(not(feature = "amortize"))] 1413 | type DrainInner<'a, T> = std::collections::hash_map::Drain<'a, T, usize>; 1414 | 1415 | /// An draining iterator over the distinct items of a `HashBag` and their occurrence counts. 1416 | /// 1417 | /// This `struct` is created by [`HashBag::drain`]. 1418 | /// See its documentation for more. 1419 | pub struct Drain<'a, T>(DrainInner<'a, T>); 1420 | 1421 | impl<'a, T> std::iter::FusedIterator for Drain<'a, T> where 1422 | DrainInner<'a, T>: std::iter::FusedIterator 1423 | { 1424 | } 1425 | 1426 | impl<'a, T> ExactSizeIterator for Drain<'a, T> where DrainInner<'a, T>: ExactSizeIterator {} 1427 | 1428 | impl<'a, T: fmt::Debug> fmt::Debug for Drain<'a, T> { 1429 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 1430 | self.0.fmt(fmt) 1431 | } 1432 | } 1433 | 1434 | impl<'a, T> Iterator for Drain<'a, T> { 1435 | type Item = (T, usize); 1436 | fn next(&mut self) -> Option { 1437 | self.0.next() 1438 | } 1439 | 1440 | fn size_hint(&self) -> (usize, Option) { 1441 | self.0.size_hint() 1442 | } 1443 | } 1444 | 1445 | #[cfg(test)] 1446 | mod tests { 1447 | use std::collections::HashSet; 1448 | use std::iter::FromIterator; 1449 | 1450 | use super::*; 1451 | 1452 | #[test] 1453 | fn format_all_the_things() { 1454 | let mut vikings: HashBag<&'static str> = 1455 | ["Einar", "Olaf", "Harald"].iter().cloned().collect(); 1456 | println!("{:?}", vikings); 1457 | println!("{:?}", vikings.iter()); 1458 | println!("{:?}", vikings.set_iter()); 1459 | println!("{:?}", vikings.clone().into_iter()); 1460 | println!("{:?}", vikings.drain()); 1461 | } 1462 | 1463 | #[test] 1464 | fn sane_iterators() { 1465 | let mut vikings: HashBag<&'static str> = 1466 | ["Einar", "Einar", "Harald"].iter().cloned().collect(); 1467 | assert_eq!(vikings.iter().count(), 3); 1468 | assert_eq!(vikings.iter().size_hint(), (3, Some(3))); 1469 | assert_eq!(vikings.iter().clone().count(), 3); 1470 | assert_eq!(vikings.set_iter().count(), 2); 1471 | assert_eq!(vikings.set_iter().clone().count(), 2); 1472 | assert_eq!(vikings.set_iter().size_hint(), (2, Some(2))); 1473 | let ii = vikings.clone().into_iter(); 1474 | assert_eq!(ii.size_hint(), (2, Some(2))); 1475 | assert_eq!(ii.count(), 2); 1476 | let di = vikings.drain(); 1477 | assert_eq!(di.size_hint(), (2, Some(2))); 1478 | assert_eq!(di.count(), 2); 1479 | } 1480 | 1481 | #[test] 1482 | fn test_difference_size_hint() { 1483 | let bag: HashBag<_> = [3, 2, 1].iter().cloned().collect(); 1484 | let empty_bag = HashBag::new(); 1485 | let mut difference = bag.difference(&empty_bag); 1486 | 1487 | // Since the difference has the same number of entries as the bag, we 1488 | // can predict how the size_hint() will behave, because the iteration 1489 | // order does not matter 1490 | assert_eq!(difference.size_hint(), (0, Some(3))); 1491 | difference.next().unwrap(); 1492 | assert_eq!(difference.size_hint(), (0, Some(2))); 1493 | difference.next().unwrap(); 1494 | assert_eq!(difference.size_hint(), (0, Some(1))); 1495 | difference.next().unwrap(); 1496 | assert_eq!(difference.size_hint(), (0, Some(0))); 1497 | assert_eq!(difference.next(), None); 1498 | assert_eq!(difference.size_hint(), (0, Some(0))); 1499 | } 1500 | 1501 | #[test] 1502 | fn test_difference_from_empty() { 1503 | do_test_difference(&[], &[], &[]); 1504 | do_test_difference(&[], &[1], &[]); 1505 | do_test_difference(&[], &[1, 1], &[]); 1506 | do_test_difference(&[], &[1, 1, 2], &[]); 1507 | } 1508 | 1509 | #[test] 1510 | fn test_difference_from_one() { 1511 | do_test_difference(&[1], &[], &[1]); 1512 | do_test_difference(&[1], &[1], &[]); 1513 | do_test_difference(&[1], &[1, 1], &[]); 1514 | do_test_difference(&[1], &[2], &[1]); 1515 | do_test_difference(&[1], &[1, 2], &[]); 1516 | do_test_difference(&[1], &[2, 2], &[1]); 1517 | } 1518 | 1519 | #[test] 1520 | fn test_difference_from_duplicate_ones() { 1521 | do_test_difference(&[1, 1], &[], &[1, 1]); 1522 | do_test_difference(&[1, 1], &[1], &[1]); 1523 | do_test_difference(&[1, 1], &[1, 1], &[]); 1524 | do_test_difference(&[1, 1], &[2], &[1, 1]); 1525 | do_test_difference(&[1, 1], &[1, 2], &[1]); 1526 | do_test_difference(&[1, 1], &[2, 2], &[1, 1]); 1527 | } 1528 | 1529 | #[test] 1530 | fn test_difference_from_one_one_two() { 1531 | do_test_difference(&[1, 1, 2], &[], &[1, 1, 2]); 1532 | do_test_difference(&[1, 1, 2], &[1], &[1, 2]); 1533 | do_test_difference(&[1, 1, 2], &[1, 1], &[2]); 1534 | do_test_difference(&[1, 1, 2], &[2], &[1, 1]); 1535 | do_test_difference(&[1, 1, 2], &[1, 2], &[1]); 1536 | do_test_difference(&[1, 1, 2], &[2, 2], &[1, 1]); 1537 | } 1538 | 1539 | #[test] 1540 | fn test_difference_from_larger_bags() { 1541 | do_test_difference(&[1, 2, 2, 3], &[3], &[1, 2, 2]); 1542 | do_test_difference(&[1, 2, 2, 3], &[4], &[1, 2, 2, 3]); 1543 | do_test_difference(&[2, 2, 2, 2], &[2, 2], &[2, 2]); 1544 | do_test_difference(&[2, 2, 2, 2], &[], &[2, 2, 2, 2]); 1545 | } 1546 | 1547 | fn do_test_difference( 1548 | self_entries: &[isize], 1549 | other_entries: &[isize], 1550 | expected_entries: &[isize], 1551 | ) { 1552 | let this = self_entries.iter().collect::>(); 1553 | let other = other_entries.iter().collect::>(); 1554 | let expected = expected_entries.iter().collect::>(); 1555 | let mut actual = HashBag::new(); 1556 | for (t, n) in this.difference(&other) { 1557 | actual.insert_many(*t, n); 1558 | } 1559 | 1560 | assert_eq!(actual, expected); 1561 | } 1562 | 1563 | #[test] 1564 | fn test_outer_join_order_with_disjoint_sets() { 1565 | do_test_outer_join_order(&[1, 2, 3], &[4, 5, 6]); 1566 | do_test_outer_join_order(&[1, 2, 2, 3], &[4, 4, 5, 6]); 1567 | } 1568 | 1569 | #[test] 1570 | fn test_outer_join_order_with_overlap() { 1571 | do_test_outer_join_order(&[1, 2, 3], &[2, 3, 4]); 1572 | do_test_outer_join_order(&[1, 1, 2, 3], &[2, 3, 3, 3, 4]); 1573 | } 1574 | 1575 | fn do_test_outer_join_order(this: &[usize], other: &[usize]) { 1576 | let this_hashbag: HashBag = this.iter().cloned().collect(); 1577 | let other_hashbag: HashBag = other.iter().cloned().collect(); 1578 | 1579 | // Assert that the first yielded key that's exclusive to other (i.e. self_count is 0) 1580 | // comes AFTER all of the keys in self 1581 | let min_other_exclusive_key_idx = this_hashbag 1582 | .outer_join(&other_hashbag) 1583 | .enumerate() 1584 | .find(|(_, (_, self_count, _))| self_count == &0) 1585 | .map(|(idx, _)| idx); 1586 | // If no such element exists that means all of the keys in other 1587 | // are in self so there's no thing to assert. 1588 | if let Some(idx) = min_other_exclusive_key_idx { 1589 | assert_eq!(idx, this_hashbag.set_len()); 1590 | } 1591 | } 1592 | 1593 | #[test] 1594 | fn test_outer_join_with_empty_self() { 1595 | do_test_outer_join(&[], &[1, 2, 2, 3], &[(&1, 0, 1), (&2, 0, 2), (&3, 0, 1)]); 1596 | } 1597 | 1598 | #[test] 1599 | fn test_outer_join_with_empty_other() { 1600 | do_test_outer_join(&[1, 2, 2, 3], &[], &[(&1, 1, 0), (&2, 2, 0), (&3, 1, 0)]); 1601 | } 1602 | 1603 | #[test] 1604 | fn test_outer_join_with_overlap() { 1605 | do_test_outer_join( 1606 | &[1, 2, 2, 3, 3], 1607 | &[3, 4, 5, 5], 1608 | &[(&1, 1, 0), (&2, 2, 0), (&3, 2, 1), (&4, 0, 1), (&5, 0, 2)], 1609 | ); 1610 | } 1611 | 1612 | fn do_test_outer_join( 1613 | this: &[usize], 1614 | other: &[usize], 1615 | expected_entries: &[(&usize, usize, usize)], 1616 | ) { 1617 | let this_hashbag: HashBag<_> = this.iter().cloned().collect(); 1618 | let other_hashbag: HashBag<_> = other.iter().cloned().collect(); 1619 | let expected: HashSet<_> = HashSet::from_iter(expected_entries.iter().cloned()); 1620 | let actual: HashSet<_> = this_hashbag.outer_join(&other_hashbag).collect(); 1621 | assert_eq!(expected, actual); 1622 | } 1623 | 1624 | #[test] 1625 | fn test_not_in_with_empty_self() { 1626 | do_test_not_in(&[], &[1, 2, 3, 3], &[]); 1627 | } 1628 | 1629 | #[test] 1630 | fn test_not_in_with_empty_other() { 1631 | do_test_not_in(&[1, 2, 3, 3], &[], &[1, 2, 3, 3]); 1632 | } 1633 | 1634 | #[test] 1635 | fn test_not_in_with_overlap() { 1636 | do_test_not_in(&[1, 2, 3, 3], &[2, 4], &[1, 3, 3]); 1637 | } 1638 | 1639 | fn do_test_not_in(this: &[usize], other: &[usize], expected_entries: &[usize]) { 1640 | let this_hashbag: HashBag<_> = this.iter().cloned().collect(); 1641 | let other_hashbag: HashBag<_> = other.iter().cloned().collect(); 1642 | let expected: HashBag<_> = expected_entries.iter().cloned().collect(); 1643 | let actual: HashBag<_> = 1644 | this_hashbag 1645 | .not_in(&other_hashbag) 1646 | .fold(HashBag::new(), |mut bag, (k, count)| { 1647 | bag.insert_many(*k, count); 1648 | bag 1649 | }); 1650 | assert_eq!(expected, actual); 1651 | } 1652 | 1653 | #[test] 1654 | fn test_signed_difference_with_empty_self() { 1655 | do_test_signed_difference(&[], &[1, 2, 2, 3], &[(&1, -1), (&2, -2), (&3, -1)]); 1656 | } 1657 | 1658 | #[test] 1659 | fn test_signed_difference_with_empty_other() { 1660 | do_test_signed_difference(&[1, 2, 2, 3], &[], &[(&1, 1), (&2, 2), (&3, 1)]); 1661 | } 1662 | 1663 | #[test] 1664 | fn test_signed_difference_with_overlap() { 1665 | do_test_signed_difference( 1666 | &[1, 2, 2, 3, 3], 1667 | &[3, 4, 5, 5], 1668 | &[(&1, 1), (&2, 2), (&3, 1), (&4, -1), (&5, -2)], 1669 | ); 1670 | } 1671 | 1672 | #[test] 1673 | fn test_signed_difference_with_both_large() { 1674 | let mut this_hashbag = HashBag::new(); 1675 | let mut other_hashbag = HashBag::new(); 1676 | 1677 | let large_count = std::isize::MAX as usize; 1678 | this_hashbag.insert_many(1, large_count + 1000); 1679 | other_hashbag.insert_many(1, large_count); 1680 | 1681 | let expected: HashSet<_> = HashSet::from_iter([(&1, 1000)].iter().cloned()); 1682 | let actual: HashSet<_> = this_hashbag.signed_difference(&other_hashbag).collect(); 1683 | assert_eq!(expected, actual); 1684 | 1685 | // and in reverse: 1686 | let expected: HashSet<_> = HashSet::from_iter([(&1, -1000)].iter().cloned()); 1687 | let actual: HashSet<_> = other_hashbag.signed_difference(&this_hashbag).collect(); 1688 | assert_eq!(expected, actual); 1689 | } 1690 | 1691 | #[test] 1692 | fn test_signed_difference_too_large_to_hold_clamp() { 1693 | let mut this_hashbag = HashBag::new(); 1694 | let empty_hashbag = HashBag::new(); 1695 | 1696 | let large_count = std::isize::MAX as usize; 1697 | this_hashbag.insert_many(1, large_count + 1000); 1698 | 1699 | let expected: HashSet<_> = HashSet::from_iter([(&1, std::isize::MAX)].iter().cloned()); 1700 | let actual: HashSet<_> = this_hashbag.signed_difference(&empty_hashbag).collect(); 1701 | assert_eq!(expected, actual); 1702 | 1703 | // and in reverse: 1704 | let expected: HashSet<_> = HashSet::from_iter([(&1, std::isize::MIN)].iter().cloned()); 1705 | let actual: HashSet<_> = empty_hashbag.signed_difference(&this_hashbag).collect(); 1706 | assert_eq!(expected, actual); 1707 | } 1708 | 1709 | fn do_test_signed_difference( 1710 | this: &[usize], 1711 | other: &[usize], 1712 | expected_entries: &[(&usize, isize)], 1713 | ) { 1714 | let this_hashbag: HashBag<_> = this.iter().cloned().collect(); 1715 | let other_hashbag: HashBag<_> = other.iter().cloned().collect(); 1716 | let expected: HashSet<_> = HashSet::from_iter(expected_entries.iter().cloned()); 1717 | let actual: HashSet<_> = this_hashbag.signed_difference(&other_hashbag).collect(); 1718 | assert_eq!(expected, actual); 1719 | } 1720 | 1721 | #[test] 1722 | fn test_no_zeros_counts() { 1723 | let mut hashbag = HashBag::new(); 1724 | hashbag.insert(100); 1725 | hashbag.retain(|_, _| 0); 1726 | hashbag.insert_many(1, 0); 1727 | hashbag.extend(vec![(2, 0)]); 1728 | assert_eq!(hashbag.len(), 0); 1729 | assert_eq!(hashbag.iter().count(), 0); 1730 | assert_eq!(hashbag.set_len(), 0); 1731 | assert_eq!(hashbag.set_iter().count(), 0); 1732 | } 1733 | 1734 | #[test] 1735 | fn remove_up_to_affects_count() { 1736 | let mut bag = HashBag::new(); 1737 | bag.insert_many(42, 3); 1738 | assert_eq!(bag.len(), 3); 1739 | assert_eq!(bag.remove_up_to(&0, 1), 0); 1740 | assert_eq!(bag.len(), 3); 1741 | assert_eq!(bag.remove_up_to(&42, 1), 2); 1742 | assert_eq!(bag.len(), 2); 1743 | assert_eq!(bag.remove_up_to(&42, 10), 0); 1744 | assert_eq!(bag.len(), 0); 1745 | } 1746 | 1747 | #[test] 1748 | fn entry_inserts_values() { 1749 | let mut bag = HashBag::new(); 1750 | bag.entry(42); 1751 | assert_eq!(bag.contains(&42), 0); 1752 | bag.entry(84).or_insert_many(3); 1753 | assert_eq!(bag.contains(&84), 3); 1754 | assert_eq!(bag.len(), 3); 1755 | bag.entry(84).or_insert_many(2); 1756 | assert_eq!(bag.len(), 3); 1757 | bag.entry(84).or_insert(); 1758 | assert_eq!(bag.len(), 3); 1759 | } 1760 | 1761 | #[test] 1762 | fn entry_affects_count() { 1763 | let mut bag = HashBag::new(); 1764 | bag.entry(42); 1765 | assert_eq!(bag.len(), 0); 1766 | bag.entry(42).and_modify(|n| *n += 3); 1767 | assert_eq!(bag.len(), 0); 1768 | bag.entry(42).or_insert_many(3); 1769 | assert_eq!(bag.len(), 3); 1770 | bag.entry(42).and_modify(|n| *n += 3); 1771 | assert_eq!(bag.len(), 6); 1772 | bag.entry(84).or_insert(); 1773 | assert_eq!(bag.len(), 7); 1774 | } 1775 | } 1776 | -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | use crate::HashBag; 2 | use core::fmt; 3 | use core::hash::{BuildHasher, Hash}; 4 | use core::marker::PhantomData; 5 | use serde::de::{SeqAccess, Visitor}; 6 | use serde::ser::{SerializeSeq, Serializer}; 7 | use serde::Deserializer; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | pub(crate) struct HashBagVisitor { 11 | marker: PhantomData HashBag>, 12 | } 13 | 14 | impl HashBagVisitor 15 | where 16 | T: Eq + Hash, 17 | S: BuildHasher + Clone, 18 | { 19 | fn new() -> Self { 20 | HashBagVisitor { 21 | marker: PhantomData, 22 | } 23 | } 24 | } 25 | 26 | impl<'de, T, S> Visitor<'de> for HashBagVisitor 27 | where 28 | T: Deserialize<'de> + Eq + Hash, 29 | S: BuildHasher + Clone + Default, 30 | { 31 | type Value = HashBag; 32 | 33 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | formatter.write_str("a HashBag") 35 | } 36 | 37 | fn visit_seq(self, mut access: M) -> Result 38 | where 39 | M: SeqAccess<'de>, 40 | { 41 | let mut bag: HashBag = 42 | HashBag::with_capacity_and_hasher(access.size_hint().unwrap_or(0), Default::default()); 43 | 44 | while let Some(entry) = access.next_element::<(T, usize)>()? { 45 | bag.insert_many(entry.0, entry.1); 46 | } 47 | 48 | Ok(bag) 49 | } 50 | } 51 | 52 | impl<'de, T, S> Deserialize<'de> for HashBag 53 | where 54 | T: Deserialize<'de> + Eq + Hash, 55 | S: BuildHasher + Clone + Default, 56 | { 57 | fn deserialize(deserializer: D) -> Result 58 | where 59 | D: Deserializer<'de>, 60 | { 61 | deserializer.deserialize_seq(HashBagVisitor::::new()) 62 | } 63 | } 64 | 65 | impl Serialize for HashBag 66 | where 67 | T: Serialize + Eq + Hash, 68 | H: BuildHasher + Clone, 69 | { 70 | fn serialize(&self, serializer: S) -> Result 71 | where 72 | S: Serializer, 73 | { 74 | let mut bag = serializer.serialize_seq(Some(self.set_len()))?; 75 | 76 | for (entry, count) in self.set_iter() { 77 | bag.serialize_element(&(entry, count))?; 78 | } 79 | 80 | bag.end() 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use super::*; 87 | 88 | use serde_json; 89 | 90 | #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] 91 | struct VeryHelpfulStruct { 92 | pub(crate) name: String, 93 | } 94 | 95 | #[test] 96 | fn format_simple_data() { 97 | let vikings: HashBag = ["Einar", "Olaf", "Olaf", "Harald", "Harald", "Harald"] 98 | .iter() 99 | .map(|s| s.to_string()) 100 | .collect(); 101 | let einar = "Einar".to_string(); 102 | let olaf = "Olaf".to_string(); 103 | let harald = "Harald".to_string(); 104 | assert_eq!(vikings.get(&einar), Some((&einar, 1))); 105 | assert_eq!(vikings.get(&olaf), Some((&olaf, 2))); 106 | assert_eq!(vikings.get(&harald), Some((&harald, 3))); 107 | println!("Constructed: {:?}", vikings); 108 | let jsonified_vikings: String = 109 | serde_json::to_string(&vikings).expect("Unable to convert data to json!"); 110 | println!("JSON: {}", jsonified_vikings); 111 | let reconstituted_vikings: HashBag = 112 | serde_json::from_str(&jsonified_vikings).expect("Unable to convert json to hashbag!"); 113 | println!("From json: {:?}", reconstituted_vikings); 114 | assert_eq!(vikings, reconstituted_vikings); 115 | } 116 | 117 | #[test] 118 | fn format_struct_data() { 119 | let vikings: HashBag = 120 | ["Einar", "Olaf", "Olaf", "Harald", "Harald", "Harald"] 121 | .iter() 122 | .map(|n| VeryHelpfulStruct { 123 | name: n.to_string(), 124 | }) 125 | .collect(); 126 | let einar = VeryHelpfulStruct { 127 | name: "Einar".to_string(), 128 | }; 129 | let olaf = VeryHelpfulStruct { 130 | name: "Olaf".to_string(), 131 | }; 132 | let harald = VeryHelpfulStruct { 133 | name: "Harald".to_string(), 134 | }; 135 | assert_eq!(vikings.get(&einar), Some((&einar, 1))); 136 | assert_eq!(vikings.get(&olaf), Some((&olaf, 2))); 137 | assert_eq!(vikings.get(&harald), Some((&harald, 3))); 138 | println!("Constructed: {:?}", vikings); 139 | let jsonified_vikings: String = 140 | serde_json::to_string(&vikings).expect("Unable to convert data to json!"); 141 | println!("JSON: {}", jsonified_vikings); 142 | let reconstituted_vikings: HashBag = 143 | serde_json::from_str(&jsonified_vikings).expect("Unable to convert json to hashbag!"); 144 | println!("From json: {:?}", reconstituted_vikings); 145 | assert_eq!(vikings, reconstituted_vikings); 146 | } 147 | 148 | #[test] 149 | fn repeat_simple_entries() { 150 | let jsonified_vikings: String = 151 | "[[\"Einar\",1],[\"Olaf\",1],[\"Olaf\",1],[\"Harald\",2],[\"Harald\",1]]".to_string(); 152 | let reconstituted_vikings: HashBag = 153 | serde_json::from_str(&jsonified_vikings).expect("Unable to convert json to hashbag!"); 154 | let einar = "Einar".to_string(); 155 | let olaf = "Olaf".to_string(); 156 | let harald = "Harald".to_string(); 157 | assert_eq!(reconstituted_vikings.get(&einar), Some((&einar, 1))); 158 | assert_eq!(reconstituted_vikings.get(&olaf), Some((&olaf, 2))); 159 | assert_eq!(reconstituted_vikings.get(&harald), Some((&harald, 3))); 160 | } 161 | 162 | #[test] 163 | fn repeat_struct_entries() { 164 | let jsonified_vikings: String = 165 | "[[{\"name\":\"Einar\"},1],[{\"name\":\"Olaf\"},1],[{\"name\":\"Olaf\"},1],[{\"name\":\"Harald\"},2],[{\"name\":\"Harald\"},1]]".to_string(); 166 | let reconstituted_vikings: HashBag = 167 | serde_json::from_str(&jsonified_vikings).expect("Unable to convert json to hashbag!"); 168 | let einar = VeryHelpfulStruct { 169 | name: "Einar".to_string(), 170 | }; 171 | let olaf = VeryHelpfulStruct { 172 | name: "Olaf".to_string(), 173 | }; 174 | let harald = VeryHelpfulStruct { 175 | name: "Harald".to_string(), 176 | }; 177 | assert_eq!(reconstituted_vikings.get(&einar), Some((&einar, 1))); 178 | assert_eq!(reconstituted_vikings.get(&olaf), Some((&olaf, 2))); 179 | assert_eq!(reconstituted_vikings.get(&harald), Some((&harald, 3))); 180 | } 181 | 182 | #[test] 183 | fn serde_bincode() { 184 | let vikings: HashBag = ["Einar", "Olaf", "Olaf", "Harald", "Harald", "Harald"] 185 | .iter() 186 | .map(|s| s.to_string()) 187 | .collect(); 188 | let einar = "Einar".to_string(); 189 | let olaf = "Olaf".to_string(); 190 | let harald = "Harald".to_string(); 191 | assert_eq!(vikings.get(&einar), Some((&einar, 1))); 192 | assert_eq!(vikings.get(&olaf), Some((&olaf, 2))); 193 | assert_eq!(vikings.get(&harald), Some((&harald, 3))); 194 | println!("Constructed: {:?}", vikings); 195 | let bincoded_vikings = 196 | bincode::serialize(&vikings).expect("Unable to serialize to bincode!"); 197 | let reconstituted_vikings: HashBag = 198 | bincode::deserialize(&bincoded_vikings).expect("Unable to deserialize bincode!"); 199 | println!("From bincode: {:?}", reconstituted_vikings); 200 | assert_eq!(vikings, reconstituted_vikings); 201 | } 202 | } 203 | --------------------------------------------------------------------------------