pax_global_header00006660000000000000000000000064145641243600014517gustar00rootroot0000000000000052 comment=1f436e313c172b0bfdfb1dbcaaaf188c642f9803 hashbag-0.1.12/000077500000000000000000000000001456412436000131755ustar00rootroot00000000000000hashbag-0.1.12/.github/000077500000000000000000000000001456412436000145355ustar00rootroot00000000000000hashbag-0.1.12/.github/DOCS.md000066400000000000000000000016121456412436000156070ustar00rootroot00000000000000# Github config and workflows In this folder there is configuration for codecoverage, dependabot, and ci workflows that check the library more deeply than the default configurations. This folder can be or was merged using a --allow-unrelated-histories merge strategy from which provides a reasonably sensible base for writing your own ci on. By using this strategy the history of the CI repo is included in your repo, and future updates to the CI can be merged later. To perform this merge run: ```shell git remote add ci https://github.com/jonhoo/rust-ci-conf.git git fetch ci git merge --allow-unrelated-histories ci/main ``` An overview of the files in this project is available at: , which contains some rationale for decisions and runs through an example of solving minimal version and OpenSSL issues. hashbag-0.1.12/.github/codecov.yml000066400000000000000000000006651456412436000167110ustar00rootroot00000000000000# ref: https://docs.codecov.com/docs/codecovyml-reference coverage: # Hold ourselves to a high bar range: 85..100 round: down precision: 1 status: # ref: https://docs.codecov.com/docs/commit-status project: default: # Avoid false negatives threshold: 1% # Test files aren't important for coverage ignore: - "tests" # Make comments less noisy comment: layout: "files" require_changes: true hashbag-0.1.12/.github/dependabot.yml000066400000000000000000000013001456412436000173570ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: daily - package-ecosystem: cargo directory: / schedule: interval: daily ignore: - dependency-name: "*" # patch and minor updates don't matter for libraries as consumers of this library build # with their own lockfile, rather than the version specified in this library's lockfile # remove this ignore rule if your package has binaries to ensure that the binaries are # built with the exact set of dependencies and those are up to date. update-types: - "version-update:semver-patch" - "version-update:semver-minor" hashbag-0.1.12/.github/workflows/000077500000000000000000000000001456412436000165725ustar00rootroot00000000000000hashbag-0.1.12/.github/workflows/check.yml000066400000000000000000000077771456412436000204140ustar00rootroot00000000000000# This workflow runs whenever a PR is opened or updated, or a commit is pushed to main. It runs # several checks: # - fmt: checks that the code is formatted according to rustfmt # - clippy: checks that the code does not contain any clippy warnings # - doc: checks that the code can be documented without errors # - hack: check combinations of feature flags # - msrv: check that the msrv specified in the crate is correct permissions: contents: read # This configuration allows maintainers of this repo to create a branch and pull request based on # the new branch. Restricting the push trigger to the main branch ensures that the PR only gets # built once. on: push: branches: [main] pull_request: # If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that # we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5 concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true name: check jobs: fmt: runs-on: ubuntu-latest name: stable / fmt steps: - uses: actions/checkout@v4 with: submodules: true - name: Install stable uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: cargo fmt --check run: cargo fmt --check clippy: runs-on: ubuntu-latest name: ${{ matrix.toolchain }} / clippy permissions: contents: read checks: write strategy: fail-fast: false matrix: # Get early warning of new lints which are regularly introduced in beta channels. toolchain: [stable, beta] steps: - uses: actions/checkout@v4 with: submodules: true - name: Install ${{ matrix.toolchain }} uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} components: clippy - name: cargo clippy uses: giraffate/clippy-action@v1 with: reporter: 'github-pr-check' github_token: ${{ secrets.GITHUB_TOKEN }} doc: # run docs generation on nightly rather than stable. This enables features like # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an # API be documented as only available in some specific platforms. runs-on: ubuntu-latest name: nightly / doc steps: - uses: actions/checkout@v4 with: submodules: true - name: Install nightly uses: dtolnay/rust-toolchain@nightly - name: cargo doc run: cargo doc --no-deps --all-features env: RUSTDOCFLAGS: --cfg docsrs hack: # cargo-hack checks combinations of feature flags to ensure that features are all additive # which is required for feature unification runs-on: ubuntu-latest name: ubuntu / stable / features steps: - uses: actions/checkout@v4 with: submodules: true - name: Install stable uses: dtolnay/rust-toolchain@stable - name: cargo install cargo-hack uses: taiki-e/install-action@cargo-hack # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 # --feature-powerset runs for every combination of features - name: cargo hack run: cargo hack --feature-powerset check msrv: # check that we can build using the minimal rust version that is specified by this crate runs-on: ubuntu-latest # we use a matrix here just because env can't be used in job names # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability strategy: matrix: msrv: ["1.40.0"] # map_get_key_value name: ubuntu / ${{ matrix.msrv }} steps: - uses: actions/checkout@v4 with: submodules: true - name: Install ${{ matrix.msrv }} uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.msrv }} - name: cargo +${{ matrix.msrv }} check run: cargo check hashbag-0.1.12/.github/workflows/scheduled.yml000066400000000000000000000043311456412436000212560ustar00rootroot00000000000000# Run scheduled (rolling) jobs on a nightly basis, as your crate may break independently of any # given PR. E.g., updates to rust nightly and updates to this crates dependencies. See check.yml for # information about how the concurrency cancellation and workflow triggering works permissions: contents: read on: push: branches: [main] pull_request: schedule: - cron: '7 7 * * *' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true name: rolling jobs: # https://twitter.com/mycoliza/status/1571295690063753218 nightly: runs-on: ubuntu-latest name: ubuntu / nightly steps: - uses: actions/checkout@v4 with: submodules: true - name: Install nightly uses: dtolnay/rust-toolchain@nightly - name: cargo generate-lockfile if: hashFiles('Cargo.lock') == '' run: cargo generate-lockfile - name: cargo test --locked run: cargo test --locked --all-features --all-targets # https://twitter.com/alcuadrado/status/1571291687837732873 update: # This action checks that updating the dependencies of this crate to the latest available that # satisfy the versions in Cargo.toml does not break this crate. This is important as consumers # of this crate will generally use the latest available crates. This is subject to the standard # Cargo semver rules (i.e cargo does not update to a new major version unless explicitly told # to). runs-on: ubuntu-latest name: ubuntu / beta / updated # There's no point running this if no Cargo.lock was checked in in the first place, since we'd # just redo what happened in the regular test job. Unfortunately, hashFiles only works in if on # steps, so we repeat it. steps: - uses: actions/checkout@v4 with: submodules: true - name: Install beta if: hashFiles('Cargo.lock') != '' uses: dtolnay/rust-toolchain@beta - name: cargo update if: hashFiles('Cargo.lock') != '' run: cargo update - name: cargo test if: hashFiles('Cargo.lock') != '' run: cargo test --locked --all-features --all-targets env: RUSTFLAGS: -D deprecated hashbag-0.1.12/.github/workflows/test.yml000066400000000000000000000151351456412436000203010ustar00rootroot00000000000000# This is the main CI workflow that runs the test suite on all pushes to main and all pull requests. # It runs the following jobs: # - required: runs the test suite on ubuntu with stable and beta rust toolchains # - minimal: runs the test suite with the minimal versions of the dependencies that satisfy the # requirements of this crate, and its dependencies # - os-check: runs the test suite on mac and windows # - coverage: runs the test suite and collects coverage information # See check.yml for information about how the concurrency cancellation and workflow triggering works permissions: contents: read on: push: branches: [main] pull_request: concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true name: test jobs: required: runs-on: ubuntu-latest name: ubuntu / ${{ matrix.toolchain }} strategy: matrix: # run on stable and beta to ensure that tests won't break on the next version of the rust # toolchain toolchain: [stable, beta] steps: - uses: actions/checkout@v4 with: submodules: true - name: Install ${{ matrix.toolchain }} uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - name: cargo generate-lockfile # enable this ci template to run regardless of whether the lockfile is checked in or not if: hashFiles('Cargo.lock') == '' run: cargo generate-lockfile # https://twitter.com/jonhoo/status/1571290371124260865 - name: cargo test --locked run: cargo test --locked --all-features --all-targets # https://github.com/rust-lang/cargo/issues/6669 - name: cargo test --doc run: cargo test --locked --all-features --doc minimal: # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure # that this crate is compatible with the minimal version that this crate and its dependencies # require. This will pickup issues where this create relies on functionality that was introduced # later than the actual version specified (e.g., when we choose just a major version, but a # method was added after this version). # # This particular check can be difficult to get to succeed as often transitive dependencies may # be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There # is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for # direct dependencies of this crate, while selecting the maximal versions for the transitive # dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase # the minimal dependency, which you do with e.g.: # ```toml # # for minimal-versions # [target.'cfg(any())'.dependencies] # openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions # ``` # The optional = true is necessary in case that dependency isn't otherwise transitively required # by your library, and the target bit is so that this dependency edge never actually affects # Cargo build order. See also # https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49. # This action is run on ubuntu with the stable toolchain, as it is not expected to fail runs-on: ubuntu-latest name: ubuntu / stable / minimal-versions steps: - uses: actions/checkout@v4 with: submodules: true - name: Install stable uses: dtolnay/rust-toolchain@stable - name: Install nightly for -Zminimal-versions uses: dtolnay/rust-toolchain@nightly - name: rustup default stable run: rustup default stable - name: cargo update -Zminimal-versions run: cargo +nightly update -Zminimal-versions - name: cargo test run: cargo test --locked --all-features --all-targets os-check: # run cargo test on mac and windows runs-on: ${{ matrix.os }} name: ${{ matrix.os }} / stable strategy: fail-fast: false matrix: os: [macos-latest, windows-latest] steps: # if your project needs OpenSSL, uncomment this to fix Windows builds. # it's commented out by default as the install command takes 5-10m. # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append # if: runner.os == 'Windows' # - run: vcpkg install openssl:x64-windows-static-md # if: runner.os == 'Windows' - uses: actions/checkout@v4 with: submodules: true - name: Install stable uses: dtolnay/rust-toolchain@stable - name: cargo generate-lockfile if: hashFiles('Cargo.lock') == '' run: cargo generate-lockfile - name: cargo test run: cargo test --locked --all-features --all-targets coverage: # use llvm-cov to build and collect coverage and outputs in a format that # is compatible with codecov.io # # note that codecov as of v4 requires that CODECOV_TOKEN from # # https://app.codecov.io/gh///settings # # is set in two places on your repo: # # - https://github.com/jonhoo/guardian/settings/secrets/actions # - https://github.com/jonhoo/guardian/settings/secrets/dependabot # # (the former is needed for codecov uploads to work with Dependabot PRs) # # PRs coming from forks of your repo will not have access to the token, but # for those, codecov allows uploading coverage reports without a token. # it's all a little weird and inconvenient. see # # https://github.com/codecov/feedback/issues/112 # # for lots of more discussion runs-on: ubuntu-latest name: ubuntu / stable / coverage steps: - uses: actions/checkout@v4 with: submodules: true - name: Install stable uses: dtolnay/rust-toolchain@stable with: components: llvm-tools-preview - name: cargo install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: cargo generate-lockfile if: hashFiles('Cargo.lock') == '' run: cargo generate-lockfile - name: cargo llvm-cov run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info - name: Record Rust version run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" - name: Upload to codecov.io uses: codecov/codecov-action@v4 with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} env_vars: OS,RUST hashbag-0.1.12/.gitignore000066400000000000000000000000101456412436000151540ustar00rootroot00000000000000/target hashbag-0.1.12/Cargo.lock000066400000000000000000000104341456412436000151040ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ahash" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", "version_check", ] [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "getrandom" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "griddle" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bb81d22191b89b117cd12d6549544bfcba0da741efdcec7c7d2fd06a0f56363" dependencies = [ "ahash", "hashbrown", ] [[package]] name = "hashbag" version = "0.1.12" dependencies = [ "bincode", "griddle", "serde", "serde_json", ] [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ "ahash", ] [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "proc-macro2" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "serde" version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "syn" version = "2.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" hashbag-0.1.12/Cargo.toml000066400000000000000000000011761456412436000151320ustar00rootroot00000000000000[package] name = "hashbag" version = "0.1.12" authors = ["Jon Gjengset "] edition = "2018" license = "MIT OR Apache-2.0" readme = "README.md" description = "An unordered multiset implementation using a hash bag" repository = "https://github.com/jonhoo/hashbag.git" keywords = ["bag","multiset","set"] categories = ["data-structures"] [dependencies] griddle = { version = "0.5", optional = true } serde = { version = "1.0", optional = true } [dev-dependencies] serde_json = { version = "1.0" } serde = { version = "1.0", features = ["derive"] } bincode = { version = "1.3" } [features] amortize = ["griddle"] hashbag-0.1.12/LICENSE-APACHE000066400000000000000000000261161456412436000151270ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Jon Gjengset Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. hashbag-0.1.12/LICENSE-MIT000066400000000000000000000020551456412436000146330ustar00rootroot00000000000000MIT License Copyright (c) 2020 Jon Gjengset Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hashbag-0.1.12/README.md000066400000000000000000000024251456412436000144570ustar00rootroot00000000000000[![Crates.io](https://img.shields.io/crates/v/hashbag.svg)](https://crates.io/crates/hashbag) [![Documentation](https://docs.rs/hashbag/badge.svg)](https://docs.rs/hashbag/) [![Codecov](https://codecov.io/github/jonhoo/hashbag/coverage.svg?branch=main)](https://codecov.io/gh/jonhoo/hashbag) [![Dependency status](https://deps.rs/repo/github/jonhoo/hashbag/status.svg)](https://deps.rs/repo/github/jonhoo/hashbag) An unordered multiset/bag implementation backed by `HashMap`. A bag, unlike a set, allows duplicate values, and keeps track of how many duplicates each value holds. This type of collection is often referred to as an unordered multiset (see also C++'s [`std::unordered_multiset`]). [`std::unordered_multiset`]: http://www.cplusplus.com/reference/unordered_set/unordered_multiset/ ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. hashbag-0.1.12/src/000077500000000000000000000000001456412436000137645ustar00rootroot00000000000000hashbag-0.1.12/src/lib.rs000066400000000000000000001526531456412436000151140ustar00rootroot00000000000000//! An unordered multiset/bag implementation backed by `HashMap`. //! //! A bag, unlike a set, allows duplicate values, and keeps track of how many //! duplicates each value holds. This type of collection is often referred to //! as an unordered multiset (see also C++'s [`std::unordered_multiset`]). //! //! This multiset/bag is implemented using a `HashMap` and so requires //! that the stored type implements `Hash + Eq`. //! //! For usage examples, see the primary type [`HashBag`]. //! //! If you want to use a hash table with [amortized resizes](https://github.com/jonhoo/griddle/), //! set the `amortize` feature. //! //! (De)serialization via serde is also available with the `serde` feature. //! Deserialization note: if the incoming data contains two instances of `T` that are the same, the resulting `HashBag` will merge //! the counts of those instances. //! //! [`std::unordered_multiset`]: http://www.cplusplus.com/reference/unordered_set/unordered_multiset/ #![deny(missing_docs, missing_debug_implementations, unreachable_pub)] #![cfg_attr(doc, deny(rustdoc::broken_intra_doc_links))] #![warn(rust_2018_idioms)] #[cfg(feature = "amortize")] use griddle::HashMap; use std::borrow::Borrow; use std::collections::hash_map::RandomState; #[cfg(not(feature = "amortize"))] use std::collections::HashMap; use std::convert::TryFrom; use std::hash::{BuildHasher, Hash}; #[cfg(feature = "serde")] mod serde; /// A hash bag implemented as a `HashMap` where the value is `usize`. /// /// A bag, unlike a set, allows duplicate values, and keeps track of how many /// duplicates each value holds. This type of collection is often referred to /// as an unordered multiset. /// /// As with the [`HashMap`] type, a `HashBag` requires that the elements /// implement the [`Eq`] and [`Hash`] traits. This can frequently be achieved by /// using `#[derive(PartialEq, Eq, Hash)]`. If you implement these yourself, /// it is important that the following property holds: /// /// ```text /// k1 == k2 -> hash(k1) == hash(k2) /// ``` /// /// In other words, if two keys are equal, their hashes must be equal. /// /// It is a logic error for an item to be modified in such a way that the /// item's hash, as determined by the [`Hash`] trait, or its equality, as /// determined by the [`Eq`] trait, changes while it is in the bag. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// // Type inference lets us omit an explicit type signature (which /// // would be `HashBag` in this example). /// let mut books = HashBag::new(); /// /// // Add some books. /// // Since we are a library, we have many copies. /// books.insert("A Dance With Dragons".to_string()); /// books.insert("To Kill a Mockingbird".to_string()); /// books.insert("To Kill a Mockingbird".to_string()); /// books.insert("The Odyssey".to_string()); /// books.insert("The Odyssey".to_string()); /// books.insert("The Odyssey".to_string()); /// books.insert("The Great Gatsby".to_string()); /// books.insert("The Great Gatsby".to_string()); /// books.insert("The Great Gatsby".to_string()); /// books.insert("The Great Gatsby".to_string()); /// /// // When we count the number of books, duplicates are included. /// assert_eq!(books.len(), 10); /// /// // Check for a specific one. /// if books.contains("The Winds of Winter") == 0 { /// println!("We have {} books, but The Winds of Winter ain't one.", /// books.len()); /// } /// /// // Remove a book. /// let had_copies = books.remove("The Odyssey"); /// // Remove returns how many copies of that book we had. /// assert_eq!(had_copies, 3); /// /// // Iterate over everything. /// // Duplicates will be listed multiple times. /// for book in &books { /// println!("{}", book); /// } /// /// // Iterate over each distinct book. /// for (book, copies) in books.set_iter() { /// println!("{} ({} copies)", book, copies); /// } /// /// // Extract the books and their counts. /// for (book, copies) in books { /// println!("{} ({} copies)", book, copies); /// } /// ``` /// /// The easiest way to use `HashBag` with a custom type is to derive /// [`Eq`] and [`Hash`]. We must also derive [`PartialEq`], this will in the /// future be implied by [`Eq`]. /// /// ``` /// use hashbag::HashBag; /// #[derive(Hash, Eq, PartialEq, Debug, Clone)] /// struct Viking { /// name: String, /// power: usize, /// } /// /// let mut vikings = HashBag::new(); /// /// vikings.insert(Viking { name: "Einar".to_string(), power: 9 }); /// vikings.insert(Viking { name: "Einar".to_string(), power: 9 }); /// vikings.insert(Viking { name: "Olaf".to_string(), power: 4 }); /// vikings.insert(Viking { name: "Olaf".to_string(), power: 5 }); /// vikings.insert(Viking { name: "Harald".to_string(), power: 8 }); /// /// // Use derived implementation to print the vikings. /// // Notice that all duplicates are printed. /// for v in &vikings { /// println!("{:?}", v); /// } /// /// // Since the derived implementation compares all the fields, /// // vikings that share a name but not a power are not duplicates. /// for (v, n) in vikings.set_iter() { /// println!("{:?} ({} of them!)", v, n); /// } /// /// // HashBags themselves can also be compared for equality, /// // and will do so by considering both the values and their counts. /// let mut vikings2 = vikings.clone(); /// assert_eq!(vikings, vikings2); /// let fallen = vikings.iter().next().unwrap(); /// vikings2.remove(fallen); /// assert_ne!(vikings, vikings2); /// vikings2.insert(Viking { name: "Snorre".to_string(), power: 1 }); /// assert_ne!(vikings, vikings2); /// ``` /// /// A `HashBag` with fixed list of elements can be initialized from an array: /// /// ``` /// use hashbag::HashBag; /// /// let mut viking_names: HashBag<&'static str> = /// [ "Einar", "Olaf", "Harald" ].iter().cloned().collect(); /// // use the values stored in the bag /// ``` /// /// You can also extend the bag easily: /// /// ``` /// use hashbag::HashBag; /// /// let mut vikings: HashBag = HashBag::new(); /// vikings.extend(std::iter::once("Snorre".to_string())); /// assert_eq!(vikings.contains("Snorre"), 1); /// /// // You can extend with many instances at once: /// vikings.extend(std::iter::once(("Snorre".to_string(), 4))); /// assert_eq!(vikings.contains("Snorre"), 5); /// /// // Extension also works with reference iterators if the type is Clone: /// let einar = String::from("Einar"); /// vikings.extend(std::iter::once(&einar)); /// assert_eq!(vikings.contains(&einar), 1); /// /// // And extend with many instances at once: /// vikings.extend(std::iter::once((&einar, 4))); /// assert_eq!(vikings.contains(&einar), 5); /// ``` pub struct HashBag { items: HashMap, count: usize, } impl Clone for HashBag { fn clone(&self) -> Self { Self { items: self.items.clone(), count: self.count, } } fn clone_from(&mut self, source: &Self) { self.items.clone_from(&source.items); self.count = source.count; } } impl HashBag { /// Creates an empty `HashBag`. /// /// The hash bag is initially created with a capacity of 0, so it will not allocate until it /// is first inserted into. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// let bag: HashBag = HashBag::new(); /// ``` #[inline] pub fn new() -> HashBag { Self::with_hasher(RandomState::new()) } /// Creates an empty `HashBag` with the specified capacity. /// /// The hash bag will be able to hold at least `capacity` distinct values without /// reallocating. If `capacity` is 0, the hash bag will not allocate. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// let bag: HashBag = HashBag::with_capacity(10); /// assert!(bag.capacity() >= 10); /// ``` #[inline] pub fn with_capacity(capacity: usize) -> HashBag { Self::with_capacity_and_hasher(capacity, RandomState::new()) } } impl HashBag { /// Returns the number of distinct values the bag can hold without reallocating. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// let bag: HashBag = HashBag::with_capacity(100); /// assert!(bag.capacity() >= 100); /// ``` #[inline] pub fn capacity(&self) -> usize { self.items.capacity() } /// An iterator visiting all elements in arbitrary order. /// /// The iterator element type is `&'a T`. /// Duplicates are yielded as many times as they appear in the bag. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// let mut bag = HashBag::new(); /// bag.insert("a"); /// bag.insert("b"); /// bag.insert("b"); /// /// // Will print in an arbitrary order. /// // b will be printed twice. /// for x in bag.iter() { /// println!("{}", x); /// } /// ``` #[inline] pub fn iter(&self) -> Iter<'_, T> { Iter::new(self.items.iter(), self.count) } /// An iterator visiting all distinct elements in arbitrary order. /// /// The iterator element type is `(&'a T, usize)`. /// Duplicated values are yielded once along with a count of the number of occurrences. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// let mut bag = HashBag::new(); /// bag.insert("a"); /// bag.insert("b"); /// bag.insert("b"); /// /// // Will print in an arbitrary order. /// for (x, n) in bag.set_iter() { /// println!("{} {}", x, n); /// } /// ``` #[inline] pub fn set_iter(&self) -> SetIter<'_, T> { SetIter(self.items.iter()) } /// Returns the number of elements in the bag. /// /// Duplicates are counted. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::new(); /// assert_eq!(bag.len(), 0); /// bag.insert(1); /// assert_eq!(bag.len(), 1); /// bag.insert(1); /// assert_eq!(bag.len(), 2); /// ``` #[inline] pub fn len(&self) -> usize { self.count } /// Returns the number of elements in the bag. /// /// Duplicates are not counted. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::new(); /// assert_eq!(bag.set_len(), 0); /// bag.insert(1); /// assert_eq!(bag.set_len(), 1); /// bag.insert(1); /// assert_eq!(bag.set_len(), 1); /// ``` #[inline] pub fn set_len(&self) -> usize { self.items.len() } /// Returns `true` if the bag contains no elements. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::new(); /// assert!(bag.is_empty()); /// bag.insert(1); /// assert!(!bag.is_empty()); /// ``` #[inline] pub fn is_empty(&self) -> bool { self.count == 0 } /// Clears the bag, returning all elements in an iterator. /// /// Duplicates appear only in the count yielded for each element. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); /// assert!(!bag.is_empty()); /// /// // prints /// // 1 1 /// // 2 1 /// // 3 2 /// // in an arbitrary order /// for (i, n) in bag.drain() { /// println!("{} {}", i, n); /// } /// /// assert!(bag.is_empty()); /// ``` #[inline] pub fn drain(&mut self) -> Drain<'_, T> { self.count = 0; Drain(self.items.drain()) } /// Clears the bag, removing all values. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::new(); /// bag.insert(1); /// bag.clear(); /// assert!(bag.is_empty()); /// ``` #[inline] pub fn clear(&mut self) { self.count = 0; self.items.clear(); } } impl HashBag where T: Eq + Hash, S: BuildHasher, { /// Creates a new empty hash bag which will use the given hasher to hash /// keys. /// /// The hash bag is also created with the default initial capacity. /// /// Warning: `hasher` is normally randomly generated, and /// is designed to allow `HashBag`s to be resistant to attacks that /// cause many collisions and very poor performance. Setting it /// manually using this function can expose a DoS attack vector. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// use std::collections::hash_map::RandomState; /// /// let s = RandomState::new(); /// let mut bag = HashBag::with_hasher(s); /// bag.insert(2); /// ``` #[inline] pub fn with_hasher(hash_builder: S) -> HashBag { HashBag { items: HashMap::with_hasher(hash_builder), count: 0, } } /// Creates an empty `HashBag` with the specified capacity, using /// `hasher` to hash the keys. /// /// The hash bag will be able to hold at least `capacity` distinct values /// without reallocating. If `capacity` is 0, the hash bag will not allocate. /// /// Warning: `hasher` is normally randomly generated, and /// is designed to allow `HashBag`s to be resistant to attacks that /// cause many collisions and very poor performance. Setting it /// manually using this function can expose a DoS attack vector. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// use std::collections::hash_map::RandomState; /// /// let s = RandomState::new(); /// let mut bag = HashBag::with_capacity_and_hasher(10, s); /// bag.insert(1); /// ``` #[inline] pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> HashBag { HashBag { items: HashMap::with_capacity_and_hasher(capacity, hash_builder), count: 0, } } /// Returns a reference to the bag's [`BuildHasher`]. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// use std::collections::hash_map::RandomState; /// /// let hasher = RandomState::new(); /// let bag: HashBag = HashBag::with_hasher(hasher); /// let hasher: &RandomState = bag.hasher(); /// ``` #[inline] pub fn hasher(&self) -> &S { self.items.hasher() } /// Reserves capacity for at least `additional` more distinct values /// to be inserted in the `HashBag`. The collection may reserve more /// space to avoid frequent reallocations. /// /// # Panics /// /// Panics if the new allocation size overflows `usize`. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// let mut bag: HashBag = HashBag::new(); /// bag.reserve(10); /// assert!(bag.capacity() >= 10); /// ``` #[inline] pub fn reserve(&mut self, additional: usize) { self.items.reserve(additional) } /// Shrinks the capacity of the ba as much as possible. It will drop /// down as much as possible while maintaining the internal rules /// and possibly leaving some space in accordance with the resize policy. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::with_capacity(100); /// bag.insert(1); /// bag.insert(2); /// assert!(bag.capacity() >= 100); /// bag.shrink_to_fit(); /// assert!(bag.capacity() >= 2); /// ``` #[inline] pub fn shrink_to_fit(&mut self) { self.items.shrink_to_fit() } /// Returns the number of instances of `value` in the bag. /// /// The value may be any borrowed form of the bag's value type, but /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for /// the value type. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); /// assert_eq!(bag.contains(&1), 1); /// assert_eq!(bag.contains(&3), 2); /// assert_eq!(bag.contains(&4), 0); /// ``` #[inline] pub fn contains(&self, value: &Q) -> usize where T: Borrow, Q: Hash + Eq, { self.items.get(value).cloned().unwrap_or(0) } /// Returns a reference to the value in the bag, if any, that is equal to the given value, /// along with its number of occurrences. /// /// The value may be any borrowed form of the bag's value type, but /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for /// the value type. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); /// assert_eq!(bag.get(&2), Some((&2, 1))); /// assert_eq!(bag.get(&3), Some((&3, 2))); /// assert_eq!(bag.get(&4), None); /// ``` #[inline] pub fn get(&self, value: &Q) -> Option<(&T, usize)> where T: Borrow, Q: Hash + Eq, { self.items .get_key_value(value) .map(|(t, count)| (t, *count)) } /// Gets a given value's corresponding entry in the bag for in-place manipulation. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag: HashBag = ['a'].iter().cloned().collect(); /// let entry = bag.entry('a').and_modify(|n| *n += 1).or_insert(); /// assert_eq!(bag.get(&'a'), Some((&'a', 2))); /// let entry = bag.entry('b').and_modify(|n| *n += 1).or_insert(); /// assert_eq!(bag.get(&'b'), Some((&'b', 1))); /// let entry = bag.entry('c').and_modify(|n| *n += 1).or_insert_many(7); /// assert_eq!(bag.get(&'c'), Some((&'c', 7))); /// ``` #[inline] pub fn entry(&mut self, value: T) -> Entry<'_, T, S> { Entry(( ForiegnEntry::new(self.items.entry(value)), &mut self.count, PhantomData, )) } /// Adds a value to the bag. /// /// The number of occurrences of the value previously in the bag is returned. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::new(); /// /// assert_eq!(bag.insert(2), 0); /// assert_eq!(bag.insert(2), 1); /// assert_eq!(bag.insert(2), 2); /// assert_eq!(bag.set_len(), 1); /// assert_eq!(bag.len(), 3); /// ``` #[inline] pub fn insert(&mut self, value: T) -> usize { self.insert_many(value, 1) } /// Adds multiple occurrences of a value to the bag. /// /// The number of occurrences of the value previously in the bag is returned. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::new(); /// /// assert_eq!(bag.insert_many(2, 1), 0); /// assert_eq!(bag.insert_many(2, 2), 1); /// assert_eq!(bag.insert_many(2, 4), 3); /// assert_eq!(bag.set_len(), 1); /// assert_eq!(bag.len(), 7); /// ``` #[inline] pub fn insert_many(&mut self, value: T, count: usize) -> usize { if count == 0 { return self.contains(&value); } self.count += count; let n = self.items.entry(value).or_insert(0); let was_there = *n; *n += count; was_there } /// Adds a value to the bag, replacing all existing occurrences, if any, that equal the given /// one. /// /// The number of occurrences of the value previously in the bag is returned. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::new(); /// bag.insert(Vec::::new()); /// bag.insert(Vec::::new()); /// assert_eq!(bag.contains(&[][..]), 2); /// assert_eq!(bag.get(&[][..]).unwrap().0.capacity(), 0); /// /// bag.replace(Vec::with_capacity(10)); /// assert_eq!(bag.contains(&[][..]), 1); /// assert_eq!(bag.get(&[][..]).unwrap().0.capacity(), 10); /// ``` #[inline] pub fn replace(&mut self, value: T) -> usize { let n = self.items.remove(&value).unwrap_or(0); self.count -= n; self.items.insert(value, 1); self.count += 1; n } /// Removes a value from the bag. /// /// The number of occurrences of the value previously in the bag is returned. /// /// The value may be any borrowed form of the bag's value type, but /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for /// the value type. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::new(); /// /// bag.insert_many('x', 2); /// assert_eq!(bag.contains(&'x'), 2); /// assert_eq!(bag.remove(&'x'), 2); /// assert_eq!(bag.contains(&'x'), 1); /// assert_eq!(bag.remove(&'x'), 1); /// assert_eq!(bag.contains(&'x'), 0); /// assert_eq!(bag.remove(&'x'), 0); /// ``` #[inline] pub fn remove(&mut self, value: &Q) -> usize where T: Borrow, Q: Hash + Eq, { match self.items.get_mut(value) { None => 0, #[cfg(debug_assertions)] Some(n) if *n == 0 => unreachable!(), Some(n) if *n == 1 => { self.count -= 1; self.items.remove(value); 1 } Some(n) => { self.count -= 1; *n -= 1; *n + 1 } } } /// Removes multiple of a value from the bag. If `quantity` is greater than the number of /// occurences, zero occurances will remain. /// /// The number of occurrences of the value currently in the bag is returned. /// /// The value may be any borrowed form of the bag's value type, but /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for /// the value type. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag = HashBag::new(); /// /// bag.insert_many('x', 10); /// assert_eq!(bag.contains(&'x'), 10); /// assert_eq!(bag.remove_up_to(&'x', 3), 7); /// assert_eq!(bag.contains(&'x'), 7); /// assert_eq!(bag.remove_up_to(&'x', 10), 0); /// ``` #[inline] pub fn remove_up_to(&mut self, value: &Q, quantity: usize) -> usize where T: Borrow, Q: Hash + Eq, { match self.items.get_mut(value) { None => 0, Some(&mut n) if n <= quantity => { self.count -= n; self.items.remove(value); 0 } Some(n) => { self.count -= quantity; *n -= quantity; *n } } } /// Returns an iterator over all of the elements that are in `self` or `other`. /// The iterator also yields the respective counts in `self` and `other` in that order. /// Elements that are in `self` are yielded before any elements that are exclusively in `other`. /// Each distinct element is yielded only once. /// /// # Examples /// ``` /// use hashbag::HashBag; /// use std::collections::HashSet; /// use std::iter::FromIterator; /// /// let a: HashBag<_> = "hash".chars().collect(); /// let b: HashBag<_> = "math".chars().collect(); /// let expected: HashSet<_> = HashSet::from_iter([(&'h', 2, 1), (&'a', 1, 1), (&'s', 1, 0), (&'m', 0, 1), (&'t', 0, 1)]); /// let actual: HashSet<_> = a.outer_join(&b).collect(); /// assert_eq!(expected, actual); /// ``` pub fn outer_join<'a, OtherS>( &'a self, other: &'a HashBag, ) -> impl Iterator where OtherS: BuildHasher, { self.items .iter() .map(move |(x, &self_count)| (x, self_count, other.contains(x))) .chain(other.items.iter().filter_map(move |(x, &other_count)| { let self_count = self.contains(x); if self_count == 0 { Some((x, self_count, other_count)) } else { None } })) } /// Returns an iterator over all the elements that are in `self` with a /// higher occurrence count than in `other`. The count in the returned /// iterator represents how many more of a given element are in `self` than /// `other`. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// use std::collections::HashSet; /// use std::iter::FromIterator; /// /// let a: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); /// let b: HashBag<_> = [2, 3].iter().cloned().collect(); /// let expected: HashSet<_> = HashSet::from_iter([(&1, 1), (&3, 1)]); /// let actual: HashSet<_> = a.difference(&b).collect(); /// assert_eq!(expected, actual); /// ``` pub fn difference<'a, OtherS>( &'a self, other: &'a HashBag, ) -> impl Iterator where OtherS: BuildHasher, { self.outer_join(other) .take_while(|(_, self_count, _)| self_count > &0) .filter(|(_x, self_count, other_count)| self_count > other_count) .map(|(x, self_count, other_count)| (x, self_count - other_count)) } /// Returns an iterator over all the elements that are in `self` or `other`. /// The iterator also yields the difference in counts between `self` and `other`. /// /// Unlike 'difference' which only yields elements that have a higher count in `self` than in `other`, /// this iterator yields all elements that are in either of the `HashBag`s. Elements that have a higher /// count in `other` than in self (including elements that are not in `self`) will have a negative count. /// /// If the difference can be represented as an `isize`, then it will be. Otherwise, the difference will be /// clamped to `isize::MIN`/`isize::MAX`, thus keeping the sign of the difference, and as much of the /// magnitude as possible. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// use std::collections::HashSet; /// use std::iter::FromIterator; /// /// let a: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); /// let b: HashBag<_> = [2, 3, 4, 4].iter().cloned().collect(); /// let expected: HashSet<_> = HashSet::from_iter([(&1, 1), (&2, 0), (&3, 1), (&4, -2)]); /// let actual: HashSet<_> = a.signed_difference(&b).collect(); /// assert_eq!(expected, actual); /// ``` pub fn signed_difference<'a, OtherS>( &'a self, other: &'a HashBag, ) -> impl Iterator where OtherS: BuildHasher, { self.outer_join(other).map(|(x, self_count, other_count)| { let diff = if self_count >= other_count { isize::try_from(self_count - other_count).unwrap_or(std::isize::MAX) } else { isize::try_from(other_count - self_count) .map(|x| -x) .unwrap_or(std::isize::MIN) }; (x, diff) }) } /// Returns an iterator over all of the elements that are in `self` but not in `other`. /// /// # Examples /// ``` /// use hashbag::HashBag; /// use std::collections::HashSet; /// use std::iter::FromIterator; /// /// let a: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); /// let b: HashBag<_> = [2, 3].iter().cloned().collect(); /// let expected: HashSet<_> = HashSet::from_iter([(&1, 1)]); /// let actual: HashSet<_> = a.not_in(&b).collect(); /// assert_eq!(expected, actual); /// ``` pub fn not_in<'a, OtherS>( &'a self, other: &'a HashBag, ) -> impl Iterator where OtherS: BuildHasher, { self.outer_join(other) .take_while(|(_, self_count, _)| self_count > &0) .filter_map(|(k, self_count, other_count)| { if other_count == 0 { Some((k, self_count)) } else { None } }) } /// Removes a value that is equal to the given one, and returns it if it was the last. /// /// If the matching value is not the last, a reference to the remainder is given, along with /// the number of occurrences prior to the removal. /// /// The value may be any borrowed form of the bag's value type, but /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for /// the value type. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); /// assert_eq!(bag.try_take(&2), Ok(2)); /// assert_eq!(bag.try_take(&3), Err(Some((&3, 2)))); /// assert_eq!(bag.try_take(&3), Ok(3)); /// assert_eq!(bag.try_take(&4), Err(None)); /// ``` #[inline] pub fn try_take(&mut self, value: &Q) -> Result> where T: Borrow, Q: Hash + Eq, { // TODO: it should be possible to make this more efficient match self.items.remove_entry(value) { Some((t, 1)) => { self.count -= 1; Ok(t) } Some((t, n)) => { self.count -= 1; self.items.insert(t, n - 1); Err(Some( self.items .get_key_value(value) .map(|(t, n)| (t, *n + 1)) .unwrap(), )) } None => Err(None), } } /// Removes and returns all occurrences of the value, if any, that is equal to the given one. /// /// The value may be any borrowed form of the bag's value type, but /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for /// the value type. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let mut bag: HashBag<_> = [1, 2, 3, 3].iter().cloned().collect(); /// assert_eq!(bag.take_all(&2), Some((2, 1))); /// assert_eq!(bag.take_all(&3), Some((3, 2))); /// assert_eq!(bag.take_all(&2), None); /// assert_eq!(bag.take_all(&3), None); /// ``` #[inline] pub fn take_all(&mut self, value: &Q) -> Option<(T, usize)> where T: Borrow, Q: Hash + Eq, { let (t, n) = self.items.remove_entry(value)?; self.count -= n; Some((t, n)) } /// Retains only the values specified by the predicate. /// /// In other words, for each value `v` retain only `f(&v)` occurrences. /// /// # Examples /// /// ``` /// use hashbag::HashBag; /// /// let xs = [0,0,0,0,0,1,1,1,1,2,2,2,3,3,4]; /// let mut bag: HashBag = xs.iter().cloned().collect(); /// bag.retain(|&k, _| k as usize); /// assert_eq!(bag.set_len(), 4); // >= 1 of all but value 0 /// assert_eq!(bag.len(), 6); /// assert_eq!(bag.contains(&0), 0); /// assert_eq!(bag.contains(&1), 1); /// assert_eq!(bag.contains(&2), 2); /// assert_eq!(bag.contains(&3), 2); /// assert_eq!(bag.contains(&4), 1); /// ``` pub fn retain(&mut self, mut f: F) where F: FnMut(&T, usize) -> usize, { let count = &mut self.count; self.items.retain(|t, n| { let keep = std::cmp::min(*n, f(t, *n)); *count -= *n - keep; if keep == 0 { false } else { *n = keep; true } }); } } // ======== standard traits use std::fmt; use std::marker::PhantomData; impl fmt::Debug for HashBag where T: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_set().entries(self.iter()).finish() } } impl Default for HashBag where T: Eq + Hash, S: BuildHasher + Default, { fn default() -> Self { Self::with_hasher(S::default()) } } impl PartialEq> for HashBag where T: Eq + Hash, S: BuildHasher, { fn eq(&self, other: &Self) -> bool { self.count == other.count && self.items == other.items } } impl Eq for HashBag where T: Eq + Hash, S: BuildHasher, { } impl<'a, T, S> Extend<&'a T> for HashBag where T: 'a + Eq + Hash + Clone, S: BuildHasher, { fn extend>(&mut self, iter: I) { for e in iter { self.insert(e.clone()); } } } impl<'a, T, S> Extend<(&'a T, usize)> for HashBag where T: 'a + Eq + Hash + Clone, S: BuildHasher, { fn extend>(&mut self, iter: I) { for (e, n) in iter { self.count += n; *self.items.entry(e.clone()).or_insert(0) += n; } } } impl Extend for HashBag where T: Eq + Hash, S: BuildHasher, { fn extend>(&mut self, iter: I) { for e in iter { self.insert(e); } } } impl Extend<(T, usize)> for HashBag where T: Eq + Hash, S: BuildHasher, { fn extend>(&mut self, iter: I) { for (e, n) in iter { self.count += n; if n != 0 { *self.items.entry(e).or_insert(0) += n; } } } } impl std::iter::FromIterator for HashBag where T: Eq + Hash, S: BuildHasher + Default, { fn from_iter>(iter: I) -> Self { let mut bag = Self::default(); bag.extend(iter); bag } } impl std::iter::FromIterator<(T, usize)> for HashBag where T: Eq + Hash, S: BuildHasher + Default, { fn from_iter>(iter: I) -> Self { let mut bag = Self::default(); bag.extend(iter); bag } } impl<'a, T, S> IntoIterator for &'a HashBag { type Item = &'a T; type IntoIter = Iter<'a, T>; fn into_iter(self) -> Iter<'a, T> { self.iter() } } impl IntoIterator for HashBag { type Item = (T, usize); type IntoIter = IntoIter; fn into_iter(self) -> IntoIter { IntoIter(self.items.into_iter()) } } // ======== entry type #[cfg(feature = "amortize")] pub(crate) mod entry { use griddle::hash_map::Entry; #[derive(Debug)] pub(crate) struct ForiegnEntry<'a, T, S> { pub(crate) entry: Entry<'a, T, usize, S>, } impl<'a, T, S> ForiegnEntry<'a, T, S> { pub(crate) fn new(entry: Entry<'a, T, usize, S>) -> Self { Self { entry } } pub(crate) fn get_mut(&mut self) -> Option<&mut usize> { match &mut self.entry { Entry::Occupied(entry) => Some(entry.get_mut()), Entry::Vacant(_) => None, } } } } #[cfg(not(feature = "amortize"))] pub(crate) mod entry { use std::{collections::hash_map::Entry, marker::PhantomData}; #[derive(Debug)] pub(crate) struct ForiegnEntry<'a, T, S> { pub(crate) entry: Entry<'a, T, usize>, data: PhantomData, } impl<'a, T, S> ForiegnEntry<'a, T, S> { pub(crate) fn new(entry: Entry<'a, T, usize>) -> Self { Self { entry, data: PhantomData, } } pub(crate) fn get_mut(&mut self) -> Option<&mut usize> { match &mut self.entry { Entry::Occupied(entry) => Some(entry.get_mut()), Entry::Vacant(_) => None, } } } } use entry::ForiegnEntry; type EntryInner<'a, T, S> = (ForiegnEntry<'a, T, S>, &'a mut usize, PhantomData); #[derive(Debug)] /// A view into a single entry in the bag, which may either be vacant or occupied. /// This `enum` is constructed from the [`entry`](HashBag::entry) method on [`HashBag`] pub struct Entry<'a, T, S>(EntryInner<'a, T, S>); impl<'a, T, S> Entry<'a, T, S> where T: Hash + Eq, S: BuildHasher, { /// Provides in-place mutable access to an occupied entry before potential inserts into the /// map. pub fn and_modify(mut self, f: F) -> Self where F: FnOnce(&mut usize), { if let Some(n) = self.0 .0.get_mut() { let init = *n; f(n); *self.0 .1 += *n; *self.0 .1 -= init; } Self((self.0 .0, self.0 .1, PhantomData)) } /// Returns a reference to the entry's value. pub fn value(&self) -> &T { self.0 .0.entry.key() } /// Ensures there is at least one instance of the value before returning a mutable reference /// to the value's count pub fn or_insert(mut self) -> usize { if self.0 .0.get_mut().is_none() { *self.0 .1 += 1; } *self.0 .0.entry.or_insert(1) } /// Ensures there is at least `quantity` instances of the value before returning a mutable reference /// to the value's count pub fn or_insert_many(mut self, quantity: usize) -> usize { if self.0 .0.get_mut().is_none() { *self.0 .1 += quantity; } *self.0 .0.entry.or_insert(quantity) } } // ======== iterators #[cfg(feature = "amortize")] type IterInner<'a, T> = griddle::hash_map::Iter<'a, T, usize>; #[cfg(not(feature = "amortize"))] type IterInner<'a, T> = std::collections::hash_map::Iter<'a, T, usize>; /// An iterator over the items of a `HashBag`. /// /// Each value is repeated as many times as it occurs in the bag. /// /// This `struct` is created by [`HashBag::iter`]. /// See its documentation for more. pub struct Iter<'a, T> { iter: IterInner<'a, T>, repeat: Option<(&'a T, usize)>, left: usize, } impl<'a, T> std::iter::FusedIterator for Iter<'a, T> where IterInner<'a, T>: std::iter::FusedIterator {} impl<'a, T> ExactSizeIterator for Iter<'a, T> where IterInner<'a, T>: ExactSizeIterator {} impl<'a, T> Clone for Iter<'a, T> { fn clone(&self) -> Self { Iter { iter: self.iter.clone(), repeat: self.repeat, left: self.left, } } } impl fmt::Debug for Iter<'_, T> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_set().entries(self.clone()).finish() } } impl<'a, T> Iter<'a, T> { fn new(it: IterInner<'a, T>, n: usize) -> Self { Self { iter: it, repeat: None, left: n, } } } impl<'a, T> Iterator for Iter<'a, T> { type Item = &'a T; fn next(&mut self) -> Option { if let Some((t, ref mut n)) = self.repeat { if *n == 0 { self.repeat = None; } else { *n -= 1; self.left -= 1; return Some(t); } } let (next, n) = self.iter.next()?; if *n > 1 { self.repeat = Some((next, *n - 1)); } self.left -= 1; Some(next) } fn size_hint(&self) -> (usize, Option) { (self.left, Some(self.left)) } } /// An iterator over the distinct items of a `HashBag` and their occurrence counts. /// /// This `struct` is created by [`HashBag::set_iter`]. /// See its documentation for more. pub struct SetIter<'a, T>(IterInner<'a, T>); impl<'a, T> std::iter::FusedIterator for SetIter<'a, T> where IterInner<'a, T>: std::iter::FusedIterator { } impl<'a, T> ExactSizeIterator for SetIter<'a, T> where IterInner<'a, T>: ExactSizeIterator {} impl<'a, T> Clone for SetIter<'a, T> { fn clone(&self) -> Self { SetIter(self.0.clone()) } } impl fmt::Debug for SetIter<'_, T> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_set().entries(self.clone()).finish() } } impl<'a, T> Iterator for SetIter<'a, T> { type Item = (&'a T, usize); fn next(&mut self) -> Option { self.0.next().map(|(t, n)| (t, *n)) } fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } } #[cfg(feature = "amortize")] type IntoIterInner = griddle::hash_map::IntoIter; #[cfg(not(feature = "amortize"))] type IntoIterInner = std::collections::hash_map::IntoIter; /// An owning iterator over the distinct items of a `HashBag` and their occurrence counts. /// /// This `struct` is created by using the implementation of [`IntoIterator`] for [`HashBag`]. pub struct IntoIter(IntoIterInner); impl std::iter::FusedIterator for IntoIter where IntoIterInner: std::iter::FusedIterator {} impl ExactSizeIterator for IntoIter where IntoIterInner: ExactSizeIterator {} impl fmt::Debug for IntoIter { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(fmt) } } impl Iterator for IntoIter { type Item = (T, usize); fn next(&mut self) -> Option { self.0.next() } fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } } #[cfg(feature = "amortize")] type DrainInner<'a, T> = griddle::hash_map::Drain<'a, T, usize>; #[cfg(not(feature = "amortize"))] type DrainInner<'a, T> = std::collections::hash_map::Drain<'a, T, usize>; /// An draining iterator over the distinct items of a `HashBag` and their occurrence counts. /// /// This `struct` is created by [`HashBag::drain`]. /// See its documentation for more. pub struct Drain<'a, T>(DrainInner<'a, T>); impl<'a, T> std::iter::FusedIterator for Drain<'a, T> where DrainInner<'a, T>: std::iter::FusedIterator { } impl<'a, T> ExactSizeIterator for Drain<'a, T> where DrainInner<'a, T>: ExactSizeIterator {} impl<'a, T: fmt::Debug> fmt::Debug for Drain<'a, T> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(fmt) } } impl<'a, T> Iterator for Drain<'a, T> { type Item = (T, usize); fn next(&mut self) -> Option { self.0.next() } fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } } #[cfg(test)] mod tests { use std::collections::HashSet; use std::iter::FromIterator; use super::*; #[test] fn format_all_the_things() { let mut vikings: HashBag<&'static str> = ["Einar", "Olaf", "Harald"].iter().cloned().collect(); println!("{:?}", vikings); println!("{:?}", vikings.iter()); println!("{:?}", vikings.set_iter()); println!("{:?}", vikings.clone().into_iter()); println!("{:?}", vikings.drain()); } #[test] fn sane_iterators() { let mut vikings: HashBag<&'static str> = ["Einar", "Einar", "Harald"].iter().cloned().collect(); assert_eq!(vikings.iter().count(), 3); assert_eq!(vikings.iter().size_hint(), (3, Some(3))); assert_eq!(vikings.iter().clone().count(), 3); assert_eq!(vikings.set_iter().count(), 2); assert_eq!(vikings.set_iter().clone().count(), 2); assert_eq!(vikings.set_iter().size_hint(), (2, Some(2))); let ii = vikings.clone().into_iter(); assert_eq!(ii.size_hint(), (2, Some(2))); assert_eq!(ii.count(), 2); let di = vikings.drain(); assert_eq!(di.size_hint(), (2, Some(2))); assert_eq!(di.count(), 2); } #[test] fn test_difference_size_hint() { let bag: HashBag<_> = [3, 2, 1].iter().cloned().collect(); let empty_bag = HashBag::new(); let mut difference = bag.difference(&empty_bag); // Since the difference has the same number of entries as the bag, we // can predict how the size_hint() will behave, because the iteration // order does not matter assert_eq!(difference.size_hint(), (0, Some(3))); difference.next().unwrap(); assert_eq!(difference.size_hint(), (0, Some(2))); difference.next().unwrap(); assert_eq!(difference.size_hint(), (0, Some(1))); difference.next().unwrap(); assert_eq!(difference.size_hint(), (0, Some(0))); assert_eq!(difference.next(), None); assert_eq!(difference.size_hint(), (0, Some(0))); } #[test] fn test_difference_from_empty() { do_test_difference(&[], &[], &[]); do_test_difference(&[], &[1], &[]); do_test_difference(&[], &[1, 1], &[]); do_test_difference(&[], &[1, 1, 2], &[]); } #[test] fn test_difference_from_one() { do_test_difference(&[1], &[], &[1]); do_test_difference(&[1], &[1], &[]); do_test_difference(&[1], &[1, 1], &[]); do_test_difference(&[1], &[2], &[1]); do_test_difference(&[1], &[1, 2], &[]); do_test_difference(&[1], &[2, 2], &[1]); } #[test] fn test_difference_from_duplicate_ones() { do_test_difference(&[1, 1], &[], &[1, 1]); do_test_difference(&[1, 1], &[1], &[1]); do_test_difference(&[1, 1], &[1, 1], &[]); do_test_difference(&[1, 1], &[2], &[1, 1]); do_test_difference(&[1, 1], &[1, 2], &[1]); do_test_difference(&[1, 1], &[2, 2], &[1, 1]); } #[test] fn test_difference_from_one_one_two() { do_test_difference(&[1, 1, 2], &[], &[1, 1, 2]); do_test_difference(&[1, 1, 2], &[1], &[1, 2]); do_test_difference(&[1, 1, 2], &[1, 1], &[2]); do_test_difference(&[1, 1, 2], &[2], &[1, 1]); do_test_difference(&[1, 1, 2], &[1, 2], &[1]); do_test_difference(&[1, 1, 2], &[2, 2], &[1, 1]); } #[test] fn test_difference_from_larger_bags() { do_test_difference(&[1, 2, 2, 3], &[3], &[1, 2, 2]); do_test_difference(&[1, 2, 2, 3], &[4], &[1, 2, 2, 3]); do_test_difference(&[2, 2, 2, 2], &[2, 2], &[2, 2]); do_test_difference(&[2, 2, 2, 2], &[], &[2, 2, 2, 2]); } fn do_test_difference( self_entries: &[isize], other_entries: &[isize], expected_entries: &[isize], ) { let this = self_entries.iter().collect::>(); let other = other_entries.iter().collect::>(); let expected = expected_entries.iter().collect::>(); let mut actual = HashBag::new(); for (t, n) in this.difference(&other) { actual.insert_many(*t, n); } assert_eq!(actual, expected); } #[test] fn test_outer_join_order_with_disjoint_sets() { do_test_outer_join_order(&[1, 2, 3], &[4, 5, 6]); do_test_outer_join_order(&[1, 2, 2, 3], &[4, 4, 5, 6]); } #[test] fn test_outer_join_order_with_overlap() { do_test_outer_join_order(&[1, 2, 3], &[2, 3, 4]); do_test_outer_join_order(&[1, 1, 2, 3], &[2, 3, 3, 3, 4]); } fn do_test_outer_join_order(this: &[usize], other: &[usize]) { let this_hashbag: HashBag = this.iter().cloned().collect(); let other_hashbag: HashBag = other.iter().cloned().collect(); // Assert that the first yielded key that's exclusive to other (i.e. self_count is 0) // comes AFTER all of the keys in self let min_other_exclusive_key_idx = this_hashbag .outer_join(&other_hashbag) .enumerate() .find(|(_, (_, self_count, _))| self_count == &0) .map(|(idx, _)| idx); // If no such element exists that means all of the keys in other // are in self so there's no thing to assert. if let Some(idx) = min_other_exclusive_key_idx { assert_eq!(idx, this_hashbag.set_len()); } } #[test] fn test_outer_join_with_empty_self() { do_test_outer_join(&[], &[1, 2, 2, 3], &[(&1, 0, 1), (&2, 0, 2), (&3, 0, 1)]); } #[test] fn test_outer_join_with_empty_other() { do_test_outer_join(&[1, 2, 2, 3], &[], &[(&1, 1, 0), (&2, 2, 0), (&3, 1, 0)]); } #[test] fn test_outer_join_with_overlap() { do_test_outer_join( &[1, 2, 2, 3, 3], &[3, 4, 5, 5], &[(&1, 1, 0), (&2, 2, 0), (&3, 2, 1), (&4, 0, 1), (&5, 0, 2)], ); } fn do_test_outer_join( this: &[usize], other: &[usize], expected_entries: &[(&usize, usize, usize)], ) { let this_hashbag: HashBag<_> = this.iter().cloned().collect(); let other_hashbag: HashBag<_> = other.iter().cloned().collect(); let expected: HashSet<_> = HashSet::from_iter(expected_entries.iter().cloned()); let actual: HashSet<_> = this_hashbag.outer_join(&other_hashbag).collect(); assert_eq!(expected, actual); } #[test] fn test_not_in_with_empty_self() { do_test_not_in(&[], &[1, 2, 3, 3], &[]); } #[test] fn test_not_in_with_empty_other() { do_test_not_in(&[1, 2, 3, 3], &[], &[1, 2, 3, 3]); } #[test] fn test_not_in_with_overlap() { do_test_not_in(&[1, 2, 3, 3], &[2, 4], &[1, 3, 3]); } fn do_test_not_in(this: &[usize], other: &[usize], expected_entries: &[usize]) { let this_hashbag: HashBag<_> = this.iter().cloned().collect(); let other_hashbag: HashBag<_> = other.iter().cloned().collect(); let expected: HashBag<_> = expected_entries.iter().cloned().collect(); let actual: HashBag<_> = this_hashbag .not_in(&other_hashbag) .fold(HashBag::new(), |mut bag, (k, count)| { bag.insert_many(*k, count); bag }); assert_eq!(expected, actual); } #[test] fn test_signed_difference_with_empty_self() { do_test_signed_difference(&[], &[1, 2, 2, 3], &[(&1, -1), (&2, -2), (&3, -1)]); } #[test] fn test_signed_difference_with_empty_other() { do_test_signed_difference(&[1, 2, 2, 3], &[], &[(&1, 1), (&2, 2), (&3, 1)]); } #[test] fn test_signed_difference_with_overlap() { do_test_signed_difference( &[1, 2, 2, 3, 3], &[3, 4, 5, 5], &[(&1, 1), (&2, 2), (&3, 1), (&4, -1), (&5, -2)], ); } #[test] fn test_signed_difference_with_both_large() { let mut this_hashbag = HashBag::new(); let mut other_hashbag = HashBag::new(); let large_count = std::isize::MAX as usize; this_hashbag.insert_many(1, large_count + 1000); other_hashbag.insert_many(1, large_count); let expected: HashSet<_> = HashSet::from_iter([(&1, 1000)].iter().cloned()); let actual: HashSet<_> = this_hashbag.signed_difference(&other_hashbag).collect(); assert_eq!(expected, actual); // and in reverse: let expected: HashSet<_> = HashSet::from_iter([(&1, -1000)].iter().cloned()); let actual: HashSet<_> = other_hashbag.signed_difference(&this_hashbag).collect(); assert_eq!(expected, actual); } #[test] fn test_signed_difference_too_large_to_hold_clamp() { let mut this_hashbag = HashBag::new(); let empty_hashbag = HashBag::new(); let large_count = std::isize::MAX as usize; this_hashbag.insert_many(1, large_count + 1000); let expected: HashSet<_> = HashSet::from_iter([(&1, std::isize::MAX)].iter().cloned()); let actual: HashSet<_> = this_hashbag.signed_difference(&empty_hashbag).collect(); assert_eq!(expected, actual); // and in reverse: let expected: HashSet<_> = HashSet::from_iter([(&1, std::isize::MIN)].iter().cloned()); let actual: HashSet<_> = empty_hashbag.signed_difference(&this_hashbag).collect(); assert_eq!(expected, actual); } fn do_test_signed_difference( this: &[usize], other: &[usize], expected_entries: &[(&usize, isize)], ) { let this_hashbag: HashBag<_> = this.iter().cloned().collect(); let other_hashbag: HashBag<_> = other.iter().cloned().collect(); let expected: HashSet<_> = HashSet::from_iter(expected_entries.iter().cloned()); let actual: HashSet<_> = this_hashbag.signed_difference(&other_hashbag).collect(); assert_eq!(expected, actual); } #[test] fn test_no_zeros_counts() { let mut hashbag = HashBag::new(); hashbag.insert(100); hashbag.retain(|_, _| 0); hashbag.insert_many(1, 0); hashbag.extend(vec![(2, 0)]); assert_eq!(hashbag.len(), 0); assert_eq!(hashbag.iter().count(), 0); assert_eq!(hashbag.set_len(), 0); assert_eq!(hashbag.set_iter().count(), 0); } #[test] fn remove_up_to_affects_count() { let mut bag = HashBag::new(); bag.insert_many(42, 3); assert_eq!(bag.len(), 3); assert_eq!(bag.remove_up_to(&0, 1), 0); assert_eq!(bag.len(), 3); assert_eq!(bag.remove_up_to(&42, 1), 2); assert_eq!(bag.len(), 2); assert_eq!(bag.remove_up_to(&42, 10), 0); assert_eq!(bag.len(), 0); } #[test] fn entry_inserts_values() { let mut bag = HashBag::new(); bag.entry(42); assert_eq!(bag.contains(&42), 0); bag.entry(84).or_insert_many(3); assert_eq!(bag.contains(&84), 3); assert_eq!(bag.len(), 3); bag.entry(84).or_insert_many(2); assert_eq!(bag.len(), 3); bag.entry(84).or_insert(); assert_eq!(bag.len(), 3); } #[test] fn entry_affects_count() { let mut bag = HashBag::new(); bag.entry(42); assert_eq!(bag.len(), 0); bag.entry(42).and_modify(|n| *n += 3); assert_eq!(bag.len(), 0); bag.entry(42).or_insert_many(3); assert_eq!(bag.len(), 3); bag.entry(42).and_modify(|n| *n += 3); assert_eq!(bag.len(), 6); bag.entry(84).or_insert(); assert_eq!(bag.len(), 7); } } hashbag-0.1.12/src/serde.rs000066400000000000000000000155411456412436000154420ustar00rootroot00000000000000use crate::HashBag; use core::fmt; use core::hash::{BuildHasher, Hash}; use core::marker::PhantomData; use serde::de::{SeqAccess, Visitor}; use serde::ser::{SerializeSeq, Serializer}; use serde::Deserializer; use serde::{Deserialize, Serialize}; pub(crate) struct HashBagVisitor { marker: PhantomData HashBag>, } impl HashBagVisitor where T: Eq + Hash, S: BuildHasher + Clone, { fn new() -> Self { HashBagVisitor { marker: PhantomData, } } } impl<'de, T, S> Visitor<'de> for HashBagVisitor where T: Deserialize<'de> + Eq + Hash, S: BuildHasher + Clone + Default, { type Value = HashBag; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a HashBag") } fn visit_seq(self, mut access: M) -> Result where M: SeqAccess<'de>, { let mut bag: HashBag = HashBag::with_capacity_and_hasher(access.size_hint().unwrap_or(0), Default::default()); while let Some(entry) = access.next_element::<(T, usize)>()? { bag.insert_many(entry.0, entry.1); } Ok(bag) } } impl<'de, T, S> Deserialize<'de> for HashBag where T: Deserialize<'de> + Eq + Hash, S: BuildHasher + Clone + Default, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_seq(HashBagVisitor::::new()) } } impl Serialize for HashBag where T: Serialize + Eq + Hash, H: BuildHasher + Clone, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut bag = serializer.serialize_seq(Some(self.set_len()))?; for (entry, count) in self.set_iter() { bag.serialize_element(&(entry, count))?; } bag.end() } } #[cfg(test)] mod tests { use super::*; use serde_json; #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] struct VeryHelpfulStruct { pub(crate) name: String, } #[test] fn format_simple_data() { let vikings: HashBag = ["Einar", "Olaf", "Olaf", "Harald", "Harald", "Harald"] .iter() .map(|s| s.to_string()) .collect(); let einar = "Einar".to_string(); let olaf = "Olaf".to_string(); let harald = "Harald".to_string(); assert_eq!(vikings.get(&einar), Some((&einar, 1))); assert_eq!(vikings.get(&olaf), Some((&olaf, 2))); assert_eq!(vikings.get(&harald), Some((&harald, 3))); println!("Constructed: {:?}", vikings); let jsonified_vikings: String = serde_json::to_string(&vikings).expect("Unable to convert data to json!"); println!("JSON: {}", jsonified_vikings); let reconstituted_vikings: HashBag = serde_json::from_str(&jsonified_vikings).expect("Unable to convert json to hashbag!"); println!("From json: {:?}", reconstituted_vikings); assert_eq!(vikings, reconstituted_vikings); } #[test] fn format_struct_data() { let vikings: HashBag = ["Einar", "Olaf", "Olaf", "Harald", "Harald", "Harald"] .iter() .map(|n| VeryHelpfulStruct { name: n.to_string(), }) .collect(); let einar = VeryHelpfulStruct { name: "Einar".to_string(), }; let olaf = VeryHelpfulStruct { name: "Olaf".to_string(), }; let harald = VeryHelpfulStruct { name: "Harald".to_string(), }; assert_eq!(vikings.get(&einar), Some((&einar, 1))); assert_eq!(vikings.get(&olaf), Some((&olaf, 2))); assert_eq!(vikings.get(&harald), Some((&harald, 3))); println!("Constructed: {:?}", vikings); let jsonified_vikings: String = serde_json::to_string(&vikings).expect("Unable to convert data to json!"); println!("JSON: {}", jsonified_vikings); let reconstituted_vikings: HashBag = serde_json::from_str(&jsonified_vikings).expect("Unable to convert json to hashbag!"); println!("From json: {:?}", reconstituted_vikings); assert_eq!(vikings, reconstituted_vikings); } #[test] fn repeat_simple_entries() { let jsonified_vikings: String = "[[\"Einar\",1],[\"Olaf\",1],[\"Olaf\",1],[\"Harald\",2],[\"Harald\",1]]".to_string(); let reconstituted_vikings: HashBag = serde_json::from_str(&jsonified_vikings).expect("Unable to convert json to hashbag!"); let einar = "Einar".to_string(); let olaf = "Olaf".to_string(); let harald = "Harald".to_string(); assert_eq!(reconstituted_vikings.get(&einar), Some((&einar, 1))); assert_eq!(reconstituted_vikings.get(&olaf), Some((&olaf, 2))); assert_eq!(reconstituted_vikings.get(&harald), Some((&harald, 3))); } #[test] fn repeat_struct_entries() { let jsonified_vikings: String = "[[{\"name\":\"Einar\"},1],[{\"name\":\"Olaf\"},1],[{\"name\":\"Olaf\"},1],[{\"name\":\"Harald\"},2],[{\"name\":\"Harald\"},1]]".to_string(); let reconstituted_vikings: HashBag = serde_json::from_str(&jsonified_vikings).expect("Unable to convert json to hashbag!"); let einar = VeryHelpfulStruct { name: "Einar".to_string(), }; let olaf = VeryHelpfulStruct { name: "Olaf".to_string(), }; let harald = VeryHelpfulStruct { name: "Harald".to_string(), }; assert_eq!(reconstituted_vikings.get(&einar), Some((&einar, 1))); assert_eq!(reconstituted_vikings.get(&olaf), Some((&olaf, 2))); assert_eq!(reconstituted_vikings.get(&harald), Some((&harald, 3))); } #[test] fn serde_bincode() { let vikings: HashBag = ["Einar", "Olaf", "Olaf", "Harald", "Harald", "Harald"] .iter() .map(|s| s.to_string()) .collect(); let einar = "Einar".to_string(); let olaf = "Olaf".to_string(); let harald = "Harald".to_string(); assert_eq!(vikings.get(&einar), Some((&einar, 1))); assert_eq!(vikings.get(&olaf), Some((&olaf, 2))); assert_eq!(vikings.get(&harald), Some((&harald, 3))); println!("Constructed: {:?}", vikings); let bincoded_vikings = bincode::serialize(&vikings).expect("Unable to serialize to bincode!"); let reconstituted_vikings: HashBag = bincode::deserialize(&bincoded_vikings).expect("Unable to deserialize bincode!"); println!("From bincode: {:?}", reconstituted_vikings); assert_eq!(vikings, reconstituted_vikings); } }