From a39cfd540281057fdd9937c913940e7d09a49b37 Mon Sep 17 00:00:00 2001 From: Martin Taillefer Date: Mon, 17 Jun 2024 19:34:00 -0700 Subject: [PATCH] Frozen collections --- .gitattributes | 1 + .github/dependabot.yml | 11 + .github/workflows/main.yml | 57 + .gitignore | 3 + Cargo.toml | 46 + LICENSE | 25 + README.md | 240 +++ TODO.md | 43 + bench.ps1 | 2 + benches/Cargo.toml | 29 + benches/int_keys.rs | 214 +++ benches/misc_keys.rs | 175 +++ benches/string_keys.rs | 351 +++++ codegen/Cargo.toml | 29 + codegen/README.md | 5 + codegen/int_keys.rs | 96 ++ codegen/misc_keys.rs | 82 ++ codegen/string_keys_length.rs | 56 + codegen/string_keys_subslice.rs | 51 + criterion.toml | 1 + frozen-collections-core/Cargo.toml | 34 + frozen-collections-core/README.md | 6 + .../src/analyzers/hash_code_analyzer.rs | 190 +++ frozen-collections-core/src/analyzers/mod.rs | 9 + .../src/analyzers/scalar_key_analyzer.rs | 86 ++ .../src/analyzers/slice_key_analyzer.rs | 285 ++++ .../src/doc_snippets/about.md | 8 + .../src/doc_snippets/contains_key_method.md | 1 + .../src/doc_snippets/contains_method.md | 1 + .../src/doc_snippets/get_from_set_method.md | 1 + .../src/doc_snippets/get_key_value_method.md | 1 + .../src/doc_snippets/get_many_mut_method.md | 5 + .../src/doc_snippets/get_method.md | 1 + .../src/doc_snippets/get_mut_method.md | 1 + .../src/doc_snippets/hash_warning.md | 21 + .../src/doc_snippets/order_warning.md | 12 + .../src/doc_snippets/type_compat_warning.md | 6 + .../src/facade_maps/facade_hash_map.rs | 349 +++++ .../src/facade_maps/facade_ordered_map.rs | 330 +++++ .../src/facade_maps/facade_scalar_map.rs | 349 +++++ .../src/facade_maps/facade_string_map.rs | 337 +++++ .../src/facade_maps/mod.rs | 11 + .../src/facade_sets/facade_hash_set.rs | 169 +++ .../src/facade_sets/facade_ordered_set.rs | 131 ++ .../src/facade_sets/facade_scalar_set.rs | 125 ++ .../src/facade_sets/facade_string_set.rs | 162 ++ .../src/facade_sets/mod.rs | 11 + .../src/hash_tables/hash_table.rs | 186 +++ .../src/hash_tables/hash_table_slot.rs | 21 + .../src/hash_tables/inline_hash_table.rs | 107 ++ .../src/hash_tables/mod.rs | 9 + .../src/hashers/bridge_hasher.rs | 37 + .../src/hashers/inline_left_range_hasher.rs | 117 ++ .../src/hashers/inline_right_range_hasher.rs | 124 ++ .../src/hashers/left_range_hasher.rs | 127 ++ .../src/hashers/mixing_hasher.rs | 67 + frozen-collections-core/src/hashers/mod.rs | 16 + .../src/hashers/passthrough_hasher.rs | 92 ++ .../src/hashers/right_range_hasher.rs | 131 ++ .../inline_maps/inline_binary_search_map.rs | 174 +++ .../inline_dense_scalar_lookup_map.rs | 141 ++ .../src/inline_maps/inline_hash_map.rs | 181 +++ .../inline_maps/inline_ordered_scan_map.rs | 166 +++ .../src/inline_maps/inline_scan_map.rs | 168 +++ .../inline_sparse_scalar_lookup_map.rs | 185 +++ .../src/inline_maps/mod.rs | 15 + .../inline_sets/inline_binary_search_set.rs | 122 ++ .../inline_dense_scalar_lookup_set.rs | 119 ++ .../src/inline_sets/inline_hash_set.rs | 194 +++ .../inline_sets/inline_ordered_scan_set.rs | 118 ++ .../src/inline_sets/inline_scan_set.rs | 118 ++ .../inline_sparse_scalar_lookup_set.rs | 153 ++ .../src/inline_sets/mod.rs | 15 + frozen-collections-core/src/lib.rs | 26 + .../src/macros/derive_scalar_macro.rs | 141 ++ .../src/macros/generator.rs | 830 +++++++++++ .../src/macros/hash_table.rs | 14 + .../src/macros/map_macros.rs | 114 ++ frozen-collections-core/src/macros/mod.rs | 12 + .../src/macros/parsing/entry.rs | 35 + .../src/macros/parsing/long_form_map.rs | 51 + .../src/macros/parsing/long_form_set.rs | 60 + .../src/macros/parsing/map.rs | 43 + .../src/macros/parsing/mod.rs | 8 + .../src/macros/parsing/payload.rs | 59 + .../src/macros/parsing/set.rs | 43 + .../src/macros/parsing/short_form_map.rs | 14 + .../src/macros/parsing/short_form_set.rs | 14 + .../src/macros/set_macros.rs | 148 ++ .../src/maps/binary_search_map.rs | 149 ++ .../src/maps/decl_macros.rs | 588 ++++++++ .../src/maps/dense_scalar_lookup_map.rs | 188 +++ frozen-collections-core/src/maps/hash_map.rs | 211 +++ frozen-collections-core/src/maps/iterators.rs | 828 +++++++++++ frozen-collections-core/src/maps/mod.rs | 18 + .../src/maps/ordered_scan_map.rs | 149 ++ frozen-collections-core/src/maps/scan_map.rs | 140 ++ .../src/maps/sparse_scalar_lookup_map.rs | 227 +++ .../src/sets/binary_search_set.rs | 126 ++ .../src/sets/decl_macros.rs | 140 ++ .../src/sets/dense_scalar_lookup_set.rs | 132 ++ frozen-collections-core/src/sets/hash_set.rs | 183 +++ frozen-collections-core/src/sets/iterators.rs | 763 ++++++++++ frozen-collections-core/src/sets/mod.rs | 18 + .../src/sets/ordered_scan_set.rs | 125 ++ frozen-collections-core/src/sets/scan_set.rs | 124 ++ .../src/sets/sparse_scalar_lookup_set.rs | 155 ++ .../src/traits/collection_magnitude.rs | 35 + frozen-collections-core/src/traits/hasher.rs | 13 + frozen-collections-core/src/traits/len.rs | 400 +++++ frozen-collections-core/src/traits/map.rs | 159 ++ .../src/traits/map_iterator.rs | 276 ++++ frozen-collections-core/src/traits/mod.rs | 21 + frozen-collections-core/src/traits/scalar.rs | 193 +++ frozen-collections-core/src/traits/set.rs | 141 ++ .../src/traits/set_iterator.rs | 60 + frozen-collections-core/src/utils/dedup.rs | 259 ++++ frozen-collections-core/src/utils/mod.rs | 7 + frozen-collections-core/src/utils/random.rs | 14 + frozen-collections-macros/Cargo.toml | 26 + frozen-collections-macros/README.md | 6 + frozen-collections-macros/src/lib.rs | 84 ++ frozen-collections/Cargo.toml | 34 + frozen-collections/src/lib.rs | 1297 +++++++++++++++++ .../tests/common/map_testing.rs | 198 +++ frozen-collections/tests/common/mod.rs | 5 + .../tests/common/set_testing.rs | 156 ++ .../tests/derive_scalar_macro_tests.rs | 26 + frozen-collections/tests/hash_macro_tests.rs | 484 ++++++ frozen-collections/tests/omni_tests.rs | 404 +++++ .../tests/ordered_macro_tests.rs | 448 ++++++ .../tests/scalar_macro_tests.rs | 345 +++++ .../tests/string_macro_tests.rs | 216 +++ tables.toml | 42 + 134 files changed, 18287 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 TODO.md create mode 100644 bench.ps1 create mode 100644 benches/Cargo.toml create mode 100644 benches/int_keys.rs create mode 100644 benches/misc_keys.rs create mode 100644 benches/string_keys.rs create mode 100644 codegen/Cargo.toml create mode 100644 codegen/README.md create mode 100644 codegen/int_keys.rs create mode 100644 codegen/misc_keys.rs create mode 100644 codegen/string_keys_length.rs create mode 100644 codegen/string_keys_subslice.rs create mode 100644 criterion.toml create mode 100644 frozen-collections-core/Cargo.toml create mode 100644 frozen-collections-core/README.md create mode 100644 frozen-collections-core/src/analyzers/hash_code_analyzer.rs create mode 100644 frozen-collections-core/src/analyzers/mod.rs create mode 100644 frozen-collections-core/src/analyzers/scalar_key_analyzer.rs create mode 100644 frozen-collections-core/src/analyzers/slice_key_analyzer.rs create mode 100644 frozen-collections-core/src/doc_snippets/about.md create mode 100644 frozen-collections-core/src/doc_snippets/contains_key_method.md create mode 100644 frozen-collections-core/src/doc_snippets/contains_method.md create mode 100644 frozen-collections-core/src/doc_snippets/get_from_set_method.md create mode 100644 frozen-collections-core/src/doc_snippets/get_key_value_method.md create mode 100644 frozen-collections-core/src/doc_snippets/get_many_mut_method.md create mode 100644 frozen-collections-core/src/doc_snippets/get_method.md create mode 100644 frozen-collections-core/src/doc_snippets/get_mut_method.md create mode 100644 frozen-collections-core/src/doc_snippets/hash_warning.md create mode 100644 frozen-collections-core/src/doc_snippets/order_warning.md create mode 100644 frozen-collections-core/src/doc_snippets/type_compat_warning.md create mode 100644 frozen-collections-core/src/facade_maps/facade_hash_map.rs create mode 100644 frozen-collections-core/src/facade_maps/facade_ordered_map.rs create mode 100644 frozen-collections-core/src/facade_maps/facade_scalar_map.rs create mode 100644 frozen-collections-core/src/facade_maps/facade_string_map.rs create mode 100644 frozen-collections-core/src/facade_maps/mod.rs create mode 100644 frozen-collections-core/src/facade_sets/facade_hash_set.rs create mode 100644 frozen-collections-core/src/facade_sets/facade_ordered_set.rs create mode 100644 frozen-collections-core/src/facade_sets/facade_scalar_set.rs create mode 100644 frozen-collections-core/src/facade_sets/facade_string_set.rs create mode 100644 frozen-collections-core/src/facade_sets/mod.rs create mode 100644 frozen-collections-core/src/hash_tables/hash_table.rs create mode 100644 frozen-collections-core/src/hash_tables/hash_table_slot.rs create mode 100644 frozen-collections-core/src/hash_tables/inline_hash_table.rs create mode 100644 frozen-collections-core/src/hash_tables/mod.rs create mode 100644 frozen-collections-core/src/hashers/bridge_hasher.rs create mode 100644 frozen-collections-core/src/hashers/inline_left_range_hasher.rs create mode 100644 frozen-collections-core/src/hashers/inline_right_range_hasher.rs create mode 100644 frozen-collections-core/src/hashers/left_range_hasher.rs create mode 100644 frozen-collections-core/src/hashers/mixing_hasher.rs create mode 100644 frozen-collections-core/src/hashers/mod.rs create mode 100644 frozen-collections-core/src/hashers/passthrough_hasher.rs create mode 100644 frozen-collections-core/src/hashers/right_range_hasher.rs create mode 100644 frozen-collections-core/src/inline_maps/inline_binary_search_map.rs create mode 100644 frozen-collections-core/src/inline_maps/inline_dense_scalar_lookup_map.rs create mode 100644 frozen-collections-core/src/inline_maps/inline_hash_map.rs create mode 100644 frozen-collections-core/src/inline_maps/inline_ordered_scan_map.rs create mode 100644 frozen-collections-core/src/inline_maps/inline_scan_map.rs create mode 100644 frozen-collections-core/src/inline_maps/inline_sparse_scalar_lookup_map.rs create mode 100644 frozen-collections-core/src/inline_maps/mod.rs create mode 100644 frozen-collections-core/src/inline_sets/inline_binary_search_set.rs create mode 100644 frozen-collections-core/src/inline_sets/inline_dense_scalar_lookup_set.rs create mode 100644 frozen-collections-core/src/inline_sets/inline_hash_set.rs create mode 100644 frozen-collections-core/src/inline_sets/inline_ordered_scan_set.rs create mode 100644 frozen-collections-core/src/inline_sets/inline_scan_set.rs create mode 100644 frozen-collections-core/src/inline_sets/inline_sparse_scalar_lookup_set.rs create mode 100644 frozen-collections-core/src/inline_sets/mod.rs create mode 100644 frozen-collections-core/src/lib.rs create mode 100644 frozen-collections-core/src/macros/derive_scalar_macro.rs create mode 100644 frozen-collections-core/src/macros/generator.rs create mode 100644 frozen-collections-core/src/macros/hash_table.rs create mode 100644 frozen-collections-core/src/macros/map_macros.rs create mode 100644 frozen-collections-core/src/macros/mod.rs create mode 100644 frozen-collections-core/src/macros/parsing/entry.rs create mode 100644 frozen-collections-core/src/macros/parsing/long_form_map.rs create mode 100644 frozen-collections-core/src/macros/parsing/long_form_set.rs create mode 100644 frozen-collections-core/src/macros/parsing/map.rs create mode 100644 frozen-collections-core/src/macros/parsing/mod.rs create mode 100644 frozen-collections-core/src/macros/parsing/payload.rs create mode 100644 frozen-collections-core/src/macros/parsing/set.rs create mode 100644 frozen-collections-core/src/macros/parsing/short_form_map.rs create mode 100644 frozen-collections-core/src/macros/parsing/short_form_set.rs create mode 100644 frozen-collections-core/src/macros/set_macros.rs create mode 100644 frozen-collections-core/src/maps/binary_search_map.rs create mode 100644 frozen-collections-core/src/maps/decl_macros.rs create mode 100644 frozen-collections-core/src/maps/dense_scalar_lookup_map.rs create mode 100644 frozen-collections-core/src/maps/hash_map.rs create mode 100644 frozen-collections-core/src/maps/iterators.rs create mode 100644 frozen-collections-core/src/maps/mod.rs create mode 100644 frozen-collections-core/src/maps/ordered_scan_map.rs create mode 100644 frozen-collections-core/src/maps/scan_map.rs create mode 100644 frozen-collections-core/src/maps/sparse_scalar_lookup_map.rs create mode 100644 frozen-collections-core/src/sets/binary_search_set.rs create mode 100644 frozen-collections-core/src/sets/decl_macros.rs create mode 100644 frozen-collections-core/src/sets/dense_scalar_lookup_set.rs create mode 100644 frozen-collections-core/src/sets/hash_set.rs create mode 100644 frozen-collections-core/src/sets/iterators.rs create mode 100644 frozen-collections-core/src/sets/mod.rs create mode 100644 frozen-collections-core/src/sets/ordered_scan_set.rs create mode 100644 frozen-collections-core/src/sets/scan_set.rs create mode 100644 frozen-collections-core/src/sets/sparse_scalar_lookup_set.rs create mode 100644 frozen-collections-core/src/traits/collection_magnitude.rs create mode 100644 frozen-collections-core/src/traits/hasher.rs create mode 100644 frozen-collections-core/src/traits/len.rs create mode 100644 frozen-collections-core/src/traits/map.rs create mode 100644 frozen-collections-core/src/traits/map_iterator.rs create mode 100644 frozen-collections-core/src/traits/mod.rs create mode 100644 frozen-collections-core/src/traits/scalar.rs create mode 100644 frozen-collections-core/src/traits/set.rs create mode 100644 frozen-collections-core/src/traits/set_iterator.rs create mode 100644 frozen-collections-core/src/utils/dedup.rs create mode 100644 frozen-collections-core/src/utils/mod.rs create mode 100644 frozen-collections-core/src/utils/random.rs create mode 100644 frozen-collections-macros/Cargo.toml create mode 100644 frozen-collections-macros/README.md create mode 100644 frozen-collections-macros/src/lib.rs create mode 100644 frozen-collections/Cargo.toml create mode 100644 frozen-collections/src/lib.rs create mode 100644 frozen-collections/tests/common/map_testing.rs create mode 100644 frozen-collections/tests/common/mod.rs create mode 100644 frozen-collections/tests/common/set_testing.rs create mode 100644 frozen-collections/tests/derive_scalar_macro_tests.rs create mode 100644 frozen-collections/tests/hash_macro_tests.rs create mode 100644 frozen-collections/tests/omni_tests.rs create mode 100644 frozen-collections/tests/ordered_macro_tests.rs create mode 100644 frozen-collections/tests/scalar_macro_tests.rs create mode 100644 frozen-collections/tests/string_macro_tests.rs create mode 100644 tables.toml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4c9e1ff --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +text eol=crlf diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..22b1e8d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 10 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..de534b7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,57 @@ +name: main + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - 1.82.0 + - beta + - nightly + + steps: + - uses: actions/checkout@v4 + - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - run: rustup component add clippy + - run: rustup component add rustfmt + - name: Build + run: cargo build --verbose --all-targets + - name: Check + run: cargo check --verbose --all-targets + - name: Clippy + run: cargo clippy --verbose --all-targets + - name: Format + run: cargo fmt -- --check + - name: Tests + run: cargo test --verbose + - name: Doc Tests + run: cargo test --doc --verbose + + coverage: + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: rustup update stable + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + - name: Generate code coverage + run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos + files: lcov.info + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8eb581d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +/.idea diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a0ba000 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,46 @@ +[workspace] +resolver = "2" +members = [ + "frozen-collections", + "frozen-collections-core", + "frozen-collections-macros", + "benches", + "codegen", +] + +[workspace.package] +version = "0.1.0" +edition = "2021" +categories = ["data-structures", "no-std", "collections"] +keywords = ["map", "set", "collection"] +repository = "https://github.com/geeknoid/frozen-collections" +license = "MIT" +readme = "README.md" +authors = ["Martin Taillefer "] +rust-version = "1.82.0" + +[workspace.lints.clippy] +pedantic = { level = "warn", priority = -1 } +correctness = { level = "warn", priority = -1 } +complexity = { level = "warn", priority = -1 } +perf = { level = "warn", priority = -1 } +cargo = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } +wildcard_imports = "allow" +too_many_lines = "allow" +multiple_crate_versions = "allow" +from-iter-instead-of-collect = "allow" +into_iter_without_iter = "allow" +inline_always = "allow" +unnecessary_wraps = "allow" +cognitive_complexity = "allow" + +[profile.bench] +codegen-units = 1 +lto = "fat" + +[profile.release] # Modify profile settings via config. +codegen-units = 1 +lto = "fat" +debug = true # Include debug info. +strip = "none" # Removes symbols or debuginfo. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b5df8da --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2024 Martin Taillefer + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1274eaa --- /dev/null +++ b/README.md @@ -0,0 +1,240 @@ +# Frozen Collections - Fast Partially Immutable Collections + +[![crate.io](https://img.shields.io/crates/v/frozen-collections.svg)](https://crates.io/crates/frozen-collections) +[![docs.rs](https://docs.rs/frozen-collections/badge.svg)](https://docs.rs/frozen-collections) +[![CI](https://github.com/geeknoid/frozen-collections/workflows/main/badge.svg)](https://github.com/geeknoid/frozen-collections/actions) +[![Coverage](https://codecov.io/gh/geeknoid/frozen-collections/graph/badge.svg?token=FCUG0EL5TI)](https://codecov.io/gh/geeknoid/frozen-collections) +[![Minimum Supported Rust Version 1.82](https://img.shields.io/badge/MSRV-1.82-blue.svg)]() +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) + +* [Summary](#summary) +* [Creation](#creation) + * [Short Form](#short-form) + * [Long Form](#long-form) +* [Partially Immutable](#partially-immutable) +* [Performance Considerations](#performance-considerations) +* [Analysis and Optimizations](#analysis-and-optimizations) +* [Cargo Features](#cargo-features) + +## Summary + +Frozen collections are designed to deliver improved +read performance relative to the standard [`HashMap`](https://doc.rust-lang.org/std/collections/hash/map/struct.HashMap.html) and +[`HashSet`](https://doc.rust-lang.org/std/collections/hash/set/struct.HashSet.html) types. They are ideal for use with long-lasting collections +which get initialized when an application starts and remain unchanged +permanently, or at least extended periods of time. This is a common +pattern in service applications. + +As part of creating a frozen collection, analysis is performed over the data that the collection +will hold to determine the best layout and algorithm to use to deliver optimal performance. +Depending on the situation, sometimes the analysis is done at compile-time whereas in +other cases it is done at runtime when the collection is initialized. +This analysis can take some time, but the value in spending this time up front +is that the collections provide faster read-time performance. + +## Creation + +Frozen collections are created with one of eight macros: +[`fz_hash_map!`](https://docs.rs/frozen-collections/macro.fz_hash_map.html), +[`fz_ordered_map!`](https://docs.rs/frozen-collections/macro.fz_ordered_map.html), +[`fz_scalar_map!`](https://docs.rs/frozen-collections/macro.fz_scalar_map.html), +[`fz_string_map!`](https://docs.rs/frozen-collections/macro.fz_string_map.html), +[`fz_hash_set!`](https://docs.rs/frozen-collections/macro.fz_hash_set.html), +[`fz_ordered_set!`](https://docs.rs/frozen-collections/macro.fz_ordered_set.html), +[`fz_scalar_set!`](https://docs.rs/frozen-collections/macro.fz_scalar_set.html), or +[`fz_string_set!`](https://docs.rs/frozen-collections/macro.fz_string_set.html). +These macros analyze the data you provide +and return a custom implementation type that's optimized for the data. All the +possible implementations implement the [`Map`] or [`Set`] traits. + +The macros exist in a short form and a long form, described below. + +### Short Form + +With the short form, you supply the data that +goes into the collection and get in return an initialized collection of an unnamed +type. For example: + +```rust +use frozen_collections::*; + +let m = fz_string_map!({ + "Alice": 1, + "Bob": 2, + "Sandy": 3, + "Tom": 4, +}); +``` + +At build time, the macro analyzes the data supplied and determines the best map +implementation type to use. As such, the type of `m` is not known to this code. `m` will +always implement the [`Map`] trait however, so you can leverage type inference even though +you don't know the actual type of `m`: + +```rust +use frozen_collections::*; + +fn main() { + let m = fz_string_map!({ + "Alice": 1, + "Bob": 2, + "Sandy": 3, + "Tom": 4, + }); + + more(m); +} + +fn more(m: M) +where + M: Map<&'static str, i32> +{ + assert!(m.contains_key(&"Alice")); +} +``` + +Rather than specifying all the data inline, you can also create a frozen collection by passing +a vector as input: + +```rust +use frozen_collections::*; + +let v = vec![ + ("Alice".to_string(), 1), + ("Bob".to_string(), 2), + ("Sandy".to_string(), 3), + ("Tom".to_string(), 4), +]; + +let m = fz_string_map!(v); +``` + +The inline form is preferred however since it results in faster code. However, whereas the inline form +requires all the data to be provided at compile time, the vector form enables the content of the +frozen collection to tbe determined at runtime. + +### Long Form + +The long form lets you provide a type alias name which will be created to +correspond to the collection implementation type chosen by the macro invocation. +Note that you must use the long form if you want to declare a static frozen collection. + +```rust +use frozen_collections::*; + +fz_string_map!(static MAP: MyMapType<&str, i32> = { + "Alice": 1, + "Bob": 2, + "Sandy": 3, + "Tom": 4, +}); +``` + +The above creates a static variable called `MAP` with keys that are strings and values which are +integers. As before, you don't know the specific implementation type selected by the macro, but +this time you have a type alias (i.e. `MyMapType`) representing that type. You can then use this alias +anywhere you'd like to in your code where you'd like to mention the type explicitly. + +To use the long form for non-static uses, replace `static` with `let`: + +```rust +use frozen_collections::*; + +fz_string_map!(let m: MyMapType<&str, i32> = { + "Alice": 1, + "Bob": 2, + "Sandy": 3, + "Tom": 4, +}); + +more(m); + +struct S { + m: MyMapType, +} + +fn more(m: MyMapType) { + assert!(m.contains_key("Alice")); +} +``` + +And like in the short form, you can also supply the collection's data via a vector: + +```rust +use frozen_collections::*; + +let v = vec![ + ("Alice".to_string(), 1), + ("Bob".to_string(), 2), + ("Sandy".to_string(), 3), + ("Tom".to_string(), 4), +]; + +fz_string_map!(let m: MyMapType<&str, i32> = v); +``` + +## Partially Immutable + +Frozen maps are only partially immutable. The keys associated with a map are determined +at creation time and cannot change, but the values can be updated at will if you have a +mutable reference to the map. + +Frozen sets however are completely immutable and so never change after creation. + +## Performance Considerations + +The analysis performed when creating maps tries to find the best concrete implementation type +given the data at hand. If all the data is visible to the macro at compile time, then you get +the best possible performance. If you supply a vector instead, then the analysis can only be +done at runtime and the resulting collection types are a bit slower. + +When creating static collections, the collections produced can often be embedded directly as constant data +into the binary of the application, thus require not initialization time and no heap space at +runtime. This also happens to be the fastest form for these collections. If possible, this happens +automatically, you don't need to do anything special to enable this behavior. + +## Analysis and Optimizations + +Unlike normal collections, the frozen collections require you to provide all the data for +the collection when you create the collection. The data you supply is analyzed which determines +which specific underlying implementation strategy to use and how to lay out data internally. + +The available implementation strategies are: + +- **Scalar as Hash**. When the keys are of an integer or enum type, this uses the keys themselves + as hash codes, avoiding the overhead of hashing. + +- **Dense Scalar Lookup**. When the keys represent a contiguous range of integer or enum values, + lookups use a simple array access instead of hashing. + +- **Sparse Scalar Lookup**. When the keys represent a sparse range of integer or enum values, + lookups use a sparse array access instead of hashing. + +- **Length as Hash**. When the keys are of a slice type, the length of the slices + are used as hash code, avoiding the overhead of hashing. + +- **Left Hand Substring Hashing**. When the keys are of a slice type, this uses sub-slices of + the keys for hashing, reducing the overhead of hashing. + +- **Right Hand Substring Hashing**. Similar to the Left Hand Substring Hashing from above, but + using right-aligned sub-slices instead. + +- **Linear Scan**. For very small collections, this avoids hashing completely by scanning through the + entries in linear order. + +- **Ordered Scan**. For very small collections where the keys implement the [`Ord`] trait, + this avoids hashing completely by scanning through the entries in linear order. + +- **Classic Hashing**. This is the fallback when none of the previous strategies apply. The + frozen implementations are generally faster than + [`HashMap`](https://doc.rust-lang.org/std/collections/hash/map/struct.HashMap.html) and + [`HashSet`](https://doc.rust-lang.org/std/collections/hash/set/struct.HashSet.html). + +## Cargo Features + +You can specify the following features when you include the `frozen_collections` crate in your +`Cargo.toml` file: + +- **`std`**. Enables small features only available when building with the standard library. + +The `std` feature is enabled by default. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..1c99dc3 --- /dev/null +++ b/TODO.md @@ -0,0 +1,43 @@ +# TODO + +## Engineering Excellence + +- Get code coverage to 100% + +- Generate BENCHMARK.md + +- Remove dependency on the bitvec crate since it seems to now be unsupported. + +## Performance + +- Look at https://lib.rs/crates/small-map for SIMD accelerated small maps. + +- with strings, use unique substrings as the actual hash code when possible. + +- The slice length analysis is too naive. It should not have a limit on the number of lengths considered, + and it should limit collisions as a percentage instead of with a fixed number. + +## Features + +- Add support for empty literal collections in the macros. + +- Consider adding support for case-insensitive strings. + +- Extend the Scalar derive macro to support more varieties of enum types. + +## Type System Nightmares + +These are some things which I haven't done yet since I can't figure out how to express these things in the +Rust type system. If you read this and you have some ideas, let me know :-) + +- The Map and Set traits do not implement Borrow semantics on their APIs, unlike all the maps & set + implementation types. It doesn't seem possible to implement Borrow on a trait unfortunately, given how + it composes. + +- FacadeStringSet/Map should have &str as key type instead of String in order to be compatible with all the other + collections. Unfortunately, switching this over is proving difficult. + +- Make the SetIterator and MapIterator traits have IntoIterator as a super-trait. This would seem to require + introducing lifetime annotations in these traits, which would really mess everything else us. + +- Make the Set and Map traits implement Eq and PartialEq. diff --git a/bench.ps1 b/bench.ps1 new file mode 100644 index 0000000..6a8e493 --- /dev/null +++ b/bench.ps1 @@ -0,0 +1,2 @@ +# Run all benchmarks and convert into the markdown +cargo criterion --message-format=json | criterion-table > BENCHMARKS.md diff --git a/benches/Cargo.toml b/benches/Cargo.toml new file mode 100644 index 0000000..b0832c7 --- /dev/null +++ b/benches/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "benches" +version = "0.0.0" +publish = false +edition.workspace = true +rust-version.workspace = true + +[dev-dependencies] +frozen-collections = { path = "../frozen-collections" } +criterion = "0.5.1" +ahash = "0.8.11" + +[[bench]] +name = "string_keys" +path = "string_keys.rs" +harness = false + +[[bench]] +name = "int_keys" +path = "int_keys.rs" +harness = false + +[[bench]] +name = "misc_keys" +path = "misc_keys.rs" +harness = false + +[lints] +workspace = true diff --git a/benches/int_keys.rs b/benches/int_keys.rs new file mode 100644 index 0000000..9573d2f --- /dev/null +++ b/benches/int_keys.rs @@ -0,0 +1,214 @@ +use std::collections::HashMap as StdHashMap; + +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; + +use frozen_collections::hashers::PassthroughHasher; +use frozen_collections::maps::*; +use frozen_collections::{fz_scalar_map, SmallCollection}; + +fn int_keys_dense_lookup(c: &mut Criterion) { + let mut group = c.benchmark_group("int_keys_dense_lookup"); + + let frozen = fz_scalar_map!({ + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + }); + + let input: Vec<(i32, i32)> = frozen.clone().into_iter().collect(); + + // 50% hit rate + let probe = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + ]; + + let map = StdHashMap::<_, _, std::hash::RandomState>::from_iter(input.clone()); + group.bench_function("StdHashMap", |b| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = StdHashMap::<_, _, ahash::RandomState>::from_iter(input.clone()); + group.bench_function("StdHashMap(ahash)", |b| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = DenseScalarLookupMap::new(input).unwrap(); + group.bench_function("DenseScalarLookupMap", |b| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = frozen; + group.bench_function("InlineDenseScalarLookupMap", |b| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + group.finish(); +} + +fn int_keys_sparse_lookup(c: &mut Criterion) { + let mut group = c.benchmark_group("int_keys_sparse_lookup"); + + let frozen = fz_scalar_map!({ + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + 19: 19, + 20: 20 + }); + + let input: Vec<(i32, i32)> = frozen.clone().into_iter().collect(); + + // 50% hit rate, 25% miss within range, 25% miss outside range + let probe = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 19, 20, 10, 11, 12, 13, 14, 15, 21, 22, 23, 24, 25, 26, + ]; + + let map = StdHashMap::<_, _, std::hash::RandomState>::from_iter(input.clone()); + group.bench_function("StdHashMap", |b| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = StdHashMap::<_, _, ahash::RandomState>::from_iter(input.clone()); + group.bench_function("StdHashMap(ahash)", |b| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = SparseScalarLookupMap::<_, _, SmallCollection>::new(input).unwrap(); + group.bench_function("SparseScalarLookupMap", |b| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = frozen; + group.bench_function("InlineSparseScalarLookupMap", |b| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + group.finish(); +} + +fn int_keys(c: &mut Criterion) { + let mut group = c.benchmark_group("int_keys"); + + for size in 3..14 { + // 50% hit rate + let input: Vec<_> = (100..100 + size).map(|x| (x * 2, x)).collect(); + let probe: Vec<_> = (100 - size / 2..100 + size + size / 2) + .map(|x| x * 2) + .collect(); + + let map = StdHashMap::<_, _, std::hash::RandomState>::from_iter(input.clone()); + group.bench_with_input(BenchmarkId::new("StdHashMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = StdHashMap::<_, _, ahash::RandomState>::from_iter(input.clone()); + group.bench_with_input( + BenchmarkId::new("StdHashMap(ahash)", size), + &size, + |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }, + ); + + let map = + HashMap::<_, _, SmallCollection, _>::new(input.clone(), PassthroughHasher::default()) + .unwrap(); + group.bench_with_input(BenchmarkId::new("HashMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = ScanMap::new(input.clone()); + group.bench_with_input(BenchmarkId::new("ScanMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = OrderedScanMap::new(input.clone()); + group.bench_with_input(BenchmarkId::new("OrderedScanMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = BinarySearchMap::new(input.clone()); + group.bench_with_input(BenchmarkId::new("BinarySearchMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + } + + group.finish(); +} +criterion_group!( + benches, + int_keys, + int_keys_dense_lookup, + int_keys_sparse_lookup, +); +criterion_main!(benches); diff --git a/benches/misc_keys.rs b/benches/misc_keys.rs new file mode 100644 index 0000000..3dd2401 --- /dev/null +++ b/benches/misc_keys.rs @@ -0,0 +1,175 @@ +use std::collections::{BTreeMap, HashMap as StdHashMap}; +use std::hash::Hash; + +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use frozen_collections::hashers::BridgeHasher; +use frozen_collections::maps::*; +use frozen_collections::SmallCollection; + +#[derive(Hash, Eq, PartialEq, Ord, PartialOrd, Clone)] +struct MyKey { + name: String, + city: String, +} + +impl MyKey { + fn new(name: &str, city: &str) -> Self { + Self { + name: name.to_string(), + city: city.to_string(), + } + } +} + +fn misc_keys(c: &mut Criterion) { + let mut group = c.benchmark_group("misc_keys"); + + for size in 3..20 { + let mut input = vec![ + (MyKey::new("Alex", "Lisbon"), 10), + (MyKey::new("Brian", "Paris"), 20), + (MyKey::new("Cathy", "New York"), 30), + (MyKey::new("Dylan", "Tokyo"), 40), + (MyKey::new("Ella", "Rio"), 50), + (MyKey::new("Fred", "Oslo"), 60), + (MyKey::new("Gina", "Montreal"), 70), + (MyKey::new("Helena", "Quebec"), 80), + (MyKey::new("Irene", "Kyiv"), 90), + (MyKey::new("Juliano", "Milan"), 100), + (MyKey::new("Kelly", "Ottawa"), 110), + (MyKey::new("Liane", "Vancouver"), 120), + (MyKey::new("Michel", "Whistler"), 130), + (MyKey::new("Normand", "St-Sauveur"), 140), + (MyKey::new("Ovid", "Oslo"), 150), + (MyKey::new("Paul", "Prague"), 160), + (MyKey::new("Quintin", "Los Angeles"), 170), + (MyKey::new("Robert", "Seattle"), 180), + (MyKey::new("Sam", "Eugene"), 190), + (MyKey::new("Teddy", "San Diego"), 200), + ]; + + let mut probe = vec![ + MyKey::new("Alex", "Lisbon"), + MyKey::new("Alex", "Lisbon2"), + MyKey::new("Brian", "Paris"), + MyKey::new("Brian", "2Paris"), + MyKey::new("Cathy", "New York"), + MyKey::new("Cathy2", "New York"), + MyKey::new("Dylan", "Tokyo"), + MyKey::new("2Dylan", "Tokyo"), + MyKey::new("Ella", "Rio"), + MyKey::new("Ella2", "Rio"), + MyKey::new("Fred", "Oslo"), + MyKey::new("Fred", "2Oslo"), + MyKey::new("Gina", "Montreal"), + MyKey::new("Gina", "Montreal2"), + MyKey::new("Helena", "Quebec"), + MyKey::new("Helena", "2Quebec"), + MyKey::new("Irene", "Kyiv"), + MyKey::new("Irene2", "Kyiv"), + MyKey::new("Juliano", "Milan"), + MyKey::new("2Juliano", "Milan"), + MyKey::new("Kelly", "Ottawa"), + MyKey::new("Kelly2", "Ottawa"), + MyKey::new("Liane", "Vancouver"), + MyKey::new("Liane", "2Vancouver"), + MyKey::new("Michel", "Whistler"), + MyKey::new("Michel", "Whistler2"), + MyKey::new("Normand", "St-Sauveur"), + MyKey::new("Normand", "2St-Sauveur"), + MyKey::new("Ovid", "Oslo"), + MyKey::new("Ovid2", "Oslo"), + MyKey::new("Paul", "Prague"), + MyKey::new("2Paul", "Prague"), + MyKey::new("Quintin", "Los Angeles"), + MyKey::new("Quintin2", "Los Angeles"), + MyKey::new("Robert", "Seattle"), + MyKey::new("Robert", "2Seattle"), + MyKey::new("Sam", "Eugene"), + MyKey::new("Sam", "Eugene2"), + MyKey::new("Teddy", "San Diego"), + MyKey::new("Teddy", "2San Diego"), + ]; + + input.truncate(size); + + // 50% hit rate + probe.truncate(size * 2); + + let map = StdHashMap::<_, _, std::hash::RandomState>::from_iter(input.clone()); + group.bench_with_input(BenchmarkId::new("StdHashMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = StdHashMap::<_, _, ahash::RandomState>::from_iter(input.clone()); + group.bench_with_input( + BenchmarkId::new("StdHashMap(ahash)", size), + &size, + |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }, + ); + + let map = BTreeMap::from_iter(input.clone()); + group.bench_with_input(BenchmarkId::new("BTreeMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = HashMap::<_, _, SmallCollection, _>::new( + input.clone(), + BridgeHasher::new(ahash::RandomState::new()), + ) + .unwrap(); + group.bench_with_input(BenchmarkId::new("HashMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = ScanMap::new(input.clone()); + group.bench_with_input(BenchmarkId::new("ScanMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = OrderedScanMap::new(input.clone()); + group.bench_with_input(BenchmarkId::new("OrderedScanMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = BinarySearchMap::new(input.clone()); + group.bench_with_input(BenchmarkId::new("BinarySearchMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + } + + group.finish(); +} + +criterion_group!(benches, misc_keys); +criterion_main!(benches); diff --git a/benches/string_keys.rs b/benches/string_keys.rs new file mode 100644 index 0000000..fcb3e3e --- /dev/null +++ b/benches/string_keys.rs @@ -0,0 +1,351 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use std::collections::HashMap as StdHashMap; + +use frozen_collections::hashers::{BridgeHasher, PassthroughHasher, RightRangeHasher}; +use frozen_collections::maps::*; +use frozen_collections::{fz_string_map, SmallCollection}; + +#[allow(clippy::useless_conversion)] +fn string_keys_length(c: &mut Criterion) { + let mut group = c.benchmark_group("string_keys_length"); + + let frozen = fz_string_map!({ + "1": 1, + "22": 2, + "333": 3, + "4444": 4, + "55555": 5, + "666666": 6, + "7777777": 7, + "88888888": 8, + "999999999": 9, + }); + + let input: Vec<(&str, i32)> = frozen.clone().into_iter().collect(); + + // 50% hit rate + let probe = [ + "1", + "22", + "333", + "4444", + "55555", + "666666", + "7777777", + "88888888", + "999999999", + "x1", + "22x", + "x333", + "4444x", + "x55555", + "666666x", + "x7777777", + "88888888x", + "x999999999", + ]; + + let map = StdHashMap::<_, _, std::hash::RandomState>::from_iter(input.clone().into_iter()); + group.bench_function("StdHashMap", |b| { + b.iter(|| { + for key in probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = StdHashMap::<_, _, ahash::RandomState>::from_iter(input.clone().into_iter()); + group.bench_function("StdHashMap(ahash)", |b| { + b.iter(|| { + for key in probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = + HashMap::<_, _, SmallCollection, _>::new(input, PassthroughHasher::default()).unwrap(); + group.bench_function("HashMap", |b| { + b.iter(|| { + for key in probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = frozen; + group.bench_function("InlineHashMap", |b| { + b.iter(|| { + for key in probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + group.finish(); +} + +#[allow(clippy::useless_conversion)] +fn string_keys_subslice(c: &mut Criterion) { + let mut group = c.benchmark_group("string_keys_subslice"); + + let frozen = fz_string_map!({ + "Red": 1, + "Green": 2, + "Blue": 3, + "Cyan": 4, + "Yellow": 5, + "Magenta": 6, + "Purple": 7, + "Orange": 8, + "Maroon": 9, + "Lilac": 10, + "Burgundy": 11, + "Peach": 12, + "White": 13, + "Black": 14, + "Brown": 15, + "Beige": 16, + "Grey": 17, + "Ecru": 18, + "Tan": 19, + "Lavender": 20, + }); + + let input: Vec<(&str, i32)> = frozen.clone().into_iter().collect(); + + // 50% hit rate + let probe = [ + "Red", + "Green", + "Blue", + "Cyan", + "Yellow", + "Magenta", + "Purple", + "Orange", + "Maroon", + "Lilac", + "Burgundy", + "Peach", + "White", + "Black", + "Brown", + "Beige", + "Grey", + "Ecru", + "Tan", + "Lavender", + "RedX", + "XGreen", + "BlueX", + "XCyan", + "YellowX", + "XMagenta", + "PurpleX", + "XOrange", + "MaroonX", + "XLilac", + "BurgundyX", + "XPeach", + "WhiteX", + "XBlack", + "BrownX", + "XBeige", + "GreyX", + "XEcru", + "TanX", + "XLavender", + ]; + + let map = StdHashMap::<_, _, std::hash::RandomState>::from_iter(input.clone().into_iter()); + group.bench_function("StdHashMap", |b| { + b.iter(|| { + for key in probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = StdHashMap::<_, _, ahash::RandomState>::from_iter(input.clone().into_iter()); + group.bench_function("StdHashMap(ahash)", |b| { + b.iter(|| { + for key in probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = HashMap::<_, _, SmallCollection, _>::new( + input, + RightRangeHasher::new(ahash::RandomState::new(), 1..3), + ) + .unwrap(); + group.bench_function("HashMap", |b| { + b.iter(|| { + for key in probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = frozen; + group.bench_function("InlineHashMap", |b| { + b.iter(|| { + for key in probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + group.finish(); +} + +fn string_keys(c: &mut Criterion) { + let mut group = c.benchmark_group("string_keys"); + + for size in 3..15 { + let mut input = vec![ + ("Red", 1), + ("Green", 2), + ("Blue", 3), + ("Cyan", 4), + ("Yellow", 5), + ("Magenta", 6), + ("Purple", 7), + ("Orange", 8), + ("Maroon", 9), + ("Lilac", 10), + ("Burgundy", 11), + ("Peach", 12), + ("White", 13), + ("Black", 14), + ("Brown", 15), + ("Beige", 16), + ("Grey", 17), + ("Ecru", 18), + ("Tan", 19), + ("Lavender", 20), + ]; + + let mut probe = vec![ + "Red", + "XRed", + "Green", + "GreenX", + "Blue", + "XBlue", + "Cyan", + "CyanX", + "Yellow", + "XYellow", + "Magenta", + "MagentaX", + "Purple", + "XPurple", + "Orange", + "OrangeX", + "Maroon", + "XMaroon", + "Lilac", + "LilacX", + "Burgundy", + "XBurgundy", + "Peach", + "PeachX", + "White", + "XWhite", + "Black", + "BlackX", + "Brown", + "XBrown", + "Beige", + "BeigeX", + "Grey", + "XGrey", + "Ecru", + "EcruX", + "Tan", + "XTan", + "Lavender", + "LavenderX", + ]; + + input.truncate(size); + + // 50% hit rate + probe.truncate(size * 2); + + let map = StdHashMap::<_, _, std::hash::RandomState>::from_iter(input.clone()); + group.bench_with_input(BenchmarkId::new("StdHashMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = StdHashMap::<_, _, ahash::RandomState>::from_iter(input.clone()); + group.bench_with_input( + BenchmarkId::new("StdHashMap(ahash)", size), + &size, + |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }, + ); + + let map = HashMap::<_, _, SmallCollection, _>::new( + input.clone(), + BridgeHasher::new(ahash::RandomState::default()), + ) + .unwrap(); + group.bench_with_input(BenchmarkId::new("HashMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = ScanMap::new(input.clone()); + group.bench_with_input(BenchmarkId::new("ScanMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = OrderedScanMap::new(input.clone()); + group.bench_with_input(BenchmarkId::new("OrderedScanMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + + let map = BinarySearchMap::new(input.clone()); + group.bench_with_input(BenchmarkId::new("BinaryScanMap", size), &size, |b, _| { + b.iter(|| { + for key in &probe { + _ = black_box(map.contains_key(key)); + } + }); + }); + } + + group.finish(); +} + +criterion_group!( + benches, + string_keys, + string_keys_length, + string_keys_subslice, +); +criterion_main!(benches); diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 0000000..504f252 --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "codegen" +version = "0.0.0" +publish = false +edition.workspace = true +rust-version.workspace = true + +[dev-dependencies] +frozen-collections = { path = "../frozen-collections" } +ahash = "0.8.11" + +[[example]] +name = "misc_keys" +path = "misc_keys.rs" + +[[example]] +name = "string_keys_subslice" +path = "string_keys_subslice.rs" + +[[example]] +name = "string_keys_length" +path = "string_keys_length.rs" + +[[example]] +name = "int_keys" +path = "int_keys.rs" + +[lints] +workspace = true diff --git a/codegen/README.md b/codegen/README.md new file mode 100644 index 0000000..023dc87 --- /dev/null +++ b/codegen/README.md @@ -0,0 +1,5 @@ +# Codegen + +This contains a few simple examples that get compiled to release +mode in order to compare the generated code for various code fragments +to determine the best implementation choices. diff --git a/codegen/int_keys.rs b/codegen/int_keys.rs new file mode 100644 index 0000000..5f760ef --- /dev/null +++ b/codegen/int_keys.rs @@ -0,0 +1,96 @@ +use frozen_collections::facade_maps::FacadeScalarMap; +use frozen_collections::hashers::PassthroughHasher; +use frozen_collections::maps::{ + BinarySearchMap, DenseScalarLookupMap, HashMap, OrderedScanMap, ScanMap, SparseScalarLookupMap, +}; +use frozen_collections::SmallCollection; +use std::collections::HashMap as StdHashMap; +use std::hint::black_box; + +fn main() { + let input = vec![(0, 0), (1, 0), (2, 0), (3, 0)]; + let probe = vec![0, 1, 2]; + + let map: StdHashMap<_, _, ahash::RandomState> = input.clone().into_iter().collect(); + for key in probe.clone() { + _ = black_box(call_std_hash_map(&map, key)); + } + + let map = HashMap::new(input.clone(), PassthroughHasher::default()).unwrap(); + for key in probe.clone() { + _ = black_box(call_hash_map(&map, key)); + } + + let map = DenseScalarLookupMap::new(input.clone()).unwrap(); + for key in probe.clone() { + _ = black_box(call_dense_scalar_lookup_map(&map, key)); + } + + let map = SparseScalarLookupMap::new(input.clone()).unwrap(); + for key in probe.clone() { + _ = black_box(call_sparse_scalar_lookup_map(&map, key)); + } + + let map = ScanMap::new(input.clone()); + for key in probe.clone() { + _ = black_box(call_scan_map(&map, key)); + } + + let map = OrderedScanMap::new(input.clone()); + for key in probe.clone() { + _ = black_box(call_ordered_scan_map(&map, key)); + } + + let map = BinarySearchMap::new(input.clone()); + for key in probe.clone() { + _ = black_box(call_binary_search_map(&map, key)); + } + + let map = FacadeScalarMap::new(input); + for key in probe { + _ = black_box(call_frozen_scalar_map(&map, key)); + } +} + +#[inline(never)] +fn call_std_hash_map(map: &StdHashMap, key: i32) -> bool { + map.contains_key(&key) +} + +#[inline(never)] +fn call_hash_map(map: &HashMap, key: i32) -> bool { + map.contains_key(&key) +} + +#[inline(never)] +fn call_dense_scalar_lookup_map(map: &DenseScalarLookupMap, key: i32) -> bool { + map.contains_key(&key) +} + +#[inline(never)] +fn call_sparse_scalar_lookup_map( + map: &SparseScalarLookupMap, + key: i32, +) -> bool { + map.contains_key(&key) +} + +#[inline(never)] +fn call_frozen_scalar_map(map: &FacadeScalarMap, key: i32) -> bool { + map.contains_key(&key) +} + +#[inline(never)] +fn call_binary_search_map(map: &BinarySearchMap, key: i32) -> bool { + map.contains_key(&key) +} + +#[inline(never)] +fn call_scan_map(map: &ScanMap, key: i32) -> bool { + map.contains_key(&key) +} + +#[inline(never)] +fn call_ordered_scan_map(map: &OrderedScanMap, key: i32) -> bool { + map.contains_key(&key) +} diff --git a/codegen/misc_keys.rs b/codegen/misc_keys.rs new file mode 100644 index 0000000..621e313 --- /dev/null +++ b/codegen/misc_keys.rs @@ -0,0 +1,82 @@ +use frozen_collections::facade_maps::FacadeHashMap; +use frozen_collections::hashers::BridgeHasher; +use frozen_collections::*; +use std::collections::HashMap as StdHashMap; +use std::hint::black_box; + +#[derive(Hash, PartialEq, Eq, Clone)] +struct MyKey { + name: String, + city: String, +} + +fn main() { + let v = vec![ + ( + MyKey { + name: "Helter1".to_string(), + city: "Skelter".to_string(), + }, + 42, + ), + ( + MyKey { + name: "Helter2".to_string(), + city: "Skelter".to_string(), + }, + 42, + ), + ( + MyKey { + name: "Helter3".to_string(), + city: "Skelter".to_string(), + }, + 42, + ), + ( + MyKey { + name: "Helter4".to_string(), + city: "Skelter".to_string(), + }, + 42, + ), + ( + MyKey { + name: "Helter5".to_string(), + city: "Skelter".to_string(), + }, + 42, + ), + ( + MyKey { + name: "Helter6".to_string(), + city: "Skelter".to_string(), + }, + 42, + ), + ]; + + let fm = FacadeHashMap::new(v.clone(), BridgeHasher::default()); + let cm = maps::HashMap::new(v.clone(), BridgeHasher::new(ahash::RandomState::new())).unwrap(); + let mut hm = StdHashMap::with_capacity_and_hasher(v.len(), ahash::RandomState::new()); + hm.extend(v.clone()); + + _ = black_box(call_frozen_map(&fm, &v[0].0)); + _ = black_box(call_hash_map(&cm, &v[0].0)); + _ = black_box(call_std_hash_map(&hm, &v[0].0)); +} + +#[inline(never)] +fn call_frozen_map(map: &FacadeHashMap, key: &MyKey) -> bool { + map.contains_key(key) +} + +#[inline(never)] +fn call_hash_map(map: &maps::HashMap, key: &MyKey) -> bool { + map.contains_key(key) +} + +#[inline(never)] +fn call_std_hash_map(map: &StdHashMap, key: &MyKey) -> bool { + map.contains_key(key) +} diff --git a/codegen/string_keys_length.rs b/codegen/string_keys_length.rs new file mode 100644 index 0000000..9143e91 --- /dev/null +++ b/codegen/string_keys_length.rs @@ -0,0 +1,56 @@ +use frozen_collections::ahash::RandomState; +use frozen_collections::facade_maps::FacadeStringMap; +use frozen_collections::*; +use std::collections::HashMap as StdHashMap; +use std::hint::black_box; + +fz_string_map!(static MAP: MyMapType<&str, i32> = { "1": 1, "22":2, "333":3, "4444":4, "55555":5, "666666":6, "7777777":7, "88888888":8, "999999999":9 }); + +fn main() { + let input = MAP.clone(); + let probe = [ + "0", + "1", + "22", + "333", + "4444", + "A", + "B", + "C", + "D", + "ABCDEFGHIJKL", + ]; + + let map: StdHashMap<_, _, ahash::RandomState> = input.iter().map(|x| (*x.0, *x.1)).collect(); + for key in probe { + _ = black_box(call_std_hash_map(&map, key)); + } + + let map = FacadeStringMap::new( + input.iter().map(|x| ((*x.0).to_string(), *x.1)).collect(), + RandomState::new(), + ); + for key in probe { + _ = black_box(call_frozen_string_map(&map, key)); + } + + let map = input; + for key in probe { + _ = black_box(call_inline_hash_map(&map, key)); + } +} + +#[inline(never)] +fn call_std_hash_map(map: &StdHashMap<&str, i32, ahash::RandomState>, key: &str) -> bool { + map.contains_key(key) +} + +#[inline(never)] +fn call_inline_hash_map(map: &MyMapType, key: &str) -> bool { + map.contains_key(key) +} + +#[inline(never)] +fn call_frozen_string_map(map: &FacadeStringMap, key: &str) -> bool { + map.contains_key(key) +} diff --git a/codegen/string_keys_subslice.rs b/codegen/string_keys_subslice.rs new file mode 100644 index 0000000..ecad445 --- /dev/null +++ b/codegen/string_keys_subslice.rs @@ -0,0 +1,51 @@ +use frozen_collections::ahash::RandomState; +use frozen_collections::facade_maps::FacadeStringMap; +use frozen_collections::*; +use std::collections::HashMap as StdHashMap; +use std::hint::black_box; + +fz_string_map!(static MAP: MyMapType<&str, i32> = { "ALongPrefixRedd": 1, "ALongPrefixGree":2, "ALongPrefixBlue":3, "ALongPrefixCyan":4 }); + +fn main() { + let input = MAP.clone(); + let probe = [ + "ALongPrefixRedd", + "ALongPrefixCyan", + "Tomato", + "Potato", + "Carrot", + ]; + + let map: StdHashMap<_, _, ahash::RandomState> = input.iter().map(|x| (*x.0, *x.1)).collect(); + for key in probe { + _ = black_box(call_std_hash_map(&map, key)); + } + + let map = FacadeStringMap::new( + input.iter().map(|x| ((*x.0).to_string(), *x.1)).collect(), + RandomState::default(), + ); + for key in probe { + _ = black_box(call_frozen_string_map(&map, key)); + } + + let map = input; + for key in probe { + _ = black_box(call_inline_left_range_hash_map(&map, key)); + } +} + +#[inline(never)] +fn call_std_hash_map(map: &StdHashMap<&str, i32, ahash::RandomState>, key: &str) -> bool { + map.contains_key(key) +} + +#[inline(never)] +fn call_frozen_string_map(map: &FacadeStringMap, key: &str) -> bool { + map.contains_key(key) +} + +#[inline(never)] +fn call_inline_left_range_hash_map(map: &MyMapType, key: &str) -> bool { + map.contains_key(key) +} diff --git a/criterion.toml b/criterion.toml new file mode 100644 index 0000000..a2a9083 --- /dev/null +++ b/criterion.toml @@ -0,0 +1 @@ +output_format = "bencher" diff --git a/frozen-collections-core/Cargo.toml b/frozen-collections-core/Cargo.toml new file mode 100644 index 0000000..627c400 --- /dev/null +++ b/frozen-collections-core/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "frozen-collections-core" +description = "Implementation logic for frozen collections." +readme = "README.md" +authors.workspace = true +version.workspace = true +edition.workspace = true +categories.workspace = true +keywords.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +ahash = "0.8.11" +hashbrown = "0.15.1" +const-random = "0.1.18" +syn = { version = "2.0.87", features = ["extra-traits", "full"] } +quote = { version = "1.0.37" } +proc-macro2 = { version = "1.0.89" } + +[dev-dependencies] +rand = "0.9.0-alpha.1" + +[dependencies.bitvec] +version = "1.0.1" +default-features = false +features = ["alloc"] + +[features] +default = ["std"] +std = [] + +[lints] +workspace = true diff --git a/frozen-collections-core/README.md b/frozen-collections-core/README.md new file mode 100644 index 0000000..f1f28c8 --- /dev/null +++ b/frozen-collections-core/README.md @@ -0,0 +1,6 @@ +# frozen-collections-core + +This crate contains some of the implementation logic for the +frozen-collections crate. Users of frozen collections +should take a dependency on the [`frozen-collections`](https://docs.rs/frozen-collections) crate +instead of this one. diff --git a/frozen-collections-core/src/analyzers/hash_code_analyzer.rs b/frozen-collections-core/src/analyzers/hash_code_analyzer.rs new file mode 100644 index 0000000..889f27e --- /dev/null +++ b/frozen-collections-core/src/analyzers/hash_code_analyzer.rs @@ -0,0 +1,190 @@ +use alloc::vec::Vec; + +use bitvec::prelude::*; + +/// How to treat a collection of hash codes for best performance. +#[derive(Clone)] +pub struct HashCodeAnalysisResult { + /// The recommended hash table size. This is not necessarily optimal, but it's good enough. + pub num_hash_slots: usize, + + /// The number of collisions when using the recommended table size. + #[allow(dead_code)] + pub num_hash_collisions: usize, +} + +/// Look for an "optimal" hash table size for a given set of hash codes. +#[allow(clippy::cast_possible_truncation)] +pub fn analyze_hash_codes(hash_codes: I) -> HashCodeAnalysisResult +where + I: Iterator, +{ + // What is a satisfactory rate of hash collisions? + const ACCEPTABLE_COLLISION_PERCENTAGE: usize = 5; + + // By how much do we shrink the acceptable # collisions per iteration? + const ACCEPTABLE_COLLISION_PERCENTAGE_OF_REDUCTION: usize = 20; + + // thresholds to categorize input sizes + const MEDIUM_INPUT_SIZE_THRESHOLD: usize = 128; + const LARGE_INPUT_SIZE_THRESHOLD: usize = 1000; + + // amount by which the table can be larger than the input + const MAX_SMALL_INPUT_MULTIPLIER: usize = 10; + const MAX_MEDIUM_INPUT_MULTIPLIER: usize = 7; + const MAX_LARGE_INPUT_MULTIPLIER: usize = 3; + + let hash_codes: Vec = hash_codes.collect(); + let mut acceptable_collisions = if hash_codes.len() < MEDIUM_INPUT_SIZE_THRESHOLD { + // for small enough inputs, we try for perfection + 0 + } else { + (hash_codes.len() / 100) * ACCEPTABLE_COLLISION_PERCENTAGE + }; + + // the minimum table size we can tolerate, given the acceptable collision rate + let mut min_size = hash_codes.len() - acceptable_collisions; + if !min_size.is_power_of_two() { + min_size = min_size.next_power_of_two(); + } + + // the maximum table size we consider, given a scaled growth factor for different input sizes + let mut max_size = if hash_codes.len() < MEDIUM_INPUT_SIZE_THRESHOLD { + hash_codes.len() * MAX_SMALL_INPUT_MULTIPLIER + } else if hash_codes.len() < LARGE_INPUT_SIZE_THRESHOLD { + hash_codes.len() * MAX_MEDIUM_INPUT_MULTIPLIER + } else { + hash_codes.len() * MAX_LARGE_INPUT_MULTIPLIER + }; + + if !max_size.is_power_of_two() { + max_size = max_size.next_power_of_two(); + } + + let mut use_table: BitVec = BitVec::with_capacity(max_size); + use_table.resize(max_size, false); + + let mut best_num_slots = 0; + let mut best_num_collisions = hash_codes.len(); + + let mut num_slots = min_size; + while num_slots <= max_size { + use_table.fill(false); + let mut num_collisions = 0; + + for code in &hash_codes { + let slot = (code % (num_slots as u64)) as usize; + if use_table[slot] { + num_collisions += 1; + if num_collisions >= best_num_collisions { + break; + } + } else { + use_table.set(slot, true); + } + } + + if num_collisions < best_num_collisions { + if best_num_slots == 0 || num_collisions <= acceptable_collisions { + best_num_collisions = num_collisions; + best_num_slots = num_slots; + } + + if num_collisions <= acceptable_collisions { + // we have a winner! + break; + } + } + + if acceptable_collisions > 0 { + // The larger the table, the fewer collisions we tolerate. The idea + // here is to reduce the risk of a table getting very big and still + // having a relatively high count of collisions. + acceptable_collisions = + (acceptable_collisions / 100) * ACCEPTABLE_COLLISION_PERCENTAGE_OF_REDUCTION; + } + + num_slots = (num_slots + 1).next_power_of_two(); + } + + HashCodeAnalysisResult { + num_hash_slots: best_num_slots, + num_hash_collisions: best_num_collisions, + } +} + +#[cfg(test)] +mod tests { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + + use super::*; + + struct AnalysisTestCase { + num_hash_codes: usize, + randomize_hash_codes: bool, + expected_num_hash_slots: usize, + expected_num_hash_collisions: usize, + } + + #[test] + fn analyze_hash_codes_test() { + const ANALYSIS_TEST_CASES: [AnalysisTestCase; 5] = [ + AnalysisTestCase { + num_hash_codes: 0, + randomize_hash_codes: true, + expected_num_hash_slots: 0, + expected_num_hash_collisions: 0, + }, + AnalysisTestCase { + num_hash_codes: 2, + randomize_hash_codes: true, + expected_num_hash_slots: 2, + expected_num_hash_collisions: 0, + }, + AnalysisTestCase { + num_hash_codes: 1000, + randomize_hash_codes: true, + expected_num_hash_slots: 1024, + expected_num_hash_collisions: 349, + }, + AnalysisTestCase { + num_hash_codes: 8_000_000, + randomize_hash_codes: false, + expected_num_hash_slots: 8_388_608, + expected_num_hash_collisions: 0, + }, + AnalysisTestCase { + num_hash_codes: 8_000_000, + randomize_hash_codes: true, + expected_num_hash_slots: 8_388_608, + expected_num_hash_collisions: 2_843_788, + }, + ]; + + for (count, case) in ANALYSIS_TEST_CASES.iter().enumerate() { + println!("Test case #{count}"); + + let mut rng = StdRng::seed_from_u64(42); + let mut hash_codes = Vec::with_capacity(case.num_hash_codes); + + if case.randomize_hash_codes { + for _ in 0..case.num_hash_codes { + hash_codes.push(rng.random()); + } + } else { + for count in 0..case.num_hash_codes { + hash_codes.push(count as u64); + } + } + + let result = analyze_hash_codes(hash_codes.iter().copied()); + + assert_eq!(case.expected_num_hash_slots, result.num_hash_slots); + assert_eq!( + case.expected_num_hash_collisions, + result.num_hash_collisions + ); + } + } +} diff --git a/frozen-collections-core/src/analyzers/mod.rs b/frozen-collections-core/src/analyzers/mod.rs new file mode 100644 index 0000000..6ebed9e --- /dev/null +++ b/frozen-collections-core/src/analyzers/mod.rs @@ -0,0 +1,9 @@ +//! Logic to analyze collection input data to assess the best implementation choices. + +pub use hash_code_analyzer::*; +pub use scalar_key_analyzer::*; +pub use slice_key_analyzer::*; + +mod hash_code_analyzer; +mod scalar_key_analyzer; +mod slice_key_analyzer; diff --git a/frozen-collections-core/src/analyzers/scalar_key_analyzer.rs b/frozen-collections-core/src/analyzers/scalar_key_analyzer.rs new file mode 100644 index 0000000..5b229a3 --- /dev/null +++ b/frozen-collections-core/src/analyzers/scalar_key_analyzer.rs @@ -0,0 +1,86 @@ +use crate::traits::Scalar; + +/// How to treat integer keys for best performance. +#[derive(PartialEq, Eq, Debug)] +pub enum ScalarKeyAnalysisResult { + /// No special optimization possible. + General, + + /// All keys are in a continuous range. + DenseRange, + + /// All keys are in a sparse range. + SparseRange, +} + +/// Look for well-known patterns we can optimize for with integer keys. +pub fn analyze_scalar_keys(keys: I) -> ScalarKeyAnalysisResult +where + K: Scalar, + I: Iterator, +{ + const MAX_SPARSE_MULTIPLIER: usize = 10; + const ALWAYS_SPARSE_THRESHOLD: usize = 128; + + let mut min = usize::MAX; + let mut max = usize::MIN; + let mut count = 0; + for key in keys { + min = min.min(key.index()); + max = max.max(key.index()); + count += 1; + } + + if count == 0 { + return ScalarKeyAnalysisResult::General; + } + + let needed_count = max - min + 1; + if needed_count == count { + ScalarKeyAnalysisResult::DenseRange + } else if needed_count <= ALWAYS_SPARSE_THRESHOLD + || needed_count < count.saturating_mul(MAX_SPARSE_MULTIPLIER) + { + ScalarKeyAnalysisResult::SparseRange + } else { + ScalarKeyAnalysisResult::General + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_analyze_scalar_keys_empty() { + let keys = vec![].into_iter(); + assert_eq!( + analyze_scalar_keys::(keys), + ScalarKeyAnalysisResult::General + ); + } + + #[test] + fn test_analyze_scalar_keys_dense_range() { + let keys = 1..=5; + assert_eq!( + analyze_scalar_keys(keys), + ScalarKeyAnalysisResult::DenseRange + ); + } + + #[test] + fn test_analyze_scalar_keys_sparse_range() { + let keys = vec![1, 3, 5, 7, 128].into_iter(); + assert_eq!( + analyze_scalar_keys(keys), + ScalarKeyAnalysisResult::SparseRange + ); + } + + #[test] + fn test_analyze_scalar_keys_general() { + let keys = vec![1, 2, 4, 8, 129].into_iter(); + assert_eq!(analyze_scalar_keys(keys), ScalarKeyAnalysisResult::General); + } +} diff --git a/frozen-collections-core/src/analyzers/slice_key_analyzer.rs b/frozen-collections-core/src/analyzers/slice_key_analyzer.rs new file mode 100644 index 0000000..4ef6c52 --- /dev/null +++ b/frozen-collections-core/src/analyzers/slice_key_analyzer.rs @@ -0,0 +1,285 @@ +use alloc::vec::Vec; +use core::cmp::{max, min}; +use core::hash::{BuildHasher, Hash}; +use core::ops::Range; +use hashbrown::HashMap as HashbrownMap; +use hashbrown::HashSet as HashbrownSet; + +/// How to treat keys which are slices for best performance. +#[derive(PartialEq, Eq, Debug)] +pub enum SliceKeyAnalysisResult { + /// No special optimization possible. + General, + + /// Hash left-justified subslices + LeftHandSubslice(Range), + + /// Hash right-justified subslices + RightHandSubslice(Range), + + /// Use the length of the slices as hash codes, instead of hashing the slices + Length, +} + +/// Look for well-known patterns we can optimize for when keys are slices. +/// +/// The idea here is to find the shortest subslice across all the input slices which are maximally unique. A corresponding +/// subslice range is then applied to incoming slices being hashed to perform lookups. Keeping the subslices as +/// short as possible minimizes the number of bytes involved in hashing, speeding up the whole process. +/// +/// What we do here is pretty simple. We loop over the input slices, looking for the shortest subslice with a good +/// enough uniqueness factor. We look at all the slices both left-justified and right-justified as this maximizes +/// the opportunities to find unique subslices, especially in the case of many slices with the same prefix or suffix. +/// +/// We also analyze the length of the input slices. If the length of the slices are sufficiently unique, +/// we can totally skip hashing and just use their lengths as hash codes. +pub fn analyze_slice_keys<'a, K, I, BH>(keys: I, bh: &BH) -> SliceKeyAnalysisResult +where + K: Hash + 'a, + I: Iterator, + BH: BuildHasher, +{ + let keys = keys.collect(); + + // first, see if we can just use slice lengths as hash codes + let result = analyze_lengths(&keys); + + if result == SliceKeyAnalysisResult::General { + // if we can't use slice lengths, look for suitable subslices + analyze_subslices(&keys, bh) + } else { + result + } +} + +/// See if we can use slice lengths instead of hashing +fn analyze_lengths(keys: &Vec<&[T]>) -> SliceKeyAnalysisResult { + const MAX_IDENTICAL_LENGTHS: usize = 3; + const MAX_SLICES: usize = 255; + + if keys.len() > MAX_SLICES { + // if there are a lof of slices, assume we'll get too many length collisions + return SliceKeyAnalysisResult::General; + } + + let mut lengths = HashbrownMap::new(); + for s in keys { + let v = lengths.get(&s.len()); + if let Some(count) = v { + if count == &MAX_IDENTICAL_LENGTHS { + return SliceKeyAnalysisResult::General; + } + + lengths.insert(s.len(), count + 1); + } else { + lengths.insert(s.len(), 1); + } + } + + SliceKeyAnalysisResult::Length +} + +/// See if we can use subslices to reduce the time spent hashing +fn analyze_subslices(keys: &Vec<&[T]>, bh: &BH) -> SliceKeyAnalysisResult +where + T: Hash, + BH: BuildHasher, +{ + // constrain the amount of work we do in this code + const MAX_SUBSLICE_LENGTH_LIMIT: usize = 16; + const ACCEPTABLE_DUPLICATE_RATIO: usize = 20; // 5% duplicates are acceptable + + let mut min_len = usize::MAX; + let mut max_len = 0; + for s in keys { + min_len = min(min_len, s.len()); + max_len = max(max_len, s.len()); + } + + // tolerate a certain amount of duplicate subslices + let acceptable_duplicates = keys.len() / ACCEPTABLE_DUPLICATE_RATIO; + + // this set is reused for each call to is_sufficiently_unique + let mut set = HashbrownSet::with_capacity(keys.len()); + + // for each subslice length, prefer the shortest length that provides enough uniqueness + let max_subslice_len = min(min_len, MAX_SUBSLICE_LENGTH_LIMIT); + + let mut subslice_len = 1; + while subslice_len <= max_subslice_len { + // For each index, get a uniqueness factor for the left-justified subslices. + // If any is above our threshold, we're done. + let mut subslice_index = 0; + while subslice_index <= min_len - subslice_len { + if is_sufficiently_unique( + keys, + subslice_index, + subslice_len, + true, + &mut set, + acceptable_duplicates, + bh, + ) { + return if subslice_len == max_len { + SliceKeyAnalysisResult::General + } else { + SliceKeyAnalysisResult::LeftHandSubslice( + subslice_index..subslice_index + subslice_len, + ) + }; + } + + subslice_index += 1; + } + + // There were no left-justified slices of this length available. + // If all the slices are of the same length, then just checking left-justification is sufficient. + // But if any slices are of different lengths, then we'll get different alignments for left- vs + // right-justified subslices, and so we also check right-justification. + if min_len != max_len { + // For each index, get a uniqueness factor for the right-justified subslices. + // If any is above our threshold, we're done. + subslice_index = 0; + while subslice_index <= min_len - subslice_len { + if is_sufficiently_unique( + keys, + subslice_index, + subslice_len, + false, + &mut set, + acceptable_duplicates, + bh, + ) { + return SliceKeyAnalysisResult::RightHandSubslice( + subslice_index..subslice_index + subslice_len, + ); + } + + subslice_index += 1; + } + } + + subslice_len += 1; + } + + // could not find a subslice that was good enough. + SliceKeyAnalysisResult::General +} + +fn is_sufficiently_unique( + keys: &Vec<&[T]>, + subslice_index: usize, + subslice_len: usize, + left_justified: bool, + set: &mut HashbrownSet, + acceptable_duplicates: usize, + bh: &BH, +) -> bool +where + T: Hash, + BH: BuildHasher, +{ + set.clear(); + + let mut acceptable_duplicates = acceptable_duplicates; + for s in keys { + let sub = if left_justified { + &s[subslice_index..subslice_index + subslice_len] + } else { + let start = s.len() - subslice_index - subslice_len; + &s[start..start + subslice_len] + }; + + if !set.insert(bh.hash_one(sub)) { + if acceptable_duplicates == 0 { + return false; + } + + acceptable_duplicates -= 1; + } + } + + true +} + +#[cfg(test)] +mod tests { + use alloc::string::{String, ToString}; + + use ahash::RandomState; + + use super::*; + + struct AnalysisTestCase<'a> { + slices: &'a [&'a str], + expected: SliceKeyAnalysisResult, + } + + #[test] + fn analyze_string_keys_test() { + const ANALYSIS_TEST_CASES: [AnalysisTestCase; 9] = [ + AnalysisTestCase { + slices: &[ + "AAA", "ABB", "ACC", "ADD", "AEE", "AFF", "AGG", "AHH", "AII", "AJJ", "AKK", + "ALL", "AMM", "ANN", "AOO", "APP", "AQQ", "ARR", "ASS", "ATT", "AUU", + ], + expected: SliceKeyAnalysisResult::LeftHandSubslice(1..2), + }, + AnalysisTestCase { + slices: &["A00", "B00", "C00", "D00"], + expected: SliceKeyAnalysisResult::LeftHandSubslice(0..1), + }, + AnalysisTestCase { + slices: &["A", "B", "C", "D", "E2"], + expected: SliceKeyAnalysisResult::LeftHandSubslice(0..1), + }, + AnalysisTestCase { + slices: &["A", "B", "C", "D", "E2", ""], + expected: SliceKeyAnalysisResult::General, + }, + AnalysisTestCase { + slices: &["XA", "XB", "XC", "XD", "XE2"], + expected: SliceKeyAnalysisResult::LeftHandSubslice(1..2), + }, + AnalysisTestCase { + slices: &["XXA", "XXB", "XXC", "XXD", "XXX", "XXXE"], + expected: SliceKeyAnalysisResult::RightHandSubslice(0..1), + }, + AnalysisTestCase { + slices: &["ABC", "DEFG", "HIJKL", "MNOPQR", "STUVWX", "YZ"], + expected: SliceKeyAnalysisResult::Length, + }, + AnalysisTestCase { + slices: &[ + "ABC", "DEFG", "HIJKL", "MNOPQR", "STUVWX", "YZ", "D2", "D3", "D4", + ], + expected: SliceKeyAnalysisResult::LeftHandSubslice(1..2), + }, + AnalysisTestCase { + slices: &["AAA", "1AA", "A1A", "AA1", "BBB", "1BB", "B1B", "BB1"], + expected: SliceKeyAnalysisResult::General, + }, + ]; + + for (count, case) in ANALYSIS_TEST_CASES.into_iter().enumerate() { + println!("Test case #{count}"); + + let keys = case.slices.iter().map(|x| x.as_bytes()); + assert_eq!(case.expected, analyze_slice_keys(keys, &RandomState::new())); + } + } + + #[test] + fn out_of_range_bug() { + let mut v = Vec::new(); + for i in 0..30 { + let mut s = i.to_string(); + s.push('1'); + v.push(s); + } + + let x = v.iter().map(String::as_bytes); + let y = &RandomState::new(); + analyze_slice_keys(x, y); + } +} diff --git a/frozen-collections-core/src/doc_snippets/about.md b/frozen-collections-core/src/doc_snippets/about.md new file mode 100644 index 0000000..9e7284a --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/about.md @@ -0,0 +1,8 @@ +A frozen collection differs from the traditional Rust collections, such as +[`HashMap`](std::collections::HashMap) and [`HashSet`](std::collections::HashSet) types in three +key ways. First, creating a frozen collection performs an analysis over the input data to +determine the best implementation strategy. Depending on the situation, this analysis is +performed at build time or runtime, and it can take a relatively long time when a collection is +very large. Second, once created, the keys in frozen collections are immutable. And third, +querying a frozen collection is typically considerably faster, which is the whole point. + diff --git a/frozen-collections-core/src/doc_snippets/contains_key_method.md b/frozen-collections-core/src/doc_snippets/contains_key_method.md new file mode 100644 index 0000000..ad594b5 --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/contains_key_method.md @@ -0,0 +1 @@ +Returns `true` if the map contains a value for the specified key. diff --git a/frozen-collections-core/src/doc_snippets/contains_method.md b/frozen-collections-core/src/doc_snippets/contains_method.md new file mode 100644 index 0000000..dedd7b6 --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/contains_method.md @@ -0,0 +1 @@ +Returns `true` if the set contains a value. diff --git a/frozen-collections-core/src/doc_snippets/get_from_set_method.md b/frozen-collections-core/src/doc_snippets/get_from_set_method.md new file mode 100644 index 0000000..750a5fa --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/get_from_set_method.md @@ -0,0 +1 @@ +Returns a reference to the value in the set, if any, that is equal to the given value. diff --git a/frozen-collections-core/src/doc_snippets/get_key_value_method.md b/frozen-collections-core/src/doc_snippets/get_key_value_method.md new file mode 100644 index 0000000..87ba853 --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/get_key_value_method.md @@ -0,0 +1 @@ +Returns the key-value pair corresponding to the supplied key. diff --git a/frozen-collections-core/src/doc_snippets/get_many_mut_method.md b/frozen-collections-core/src/doc_snippets/get_many_mut_method.md new file mode 100644 index 0000000..5613dae --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/get_many_mut_method.md @@ -0,0 +1,5 @@ +Attempts to get mutable references to `N` values in the map at once. + +Returns an array of length `N` with the results of each query. For soundness, at most one +mutable reference will be returned to any value. [`None`] will be returned if any of the +keys are duplicates or missing. diff --git a/frozen-collections-core/src/doc_snippets/get_method.md b/frozen-collections-core/src/doc_snippets/get_method.md new file mode 100644 index 0000000..722723e --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/get_method.md @@ -0,0 +1 @@ +Returns a reference to the value corresponding to the key. diff --git a/frozen-collections-core/src/doc_snippets/get_mut_method.md b/frozen-collections-core/src/doc_snippets/get_mut_method.md new file mode 100644 index 0000000..0f11f71 --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/get_mut_method.md @@ -0,0 +1 @@ +Returns a mutable reference to the value corresponding to the key. diff --git a/frozen-collections-core/src/doc_snippets/hash_warning.md b/frozen-collections-core/src/doc_snippets/hash_warning.md new file mode 100644 index 0000000..903fabc --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/hash_warning.md @@ -0,0 +1,21 @@ +This type requires that the keys +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. +Violating this property is a logic error. + +It is also a logic error for a key to be modified in such a way that the key's +hash, as determined by the [`Hash`] trait, or its equality, as determined by +the [`Eq`] trait, changes while it is in the collection. This is normally only +possible through [`core::cell::Cell`], [`core::cell::RefCell`], global state, I/O, +or unsafe code. + +The behavior resulting from either logic error can include panics, incorrect results, +memory leaks, and non-termination. + diff --git a/frozen-collections-core/src/doc_snippets/order_warning.md b/frozen-collections-core/src/doc_snippets/order_warning.md new file mode 100644 index 0000000..2a7f01a --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/order_warning.md @@ -0,0 +1,12 @@ +This type requires that the keys +implement the [`Ord`] trait. This can frequently be achieved by +using `#[derive(PartialOrd, Ord)]`. + +It is a logic error for a key to be modified in such a way that the key's +order, as determined by the [`Ord`] trait, or its equality, as determined by +the [`Eq`] trait, changes while it is in the map. This is normally only +possible through [`core::cell::Cell`], [`core::cell::RefCell`], global state, I/O, or unsafe code. + +The behavior resulting from the above logic error can include panics, incorrect results, +memory leaks, and non-termination. + diff --git a/frozen-collections-core/src/doc_snippets/type_compat_warning.md b/frozen-collections-core/src/doc_snippets/type_compat_warning.md new file mode 100644 index 0000000..889bb45 --- /dev/null +++ b/frozen-collections-core/src/doc_snippets/type_compat_warning.md @@ -0,0 +1,6 @@ +
+This type is an implementation detail of the `frozen_collections` crate. +This API is therefore not stable and may change at any time. Please do not +use this type directly, and instead use the public API provided by the +`frozen_collections` crate. +
diff --git a/frozen-collections-core/src/facade_maps/facade_hash_map.rs b/frozen-collections-core/src/facade_maps/facade_hash_map.rs new file mode 100644 index 0000000..5b2de00 --- /dev/null +++ b/frozen-collections-core/src/facade_maps/facade_hash_map.rs @@ -0,0 +1,349 @@ +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +use crate::hashers::BridgeHasher; +use crate::maps::{ + HashMap, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, ScanMap, Values, ValuesMut, +}; +use crate::traits::{Hasher, LargeCollection, Len, Map, MapIterator}; +use crate::utils::dedup_by_hash_keep_last; + +#[derive(Clone)] +enum MapTypes { + Hash(HashMap), + Scanning(ScanMap), +} + +/// A map optimized for fast read access with hashable keys. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/hash_warning.md")] +/// +/// # Alternate Choices +/// +/// If your keys are integers or enum variants, you should use the [`FacadeScalarMap`](crate::facade_maps::FacadeScalarMap) type instead. +/// If your keys are strings, you should use the [`FacadeStringMap`](crate::facade_maps::FacadeStringMap) type instead. Both of these will +/// deliver better performance since they are specifically optimized for those key types. +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct FacadeHashMap { + map_impl: MapTypes, +} + +impl FacadeHashMap +where + K: Eq, + H: Hasher, +{ + /// Creates a frozen map which uses the given hash builder to hash keys. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn new(mut entries: Vec<(K, V)>, hasher: H) -> Self { + dedup_by_hash_keep_last(&mut entries, &hasher); + + Self { + map_impl: if entries.len() < 3 { + MapTypes::Scanning(ScanMap::new_raw(entries)) + } else { + MapTypes::Hash(HashMap::new_half_baked(entries, hasher).unwrap()) + }, + } + } +} + +impl FacadeHashMap { + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline(always)] + #[must_use] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: ?Sized + Eq, + H: Hasher, + { + match &self.map_impl { + MapTypes::Hash(m) => m.get(key), + MapTypes::Scanning(m) => m.get(key), + } + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: ?Sized + Eq, + H: Hasher, + { + match &self.map_impl { + MapTypes::Hash(m) => m.get_key_value(key), + MapTypes::Scanning(m) => m.get_key_value(key), + } + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: ?Sized + Eq, + H: Hasher, + { + match &mut self.map_impl { + MapTypes::Hash(m) => m.get_mut(key), + MapTypes::Scanning(m) => m.get_mut(key), + } + } + + #[doc = include_str!("../doc_snippets/get_many_mut_method.md")] + #[must_use] + pub fn get_many_mut(&mut self, keys: [&Q; N]) -> Option<[&mut V; N]> + where + K: Borrow, + Q: ?Sized + Eq, + H: Hasher, + { + match &mut self.map_impl { + MapTypes::Hash(m) => m.get_many_mut(keys), + MapTypes::Scanning(m) => m.get_many_mut(keys), + } + } + + #[doc = include_str!("../doc_snippets/contains_key_method.md")] + #[inline] + #[must_use] + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + Q: ?Sized + Eq, + H: Hasher, + { + self.get(key).is_some() + } +} + +impl Len for FacadeHashMap { + fn len(&self) -> usize { + match &self.map_impl { + MapTypes::Hash(m) => m.len(), + MapTypes::Scanning(m) => m.len(), + } + } +} + +impl Index<&Q> for FacadeHashMap +where + K: Borrow, + Q: ?Sized + Eq, + H: Hasher, +{ + type Output = V; + + fn index(&self, index: &Q) -> &Self::Output { + self.get(index).expect("index should be valid") + } +} + +impl Default for FacadeHashMap +where + H: Default, +{ + fn default() -> Self { + Self { + map_impl: MapTypes::Hash(HashMap::::default()), + } + } +} + +impl Debug for FacadeHashMap +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match &self.map_impl { + MapTypes::Hash(m) => m.fmt(f), + MapTypes::Scanning(m) => m.fmt(f), + } + } +} + +impl PartialEq for FacadeHashMap +where + K: Eq, + V: PartialEq, + MT: Map, + H: Hasher, +{ + fn eq(&self, other: &MT) -> bool { + if self.len() != other.len() { + return false; + } + + self.iter() + .all(|(key, value)| other.get(key).map_or(false, |v| *value == *v)) + } +} + +impl Eq for FacadeHashMap +where + K: Eq, + V: Eq, + H: Hasher, +{ +} + +impl<'a, K, V, H> IntoIterator for &'a FacadeHashMap { + type Item = (&'a K, &'a V); + type IntoIter = Iter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, K, V, H> IntoIterator for &'a mut FacadeHashMap { + type Item = (&'a K, &'a mut V); + type IntoIter = IterMut<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl IntoIterator for FacadeHashMap { + type Item = (K, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self.map_impl { + MapTypes::Hash(m) => m.into_iter(), + MapTypes::Scanning(m) => m.into_iter(), + } + } +} + +impl MapIterator for FacadeHashMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a, + H: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a, + H: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a, + H: 'a; + + type IntoKeyIterator = IntoKeys; + type IntoValueIterator = IntoValues; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a, + H: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a, + H: 'a; + + fn iter(&self) -> Self::Iterator<'_> { + match &self.map_impl { + MapTypes::Hash(m) => m.iter(), + MapTypes::Scanning(m) => m.iter(), + } + } + + fn keys(&self) -> Self::KeyIterator<'_> { + match &self.map_impl { + MapTypes::Hash(m) => m.keys(), + MapTypes::Scanning(m) => m.keys(), + } + } + + fn values(&self) -> Self::ValueIterator<'_> { + match &self.map_impl { + MapTypes::Hash(m) => m.values(), + MapTypes::Scanning(m) => m.values(), + } + } + + fn into_keys(self) -> Self::IntoKeyIterator { + match self.map_impl { + MapTypes::Hash(m) => m.into_keys(), + MapTypes::Scanning(m) => m.into_keys(), + } + } + + fn into_values(self) -> Self::IntoValueIterator { + match self.map_impl { + MapTypes::Hash(m) => m.into_values(), + MapTypes::Scanning(m) => m.into_values(), + } + } + + fn iter_mut(&mut self) -> Self::MutIterator<'_> { + match &mut self.map_impl { + MapTypes::Hash(m) => m.iter_mut(), + MapTypes::Scanning(m) => m.iter_mut(), + } + } + + fn values_mut(&mut self) -> Self::ValueMutIterator<'_> { + match &mut self.map_impl { + MapTypes::Hash(m) => m.values_mut(), + MapTypes::Scanning(m) => m.values_mut(), + } + } +} + +impl Map for FacadeHashMap +where + K: Eq, + H: Hasher, +{ + #[inline] + fn contains_key(&self, key: &K) -> bool { + self.contains_key(key) + } + + #[inline] + fn get(&self, key: &K) -> Option<&V> { + self.get(key) + } + + #[inline] + fn get_key_value(&self, key: &K) -> Option<(&K, &V)> { + self.get_key_value(key) + } + + #[inline] + fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.get_mut(key) + } + + #[inline] + fn get_many_mut(&mut self, keys: [&K; N]) -> Option<[&mut V; N]> { + self.get_many_mut(keys) + } +} diff --git a/frozen-collections-core/src/facade_maps/facade_ordered_map.rs b/frozen-collections-core/src/facade_maps/facade_ordered_map.rs new file mode 100644 index 0000000..2bcdc43 --- /dev/null +++ b/frozen-collections-core/src/facade_maps/facade_ordered_map.rs @@ -0,0 +1,330 @@ +use crate::maps::{ + BinarySearchMap, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, OrderedScanMap, Values, + ValuesMut, +}; +use crate::traits::{Len, Map, MapIterator}; +use crate::utils::dedup_by_keep_last; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +#[derive(Clone)] +enum MapTypes { + BinarySearch(BinarySearchMap), + Scanning(OrderedScanMap), +} + +/// A map optimized for fast read access with ordered keys. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +/// # Alternate Choices +/// +/// If your keys are integers or enum variants, you should use the [`FacadeScalarMap`](crate::facade_maps::FacadeScalarMap) type instead. +/// If your keys are strings, you should use the [`FacadeStringMap`](crate::facade_maps::FacadeStringMap) type instead. Both of these will +/// deliver better performance since they are specifically optimized for those key types. +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct FacadeOrderedMap { + map_impl: MapTypes, +} + +impl FacadeOrderedMap +where + K: Ord + Eq, +{ + /// Creates a frozen ordered map. + #[must_use] + pub fn new(mut entries: Vec<(K, V)>) -> Self { + entries.sort_by(|x, y| x.0.cmp(&y.0)); + dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + + Self { + map_impl: if entries.len() < 5 { + MapTypes::Scanning(OrderedScanMap::new_raw(entries)) + } else { + MapTypes::BinarySearch(BinarySearchMap::new_raw(entries)) + }, + } + } +} + +impl FacadeOrderedMap { + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline(always)] + #[must_use] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: ?Sized + Ord + Eq, + { + match &self.map_impl { + MapTypes::BinarySearch(m) => m.get(key), + MapTypes::Scanning(m) => m.get(key), + } + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: ?Sized + Ord + Eq, + { + match &self.map_impl { + MapTypes::BinarySearch(m) => m.get_key_value(key), + MapTypes::Scanning(m) => m.get_key_value(key), + } + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: ?Sized + Ord + Eq, + { + match &mut self.map_impl { + MapTypes::BinarySearch(m) => m.get_mut(key), + MapTypes::Scanning(m) => m.get_mut(key), + } + } + + #[doc = include_str!("../doc_snippets/get_many_mut_method.md")] + #[must_use] + pub fn get_many_mut(&mut self, keys: [&Q; N]) -> Option<[&mut V; N]> + where + K: Borrow, + Q: ?Sized + Ord + Eq, + { + match &mut self.map_impl { + MapTypes::BinarySearch(m) => m.get_many_mut(keys), + MapTypes::Scanning(m) => m.get_many_mut(keys), + } + } + + #[doc = include_str!("../doc_snippets/contains_key_method.md")] + #[inline] + #[must_use] + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + Q: ?Sized + Ord + Eq, + { + self.get(key).is_some() + } +} + +impl Len for FacadeOrderedMap { + fn len(&self) -> usize { + match &self.map_impl { + MapTypes::BinarySearch(m) => m.len(), + MapTypes::Scanning(m) => m.len(), + } + } +} + +impl Index<&Q> for FacadeOrderedMap +where + K: Borrow, + Q: ?Sized + Ord + Eq, +{ + type Output = V; + + fn index(&self, index: &Q) -> &Self::Output { + self.get(index).expect("index should be valid") + } +} + +impl Default for FacadeOrderedMap { + fn default() -> Self { + Self { + map_impl: MapTypes::Scanning(OrderedScanMap::default()), + } + } +} + +impl Debug for FacadeOrderedMap +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match &self.map_impl { + MapTypes::BinarySearch(m) => m.fmt(f), + MapTypes::Scanning(m) => m.fmt(f), + } + } +} + +impl PartialEq for FacadeOrderedMap +where + K: Ord + Eq, + V: PartialEq, + MT: Map, +{ + fn eq(&self, other: &MT) -> bool { + if self.len() != other.len() { + return false; + } + + self.iter() + .all(|(key, value)| other.get(key).map_or(false, |v| *value == *v)) + } +} + +impl Eq for FacadeOrderedMap +where + K: Ord + Eq, + V: Eq, +{ +} + +impl<'a, K, V> IntoIterator for &'a FacadeOrderedMap { + type Item = (&'a K, &'a V); + type IntoIter = Iter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, K, V> IntoIterator for &'a mut FacadeOrderedMap { + type Item = (&'a K, &'a mut V); + type IntoIter = IterMut<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl IntoIterator for FacadeOrderedMap { + type Item = (K, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self.map_impl { + MapTypes::BinarySearch(m) => m.into_iter(), + MapTypes::Scanning(m) => m.into_iter(), + } + } +} + +impl MapIterator for FacadeOrderedMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type IntoKeyIterator = IntoKeys; + type IntoValueIterator = IntoValues; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + fn iter(&self) -> Self::Iterator<'_> { + match &self.map_impl { + MapTypes::BinarySearch(m) => m.iter(), + MapTypes::Scanning(m) => m.iter(), + } + } + + fn keys(&self) -> Self::KeyIterator<'_> { + match &self.map_impl { + MapTypes::BinarySearch(m) => m.keys(), + MapTypes::Scanning(m) => m.keys(), + } + } + + fn values(&self) -> Self::ValueIterator<'_> { + match &self.map_impl { + MapTypes::BinarySearch(m) => m.values(), + MapTypes::Scanning(m) => m.values(), + } + } + + fn into_keys(self) -> Self::IntoKeyIterator { + match self.map_impl { + MapTypes::BinarySearch(m) => m.into_keys(), + MapTypes::Scanning(m) => m.into_keys(), + } + } + + fn into_values(self) -> Self::IntoValueIterator { + match self.map_impl { + MapTypes::BinarySearch(m) => m.into_values(), + MapTypes::Scanning(m) => m.into_values(), + } + } + + fn iter_mut(&mut self) -> Self::MutIterator<'_> { + match &mut self.map_impl { + MapTypes::BinarySearch(m) => m.iter_mut(), + MapTypes::Scanning(m) => m.iter_mut(), + } + } + + fn values_mut(&mut self) -> Self::ValueMutIterator<'_> { + match &mut self.map_impl { + MapTypes::BinarySearch(m) => m.values_mut(), + MapTypes::Scanning(m) => m.values_mut(), + } + } +} + +impl Map for FacadeOrderedMap +where + K: Ord + Eq, +{ + #[inline] + fn contains_key(&self, key: &K) -> bool { + self.contains_key(key) + } + + #[inline] + fn get(&self, key: &K) -> Option<&V> { + self.get(key) + } + + #[inline] + fn get_key_value(&self, key: &K) -> Option<(&K, &V)> { + self.get_key_value(key) + } + + #[inline] + fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.get_mut(key) + } + + #[inline] + fn get_many_mut(&mut self, keys: [&K; N]) -> Option<[&mut V; N]> { + self.get_many_mut(keys) + } +} diff --git a/frozen-collections-core/src/facade_maps/facade_scalar_map.rs b/frozen-collections-core/src/facade_maps/facade_scalar_map.rs new file mode 100644 index 0000000..c9f0600 --- /dev/null +++ b/frozen-collections-core/src/facade_maps/facade_scalar_map.rs @@ -0,0 +1,349 @@ +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +use crate::analyzers::{analyze_scalar_keys, ScalarKeyAnalysisResult}; +use crate::hashers::PassthroughHasher; +use crate::maps::{ + DenseScalarLookupMap, HashMap, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, + SparseScalarLookupMap, Values, ValuesMut, +}; +use crate::traits::{LargeCollection, Len, Map, MapIterator, Scalar}; +use crate::utils::dedup_by_keep_last; + +#[derive(Clone)] +enum MapTypes { + Hash(HashMap), + Dense(DenseScalarLookupMap), + Sparse(SparseScalarLookupMap), +} + +/// A map optimized for fast read access using scalar keys. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct FacadeScalarMap { + map_impl: MapTypes, +} + +impl FacadeScalarMap +where + K: Scalar, +{ + /// Creates a frozen map. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn new(mut entries: Vec<(K, V)>) -> Self { + entries.sort_by(|x, y| x.0.cmp(&y.0)); + dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + + Self { + map_impl: match analyze_scalar_keys(entries.iter().map(|x| x.0)) { + ScalarKeyAnalysisResult::DenseRange => { + MapTypes::Dense(DenseScalarLookupMap::new_raw(entries)) + } + ScalarKeyAnalysisResult::SparseRange => { + MapTypes::Sparse(SparseScalarLookupMap::new_raw(entries)) + } + ScalarKeyAnalysisResult::General => { + let h = PassthroughHasher::new(); + MapTypes::Hash(HashMap::new_half_baked(entries, h).unwrap()) + } + }, + } + } +} + +impl FacadeScalarMap { + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline(always)] + #[must_use] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Scalar, + { + match &self.map_impl { + MapTypes::Hash(m) => m.get(key), + MapTypes::Dense(m) => m.get(key), + MapTypes::Sparse(m) => m.get(key), + } + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: Scalar, + { + match &self.map_impl { + MapTypes::Hash(m) => m.get_key_value(key), + MapTypes::Dense(m) => m.get_key_value(key), + MapTypes::Sparse(m) => m.get_key_value(key), + } + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Scalar, + { + match &mut self.map_impl { + MapTypes::Hash(m) => m.get_mut(key), + MapTypes::Dense(m) => m.get_mut(key), + MapTypes::Sparse(m) => m.get_mut(key), + } + } + + #[doc = include_str!("../doc_snippets/get_many_mut_method.md")] + #[must_use] + pub fn get_many_mut(&mut self, keys: [&Q; N]) -> Option<[&mut V; N]> + where + K: Borrow, + Q: Scalar, + { + match &mut self.map_impl { + MapTypes::Hash(m) => m.get_many_mut(keys), + MapTypes::Dense(m) => m.get_many_mut(keys), + MapTypes::Sparse(m) => m.get_many_mut(keys), + } + } + + #[doc = include_str!("../doc_snippets/contains_key_method.md")] + #[inline] + #[must_use] + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + Q: Scalar, + { + self.get(key).is_some() + } +} + +impl Len for FacadeScalarMap { + fn len(&self) -> usize { + match &self.map_impl { + MapTypes::Hash(m) => m.len(), + MapTypes::Dense(m) => m.len(), + MapTypes::Sparse(m) => m.len(), + } + } +} + +impl Index<&Q> for FacadeScalarMap +where + K: Borrow, + Q: Scalar, +{ + type Output = V; + + fn index(&self, index: &Q) -> &Self::Output { + self.get(index).expect("index should be valid") + } +} + +impl Default for FacadeScalarMap { + fn default() -> Self { + Self { + map_impl: MapTypes::Dense(DenseScalarLookupMap::default()), + } + } +} + +impl Debug for FacadeScalarMap +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match &self.map_impl { + MapTypes::Hash(m) => m.fmt(f), + MapTypes::Dense(m) => m.fmt(f), + MapTypes::Sparse(m) => m.fmt(f), + } + } +} + +impl PartialEq for FacadeScalarMap +where + K: Scalar, + V: PartialEq, + MT: Map, +{ + fn eq(&self, other: &MT) -> bool { + if self.len() != other.len() { + return false; + } + + self.iter() + .all(|(key, value)| other.get(key).map_or(false, |v| *value == *v)) + } +} + +impl Eq for FacadeScalarMap +where + K: Scalar, + V: Eq, +{ +} + +impl<'a, K, V> IntoIterator for &'a FacadeScalarMap { + type Item = (&'a K, &'a V); + type IntoIter = Iter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, K, V> IntoIterator for &'a mut FacadeScalarMap { + type Item = (&'a K, &'a mut V); + type IntoIter = IterMut<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl IntoIterator for FacadeScalarMap { + type Item = (K, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self.map_impl { + MapTypes::Hash(m) => m.into_iter(), + MapTypes::Dense(m) => m.into_iter(), + MapTypes::Sparse(m) => m.into_iter(), + } + } +} + +impl MapIterator for FacadeScalarMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type IntoKeyIterator = IntoKeys; + type IntoValueIterator = IntoValues; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + fn iter(&self) -> Self::Iterator<'_> { + match &self.map_impl { + MapTypes::Hash(m) => m.iter(), + MapTypes::Dense(m) => m.iter(), + MapTypes::Sparse(m) => m.iter(), + } + } + + fn keys(&self) -> Self::KeyIterator<'_> { + match &self.map_impl { + MapTypes::Hash(m) => m.keys(), + MapTypes::Dense(m) => m.keys(), + MapTypes::Sparse(m) => m.keys(), + } + } + + fn values(&self) -> Self::ValueIterator<'_> { + match &self.map_impl { + MapTypes::Hash(m) => m.values(), + MapTypes::Dense(m) => m.values(), + MapTypes::Sparse(m) => m.values(), + } + } + + fn into_keys(self) -> Self::IntoKeyIterator { + match self.map_impl { + MapTypes::Hash(m) => m.into_keys(), + MapTypes::Dense(m) => m.into_keys(), + MapTypes::Sparse(m) => m.into_keys(), + } + } + + fn into_values(self) -> Self::IntoValueIterator { + match self.map_impl { + MapTypes::Hash(m) => m.into_values(), + MapTypes::Dense(m) => m.into_values(), + MapTypes::Sparse(m) => m.into_values(), + } + } + + fn iter_mut(&mut self) -> Self::MutIterator<'_> { + match &mut self.map_impl { + MapTypes::Hash(m) => m.iter_mut(), + MapTypes::Dense(m) => m.iter_mut(), + MapTypes::Sparse(m) => m.iter_mut(), + } + } + + fn values_mut(&mut self) -> Self::ValueMutIterator<'_> { + match &mut self.map_impl { + MapTypes::Hash(m) => m.values_mut(), + MapTypes::Dense(m) => m.values_mut(), + MapTypes::Sparse(m) => m.values_mut(), + } + } +} + +impl Map for FacadeScalarMap +where + K: Scalar, +{ + #[inline] + fn contains_key(&self, key: &K) -> bool { + self.contains_key(key) + } + + #[inline] + fn get(&self, key: &K) -> Option<&V> { + self.get(key) + } + + #[inline] + fn get_key_value(&self, key: &K) -> Option<(&K, &V)> { + self.get_key_value(key) + } + + #[inline] + fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.get_mut(key) + } + + #[inline] + fn get_many_mut(&mut self, keys: [&K; N]) -> Option<[&mut V; N]> { + self.get_many_mut(keys) + } +} diff --git a/frozen-collections-core/src/facade_maps/facade_string_map.rs b/frozen-collections-core/src/facade_maps/facade_string_map.rs new file mode 100644 index 0000000..27e2594 --- /dev/null +++ b/frozen-collections-core/src/facade_maps/facade_string_map.rs @@ -0,0 +1,337 @@ +use alloc::string::String; +use alloc::vec::Vec; +use core::fmt::{Debug, Formatter, Result}; +use core::hash::BuildHasher; +use core::ops::Index; + +use ahash::RandomState; + +use crate::analyzers::{analyze_slice_keys, SliceKeyAnalysisResult}; +use crate::hashers::{BridgeHasher, LeftRangeHasher, RightRangeHasher}; +use crate::maps::{ + HashMap, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut, +}; +use crate::traits::{LargeCollection, Len, Map, MapIterator}; +use crate::utils::dedup_by_keep_last; + +#[derive(Clone)] +enum MapTypes { + LeftRange(HashMap>), + RightRange(HashMap>), + Hash(HashMap>), +} + +/// A map optimized for fast read access using string keys. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct FacadeStringMap { + map_impl: MapTypes, +} + +impl FacadeStringMap +where + BH: BuildHasher, +{ + /// Creates a frozen map which will use the given hash builder to hash + /// keys. + #[must_use] + #[allow(clippy::missing_panics_doc)] + pub fn new(mut entries: Vec<(String, V)>, bh: BH) -> Self { + entries.sort_by(|x, y| x.0.cmp(&y.0)); + dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + + Self { + map_impl: { + match analyze_slice_keys(entries.iter().map(|x| x.0.as_bytes()), &bh) { + SliceKeyAnalysisResult::General | SliceKeyAnalysisResult::Length => { + let h = BridgeHasher::new(bh); + MapTypes::Hash(HashMap::new_half_baked(entries, h).unwrap()) + } + + SliceKeyAnalysisResult::LeftHandSubslice(range) => { + let h = LeftRangeHasher::new(bh, range); + MapTypes::LeftRange(HashMap::new_half_baked(entries, h).unwrap()) + } + + SliceKeyAnalysisResult::RightHandSubslice(range) => { + let h = RightRangeHasher::new(bh, range); + MapTypes::RightRange(HashMap::new_half_baked(entries, h).unwrap()) + } + } + }, + } + } + + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline(always)] + #[must_use] + pub fn get(&self, key: &str) -> Option<&V> { + match &self.map_impl { + MapTypes::LeftRange(m) => m.get(key), + MapTypes::RightRange(m) => m.get(key), + MapTypes::Hash(m) => m.get(key), + } + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &str) -> Option<(&String, &V)> { + match &self.map_impl { + MapTypes::LeftRange(m) => m.get_key_value(key), + MapTypes::RightRange(m) => m.get_key_value(key), + MapTypes::Hash(m) => m.get_key_value(key), + } + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &str) -> Option<&mut V> { + match &mut self.map_impl { + MapTypes::LeftRange(m) => m.get_mut(key), + MapTypes::RightRange(m) => m.get_mut(key), + MapTypes::Hash(m) => m.get_mut(key), + } + } + + #[doc = include_str!("../doc_snippets/get_many_mut_method.md")] + #[must_use] + pub fn get_many_mut(&mut self, keys: [&str; N]) -> Option<[&mut V; N]> { + match &mut self.map_impl { + MapTypes::LeftRange(m) => m.get_many_mut(keys), + MapTypes::RightRange(m) => m.get_many_mut(keys), + MapTypes::Hash(m) => m.get_many_mut(keys), + } + } + + #[doc = include_str!("../doc_snippets/contains_key_method.md")] + #[inline] + #[must_use] + pub fn contains_key(&self, key: &str) -> bool { + self.get(key).is_some() + } +} + +impl Len for FacadeStringMap { + fn len(&self) -> usize { + match &self.map_impl { + MapTypes::LeftRange(m) => m.len(), + MapTypes::RightRange(m) => m.len(), + MapTypes::Hash(m) => m.len(), + } + } +} + +impl Index<&String> for FacadeStringMap +where + BH: BuildHasher, +{ + type Output = V; + + fn index(&self, index: &String) -> &Self::Output { + self.get(index).expect("index should be valid") + } +} + +impl Default for FacadeStringMap +where + BH: Default, +{ + fn default() -> Self { + Self { + map_impl: MapTypes::Hash(HashMap::default()), + } + } +} + +impl Debug for FacadeStringMap +where + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match &self.map_impl { + MapTypes::LeftRange(m) => m.fmt(f), + MapTypes::RightRange(m) => m.fmt(f), + MapTypes::Hash(m) => m.fmt(f), + } + } +} + +impl PartialEq for FacadeStringMap +where + V: PartialEq, + MT: Map, + BH: BuildHasher, +{ + fn eq(&self, other: &MT) -> bool { + if self.len() != other.len() { + return false; + } + + self.iter() + .all(|(key, value)| other.get(key).map_or(false, |v| *value == *v)) + } +} + +impl Eq for FacadeStringMap +where + V: Eq, + BH: BuildHasher, +{ +} + +impl<'a, V, BH> IntoIterator for &'a FacadeStringMap { + type Item = (&'a String, &'a V); + type IntoIter = Iter<'a, String, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, V, BH> IntoIterator for &'a mut FacadeStringMap { + type Item = (&'a String, &'a mut V); + type IntoIter = IterMut<'a, String, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl IntoIterator for FacadeStringMap { + type Item = (String, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self.map_impl { + MapTypes::LeftRange(m) => m.into_iter(), + MapTypes::RightRange(m) => m.into_iter(), + MapTypes::Hash(m) => m.into_iter(), + } + } +} + +impl MapIterator for FacadeStringMap { + type Iterator<'a> + = Iter<'a, String, V> + where + V: 'a, + BH: 'a; + + type KeyIterator<'a> + = Keys<'a, String, V> + where + V: 'a, + BH: 'a; + + type ValueIterator<'a> + = Values<'a, String, V> + where + V: 'a, + BH: 'a; + + type IntoKeyIterator = IntoKeys; + type IntoValueIterator = IntoValues; + + type MutIterator<'a> + = IterMut<'a, String, V> + where + V: 'a, + BH: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, String, V> + where + V: 'a, + BH: 'a; + + fn iter(&self) -> Self::Iterator<'_> { + match &self.map_impl { + MapTypes::LeftRange(m) => m.iter(), + MapTypes::RightRange(m) => m.iter(), + MapTypes::Hash(m) => m.iter(), + } + } + + fn keys(&self) -> Self::KeyIterator<'_> { + match &self.map_impl { + MapTypes::LeftRange(m) => m.keys(), + MapTypes::RightRange(m) => m.keys(), + MapTypes::Hash(m) => m.keys(), + } + } + + fn values(&self) -> Self::ValueIterator<'_> { + match &self.map_impl { + MapTypes::LeftRange(m) => m.values(), + MapTypes::RightRange(m) => m.values(), + MapTypes::Hash(m) => m.values(), + } + } + + fn into_keys(self) -> Self::IntoKeyIterator { + match self.map_impl { + MapTypes::LeftRange(m) => m.into_keys(), + MapTypes::RightRange(m) => m.into_keys(), + MapTypes::Hash(m) => m.into_keys(), + } + } + + fn into_values(self) -> Self::IntoValueIterator { + match self.map_impl { + MapTypes::LeftRange(m) => m.into_values(), + MapTypes::RightRange(m) => m.into_values(), + MapTypes::Hash(m) => m.into_values(), + } + } + + fn iter_mut(&mut self) -> Self::MutIterator<'_> { + match &mut self.map_impl { + MapTypes::LeftRange(m) => m.iter_mut(), + MapTypes::RightRange(m) => m.iter_mut(), + MapTypes::Hash(m) => m.iter_mut(), + } + } + + fn values_mut(&mut self) -> Self::ValueMutIterator<'_> { + match &mut self.map_impl { + MapTypes::LeftRange(m) => m.values_mut(), + MapTypes::RightRange(m) => m.values_mut(), + MapTypes::Hash(m) => m.values_mut(), + } + } +} + +impl Map for FacadeStringMap +where + BH: BuildHasher, +{ + #[inline] + fn contains_key(&self, key: &String) -> bool { + self.contains_key(key) + } + + #[inline] + fn get(&self, key: &String) -> Option<&V> { + self.get(key) + } + + #[inline] + fn get_key_value(&self, key: &String) -> Option<(&String, &V)> { + self.get_key_value(key) + } + + #[inline] + fn get_mut(&mut self, key: &String) -> Option<&mut V> { + self.get_mut(key) + } + + #[inline] + fn get_many_mut(&mut self, keys: [&String; N]) -> Option<[&mut V; N]> { + self.get_many_mut(keys.map(String::as_str)) + } +} diff --git a/frozen-collections-core/src/facade_maps/mod.rs b/frozen-collections-core/src/facade_maps/mod.rs new file mode 100644 index 0000000..5c080d4 --- /dev/null +++ b/frozen-collections-core/src/facade_maps/mod.rs @@ -0,0 +1,11 @@ +//! Wrappers around other map types allowing runtime selection of implementation type based on input. + +pub use facade_hash_map::FacadeHashMap; +pub use facade_ordered_map::FacadeOrderedMap; +pub use facade_scalar_map::FacadeScalarMap; +pub use facade_string_map::FacadeStringMap; + +mod facade_hash_map; +mod facade_ordered_map; +mod facade_scalar_map; +mod facade_string_map; diff --git a/frozen-collections-core/src/facade_sets/facade_hash_set.rs b/frozen-collections-core/src/facade_sets/facade_hash_set.rs new file mode 100644 index 0000000..074f9df --- /dev/null +++ b/frozen-collections-core/src/facade_sets/facade_hash_set.rs @@ -0,0 +1,169 @@ +use crate::facade_maps::FacadeHashMap; +use crate::hashers::BridgeHasher; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, debug_fn, into_iter_fn, into_iter_ref_fn, partial_eq_fn, + set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Hasher, Len, MapIterator, Set, SetIterator}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A set optimized for fast read access with hashable values. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/hash_warning.md")] +/// +/// # Alternate Choices +/// +/// If your values are integers or enum variants, you should use the [`FacadeScalarSet`](crate::facade_sets::FacadeScalarSet) type instead. +/// If your values are strings, you should use the [`FacadeStringSet`](crate::facade_sets::FacadeStringSet) type instead. Both of these will +/// deliver better performance since they are specifically optimized for those value types. +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct FacadeHashSet { + map: FacadeHashMap, +} + +impl FacadeHashSet +where + T: Eq, + H: Hasher, +{ + /// Creates a new frozen set which uses the given hash builder to hash values. + #[must_use] + pub const fn new(map: FacadeHashMap) -> Self { + Self { map } + } +} + +impl FacadeHashSet { + #[doc = include_str!("../doc_snippets/get_from_set_method.md")] + #[inline] + #[must_use] + pub fn get(&self, value: &Q) -> Option<&T> + where + T: Borrow, + H: Hasher, + Q: ?Sized + Eq, + { + Some(self.map.get_key_value(value)?.0) + } + + #[doc = include_str!("../doc_snippets/contains_method.md")] + #[inline] + #[must_use] + pub fn contains(&self, value: &Q) -> bool + where + T: Borrow, + H: Hasher, + Q: ?Sized + Eq, + { + self.get(value).is_some() + } +} + +impl Len for FacadeHashSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for FacadeHashSet +where + T: Debug, +{ + debug_fn!(); +} + +impl Default for FacadeHashSet +where + H: Default, +{ + fn default() -> Self { + Self { + map: FacadeHashMap::default(), + } + } +} + +impl IntoIterator for FacadeHashSet { + into_iter_fn!(); +} + +impl<'a, T, H> IntoIterator for &'a FacadeHashSet { + into_iter_ref_fn!(); +} + +impl SetIterator for FacadeHashSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a, + H: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for FacadeHashSet +where + T: Eq, + H: Hasher, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &FacadeHashSet +where + T: Hash + Eq + Clone, + ST: Set, + H: Hasher + Default, +{ + bitor_fn!(H); +} + +impl BitAnd<&ST> for &FacadeHashSet +where + T: Hash + Eq + Clone, + ST: Set, + H: Hasher + Default, +{ + bitand_fn!(H); +} + +impl BitXor<&ST> for &FacadeHashSet +where + T: Hash + Eq + Clone, + ST: Set, + H: Hasher + Default, +{ + bitxor_fn!(H); +} + +impl Sub<&ST> for &FacadeHashSet +where + T: Hash + Eq + Clone, + ST: Set, + H: Hasher + Default, +{ + sub_fn!(H); +} + +impl PartialEq for FacadeHashSet +where + T: Eq, + ST: Set, + H: Hasher, +{ + partial_eq_fn!(); +} + +impl Eq for FacadeHashSet +where + T: Eq, + H: Hasher, +{ +} diff --git a/frozen-collections-core/src/facade_sets/facade_ordered_set.rs b/frozen-collections-core/src/facade_sets/facade_ordered_set.rs new file mode 100644 index 0000000..2607cc5 --- /dev/null +++ b/frozen-collections-core/src/facade_sets/facade_ordered_set.rs @@ -0,0 +1,131 @@ +use crate::facade_maps::FacadeOrderedMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Set, SetIterator}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A set optimized for fast read access with ordered values. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +/// # Alternate Choices +/// +/// If your values are integers or enum variants, you should use the [`FacadeScalarSet`](crate::facade_sets::FacadeScalarSet) type instead. +/// If your values are strings, you should use the [`FacadeStringSet`](crate::facade_sets::FacadeStringSet) type instead. Both of these will +/// deliver better performance since they are specifically optimized for those value types. +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct FacadeOrderedSet { + map: FacadeOrderedMap, +} + +impl FacadeOrderedSet +where + T: Ord + Eq, +{ + /// Creates a new frozen ordered set. + #[must_use] + pub const fn new(map: FacadeOrderedMap) -> Self { + Self { map } + } +} + +impl FacadeOrderedSet { + get_fn!(Ord); + contains_fn!(Ord); +} + +impl Len for FacadeOrderedSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for FacadeOrderedSet +where + T: Debug, +{ + debug_fn!(); +} + +impl Default for FacadeOrderedSet { + fn default() -> Self { + Self { + map: FacadeOrderedMap::default(), + } + } +} + +impl IntoIterator for FacadeOrderedSet { + into_iter_fn!(); +} + +impl<'a, T> IntoIterator for &'a FacadeOrderedSet { + into_iter_ref_fn!(); +} + +impl SetIterator for FacadeOrderedSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for FacadeOrderedSet +where + T: Ord, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &FacadeOrderedSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &FacadeOrderedSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &FacadeOrderedSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &FacadeOrderedSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for FacadeOrderedSet +where + T: Ord, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for FacadeOrderedSet where T: Ord {} diff --git a/frozen-collections-core/src/facade_sets/facade_scalar_set.rs b/frozen-collections-core/src/facade_sets/facade_scalar_set.rs new file mode 100644 index 0000000..6798b1b --- /dev/null +++ b/frozen-collections-core/src/facade_sets/facade_scalar_set.rs @@ -0,0 +1,125 @@ +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +use crate::facade_maps::FacadeScalarMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Scalar, Set, SetIterator}; + +/// A set optimized for fast read access with integer or enum values. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct FacadeScalarSet { + map: FacadeScalarMap, +} + +impl FacadeScalarSet +where + T: Scalar, +{ + /// Creates a new frozen set. + #[must_use] + pub const fn new(map: FacadeScalarMap) -> Self { + Self { map } + } +} + +impl FacadeScalarSet { + get_fn!(Scalar); + contains_fn!(Scalar); +} + +impl Len for FacadeScalarSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for FacadeScalarSet +where + T: Debug, +{ + debug_fn!(); +} + +impl Default for FacadeScalarSet { + fn default() -> Self { + Self { + map: FacadeScalarMap::default(), + } + } +} + +impl IntoIterator for FacadeScalarSet { + into_iter_fn!(); +} + +impl<'a, T> IntoIterator for &'a FacadeScalarSet { + into_iter_ref_fn!(); +} + +impl SetIterator for FacadeScalarSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for FacadeScalarSet +where + T: Scalar, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &FacadeScalarSet +where + T: Hash + Eq + Scalar + Clone, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &FacadeScalarSet +where + T: Hash + Eq + Scalar + Clone, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &FacadeScalarSet +where + T: Hash + Eq + Scalar + Clone, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &FacadeScalarSet +where + T: Hash + Eq + Scalar + Clone, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for FacadeScalarSet +where + T: Scalar, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for FacadeScalarSet where T: Scalar {} diff --git a/frozen-collections-core/src/facade_sets/facade_string_set.rs b/frozen-collections-core/src/facade_sets/facade_string_set.rs new file mode 100644 index 0000000..21ccd84 --- /dev/null +++ b/frozen-collections-core/src/facade_sets/facade_string_set.rs @@ -0,0 +1,162 @@ +use crate::facade_maps::FacadeStringMap; +use crate::sets::decl_macros::{debug_fn, partial_eq_fn}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Set, SetIterator}; +use ahash::RandomState; +use core::fmt::Debug; +use core::hash::BuildHasher; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A set optimized for fast read access with string values. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct FacadeStringSet { + map: FacadeStringMap<(), BH>, +} + +impl FacadeStringSet +where + BH: BuildHasher, +{ + /// Creates a new frozen set which will use the given hasher to hash values. + #[must_use] + pub const fn new(map: FacadeStringMap<(), BH>) -> Self { + Self { map } + } + + #[doc = include_str!("../doc_snippets/contains_method.md")] + #[inline(always)] + #[must_use] + pub fn contains(&self, value: &str) -> bool { + self.get(value).is_some() + } + + #[doc = include_str!("../doc_snippets/get_from_set_method.md")] + #[inline] + #[must_use] + pub fn get(&self, value: &str) -> Option<&String> { + Some(self.map.get_key_value(value)?.0) + } +} + +impl Len for FacadeStringSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for FacadeStringSet { + debug_fn!(); +} + +impl Default for FacadeStringSet +where + BH: Default, +{ + fn default() -> Self { + Self { + map: FacadeStringMap::default(), + } + } +} + +impl IntoIterator for FacadeStringSet { + type Item = String; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let it = self.map.into_iter(); + IntoIter::new(it) + } +} + +impl<'a, BH> IntoIterator for &'a FacadeStringSet { + type Item = &'a String; + type IntoIter = Iter<'a, String>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl SetIterator for FacadeStringSet { + type Iterator<'a> + = Iter<'a, String> + where + String: 'a, + BH: 'a; + + fn iter(&self) -> Iter<'_, String> { + Iter::new(self.map.iter()) + } +} + +impl Set for FacadeStringSet +where + BH: BuildHasher, +{ + fn contains(&self, value: &String) -> bool { + self.contains(value) + } +} + +impl BitOr<&ST> for &FacadeStringSet +where + ST: Set, + BH: BuildHasher + Default, +{ + type Output = hashbrown::HashSet; + + fn bitor(self, rhs: &ST) -> Self::Output { + Self::Output::from_iter(self.union(rhs).cloned()) + } +} + +impl BitAnd<&ST> for &FacadeStringSet +where + ST: Set, + BH: BuildHasher + Default, +{ + type Output = hashbrown::HashSet; + + fn bitand(self, rhs: &ST) -> Self::Output { + Self::Output::from_iter(self.intersection(rhs).cloned()) + } +} + +impl BitXor<&ST> for &FacadeStringSet +where + ST: Set, + BH: BuildHasher + Default, +{ + type Output = hashbrown::HashSet; + + fn bitxor(self, rhs: &ST) -> Self::Output { + self.symmetric_difference(rhs).cloned().collect() + } +} + +impl Sub<&ST> for &FacadeStringSet +where + ST: Set, + BH: BuildHasher + Default, +{ + type Output = hashbrown::HashSet; + + fn sub(self, rhs: &ST) -> Self::Output { + self.difference(rhs).cloned().collect() + } +} + +impl PartialEq for FacadeStringSet +where + ST: Set, + BH: BuildHasher, +{ + partial_eq_fn!(); +} + +impl Eq for FacadeStringSet where BH: BuildHasher {} diff --git a/frozen-collections-core/src/facade_sets/mod.rs b/frozen-collections-core/src/facade_sets/mod.rs new file mode 100644 index 0000000..1519825 --- /dev/null +++ b/frozen-collections-core/src/facade_sets/mod.rs @@ -0,0 +1,11 @@ +//! Wrappers around other set types allowing runtime selection of implementation type based on input. + +pub use facade_hash_set::FacadeHashSet; +pub use facade_ordered_set::FacadeOrderedSet; +pub use facade_scalar_set::FacadeScalarSet; +pub use facade_string_set::FacadeStringSet; + +mod facade_hash_set; +mod facade_ordered_set; +mod facade_scalar_set; +mod facade_string_set; diff --git a/frozen-collections-core/src/hash_tables/hash_table.rs b/frozen-collections-core/src/hash_tables/hash_table.rs new file mode 100644 index 0000000..9845414 --- /dev/null +++ b/frozen-collections-core/src/hash_tables/hash_table.rs @@ -0,0 +1,186 @@ +use std::fmt::{Debug, Formatter, Result}; +use std::mem::MaybeUninit; +use std::ops::Range; + +use crate::analyzers::analyze_hash_codes; +use crate::hash_tables::HashTableSlot; +use crate::traits::{CollectionMagnitude, Len, SmallCollection}; +use bitvec::macros::internal::funty::Fundamental; + +/// A general purpose hash table. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +/// +/// The `CM` type parameter is the collection magnitude, which +/// determines the maximum number of entries that can be stored in the hash table. +/// +/// This implementation always has a power-of-two number of hash slots. This speeds up +/// lookups by avoiding the need to perform a modulo operation. +#[derive(Clone)] +pub struct HashTable { + pub mask: u64, + pub slots: Box<[HashTableSlot]>, + pub entries: Box<[T]>, +} + +struct PrepItem { + pub hash_slot_index: usize, + pub entry: T, +} + +impl HashTable +where + CM: CollectionMagnitude, +{ + /// Creates a new hash table. + /// + /// This function assumes that there are no duplicates in the input vector. + pub fn new(mut entries: Vec, hash: F) -> std::result::Result + where + F: Fn(&T) -> u64, + { + if entries.is_empty() { + return Ok(Self::default()); + } else if entries.len() > CM::MAX_CAPACITY { + return Err("too many entries for the selected collection magnitude".to_string()); + } + + let num_hash_slots = analyze_hash_codes(entries.iter().map(&hash)).num_hash_slots; + + let mut prep_items = Vec::with_capacity(entries.len()); + while let Some(entry) = entries.pop() { + let hash_code = hash(&entry); + let hash_slot_index = (hash_code % num_hash_slots as u64).as_usize(); + + prep_items.push(PrepItem { + hash_slot_index, + entry, + }); + } + + // sort items so hash collisions are contiguous. + prep_items.sort_unstable_by(|x, y| x.hash_slot_index.cmp(&y.hash_slot_index)); + + let mut entry_index = 0; + let mut slots = Vec::with_capacity(num_hash_slots); + let mut final_entries = entries; + + slots.resize_with(num_hash_slots, || HashTableSlot::new(CM::ZERO, CM::ZERO)); + + while let Some(mut item) = prep_items.pop() { + let hash_slot_index = item.hash_slot_index; + let mut num_entries_in_hash_slot = 0; + + loop { + final_entries.push(item.entry); + num_entries_in_hash_slot += 1; + + if let Some(last) = prep_items.last() { + if last.hash_slot_index == hash_slot_index { + item = prep_items.pop().unwrap(); + continue; + } + } + + break; + } + + slots[hash_slot_index] = HashTableSlot::new( + CM::try_from(entry_index).unwrap_or(CM::ZERO), + CM::try_from(entry_index + num_entries_in_hash_slot).unwrap_or(CM::ZERO), + ); + + entry_index += num_entries_in_hash_slot; + } + + Ok(Self { + mask: (slots.len() - 1) as u64, + slots: slots.into_boxed_slice(), + entries: final_entries.into_boxed_slice(), + }) + } +} + +impl HashTable +where + CM: CollectionMagnitude, +{ + #[inline] + pub fn find(&self, hash_code: u64, mut eq: impl FnMut(&T) -> bool) -> Option<&T> { + let hash_slot_index = (hash_code & self.mask).as_usize(); + let hash_slot = unsafe { self.slots.get_unchecked(hash_slot_index) }; + let range: Range = hash_slot.min_index.into()..hash_slot.max_index.into(); + let entries = unsafe { self.entries.get_unchecked(range) }; + entries.iter().find(|entry| eq(entry)) + } + + #[inline] + pub fn find_mut(&mut self, hash_code: u64, mut eq: impl FnMut(&T) -> bool) -> Option<&mut T> { + let hash_slot_index = (hash_code & self.mask).as_usize(); + let hash_slot = unsafe { self.slots.get_unchecked(hash_slot_index) }; + let range: Range = hash_slot.min_index.into()..hash_slot.max_index.into(); + let entries = unsafe { self.entries.get_unchecked_mut(range) }; + entries.iter_mut().find(|entry| eq(entry)) + } + + pub fn get_many_mut( + &mut self, + hashes: [u64; N], + mut eq: impl FnMut(usize, &T) -> bool, + ) -> Option<[&mut T; N]> { + let mut result: MaybeUninit<[&mut T; N]> = MaybeUninit::uninit(); + let p = result.as_mut_ptr(); + let x: *mut Self = self; + + for (i, hash_code) in hashes.into_iter().enumerate() { + unsafe { + (*p)[i] = (*x).find_mut(hash_code, |entry| eq(i, entry))?; + } + } + + let result = unsafe { result.assume_init() }; + + // make sure there are no duplicates + for i in 0..result.len() { + for j in 0..i { + let p0 = result[i] as *const T; + let p1 = result[j] as *const T; + + if p0 == p1 { + return None; + } + } + } + + Some(result) + } +} + +impl Len for HashTable { + #[inline] + fn len(&self) -> usize { + self.entries.len() + } +} + +impl Debug for HashTable +where + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_set().entries(&self.entries).finish() + } +} + +impl Default for HashTable +where + CM: CollectionMagnitude, +{ + fn default() -> Self { + Self { + mask: 0, + slots: vec![HashTableSlot::new(CM::ZERO, CM::ZERO)].into_boxed_slice(), + entries: Box::new([]), + } + } +} diff --git a/frozen-collections-core/src/hash_tables/hash_table_slot.rs b/frozen-collections-core/src/hash_tables/hash_table_slot.rs new file mode 100644 index 0000000..6880c8d --- /dev/null +++ b/frozen-collections-core/src/hash_tables/hash_table_slot.rs @@ -0,0 +1,21 @@ +/// An individual slot in a hash table. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +/// +/// A slot contains the range of indices in the table's entry vector +/// that contain entries that hash to this slot. +#[derive(Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct HashTableSlot { + pub(crate) min_index: CM, + pub(crate) max_index: CM, +} + +impl HashTableSlot { + pub const fn new(min_index: CM, max_index: CM) -> Self { + Self { + min_index, + max_index, + } + } +} diff --git a/frozen-collections-core/src/hash_tables/inline_hash_table.rs b/frozen-collections-core/src/hash_tables/inline_hash_table.rs new file mode 100644 index 0000000..1d43018 --- /dev/null +++ b/frozen-collections-core/src/hash_tables/inline_hash_table.rs @@ -0,0 +1,107 @@ +use crate::hash_tables::HashTableSlot; +use crate::traits::{CollectionMagnitude, SmallCollection}; +use bitvec::macros::internal::funty::Fundamental; +use core::fmt::{Debug, Formatter, Result}; +use core::mem::MaybeUninit; +use core::ops::Range; + +/// A hash table that stores its entries inline. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +/// +/// # Type Parameters +/// +/// - `T`: The data held in the hash table. +/// - `CM`: The magnitude of the collection. +/// - `SZ`: The length of the map. +/// - `NHS`: The number of hash table slots. +/// +/// This implementation always has a power-of-two number of hash slots. This speeds up +/// lookups by avoiding the need to perform a modulo operation. +#[derive(Clone)] +pub struct InlineHashTable { + mask: u64, + slots: [HashTableSlot; NHS], + pub(crate) entries: [T; SZ], +} + +impl InlineHashTable { + /// Creates a new hash table. + /// + /// This function assumes that the slots and processed entries are in proper order. + pub const fn new_raw(slots: [HashTableSlot; NHS], processed_entries: [T; SZ]) -> Self { + Self { + mask: (NHS - 1) as u64, + slots, + entries: processed_entries, + } + } +} + +impl InlineHashTable +where + CM: CollectionMagnitude, +{ + #[inline] + pub(crate) fn find(&self, hash_code: u64, mut eq: impl FnMut(&T) -> bool) -> Option<&T> { + let hash_slot_index = (hash_code & self.mask).as_usize(); + let hash_slot = unsafe { self.slots.get_unchecked(hash_slot_index) }; + let range: Range = hash_slot.min_index.into()..hash_slot.max_index.into(); + let entries = unsafe { self.entries.get_unchecked(range) }; + entries.iter().find(|entry| eq(entry)) + } + + #[inline] + pub(crate) fn find_mut( + &mut self, + hash_code: u64, + mut eq: impl FnMut(&T) -> bool, + ) -> Option<&mut T> { + let hash_slot_index = (hash_code & self.mask).as_usize(); + let hash_slot = unsafe { self.slots.get_unchecked(hash_slot_index) }; + let range: Range = hash_slot.min_index.into()..hash_slot.max_index.into(); + let entries = unsafe { self.entries.get_unchecked_mut(range) }; + entries.iter_mut().find(|entry| eq(entry)) + } + + pub(crate) fn get_many_mut( + &mut self, + hashes: [u64; N], + mut eq: impl FnMut(usize, &T) -> bool, + ) -> Option<[&mut T; N]> { + let mut result: MaybeUninit<[&mut T; N]> = MaybeUninit::uninit(); + let p = result.as_mut_ptr(); + let x: *mut Self = self; + + for (i, hash_code) in hashes.into_iter().enumerate() { + unsafe { + (*p)[i] = (*x).find_mut(hash_code, |entry| eq(i, entry))?; + } + } + + let result = unsafe { result.assume_init() }; + + // make sure there are no duplicates + for i in 0..result.len() { + for j in 0..i { + let p0 = result[i] as *const T; + let p1 = result[j] as *const T; + + if p0 == p1 { + return None; + } + } + } + + Some(result) + } +} + +impl Debug for InlineHashTable +where + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_set().entries(&self.entries).finish() + } +} diff --git a/frozen-collections-core/src/hash_tables/mod.rs b/frozen-collections-core/src/hash_tables/mod.rs new file mode 100644 index 0000000..0ae24a0 --- /dev/null +++ b/frozen-collections-core/src/hash_tables/mod.rs @@ -0,0 +1,9 @@ +//! Foundational hash table design. + +pub(crate) use crate::hash_tables::hash_table::HashTable; +pub use crate::hash_tables::hash_table_slot::HashTableSlot; +pub use crate::hash_tables::inline_hash_table::InlineHashTable; + +mod hash_table; +mod hash_table_slot; +mod inline_hash_table; diff --git a/frozen-collections-core/src/hashers/bridge_hasher.rs b/frozen-collections-core/src/hashers/bridge_hasher.rs new file mode 100644 index 0000000..1bf173c --- /dev/null +++ b/frozen-collections-core/src/hashers/bridge_hasher.rs @@ -0,0 +1,37 @@ +use crate::traits::Hasher; +use ahash::RandomState; +use core::hash::{BuildHasher, Hash}; + +/// Wraps a normal [`BuildHasher`]. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[derive(Clone)] +pub struct BridgeHasher { + bh: BH, +} + +impl BridgeHasher { + #[must_use] + pub const fn new(bh: BH) -> Self { + Self { bh } + } +} + +impl Hasher for BridgeHasher +where + T: ?Sized + Hash, + BH: BuildHasher, +{ + fn hash(&self, value: &T) -> u64 { + self.bh.hash_one(value) + } +} + +impl Default for BridgeHasher +where + BH: Default, +{ + fn default() -> Self { + Self::new(BH::default()) + } +} diff --git a/frozen-collections-core/src/hashers/inline_left_range_hasher.rs b/frozen-collections-core/src/hashers/inline_left_range_hasher.rs new file mode 100644 index 0000000..4b055e9 --- /dev/null +++ b/frozen-collections-core/src/hashers/inline_left_range_hasher.rs @@ -0,0 +1,117 @@ +use crate::traits::Hasher; +use ahash::RandomState; +use core::hash::{BuildHasher, Hash}; + +/// Hashes a portion of a left-aligned slice. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[derive(Clone)] +pub struct InlineLeftRangeHasher +{ + bh: BH, +} + +impl + InlineLeftRangeHasher +{ + #[must_use] + pub const fn new(bh: BH) -> Self { + Self { bh } + } +} + +impl Hasher<[T]> + for InlineLeftRangeHasher +where + T: Hash, + BH: BuildHasher, +{ + fn hash(&self, value: &[T]) -> u64 { + if value.len() < RANGE_END { + return 0; + } + + self.bh.hash_one(&value[RANGE_START..RANGE_END]) + } +} + +impl Hasher + for InlineLeftRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &String) -> u64 { + let b = value.as_bytes(); + if b.len() < RANGE_END { + return 0; + } + + self.bh.hash_one(&b[RANGE_START..RANGE_END]) + } +} + +impl Hasher<&str> + for InlineLeftRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &&str) -> u64 { + let b = value.as_bytes(); + if b.len() < RANGE_END { + return 0; + } + + self.bh.hash_one(&b[RANGE_START..RANGE_END]) + } +} + +impl Hasher + for InlineLeftRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &str) -> u64 { + let b = value.as_bytes(); + if b.len() < RANGE_END { + return 0; + } + + self.bh.hash_one(&b[RANGE_START..RANGE_END]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_left_range_hasher_hash_slice() { + let hasher = InlineLeftRangeHasher::<0, 3>::new(RandomState::default()); + assert_eq!( + hasher.hash(vec![1, 2, 3, 4].as_slice()), + hasher.bh.hash_one(vec![1, 2, 3].as_slice()) + ); + assert_eq!(hasher.hash(vec![1, 2].as_slice()), 0); + } + + #[test] + fn test_left_range_hasher_hash_string() { + let hasher = InlineLeftRangeHasher::<0, 3>::new(RandomState::default()); + assert_eq!(hasher.hash(&"abcd".to_string()), hasher.bh.hash_one(b"abc")); + assert_eq!(hasher.hash(&"ab".to_string()), 0); + } + + #[test] + fn test_left_range_hasher_hash_str_ref() { + let hasher = InlineLeftRangeHasher::<0, 3>::new(RandomState::default()); + assert_eq!(hasher.hash(&"abcd"), hasher.bh.hash_one(b"abc")); + assert_eq!(hasher.hash(&"ab"), 0); + } + + #[test] + fn test_left_range_hasher_hash_str() { + let hasher = InlineLeftRangeHasher::<0, 3>::new(RandomState::default()); + assert_eq!(hasher.hash("abcd"), hasher.bh.hash_one(b"abc")); + assert_eq!(hasher.hash("ab"), 0); + } +} diff --git a/frozen-collections-core/src/hashers/inline_right_range_hasher.rs b/frozen-collections-core/src/hashers/inline_right_range_hasher.rs new file mode 100644 index 0000000..93106e8 --- /dev/null +++ b/frozen-collections-core/src/hashers/inline_right_range_hasher.rs @@ -0,0 +1,124 @@ +use crate::traits::Hasher; +use ahash::RandomState; +use core::hash::{BuildHasher, Hash}; + +/// Hashes a portion of a right-aligned slice. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[derive(Clone)] +pub struct InlineRightRangeHasher< + const RANGE_START: usize, + const RANGE_END: usize, + BH = RandomState, +> { + bh: BH, +} + +impl + InlineRightRangeHasher +{ + #[must_use] + pub const fn new(bh: BH) -> Self { + Self { bh } + } +} + +impl Hasher<[T]> + for InlineRightRangeHasher +where + T: Hash, + BH: BuildHasher, +{ + fn hash(&self, value: &[T]) -> u64 { + if value.len() <= RANGE_END { + return 0; + } + + let effective_range = value.len() - RANGE_END..value.len() - RANGE_START; + self.bh.hash_one(&value[effective_range]) + } +} + +impl Hasher + for InlineRightRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &String) -> u64 { + let b = value.as_bytes(); + if b.len() < RANGE_END { + return 0; + } + + let effective_range = value.len() - RANGE_END..value.len() - RANGE_START; + self.bh.hash_one(&b[effective_range]) + } +} + +impl Hasher<&str> + for InlineRightRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &&str) -> u64 { + let b = value.as_bytes(); + if b.len() < RANGE_END { + return 0; + } + + let effective_range = value.len() - RANGE_END..value.len() - RANGE_START; + self.bh.hash_one(&b[effective_range]) + } +} + +impl Hasher + for InlineRightRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &str) -> u64 { + let b = value.as_bytes(); + if b.len() < RANGE_END { + return 0; + } + + let effective_range = value.len() - RANGE_END..value.len() - RANGE_START; + self.bh.hash_one(&b[effective_range]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_right_range_hasher_hash_slice() { + let hasher = InlineRightRangeHasher::<1, 3>::new(RandomState::default()); + assert_eq!( + hasher.hash(vec![1, 2, 3, 4].as_slice()), + hasher.bh.hash_one(vec![2, 3].as_slice()) + ); + assert_eq!(hasher.hash(vec![1, 2].as_slice()), 0); + } + + #[test] + fn test_right_range_hasher_hash_string() { + let hasher = InlineRightRangeHasher::<1, 3>::new(RandomState::default()); + assert_eq!(hasher.hash(&"abcd".to_string()), hasher.bh.hash_one(b"bc")); + assert_eq!(hasher.hash(&"a".to_string()), 0); + } + + #[test] + fn test_right_range_hasher_hash_str_ref() { + let hasher = InlineRightRangeHasher::<1, 3>::new(RandomState::default()); + assert_eq!(hasher.hash(&"abcd"), hasher.bh.hash_one(b"bc")); + assert_eq!(hasher.hash(&"a"), 0); + } + + #[test] + fn test_right_range_hasher_hash_str() { + let hasher = InlineRightRangeHasher::<1, 3>::new(RandomState::default()); + assert_eq!(hasher.hash("abcd"), hasher.bh.hash_one(b"bc")); + assert_eq!(hasher.hash("a"), 0); + } +} diff --git a/frozen-collections-core/src/hashers/left_range_hasher.rs b/frozen-collections-core/src/hashers/left_range_hasher.rs new file mode 100644 index 0000000..3c3ce2f --- /dev/null +++ b/frozen-collections-core/src/hashers/left_range_hasher.rs @@ -0,0 +1,127 @@ +use crate::traits::Hasher; +use ahash::RandomState; +use core::hash::{BuildHasher, Hash}; +use core::ops::Range; + +/// Hashes a portion of a left-aligned slice. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[derive(Clone)] +pub struct LeftRangeHasher { + bh: BH, + range: Range, +} + +impl LeftRangeHasher { + #[must_use] + pub const fn new(bh: BH, range: Range) -> Self { + Self { bh, range } + } +} + +impl Hasher<[T]> for LeftRangeHasher +where + T: Hash, + BH: BuildHasher, +{ + fn hash(&self, value: &[T]) -> u64 { + if value.len() < self.range.end { + return 0; + } + + self.bh.hash_one(&value[self.range.clone()]) + } +} + +impl Hasher for LeftRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &String) -> u64 { + let b = value.as_bytes(); + if b.len() < self.range.end { + return 0; + } + + self.bh.hash_one(&b[self.range.clone()]) + } +} + +impl Hasher<&str> for LeftRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &&str) -> u64 { + let b = value.as_bytes(); + if b.len() < self.range.end { + return 0; + } + + self.bh.hash_one(&b[self.range.clone()]) + } +} + +impl Hasher for LeftRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &str) -> u64 { + let b = value.as_bytes(); + if b.len() < self.range.end { + return 0; + } + + self.bh.hash_one(&b[self.range.clone()]) + } +} + +impl Default for LeftRangeHasher +where + BH: Default, +{ + fn default() -> Self { + Self::new(BH::default(), 0..0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_left_range_hasher_hash_slice() { + let hasher = LeftRangeHasher::new(RandomState::default(), 0..3); + assert_eq!( + hasher.hash(vec![1, 2, 3, 4].as_slice()), + hasher.bh.hash_one(vec![1, 2, 3].as_slice()) + ); + assert_eq!(hasher.hash(vec![1, 2].as_slice()), 0); + } + + #[test] + fn test_left_range_hasher_hash_string() { + let hasher = LeftRangeHasher::new(RandomState::default(), 0..3); + assert_eq!(hasher.hash(&"abcd".to_string()), hasher.bh.hash_one(b"abc")); + assert_eq!(hasher.hash(&"ab".to_string()), 0); + } + + #[test] + fn test_left_range_hasher_hash_str_ref() { + let hasher = LeftRangeHasher::new(RandomState::default(), 0..3); + assert_eq!(hasher.hash(&"abcd"), hasher.bh.hash_one(b"abc")); + assert_eq!(hasher.hash(&"ab"), 0); + } + + #[test] + fn test_left_range_hasher_hash_str() { + let hasher = LeftRangeHasher::new(RandomState::default(), 0..3); + assert_eq!(hasher.hash("abcd"), hasher.bh.hash_one(b"abc")); + assert_eq!(hasher.hash("ab"), 0); + } + + #[test] + fn test_left_range_hasher_default() { + let hasher: LeftRangeHasher = LeftRangeHasher::default(); + assert_eq!(hasher.range, 0..0); + } +} diff --git a/frozen-collections-core/src/hashers/mixing_hasher.rs b/frozen-collections-core/src/hashers/mixing_hasher.rs new file mode 100644 index 0000000..2da896f --- /dev/null +++ b/frozen-collections-core/src/hashers/mixing_hasher.rs @@ -0,0 +1,67 @@ +// NOTE: Currently unused, keeping it around just in case... + +use crate::traits::{Hasher, Scalar}; + +/// A hasher for scalar values which performs mixing on the values. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[derive(Clone)] +pub struct MixingHasher {} + +impl MixingHasher { + #[must_use] + pub const fn new() -> Self { + Self {} + } +} + +impl Hasher<&[T]> for MixingHasher { + fn hash(&self, value: &&[T]) -> u64 { + mix(value.len() as u64) + } +} + +impl Hasher for MixingHasher { + fn hash(&self, value: &String) -> u64 { + mix(value.len() as u64) + } +} + +impl Hasher for MixingHasher { + fn hash(&self, value: &str) -> u64 { + mix(value.len() as u64) + } +} + +impl Hasher<&str> for MixingHasher { + fn hash(&self, value: &&str) -> u64 { + mix(value.len() as u64) + } +} + +impl Hasher for MixingHasher +where + S: Scalar, +{ + fn hash(&self, value: &S) -> u64 { + mix(value.as_u64()) + } +} + +impl Default for MixingHasher { + fn default() -> Self { + Self::new() + } +} + +/// Take an integer value and 'mix' it into a hash code. +/// +/// This function is based on code from . +#[must_use] +#[inline] +const fn mix(mut value: u64) -> u64 { + value ^= value.wrapping_shr(23); + value = value.wrapping_mul(0x2127_599b_f432_5c37); + value ^= value.wrapping_shr(47); + value +} diff --git a/frozen-collections-core/src/hashers/mod.rs b/frozen-collections-core/src/hashers/mod.rs new file mode 100644 index 0000000..6165853 --- /dev/null +++ b/frozen-collections-core/src/hashers/mod.rs @@ -0,0 +1,16 @@ +//! Hasher implementations for various situations. + +pub use crate::hashers::bridge_hasher::BridgeHasher; +pub use crate::hashers::inline_left_range_hasher::InlineLeftRangeHasher; +pub use crate::hashers::inline_right_range_hasher::InlineRightRangeHasher; +pub use crate::hashers::left_range_hasher::LeftRangeHasher; +pub use crate::hashers::passthrough_hasher::PassthroughHasher; +pub use crate::hashers::right_range_hasher::RightRangeHasher; + +mod bridge_hasher; +mod inline_left_range_hasher; +mod inline_right_range_hasher; +mod left_range_hasher; +//mod mixing_hasher; +mod passthrough_hasher; +mod right_range_hasher; diff --git a/frozen-collections-core/src/hashers/passthrough_hasher.rs b/frozen-collections-core/src/hashers/passthrough_hasher.rs new file mode 100644 index 0000000..fd19dd6 --- /dev/null +++ b/frozen-collections-core/src/hashers/passthrough_hasher.rs @@ -0,0 +1,92 @@ +use crate::traits::{Hasher, Scalar}; + +/// A hasher that simply returns the value as the hash. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[derive(Clone)] +pub struct PassthroughHasher {} + +impl PassthroughHasher { + #[must_use] + pub const fn new() -> Self { + Self {} + } +} + +impl Hasher<[T]> for PassthroughHasher { + fn hash(&self, value: &[T]) -> u64 { + value.len() as u64 + } +} + +impl Hasher for PassthroughHasher { + fn hash(&self, value: &String) -> u64 { + value.len() as u64 + } +} + +impl Hasher for PassthroughHasher { + fn hash(&self, value: &str) -> u64 { + value.len() as u64 + } +} + +impl Hasher<&str> for PassthroughHasher { + fn hash(&self, value: &&str) -> u64 { + value.len() as u64 + } +} + +impl Hasher for PassthroughHasher +where + S: Scalar, +{ + fn hash(&self, value: &S) -> u64 { + value.index() as u64 + } +} + +impl Default for PassthroughHasher { + fn default() -> Self { + Self::new() + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hash_string_returns_length() { + let hasher = PassthroughHasher::new(); + let value = String::from("hello"); + assert_eq!(hasher.hash(&value), 5); + } + + #[test] + fn hash_str_returns_length() { + let hasher = PassthroughHasher::new(); + let value = "world"; + assert_eq!(hasher.hash(value), 5); + } + + #[test] + fn hash_slice_returns_length() { + let hasher = PassthroughHasher::new(); + let binding = vec![1, 2, 3, 4]; + let value = binding.as_slice(); + assert_eq!(hasher.hash(value), 4); + } + + #[test] + fn hash_scalar_returns_index() { + let hasher = PassthroughHasher::new(); + let index = Scalar::index(&42) as u64; + assert_eq!(hasher.hash(&42), index); + } + + #[test] + fn default_creates_instance() { + let hasher = PassthroughHasher::default(); + assert_eq!(hasher.hash(&"default"), 7); + } +} diff --git a/frozen-collections-core/src/hashers/right_range_hasher.rs b/frozen-collections-core/src/hashers/right_range_hasher.rs new file mode 100644 index 0000000..740d449 --- /dev/null +++ b/frozen-collections-core/src/hashers/right_range_hasher.rs @@ -0,0 +1,131 @@ +use crate::traits::Hasher; +use ahash::RandomState; +use core::hash::{BuildHasher, Hash}; +use core::ops::Range; + +/// Hashes a portion of a right-aligned slice. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[derive(Clone)] +pub struct RightRangeHasher { + bh: BH, + range: Range, +} + +impl RightRangeHasher { + #[must_use] + pub const fn new(bh: BH, range: Range) -> Self { + Self { bh, range } + } +} + +impl Hasher<[T]> for RightRangeHasher +where + T: Hash, + BH: BuildHasher, +{ + fn hash(&self, value: &[T]) -> u64 { + if value.len() <= self.range.end { + return 0; + } + + let effective_range = value.len() - self.range.end..value.len() - self.range.start; + self.bh.hash_one(&value[effective_range]) + } +} + +impl Hasher for RightRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &String) -> u64 { + let b = value.as_bytes(); + if b.len() <= self.range.end { + return 0; + } + + let effective_range = value.len() - self.range.end..value.len() - self.range.start; + self.bh.hash_one(&b[effective_range]) + } +} + +impl Hasher<&str> for RightRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &&str) -> u64 { + let b = value.as_bytes(); + if b.len() <= self.range.end { + return 0; + } + + let effective_range = value.len() - self.range.end..value.len() - self.range.start; + self.bh.hash_one(&b[effective_range]) + } +} + +impl Hasher for RightRangeHasher +where + BH: BuildHasher, +{ + fn hash(&self, value: &str) -> u64 { + let b = value.as_bytes(); + if b.len() <= self.range.end { + return 0; + } + + let effective_range = value.len() - self.range.end..value.len() - self.range.start; + self.bh.hash_one(&b[effective_range]) + } +} + +impl Default for RightRangeHasher +where + BH: Default, +{ + fn default() -> Self { + Self::new(BH::default(), 0..0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_right_range_hasher_hash_slice() { + let hasher = RightRangeHasher::new(RandomState::default(), 1..3); + assert_eq!( + hasher.hash(vec![1, 2, 3, 4].as_slice()), + hasher.bh.hash_one(vec![2, 3].as_slice()) + ); + assert_eq!(hasher.hash(vec![1, 2].as_slice()), 0); + } + + #[test] + fn test_right_range_hasher_hash_string() { + let hasher = RightRangeHasher::new(RandomState::default(), 1..3); + assert_eq!(hasher.hash(&"abcd".to_string()), hasher.bh.hash_one(b"bc")); + assert_eq!(hasher.hash(&"a".to_string()), 0); + } + + #[test] + fn test_right_range_hasher_hash_str_ref() { + let hasher = RightRangeHasher::new(RandomState::default(), 1..3); + assert_eq!(hasher.hash(&"abcd"), hasher.bh.hash_one(b"bc")); + assert_eq!(hasher.hash(&"a"), 0); + } + + #[test] + fn test_right_range_hasher_hash_str() { + let hasher = RightRangeHasher::new(RandomState::default(), 1..3); + assert_eq!(hasher.hash("abcd"), hasher.bh.hash_one(b"bc")); + assert_eq!(hasher.hash("a"), 0); + } + + #[test] + fn test_right_range_hasher_default() { + let hasher: RightRangeHasher = RightRangeHasher::default(); + assert_eq!(hasher.range, 0..0); + } +} diff --git a/frozen-collections-core/src/inline_maps/inline_binary_search_map.rs b/frozen-collections-core/src/inline_maps/inline_binary_search_map.rs new file mode 100644 index 0000000..a8af5e1 --- /dev/null +++ b/frozen-collections-core/src/inline_maps/inline_binary_search_map.rs @@ -0,0 +1,174 @@ +// NOTE: This is not currently in use, but keeping it around just in case ... + +use crate::maps::decl_macros::{ + binary_search_core, contains_key_fn, debug_fn, get_many_mut_body, get_many_mut_fn, index_fn, + into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, map_boilerplate_for_slice, + map_iterator_boilerplate_for_slice, partial_eq_fn, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{Len, Map, MapIterator}; +use crate::utils::dedup_by_keep_last; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +/// A general purpose map implemented using binary search. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +/// # Type Parameters +/// +/// - `K`: The key type. +/// - `V`: The value type. +/// - `SZ`: The number of entries in the map. +#[derive(Clone)] +pub struct InlineBinarySearchMap { + entries: [(K, V); SZ], +} + +impl InlineBinarySearchMap +where + K: Ord, +{ + /// Creates a frozen map. + /// + /// # Errors + /// + /// Fails if the length of the vector, after removing duplicates, isn't equal to the generic parameter `SZ`. + pub fn new(mut entries: Vec<(K, V)>) -> core::result::Result { + entries.sort_by(|x, y| x.0.cmp(&y.0)); + dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + + let len = entries.len(); + Ok(Self::new_raw(entries.try_into().map_err(|_| { + format!("incorrect # of entries: got {len} but SZ={SZ}") + })?)) + } + + /// Creates a frozen map. + /// + /// This function assumes the vector is sorted according to the ordering of the [`Ord`] trait. + #[must_use] + pub const fn new_raw(processed_entries: [(K, V); SZ]) -> Self { + Self { + entries: processed_entries, + } + } +} + +impl InlineBinarySearchMap { + binary_search_core!(); +} + +impl Len for InlineBinarySearchMap { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineBinarySearchMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Index<&Q> for InlineBinarySearchMap +where + K: Borrow, + Q: ?Sized + Ord, +{ + index_fn!(); +} + +impl IntoIterator for InlineBinarySearchMap { + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V, const SZ: usize> IntoIterator for &'a InlineBinarySearchMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V, const SZ: usize> IntoIterator for &'a mut InlineBinarySearchMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for InlineBinarySearchMap +where + K: Ord, + V: PartialEq, + MT: Map, +{ + partial_eq_fn!(); +} + +impl Eq for InlineBinarySearchMap +where + K: Ord, + V: Eq, +{ +} + +impl MapIterator for InlineBinarySearchMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map for InlineBinarySearchMap +where + K: Ord, +{ + map_boilerplate_for_slice!(entries); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_fails_when_too_many_entries() { + let mut entries = Vec::new(); + for i in 0..3 { + entries.push((i, 42)); + } + + let map = InlineBinarySearchMap::<_, _, 2>::new(entries); + assert_eq!( + map, + Err("incorrect # of entries: got 3 but SZ=2".to_string()) + ); + } +} diff --git a/frozen-collections-core/src/inline_maps/inline_dense_scalar_lookup_map.rs b/frozen-collections-core/src/inline_maps/inline_dense_scalar_lookup_map.rs new file mode 100644 index 0000000..bf4a712 --- /dev/null +++ b/frozen-collections-core/src/inline_maps/inline_dense_scalar_lookup_map.rs @@ -0,0 +1,141 @@ +use crate::maps::decl_macros::{ + contains_key_fn, debug_fn, dense_scalar_lookup_core, get_many_mut_body, get_many_mut_fn, + index_fn, into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, + map_boilerplate_for_slice, map_iterator_boilerplate_for_slice, partial_eq_fn, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{Len, Map, MapIterator, Scalar}; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +/// A map whose keys are a continuous range in a sequence of scalar values. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +/// # Type Parameters +/// +/// - `K`: The key type. +/// - `V`: The value type. +/// - `SZ`: The number of entries in the map. +#[derive(Clone)] +pub struct InlineDenseScalarLookupMap { + min: usize, + max: usize, + entries: [(K, V); SZ], +} + +impl InlineDenseScalarLookupMap +where + K: Scalar, +{ + /// Creates a frozen map. + /// + /// This function assumes that `min` <= `max` and that the vector is sorted according to the + /// order of the [`Ord`] trait. + #[must_use] + pub const fn new_raw(processed_entries: [(K, V); SZ], min: usize, max: usize) -> Self { + Self { + min, + max, + entries: processed_entries, + } + } +} + +impl InlineDenseScalarLookupMap { + dense_scalar_lookup_core!(); +} + +impl Len for InlineDenseScalarLookupMap { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineDenseScalarLookupMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Index<&Q> for InlineDenseScalarLookupMap +where + K: Borrow, + Q: Scalar, +{ + index_fn!(); +} + +impl IntoIterator for InlineDenseScalarLookupMap { + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V, const SZ: usize> IntoIterator for &'a InlineDenseScalarLookupMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V, const SZ: usize> IntoIterator for &'a mut InlineDenseScalarLookupMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for InlineDenseScalarLookupMap +where + K: Scalar, + V: PartialEq, + MT: Map, +{ + partial_eq_fn!(); +} + +impl Eq for InlineDenseScalarLookupMap +where + K: Scalar, + V: Eq, +{ +} + +impl MapIterator for InlineDenseScalarLookupMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map for InlineDenseScalarLookupMap +where + K: Scalar, +{ + map_boilerplate_for_slice!(entries); +} diff --git a/frozen-collections-core/src/inline_maps/inline_hash_map.rs b/frozen-collections-core/src/inline_maps/inline_hash_map.rs new file mode 100644 index 0000000..5631cfd --- /dev/null +++ b/frozen-collections-core/src/inline_maps/inline_hash_map.rs @@ -0,0 +1,181 @@ +use crate::hash_tables::InlineHashTable; +use crate::hashers::BridgeHasher; +use crate::maps::decl_macros::{ + hash_core, index_fn, into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, + map_boilerplate_for_slice, map_iterator_boilerplate_for_slice, partial_eq_fn, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{CollectionMagnitude, Hasher, Len, Map, MapIterator, SmallCollection}; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +/// A general purpose map implemented using a hash table. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/hash_warning.md")] +/// +/// # Type Parameters +/// +/// - `K`: The key type. +/// - `V`: The value type. +/// - `CM`: The magnitude of the map, one of [`SmallCollection`](crate::traits::SmallCollection), [`MediumCollection`](crate::traits::MediumCollection), or [`LargeCollection`](crate::traits::LargeCollection). +/// - `SZ`: The number of entries in the map. +/// - `NHS`: The number of hash table slots. +/// - `H`: The hasher to generate hash codes. +#[derive(Clone)] +pub struct InlineHashMap< + K, + V, + const SZ: usize, + const NHS: usize, + CM = SmallCollection, + H = BridgeHasher, +> { + table: InlineHashTable<(K, V), SZ, NHS, CM>, + hasher: H, +} + +impl InlineHashMap +where + K: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + /// Creates a frozen map. + #[must_use] + pub const fn new_raw(table: InlineHashTable<(K, V), SZ, NHS, CM>, hasher: H) -> Self { + Self { table, hasher } + } +} + +impl InlineHashMap +where + CM: CollectionMagnitude, +{ + hash_core!(); +} + +impl Len for InlineHashMap { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineHashMap +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.table.fmt(f) + } +} + +impl Index<&Q> + for InlineHashMap +where + K: Borrow, + Q: ?Sized + Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + index_fn!(); +} + +impl IntoIterator + for InlineHashMap +{ + into_iter_fn_for_slice!(table entries); +} + +impl<'a, K, V, const SZ: usize, const NHS: usize, CM, H> IntoIterator + for &'a InlineHashMap +{ + into_iter_ref_fn!(); +} + +impl<'a, K, V, const SZ: usize, const NHS: usize, CM, H> IntoIterator + for &'a mut InlineHashMap +{ + into_iter_mut_ref_fn!(); +} + +impl PartialEq + for InlineHashMap +where + K: Eq, + CM: CollectionMagnitude, + V: PartialEq, + MT: Map, + H: Hasher, +{ + partial_eq_fn!(); +} + +impl Eq for InlineHashMap +where + K: Eq, + CM: CollectionMagnitude, + V: Eq, + H: Hasher, +{ +} + +impl MapIterator + for InlineHashMap +{ + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + map_iterator_boilerplate_for_slice!(table entries); +} + +impl Map + for InlineHashMap +where + K: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + map_boilerplate_for_slice!(table entries); +} diff --git a/frozen-collections-core/src/inline_maps/inline_ordered_scan_map.rs b/frozen-collections-core/src/inline_maps/inline_ordered_scan_map.rs new file mode 100644 index 0000000..ae8482d --- /dev/null +++ b/frozen-collections-core/src/inline_maps/inline_ordered_scan_map.rs @@ -0,0 +1,166 @@ +use crate::maps::decl_macros::{ + contains_key_fn, debug_fn, get_many_mut_body, get_many_mut_fn, index_fn, + into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, map_boilerplate_for_slice, + map_iterator_boilerplate_for_slice, ordered_scan_core, partial_eq_fn, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{Len, Map, MapIterator}; +use crate::utils::dedup_by_keep_last; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; +use std::cmp::Ordering; + +/// A general purpose map implemented using linear scanning. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +/// # Type Parameters +/// +/// - `K`: The key type. +/// - `V`: The value type. +/// - `SZ`: The number of entries in the map. +#[derive(Clone)] +pub struct InlineOrderedScanMap { + entries: [(K, V); SZ], +} + +impl InlineOrderedScanMap +where + K: Ord, +{ + /// Creates a frozen map. + /// + /// # Errors + /// + /// Fails if the length of the vector, after removing duplicates, isn't equal to the generic parameter `SZ`, + pub fn new(mut entries: Vec<(K, V)>) -> core::result::Result { + entries.sort_by(|x, y| x.0.cmp(&y.0)); + dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + + let len = entries.len(); + Ok(Self::new_raw(entries.try_into().map_err(|_| { + format!("incorrect # of entries: got {len} but SZ={SZ}") + })?)) + } + + /// Creates a frozen map. + /// + /// This function assumes the vector is sorted according to the ordering of the [`Ord`] trait. + #[must_use] + pub const fn new_raw(processed_entries: [(K, V); SZ]) -> Self { + Self { + entries: processed_entries, + } + } +} + +impl InlineOrderedScanMap { + ordered_scan_core!(); +} + +impl Len for InlineOrderedScanMap { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineOrderedScanMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Index<&Q> for InlineOrderedScanMap +where + K: Borrow, + Q: ?Sized + Ord, +{ + index_fn!(); +} + +impl IntoIterator for InlineOrderedScanMap { + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V, const SZ: usize> IntoIterator for &'a InlineOrderedScanMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V, const SZ: usize> IntoIterator for &'a mut InlineOrderedScanMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for InlineOrderedScanMap +where + K: Ord, + V: PartialEq, + MT: Map, +{ + partial_eq_fn!(); +} + +impl MapIterator for InlineOrderedScanMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map for InlineOrderedScanMap +where + K: Ord, +{ + map_boilerplate_for_slice!(entries); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_fails_when_bad_range() { + let mut entries = Vec::new(); + for i in 0..3 { + entries.push((i, 42)); + } + + let map = InlineOrderedScanMap::<_, _, 2>::new(entries); + assert_eq!( + map, + Err("incorrect # of entries: got 3 but SZ=2".to_string()) + ); + } +} diff --git a/frozen-collections-core/src/inline_maps/inline_scan_map.rs b/frozen-collections-core/src/inline_maps/inline_scan_map.rs new file mode 100644 index 0000000..975bc11 --- /dev/null +++ b/frozen-collections-core/src/inline_maps/inline_scan_map.rs @@ -0,0 +1,168 @@ +use crate::maps::decl_macros::{ + contains_key_fn, debug_fn, get_many_mut_body, get_many_mut_fn, index_fn, + into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, map_boilerplate_for_slice, + map_iterator_boilerplate_for_slice, partial_eq_fn, scan_core, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{Len, Map, MapIterator}; +use crate::utils::slow_dedup_by_keep_last; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +/// A general purpose map implemented using linear scanning. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +/// # Type Parameters +/// +/// - `K`: The key type. +/// - `V`: The value type. +/// - `SZ`: The number of entries in the map. +#[derive(Clone)] +pub struct InlineScanMap { + entries: [(K, V); SZ], +} + +impl InlineScanMap +where + K: Eq, +{ + /// Creates a frozen map. + /// + /// # Errors + /// + /// Fails if the length of the vector, after removing duplicates, isn't equal to the generic parameter `SZ`. + pub fn new(mut entries: Vec<(K, V)>) -> core::result::Result { + slow_dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + + let len = entries.len(); + Ok(Self::new_raw(entries.try_into().map_err(|_| { + format!("incorrect # of entries: got {len} but SZ={SZ}") + })?)) + } + + /// Creates a frozen map. + #[must_use] + pub const fn new_raw(processed_entries: [(K, V); SZ]) -> Self { + Self { + entries: processed_entries, + } + } +} + +impl InlineScanMap { + scan_core!(); +} + +impl Len for InlineScanMap { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineScanMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Index<&Q> for InlineScanMap +where + K: Borrow, + Q: ?Sized + Eq, +{ + index_fn!(); +} + +impl IntoIterator for InlineScanMap { + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V, const SZ: usize> IntoIterator for &'a InlineScanMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V, const SZ: usize> IntoIterator for &'a mut InlineScanMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for InlineScanMap +where + K: Eq, + V: PartialEq, + MT: Map, +{ + partial_eq_fn!(); +} + +impl Eq for InlineScanMap +where + K: Eq, + V: PartialEq, +{ +} + +impl MapIterator for InlineScanMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map for InlineScanMap +where + K: Eq, +{ + map_boilerplate_for_slice!(entries); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_fails_when_bad_range() { + let mut entries = Vec::new(); + for i in 0..3 { + entries.push((i, 42)); + } + + let map = InlineScanMap::<_, _, 2>::new(entries); + assert_eq!( + map, + Err("incorrect # of entries: got 3 but SZ=2".to_string()) + ); + } +} diff --git a/frozen-collections-core/src/inline_maps/inline_sparse_scalar_lookup_map.rs b/frozen-collections-core/src/inline_maps/inline_sparse_scalar_lookup_map.rs new file mode 100644 index 0000000..fada886 --- /dev/null +++ b/frozen-collections-core/src/inline_maps/inline_sparse_scalar_lookup_map.rs @@ -0,0 +1,185 @@ +use crate::maps::decl_macros::{ + contains_key_fn, debug_fn, get_many_mut_body, get_many_mut_fn, index_fn, + into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, map_boilerplate_for_slice, + map_iterator_boilerplate_for_slice, partial_eq_fn, sparse_scalar_lookup_core, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{CollectionMagnitude, Len, Map, MapIterator, Scalar, SmallCollection}; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +/// A map whose keys are a sparse range of integers. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +/// # Type Parameters +/// +/// - `K`: The key type. +/// - `V`: The value type. +/// - `CM`: The magnitude of the map, one of [`SmallCollection`](crate::traits::SmallCollection), [`MediumCollection`](crate::traits::MediumCollection), or [`LargeCollection`](crate::traits::LargeCollection). +/// - `SZ`: The number of entries in the map. +/// - `LTSZ`: The number of entries in the lookup table. +#[derive(Clone)] +pub struct InlineSparseScalarLookupMap< + K, + V, + const SZ: usize, + const LTSZ: usize, + CM = SmallCollection, +> { + min: usize, + max: usize, + lookup: [CM; LTSZ], + entries: [(K, V); SZ], +} + +impl InlineSparseScalarLookupMap +where + K: Scalar, + CM: CollectionMagnitude, +{ + /// Creates a frozen map. + #[must_use] + pub const fn new_raw( + processed_entries: [(K, V); SZ], + lookup: [CM; LTSZ], + min: usize, + max: usize, + ) -> Self { + Self { + min, + max, + lookup, + entries: processed_entries, + } + } +} + +impl InlineSparseScalarLookupMap +where + CM: CollectionMagnitude, +{ + sparse_scalar_lookup_core!(); +} + +impl Len + for InlineSparseScalarLookupMap +{ + fn len(&self) -> usize { + SZ + } +} + +impl Debug + for InlineSparseScalarLookupMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Index<&Q> + for InlineSparseScalarLookupMap +where + K: Borrow, + Q: Scalar, + CM: CollectionMagnitude, +{ + index_fn!(); +} + +impl IntoIterator + for InlineSparseScalarLookupMap +{ + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V, const SZ: usize, const LTSZ: usize, CM> IntoIterator + for &'a InlineSparseScalarLookupMap +where + CM: CollectionMagnitude, +{ + into_iter_ref_fn!(); +} + +impl<'a, K, V, const SZ: usize, const LTSZ: usize, CM> IntoIterator + for &'a mut InlineSparseScalarLookupMap +where + CM: CollectionMagnitude, +{ + into_iter_mut_ref_fn!(); +} + +impl PartialEq + for InlineSparseScalarLookupMap +where + K: Scalar, + V: PartialEq, + MT: Map, + CM: CollectionMagnitude, +{ + partial_eq_fn!(); +} + +impl Eq + for InlineSparseScalarLookupMap +where + K: Scalar, + V: Eq, + CM: CollectionMagnitude, +{ +} + +impl MapIterator + for InlineSparseScalarLookupMap +{ + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map + for InlineSparseScalarLookupMap +where + K: Scalar, + CM: CollectionMagnitude, +{ + map_boilerplate_for_slice!(entries); +} diff --git a/frozen-collections-core/src/inline_maps/mod.rs b/frozen-collections-core/src/inline_maps/mod.rs new file mode 100644 index 0000000..2b3787c --- /dev/null +++ b/frozen-collections-core/src/inline_maps/mod.rs @@ -0,0 +1,15 @@ +//! Specialized static-friendly read-only map types. + +// pub use inline_binary_search_map::InlineBinarySearchMap; +pub use inline_dense_scalar_lookup_map::InlineDenseScalarLookupMap; +pub use inline_hash_map::InlineHashMap; +pub use inline_ordered_scan_map::InlineOrderedScanMap; +pub use inline_scan_map::InlineScanMap; +pub use inline_sparse_scalar_lookup_map::InlineSparseScalarLookupMap; + +// mod inline_binary_search_map; +mod inline_dense_scalar_lookup_map; +mod inline_hash_map; +mod inline_ordered_scan_map; +mod inline_scan_map; +mod inline_sparse_scalar_lookup_map; diff --git a/frozen-collections-core/src/inline_sets/inline_binary_search_set.rs b/frozen-collections-core/src/inline_sets/inline_binary_search_set.rs new file mode 100644 index 0000000..3ffc74a --- /dev/null +++ b/frozen-collections-core/src/inline_sets/inline_binary_search_set.rs @@ -0,0 +1,122 @@ +// NOTE: This is not currently in use, but keeping it around just in case ... + +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +use crate::inline_maps::InlineBinarySearchMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Set, SetIterator}; + +/// A general purpose set implemented using binary search. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +/// # Type Parameters +/// +/// - `T`: The value type. +/// - `SZ`: The number of entries in the set. +#[derive(Clone)] +pub struct InlineBinarySearchSet { + map: InlineBinarySearchMap, +} + +impl InlineBinarySearchSet +where + T: Ord, +{ + /// Creates a frozen set. + #[must_use] + pub const fn new(map: InlineBinarySearchMap) -> Self { + Self { map } + } + + get_fn!(Ord); + contains_fn!(Ord); +} + +impl Len for InlineBinarySearchSet { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineBinarySearchSet +where + T: Debug, +{ + debug_fn!(); +} + +impl IntoIterator for InlineBinarySearchSet { + into_iter_fn!(); +} + +impl<'a, T, const SZ: usize> IntoIterator for &'a InlineBinarySearchSet { + into_iter_ref_fn!(); +} + +impl SetIterator for InlineBinarySearchSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for InlineBinarySearchSet +where + T: Ord, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &InlineBinarySearchSet +where + T: Hash + Ord + Clone, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &InlineBinarySearchSet +where + T: Hash + Ord + Clone, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &InlineBinarySearchSet +where + T: Hash + Ord + Clone, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &InlineBinarySearchSet +where + T: Hash + Ord + Clone, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for InlineBinarySearchSet +where + T: Ord, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for InlineBinarySearchSet where T: Ord {} diff --git a/frozen-collections-core/src/inline_sets/inline_dense_scalar_lookup_set.rs b/frozen-collections-core/src/inline_sets/inline_dense_scalar_lookup_set.rs new file mode 100644 index 0000000..c097569 --- /dev/null +++ b/frozen-collections-core/src/inline_sets/inline_dense_scalar_lookup_set.rs @@ -0,0 +1,119 @@ +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +use crate::inline_maps::InlineDenseScalarLookupMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Scalar, Set, SetIterator}; + +/// A set whose values are a continuous range in a sequence of scalar values. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +/// # Type Parameters +/// +/// - `T`: The value type. +/// - `SZ`: The number of entries in the set. +#[derive(Clone)] +pub struct InlineDenseScalarLookupSet { + map: InlineDenseScalarLookupMap, +} + +impl InlineDenseScalarLookupSet +where + T: Scalar, +{ + /// Creates a frozen set. + #[must_use] + pub const fn new(map: InlineDenseScalarLookupMap) -> Self { + Self { map } + } + + get_fn!(Scalar); + contains_fn!(Scalar); +} + +impl Len for InlineDenseScalarLookupSet { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineDenseScalarLookupSet +where + T: Debug, +{ + debug_fn!(); +} + +impl IntoIterator for InlineDenseScalarLookupSet { + into_iter_fn!(); +} + +impl<'a, T, const SZ: usize> IntoIterator for &'a InlineDenseScalarLookupSet { + into_iter_ref_fn!(); +} + +impl SetIterator for InlineDenseScalarLookupSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for InlineDenseScalarLookupSet +where + T: Scalar, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &InlineDenseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &InlineDenseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &InlineDenseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &InlineDenseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for InlineDenseScalarLookupSet +where + T: Scalar, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for InlineDenseScalarLookupSet where T: Scalar {} diff --git a/frozen-collections-core/src/inline_sets/inline_hash_set.rs b/frozen-collections-core/src/inline_sets/inline_hash_set.rs new file mode 100644 index 0000000..2b8ae0f --- /dev/null +++ b/frozen-collections-core/src/inline_sets/inline_hash_set.rs @@ -0,0 +1,194 @@ +use crate::hashers::BridgeHasher; +use crate::inline_maps::InlineHashMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, debug_fn, into_iter_fn, into_iter_ref_fn, partial_eq_fn, + set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{ + CollectionMagnitude, Hasher, Len, MapIterator, Set, SetIterator, SmallCollection, +}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A general purpose set implemented using a hash table. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/hash_warning.md")] +/// +/// # Type Parameters +/// +/// - `T`: The value type. +/// - `CM`: The magnitude of the set, one of [`SmallCollection`](crate::traits::SmallCollection), [`MediumCollection`](crate::traits::MediumCollection), or [`LargeCollection`](crate::traits::LargeCollection). +/// - `SZ`: The number of entries in the set. +/// - `NHS`: The number of hash table slots. +/// - `H`: The hasher to generate hash codes. +#[derive(Clone)] +pub struct InlineHashSet< + T, + const SZ: usize, + const NHS: usize, + CM = SmallCollection, + H = BridgeHasher, +> { + map: InlineHashMap, +} + +impl InlineHashSet +where + CM: CollectionMagnitude, + H: Hasher, +{ + /// Creates a frozen set. + #[must_use] + pub const fn new(map: InlineHashMap) -> Self { + Self { map } + } +} + +impl InlineHashSet +where + CM: CollectionMagnitude, +{ + #[doc = include_str!("../doc_snippets/get_from_set_method.md")] + #[inline] + #[must_use] + pub fn get(&self, value: &Q) -> Option<&T> + where + T: Borrow, + Q: ?Sized + Eq, + H: Hasher, + { + Some(self.map.get_key_value(value)?.0) + } + + #[doc = include_str!("../doc_snippets/contains_method.md")] + #[inline] + #[must_use] + pub fn contains(&self, value: &Q) -> bool + where + T: Borrow, + Q: ?Sized + Eq, + H: Hasher, + { + self.get(value).is_some() + } +} + +impl Len for InlineHashSet { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineHashSet +where + T: Eq + Debug, + CM: CollectionMagnitude, + H: Hasher, +{ + debug_fn!(); +} + +impl IntoIterator + for InlineHashSet +{ + into_iter_fn!(); +} + +impl<'a, T, const SZ: usize, const NHS: usize, CM, H> IntoIterator + for &'a InlineHashSet +where + T: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + into_iter_ref_fn!(); +} + +impl SetIterator + for InlineHashSet +{ + type Iterator<'a> + = Iter<'a, T> + where + T: 'a, + CM: 'a, + H: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for InlineHashSet +where + T: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> + for &InlineHashSet +where + T: Hash + Eq + Clone, + ST: Set, + CM: CollectionMagnitude, + H: Hasher + Default, +{ + bitor_fn!(H); +} + +impl BitAnd<&ST> + for &InlineHashSet +where + T: Hash + Eq + Clone, + ST: Set, + CM: CollectionMagnitude, + H: Hasher + Default, +{ + bitand_fn!(H); +} + +impl BitXor<&ST> + for &InlineHashSet +where + T: Hash + Eq + Clone, + ST: Set, + CM: CollectionMagnitude, + H: Hasher + Default, +{ + bitxor_fn!(H); +} + +impl Sub<&ST> for &InlineHashSet +where + T: Hash + Eq + Clone, + ST: Set, + CM: CollectionMagnitude, + H: Hasher + Default, +{ + sub_fn!(H); +} + +impl PartialEq + for InlineHashSet +where + T: Eq, + ST: Set, + CM: CollectionMagnitude, + H: Hasher, +{ + partial_eq_fn!(); +} + +impl Eq for InlineHashSet +where + T: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ +} diff --git a/frozen-collections-core/src/inline_sets/inline_ordered_scan_set.rs b/frozen-collections-core/src/inline_sets/inline_ordered_scan_set.rs new file mode 100644 index 0000000..d868108 --- /dev/null +++ b/frozen-collections-core/src/inline_sets/inline_ordered_scan_set.rs @@ -0,0 +1,118 @@ +use crate::inline_maps::InlineOrderedScanMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Set, SetIterator}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A general purpose set implemented using linear scanning. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +/// # Type Parameters +/// +/// - `T`: The value type. +/// - `SZ`: The number of entries in the set. +#[derive(Clone)] +pub struct InlineOrderedScanSet { + map: InlineOrderedScanMap, +} + +impl InlineOrderedScanSet { + /// Creates a frozen set. + #[must_use] + pub const fn new(map: InlineOrderedScanMap) -> Self { + Self { map } + } +} + +impl InlineOrderedScanSet { + get_fn!(Ord); + contains_fn!(Ord); +} + +impl Len for InlineOrderedScanSet { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineOrderedScanSet +where + T: Debug, +{ + debug_fn!(); +} + +impl IntoIterator for InlineOrderedScanSet { + into_iter_fn!(); +} + +impl<'a, T, const SZ: usize> IntoIterator for &'a InlineOrderedScanSet { + into_iter_ref_fn!(); +} + +impl SetIterator for InlineOrderedScanSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for InlineOrderedScanSet +where + T: Ord, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &InlineOrderedScanSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &InlineOrderedScanSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &InlineOrderedScanSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &InlineOrderedScanSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for InlineOrderedScanSet +where + T: Ord, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for InlineOrderedScanSet where T: Ord {} diff --git a/frozen-collections-core/src/inline_sets/inline_scan_set.rs b/frozen-collections-core/src/inline_sets/inline_scan_set.rs new file mode 100644 index 0000000..d8a8344 --- /dev/null +++ b/frozen-collections-core/src/inline_sets/inline_scan_set.rs @@ -0,0 +1,118 @@ +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +use crate::inline_maps::InlineScanMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Set, SetIterator}; + +/// A general purpose set implemented using linear scanning. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +/// # Type Parameters +/// +/// - `T`: The value type. +/// - `SZ`: The number of entries in the set. +#[derive(Clone)] +pub struct InlineScanSet { + map: InlineScanMap, +} + +impl InlineScanSet { + /// Creates a frozen set. + #[must_use] + pub const fn new(map: InlineScanMap) -> Self { + Self { map } + } +} + +impl InlineScanSet { + get_fn!(Eq); + contains_fn!(Eq); +} + +impl Len for InlineScanSet { + fn len(&self) -> usize { + SZ + } +} + +impl Debug for InlineScanSet +where + T: Debug, +{ + debug_fn!(); +} + +impl IntoIterator for InlineScanSet { + into_iter_fn!(); +} + +impl<'a, T, const SZ: usize> IntoIterator for &'a InlineScanSet { + into_iter_ref_fn!(); +} + +impl SetIterator for InlineScanSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for InlineScanSet +where + T: Eq, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &InlineScanSet +where + T: Hash + Eq + Clone, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &InlineScanSet +where + T: Hash + Eq + Clone, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &InlineScanSet +where + T: Hash + Eq + Clone, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &InlineScanSet +where + T: Hash + Eq + Clone, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for InlineScanSet +where + T: Eq, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for InlineScanSet where T: Eq {} diff --git a/frozen-collections-core/src/inline_sets/inline_sparse_scalar_lookup_set.rs b/frozen-collections-core/src/inline_sets/inline_sparse_scalar_lookup_set.rs new file mode 100644 index 0000000..cd11601 --- /dev/null +++ b/frozen-collections-core/src/inline_sets/inline_sparse_scalar_lookup_set.rs @@ -0,0 +1,153 @@ +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +use crate::inline_maps::InlineSparseScalarLookupMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{ + CollectionMagnitude, Len, MapIterator, Scalar, Set, SetIterator, SmallCollection, +}; + +/// A set whose values are scalars. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +/// # Type Parameters +/// +/// - `T`: The value type. +/// - `CM`: The magnitude of the set, one of [`SmallCollection`](crate::traits::SmallCollection), [`MediumCollection`](crate::traits::MediumCollection), or [`LargeCollection`](crate::traits::LargeCollection). +/// - `SZ`: The number of entries in the set. +/// - `LTSZ`: The number of entries in the lookup table. +#[derive(Clone)] +pub struct InlineSparseScalarLookupSet +{ + map: InlineSparseScalarLookupMap, +} + +impl InlineSparseScalarLookupSet { + /// Creates a frozen set. + #[must_use] + pub const fn new(map: InlineSparseScalarLookupMap) -> Self { + Self { map } + } +} + +impl InlineSparseScalarLookupSet +where + CM: CollectionMagnitude, +{ + get_fn!(Scalar); + contains_fn!(Scalar); +} + +impl Len + for InlineSparseScalarLookupSet +{ + fn len(&self) -> usize { + SZ + } +} + +impl Debug + for InlineSparseScalarLookupSet +where + T: Debug, +{ + debug_fn!(); +} + +impl IntoIterator + for InlineSparseScalarLookupSet +{ + into_iter_fn!(); +} + +impl<'a, T, const SZ: usize, const LTSZ: usize, CM> IntoIterator + for &'a InlineSparseScalarLookupSet +{ + into_iter_ref_fn!(); +} + +impl SetIterator + for InlineSparseScalarLookupSet +{ + type Iterator<'a> + = Iter<'a, T> + where + T: 'a, + CM: 'a; + + set_iterator_boilerplate!(); +} + +impl Set + for InlineSparseScalarLookupSet +where + T: Scalar, + CM: CollectionMagnitude, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> + for &InlineSparseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, + CM: CollectionMagnitude, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> + for &InlineSparseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, + CM: CollectionMagnitude, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> + for &InlineSparseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, + CM: CollectionMagnitude, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> + for &InlineSparseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, + CM: CollectionMagnitude, +{ + sub_fn!(RandomState); +} + +impl PartialEq + for InlineSparseScalarLookupSet +where + T: Scalar, + ST: Set, + CM: CollectionMagnitude, +{ + partial_eq_fn!(); +} + +impl Eq for InlineSparseScalarLookupSet +where + T: Scalar, + CM: CollectionMagnitude, +{ +} diff --git a/frozen-collections-core/src/inline_sets/mod.rs b/frozen-collections-core/src/inline_sets/mod.rs new file mode 100644 index 0000000..59b690a --- /dev/null +++ b/frozen-collections-core/src/inline_sets/mod.rs @@ -0,0 +1,15 @@ +//! Specialized static-friendly read-only set types. + +//pub use inline_binary_search_set::InlineBinarySearchSet; +pub use inline_dense_scalar_lookup_set::InlineDenseScalarLookupSet; +pub use inline_hash_set::InlineHashSet; +pub use inline_ordered_scan_set::InlineOrderedScanSet; +pub use inline_scan_set::InlineScanSet; +pub use inline_sparse_scalar_lookup_set::InlineSparseScalarLookupSet; + +//mod inline_binary_search_set; +mod inline_dense_scalar_lookup_set; +mod inline_hash_set; +mod inline_ordered_scan_set; +mod inline_scan_set; +mod inline_sparse_scalar_lookup_set; diff --git a/frozen-collections-core/src/lib.rs b/frozen-collections-core/src/lib.rs new file mode 100644 index 0000000..9d6d67e --- /dev/null +++ b/frozen-collections-core/src/lib.rs @@ -0,0 +1,26 @@ +//! Implementation crate for the frozen collections. +//! +//!
+//! This crate is an implementation detail of the `frozen_collections` crate. +//! This crate's API is therefore not stable and may change at any time. Please do not +//! use this crate directly, and instead use the public API provided by the +//! `frozen_collections` crate. +//!
+ +#![cfg_attr(not(any(test, feature = "std")), no_std)] + +extern crate alloc; +extern crate core; + +mod analyzers; +pub mod facade_maps; +pub mod facade_sets; +pub mod hash_tables; +pub mod hashers; +pub mod inline_maps; +pub mod inline_sets; +pub mod macros; +pub mod maps; +pub mod sets; +pub mod traits; +mod utils; diff --git a/frozen-collections-core/src/macros/derive_scalar_macro.rs b/frozen-collections-core/src/macros/derive_scalar_macro.rs new file mode 100644 index 0000000..1a5d261 --- /dev/null +++ b/frozen-collections-core/src/macros/derive_scalar_macro.rs @@ -0,0 +1,141 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Error, Fields}; + +/// Implementation logic for the `Scalar` derive macro. +/// +/// # Errors +/// +/// Bad things happen to bad input +#[allow(clippy::module_name_repetitions)] +pub fn derive_scalar_macro(args: TokenStream) -> syn::Result { + let input: DeriveInput = syn::parse2(args)?; + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let Data::Enum(variants) = &input.data else { + return Err(Error::new_spanned( + name, + "Scalar can only be used with enums", + )); + }; + + if variants.variants.is_empty() { + return Err(Error::new_spanned( + name, + "Scalar can only be used with non-empty enums", + )); + } + + for v in &variants.variants { + if v.fields != Fields::Unit { + return Err(Error::new_spanned( + name, + "Scalar can only be used with enums that only contain unit variants", + )); + } + + if v.discriminant.is_some() { + return Err(Error::new_spanned( + name, + "Scalar can only be used with enums that do not have explicit discriminants", + )); + } + } + + let mut matches = Vec::new(); + for variant in &variants.variants { + let ident = &variant.ident; + + let index = matches.len(); + matches.push(quote! { #name::#ident => #index}); + } + + Ok(quote! { + #[automatically_derived] + impl #impl_generics ::frozen_collections::Scalar for #name #ty_generics #where_clause { + fn index(&self) -> usize { + match self { + #(#matches),* + } + } + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic() { + assert!(derive_scalar_macro(quote!( + enum Color { + Red, + Green, + Blue, + } + )) + .is_ok()); + } + + #[test] + fn only_with_enums() { + let r = derive_scalar_macro(quote!( + struct Color { + red: i32, + green: i32, + blue: i32, + } + )); + + assert_eq!( + "Scalar can only be used with enums", + r.unwrap_err().to_string() + ); + } + + #[test] + fn non_empty_enums() { + let r = derive_scalar_macro(quote!( + enum Color {} + )); + + assert_eq!( + "Scalar can only be used with non-empty enums", + r.unwrap_err().to_string() + ); + } + + #[test] + fn only_unit_variants() { + let r = derive_scalar_macro(quote!( + enum Color { + Red, + Green(i32), + Blue, + } + )); + + assert_eq!( + "Scalar can only be used with enums that only contain unit variants", + r.unwrap_err().to_string() + ); + } + + #[test] + fn no_explicit_discriminants() { + let r = derive_scalar_macro(quote!( + enum Color { + Red, + Green = 2, + Blue, + } + )); + + assert_eq!( + "Scalar can only be used with enums that do not have explicit discriminants", + r.unwrap_err().to_string() + ); + } +} diff --git a/frozen-collections-core/src/macros/generator.rs b/frozen-collections-core/src/macros/generator.rs new file mode 100644 index 0000000..d17329d --- /dev/null +++ b/frozen-collections-core/src/macros/generator.rs @@ -0,0 +1,830 @@ +use crate::analyzers::{ + analyze_scalar_keys, analyze_slice_keys, ScalarKeyAnalysisResult, SliceKeyAnalysisResult, +}; +use crate::hash_tables::HashTable; +use crate::hashers::{LeftRangeHasher, PassthroughHasher, RightRangeHasher}; +use crate::macros::parsing::entry::Entry; +use crate::macros::parsing::payload::Payload; +use crate::traits::{ + CollectionMagnitude, Hasher, LargeCollection, MediumCollection, Scalar, SmallCollection, +}; +use crate::utils::{dedup_by_keep_last, slow_dedup_by_keep_last}; +use ahash::RandomState; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use std::fmt::Display; +use std::ops::Range; +use std::str::FromStr; +use syn::{parse2, Expr, ExprLit, Lit, LitInt, LitStr}; + +struct ProcessedEntry { + base: Entry, + parsed_key: K, + hash_code: u64, +} + +impl ToTokens for ProcessedEntry { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.base.to_tokens(tokens); + } +} + +pub struct Output { + pub ctor: TokenStream, + pub type_sig: TokenStream, + pub constant: bool, +} + +impl Output { + const fn new(ctor: TokenStream, type_sig: TokenStream, constant: bool) -> Self { + Self { + ctor, + type_sig, + constant, + } + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum MacroKind { + Scalar, + String, + Hashed, + Ordered, +} + +#[derive(Eq, PartialEq)] +enum ScalarType { + I8, + I16, + I32, + I64, + ISize, + U8, + U16, + U32, + U64, + USize, + Undecided, +} + +#[derive(Eq, PartialEq)] +enum DiscoveredKeyKind { + LiteralScalar(ScalarType), + LiteralString, + Expression, +} + +enum EffectiveKeyKind { + AllLiteralScalars(ScalarType), + LiteralAndExpressionScalars, + AllLiteralStrings, + LiteralAndExpressionStrings, + Hashed, + Ordered, +} + +struct Generator { + entries: Vec, + seeds: (u64, u64, u64, u64), + as_set: bool, + key_type: TokenStream, + value_type: TokenStream, +} + +pub fn generate( + payload: Payload, + seeds: (u64, u64, u64, u64), + as_set: bool, + key_type: TokenStream, + value_type: TokenStream, + macro_kind: MacroKind, +) -> syn::Result { + let mut gen = Generator { + entries: vec![], + seeds, + as_set, + key_type, + value_type, + }; + + match payload { + Payload::InlineEntries(entries) => { + gen.entries = entries; + + if gen.entries.is_empty() { + return Err(syn::Error::new( + Span::call_site(), + "no collection entries supplied", + )); + } + + match gen.assess_keys(macro_kind)? { + EffectiveKeyKind::AllLiteralScalars(ScalarType::I8) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::I16) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::I32) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::I64) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::ISize) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::U8) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::U16) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::U32) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::U64) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::USize) => { + gen.handle_literal_scalar_keys::("") + } + EffectiveKeyKind::AllLiteralScalars(ScalarType::Undecided) => { + gen.handle_literal_scalar_keys::("i32") + } + EffectiveKeyKind::LiteralAndExpressionScalars => { + gen.handle_non_literal_scalar_keys() + } + EffectiveKeyKind::AllLiteralStrings => gen.handle_literal_string_keys(), + EffectiveKeyKind::LiteralAndExpressionStrings => { + gen.handle_non_literal_string_keys() + } + EffectiveKeyKind::Hashed => gen.handle_hashed_keys(), + EffectiveKeyKind::Ordered => gen.handle_ordered_keys(), + } + } + + Payload::Vector(expr) => match macro_kind { + MacroKind::Scalar => Ok(gen.emit_facade(&expr, "Scalar")), + MacroKind::String => Ok(gen.emit_facade(&expr, "String")), + MacroKind::Hashed => Ok(gen.emit_facade(&expr, "Hash")), + MacroKind::Ordered => Ok(gen.emit_facade(&expr, "Ordered")), + }, + } +} + +impl Generator { + fn assess_keys(&self, macro_kind: MacroKind) -> syn::Result { + let mut num_strings = 0; + let mut num_scalars = 0; + let mut scalar_type: ScalarType = ScalarType::Undecided; + + for entry in &self.entries { + let discovered_key_kind = match &entry.key { + Expr::Lit(expr) => Self::eval_literal_expr(expr)?, + Expr::Group(group) => match &*group.expr { + Expr::Lit(expr) => Self::eval_literal_expr(expr)?, + _ => DiscoveredKeyKind::Expression, + }, + _ => DiscoveredKeyKind::Expression, + }; + + if macro_kind == MacroKind::Scalar + && discovered_key_kind == DiscoveredKeyKind::LiteralString + { + return Err(syn::Error::new( + Span::call_site(), + "scalar macro cannot contain string keys", + )); + } else if macro_kind == MacroKind::String + && discovered_key_kind != DiscoveredKeyKind::LiteralString + && discovered_key_kind != DiscoveredKeyKind::Expression + { + return Err(syn::Error::new( + Span::call_site(), + "string macro cannot contain scalar keys", + )); + } + + match discovered_key_kind { + DiscoveredKeyKind::LiteralScalar(ScalarType::Undecided) => num_scalars += 1, + DiscoveredKeyKind::LiteralScalar(discovered_scalar_type) => { + num_scalars += 1; + if scalar_type == ScalarType::Undecided { + scalar_type = discovered_scalar_type; + } else if discovered_scalar_type != scalar_type { + return Err(syn::Error::new( + Span::call_site(), + "incompatible scalar literal type", + )); + } + } + + DiscoveredKeyKind::LiteralString => num_strings += 1, + DiscoveredKeyKind::Expression => {} + } + } + + Ok(if num_scalars == self.entries.len() { + EffectiveKeyKind::AllLiteralScalars(scalar_type) + } else if num_scalars > 0 && num_strings == 0 { + EffectiveKeyKind::LiteralAndExpressionScalars + } else if num_strings == self.entries.len() { + EffectiveKeyKind::AllLiteralStrings + } else if num_strings > 0 { + EffectiveKeyKind::LiteralAndExpressionStrings + } else { + match macro_kind { + MacroKind::Scalar => EffectiveKeyKind::LiteralAndExpressionScalars, + MacroKind::String => EffectiveKeyKind::LiteralAndExpressionStrings, + MacroKind::Hashed => EffectiveKeyKind::Hashed, + MacroKind::Ordered => EffectiveKeyKind::Ordered, + } + }) + } + + fn eval_literal_expr(expr: &ExprLit) -> syn::Result { + let kind = match &expr.lit { + Lit::Str(_) => DiscoveredKeyKind::LiteralString, + Lit::Int(expr) => match expr.suffix() { + "i8" => DiscoveredKeyKind::LiteralScalar(ScalarType::I8), + "i16" => DiscoveredKeyKind::LiteralScalar(ScalarType::I16), + "i32" => DiscoveredKeyKind::LiteralScalar(ScalarType::I32), + "i64" => DiscoveredKeyKind::LiteralScalar(ScalarType::I64), + "isize" => DiscoveredKeyKind::LiteralScalar(ScalarType::ISize), + "u8" => DiscoveredKeyKind::LiteralScalar(ScalarType::U8), + "u16" => DiscoveredKeyKind::LiteralScalar(ScalarType::U16), + "u32" => DiscoveredKeyKind::LiteralScalar(ScalarType::U32), + "u64" => DiscoveredKeyKind::LiteralScalar(ScalarType::U64), + "usize" => DiscoveredKeyKind::LiteralScalar(ScalarType::USize), + "" => DiscoveredKeyKind::LiteralScalar(ScalarType::Undecided), + _ => { + return Err(syn::Error::new_spanned( + expr, + format!("unknown suffix {} for scalar value", expr.suffix()), + )) + } + }, + _ => { + return Err(syn::Error::new_spanned( + expr, + "invalid literal, expecting a scalar or string value", + )) + } + }; + + Ok(kind) + } + + fn handle_literal_scalar_keys(self, suffix: &str) -> syn::Result + where + K: Scalar + Ord + FromStr, + K::Err: Display, + { + let mut processed_entries = Vec::with_capacity(self.entries.len()); + + let hasher = PassthroughHasher::new(); + for entry in &self.entries { + let lit = parse2::(entry.key.to_token_stream())?; + let k = lit.base10_parse::()?; + + let mut e = entry.clone(); + if !suffix.is_empty() { + e = Entry { + key: syn::parse_str::(&format!("{lit}{suffix}"))?, + value: e.value, + } + } + + processed_entries.push(ProcessedEntry { + base: e, + parsed_key: k, + hash_code: hasher.hash(&k), + }); + } + + processed_entries.sort_by_key(|x| x.parsed_key); + dedup_by_keep_last(&mut processed_entries, |x, y| x.parsed_key == y.parsed_key); + + let analysis = analyze_scalar_keys(processed_entries.iter().map(|x| x.parsed_key)); + Ok(match analysis { + ScalarKeyAnalysisResult::DenseRange => { + self.emit_inline_dense_scalar_lookup(&processed_entries) + } + ScalarKeyAnalysisResult::SparseRange => { + self.emit_inline_sparse_scalar_lookup(&processed_entries) + } + ScalarKeyAnalysisResult::General => { + if processed_entries.len() < 3 { + self.emit_inline_scan(&processed_entries) + } else { + self.emit_inline_hash_with_passthrough(processed_entries) + } + } + }) + } + + fn handle_literal_string_keys(self) -> syn::Result { + let bh = RandomState::with_seeds(self.seeds.0, self.seeds.1, self.seeds.2, self.seeds.3); + let mut processed_entries = Vec::with_capacity(self.entries.len()); + + for entry in &self.entries { + let ls = parse2::(entry.key.to_token_stream())?; + processed_entries.push(ProcessedEntry { + base: entry.clone(), + parsed_key: ls.value().to_string(), + hash_code: bh.hash_one(ls.value().to_string().as_str()), + }); + } + + processed_entries.sort_by(|x, y| x.parsed_key.cmp(&y.parsed_key)); + dedup_by_keep_last(&mut processed_entries, |x, y| x.parsed_key == y.parsed_key); + + if processed_entries.len() < 3 { + return Ok(self.emit_inline_scan(&processed_entries)); + } else if processed_entries.len() < 5 { + return Ok(self.emit_inline_ordered_scan(&processed_entries)); + } + + let analysis = analyze_slice_keys( + processed_entries.iter().map(|x| x.parsed_key.as_bytes()), + &bh, + ); + + Ok(match analysis { + SliceKeyAnalysisResult::LeftHandSubslice(range) => { + let hasher = LeftRangeHasher::new(bh, range.clone()); + for entry in &mut processed_entries { + entry.hash_code = hasher.hash(&entry.parsed_key.as_str()); + } + + self.emit_inline_hash_with_range( + processed_entries, + range, + "e!(InlineLeftRangeHasher), + ) + } + + SliceKeyAnalysisResult::RightHandSubslice(range) => { + let hasher = RightRangeHasher::new(bh, range.clone()); + for entry in &mut processed_entries { + entry.hash_code = hasher.hash(&entry.parsed_key.as_str()); + } + + self.emit_inline_hash_with_range( + processed_entries, + range, + "e!(InlineRightRangeHasher), + ) + } + + SliceKeyAnalysisResult::Length => { + let hasher = PassthroughHasher::new(); + for entry in &mut processed_entries { + entry.hash_code = hasher.hash(&entry.parsed_key.as_str()); + } + + self.emit_inline_hash_with_passthrough(processed_entries) + } + + SliceKeyAnalysisResult::General => self.emit_inline_hash_with_bridge(processed_entries), + }) + } + + fn handle_non_literal_scalar_keys(mut self) -> syn::Result { + slow_dedup_by_keep_last(&mut self.entries, |x, y| x.key == y.key); + + let mut processed_entries = Vec::with_capacity(self.entries.len()); + + for entry in &self.entries { + processed_entries.push(ProcessedEntry { + base: entry.clone(), + parsed_key: (), + hash_code: 0, + }); + } + + if processed_entries.len() < 3 { + Ok(self.emit_inline_scan(&processed_entries)) + } else { + Ok(self.emit_hash_with_bridge(&processed_entries)) + } + } + + fn handle_non_literal_string_keys(mut self) -> syn::Result { + slow_dedup_by_keep_last(&mut self.entries, |x, y| x.key == y.key); + + let mut processed_entries = Vec::with_capacity(self.entries.len()); + + for entry in &self.entries { + processed_entries.push(ProcessedEntry { + base: entry.clone(), + parsed_key: (), + hash_code: 0, + }); + } + + if processed_entries.len() < 3 { + Ok(self.emit_inline_scan(&processed_entries)) + } else { + Ok(self.emit_hash_with_bridge(&processed_entries)) + } + } + + fn handle_hashed_keys(mut self) -> syn::Result { + slow_dedup_by_keep_last(&mut self.entries, |x, y| x.key == y.key); + + let mut processed_entries = Vec::with_capacity(self.entries.len()); + + for entry in &self.entries { + processed_entries.push(ProcessedEntry { + base: entry.clone(), + parsed_key: (), + hash_code: 0, + }); + } + + if processed_entries.len() < 3 { + Ok(self.emit_inline_scan(&processed_entries)) + } else { + Ok(self.emit_hash_with_bridge(&processed_entries)) + } + } + + fn handle_ordered_keys(mut self) -> syn::Result { + slow_dedup_by_keep_last(&mut self.entries, |x, y| x.key == y.key); + + let mut processed_entries = Vec::with_capacity(self.entries.len()); + + for entry in &self.entries { + processed_entries.push(ProcessedEntry { + base: entry.clone(), + parsed_key: (), + hash_code: 0, + }); + } + + if processed_entries.len() < 3 { + Ok(self.emit_inline_scan(&processed_entries)) + } else if processed_entries.len() < 6 { + Ok(self.emit_ordered_scan(&processed_entries)) + } else { + Ok(self.emit_binary_search(&processed_entries)) + } + } + + fn emit_inline_scan(self, entries: &[ProcessedEntry]) -> Output { + let key_type = &self.key_type; + let value_type = &self.value_type; + let len = proc_macro2::Literal::usize_unsuffixed(entries.len()); + + let mut ty = quote!(::frozen_collections::inline_maps::InlineScanMap); + let mut generics = quote!(<#key_type, #value_type, #len>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new_raw([ + #( + #entries, + )* + ])); + + if self.as_set { + ty = quote!(::frozen_collections::inline_sets::InlineScanSet); + generics = quote!(<#key_type, #len>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, true) + } + + fn emit_inline_ordered_scan(self, entries: &[ProcessedEntry]) -> Output { + let key_type = &self.key_type; + let value_type = &self.value_type; + let len = proc_macro2::Literal::usize_unsuffixed(entries.len()); + + let mut ty = quote!(::frozen_collections::inline_maps::InlineOrderedScanMap); + let mut generics = quote!(<#key_type, #value_type, #len>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new_raw([ + #( + #entries, + )* + ])); + + if self.as_set { + ty = quote!(::frozen_collections::inline_sets::InlineOrderedScanSet); + generics = quote!(<#key_type, #len>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, true) + } + + fn emit_inline_dense_scalar_lookup(self, entries: &[ProcessedEntry]) -> Output + where + K: Scalar + Ord + FromStr, + { + let key_type = &self.key_type; + let value_type = &self.value_type; + let len = proc_macro2::Literal::usize_unsuffixed(entries.len()); + + let min_key = &entries[0].parsed_key.index(); + let max_key = &entries[entries.len() - 1].parsed_key.index(); + + let mut ty = quote!(::frozen_collections::inline_maps::InlineDenseScalarLookupMap); + let mut generics = quote!(<#key_type, #value_type, #len>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new_raw([ + #( + #entries, + )* + ], #min_key, #max_key)); + + if self.as_set { + ty = quote!(::frozen_collections::inline_sets::InlineDenseScalarLookupSet); + generics = quote!(<#key_type, #len>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, true) + } + + fn emit_inline_sparse_scalar_lookup(self, entries: &[ProcessedEntry]) -> Output + where + K: Scalar + Ord + FromStr, + { + let min_key = &entries[0].parsed_key.index(); + let max_key = &entries[entries.len() - 1].parsed_key.index(); + + let count = max_key - min_key + 1; + let mut lookup = vec![0; count]; + + for (i, entry) in entries.iter().enumerate() { + let index_in_lookup = entry.parsed_key.index() - min_key; + let index_in_entries = i + 1; + lookup[index_in_lookup] = index_in_entries; + } + + let key_type = &self.key_type; + let value_type = &self.value_type; + let len = proc_macro2::Literal::usize_unsuffixed(entries.len()); + let magnitude = collection_magnitude(count); + let lookup = lookup + .iter() + .map(|x| proc_macro2::Literal::usize_unsuffixed(*x)); + let lookup_len = proc_macro2::Literal::usize_unsuffixed(lookup.len()); + + let mut ty = quote!(::frozen_collections::inline_maps::InlineSparseScalarLookupMap); + let mut generics = quote!(<#key_type, #value_type, #len, #lookup_len, #magnitude>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new_raw([ + #( + #entries, + )* + ], + [ + #( + #lookup, + )* + ], #min_key, #max_key)); + + if self.as_set { + ty = quote!(::frozen_collections::inline_sets::InlineSparseScalarLookupSet); + generics = quote!(<#key_type, #len, #lookup_len, #magnitude>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, true) + } + + fn emit_inline_hash_with_bridge(self, entries: Vec>) -> Output { + let key_type = &self.key_type; + let value_type = &self.value_type; + let len = proc_macro2::Literal::usize_unsuffixed(entries.len()); + let (ht, magnitude, num_slots) = self.hash_table(entries); + let (s0, s1, s2, s3) = self.seeds; + + let mut ty = quote!(::frozen_collections::inline_maps::InlineHashMap); + let mut generics = quote!(<#key_type, #value_type, #len, #num_slots, #magnitude, ::frozen_collections::hashers::BridgeHasher<::frozen_collections::ahash::RandomState>>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new_raw(#ht, ::frozen_collections::hashers::BridgeHasher::new(::frozen_collections::ahash::RandomState::with_seeds(#s0, #s1, #s2, #s3)))); + + if self.as_set { + ty = quote!(::frozen_collections::inline_sets::InlineHashSet); + generics = quote!(<#key_type, #len, #num_slots, #magnitude, ::frozen_collections::hashers::BridgeHasher<::frozen_collections::ahash::RandomState>>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, true) + } + + fn emit_inline_hash_with_range( + self, + entries: Vec>, + hash_range: Range, + hasher_type: &TokenStream, + ) -> Output { + let key_type = &self.key_type; + let value_type = &self.value_type; + let len = proc_macro2::Literal::usize_unsuffixed(entries.len()); + let (ht, magnitude, num_slots) = self.hash_table(entries); + let (s0, s1, s2, s3) = self.seeds; + let range_start = proc_macro2::Literal::usize_unsuffixed(hash_range.start); + let range_end = proc_macro2::Literal::usize_unsuffixed(hash_range.end); + + let mut ty = quote!(::frozen_collections::inline_maps::InlineHashMap); + let mut generics = quote!(<#key_type, #value_type, #len, #num_slots, #magnitude, ::frozen_collections::hashers::#hasher_type<#range_start, #range_end, ::frozen_collections::ahash::RandomState>>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new_raw(#ht, ::frozen_collections::hashers::#hasher_type::new(::frozen_collections::ahash::RandomState::with_seeds(#s0, #s1, #s2, #s3)))); + + if self.as_set { + ty = quote!(::frozen_collections::inline_sets::InlineHashSet); + generics = quote!(<#key_type, #len, #num_slots, #magnitude, ::frozen_collections::hashers::#hasher_type<#range_start, #range_end, ::frozen_collections::ahash::RandomState>>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, true) + } + + fn emit_inline_hash_with_passthrough(self, entries: Vec>) -> Output { + let key_type = &self.key_type; + let value_type = &self.value_type; + let len = proc_macro2::Literal::usize_unsuffixed(entries.len()); + let (ht, magnitude, num_slots) = self.hash_table(entries); + + let mut ty = quote!(::frozen_collections::inline_maps::InlineHashMap); + let mut generics = quote!(<#key_type, #value_type, #len, #num_slots, #magnitude, ::frozen_collections::hashers::PassthroughHasher>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new_raw(#ht, ::frozen_collections::hashers::PassthroughHasher::new())); + + if self.as_set { + ty = quote!(::frozen_collections::inline_sets::InlineHashSet); + generics = quote!(<#key_type, #len, #num_slots, #magnitude, ::frozen_collections::hashers::PassthroughHasher>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, true) + } + + fn emit_hash_with_bridge(self, entries: &[ProcessedEntry]) -> Output { + let key_type = &self.key_type; + let value_type = &self.value_type; + + let mut ty = quote!(::frozen_collections::maps::HashMap); + let magnitude = collection_magnitude(entries.len()); + let mut generics = quote!(<#key_type, #value_type, #magnitude>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new(vec![ + #( + #entries, + )* + ], ::frozen_collections::hashers::BridgeHasher::new(::frozen_collections::ahash::RandomState::new())).unwrap()); + + if self.as_set { + ty = quote!(::frozen_collections::sets::HashSet); + generics = quote!(<#key_type, #magnitude>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, false) + } + + fn emit_ordered_scan(self, entries: &[ProcessedEntry]) -> Output { + let key_type = &self.key_type; + let value_type = &self.value_type; + + let mut ty = quote!(::frozen_collections::maps::OrderedScanMap); + let mut generics = quote!(<#key_type, #value_type>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new(vec![ + #( + #entries, + )* + ])); + + if self.as_set { + ty = quote!(::frozen_collections::sets::OrderedScanSet); + generics = quote!(<#key_type>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, false) + } + + fn emit_binary_search(self, entries: &[ProcessedEntry]) -> Output { + let key_type = &self.key_type; + let value_type = &self.value_type; + + let mut ty = quote!(::frozen_collections::maps::BinarySearchMap); + let mut generics = quote!(<#key_type, #value_type>); + let mut type_sig = quote!(#ty::#generics); + let mut ctor = quote!(#type_sig::new(vec![ + #( + #entries, + )* + ])); + + if self.as_set { + ty = quote!(::frozen_collections::sets::BinarySearchSet); + generics = quote!(<#key_type>); + type_sig = quote!(#ty::#generics); + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, false) + } + + fn emit_facade(self, expr: &Expr, variety: &str) -> Output { + let key_type = &self.key_type; + let value_type = &self.value_type; + + let mut type_name = format_ident!("Facade{}Map", variety); + let mut ty = quote!(::frozen_collections::facade_maps::#type_name); + + let mut type_sig = if variety == "String" { + quote!(#ty::<#value_type>) + } else { + quote!(#ty::<#key_type, #value_type>) + }; + + let converted_expr = if self.as_set { + quote!(#expr.into_iter().map(|x| (x, ())).collect()) + } else { + quote!(#expr) + }; + + let mut ctor = if variety == "Hash" { + quote!(#type_sig::new(#converted_expr, ::frozen_collections::hashers::BridgeHasher::new(::frozen_collections::ahash::RandomState::new()))) + } else if variety == "String" { + quote!(#type_sig::new(#converted_expr, ::frozen_collections::ahash::RandomState::new())) + } else { + quote!(#type_sig::new(#converted_expr)) + }; + + if self.as_set { + type_name = format_ident!("Facade{}Set", variety); + ty = quote!(::frozen_collections::facade_sets::#type_name); + + type_sig = if variety == "String" { + quote!(#ty) + } else { + quote!(#ty::<#key_type>) + }; + + ctor = quote!(#type_sig::new(#ctor)); + } + + Output::new(ctor, type_sig, false) + } + + fn hash_table( + &self, + entries: Vec>, + ) -> (TokenStream, TokenStream, TokenStream) { + let key_type = &self.key_type; + let value_type = &self.value_type; + let len = proc_macro2::Literal::usize_unsuffixed(entries.len()); + + let ht = HashTable::<_, LargeCollection>::new(entries, |x| x.hash_code).unwrap(); + let slots = ht.slots; + let num_slots = proc_macro2::Literal::usize_unsuffixed(slots.len()); + let entries = ht.entries; + let magnitude = collection_magnitude(slots.len()); + + ( + quote!(::frozen_collections::hash_tables::InlineHashTable::<(#key_type, #value_type), #len, #num_slots, #magnitude>::new_raw( + [ + #( + #slots, + )* + ], + [ + #( + #entries, + )* + ], + )), + magnitude, + quote!(#num_slots), + ) + } +} + +fn collection_magnitude(len: usize) -> TokenStream { + if len <= SmallCollection::MAX_CAPACITY { + quote!(::frozen_collections::SmallCollection) + } else if len <= MediumCollection::MAX_CAPACITY { + quote!(::frozen_collections::MediumCollection) + } else { + quote!(::frozen_collections::LargeCollection) + } +} diff --git a/frozen-collections-core/src/macros/hash_table.rs b/frozen-collections-core/src/macros/hash_table.rs new file mode 100644 index 0000000..467e8cc --- /dev/null +++ b/frozen-collections-core/src/macros/hash_table.rs @@ -0,0 +1,14 @@ +use crate::hash_tables::HashTableSlot; +use proc_macro2::Literal; +use quote::{quote, ToTokens}; + +impl ToTokens for HashTableSlot { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let min_index = Literal::usize_unsuffixed(self.min_index); + let max_index = Literal::usize_unsuffixed(self.max_index); + + tokens.extend( + quote!(::frozen_collections::hash_tables::HashTableSlot::new(#min_index, #max_index)), + ); + } +} diff --git a/frozen-collections-core/src/macros/map_macros.rs b/frozen-collections-core/src/macros/map_macros.rs new file mode 100644 index 0000000..de2dd4a --- /dev/null +++ b/frozen-collections-core/src/macros/map_macros.rs @@ -0,0 +1,114 @@ +use crate::macros::generator; +use crate::macros::generator::MacroKind; +use crate::macros::parsing::long_form_map::LongFormMap; +use crate::macros::parsing::map::Map; +use crate::macros::parsing::short_form_map::ShortFormMap; +use crate::utils::pick_compile_time_random_seeds; +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse2; + +/// Implementation logic for the `fz_hash_map!` macro. +/// +/// # Errors +/// +/// Bad things happen to bad input +pub fn fz_hash_map_macro(args: TokenStream) -> syn::Result { + fz_map_macro(args, pick_compile_time_random_seeds(), MacroKind::Hashed) +} + +/// Implementation logic for the `fz_ordered_map!` macro. +/// +/// # Errors +/// +/// Bad things happen to bad input +pub fn fz_ordered_map_macro(args: TokenStream) -> syn::Result { + fz_map_macro(args, pick_compile_time_random_seeds(), MacroKind::Ordered) +} + +/// Implementation logic for the `fz_string_map!` macro. +/// +/// # Errors +/// +/// Bad things happen to bad input +pub fn fz_string_map_macro(args: TokenStream) -> syn::Result { + fz_map_macro(args, pick_compile_time_random_seeds(), MacroKind::String) +} + +/// Implementation logic for the `fz_scalar_map!` macro. +/// +/// # Errors +/// +/// Bad things happen to bad input +pub fn fz_scalar_map_macro(args: TokenStream) -> syn::Result { + fz_map_macro(args, pick_compile_time_random_seeds(), MacroKind::Scalar) +} + +fn fz_map_macro( + args: TokenStream, + seeds: (u64, u64, u64, u64), + macro_kind: MacroKind, +) -> syn::Result { + let input = parse2::(args)?; + + match input { + Map::Short(map) => short_form_fz_map_macro(map, seeds, macro_kind), + Map::Long(map) => long_form_fz_map_macro(map, seeds, macro_kind), + } +} + +fn short_form_fz_map_macro( + map: ShortFormMap, + seeds: (u64, u64, u64, u64), + macro_kind: MacroKind, +) -> syn::Result { + Ok(generator::generate(map.payload, seeds, false, quote!(_), quote!(_), macro_kind)?.ctor) +} + +fn long_form_fz_map_macro( + map: LongFormMap, + seeds: (u64, u64, u64, u64), + macro_kind: MacroKind, +) -> syn::Result { + let key_type = map.key_type; + let value_type = map.value_type; + + let key_type = if map.key_type_amp { + quote!(&'static #key_type) + } else { + quote!(#key_type) + }; + + let value_type = quote!(#value_type); + + let output = generator::generate(map.payload, seeds, false, key_type, value_type, macro_kind)?; + + let type_sig = output.type_sig; + let ctor = output.ctor; + let var_name = &map.var_name; + let type_name = &map.type_name; + let visibility = &map.visibility; + + if !map.is_static { + let mutable = if map.is_mutable { + quote!(mut) + } else { + quote!() + }; + + Ok(quote!( + type #type_name = #type_sig; + let #mutable #var_name: #type_name = #ctor; + )) + } else if output.constant { + Ok(quote!( + #visibility type #type_name = #type_sig; + #visibility static #var_name: #type_name = #ctor; + )) + } else { + Ok(quote!( + #visibility type #type_name = #type_sig; + #visibility static #var_name: std::sync::LazyLock<#type_name> = std::sync::LazyLock::new(|| { #ctor }); + )) + } +} diff --git a/frozen-collections-core/src/macros/mod.rs b/frozen-collections-core/src/macros/mod.rs new file mode 100644 index 0000000..1f77050 --- /dev/null +++ b/frozen-collections-core/src/macros/mod.rs @@ -0,0 +1,12 @@ +//! Implementation logic for frozen collection macros. + +pub use derive_scalar_macro::derive_scalar_macro; +pub use map_macros::*; +pub use set_macros::*; + +mod derive_scalar_macro; +mod generator; +mod hash_table; +mod map_macros; +mod parsing; +mod set_macros; diff --git a/frozen-collections-core/src/macros/parsing/entry.rs b/frozen-collections-core/src/macros/parsing/entry.rs new file mode 100644 index 0000000..1c06d76 --- /dev/null +++ b/frozen-collections-core/src/macros/parsing/entry.rs @@ -0,0 +1,35 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream}; +use syn::{Expr, Token}; + +#[derive(Clone)] +pub struct Entry { + pub key: Expr, + pub value: Option, +} + +impl Parse for Entry { + fn parse(input: ParseStream) -> syn::Result { + let key = input.parse::()?; + + if input.parse::]>().is_err() { + _ = input.parse::()?; + } + + let value = Some(input.parse::()?); + + Ok(Self { key, value }) + } +} + +impl ToTokens for Entry { + fn to_tokens(&self, tokens: &mut TokenStream) { + let key = &self.key; + if let Some(value) = &self.value { + tokens.extend(quote!((#key, #value))); + } else { + tokens.extend(quote!((#key, ()))); + } + } +} diff --git a/frozen-collections-core/src/macros/parsing/long_form_map.rs b/frozen-collections-core/src/macros/parsing/long_form_map.rs new file mode 100644 index 0000000..3c64250 --- /dev/null +++ b/frozen-collections-core/src/macros/parsing/long_form_map.rs @@ -0,0 +1,51 @@ +use crate::macros::parsing::payload::{parse_map_payload, Payload}; +use proc_macro2::Ident; +use syn::parse::{Parse, ParseStream}; +use syn::token::Eq; +use syn::{Path, Token, Visibility}; + +pub struct LongFormMap { + pub var_name: Ident, + pub type_name: Ident, + pub key_type_amp: bool, + pub key_type: Path, + pub value_type: Path, + pub payload: Payload, + + pub visibility: Visibility, + pub is_static: bool, + pub is_mutable: bool, +} + +impl Parse for LongFormMap { + fn parse(input: ParseStream) -> syn::Result { + // var_name: type_name + let var_name = input.parse::()?; + input.parse::()?; + let type_name = input.parse::()?; + + // + input.parse::()?; + let key_type_amp = input.parse::().ok(); + let key_type = input.parse::()?; + input.parse::()?; + let value_type = input.parse::()?; + input.parse::]>()?; + + input.parse::()?; + + Ok(Self { + var_name, + type_name, + key_type_amp: key_type_amp.is_some(), + key_type, + value_type, + payload: parse_map_payload(input)?, + + // these will be overridden by the caller + visibility: Visibility::Inherited, + is_static: false, + is_mutable: false, + }) + } +} diff --git a/frozen-collections-core/src/macros/parsing/long_form_set.rs b/frozen-collections-core/src/macros/parsing/long_form_set.rs new file mode 100644 index 0000000..ecfba14 --- /dev/null +++ b/frozen-collections-core/src/macros/parsing/long_form_set.rs @@ -0,0 +1,60 @@ +use crate::macros::parsing::payload::{parse_set_payload, Payload}; +use proc_macro2::Ident; +use syn::parse::{Parse, ParseStream}; +use syn::token::Eq; +use syn::{Expr, Path, Token, Visibility}; + +pub struct LongFormSet { + pub var_name: Ident, + pub type_name: Ident, + pub value_type_amp: bool, + pub value_type: Path, + pub payload: Payload, + + pub visibility: Visibility, + pub is_static: bool, + pub is_mutable: bool, +} + +impl Parse for LongFormSet { + fn parse(input: ParseStream) -> syn::Result { + // var_name: type_name + let var_name = input.parse::()?; + input.parse::()?; + let type_name = input.parse::()?; + + // + input.parse::()?; + let value_type_amp = input.parse::().ok(); + let value_type = input.parse::()?; + input.parse::]>()?; + + input.parse::()?; + + Ok(Self { + var_name, + type_name, + value_type_amp: value_type_amp.is_some(), + value_type, + payload: parse_set_payload(input)?, + + // these will be overridden by the caller + visibility: Visibility::Inherited, + is_static: false, + is_mutable: false, + }) + } +} + +#[derive(Clone)] +pub struct SetEntry { + pub value: Expr, +} + +impl Parse for SetEntry { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + value: input.parse::()?, + }) + } +} diff --git a/frozen-collections-core/src/macros/parsing/map.rs b/frozen-collections-core/src/macros/parsing/map.rs new file mode 100644 index 0000000..eaac204 --- /dev/null +++ b/frozen-collections-core/src/macros/parsing/map.rs @@ -0,0 +1,43 @@ +use crate::macros::parsing::long_form_map::LongFormMap; +use crate::macros::parsing::short_form_map::ShortFormMap; +use syn::parse::Parse; +use syn::{Token, Visibility}; + +pub enum Map { + Short(ShortFormMap), + Long(LongFormMap), +} + +impl Parse for Map { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let visibility = input.parse::()?; + if visibility != Visibility::Inherited && !input.peek(syn::Token![static]) { + return Err(input.error("expected `static`")); + } + + if input.peek(Token![static]) { + input.parse::()?; + let mut m = input.parse::()?; + m.visibility = visibility; + m.is_static = true; + Ok(Self::Long(m)) + } else if input.peek(Token![let]) { + input.parse::()?; + + let is_mutable = if input.peek(Token![mut]) { + input.parse::()?; + true + } else { + false + }; + + let mut m = input.parse::()?; + m.visibility = visibility; + m.is_static = false; + m.is_mutable = is_mutable; + Ok(Self::Long(m)) + } else { + Ok(Self::Short(input.parse()?)) + } + } +} diff --git a/frozen-collections-core/src/macros/parsing/mod.rs b/frozen-collections-core/src/macros/parsing/mod.rs new file mode 100644 index 0000000..5cfaeb7 --- /dev/null +++ b/frozen-collections-core/src/macros/parsing/mod.rs @@ -0,0 +1,8 @@ +pub mod entry; +pub mod long_form_map; +pub mod long_form_set; +pub mod map; +pub mod payload; +pub mod set; +pub mod short_form_map; +pub mod short_form_set; diff --git a/frozen-collections-core/src/macros/parsing/payload.rs b/frozen-collections-core/src/macros/parsing/payload.rs new file mode 100644 index 0000000..aa57274 --- /dev/null +++ b/frozen-collections-core/src/macros/parsing/payload.rs @@ -0,0 +1,59 @@ +use crate::macros::parsing::entry::Entry; +use crate::macros::parsing::long_form_set::SetEntry; +use syn::parse::Parse; +use syn::{braced, Expr, Token}; + +/// Data associated with a frozen collection macro. +pub enum Payload { + /// Entries supplied inline with the macro + InlineEntries(Vec), + + /// An expression producing a vector of values to insert into the collection. + Vector(Expr), +} + +#[allow(clippy::module_name_repetitions)] +pub fn parse_set_payload(input: syn::parse::ParseStream) -> syn::Result { + let payload = if input.peek(::syn::token::Brace) { + // { value, value, ... }; + let content; + _ = braced!(content in input); + Payload::InlineEntries( + content + .parse_terminated(SetEntry::parse, Token![,])? + .into_iter() + .map(|x| Entry { + key: x.value, + value: None, + }) + .collect(), + ) + } else if input.is_empty() { + Payload::InlineEntries(Vec::new()) + } else { + Payload::Vector(input.parse::()?) + }; + + Ok(payload) +} + +#[allow(clippy::module_name_repetitions)] +pub fn parse_map_payload(input: syn::parse::ParseStream) -> syn::Result { + let payload = if input.peek(::syn::token::Brace) { + // { key: value, key: value, ... }; + let content; + _ = braced!(content in input); + Payload::InlineEntries( + content + .parse_terminated(Entry::parse, Token![,])? + .into_iter() + .collect(), + ) + } else if input.is_empty() { + Payload::InlineEntries(Vec::new()) + } else { + Payload::Vector(input.parse::()?) + }; + + Ok(payload) +} diff --git a/frozen-collections-core/src/macros/parsing/set.rs b/frozen-collections-core/src/macros/parsing/set.rs new file mode 100644 index 0000000..1e7e81a --- /dev/null +++ b/frozen-collections-core/src/macros/parsing/set.rs @@ -0,0 +1,43 @@ +use crate::macros::parsing::long_form_set::LongFormSet; +use crate::macros::parsing::short_form_set::ShortFormSet; +use syn::parse::Parse; +use syn::{Token, Visibility}; + +pub enum Set { + Short(ShortFormSet), + Long(LongFormSet), +} + +impl Parse for Set { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let visibility = input.parse::()?; + if visibility != Visibility::Inherited && !input.peek(syn::Token![static]) { + return Err(input.error("expected `static`")); + } + + if input.peek(Token![static]) { + input.parse::()?; + let mut s = input.parse::()?; + s.visibility = visibility; + s.is_static = true; + Ok(Self::Long(s)) + } else if input.peek(Token![let]) { + input.parse::()?; + + let is_mutable = if input.peek(Token![mut]) { + input.parse::()?; + true + } else { + false + }; + + let mut s = input.parse::()?; + s.visibility = visibility; + s.is_static = false; + s.is_mutable = is_mutable; + Ok(Self::Long(s)) + } else { + Ok(Self::Short(input.parse()?)) + } + } +} diff --git a/frozen-collections-core/src/macros/parsing/short_form_map.rs b/frozen-collections-core/src/macros/parsing/short_form_map.rs new file mode 100644 index 0000000..dc720a6 --- /dev/null +++ b/frozen-collections-core/src/macros/parsing/short_form_map.rs @@ -0,0 +1,14 @@ +use crate::macros::parsing::payload::{parse_map_payload, Payload}; +use syn::parse::{Parse, ParseStream}; + +pub struct ShortFormMap { + pub payload: Payload, +} + +impl Parse for ShortFormMap { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + payload: parse_map_payload(input)?, + }) + } +} diff --git a/frozen-collections-core/src/macros/parsing/short_form_set.rs b/frozen-collections-core/src/macros/parsing/short_form_set.rs new file mode 100644 index 0000000..155cb41 --- /dev/null +++ b/frozen-collections-core/src/macros/parsing/short_form_set.rs @@ -0,0 +1,14 @@ +use crate::macros::parsing::payload::{parse_set_payload, Payload}; +use syn::parse::{Parse, ParseStream}; + +pub struct ShortFormSet { + pub payload: Payload, +} + +impl Parse for ShortFormSet { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + payload: parse_set_payload(input)?, + }) + } +} diff --git a/frozen-collections-core/src/macros/set_macros.rs b/frozen-collections-core/src/macros/set_macros.rs new file mode 100644 index 0000000..7e2368f --- /dev/null +++ b/frozen-collections-core/src/macros/set_macros.rs @@ -0,0 +1,148 @@ +use crate::macros::generator; +use crate::macros::generator::MacroKind; +use crate::macros::parsing::long_form_set::LongFormSet; +use crate::macros::parsing::set::Set; +use crate::macros::parsing::short_form_set::ShortFormSet; +use crate::utils::pick_compile_time_random_seeds; +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse2; + +/// Implementation logic for the `fz_hash_set!` macro. +/// +/// # Errors +/// +/// Bad things happen to bad input +pub fn fz_hash_set_macro(args: TokenStream) -> syn::Result { + fz_set_macro(args, pick_compile_time_random_seeds(), MacroKind::Hashed) +} + +/// Implementation logic for the `fz_ordered_set!` macro. +/// +/// # Errors +/// +/// Bad things happen to bad input +pub fn fz_ordered_set_macro(args: TokenStream) -> syn::Result { + fz_set_macro(args, pick_compile_time_random_seeds(), MacroKind::Ordered) +} + +/// Implementation logic for the `fz_string_set!` macro. +/// +/// # Errors +/// +/// Bad things happen to bad input +pub fn fz_string_set_macro(args: TokenStream) -> syn::Result { + fz_set_macro(args, pick_compile_time_random_seeds(), MacroKind::String) +} + +/// Implementation logic for the `fz_scalar_set!` macro. +/// +/// # Errors +/// +/// Bad things happen to bad input +pub fn fz_scalar_set_macro(args: TokenStream) -> syn::Result { + fz_set_macro(args, pick_compile_time_random_seeds(), MacroKind::Scalar) +} + +fn fz_set_macro( + args: TokenStream, + seeds: (u64, u64, u64, u64), + macro_kind: MacroKind, +) -> syn::Result { + let input = parse2::(args)?; + + match input { + Set::Short(set) => short_form_fz_set_macro(set, seeds, macro_kind), + Set::Long(set) => long_form_fz_set_macro(set, seeds, macro_kind), + } +} + +fn short_form_fz_set_macro( + set: ShortFormSet, + seeds: (u64, u64, u64, u64), + macro_kind: MacroKind, +) -> syn::Result { + Ok(generator::generate(set.payload, seeds, true, quote!(_), quote!(_), macro_kind)?.ctor) +} + +fn long_form_fz_set_macro( + set: LongFormSet, + seeds: (u64, u64, u64, u64), + macro_kind: MacroKind, +) -> syn::Result { + let value_type = set.value_type; + + let value_type = if set.value_type_amp { + quote!(&'static #value_type) + } else { + quote!(#value_type) + }; + + let output = generator::generate(set.payload, seeds, true, value_type, quote!(_), macro_kind)?; + + let type_sig = output.type_sig; + let ctor = output.ctor; + let var_name = &set.var_name; + let type_name = &set.type_name; + let visibility = &set.visibility; + + if !set.is_static { + let mutable = if set.is_mutable { + quote!(mut) + } else { + quote!() + }; + + Ok(quote!( + type #type_name = #type_sig; + let #mutable #var_name: #type_name = #ctor; + )) + } else if output.constant { + Ok(quote!( + #visibility type #type_name = #type_sig; + #visibility static #var_name: #type_name = #ctor; + )) + } else { + Ok(quote!( + #visibility type #type_name = #type_sig; + #visibility static #var_name: std::sync::LazyLock<#type_name> = std::sync::LazyLock::new(|| { #ctor }); + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use quote::quote; + + #[test] + fn no_entries() { + let r = fz_scalar_set_macro(quote!({})); + + assert_eq!("no collection entries supplied", r.unwrap_err().to_string()); + } + + #[test] + fn invalid_suffix() { + let r = fz_scalar_set_macro(quote!( + { 123iXXX, 234iXXX } + )); + + assert_eq!( + "unknown suffix iXXX for scalar value", + r.unwrap_err().to_string() + ); + } + + #[test] + fn invalid_literal() { + let r = fz_scalar_set_macro(quote!( + { 123.0, 234.0 } + )); + + assert_eq!( + "invalid literal, expecting a scalar or string value", + r.unwrap_err().to_string() + ); + } +} diff --git a/frozen-collections-core/src/maps/binary_search_map.rs b/frozen-collections-core/src/maps/binary_search_map.rs new file mode 100644 index 0000000..d63b433 --- /dev/null +++ b/frozen-collections-core/src/maps/binary_search_map.rs @@ -0,0 +1,149 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +use crate::maps::decl_macros::{ + binary_search_core, contains_key_fn, debug_fn, get_many_mut_body, get_many_mut_fn, index_fn, + into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, map_boilerplate_for_slice, + map_iterator_boilerplate_for_slice, partial_eq_fn, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{Len, Map, MapIterator}; +use crate::utils::dedup_by_keep_last; + +/// A general purpose map implemented using binary search. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +#[derive(Clone)] +pub struct BinarySearchMap { + entries: Box<[(K, V)]>, +} + +impl BinarySearchMap +where + K: Ord, +{ + /// Creates a frozen map. + #[must_use] + pub fn new(mut entries: Vec<(K, V)>) -> Self { + entries.sort_by(|x, y| x.0.cmp(&y.0)); + dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + Self::new_raw(entries) + } + + /// Creates a frozen map. + #[must_use] + pub(crate) fn new_raw(processed_entries: Vec<(K, V)>) -> Self { + Self { + entries: processed_entries.into_boxed_slice(), + } + } +} + +impl BinarySearchMap { + binary_search_core!(); +} + +impl Len for BinarySearchMap { + fn len(&self) -> usize { + self.entries.len() + } +} + +impl Debug for BinarySearchMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Default for BinarySearchMap { + fn default() -> Self { + Self { + entries: Box::default(), + } + } +} + +impl Index<&Q> for BinarySearchMap +where + K: Borrow, + Q: ?Sized + Ord, +{ + index_fn!(); +} + +impl IntoIterator for BinarySearchMap { + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V> IntoIterator for &'a BinarySearchMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V> IntoIterator for &'a mut BinarySearchMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for BinarySearchMap +where + K: Ord, + V: PartialEq, + MT: Map, +{ + partial_eq_fn!(); +} + +impl Eq for BinarySearchMap +where + K: Ord, + V: Eq, +{ +} + +impl MapIterator for BinarySearchMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map for BinarySearchMap +where + K: Ord, +{ + map_boilerplate_for_slice!(entries); +} diff --git a/frozen-collections-core/src/maps/decl_macros.rs b/frozen-collections-core/src/maps/decl_macros.rs new file mode 100644 index 0000000..6f0bcb8 --- /dev/null +++ b/frozen-collections-core/src/maps/decl_macros.rs @@ -0,0 +1,588 @@ +macro_rules! get_many_mut_fn { + ($( $generic_type_bound1:ident $(+ $generic_type_bound2:ident)*)?) => { + #[doc = include_str!("../doc_snippets/get_many_mut_method.md")] + #[must_use] + pub fn get_many_mut(&mut self, keys: [&Q; N]) -> Option<[&mut V; N]> + where + K: Borrow, + Q: ?Sized $(+ $generic_type_bound1 $(+ $generic_type_bound2)*)?, + { + get_many_mut_body!(self, keys); + } + }; +} + +macro_rules! get_many_mut_body { + ($self:ident, $keys:ident) => { + if crate::utils::slow_find_duplicate(&$keys).is_some() { + return None; + } + + let mut result: core::mem::MaybeUninit<[&mut V; N]> = core::mem::MaybeUninit::uninit(); + let p = result.as_mut_ptr(); + let x: *mut Self = $self; + + unsafe { + for (i, key) in $keys.iter().enumerate() { + (*p)[i] = (*x).get_mut(key)?; + } + + return Some(result.assume_init()); + } + }; +} + +macro_rules! contains_key_fn { + ($( $generic_type_bound1:ident $(+ $generic_type_bound2:ident)*)?) => { + #[doc = include_str!("../doc_snippets/contains_key_method.md")] + #[inline] + #[must_use] + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + Q: ?Sized $(+ $generic_type_bound1 $(+ $generic_type_bound2)*)?, + { + self.get(key).is_some() + } + } +} + +macro_rules! index_fn { + () => { + type Output = V; + + fn index(&self, index: &Q) -> &Self::Output { + self.get(index).expect("index should be valid") + } + }; +} + +macro_rules! into_iter_fn_for_slice { + ($($entries:ident)+) => { + type Item = (K, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self$(. $entries)+.into()) + } + }; +} + +macro_rules! into_iter_ref_fn { + () => { + type Item = (&'a K, &'a V); + type IntoIter = Iter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } + }; +} + +macro_rules! into_iter_mut_ref_fn { + () => { + type Item = (&'a K, &'a mut V); + type IntoIter = IterMut<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } + }; +} + +macro_rules! partial_eq_fn { + () => { + fn eq(&self, other: &MT) -> bool { + if self.len() != other.len() { + return false; + } + + return self + .iter() + .all(|(key, value)| other.get(key).map_or(false, |v| *value == *v)); + } + }; +} + +macro_rules! map_iterator_boilerplate_for_slice { + ($($entries:ident)+) => { + type IntoKeyIterator = IntoKeys; + type IntoValueIterator = IntoValues; + + #[inline] + fn iter(&self) -> Self::Iterator<'_> { + Iter::new(&self$(. $entries)+) + } + + #[inline] + fn keys(&self) -> Self::KeyIterator<'_> { + Keys::new(&self$(. $entries)+) + } + + #[inline] + fn values(&self) -> Self::ValueIterator<'_> { + Values::new(&self$(. $entries)+) + } + + #[inline] + fn into_keys(self) -> Self::IntoKeyIterator { + // NOTE: this allocates and copies everything into a vector for the sake of iterating the vector. + // This is the best I could come up with, let me know if you see a way around the need to copy. + IntoKeys::new(Vec::from(self$(. $entries)+).into_boxed_slice()) + } + + #[inline] + fn into_values(self) -> Self::IntoValueIterator { + // NOTE: this allocates and copies everything into a vector for the sake of iterating the vector. + // This is the best I could come up with, let me know if you see a way around the need to copy. + IntoValues::new(Vec::from(self$(. $entries)+).into_boxed_slice()) + } + + #[inline] + fn iter_mut(&mut self) -> Self::MutIterator<'_> { + IterMut::new(self$(. $entries)+.as_mut()) + } + + #[inline] + fn values_mut(&mut self) -> Self::ValueMutIterator<'_> { + ValuesMut::new(self$(. $entries)+.as_mut()) + } + }; +} + +macro_rules! map_boilerplate_for_slice { + ($($entries:ident)+) => { + #[inline] + fn contains_key(&self, key: &K) -> bool { + self.contains_key(key) + } + + #[inline] + fn get(&self, key: &K) -> Option<&V> { + Self::get(self, key) + } + + #[inline] + fn get_key_value(&self, key: &K) -> Option<(&K, &V)> { + Self::get_key_value(self, key) + } + + #[inline] + fn get_mut(&mut self, key: &K) -> Option<&mut V> { + Self::get_mut(self, key) + } + + #[inline] + fn get_many_mut(&mut self, keys: [&K; N]) -> Option<[&mut V; N]> { + Self::get_many_mut(self, keys) + } + }; +} + +macro_rules! debug_fn { + () => { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let pairs = self.entries.iter().map(|x| (&x.0, &x.1)); + f.debug_map().entries(pairs).finish() + } + }; +} + +macro_rules! dense_scalar_lookup_core { + () => { + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline] + #[must_use] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Scalar, + { + let index = key.index(); + if index >= self.min && index <= self.max { + let entry = unsafe { self.entries.get_unchecked(index - self.min) }; + Some(&entry.1) + } else { + None + } + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: Scalar, + { + let index = key.index(); + if index >= self.min && index <= self.max { + let entry = unsafe { self.entries.get_unchecked(index - self.min) }; + Some((&entry.0, &entry.1)) + } else { + None + } + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Scalar, + { + let index = key.index(); + if index >= self.min && index <= self.max { + let entry = unsafe { self.entries.get_unchecked_mut(index - self.min) }; + Some(&mut entry.1) + } else { + None + } + } + + get_many_mut_fn!(Scalar); + contains_key_fn!(Scalar); + }; +} + +macro_rules! binary_search_core { + () => { + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline] + #[must_use] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: ?Sized + Ord, + { + self.entries + .binary_search_by_key(&key, |entry| entry.0.borrow()) + .map(|index| &self.entries[index].1) + .ok() + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: ?Sized + Ord, + { + self.entries + .binary_search_by_key(&key, |entry| entry.0.borrow()) + .map(|index| &mut self.entries[index].1) + .ok() + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: ?Sized + Ord, + { + self.entries + .binary_search_by_key(&key, |entry| entry.0.borrow()) + .map(|index| (&self.entries[index].0, &self.entries[index].1)) + .ok() + } + + get_many_mut_fn!(Ord); + contains_key_fn!(Ord); + }; +} + +macro_rules! sparse_scalar_lookup_core { + () => { + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline] + #[must_use] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Scalar, + { + let index = key.index(); + if index >= self.min && index <= self.max { + let index_in_lookup = index - self.min; + let index_in_entries = + unsafe { (*self.lookup.get_unchecked(index_in_lookup)).into() }; + if index_in_entries > 0 { + let entry = unsafe { self.entries.get_unchecked(index_in_entries - 1) }; + return Some(&entry.1); + } + } + + None + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: Scalar, + { + let index = key.index(); + if index >= self.min && index <= self.max { + let index_in_lookup = index - self.min; + let index_in_entries = + unsafe { (*self.lookup.get_unchecked(index_in_lookup)).into() }; + if index_in_entries > 0 { + let entry = unsafe { self.entries.get_unchecked(index_in_entries - 1) }; + return Some((&entry.0, &entry.1)); + } + } + + None + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Scalar, + { + let index = key.index(); + if index >= self.min && index <= self.max { + let index_in_lookup = index - self.min; + let index_in_entries = + unsafe { (*self.lookup.get_unchecked(index_in_lookup)).into() }; + if index_in_entries > 0 { + let entry = unsafe { self.entries.get_unchecked_mut(index_in_entries - 1) }; + return Some(&mut entry.1); + } + } + + None + } + + get_many_mut_fn!(Scalar); + contains_key_fn!(Scalar); + }; +} + +macro_rules! scan_core { + () => { + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline] + #[must_use] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: ?Sized + Eq, + { + for entry in &self.entries { + if key.eq(entry.0.borrow()) { + return Some(&entry.1); + } + } + + None + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: ?Sized + Eq, + { + for entry in &mut self.entries { + if key.eq(entry.0.borrow()) { + return Some(&mut entry.1); + } + } + + None + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: ?Sized + Eq, + { + for entry in &self.entries { + if key.eq(entry.0.borrow()) { + return Some((&entry.0, &entry.1)); + } + } + + None + } + + get_many_mut_fn!(Eq); + contains_key_fn!(Eq); + }; +} + +macro_rules! ordered_scan_core { + () => { + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline] + #[must_use] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: ?Sized + Ord, + { + for entry in &self.entries { + let ord = key.cmp(entry.0.borrow()); + if ord == Ordering::Equal { + return Some(&entry.1); + } else if ord == Ordering::Less { + break; + } + } + + None + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: ?Sized + Ord, + { + for entry in &mut self.entries { + let ord = key.cmp(entry.0.borrow()); + if ord == Ordering::Equal { + return Some(&mut entry.1); + } else if ord == Ordering::Less { + break; + } + } + + None + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + Q: ?Sized + Ord, + { + for entry in &self.entries { + let ord = key.cmp(entry.0.borrow()); + if ord == Ordering::Equal { + return Some((&entry.0, &entry.1)); + } else if ord == Ordering::Less { + break; + } + } + + None + } + + get_many_mut_fn!(Ord); + contains_key_fn!(Ord); + }; +} + +macro_rules! hash_core { + () => { + #[doc = include_str!("../doc_snippets/get_method.md")] + #[inline] + #[must_use] + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + H: Hasher, + Q: ?Sized + Eq, + { + self.table + .find(self.hasher.hash(key), |entry| key.eq(entry.0.borrow())) + .map(|(_, v)| v) + } + + #[doc = include_str!("../doc_snippets/get_key_value_method.md")] + #[inline] + #[must_use] + pub fn get_key_value(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + H: Hasher, + Q: ?Sized + Eq, + { + self.table + .find(self.hasher.hash(key), |entry| key.eq(entry.0.borrow())) + .map(|(k, v)| (k, v)) + } + + #[doc = include_str!("../doc_snippets/get_mut_method.md")] + #[inline] + #[must_use] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + H: Hasher, + Q: ?Sized + Eq, + { + self.table + .find_mut(self.hasher.hash(key), |entry| key.eq(entry.0.borrow())) + .map(|(_, v)| v) + } + + #[doc = include_str!("../doc_snippets/get_many_mut_method.md")] + #[must_use] + pub fn get_many_mut(&mut self, keys: [&Q; N]) -> Option<[&mut V; N]> + where + K: Borrow, + H: Hasher, + Q: ?Sized + Eq, + { + let mut hashes = [0_u64; N]; + for i in 0..N { + hashes[i] = self.hasher.hash(keys[i]); + } + + self.table + .get_many_mut(hashes, |i, (k, _)| keys[i].eq(k.borrow())) + .map(|res| res.map(|(_, v)| v)) + } + + #[doc = include_str!("../doc_snippets/contains_key_method.md")] + #[inline] + #[must_use] + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + H: Hasher, + Q: ?Sized + Eq, + { + self.get(key).is_some() + } + }; +} + +pub(crate) use binary_search_core; +pub(crate) use contains_key_fn; +pub(crate) use debug_fn; +pub(crate) use dense_scalar_lookup_core; +pub(crate) use get_many_mut_body; +pub(crate) use get_many_mut_fn; +pub(crate) use hash_core; +pub(crate) use index_fn; +pub(crate) use into_iter_fn_for_slice; +pub(crate) use into_iter_mut_ref_fn; +pub(crate) use into_iter_ref_fn; +pub(crate) use map_boilerplate_for_slice; +pub(crate) use map_iterator_boilerplate_for_slice; +pub(crate) use ordered_scan_core; +pub(crate) use partial_eq_fn; +pub(crate) use scan_core; +pub(crate) use sparse_scalar_lookup_core; diff --git a/frozen-collections-core/src/maps/dense_scalar_lookup_map.rs b/frozen-collections-core/src/maps/dense_scalar_lookup_map.rs new file mode 100644 index 0000000..020d1cd --- /dev/null +++ b/frozen-collections-core/src/maps/dense_scalar_lookup_map.rs @@ -0,0 +1,188 @@ +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +use crate::maps::decl_macros::{ + contains_key_fn, debug_fn, dense_scalar_lookup_core, get_many_mut_body, get_many_mut_fn, + index_fn, into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, + map_boilerplate_for_slice, map_iterator_boilerplate_for_slice, partial_eq_fn, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{Len, Map, MapIterator, Scalar}; +use crate::utils::dedup_by_keep_last; + +/// A map whose keys are a continuous range in a sequence of scalar values. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +#[derive(Clone)] +pub struct DenseScalarLookupMap { + min: usize, + max: usize, + entries: Box<[(K, V)]>, +} + +impl DenseScalarLookupMap +where + K: Scalar, +{ + /// Creates a frozen map. + /// + /// # Errors + /// + /// Fails if the all the keys in the input vector, after sorting and dedupping, + /// don't represent a continuous range of values. + pub fn new(mut entries: Vec<(K, V)>) -> core::result::Result { + entries.sort_by_key(|x| x.0); + dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + + if entries.is_empty() { + return Ok(Self::default()); + } + + let min = entries[0].0.index(); + let max = entries[entries.len() - 1].0.index(); + + if entries.len() == max - min + 1 { + Ok(Self::new_raw(entries)) + } else { + Err("keys must be in a contiguous range <= usize::MAX in size".to_string()) + } + } + + /// Creates a new frozen map. + /// + /// This function assumes that `min` <= `max` and that the vector is sorted according to the + /// order of the [`Ord`] trait. + #[must_use] + pub(crate) fn new_raw(processed_entries: Vec<(K, V)>) -> Self { + Self { + min: processed_entries[0].0.index(), + max: processed_entries[processed_entries.len() - 1].0.index(), + entries: processed_entries.into_boxed_slice(), + } + } +} + +impl DenseScalarLookupMap { + dense_scalar_lookup_core!(); +} + +impl Len for DenseScalarLookupMap { + fn len(&self) -> usize { + self.entries.len() + } +} + +impl Debug for DenseScalarLookupMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Default for DenseScalarLookupMap { + fn default() -> Self { + Self { + min: 1, + max: 0, + entries: Box::new([]), + } + } +} + +impl Index<&Q> for DenseScalarLookupMap +where + K: Borrow, + Q: Scalar, +{ + index_fn!(); +} + +impl IntoIterator for DenseScalarLookupMap { + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V> IntoIterator for &'a DenseScalarLookupMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V> IntoIterator for &'a mut DenseScalarLookupMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for DenseScalarLookupMap +where + K: Scalar, + V: PartialEq, + MT: Map, +{ + partial_eq_fn!(); +} + +impl Eq for DenseScalarLookupMap +where + K: Scalar, + V: Eq, +{ +} + +impl MapIterator for DenseScalarLookupMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map for DenseScalarLookupMap +where + K: Scalar, +{ + map_boilerplate_for_slice!(entries); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn error_in_new() { + let map = DenseScalarLookupMap::::new(vec![(1, 1), (2, 2), (4, 3)]); + assert_eq!( + map, + Err("keys must be in a contiguous range <= usize::MAX in size".to_string()) + ); + } +} diff --git a/frozen-collections-core/src/maps/hash_map.rs b/frozen-collections-core/src/maps/hash_map.rs new file mode 100644 index 0000000..1713041 --- /dev/null +++ b/frozen-collections-core/src/maps/hash_map.rs @@ -0,0 +1,211 @@ +use crate::hash_tables::HashTable; +use crate::hashers::BridgeHasher; +use crate::maps::decl_macros::{ + hash_core, index_fn, into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, + map_boilerplate_for_slice, map_iterator_boilerplate_for_slice, partial_eq_fn, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{CollectionMagnitude, Hasher, Len, Map, MapIterator, SmallCollection}; +use crate::utils::dedup_by_hash_keep_last; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +/// A general purpose map implemented using a hash table. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/hash_warning.md")] +/// +#[derive(Clone)] +pub struct HashMap { + table: HashTable<(K, V), CM>, + hasher: H, +} + +impl HashMap +where + K: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + /// Creates a frozen map. + /// + /// # Errors + /// + /// Fails if the number of entries in the vector, after deduplication, exceeds the + /// magnitude of the collection as specified by the `CM` generic argument. + pub fn new(mut entries: Vec<(K, V)>, hasher: H) -> core::result::Result { + dedup_by_hash_keep_last(&mut entries, &hasher); + Self::new_half_baked(entries, hasher) + } + + /// Creates a frozen map. + pub(crate) fn new_half_baked( + processed_entries: Vec<(K, V)>, + hasher: H, + ) -> core::result::Result { + let c = &hasher; + let h = |entry: &(K, V)| c.hash(&entry.0); + Ok(Self::new_raw( + HashTable::<(K, V), CM>::new(processed_entries, h)?, + hasher, + )) + } + + /// Creates a frozen map. + pub(crate) const fn new_raw(table: HashTable<(K, V), CM>, hasher: H) -> Self { + Self { table, hasher } + } +} + +impl HashMap +where + CM: CollectionMagnitude, +{ + hash_core!(); +} + +impl Len for HashMap { + fn len(&self) -> usize { + self.table.len() + } +} + +impl Default for HashMap +where + H: Default, + CM: CollectionMagnitude, +{ + fn default() -> Self { + Self { + table: HashTable::default(), + hasher: H::default(), + } + } +} + +impl Debug for HashMap +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.table.fmt(f) + } +} + +impl Index<&Q> for HashMap +where + K: Borrow, + Q: ?Sized + Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + index_fn!(); +} + +impl IntoIterator for HashMap { + into_iter_fn_for_slice!(table entries); +} + +impl<'a, K, V, CM, H> IntoIterator for &'a HashMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V, CM, H> IntoIterator for &'a mut HashMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for HashMap +where + K: Eq, + CM: CollectionMagnitude, + V: PartialEq, + MT: Map, + H: Hasher, +{ + partial_eq_fn!(); +} + +impl Eq for HashMap +where + K: Eq, + CM: CollectionMagnitude, + V: Eq, + H: Hasher, +{ +} + +impl MapIterator for HashMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a, + H: 'a; + + map_iterator_boilerplate_for_slice!(table entries); +} + +impl Map for HashMap +where + K: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + map_boilerplate_for_slice!(table entries); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_fails_when_entries_exceed_collection_magnitude() { + let mut entries = Vec::new(); + for i in 0..=SmallCollection::MAX_CAPACITY { + entries.push((i, 42)); + } + + let map = HashMap::<_, _, SmallCollection>::new(entries, BridgeHasher::default()); + assert_eq!( + map, + Err("too many entries for the selected collection magnitude".to_string()) + ); + } +} diff --git a/frozen-collections-core/src/maps/iterators.rs b/frozen-collections-core/src/maps/iterators.rs new file mode 100644 index 0000000..63f38bc --- /dev/null +++ b/frozen-collections-core/src/maps/iterators.rs @@ -0,0 +1,828 @@ +use alloc::boxed::Box; +use core::fmt::{Debug, Formatter}; +use core::iter::FusedIterator; + +/// An iterator over the entries of a map. +pub struct Iter<'a, K, V> { + inner: core::slice::Iter<'a, (K, V)>, +} + +impl<'a, K, V> Iter<'a, K, V> { + pub(crate) fn new(entries: &'a [(K, V)]) -> Self { + Self { + inner: entries.iter(), + } + } +} + +impl<'a, K, V> Iterator for Iter<'a, K, V> { + type Item = (&'a K, &'a V); + + fn next(&mut self) -> Option { + self.inner.next().map(|entry| (&entry.0, &entry.1)) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (k, v)| f(acc, (k, v))) + } +} + +impl<'a, K, V> ExactSizeIterator for Iter<'a, K, V> { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a, K, V> Clone for Iter<'a, K, V> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl<'a, K, V> Debug for Iter<'a, K, V> +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// An iterator over the entries of a map providing mutable values. +pub struct IterMut<'a, K, V> { + inner: core::slice::IterMut<'a, (K, V)>, +} + +impl<'a, K, V> IterMut<'a, K, V> { + pub(crate) fn new(entries: &'a mut [(K, V)]) -> Self { + Self { + inner: entries.iter_mut(), + } + } +} + +impl<'a, K, V> Iterator for IterMut<'a, K, V> { + type Item = (&'a K, &'a mut V); + + fn next(&mut self) -> Option { + self.inner.next().map(|entry| (&entry.0, &mut entry.1)) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (k, v)| f(acc, (k, v))) + } +} + +impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a, K, V> FusedIterator for IterMut<'a, K, V> {} + +impl<'a, K, V> Debug for IterMut<'a, K, V> +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.inner.fmt(f) + } +} + +/// An iterator over the keys of a map. +pub struct Keys<'a, K, V> { + inner: Iter<'a, K, V>, +} + +impl<'a, K, V> Keys<'a, K, V> { + #[must_use] + pub fn new(entries: &'a [(K, V)]) -> Self { + Self { + inner: Iter::new(entries), + } + } +} + +impl<'a, K, V> Iterator for Keys<'a, K, V> { + type Item = &'a K; + + fn next(&mut self) -> Option { + self.inner.next().map(|x| x.0) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (k, _)| f(acc, k)) + } +} + +impl<'a, K, V> ExactSizeIterator for Keys<'a, K, V> { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a, K, V> FusedIterator for Keys<'a, K, V> {} + +impl<'a, K, V> Clone for Keys<'a, K, V> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl<'a, K, V> Debug for Keys<'a, K, V> +where + K: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// An iterator over the values of a map. +pub struct Values<'a, K, V> { + inner: Iter<'a, K, V>, +} + +impl<'a, K, V> Values<'a, K, V> { + #[must_use] + pub fn new(entries: &'a [(K, V)]) -> Self { + Self { + inner: Iter::new(entries), + } + } +} + +impl<'a, K, V> Iterator for Values<'a, K, V> { + type Item = &'a V; + + fn next(&mut self) -> Option { + self.inner.next().map(|x| x.1) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (_, v)| f(acc, v)) + } +} + +impl<'a, K, V> ExactSizeIterator for Values<'a, K, V> { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a, K, V> FusedIterator for Values<'a, K, V> {} + +impl<'a, K, V> Clone for Values<'a, K, V> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl<'a, K, V> Debug for Values<'a, K, V> +where + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +/// An iterator over the mutable values of a map. +pub struct ValuesMut<'a, K, V> { + inner: IterMut<'a, K, V>, +} + +impl<'a, K, V> ValuesMut<'a, K, V> { + pub(crate) fn new(entries: &'a mut [(K, V)]) -> Self { + Self { + inner: IterMut::new(entries), + } + } +} + +impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { + type Item = &'a mut V; + + fn next(&mut self) -> Option { + self.inner.next().map(|entry| entry.1) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (_, v)| f(acc, v)) + } +} + +impl<'a, K, V> ExactSizeIterator for ValuesMut<'a, K, V> { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a, K, V> FusedIterator for ValuesMut<'a, K, V> {} + +impl<'a, K, V> Debug for ValuesMut<'a, K, V> +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.inner.fmt(f) + } +} + +/// A consuming iterator over the entries in a map. +pub struct IntoIter { + inner: alloc::vec::IntoIter<(K, V)>, +} + +impl IntoIter { + pub(crate) fn new(entries: Box<[(K, V)]>) -> Self { + Self { + inner: entries.into_vec().into_iter(), + } + } +} + +impl Iterator for IntoIter { + type Item = (K, V); + + fn next(&mut self) -> Option { + self.inner.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (k, v)| f(acc, (k, v))) + } +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for IntoIter {} + +impl Debug for IntoIter +where + K: Debug, + V: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.inner.fmt(f) + } +} + +/// A consuming iterator over the keys in a map. +pub struct IntoKeys { + inner: IntoIter, +} + +impl IntoKeys { + pub(crate) fn new(entries: Box<[(K, V)]>) -> Self { + Self { + inner: IntoIter::new(entries), + } + } +} + +impl Iterator for IntoKeys { + type Item = K; + + fn next(&mut self) -> Option { + self.inner.next().map(|x| x.0) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (k, _)| f(acc, k)) + } +} + +impl ExactSizeIterator for IntoKeys { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for IntoKeys {} + +/// A consuming iterator over the values in a map. +pub struct IntoValues { + inner: IntoIter, +} + +impl IntoValues { + pub(crate) fn new(entries: Box<[(K, V)]>) -> Self { + Self { + inner: IntoIter::new(entries), + } + } +} + +impl Iterator for IntoValues { + type Item = V; + + fn next(&mut self) -> Option { + self.inner.next().map(|x| x.1) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (_, v)| f(acc, v)) + } +} + +impl ExactSizeIterator for IntoValues { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for IntoValues {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_iter() { + let entries = vec![("Alice", 1), ("Bob", 2), ("Sandy", 3), ("Tom", 4)]; + let iter = Iter::new(&entries); + assert_eq!(entries.len(), iter.len()); + + let collected: Vec<_> = iter.collect(); + assert_eq!( + collected, + vec![(&"Alice", &1), (&"Bob", &2), (&"Sandy", &3), (&"Tom", &4)] + ); + } + + #[test] + fn test_iter_count() { + let entries = vec![("Alice", 1), ("Bob", 2), ("Sandy", 3), ("Tom", 4)]; + let iter = Iter::new(&entries); + assert_eq!(entries.len(), iter.len()); + assert_eq!(entries.len(), iter.count()); + } + + #[test] + fn test_iter_empty() { + let entries: Vec<(&str, i32)> = vec![]; + let mut iter = Iter::new(&entries); + assert_eq!(0, iter.len()); + assert!(iter.next().is_none()); + } + + #[test] + fn test_iter_debug() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let iter = Iter::new(&entries); + + let debug_str = format!("{iter:?}"); + assert!(debug_str.contains("Alice")); + assert!(debug_str.contains("Bob")); + } + + #[test] + fn test_iter_mut() { + let mut entries = vec![("Alice", 1), ("Bob", 2), ("Sandy", 3), ("Tom", 4)]; + let iter_mut = IterMut::new(&mut entries); + + for (_, v) in iter_mut { + *v += 1; + } + + let expected = vec![("Alice", 2), ("Bob", 3), ("Sandy", 4), ("Tom", 5)]; + assert_eq!(entries, expected); + } + + #[test] + fn test_iter_mut_count() { + let mut entries = vec![("Alice", 1), ("Bob", 2), ("Sandy", 3), ("Tom", 4)]; + let iter_mut = IterMut::new(&mut entries); + assert_eq!(4, iter_mut.count()); + } + + #[test] + fn test_iter_mut_empty() { + let mut entries: Vec<(&str, i32)> = vec![]; + let mut iter_mut = IterMut::new(&mut entries); + assert_eq!(0, iter_mut.len()); + assert!(iter_mut.next().is_none()); + } + + #[test] + fn test_iter_size_hint() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let iter = Iter::new(&entries); + + assert_eq!(iter.size_hint(), (2, Some(2))); + } + + #[test] + fn test_iter_mut_size_hint() { + let mut entries = vec![("Alice", 1), ("Bob", 2)]; + let iter_mut = IterMut::new(&mut entries); + + assert_eq!(iter_mut.size_hint(), (2, Some(2))); + } + + #[test] + fn test_iter_clone() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let iter = Iter::new(&entries); + let iter_clone = iter.clone(); + + let collected: Vec<_> = iter_clone.collect(); + assert_eq!(collected, vec![(&"Alice", &1), (&"Bob", &2)]); + } + + #[test] + fn test_iter_mut_debug() { + let mut entries = vec![("Alice", 1), ("Bob", 2)]; + let iter_mut = IterMut::new(&mut entries); + + let debug_str = format!("{iter_mut:?}"); + assert!(debug_str.contains("Alice")); + assert!(debug_str.contains("Bob")); + } + + #[test] + fn test_keys() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let keys = Keys::new(&entries); + assert_eq!(entries.len(), keys.len()); + + let collected: Vec<_> = keys.collect(); + assert_eq!(collected, vec![&"Alice", &"Bob"]); + } + + #[test] + fn test_keys_count() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let keys = Keys::new(&entries); + assert_eq!(2, keys.count()); + } + + #[test] + fn test_keys_empty() { + let entries: Vec<(&str, i32)> = vec![]; + let mut keys = Keys::new(&entries); + assert_eq!(0, keys.len()); + assert!(keys.next().is_none()); + } + + #[test] + fn test_keys_size_hint() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let keys = Keys::new(&entries); + + assert_eq!(keys.size_hint(), (2, Some(2))); + } + + #[test] + fn test_keys_clone() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let keys = Keys::new(&entries); + let keys_clone = keys.clone(); + + let collected: Vec<_> = keys_clone.collect(); + assert_eq!(collected, vec![&"Alice", &"Bob"]); + } + + #[test] + fn test_keys_debug() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let keys = Keys::new(&entries); + + let debug_str = format!("{keys:?}"); + assert!(debug_str.contains("Alice")); + assert!(debug_str.contains("Bob")); + } + + #[test] + fn test_values() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let values = Values::new(&entries); + assert_eq!(entries.len(), values.len()); + + let collected: Vec<_> = values.collect(); + assert_eq!(collected, vec![&1, &2]); + } + + #[test] + fn test_values_count() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let values = Values::new(&entries); + assert_eq!(2, values.count()); + } + + #[test] + fn test_values_empty() { + let entries: Vec<(&str, i32)> = vec![]; + let mut values = Values::new(&entries); + assert_eq!(0, values.len()); + assert!(values.next().is_none()); + } + + #[test] + fn test_values_size_hint() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let values = Values::new(&entries); + + assert_eq!(values.size_hint(), (2, Some(2))); + } + + #[test] + fn test_values_clone() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let values = Values::new(&entries); + let values_clone = values.clone(); + + let collected: Vec<_> = values_clone.collect(); + assert_eq!(collected, vec![&1, &2]); + } + + #[test] + fn test_values_debug() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let values = Values::new(&entries); + + let debug_str = format!("{values:?}"); + assert!(debug_str.contains('1')); + assert!(debug_str.contains('2')); + } + + #[test] + fn test_into_keys() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let into_keys = IntoKeys::new(entries.into_boxed_slice()); + assert_eq!(2, into_keys.len()); + + let collected: Vec<_> = into_keys.collect(); + assert_eq!(collected, vec!["Alice", "Bob"]); + } + + #[test] + fn test_into_keys_count() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let into_keys = IntoKeys::new(entries.into_boxed_slice()); + assert_eq!(2, into_keys.count()); + } + + #[test] + fn test_into_keys_empty() { + let entries: Vec<(&str, i32)> = vec![]; + let mut into_keys = IntoKeys::new(entries.into_boxed_slice()); + assert_eq!(0, into_keys.len()); + assert!(into_keys.next().is_none()); + } + + #[test] + fn test_into_keys_size_hint() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let into_keys = IntoKeys::new(entries.into_boxed_slice()); + + assert_eq!(into_keys.size_hint(), (2, Some(2))); + } + + #[test] + fn test_into_values() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let into_values = IntoValues::new(entries.into_boxed_slice()); + assert_eq!(2, into_values.len()); + + let collected: Vec<_> = into_values.collect(); + assert_eq!(collected, vec![1, 2]); + } + + #[test] + fn test_into_values_count() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let into_values = IntoValues::new(entries.into_boxed_slice()); + assert_eq!(2, into_values.count()); + } + + #[test] + fn test_into_values_empty() { + let entries: Vec<(&str, i32)> = vec![]; + let mut into_values = IntoValues::new(entries.into_boxed_slice()); + assert_eq!(0, into_values.len()); + assert!(into_values.next().is_none()); + } + + #[test] + fn test_into_values_size_hint() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let into_values = IntoValues::new(entries.into_boxed_slice()); + assert_eq!(into_values.size_hint(), (2, Some(2))); + } + + #[test] + fn test_values_mut() { + let mut entries = vec![("Alice", 1), ("Bob", 2)]; + let values_mut = ValuesMut::new(&mut entries); + assert_eq!(2, values_mut.len()); + + for v in values_mut { + *v += 1; + } + + let expected = vec![("Alice", 2), ("Bob", 3)]; + assert_eq!(entries, expected); + } + + #[test] + fn test_values_mut_count() { + let mut entries = vec![("Alice", 1), ("Bob", 2)]; + let values_mut = ValuesMut::new(&mut entries); + assert_eq!(2, values_mut.count()); + } + + #[test] + fn test_values_mut_empty() { + let mut entries: Vec<(&str, i32)> = vec![]; + let mut values_mut = ValuesMut::new(&mut entries); + assert_eq!(0, values_mut.len()); + assert!(values_mut.next().is_none()); + } + + #[test] + fn test_values_mut_size_hint() { + let mut entries = vec![("Alice", 1), ("Bob", 2)]; + let values_mut = ValuesMut::new(&mut entries); + assert_eq!(values_mut.size_hint(), (2, Some(2))); + } + + #[test] + fn test_values_mut_debug() { + let mut entries = vec![("Alice", 1), ("Bob", 2)]; + let values_mut = ValuesMut::new(&mut entries); + + let debug_str = format!("{values_mut:?}"); + assert!(debug_str.contains("Alice")); + assert!(debug_str.contains("Bob")); + } + + #[test] + fn test_into_iter() { + let entries = vec![("Alice", 1), ("Bob", 2), ("Sandy", 3), ("Tom", 4)]; + let into_iter = IntoIter::new(entries.into_boxed_slice()); + assert_eq!(4, into_iter.len()); + + let collected: Vec<_> = into_iter.collect(); + assert_eq!( + collected, + vec![("Alice", 1), ("Bob", 2), ("Sandy", 3), ("Tom", 4)] + ); + } + + #[test] + fn test_into_iter_count() { + let entries = vec![("Alice", 1), ("Bob", 2), ("Sandy", 3), ("Tom", 4)]; + let into_iter = IntoIter::new(entries.into_boxed_slice()); + assert_eq!(4, into_iter.count()); + } + + #[test] + fn test_into_iter_empty() { + let entries: Vec<(&str, i32)> = vec![]; + let mut into_iter = IntoIter::new(entries.into_boxed_slice()); + assert_eq!(0, into_iter.len()); + assert!(into_iter.next().is_none()); + } + + #[test] + fn test_into_iter_size_hint() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let into_iter = IntoIter::new(entries.into_boxed_slice()); + + assert_eq!(into_iter.size_hint(), (2, Some(2))); + } + + #[test] + fn test_into_iter_debug() { + let entries = vec![("Alice", 1), ("Bob", 2)]; + let into_iter = IntoIter::new(entries.into_boxed_slice()); + + let debug_str = format!("{into_iter:?}"); + assert!(debug_str.contains("Alice")); + assert!(debug_str.contains("Bob")); + } +} diff --git a/frozen-collections-core/src/maps/mod.rs b/frozen-collections-core/src/maps/mod.rs new file mode 100644 index 0000000..51b0bde --- /dev/null +++ b/frozen-collections-core/src/maps/mod.rs @@ -0,0 +1,18 @@ +//! Specialized read-only map types. + +pub use binary_search_map::BinarySearchMap; +pub use dense_scalar_lookup_map::DenseScalarLookupMap; +pub use hash_map::HashMap; +pub use iterators::*; +pub use ordered_scan_map::OrderedScanMap; +pub use scan_map::ScanMap; +pub use sparse_scalar_lookup_map::SparseScalarLookupMap; + +mod binary_search_map; +pub(crate) mod decl_macros; +mod dense_scalar_lookup_map; +mod hash_map; +mod iterators; +mod ordered_scan_map; +mod scan_map; +mod sparse_scalar_lookup_map; diff --git a/frozen-collections-core/src/maps/ordered_scan_map.rs b/frozen-collections-core/src/maps/ordered_scan_map.rs new file mode 100644 index 0000000..27cd2db --- /dev/null +++ b/frozen-collections-core/src/maps/ordered_scan_map.rs @@ -0,0 +1,149 @@ +use crate::maps::decl_macros::{ + contains_key_fn, debug_fn, get_many_mut_body, get_many_mut_fn, index_fn, + into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, map_boilerplate_for_slice, + map_iterator_boilerplate_for_slice, ordered_scan_core, partial_eq_fn, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{Len, Map, MapIterator}; +use crate::utils::dedup_by_keep_last; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; +use std::cmp::Ordering; + +/// A general purpose map implemented using linear scanning. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +#[derive(Clone)] +pub struct OrderedScanMap { + entries: Box<[(K, V)]>, +} + +impl OrderedScanMap +where + K: Ord, +{ + /// Creates a frozen map. + #[must_use] + pub fn new(mut entries: Vec<(K, V)>) -> Self { + entries.sort_by(|x, y| x.0.cmp(&y.0)); + dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + Self::new_raw(entries) + } + + /// Creates a frozen map. + #[must_use] + pub fn new_raw(processed_entries: Vec<(K, V)>) -> Self { + Self { + entries: processed_entries.into_boxed_slice(), + } + } +} + +impl OrderedScanMap { + ordered_scan_core!(); +} + +impl Len for OrderedScanMap { + fn len(&self) -> usize { + self.entries.len() + } +} + +impl Debug for OrderedScanMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Default for OrderedScanMap { + fn default() -> Self { + Self { + entries: Box::default(), + } + } +} + +impl Index<&Q> for OrderedScanMap +where + K: Borrow, + Q: ?Sized + Ord, +{ + index_fn!(); +} + +impl IntoIterator for OrderedScanMap { + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V> IntoIterator for &'a OrderedScanMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V> IntoIterator for &'a mut OrderedScanMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for OrderedScanMap +where + K: Ord, + V: PartialEq, + MT: Map, +{ + partial_eq_fn!(); +} + +impl Eq for OrderedScanMap +where + K: Ord, + V: Eq, +{ +} + +impl MapIterator for OrderedScanMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map for OrderedScanMap +where + K: Ord, +{ + map_boilerplate_for_slice!(entries); +} diff --git a/frozen-collections-core/src/maps/scan_map.rs b/frozen-collections-core/src/maps/scan_map.rs new file mode 100644 index 0000000..9d3b5d3 --- /dev/null +++ b/frozen-collections-core/src/maps/scan_map.rs @@ -0,0 +1,140 @@ +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +use crate::maps::decl_macros::{ + contains_key_fn, debug_fn, get_many_mut_body, get_many_mut_fn, index_fn, + into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, map_boilerplate_for_slice, + map_iterator_boilerplate_for_slice, partial_eq_fn, scan_core, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{Len, Map, MapIterator}; +use crate::utils::slow_dedup_by_keep_last; + +/// A general purpose map implemented using linear scanning. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +#[derive(Clone, Eq)] +pub struct ScanMap { + entries: Box<[(K, V)]>, +} + +impl ScanMap +where + K: Eq, +{ + /// Creates a frozen map. + #[must_use] + pub fn new(mut entries: Vec<(K, V)>) -> Self { + slow_dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + Self::new_raw(entries) + } + + /// Creates a frozen map. + #[must_use] + pub(crate) fn new_raw(processed_entries: Vec<(K, V)>) -> Self { + Self { + entries: processed_entries.into_boxed_slice(), + } + } +} + +impl ScanMap { + scan_core!(); +} + +impl Len for ScanMap { + fn len(&self) -> usize { + self.entries.len() + } +} + +impl Debug for ScanMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Default for ScanMap { + fn default() -> Self { + Self { + entries: Box::default(), + } + } +} + +impl Index<&Q> for ScanMap +where + K: Borrow, + Q: ?Sized + Eq, +{ + index_fn!(); +} + +impl IntoIterator for ScanMap { + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V> IntoIterator for &'a ScanMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V> IntoIterator for &'a mut ScanMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for ScanMap +where + K: Eq, + V: PartialEq, + MT: Map, +{ + partial_eq_fn!(); +} + +impl MapIterator for ScanMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map for ScanMap +where + K: Eq, +{ + map_boilerplate_for_slice!(entries); +} diff --git a/frozen-collections-core/src/maps/sparse_scalar_lookup_map.rs b/frozen-collections-core/src/maps/sparse_scalar_lookup_map.rs new file mode 100644 index 0000000..73dd4c5 --- /dev/null +++ b/frozen-collections-core/src/maps/sparse_scalar_lookup_map.rs @@ -0,0 +1,227 @@ +use crate::maps::decl_macros::{ + contains_key_fn, debug_fn, get_many_mut_body, get_many_mut_fn, index_fn, + into_iter_fn_for_slice, into_iter_mut_ref_fn, into_iter_ref_fn, map_boilerplate_for_slice, + map_iterator_boilerplate_for_slice, partial_eq_fn, sparse_scalar_lookup_core, +}; +use crate::maps::{IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; +use crate::traits::{CollectionMagnitude, Len, Map, MapIterator, Scalar, SmallCollection}; +use crate::utils::dedup_by_keep_last; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter, Result}; +use core::ops::Index; + +/// A map whose keys are a sparse range of values from a scalar. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +#[derive(Clone)] +pub struct SparseScalarLookupMap { + min: usize, + max: usize, + lookup: Box<[CM]>, + entries: Box<[(K, V)]>, +} + +impl SparseScalarLookupMap +where + K: Scalar, + CM: CollectionMagnitude, + >::Error: Debug, +{ + /// Creates a new `IntegerSparseLookupMap` from a list of entries. + /// + /// Note that this supports 1 less entry relative to the maximum capacity of the collection scale + /// since 0 is used as a sentinel value within the lookup table. + /// + /// # Errors + /// + /// Fails if the number of entries in the vector, after deduplication, exceeds the + /// magnitude of the collection as specified by the `CM` generic argument. + pub fn new(mut entries: Vec<(K, V)>) -> core::result::Result { + entries.sort_by_key(|x| x.0); + dedup_by_keep_last(&mut entries, |x, y| x.0.eq(&y.0)); + + if entries.is_empty() { + return Ok(Self::default()); + } + + let min = entries[0].0.index(); + let max = entries[entries.len() - 1].0.index(); + + if max - min + 1 >= CM::MAX_CAPACITY { + Err("the range of keys is too large for the selected collection magnitude".to_string()) + } else { + Ok(Self::new_raw(entries)) + } + } + + /// Creates a new frozen map. + /// + /// # Panics + /// + /// Panics if the number of entries in the vector exceeds the + /// magnitude of the collection as specified by the `CM` generic argument. + #[must_use] + pub(crate) fn new_raw(processed_entries: Vec<(K, V)>) -> Self { + let min = processed_entries[0].0.index(); + let max = processed_entries[processed_entries.len() - 1].0.index(); + let count = max - min + 1; + + let mut lookup = Vec::::with_capacity(count); + lookup.resize(lookup.capacity(), CM::ZERO); + + for (i, entry) in processed_entries.iter().enumerate() { + let index_in_lookup = entry.0.index() - min; + let index_in_entries = CM::try_from(i + 1).expect("less than CM::MAX"); + lookup[index_in_lookup] = index_in_entries; + } + + Self { + min, + max, + lookup: lookup.into_boxed_slice(), + entries: processed_entries.into_boxed_slice(), + } + } +} + +impl SparseScalarLookupMap +where + CM: CollectionMagnitude, +{ + sparse_scalar_lookup_core!(); +} + +impl Len for SparseScalarLookupMap { + fn len(&self) -> usize { + self.entries.len() + } +} + +impl Debug for SparseScalarLookupMap +where + K: Debug, + V: Debug, +{ + debug_fn!(); +} + +impl Default for SparseScalarLookupMap { + fn default() -> Self { + Self { + min: 1, + max: 0, + lookup: Box::new([]), + entries: Box::new([]), + } + } +} + +impl Index<&Q> for SparseScalarLookupMap +where + K: Borrow, + Q: Scalar, + CM: CollectionMagnitude, +{ + index_fn!(); +} + +impl IntoIterator for SparseScalarLookupMap { + into_iter_fn_for_slice!(entries); +} + +impl<'a, K, V, CM> IntoIterator for &'a SparseScalarLookupMap { + into_iter_ref_fn!(); +} + +impl<'a, K, V, CM> IntoIterator for &'a mut SparseScalarLookupMap { + into_iter_mut_ref_fn!(); +} + +impl PartialEq for SparseScalarLookupMap +where + K: Scalar, + V: PartialEq, + MT: Map, + CM: CollectionMagnitude, +{ + partial_eq_fn!(); +} + +impl Eq for SparseScalarLookupMap +where + K: Scalar, + V: Eq, + CM: CollectionMagnitude, +{ +} + +impl MapIterator for SparseScalarLookupMap { + type Iterator<'a> + = Iter<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + type KeyIterator<'a> + = Keys<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + type ValueIterator<'a> + = Values<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + type MutIterator<'a> + = IterMut<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + type ValueMutIterator<'a> + = ValuesMut<'a, K, V> + where + K: 'a, + V: 'a, + CM: 'a; + + map_iterator_boilerplate_for_slice!(entries); +} + +impl Map for SparseScalarLookupMap +where + K: Scalar, + CM: CollectionMagnitude, +{ + map_boilerplate_for_slice!(entries); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_fails_when_entries_exceed_collection_magnitude() { + let mut entries = Vec::new(); + for i in 0..SmallCollection::MAX_CAPACITY { + entries.push((i, 42)); + } + + let map = SparseScalarLookupMap::<_, _, SmallCollection>::new(entries); + assert_eq!( + map, + Err("the range of keys is too large for the selected collection magnitude".to_string()) + ); + } +} diff --git a/frozen-collections-core/src/sets/binary_search_set.rs b/frozen-collections-core/src/sets/binary_search_set.rs new file mode 100644 index 0000000..af13560 --- /dev/null +++ b/frozen-collections-core/src/sets/binary_search_set.rs @@ -0,0 +1,126 @@ +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +use crate::maps::BinarySearchMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Set, SetIterator}; + +/// A general purpose set implemented using binary search. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +#[derive(Clone)] +pub struct BinarySearchSet { + map: BinarySearchMap, +} + +impl BinarySearchSet +where + T: Ord, +{ + /// Creates a frozen set. + #[must_use] + pub const fn new(map: BinarySearchMap) -> Self { + Self { map } + } +} + +impl BinarySearchSet { + get_fn!(Ord); + contains_fn!(Ord); +} + +impl Len for BinarySearchSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for BinarySearchSet +where + T: Debug, +{ + debug_fn!(); +} + +impl Default for BinarySearchSet { + fn default() -> Self { + Self { + map: BinarySearchMap::default(), + } + } +} + +impl IntoIterator for BinarySearchSet { + into_iter_fn!(); +} + +impl<'a, T> IntoIterator for &'a BinarySearchSet { + into_iter_ref_fn!(); +} + +impl SetIterator for BinarySearchSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for BinarySearchSet +where + T: Ord, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &BinarySearchSet +where + T: Hash + Ord + Clone, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &BinarySearchSet +where + T: Hash + Ord + Clone, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &BinarySearchSet +where + T: Hash + Ord + Clone, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &BinarySearchSet +where + T: Hash + Ord + Clone, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for BinarySearchSet +where + T: Ord, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for BinarySearchSet where T: Ord {} diff --git a/frozen-collections-core/src/sets/decl_macros.rs b/frozen-collections-core/src/sets/decl_macros.rs new file mode 100644 index 0000000..1096546 --- /dev/null +++ b/frozen-collections-core/src/sets/decl_macros.rs @@ -0,0 +1,140 @@ +macro_rules! get_fn { + ($( $generic_type_bound1:ident $(+ $generic_type_bound2:ident)*)?) => { + #[doc = include_str!("../doc_snippets/get_from_set_method.md")] + #[inline] + #[must_use] + pub fn get(&self, value: &Q) -> Option<&T> + where + T: Borrow, + Q: ?Sized $(+ $generic_type_bound1 $(+ $generic_type_bound2)*)?, + { + Some(self.map.get_key_value(value)?.0) + } + }; +} + +macro_rules! contains_fn { + ($( $generic_type_bound1:ident $(+ $generic_type_bound2:ident)*)?) => { + #[doc = include_str!("../doc_snippets/contains_method.md")] + #[inline] + #[must_use] + pub fn contains(&self, value: &Q) -> bool + where + T: Borrow, + Q: ?Sized $(+ $generic_type_bound1 $(+ $generic_type_bound2)*)?, + { + self.get(value).is_some() + } + }; +} + +macro_rules! partial_eq_fn { + () => { + fn eq(&self, other: &ST) -> bool { + if self.len() != other.len() { + return false; + } + + self.iter().all(|value| other.contains(value)) + } + }; +} + +macro_rules! into_iter_fn { + () => { + type Item = T; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self.map.into_iter()) + } + }; +} + +macro_rules! into_iter_ref_fn { + () => { + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } + }; +} + +macro_rules! bitor_fn { + ($build_hasher:ident) => { + type Output = hashbrown::HashSet; + + fn bitor(self, rhs: &ST) -> Self::Output { + Self::Output::from_iter(self.union(rhs).cloned()) + } + }; +} + +macro_rules! bitand_fn { + ($build_hasher:ident) => { + type Output = hashbrown::HashSet; + + fn bitand(self, rhs: &ST) -> Self::Output { + Self::Output::from_iter(self.intersection(rhs).cloned()) + } + }; +} + +macro_rules! bitxor_fn { + ($build_hasher:ident) => { + type Output = hashbrown::HashSet; + + fn bitxor(self, rhs: &ST) -> Self::Output { + self.symmetric_difference(rhs).cloned().collect() + } + }; +} + +macro_rules! sub_fn { + ($build_hasher:ident) => { + type Output = hashbrown::HashSet; + + fn sub(self, rhs: &ST) -> Self::Output { + self.difference(rhs).cloned().collect() + } + }; +} + +macro_rules! set_iterator_boilerplate { + () => { + fn iter(&self) -> Iter<'_, T> { + Iter::new(self.map.iter()) + } + }; +} + +macro_rules! set_boilerplate { + () => { + fn contains(&self, value: &T) -> bool { + self.contains(value) + } + }; +} + +macro_rules! debug_fn { + () => { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_set().entries(self.iter()).finish() + } + }; +} + +pub(crate) use bitand_fn; +pub(crate) use bitor_fn; +pub(crate) use bitxor_fn; +pub(crate) use contains_fn; +pub(crate) use debug_fn; +pub(crate) use get_fn; +pub(crate) use into_iter_fn; +pub(crate) use into_iter_ref_fn; +pub(crate) use partial_eq_fn; +pub(crate) use set_boilerplate; +pub(crate) use set_iterator_boilerplate; +pub(crate) use sub_fn; diff --git a/frozen-collections-core/src/sets/dense_scalar_lookup_set.rs b/frozen-collections-core/src/sets/dense_scalar_lookup_set.rs new file mode 100644 index 0000000..f04717f --- /dev/null +++ b/frozen-collections-core/src/sets/dense_scalar_lookup_set.rs @@ -0,0 +1,132 @@ +use crate::maps::DenseScalarLookupMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Scalar, Set, SetIterator}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A set whose values are a continuous range in a sequence of scalar values. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +#[derive(Clone)] +pub struct DenseScalarLookupSet { + map: DenseScalarLookupMap, +} + +impl DenseScalarLookupSet +where + T: Scalar, +{ + /// Creates a frozen set. + /// + /// # Errors + /// + /// Fails if the all the values in the input vector, after sorting and dedupping, + /// don't represent a continuous range. + #[must_use] + pub const fn new(map: DenseScalarLookupMap) -> Self { + Self { map } + } +} + +impl DenseScalarLookupSet { + get_fn!(Scalar); + contains_fn!(Scalar); +} + +impl Len for DenseScalarLookupSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for DenseScalarLookupSet +where + T: Debug, +{ + debug_fn!(); +} + +impl Default for DenseScalarLookupSet +where + T: Scalar, +{ + fn default() -> Self { + Self { + map: DenseScalarLookupMap::default(), + } + } +} + +impl IntoIterator for DenseScalarLookupSet { + into_iter_fn!(); +} + +impl<'a, T> IntoIterator for &'a DenseScalarLookupSet { + into_iter_ref_fn!(); +} + +impl SetIterator for DenseScalarLookupSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for DenseScalarLookupSet +where + T: Scalar, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &DenseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &DenseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &DenseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &DenseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for DenseScalarLookupSet +where + T: Scalar, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for DenseScalarLookupSet where T: Scalar {} diff --git a/frozen-collections-core/src/sets/hash_set.rs b/frozen-collections-core/src/sets/hash_set.rs new file mode 100644 index 0000000..5ffd1b6 --- /dev/null +++ b/frozen-collections-core/src/sets/hash_set.rs @@ -0,0 +1,183 @@ +use crate::hashers::BridgeHasher; +use crate::maps::HashMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, debug_fn, into_iter_fn, into_iter_ref_fn, partial_eq_fn, + set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{CollectionMagnitude, Len, SmallCollection}; +use crate::traits::{Hasher, MapIterator, Set, SetIterator}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A general purpose set implemented using a hash table. +/// +/// A general purpose map implemented using a hash table. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/hash_warning.md")] +/// +#[derive(Clone)] +pub struct HashSet { + map: HashMap, +} + +impl HashSet +where + T: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + /// Creates a frozen set. + /// + /// # Errors + /// + /// Fails if the number of entries in the vector, after deduplication, exceeds the + /// magnitude of the collection as specified by the `CM` generic argument. + #[must_use] + pub const fn new(map: HashMap) -> Self { + Self { map } + } +} + +impl HashSet +where + CM: CollectionMagnitude, +{ + #[doc = include_str!("../doc_snippets/get_from_set_method.md")] + #[inline] + #[must_use] + pub fn get(&self, value: &Q) -> Option<&T> + where + T: Borrow, + H: Hasher, + Q: ?Sized + Eq, + { + Some(self.map.get_key_value(value)?.0) + } + + #[doc = include_str!("../doc_snippets/contains_method.md")] + #[inline] + #[must_use] + pub fn contains(&self, value: &Q) -> bool + where + T: Borrow, + H: Hasher, + Q: ?Sized + Eq, + { + self.get(value).is_some() + } +} + +impl Len for HashSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for HashSet +where + T: Debug, +{ + debug_fn!(); +} + +impl Default for HashSet +where + CM: CollectionMagnitude, + H: Default, +{ + fn default() -> Self { + Self { + map: HashMap::default(), + } + } +} + +impl IntoIterator for HashSet { + into_iter_fn!(); +} + +impl<'a, T, CM, H> IntoIterator for &'a HashSet { + into_iter_ref_fn!(); +} + +impl SetIterator for HashSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a, + CM: 'a, + H: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for HashSet +where + T: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &HashSet +where + T: Hash + Eq + Clone, + ST: Set, + CM: CollectionMagnitude, + H: Hasher + Default, +{ + bitor_fn!(H); +} + +impl BitAnd<&ST> for &HashSet +where + T: Hash + Eq + Clone, + ST: Set, + CM: CollectionMagnitude, + H: Hasher + Default, +{ + bitand_fn!(H); +} + +impl BitXor<&ST> for &HashSet +where + T: Hash + Eq + Clone, + ST: Set, + CM: CollectionMagnitude, + H: Hasher + Default, +{ + bitxor_fn!(H); +} + +impl Sub<&ST> for &HashSet +where + T: Hash + Eq + Clone, + ST: Set, + CM: CollectionMagnitude, + H: Hasher + Default, +{ + sub_fn!(H); +} + +impl PartialEq for HashSet +where + T: Eq, + ST: Set, + H: Hasher, +{ + partial_eq_fn!(); +} + +impl Eq for HashSet +where + T: Eq, + CM: CollectionMagnitude, + H: Hasher, +{ +} diff --git a/frozen-collections-core/src/sets/iterators.rs b/frozen-collections-core/src/sets/iterators.rs new file mode 100644 index 0000000..ff333c2 --- /dev/null +++ b/frozen-collections-core/src/sets/iterators.rs @@ -0,0 +1,763 @@ +use crate::traits::{Set, SetIterator}; +use core::cmp::min; +use core::fmt::{Debug, Formatter}; +use core::iter::FusedIterator; +use std::iter::Chain; + +/// An iterator over the values of a set. +pub struct Iter<'a, T> { + inner: crate::maps::Iter<'a, T, ()>, +} + +impl<'a, T> Iter<'a, T> { + pub(crate) const fn new(inner: crate::maps::Iter<'a, T, ()>) -> Self { + Self { inner } + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + self.inner.next().map(|entry| entry.0) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (k, ())| f(acc, k)) + } +} + +impl<'a, T> ExactSizeIterator for Iter<'a, T> { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'a, T> FusedIterator for Iter<'a, T> {} + +impl<'a, T> Clone for Iter<'a, T> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl<'a, T> Debug for Iter<'a, T> +where + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_list().entries((*self).clone()).finish() + } +} + +/// A consuming iterator over the values of a set. +pub struct IntoIter { + inner: crate::maps::IntoIter, +} + +impl IntoIter { + pub(crate) const fn new(inner: crate::maps::IntoIter) -> Self { + Self { inner } + } +} + +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + self.inner.next().map(|entry| entry.0) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.inner.count() + } + + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.inner.fold(init, |acc, (k, ())| f(acc, k)) + } +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for IntoIter {} + +/// An iterator that returns the union between two sets. +pub struct Union<'a, S1, S2, T> +where + S1: Set, + S2: Set, + T: 'a, +{ + s1: &'a S1, + s1_iter: >::Iterator<'a>, + s2: &'a S2, + s2_iter: >::Iterator<'a>, +} + +impl<'a, S1, S2, T> Union<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ + pub(crate) fn new(s1: &'a S1, s2: &'a S2) -> Self { + Self { + s1_iter: s1.iter(), + s1, + s2_iter: s2.iter(), + s2, + } + } +} + +impl<'a, S1, S2, T> Iterator for Union<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ + type Item = &'a T; + + #[allow(clippy::needless_borrow)] + fn next(&mut self) -> Option { + if self.s1.len() > self.s2.len() { + let item = self.s1_iter.next(); + if item.is_some() { + return item; + } + + loop { + let item = self.s2_iter.next()?; + if !self.s1.contains(&item) { + return Some(item); + } + } + } else { + let item = self.s2_iter.next(); + if item.is_some() { + return item; + } + + loop { + let item = self.s1_iter.next()?; + if !self.s2.contains(&item) { + return Some(item); + } + } + } + } + + fn size_hint(&self) -> (usize, Option) { + let h1 = self.s1_iter.size_hint(); + let h2 = self.s2_iter.size_hint(); + + let mut max = None; + if let Some(h1x) = h1.1 { + if let Some(h2x) = h2.1 { + max = h1x.checked_add(h2x); + } + } + + (min(h1.0, h2.0), max) + } +} + +impl<'a, S1, S2, T> Clone for Union<'a, S1, S2, T> +where + S1: Set, + S2: Set, + >::Iterator<'a>: Clone, + >::Iterator<'a>: Clone, +{ + fn clone(&self) -> Self { + Self { + s1: self.s1, + s1_iter: self.s1_iter.clone(), + s2: self.s2, + s2_iter: self.s2_iter.clone(), + } + } +} + +impl<'a, S1, S2, T> FusedIterator for Union<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ +} + +impl<'a, S1, S2, T> Debug for Union<'a, S1, S2, T> +where + S1: Set, + S2: Set, + >::Iterator<'a>: Clone, + >::Iterator<'a>: Clone, + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_list().entries((*self).clone()).finish() + } +} + +/// An iterator that returns the symmetric difference between two sets. +pub struct SymmetricDifference<'a, S1, S2, T> +where + S1: Set, + S2: Set, + T: 'a, +{ + iter: Chain, Difference<'a, S2, S1, T>>, +} + +impl<'a, S1, S2, T> SymmetricDifference<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ + pub(crate) fn new(s1: &'a S1, s2: &'a S2) -> Self { + Self { + iter: s1.difference(s2).chain(s2.difference(s1)), + } + } +} + +impl<'a, S1, S2, T> Iterator for SymmetricDifference<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ + type Item = &'a T; + + fn next(&mut self) -> Option<&'a T> { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.iter.count() + } +} + +impl<'a, S1, S2, T> Clone for SymmetricDifference<'a, S1, S2, T> +where + S1: Set, + S2: Set, + >::Iterator<'a>: Clone, + >::Iterator<'a>: Clone, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + } + } +} + +impl<'a, S1, S2, T> FusedIterator for SymmetricDifference<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ +} + +impl<'a, S1, S2, T> Debug for SymmetricDifference<'a, S1, S2, T> +where + S1: Set, + S2: Set, + >::Iterator<'a>: Clone, + >::Iterator<'a>: Clone, + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.iter.fmt(f) + } +} + +/// An iterator that returns the difference between two sets. +pub struct Difference<'a, S1, S2, T> +where + S1: Set, + S2: Set, + T: 'a, +{ + s1: &'a S1, + s1_iter: >::Iterator<'a>, + s2: &'a S2, +} + +impl<'a, S1, S2, T> Difference<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ + pub(crate) fn new(s1: &'a S1, s2: &'a S2) -> Self { + Self { + s1_iter: s1.iter(), + s1, + s2, + } + } +} + +impl<'a, S1, S2, T> Iterator for Difference<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ + type Item = &'a T; + + #[allow(clippy::needless_borrow)] + fn next(&mut self) -> Option { + loop { + let item = self.s1_iter.next()?; + if !self.s2.contains(&item) { + return Some(item); + } + } + } + + fn size_hint(&self) -> (usize, Option) { + let (_, upper) = self.s1_iter.size_hint(); + (0, upper) + } +} + +impl<'a, S1, S2, T> Clone for Difference<'a, S1, S2, T> +where + S1: Set, + S2: Set, + >::Iterator<'a>: Clone, + >::Iterator<'a>: Clone, +{ + fn clone(&self) -> Self { + Self { + s1: self.s1, + s1_iter: self.s1_iter.clone(), + s2: self.s2, + } + } +} + +impl<'a, S1, S2, T> FusedIterator for Difference<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ +} + +impl<'a, S1, S2, T> Debug for Difference<'a, S1, S2, T> +where + S1: Set, + S2: Set, + >::Iterator<'a>: Clone, + >::Iterator<'a>: Clone, + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_list().entries((*self).clone()).finish() + } +} + +/// An iterator that returns the intersection between two sets. +pub struct Intersection<'a, S1, S2, T> +where + S1: Set, + S2: Set, + T: 'a, +{ + s1: &'a S1, + s1_iter: >::Iterator<'a>, + s2: &'a S2, + s2_iter: >::Iterator<'a>, +} + +impl<'a, S1, S2, T> Intersection<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ + pub(crate) fn new(s1: &'a S1, s2: &'a S2) -> Self { + Self { + s1_iter: s1.iter(), + s1, + s2_iter: s2.iter(), + s2, + } + } +} + +impl<'a, S1, S2, T> Iterator for Intersection<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ + type Item = &'a T; + + #[allow(clippy::needless_borrow)] + fn next(&mut self) -> Option { + if self.s1.len() < self.s2.len() { + loop { + let item = self.s1_iter.next()?; + if self.s2.contains(&item) { + return Some(item); + } + } + } else { + loop { + let item = self.s2_iter.next()?; + if self.s1.contains(&item) { + return Some(item); + } + } + } + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(min(self.s1.len(), self.s2.len()))) + } +} + +impl<'a, S1, S2, T> Clone for Intersection<'a, S1, S2, T> +where + S1: Set, + S2: Set, + >::Iterator<'a>: Clone, + >::Iterator<'a>: Clone, +{ + fn clone(&self) -> Self { + Self { + s1: self.s1, + s1_iter: self.s1_iter.clone(), + s2: self.s2, + s2_iter: self.s2_iter.clone(), + } + } +} + +impl<'a, S1, S2, T> FusedIterator for Intersection<'a, S1, S2, T> +where + S1: Set, + S2: Set, +{ +} + +impl<'a, S1, S2, T> Debug for Intersection<'a, S1, S2, T> +where + S1: Set, + S2: Set, + >::Iterator<'a>: Clone, + >::Iterator<'a>: Clone, + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_list().entries((*self).clone()).finish() + } +} +#[cfg(test)] +mod tests { + use super::*; + use crate::maps::{IntoIter as MapIntoIter, Iter as MapIter}; + use std::collections::HashSet as StdHashSet; + + #[test] + fn test_iter() { + let entries = vec![("Alice", ()), ("Bob", ())]; + let map_iter = MapIter::new(&entries); + let iter = Iter::new(map_iter); + + let collected: Vec<_> = iter.collect(); + assert_eq!(collected, vec![&"Alice", &"Bob"]); + } + + #[test] + fn test_iter_empty() { + let entries: Vec<(&str, ())> = vec![]; + let map_iter = MapIter::new(&entries); + let mut iter = Iter::new(map_iter); + + assert_eq!(iter.next(), None); + } + + #[test] + fn test_iter_size_hint() { + let entries = vec![("Alice", ()), ("Bob", ())]; + let map_iter = MapIter::new(&entries); + let iter = Iter::new(map_iter); + + assert_eq!(iter.size_hint(), (2, Some(2))); + } + + #[test] + fn test_iter_clone() { + let entries = vec![("Alice", ()), ("Bob", ())]; + let map_iter = MapIter::new(&entries); + let iter = Iter::new(map_iter); + let iter_clone = iter.clone(); + + let collected: Vec<_> = iter_clone.collect(); + assert_eq!(collected, vec![&"Alice", &"Bob"]); + } + + #[test] + fn test_iter_debug() { + let entries = vec![("Alice", ()), ("Bob", ())]; + let map_iter = MapIter::new(&entries); + let iter = Iter::new(map_iter); + + let debug_str = format!("{iter:?}"); + assert!(debug_str.contains("Alice")); + assert!(debug_str.contains("Bob")); + } + + #[test] + fn test_into_iter() { + let entries = vec![("Alice", ()), ("Bob", ())]; + let map_into_iter = MapIntoIter::new(entries.into_boxed_slice()); + let into_iter = IntoIter::new(map_into_iter); + + let collected: Vec<_> = into_iter.collect(); + assert_eq!(collected, vec!["Alice", "Bob"]); + } + + #[test] + fn test_into_iter_empty() { + let entries: Vec<(&str, ())> = vec![]; + let map_into_iter = MapIntoIter::new(entries.into_boxed_slice()); + let mut into_iter = IntoIter::new(map_into_iter); + + assert_eq!(into_iter.next(), None); + } + + #[test] + fn test_into_iter_size_hint() { + let entries = vec![("Alice", ()), ("Bob", ())]; + let map_into_iter = MapIntoIter::new(entries.into_boxed_slice()); + let into_iter = IntoIter::new(map_into_iter); + + assert_eq!(into_iter.size_hint(), (2, Some(2))); + } + + #[test] + fn test_union() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let union = Union::new(&set1, &set2); + + let mut collected: Vec<_> = union.collect(); + collected.sort(); + assert_eq!(collected, vec![&"Alice", &"Bob", &"Charlie"]); + } + + #[test] + fn test_union_empty() { + let set1: StdHashSet<&str> = StdHashSet::new(); + let set2: StdHashSet<&str> = StdHashSet::new(); + let union = Union::new(&set1, &set2); + + assert_eq!(union.count(), 0); + } + + #[test] + fn test_symmetric_difference() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let symmetric_difference = SymmetricDifference::new(&set1, &set2); + + let collected: Vec<_> = symmetric_difference.collect(); + assert_eq!(collected, vec![&"Alice", &"Charlie"]); + } + + #[test] + fn test_symmetric_difference_empty() { + let set1: StdHashSet<&str> = StdHashSet::new(); + let set2: StdHashSet<&str> = StdHashSet::new(); + let symmetric_difference = SymmetricDifference::new(&set1, &set2); + + assert_eq!(symmetric_difference.count(), 0); + } + + #[test] + fn test_difference() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let difference = Difference::new(&set1, &set2); + + let collected: Vec<_> = difference.collect(); + assert_eq!(collected, vec![&"Alice"]); + } + + #[test] + fn test_difference_empty() { + let set1: StdHashSet<&str> = StdHashSet::new(); + let set2: StdHashSet<&str> = StdHashSet::new(); + let difference = Difference::new(&set1, &set2); + + assert_eq!(difference.count(), 0); + } + + #[test] + fn test_intersection() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let intersection = Intersection::new(&set1, &set2); + + let collected: Vec<_> = intersection.collect(); + assert_eq!(collected, vec![&"Bob"]); + } + + #[test] + fn test_intersection_empty() { + let set1: StdHashSet<&str> = StdHashSet::new(); + let set2: StdHashSet<&str> = StdHashSet::new(); + let intersection = Intersection::new(&set1, &set2); + + assert_eq!(intersection.count(), 0); + } + + #[test] + fn test_difference_clone() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let difference = Difference::new(&set1, &set2); + let difference_clone = difference.clone(); + + let collected: Vec<_> = difference_clone.collect(); + assert_eq!(collected, vec![&"Alice"]); + } + + #[test] + fn test_intersection_clone() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let intersection = Intersection::new(&set1, &set2); + let intersection_clone = intersection.clone(); + + let collected: Vec<_> = intersection_clone.collect(); + assert_eq!(collected, vec![&"Bob"]); + } + + #[test] + fn test_symmetric_difference_clone() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let symmetric_difference = SymmetricDifference::new(&set1, &set2); + let symmetric_difference_clone = symmetric_difference.clone(); + + let collected: Vec<_> = symmetric_difference_clone.collect(); + assert_eq!(collected, vec![&"Alice", &"Charlie"]); + } + + #[test] + fn test_union_clone() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let union = Union::new(&set1, &set2); + let union_clone = union.clone(); + + let mut collected: Vec<_> = union_clone.collect(); + collected.sort(); + assert_eq!(collected, vec![&"Alice", &"Bob", &"Charlie"]); + } + + #[test] + fn test_union_fmt() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let union = Union::new(&set1, &set2); + + let debug_str = format!("{union:?}"); + assert!(debug_str.contains("Alice")); + assert!(debug_str.contains("Bob")); + assert!(debug_str.contains("Charlie")); + } + + #[test] + fn test_symmetric_difference_fmt() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let symmetric_difference = SymmetricDifference::new(&set1, &set2); + + let debug_str = format!("{symmetric_difference:?}"); + assert!(debug_str.contains("Alice")); + assert!(debug_str.contains("Charlie")); + } + + #[test] + fn test_difference_fmt() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let difference = Difference::new(&set1, &set2); + + let debug_str = format!("{difference:?}"); + assert!(debug_str.contains("Alice")); + } + + #[test] + fn test_intersection_fmt() { + let set1 = vec!["Alice", "Bob"].into_iter().collect::>(); + let set2 = vec!["Bob", "Charlie"] + .into_iter() + .collect::>(); + let intersection = Intersection::new(&set1, &set2); + + let debug_str = format!("{intersection:?}"); + assert!(debug_str.contains("Bob")); + } +} diff --git a/frozen-collections-core/src/sets/mod.rs b/frozen-collections-core/src/sets/mod.rs new file mode 100644 index 0000000..be6acbf --- /dev/null +++ b/frozen-collections-core/src/sets/mod.rs @@ -0,0 +1,18 @@ +//! Specialized read-only set types. + +pub use binary_search_set::BinarySearchSet; +pub use dense_scalar_lookup_set::DenseScalarLookupSet; +pub use hash_set::HashSet; +pub use iterators::*; +pub use ordered_scan_set::OrderedScanSet; +pub use scan_set::ScanSet; +pub use sparse_scalar_lookup_set::SparseScalarLookupSet; + +mod binary_search_set; +pub(crate) mod decl_macros; +mod dense_scalar_lookup_set; +mod hash_set; +mod iterators; +mod ordered_scan_set; +mod scan_set; +mod sparse_scalar_lookup_set; diff --git a/frozen-collections-core/src/sets/ordered_scan_set.rs b/frozen-collections-core/src/sets/ordered_scan_set.rs new file mode 100644 index 0000000..27b1643 --- /dev/null +++ b/frozen-collections-core/src/sets/ordered_scan_set.rs @@ -0,0 +1,125 @@ +use crate::maps::OrderedScanMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Set, SetIterator}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A general purpose set implemented with linear scanning. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +#[doc = include_str!("../doc_snippets/order_warning.md")] +/// +#[derive(Clone)] +pub struct OrderedScanSet { + map: OrderedScanMap, +} + +impl OrderedScanSet +where + T: Ord, +{ + /// Creates a frozen set. + #[must_use] + pub const fn new(map: OrderedScanMap) -> Self { + Self { map } + } +} + +impl OrderedScanSet { + get_fn!(Ord); + contains_fn!(Ord); +} + +impl Len for OrderedScanSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for OrderedScanSet +where + T: Debug, +{ + debug_fn!(); +} + +impl Default for OrderedScanSet { + fn default() -> Self { + Self { + map: OrderedScanMap::default(), + } + } +} + +impl IntoIterator for OrderedScanSet { + into_iter_fn!(); +} + +impl<'a, T> IntoIterator for &'a OrderedScanSet { + into_iter_ref_fn!(); +} + +impl SetIterator for OrderedScanSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for OrderedScanSet +where + T: Ord, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &OrderedScanSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &OrderedScanSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &OrderedScanSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &OrderedScanSet +where + T: Hash + Eq + Ord + Clone, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for OrderedScanSet +where + T: Ord, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for OrderedScanSet where T: Ord {} diff --git a/frozen-collections-core/src/sets/scan_set.rs b/frozen-collections-core/src/sets/scan_set.rs new file mode 100644 index 0000000..3e8ecdc --- /dev/null +++ b/frozen-collections-core/src/sets/scan_set.rs @@ -0,0 +1,124 @@ +use crate::maps::ScanMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{Len, MapIterator, Set, SetIterator}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A general purpose set implemented with linear scanning. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +#[derive(Clone)] +pub struct ScanSet { + map: ScanMap, +} + +impl ScanSet +where + T: Eq, +{ + /// Creates a frozen set. + #[must_use] + pub const fn new(map: ScanMap) -> Self { + Self { map } + } +} + +impl ScanSet { + get_fn!(Eq); + contains_fn!(Eq); +} + +impl Len for ScanSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for ScanSet +where + T: Debug, +{ + debug_fn!(); +} + +impl Default for ScanSet { + fn default() -> Self { + Self { + map: ScanMap::default(), + } + } +} + +impl IntoIterator for ScanSet { + into_iter_fn!(); +} + +impl<'a, T> IntoIterator for &'a ScanSet { + into_iter_ref_fn!(); +} + +impl SetIterator for ScanSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for ScanSet +where + T: Eq, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &ScanSet +where + T: Hash + Eq + Clone, + ST: Set, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &ScanSet +where + T: Hash + Eq + Clone, + ST: Set, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &ScanSet +where + T: Hash + Eq + Clone, + ST: Set, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &ScanSet +where + T: Hash + Eq + Clone, + ST: Set, +{ + sub_fn!(RandomState); +} + +impl PartialEq for ScanSet +where + T: Eq, + ST: Set, +{ + partial_eq_fn!(); +} + +impl Eq for ScanSet where T: Eq {} diff --git a/frozen-collections-core/src/sets/sparse_scalar_lookup_set.rs b/frozen-collections-core/src/sets/sparse_scalar_lookup_set.rs new file mode 100644 index 0000000..9330932 --- /dev/null +++ b/frozen-collections-core/src/sets/sparse_scalar_lookup_set.rs @@ -0,0 +1,155 @@ +use crate::maps::SparseScalarLookupMap; +use crate::sets::decl_macros::{ + bitand_fn, bitor_fn, bitxor_fn, contains_fn, debug_fn, get_fn, into_iter_fn, into_iter_ref_fn, + partial_eq_fn, set_boilerplate, set_iterator_boilerplate, sub_fn, +}; +use crate::sets::{IntoIter, Iter}; +use crate::traits::{ + CollectionMagnitude, Len, MapIterator, Scalar, Set, SetIterator, SmallCollection, +}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; + +/// A set whose values are a sparse range of values from a scalar. +/// +#[doc = include_str!("../doc_snippets/type_compat_warning.md")] +#[doc = include_str!("../doc_snippets/about.md")] +/// +#[derive(Clone)] +pub struct SparseScalarLookupSet { + map: SparseScalarLookupMap, +} + +impl SparseScalarLookupSet +where + T: Scalar, + CM: CollectionMagnitude, + >::Error: Debug, +{ + /// Creates a frozen set. + /// + /// # Errors + /// + /// Fails if the number of entries in the vector, after deduplication, exceeds the + /// magnitude of the collection as specified by the `CM` generic argument. + #[must_use] + pub const fn new(map: SparseScalarLookupMap) -> Self { + Self { map } + } +} + +impl SparseScalarLookupSet +where + CM: CollectionMagnitude, +{ + get_fn!(Scalar); + contains_fn!(Scalar); +} + +impl Len for SparseScalarLookupSet { + fn len(&self) -> usize { + self.map.len() + } +} + +impl Debug for SparseScalarLookupSet +where + T: Debug, +{ + debug_fn!(); +} + +impl Default for SparseScalarLookupSet +where + T: Scalar, +{ + fn default() -> Self { + Self { + map: SparseScalarLookupMap::default(), + } + } +} + +impl IntoIterator for SparseScalarLookupSet { + into_iter_fn!(); +} + +impl<'a, T, CM> IntoIterator for &'a SparseScalarLookupSet +where + T: Scalar, + CM: CollectionMagnitude, +{ + into_iter_ref_fn!(); +} + +impl SetIterator for SparseScalarLookupSet { + type Iterator<'a> + = Iter<'a, T> + where + T: 'a, + CM: 'a; + + set_iterator_boilerplate!(); +} + +impl Set for SparseScalarLookupSet +where + T: Scalar, + CM: CollectionMagnitude, +{ + set_boilerplate!(); +} + +impl BitOr<&ST> for &SparseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, + CM: CollectionMagnitude, +{ + bitor_fn!(RandomState); +} + +impl BitAnd<&ST> for &SparseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, + CM: CollectionMagnitude, +{ + bitand_fn!(RandomState); +} + +impl BitXor<&ST> for &SparseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, + CM: CollectionMagnitude, +{ + bitxor_fn!(RandomState); +} + +impl Sub<&ST> for &SparseScalarLookupSet +where + T: Scalar + Hash, + ST: Set, + CM: CollectionMagnitude, +{ + sub_fn!(RandomState); +} + +impl PartialEq for SparseScalarLookupSet +where + T: Scalar, + ST: Set, + CM: CollectionMagnitude, +{ + partial_eq_fn!(); +} + +impl Eq for SparseScalarLookupSet +where + T: Scalar, + CM: CollectionMagnitude, +{ +} diff --git a/frozen-collections-core/src/traits/collection_magnitude.rs b/frozen-collections-core/src/traits/collection_magnitude.rs new file mode 100644 index 0000000..a5887a7 --- /dev/null +++ b/frozen-collections-core/src/traits/collection_magnitude.rs @@ -0,0 +1,35 @@ +/// Controls the magnitude of collection types. +/// +/// This trait indicates that a collection's layout can be optimized at compile-time depending on +/// the max capacity that the collection can hold. +pub trait CollectionMagnitude: Copy + TryFrom + Into { + /// The maximum number of entries supported in the collection. + const MAX_CAPACITY: usize; + + /// The zero value for the magnitude. + const ZERO: Self; +} + +/// A small collection that can hold up to 255 entries. +pub type SmallCollection = u8; + +impl CollectionMagnitude for SmallCollection { + const MAX_CAPACITY: usize = Self::MAX as usize; + const ZERO: Self = 0; +} + +/// A medium collection that can hold up to 65,535 entries. +pub type MediumCollection = u16; + +impl CollectionMagnitude for MediumCollection { + const MAX_CAPACITY: usize = Self::MAX as usize; + const ZERO: Self = 0; +} + +/// A large collection that can hold up to [`usize::MAX`] entries. +pub type LargeCollection = usize; + +impl CollectionMagnitude for LargeCollection { + const MAX_CAPACITY: Self = Self::MAX; + const ZERO: Self = 0; +} diff --git a/frozen-collections-core/src/traits/hasher.rs b/frozen-collections-core/src/traits/hasher.rs new file mode 100644 index 0000000..fa7f22b --- /dev/null +++ b/frozen-collections-core/src/traits/hasher.rs @@ -0,0 +1,13 @@ +/// Hashes values of a specific type. +/// +/// This provides a hashing mechanism which is orthogonal to the normal +/// [`Hash`] trait. This allows for the creation of hashers that are +/// specialized for specific types, and can be used in contexts where the +/// standard `Hash` trait is not desirable. +pub trait Hasher +where + T: ?Sized, +{ + /// Produce a hash value for the given value. + fn hash(&self, value: &T) -> u64; +} diff --git a/frozen-collections-core/src/traits/len.rs b/frozen-collections-core/src/traits/len.rs new file mode 100644 index 0000000..0dff37b --- /dev/null +++ b/frozen-collections-core/src/traits/len.rs @@ -0,0 +1,400 @@ +use alloc::boxed::Box; +use alloc::collections::VecDeque; +use alloc::rc::Rc; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::vec::Vec; + +/// Types that can return a length. +pub trait Len { + /// Returns the length of the value. + fn len(&self) -> usize; + + /// Returns whether the value is empty. + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[cfg(feature = "std")] +impl Len for std::collections::HashSet { + fn len(&self) -> usize { + self.len() + } +} + +#[cfg(feature = "std")] +impl Len for std::collections::HashMap { + fn len(&self) -> usize { + self.len() + } +} + +impl Len for String { + fn len(&self) -> usize { + self.len() + } +} + +impl Len for str { + fn len(&self) -> usize { + self.len() + } +} + +impl Len for &str { + fn len(&self) -> usize { + str::len(self) + } +} + +impl Len for core::ffi::CStr { + fn len(&self) -> usize { + self.to_bytes().len() + } +} + +#[cfg(feature = "std")] +impl Len for std::ffi::CString { + fn len(&self) -> usize { + self.as_bytes().len() + } +} + +impl Len for [T] { + fn len(&self) -> usize { + self.len() + } +} + +impl Len for Box { + fn len(&self) -> usize { + T::len(self) + } +} + +impl Len for Rc { + fn len(&self) -> usize { + T::len(self) + } +} + +impl Len for Arc { + fn len(&self) -> usize { + T::len(self) + } +} + +#[cfg(feature = "std")] +impl Len for std::collections::BTreeMap { + fn len(&self) -> usize { + self.len() + } +} + +#[cfg(feature = "std")] +impl Len for std::collections::BTreeSet { + fn len(&self) -> usize { + self.len() + } +} + +#[cfg(feature = "std")] +impl Len for std::collections::BinaryHeap { + fn len(&self) -> usize { + self.len() + } +} + +#[cfg(feature = "std")] +impl Len for std::collections::LinkedList { + fn len(&self) -> usize { + self.len() + } +} + +impl Len for Vec { + fn len(&self) -> usize { + self.len() + } +} + +impl Len for VecDeque { + fn len(&self) -> usize { + self.len() + } +} + +#[cfg(feature = "std")] +impl Len for std::ffi::OsStr { + fn len(&self) -> usize { + self.len() + } +} + +#[cfg(feature = "std")] +impl Len for std::ffi::OsString { + fn len(&self) -> usize { + self.as_os_str().len() + } +} + +impl Len for hashbrown::HashMap { + fn len(&self) -> usize { + self.len() + } +} + +impl Len for hashbrown::HashSet { + fn len(&self) -> usize { + self.len() + } +} + +#[cfg(test)] +mod tests { + use crate::traits::Len; + use alloc::collections::VecDeque; + use alloc::rc::Rc; + use alloc::sync::Arc; + + fn get_len(value: &T) -> usize { + value.len() + } + + #[test] + #[cfg(feature = "std")] + fn hashset_len_and_is_empty() { + let mut set = std::collections::HashSet::new(); + assert_eq!(get_len(&set), 0); + assert!(set.is_empty()); + + set.insert(1); + assert_eq!(get_len(&set), 1); + assert!(!set.is_empty()); + } + + #[test] + #[cfg(feature = "std")] + fn hashmap_len_and_is_empty() { + let mut map = std::collections::HashMap::new(); + assert_eq!(get_len(&map), 0); + assert!(map.is_empty()); + + map.insert("key", "value"); + assert_eq!(get_len(&map), 1); + assert!(!map.is_empty()); + } + + #[test] + #[cfg(feature = "std")] + fn hashbrown_hashset_len_and_is_empty() { + let mut set = hashbrown::HashSet::new(); + assert_eq!(get_len(&set), 0); + assert!(set.is_empty()); + + set.insert(1); + assert_eq!(get_len(&set), 1); + assert!(!set.is_empty()); + } + + #[test] + #[cfg(feature = "std")] + fn hashbrown_hashmap_len_and_is_empty() { + let mut map = hashbrown::HashMap::new(); + assert_eq!(get_len(&map), 0); + assert!(map.is_empty()); + + map.insert("key", "value"); + assert_eq!(get_len(&map), 1); + assert!(!map.is_empty()); + } + + #[test] + fn string_len_and_is_empty() { + let s = String::new(); + assert_eq!(get_len(&s), 0); + assert!(s.is_empty()); + + let s = String::from("hello"); + assert_eq!(get_len(&s), 5); + assert!(!s.is_empty()); + } + + #[test] + fn str_len_and_is_empty() { + let s = ""; + assert_eq!(get_len(&s), 0); + assert!(s.is_empty()); + + let s = "hello"; + assert_eq!(get_len(&s), 5); + assert!(!s.is_empty()); + } + + #[test] + #[cfg(feature = "std")] + fn cstring_len_and_is_empty() { + use std::ffi::CString; + let s = CString::new("").unwrap(); + assert_eq!(get_len(&s), 0); + assert!(s.is_empty()); + + let s = CString::new("hello").unwrap(); + assert_eq!(get_len(&s), 5); + assert!(!s.is_empty()); + } + + #[test] + #[cfg(feature = "std")] + fn cstr_len_and_is_empty() { + let s = c""; + assert_eq!(get_len(s), 0); + assert!(s.is_empty()); + + let s = c"hello"; + assert_eq!(get_len(s), 5); + assert!(!s.is_empty()); + } + + #[test] + fn vec_len_and_is_empty() { + let v: Vec = Vec::new(); + assert_eq!(get_len(&v), 0); + assert!(v.is_empty()); + + let v = vec![1, 2, 3]; + assert_eq!(get_len(&v), 3); + assert!(!v.is_empty()); + } + + #[test] + fn vecdeque_len_and_is_empty() { + let v: VecDeque = VecDeque::new(); + assert_eq!(get_len(&v), 0); + assert!(v.is_empty()); + + let v: VecDeque = vec![1, 2, 3].into(); + assert_eq!(get_len(&v), 3); + assert!(!v.is_empty()); + } + + #[test] + fn box_len_and_is_empty() { + let b: Box = Box::from(""); + assert_eq!(get_len(&b), 0); + assert!(b.is_empty()); + + let b: Box = Box::from("hello"); + assert_eq!(get_len(&b), 5); + assert!(!b.is_empty()); + } + + #[test] + fn rc_len_and_is_empty() { + let r: Rc = Rc::from(""); + assert_eq!(get_len(&r), 0); + assert!(r.is_empty()); + + let r: Rc = Rc::from("hello"); + assert_eq!(get_len(&r), 5); + assert!(!r.is_empty()); + } + + #[test] + fn arc_len_and_is_empty() { + let a: Arc = Arc::from(""); + assert_eq!(get_len(&a), 0); + assert!(a.is_empty()); + + let a: Arc = Arc::from("hello"); + assert_eq!(get_len(&a), 5); + assert!(!a.is_empty()); + } + + #[test] + fn btreemap_len_and_is_empty() { + use std::collections::BTreeMap; + let mut map = BTreeMap::new(); + assert_eq!(get_len(&map), 0); + assert!(map.is_empty()); + + map.insert("key", "value"); + assert_eq!(get_len(&map), 1); + assert!(!map.is_empty()); + } + + #[test] + fn btreeset_len_and_is_empty() { + use std::collections::BTreeSet; + let mut set = BTreeSet::new(); + assert_eq!(get_len(&set), 0); + assert!(set.is_empty()); + + set.insert(1); + assert_eq!(get_len(&set), 1); + assert!(!set.is_empty()); + } + + #[test] + fn binaryheap_len_and_is_empty() { + use std::collections::BinaryHeap; + let mut heap = BinaryHeap::new(); + assert_eq!(get_len(&heap), 0); + assert!(heap.is_empty()); + + heap.push(1); + assert_eq!(get_len(&heap), 1); + assert!(!heap.is_empty()); + } + + #[test] + fn linkedlist_len_and_is_empty() { + use std::collections::LinkedList; + let mut list = LinkedList::new(); + assert_eq!(get_len(&list), 0); + assert!(list.is_empty()); + + list.push_back(1); + assert_eq!(get_len(&list), 1); + assert!(!list.is_empty()); + } + + #[test] + #[cfg(feature = "std")] + fn osstr_len_and_is_empty() { + use std::ffi::OsStr; + let s = OsStr::new(""); + assert_eq!(get_len(s), 0); + assert!(s.is_empty()); + + let s = OsStr::new("hello"); + assert_eq!(get_len(s), 5); + assert!(!s.is_empty()); + } + + #[test] + #[cfg(feature = "std")] + fn osstring_len_and_is_empty() { + use std::ffi::OsString; + let s = OsString::new(); + assert_eq!(get_len(&s), 0); + assert!(s.is_empty()); + + let s = OsString::from("hello"); + assert_eq!(get_len(&s), 5); + assert!(!s.is_empty()); + } + + #[test] + #[cfg(feature = "std")] + fn slice_len_and_is_empty() { + let s: &[u8] = [].as_slice(); + assert_eq!(get_len(s), 0); + assert!(s.is_empty()); + + let s = [0, 1, 2, 3, 4].as_slice(); + assert_eq!(get_len(s), 5); + assert!(!s.is_empty()); + } +} diff --git a/frozen-collections-core/src/traits/map.rs b/frozen-collections-core/src/traits/map.rs new file mode 100644 index 0000000..ed229f7 --- /dev/null +++ b/frozen-collections-core/src/traits/map.rs @@ -0,0 +1,159 @@ +use crate::traits::{Len, MapIterator}; +use crate::utils::{find_duplicate, slow_find_duplicate}; + +#[cfg(feature = "std")] +use core::hash::{BuildHasher, Hash}; +use core::mem::MaybeUninit; + +/// Common abstractions for maps. +pub trait Map: Len + MapIterator { + /// Checks whether a particular value is present in the map. + #[must_use] + fn contains_key(&self, key: &K) -> bool; + + /// Gets a value from the map. + #[must_use] + fn get(&self, key: &K) -> Option<&V>; + + /// Gets a key and value from the map. + #[must_use] + fn get_key_value(&self, key: &K) -> Option<(&K, &V)>; + + /// Gets a mutable value from the map. + #[must_use] + fn get_mut(&mut self, key: &K) -> Option<&mut V>; + + /// Gets multiple mutable values from the map. + #[must_use] + fn get_many_mut(&mut self, keys: [&K; N]) -> Option<[&mut V; N]>; +} + +#[cfg(feature = "std")] +impl Map for std::collections::HashMap +where + K: Hash + Eq, + BH: BuildHasher, +{ + #[inline] + fn contains_key(&self, key: &K) -> bool { + Self::contains_key(self, key) + } + + #[inline] + fn get(&self, key: &K) -> Option<&V> { + Self::get(self, key) + } + + #[inline] + fn get_key_value(&self, key: &K) -> Option<(&K, &V)> { + Self::get_key_value(self, key) + } + + #[inline] + fn get_mut(&mut self, key: &K) -> Option<&mut V> { + Self::get_mut(self, key) + } + + fn get_many_mut(&mut self, keys: [&K; N]) -> Option<[&mut V; N]> { + if find_duplicate(keys.iter()).is_some() { + return None; + } + + let mut result: MaybeUninit<[&mut V; N]> = MaybeUninit::uninit(); + let p = result.as_mut_ptr(); + let x: *mut Self = self; + unsafe { + for (i, key) in keys.iter().enumerate() { + (*p)[i] = (*x).get_mut(key)?; + } + + Some(result.assume_init()) + } + } +} + +#[cfg(feature = "std")] +impl Map for std::collections::BTreeMap +where + K: Ord, +{ + #[inline] + fn contains_key(&self, key: &K) -> bool { + Self::contains_key(self, key) + } + + #[inline] + fn get(&self, key: &K) -> Option<&V> { + Self::get(self, key) + } + + #[inline] + fn get_key_value(&self, key: &K) -> Option<(&K, &V)> { + Self::get_key_value(self, key) + } + + #[inline] + fn get_mut(&mut self, key: &K) -> Option<&mut V> { + Self::get_mut(self, key) + } + + fn get_many_mut(&mut self, keys: [&K; N]) -> Option<[&mut V; N]> { + if slow_find_duplicate(&keys).is_some() { + return None; + } + + let mut result: MaybeUninit<[&mut V; N]> = MaybeUninit::uninit(); + let p = result.as_mut_ptr(); + let x: *mut Self = self; + unsafe { + for (i, key) in keys.iter().enumerate() { + (*p)[i] = (*x).get_mut(key)?; + } + + Some(result.assume_init()) + } + } +} + +impl Map for hashbrown::HashMap +where + K: Hash + Eq, + BH: BuildHasher, +{ + #[inline] + fn contains_key(&self, key: &K) -> bool { + Self::contains_key(self, key) + } + + #[inline] + fn get(&self, key: &K) -> Option<&V> { + Self::get(self, key) + } + + #[inline] + fn get_key_value(&self, key: &K) -> Option<(&K, &V)> { + Self::get_key_value(self, key) + } + + #[inline] + fn get_mut(&mut self, key: &K) -> Option<&mut V> { + Self::get_mut(self, key) + } + + fn get_many_mut(&mut self, keys: [&K; N]) -> Option<[&mut V; N]> { + if find_duplicate(keys.iter()).is_some() { + return None; + } + + let mut result: MaybeUninit<[&mut V; N]> = MaybeUninit::uninit(); + let p = result.as_mut_ptr(); + let x: *mut Self = self; + unsafe { + for (i, key) in keys.iter().enumerate() { + (*p)[i] = (*x).get_mut(*key)?; + } + + Some(result.assume_init()) + } + } +} diff --git a/frozen-collections-core/src/traits/map_iterator.rs b/frozen-collections-core/src/traits/map_iterator.rs new file mode 100644 index 0000000..835ea2f --- /dev/null +++ b/frozen-collections-core/src/traits/map_iterator.rs @@ -0,0 +1,276 @@ +#[cfg(feature = "std")] +use core::hash::BuildHasher; + +/// Common iteration abstractions for maps. +pub trait MapIterator: IntoIterator { + type Iterator<'a>: Iterator + where + Self: 'a, + K: 'a, + V: 'a; + + type KeyIterator<'a>: Iterator + where + Self: 'a, + K: 'a; + + type ValueIterator<'a>: Iterator + where + Self: 'a, + V: 'a; + + type IntoKeyIterator: Iterator; + type IntoValueIterator: Iterator; + + type MutIterator<'a>: Iterator + where + Self: 'a, + K: 'a, + V: 'a; + + type ValueMutIterator<'a>: Iterator + where + Self: 'a, + V: 'a; + + /// An iterator visiting all entries in arbitrary order. + #[must_use] + fn iter(&self) -> Self::Iterator<'_>; + + /// An iterator visiting all keys in arbitrary order. + #[must_use] + fn keys(&self) -> Self::KeyIterator<'_>; + + /// An iterator visiting all values in arbitrary order. + #[must_use] + fn values(&self) -> Self::ValueIterator<'_>; + + /// A consuming iterator visiting all keys in arbitrary order. + #[must_use] + fn into_keys(self) -> Self::IntoKeyIterator; + + /// A consuming iterator visiting all values in arbitrary order. + #[must_use] + fn into_values(self) -> Self::IntoValueIterator; + + /// An iterator producing mutable references to all entries in arbitrary order. + #[must_use] + fn iter_mut(&mut self) -> Self::MutIterator<'_>; + + /// An iterator visiting all values mutably in arbitrary order. + #[must_use] + fn values_mut(&mut self) -> Self::ValueMutIterator<'_>; +} + +#[cfg(feature = "std")] +impl MapIterator for std::collections::HashMap +where + BH: BuildHasher, +{ + type Iterator<'a> + = std::collections::hash_map::Iter<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + type KeyIterator<'a> + = std::collections::hash_map::Keys<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + type ValueIterator<'a> + = std::collections::hash_map::Values<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + type IntoKeyIterator = std::collections::hash_map::IntoKeys; + type IntoValueIterator = std::collections::hash_map::IntoValues; + type MutIterator<'a> + = std::collections::hash_map::IterMut<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + type ValueMutIterator<'a> + = std::collections::hash_map::ValuesMut<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + #[inline] + fn iter(&self) -> Self::Iterator<'_> { + Self::iter(self) + } + + #[inline] + fn keys(&self) -> Self::KeyIterator<'_> { + Self::keys(self) + } + + #[inline] + fn values(&self) -> Self::ValueIterator<'_> { + Self::values(self) + } + + fn into_keys(self) -> Self::IntoKeyIterator { + Self::into_keys(self) + } + + fn into_values(self) -> Self::IntoValueIterator { + Self::into_values(self) + } + + fn iter_mut(&mut self) -> Self::MutIterator<'_> { + Self::iter_mut(self) + } + + fn values_mut(&mut self) -> Self::ValueMutIterator<'_> { + self.values_mut() + } +} + +#[cfg(feature = "std")] +impl MapIterator for std::collections::BTreeMap { + type Iterator<'a> + = std::collections::btree_map::Iter<'a, K, V> + where + K: 'a, + V: 'a; + + type KeyIterator<'a> + = std::collections::btree_map::Keys<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueIterator<'a> + = std::collections::btree_map::Values<'a, K, V> + where + K: 'a, + V: 'a; + + type IntoKeyIterator = std::collections::btree_map::IntoKeys; + type IntoValueIterator = std::collections::btree_map::IntoValues; + type MutIterator<'a> + = std::collections::btree_map::IterMut<'a, K, V> + where + K: 'a, + V: 'a; + + type ValueMutIterator<'a> + = std::collections::btree_map::ValuesMut<'a, K, V> + where + K: 'a, + V: 'a; + + #[inline] + fn iter(&self) -> Self::Iterator<'_> { + Self::iter(self) + } + + #[inline] + fn keys(&self) -> Self::KeyIterator<'_> { + Self::keys(self) + } + + #[inline] + fn values(&self) -> Self::ValueIterator<'_> { + Self::values(self) + } + + fn into_keys(self) -> Self::IntoKeyIterator { + Self::into_keys(self) + } + + fn into_values(self) -> Self::IntoValueIterator { + Self::into_values(self) + } + + fn iter_mut(&mut self) -> Self::MutIterator<'_> { + Self::iter_mut(self) + } + + fn values_mut(&mut self) -> Self::ValueMutIterator<'_> { + self.values_mut() + } +} + +impl MapIterator for hashbrown::HashMap +where + BH: BuildHasher, +{ + type Iterator<'a> + = hashbrown::hash_map::Iter<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + type KeyIterator<'a> + = hashbrown::hash_map::Keys<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + type ValueIterator<'a> + = hashbrown::hash_map::Values<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + type IntoKeyIterator = hashbrown::hash_map::IntoKeys; + type IntoValueIterator = hashbrown::hash_map::IntoValues; + type MutIterator<'a> + = hashbrown::hash_map::IterMut<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + type ValueMutIterator<'a> + = hashbrown::hash_map::ValuesMut<'a, K, V> + where + K: 'a, + V: 'a, + BH: 'a; + + #[inline] + fn iter(&self) -> Self::Iterator<'_> { + Self::iter(self) + } + + #[inline] + fn keys(&self) -> Self::KeyIterator<'_> { + Self::keys(self) + } + + #[inline] + fn values(&self) -> Self::ValueIterator<'_> { + Self::values(self) + } + + fn into_keys(self) -> Self::IntoKeyIterator { + Self::into_keys(self) + } + + fn into_values(self) -> Self::IntoValueIterator { + Self::into_values(self) + } + + fn iter_mut(&mut self) -> Self::MutIterator<'_> { + Self::iter_mut(self) + } + + fn values_mut(&mut self) -> Self::ValueMutIterator<'_> { + self.values_mut() + } +} diff --git a/frozen-collections-core/src/traits/mod.rs b/frozen-collections-core/src/traits/mod.rs new file mode 100644 index 0000000..be58051 --- /dev/null +++ b/frozen-collections-core/src/traits/mod.rs @@ -0,0 +1,21 @@ +//! Traits to support frozen collections. + +pub use crate::traits::collection_magnitude::{ + CollectionMagnitude, LargeCollection, MediumCollection, SmallCollection, +}; +pub use crate::traits::hasher::Hasher; +pub use crate::traits::len::Len; +pub use crate::traits::map::Map; +pub use crate::traits::map_iterator::MapIterator; +pub use crate::traits::scalar::Scalar; +pub use crate::traits::set::Set; +pub use crate::traits::set_iterator::SetIterator; + +mod collection_magnitude; +mod hasher; +mod len; +mod map; +mod map_iterator; +mod scalar; +mod set; +mod set_iterator; diff --git a/frozen-collections-core/src/traits/scalar.rs b/frozen-collections-core/src/traits/scalar.rs new file mode 100644 index 0000000..48b241c --- /dev/null +++ b/frozen-collections-core/src/traits/scalar.rs @@ -0,0 +1,193 @@ +use core::num::{ + NonZeroI16, NonZeroI32, NonZeroI8, NonZeroIsize, NonZeroU16, NonZeroU32, NonZeroU8, + NonZeroUsize, +}; + +/// A scalar value with an index in its sequence of possible values. +pub trait Scalar: Ord + Clone + Copy { + /// Returns a value's index into its containing sequence. + fn index(&self) -> usize; +} + +macro_rules! impl_unsigned_scalar { + ($($t:ty),*) => { + $( + impl Scalar for $t { + #[inline] + #[allow(clippy::cast_possible_truncation)] + fn index(&self) -> usize { + *self as usize + } + } + )* + }; +} + +macro_rules! impl_signed_scalar { + ($($t:ty: $unsigned_ty:ty: $mask:expr),*) => { + $( + impl Scalar for $t { + #[inline] + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_possible_truncation)] + fn index(&self) -> usize { + ((*self as $unsigned_ty) ^ $mask) as usize + } + } + )* + }; +} + +macro_rules! impl_unsigned_nz_scalar { + ($($t:ty),*) => { + $( + impl Scalar for $t { + #[inline] + #[allow(clippy::cast_possible_truncation)] + fn index(&self) -> usize { + (*self).get() as usize + } + } + )* + }; +} + +macro_rules! impl_signed_nz_scalar { + ($($t:ty: $unsigned_ty:ty: $mask:expr),*) => { + $( + impl Scalar for $t { + #[inline] + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_possible_truncation)] + fn index(&self) -> usize { + (((*self).get() as $unsigned_ty) ^ $mask) as usize + } + } + )* + }; +} + +#[cfg(target_pointer_width = "64")] +impl_unsigned_scalar!(u8, u16, u32, u64, usize); +#[cfg(target_pointer_width = "64")] +impl_signed_scalar!(i8:u8:0x80, i16:u16:0x8000, i32:u32:0x8000_0000, i64:u64:0x8000_0000_0000_0000, isize:usize:0x8000_0000_0000_0000); +#[cfg(target_pointer_width = "64")] +impl_unsigned_nz_scalar!( + NonZeroU8, + NonZeroU16, + NonZeroU32, + core::num::NonZeroU64, + NonZeroUsize +); +#[cfg(target_pointer_width = "64")] +impl_signed_nz_scalar!(NonZeroI8:u8:0x80, NonZeroI16:u16:0x8000, NonZeroI32:u32:0x8000_0000, core::num::NonZeroI64:u64:0x8000_0000_0000_0000, NonZeroIsize:usize:0x8000_0000_0000_0000); + +#[cfg(target_pointer_width = "32")] +impl_unsigned_scalar!(u8, u16, u32, usize); +#[cfg(target_pointer_width = "32")] +impl_signed_scalar!(i8:u8:0x80, i16:u16:0x8000, i32:u32:0x8000_0000, isize:usize:0x8000_0000); +#[cfg(target_pointer_width = "32")] +impl_unsigned_nz_scalar!(NonZeroU8, NonZeroU16, NonZeroU32, NonZeroUsize); +#[cfg(target_pointer_width = "32")] +impl_signed_nz_scalar!(NonZeroI8:u8:0x80, NonZeroI16:u16:0x8000, NonZeroI32:u32:0x8000_0000, NonZeroIsize:usize:0x8000_0000); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_unsigned_scalar() { + assert_eq!(5u8.index(), 5); + assert_eq!(10u16.index(), 10); + assert_eq!(20u32.index(), 20); + assert_eq!(30usize.index(), 30); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn test_unsigned_scalar_64() { + assert_eq!(40u64.index(), 40); + } + + #[test] + fn test_signed_scalar() { + assert_eq!((-5i8).index(), 0x80 - 5); + assert_eq!((-10i16).index(), 0x8000 - 10); + assert_eq!((-20i32).index(), 0x8000_0000 - 20); + + assert_eq!(5i8.index(), 0x80 + 5); + assert_eq!(10i16.index(), 0x8000 + 10); + assert_eq!(20i32.index(), 0x8000_0000 + 20); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn test_signed_scalar_32() { + assert_eq!((-30isize).index(), 0x8000_0000 - 30); + assert_eq!(30isize.index(), 0x8000_0000 + 30); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn test_signed_scalar_64() { + assert_eq!((-30isize).index(), 0x8000_0000_0000_0000 - 30); + assert_eq!((-40i64).index(), 0x8000_0000_0000_0000 - 40); + + assert_eq!(30isize.index(), 0x8000_0000_0000_0000 + 30); + assert_eq!((40i64).index(), 0x8000_0000_0000_0000 + 40); + } + + #[test] + fn test_unsigned_nz_scalar() { + assert_eq!(NonZeroU8::new(5).unwrap().index(), 5); + assert_eq!(NonZeroU16::new(10).unwrap().index(), 10); + assert_eq!(NonZeroU32::new(20).unwrap().index(), 20); + assert_eq!(NonZeroUsize::new(30).unwrap().index(), 30); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn test_unsigned_nz_scalar_64() { + assert_eq!(core::num::NonZeroU64::new(40).unwrap().index(), 40); + } + + #[test] + fn test_signed_nz_scalar() { + assert_eq!(NonZeroI8::new(-5).unwrap().index(), 0x80 - 5); + assert_eq!(NonZeroI16::new(-10).unwrap().index(), 0x8000 - 10); + assert_eq!(NonZeroI32::new(-20).unwrap().index(), 0x8000_0000 - 20); + + assert_eq!(NonZeroI8::new(5).unwrap().index(), 0x80 + 5); + assert_eq!(NonZeroI16::new(10).unwrap().index(), 0x8000 + 10); + assert_eq!(NonZeroI32::new(20).unwrap().index(), 0x8000_0000 + 20); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn test_signed_nz_scalar_32() { + assert_eq!(NonZeroIsize::new(-30).unwrap().index(), 0x8000_0000 - 30); + assert_eq!(NonZeroIsize::new(30).unwrap().index(), 0x8000_0000 + 30); + } + + #[test] + #[cfg(target_pointer_width = "64")] + fn test_signed_nz_scalar_64() { + assert_eq!( + NonZeroIsize::new(-30).unwrap().index(), + 0x8000_0000_0000_0000 - 30 + ); + assert_eq!( + core::num::NonZeroI64::new(-40).unwrap().index(), + 0x8000_0000_0000_0000 - 40 + ); + + assert_eq!( + NonZeroIsize::new(30).unwrap().index(), + 0x8000_0000_0000_0000 + 30 + ); + assert_eq!( + core::num::NonZeroI64::new(40).unwrap().index(), + 0x8000_0000_0000_0000 + 40 + ); + } +} diff --git a/frozen-collections-core/src/traits/set.rs b/frozen-collections-core/src/traits/set.rs new file mode 100644 index 0000000..a158531 --- /dev/null +++ b/frozen-collections-core/src/traits/set.rs @@ -0,0 +1,141 @@ +use crate::sets::{Difference, Intersection, SymmetricDifference, Union}; +use crate::traits::{Len, SetIterator}; + +#[cfg(feature = "std")] +use core::hash::{BuildHasher, Hash}; + +/// Common abstractions for sets. +pub trait Set: Len + SetIterator { + /// Checks whether a particular value is present in the set. + #[must_use] + fn contains(&self, value: &T) -> bool; + + /// Visits the values representing the union, + /// i.e., all the values in `self` or `other`, without duplicates. + #[must_use] + fn union<'a, ST>(&'a self, other: &'a ST) -> Union<'a, Self, ST, T> + where + ST: Set, + Self: Sized, + { + Union::new(self, other) + } + + /// Visits the values representing the symmetric difference, + /// i.e., the values that are in `self` or in `other` but not in both. + #[must_use] + fn symmetric_difference<'a, ST>(&'a self, other: &'a ST) -> SymmetricDifference<'a, Self, ST, T> + where + ST: Set, + Self: Sized, + { + SymmetricDifference::new(self, other) + } + + /// Visits the values representing the difference, + /// i.e., the values that are in `self` but not in `other`. + #[must_use] + fn difference<'a, ST>(&'a self, other: &'a ST) -> Difference<'a, Self, ST, T> + where + ST: Set, + Self: Sized, + { + Difference::new(self, other) + } + + /// Visits the values representing the intersection, + /// i.e., the values that are both in `self` and `other`. + /// + /// When an equal element is present in `self` and `other` + /// then the resulting `Intersection` may yield references to + /// one or the other. This can be relevant if `T` contains fields which + /// are not compared by its `Eq` implementation, and may hold different + /// value between the two equal copies of `T` in the two sets. + #[must_use] + fn intersection<'a, ST>(&'a self, other: &'a ST) -> Intersection<'a, Self, ST, T> + where + ST: Set, + Self: Sized, + { + Intersection::new(self, other) + } + + /// Returns `true` if `self` has no entries in common with `other`. + /// This is equivalent to checking for an empty intersection. + #[must_use] + fn is_disjoint<'a, ST>(&'a self, other: &'a ST) -> bool + where + ST: Set, + Self: Sized, + { + if self.len() <= other.len() { + self.iter().all(|v| !other.contains(v)) + } else { + other.iter().all(|v| !self.contains(v)) + } + } + + /// Returns `true` if the set is a subset of another, + /// i.e., `other` contains at least all the values in `self`. + #[must_use] + fn is_subset<'a, ST>(&'a self, other: &'a ST) -> bool + where + ST: Set, + Self: Sized, + { + if self.len() <= other.len() { + self.iter().all(|v| other.contains(v)) + } else { + false + } + } + + /// Returns `true` if the set is a superset of another, + /// i.e., `self` contains at least all the values in `other`. + #[must_use] + fn is_superset<'a, ST>(&'a self, other: &'a ST) -> bool + where + ST: Set, + Self: Sized, + { + if other.len() <= self.len() { + other.iter().all(|v| self.contains(v)) + } else { + false + } + } +} + +#[cfg(feature = "std")] +impl Set for std::collections::HashSet +where + T: Hash + Eq, + BH: BuildHasher, +{ + #[inline] + fn contains(&self, value: &T) -> bool { + Self::contains(self, value) + } +} + +#[cfg(feature = "std")] +impl Set for std::collections::BTreeSet +where + T: Ord, +{ + #[inline] + fn contains(&self, value: &T) -> bool { + Self::contains(self, value) + } +} + +impl Set for hashbrown::hash_set::HashSet +where + T: Hash + Eq, + BH: BuildHasher, +{ + #[inline] + fn contains(&self, value: &T) -> bool { + Self::contains(self, value) + } +} diff --git a/frozen-collections-core/src/traits/set_iterator.rs b/frozen-collections-core/src/traits/set_iterator.rs new file mode 100644 index 0000000..5cf0a9f --- /dev/null +++ b/frozen-collections-core/src/traits/set_iterator.rs @@ -0,0 +1,60 @@ +#[cfg(feature = "std")] +use core::hash::BuildHasher; + +/// Common iteration abstractions for sets. +pub trait SetIterator: IntoIterator { + type Iterator<'a>: Iterator + where + Self: 'a, + T: 'a; + + /// An iterator visiting all entries in arbitrary order. + #[must_use] + fn iter(&self) -> Self::Iterator<'_>; +} + +#[cfg(feature = "std")] +impl SetIterator for std::collections::HashSet +where + BH: BuildHasher, +{ + type Iterator<'a> + = std::collections::hash_set::Iter<'a, T> + where + T: 'a, + BH: 'a; + + #[inline] + fn iter(&self) -> Self::Iterator<'_> { + Self::iter(self) + } +} + +#[cfg(feature = "std")] +impl SetIterator for std::collections::BTreeSet { + type Iterator<'a> + = std::collections::btree_set::Iter<'a, T> + where + T: 'a; + + #[inline] + fn iter(&self) -> Self::Iterator<'_> { + Self::iter(self) + } +} + +impl SetIterator for hashbrown::hash_set::HashSet +where + BH: BuildHasher, +{ + type Iterator<'a> + = hashbrown::hash_set::Iter<'a, T> + where + T: 'a, + BH: 'a; + + #[inline] + fn iter(&self) -> Self::Iterator<'_> { + Self::iter(self) + } +} diff --git a/frozen-collections-core/src/utils/dedup.rs b/frozen-collections-core/src/utils/dedup.rs new file mode 100644 index 0000000..c1a534e --- /dev/null +++ b/frozen-collections-core/src/utils/dedup.rs @@ -0,0 +1,259 @@ +//! Duplicate removal utility functions for frozen collections. + +use crate::traits::Hasher; +use core::hash::Hash; +use hashbrown::HashSet as HashbrownSet; + +/// Remove duplicates from a vector, keeping the last occurrence of each duplicate. +/// +/// This assumes the input vector is fairly short as time complexity is very high. +pub fn slow_dedup_by_keep_last(unsorted_entries: &mut Vec, mut cmp: F) +where + F: FnMut(&mut T, &mut T) -> bool, +{ + if unsorted_entries.len() < 2 { + return; + } + + let mut dupes = HashbrownSet::new(); + for i in 0..unsorted_entries.len() { + for j in (i + 1)..unsorted_entries.len() { + let (s0, s1) = unsorted_entries.split_at_mut(j); + if cmp(&mut s0[i], &mut s1[0]) { + dupes.insert(i); + break; + } + } + } + + if dupes.is_empty() { + return; + } + + let mut index = 0; + unsorted_entries.retain(|_| { + let result = !dupes.contains(&index); + index += 1; + result + }); +} + +/// Remove duplicates from a vector, keeping the last occurrence of each duplicate. +/// +/// This assumes the input vector is sorted. +#[allow(clippy::module_name_repetitions)] +pub fn dedup_by_keep_last(sorted_entries: &mut Vec, mut cmp: F) +where + F: FnMut(&mut T, &mut T) -> bool, +{ + if sorted_entries.len() < 2 { + return; + } + + let mut dupes = HashbrownSet::new(); + for i in 0..sorted_entries.len() - 1 { + let (s0, s1) = sorted_entries.split_at_mut(i + 1); + if cmp(&mut s0[i], &mut s1[0]) { + dupes.insert(i); + } + } + + if dupes.is_empty() { + return; + } + + let mut index = 0; + sorted_entries.retain(|_| { + let result = !dupes.contains(&index); + index += 1; + result + }); +} + +struct Entry<'a, K> { + pub hash: u64, + pub index: usize, + pub key: &'a K, +} + +impl<'a, K> Hash for Entry<'a, K> +where + K: Eq, +{ + fn hash(&self, state: &mut H) { + state.write_u64(self.hash); + } +} + +impl<'a, K: Eq> PartialEq for Entry<'a, K> { + fn eq(&self, other: &Self) -> bool { + self.key.eq(other.key) + } +} + +impl<'a, K: Eq> Eq for Entry<'a, K> {} + +/// Remove duplicates from a vector, keeping the last occurrence of each duplicate. +#[allow(clippy::module_name_repetitions)] +pub fn dedup_by_hash_keep_last(unsorted_entries: &mut Vec<(K, V)>, hasher: &H) +where + K: Eq, + H: Hasher, +{ + if unsorted_entries.len() < 2 { + return; + } + + let mut dupes = Vec::new(); + { + let mut keep = HashbrownSet::with_capacity(unsorted_entries.len()); + for (i, pair) in unsorted_entries.iter().enumerate() { + let hash = hasher.hash(&pair.0); + + let entry = Entry { + hash, + index: i, + key: &pair.0, + }; + + let old = keep.replace(entry); + if let Some(old) = old { + dupes.push(old.index); + } + } + } + + if dupes.is_empty() { + // no duplicates found, we're done + return; + } + + // remove the duplicates from the input vector + let mut index = 0; + unsorted_entries.retain(|_| { + let result = !dupes.contains(&index); + index += 1; + result + }); +} + +/// Look for the first duplicate value if any. +pub fn find_duplicate<'a, T, I>(values: I) -> Option +where + T: Hash + Eq + 'a, + I: Iterator, +{ + let mut s = HashbrownSet::new(); + + for (i, v) in values.enumerate() { + if !s.insert(v) { + return Some(i); + } + } + + None +} + +/// Look for the first duplicate value if any (assumes `values` is a relatively small array). +pub fn slow_find_duplicate(values: &[T]) -> Option { + for i in 0..values.len() { + for j in 0..i { + if values[j].eq(&values[i]) { + return Some(i); + } + } + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_slow_dedup_by_keep_last_no_duplicates() { + let mut vec = vec![(1, "one"), (2, "two"), (3, "three")]; + slow_dedup_by_keep_last(&mut vec, |x, y| x.0.eq(&y.0)); + assert_eq!(vec, vec![(1, "one"), (2, "two"), (3, "three")]); + } + + #[test] + fn test_slow_dedup_by_keep_last_with_duplicates() { + let mut vec = vec![ + (1, "one"), + (2, "two"), + (2, "two duplicate"), + (3, "three"), + (3, "three duplicate"), + (3, "three last"), + ]; + slow_dedup_by_keep_last(&mut vec, |x, y| x.0.eq(&y.0)); + assert_eq!( + vec, + vec![(1, "one"), (2, "two duplicate"), (3, "three last")] + ); + } + + #[test] + fn test_slow_dedup_by_keep_last_empty_vector() { + let mut vec: Vec<(u8, &str)> = Vec::new(); + slow_dedup_by_keep_last(&mut vec, |x, y| x.0.eq(&y.0)); + assert!(vec.is_empty()); + } + + #[test] + fn test_slow_dedup_by_key_keep_last_all_same_entries() { + let mut vec = vec![(1, "one"), (1, "one duplicate"), (1, "one last")]; + slow_dedup_by_keep_last(&mut vec, |x, y| x.0.eq(&y.0)); + assert_eq!(vec, vec![(1, "one last")]); + } + + #[test] + fn test_find_duplicate_no_duplicates() { + let vec = [1, 2, 3]; + assert_eq!(find_duplicate(vec.iter()), None); + } + + #[test] + fn test_find_duplicate_with_duplicates() { + let vec = [1, 2, 2, 3]; + assert_eq!(find_duplicate(vec.iter()), Some(2)); + } + + #[test] + fn test_find_duplicate_empty_slice() { + let vec: Vec = Vec::new(); + assert_eq!(find_duplicate(vec.iter()), None); + } + + #[test] + fn test_find_duplicate_all_same_entries() { + let vec = [1, 1, 1]; + assert_eq!(find_duplicate(vec.iter()), Some(1)); + } + + #[test] + fn test_find_duplicate_slow_no_duplicates() { + let vec = vec![1, 2, 3]; + assert_eq!(slow_find_duplicate(&vec), None); + } + + #[test] + fn test_find_duplicate_slow_with_duplicates() { + let vec = vec![1, 2, 2, 3]; + assert_eq!(slow_find_duplicate(&vec), Some(2)); + } + + #[test] + fn test_find_duplicate_slow_empty_slice() { + let vec: Vec = Vec::new(); + assert_eq!(slow_find_duplicate(&vec), None); + } + + #[test] + fn test_find_duplicate_slow_all_same_entries() { + let vec = vec![1, 1, 1]; + assert_eq!(slow_find_duplicate(&vec), Some(1)); + } +} diff --git a/frozen-collections-core/src/utils/mod.rs b/frozen-collections-core/src/utils/mod.rs new file mode 100644 index 0000000..32dccf8 --- /dev/null +++ b/frozen-collections-core/src/utils/mod.rs @@ -0,0 +1,7 @@ +//! Utility functions and types for internal use. + +pub use dedup::*; +pub use random::*; + +mod dedup; +mod random; diff --git a/frozen-collections-core/src/utils/random.rs b/frozen-collections-core/src/utils/random.rs new file mode 100644 index 0000000..154d3a4 --- /dev/null +++ b/frozen-collections-core/src/utils/random.rs @@ -0,0 +1,14 @@ +//! Random # utilities for frozen collections. + +use const_random::const_random; + +/// Pick four random seeds at compile time. +#[must_use] +pub const fn pick_compile_time_random_seeds() -> (u64, u64, u64, u64) { + ( + const_random!(u64), + const_random!(u64), + const_random!(u64), + const_random!(u64), + ) +} diff --git a/frozen-collections-macros/Cargo.toml b/frozen-collections-macros/Cargo.toml new file mode 100644 index 0000000..b75a04e --- /dev/null +++ b/frozen-collections-macros/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "frozen-collections-macros" +description = "Macros to support frozen collections" +readme = "README.md" +authors.workspace = true +version.workspace = true +edition.workspace = true +categories.workspace = true +keywords.workspace = true +repository.workspace = true +license.workspace = true + +[lib] +name = "frozen_collections_macros" +path = "src/lib.rs" +proc-macro = true + +[dependencies] +proc-macro-error = "1.0.4" + +[dependencies.frozen-collections-core] +path = "../frozen-collections-core" +version = "0.1.0" + +[lints] +workspace = true diff --git a/frozen-collections-macros/README.md b/frozen-collections-macros/README.md new file mode 100644 index 0000000..f51a5c3 --- /dev/null +++ b/frozen-collections-macros/README.md @@ -0,0 +1,6 @@ +# frozen-collections-macros + +This crate contains some of the implementation logic for the +frozen-collections crate. Users of frozen collections +should take a dependency on the [`frozen-collections`](https://docs.rs/frozen-collections) crate +instead of this one. diff --git a/frozen-collections-macros/src/lib.rs b/frozen-collections-macros/src/lib.rs new file mode 100644 index 0000000..b818bd4 --- /dev/null +++ b/frozen-collections-macros/src/lib.rs @@ -0,0 +1,84 @@ +//! Implementation crate for the frozen collections. +//! +//!
+//! This crate is an implementation detail of the `frozen_collections` crate. +//! This crate's API is therefore not stable and may change at any time. Please do not +//! use this crate directly, and instead use the public API provided by the +//! `frozen_collections` crate. +//!
+ +use frozen_collections_core::macros::*; +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; + +#[proc_macro] +#[proc_macro_error] +pub fn fz_hash_map(item: TokenStream) -> TokenStream { + fz_hash_map_macro(item.into()) + .unwrap_or_else(|error| error.to_compile_error()) + .into() +} + +#[proc_macro] +#[proc_macro_error] +pub fn fz_hash_set(item: TokenStream) -> TokenStream { + fz_hash_set_macro(item.into()) + .unwrap_or_else(|error| error.to_compile_error()) + .into() +} + +#[proc_macro] +#[proc_macro_error] +pub fn fz_ordered_map(item: TokenStream) -> TokenStream { + fz_ordered_map_macro(item.into()) + .unwrap_or_else(|error| error.to_compile_error()) + .into() +} + +#[proc_macro] +#[proc_macro_error] +pub fn fz_ordered_set(item: TokenStream) -> TokenStream { + fz_ordered_set_macro(item.into()) + .unwrap_or_else(|error| error.to_compile_error()) + .into() +} + +#[proc_macro] +#[proc_macro_error] +pub fn fz_string_map(item: TokenStream) -> TokenStream { + fz_string_map_macro(item.into()) + .unwrap_or_else(|error| error.to_compile_error()) + .into() +} + +#[proc_macro] +#[proc_macro_error] +pub fn fz_string_set(item: TokenStream) -> TokenStream { + fz_string_set_macro(item.into()) + .unwrap_or_else(|error| error.to_compile_error()) + .into() +} + +#[proc_macro] +#[proc_macro_error] +pub fn fz_scalar_map(item: TokenStream) -> TokenStream { + fz_scalar_map_macro(item.into()) + .unwrap_or_else(|error| error.to_compile_error()) + .into() +} + +#[proc_macro] +#[proc_macro_error] +pub fn fz_scalar_set(item: TokenStream) -> TokenStream { + fz_scalar_set_macro(item.into()) + .unwrap_or_else(|error| error.to_compile_error()) + .into() +} + +#[proc_macro_derive(Scalar)] +#[proc_macro_error] +pub fn derive_scalar(item: TokenStream) -> TokenStream { + derive_scalar_macro(item.into()) + .unwrap_or_else(|error| error.to_compile_error()) + .into() +} diff --git a/frozen-collections/Cargo.toml b/frozen-collections/Cargo.toml new file mode 100644 index 0000000..a0293ce --- /dev/null +++ b/frozen-collections/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "frozen-collections" +description = "Fast partially-immutable collections." +readme.workspace = true +authors.workspace = true +version.workspace = true +edition.workspace = true +categories.workspace = true +keywords.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +ahash = { version = "0.8.11" } + +[dev-dependencies] +quote = { version = "1.0.37" } +hashbrown = { version = "0.15.2" } + +[dependencies.frozen-collections-macros] +path = "../frozen-collections-macros" +version = "0.1.0" + +[dependencies.frozen-collections-core] +path = "../frozen-collections-core" +default-features = false +version = "0.1.0" + +[lints] +workspace = true + +[features] +default = ["std"] +std = ["frozen-collections-core/std"] diff --git a/frozen-collections/src/lib.rs b/frozen-collections/src/lib.rs new file mode 100644 index 0000000..ffe89ae --- /dev/null +++ b/frozen-collections/src/lib.rs @@ -0,0 +1,1297 @@ +#![cfg_attr(not(any(test, feature = "std")), no_std)] + +//! Frozen collections: fast partially immutable collections +//! +//! Frozen collections are designed to deliver improved +//! read performance relative to the standard [`HashMap`](std::collections::HashMap) and +//! [`HashSet`](std::collections::HashSet) types. They are ideal for use with long-lasting collections +//! which get initialized when an application starts and remain unchanged +//! permanently, or at least extended periods of time. This is a common +//! pattern in service applications. +//! +//! As part of creating a frozen collection, analysis is performed over the data that the collection +//! will hold to determine the best layout and algorithm to use to deliver optimal performance. +//! Depending on the situation, sometimes the analysis is done at compile-time whereas in +//! other cases it is done at runtime when the collection is initialized. +//! This analysis can take some time, but the value in spending this time up front +//! is that the collections provide faster read-time performance. +//! +//! # Creation +//! +//! Frozen collections are created with one of eight macros: [`fz_hash_map!`], [`fz_ordered_map!`], +//! [`fz_scalar_map!`], [`fz_string_map!`], [`fz_hash_set!`], [`fz_ordered_set!`], +//! [`fz_scalar_set!`], or [`fz_string_set!`]. These macros analyze the data you provide +//! and return a custom implementation type that's optimized for the data. All the +//! possible implementations implement the [`Map`] or [`Set`] traits. +//! +//! The macros exist in a short form and a long form, described below. +//! +//! ## Short Form +//! +//! With the short form, you supply the data that +//! goes into the collection and get in return an initialized collection of an unnamed +//! type. For example: +//! +//! ```rust +//! use frozen_collections::*; +//! +//! let m = fz_string_map!({ +//! "Alice": 1, +//! "Bob": 2, +//! "Sandy": 3, +//! "Tom": 4, +//! }); +//! ``` +//! +//! At build time, the macro analyzes the data supplied and determines the best map +//! implementation type to use. As such, the type of `m` is not known to this code. `m` will +//! always implement the [`Map`] trait however, so you can leverage type inference even though +//! you don't know the actual type of `m`: +//! +//! ```rust +//! use frozen_collections::*; +//! +//! fn main() { +//! let m = fz_string_map!({ +//! "Alice": 1, +//! "Bob": 2, +//! "Sandy": 3, +//! "Tom": 4, +//! }); +//! +//! more(m); +//! } +//! +//! fn more(m: M) +//! where +//! M: Map<&'static str, i32> +//! { +//! assert!(m.contains_key(&"Alice")); +//! } +//! ``` +//! +//! Rather than specifying all the data inline, you can also create a frozen collection by passing +//! a vector as input: +//! +//! ```rust +//! use frozen_collections::*; +//! +//! let v = vec![ +//! ("Alice".to_string(), 1), +//! ("Bob".to_string(), 2), +//! ("Sandy".to_string(), 3), +//! ("Tom".to_string(), 4), +//! ]; +//! +//! let m = fz_string_map!(v); +//! ``` +//! +//! The inline form is preferred however since it results in faster code. However, whereas the inline form +//! requires all the data to be provided at compile time, the vector form enables the content of the +//! frozen collection to tbe determined at runtime. +//! +//! ## Long Form +//! +//! The long form lets you provide a type alias name which will be created to +//! correspond to the collection implementation type chosen by the macro invocation. +//! Note that you must use the long form if you want to declare a static frozen collection. +//! +//! ```rust +//! use frozen_collections::*; +//! +//! fz_string_map!(static MAP: MyMapType<&str, i32> = { +//! "Alice": 1, +//! "Bob": 2, +//! "Sandy": 3, +//! "Tom": 4, +//! }); +//! ``` +//! +//! The above creates a static variable called `MAP` with keys that are strings and values which are +//! integers. As before, you don't know the specific implementation type selected by the macro, but +//! this time you have a type alias (i.e. `MyMapType`) representing that type. You can then use this alias +//! anywhere you'd like to in your code where you'd like to mention the type explicitly. +//! +//! To use the long form for non-static uses, replace `static` with `let`: +//! +//! ```rust +//! use frozen_collections::*; +//! +//! fz_string_map!(let m: MyMapType<&str, i32> = { +//! "Alice": 1, +//! "Bob": 2, +//! "Sandy": 3, +//! "Tom": 4, +//! }); +//! +//! more(m); +//! +//! struct S { +//! m: MyMapType, +//! } +//! +//! fn more(m: MyMapType) { +//! assert!(m.contains_key("Alice")); +//! } +//! ``` +//! +//! And like in the short form, you can also supply the collection's data via a vector: +//! +//! ```rust +//! use frozen_collections::*; +//! +//! let v = vec![ +//! ("Alice".to_string(), 1), +//! ("Bob".to_string(), 2), +//! ("Sandy".to_string(), 3), +//! ("Tom".to_string(), 4), +//! ]; +//! +//! fz_string_map!(let m: MyMapType<&str, i32> = v); +//! ``` +//! +//! # Partially Immutable +//! +//! Frozen maps are only partially immutable. The keys associated with a map are determined +//! at creation time and cannot change, but the values can be updated at will if you have a +//! mutable reference to the map. +//! +//! Frozen sets however are completely immutable and so never change after creation. +//! +//! # Performance Considerations +//! +//! The analysis performed when creating maps tries to find the best concrete implementation type +//! given the data at hand. If all the data is visible to the macro at compile time, then you get +//! the best possible performance. If you supply a vector instead, then the analysis can only be +//! done at runtime and the resulting collection types are a bit slower. +//! +//! When creating static collections, the collections produced can often be embedded directly as constant data +//! into the binary of the application, thus require not initialization time and no heap space at +//! runtime. This also happens to be the fastest form for these collections. If possible, this happens +//! automatically, you don't need to do anything special to enable this behavior. +//! +//! # Analysis and Optimizations +//! +//! Unlike normal collections, the frozen collections require you to provide all the data for +//! the collection when you create the collection. The data you supply is analyzed which determines +//! which specific underlying implementation strategy to use and how to lay out data internally. +//! +//! The available implementation strategies are: +//! +//! - **Scalar as Hash**. When the keys are of an integer or enum type, this uses the keys themselves +//! as hash codes, avoiding the overhead of hashing. +//! +//! - **Dense Scalar Lookup**. When the keys represent a contiguous range of integer or enum values, +//! lookups use a simple array access instead of hashing. +//! +//! - **Sparse Scalar Lookup**. When the keys represent a sparse range of integer or enum values, +//! lookups use a sparse array access instead of hashing. +//! +//! - **Length as Hash**. When the keys are of a slice type, the length of the slices +//! are used as hash code, avoiding the overhead of hashing. +//! +//! - **Left Hand Substring Hashing**. When the keys are of a slice type, this uses sub-slices of +//! the keys for hashing, reducing the overhead of hashing. +//! +//! - **Right Hand Substring Hashing**. Similar to the Left Hand Substring Hashing from above, but +//! using right-aligned sub-slices instead. +//! +//! - **Linear Scan**. For very small collections, this avoids hashing completely by scanning through the +//! entries in linear order. +//! +//! - **Ordered Scan**. For very small collections where the keys implement the [`Ord`] trait, +//! this avoids hashing completely by scanning through the entries in linear order. +//! +//! - **Classic Hashing**. This is the fallback when none of the previous strategies apply. The +//! frozen implementations are generally faster than [`std::collections::HashMap`] and +//! [`std::collections::HashSet`]. +//! +//! # Cargo Features +//! +//! You can specify the following features when you include the `frozen_collections` crate in your +//! `Cargo.toml` file: +//! +//! - **`std`**. Enables small features only available when building with the standard library. +//! +//! The `std` feature is enabled by default. + +extern crate alloc; + +pub use frozen_collections_core::traits::{Len, Map, MapIterator, Scalar, Set, SetIterator}; + +#[doc(hidden)] +pub use frozen_collections_core::traits::{ + CollectionMagnitude, Hasher, LargeCollection, MediumCollection, SmallCollection, +}; + +/// Creates an efficient map with a fixed set of hashable keys. +/// +/// The concrete type used to implement the map is based on an analysis of the input you +/// provide. Although the types vary, they all implement the [`Map`] trait, so refer to the +/// trait for API documentation. +/// +/// # Alternate Choices +/// +/// If your keys are integers or enum variants, you should use the [`fz_scalar_map!`] macro instead. +/// If your keys are strings, you should use the [`fz_string_map`] macro instead. Both of these will +/// deliver better performance since they are specifically optimized for those key types. +/// +/// # Example +/// +/// ``` +/// use frozen_collections::*; +/// +/// // The key type we use to index into our example maps +/// #[derive(Eq, PartialEq, Hash, Clone, Debug)] +/// struct Key { +/// pub name: &'static str, +/// pub age: i32, +/// } +/// +/// // Declare a global static map. This results in a static variable called MY_MAP_0 of type MyMapType0. +/// fz_hash_map!(static MY_MAP_0: MyMapType0 = { +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// fn variables() { +/// // Declare a local static map. This results in a local variable called MY_MAP_1 of type MyMapType1. +/// fz_hash_map!(static MY_MAP_1: MyMapType1 = { +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// // Declare a local map. This results in a local variable called my_map_2 of type MyMapType2. +/// fz_hash_map!(let my_map_2: MyMapType2 = { +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// // Declare a mutable local map. This results in a local variable called my_map_3 of type MyMapType3. +/// fz_hash_map!(let mut my_map_3: MyMapType3 = { +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// // Declare a local map. This results in a local variable called my_map_4 of an unknown type. +/// let my_map_4 = fz_hash_map!({ +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// let v = vec![ +/// (Key { name: "Alice", age: 30}, 1), +/// (Key { name: "Bob", age: 40}, 2), +/// ]; +/// +/// // Declare a local map. This results in a local variable called my_map_5 of type MyMapType4. +/// fz_hash_map!(let my_map_5: MyMapType4 = v); +/// +/// let v = vec![ +/// (Key { name: "Alice", age: 30}, 1), +/// (Key { name: "Bob", age: 40}, 2), +/// ]; +/// +/// // Declare a local map. This results in a local variable called my_map_6 of an unknown type. +/// let my_map_6 = fz_hash_map!(v); +/// +/// // no matter how the maps are declared, no matter the type selected to implement the map, +/// // they all work the same way and have the same API surface and implement the `Map` trait. +/// assert_eq!(MY_MAP_0, MY_MAP_1); +/// assert_eq!(MY_MAP_0, my_map_2); +/// assert_eq!(MY_MAP_0, my_map_3); +/// assert_eq!(MY_MAP_0, my_map_4); +/// assert_eq!(MY_MAP_0, my_map_5); +/// assert_eq!(MY_MAP_0, my_map_6); +/// +/// assert_eq!( +/// Some(&1), +/// MY_MAP_0.get(&Key { +/// name: "Alice", +/// age: 30 +/// }) +/// ); +/// +/// assert_eq!( +/// Some(&2), +/// MY_MAP_0.get(&Key { +/// name: "Bob", +/// age: 40 +/// }) +/// ); +/// +/// assert_eq!( +/// None, +/// MY_MAP_0.get(&Key { +/// name: "Fred", +/// age: 50 +/// }) +/// ); +/// } +/// +/// // How to embed a map into a struct using the map's type. +/// struct MyStruct0 { +/// map: MyMapType0, +/// } +/// +/// // how to embed a map into a struct using the `Map` trait. +/// struct MyStruct1 +/// where +/// M: Map, +/// { +/// map: M, +/// } +/// +/// // You may need to have specific instances of a map in your process where the keys are the same, but +/// // the values differ. For that case, you can create a static instance with placeholder values. +/// // Then you clone this static instance when needed and set the values that you need in each case. +/// // So you'll have a single set of keys, but different values in each map instance. +/// fn structs() { +/// let mut ms0 = MyStruct0 { +/// map: MY_MAP_0.clone(), +/// }; +/// +/// let mut ms1 = MyStruct1 { +/// map: MY_MAP_0.clone(), +/// }; +/// +/// // set a custom value in ms0 +/// if let Some(v) = ms0.map.get_mut(&Key { +/// name: "Alice", +/// age: 30, +/// }) { +/// *v = 3; +/// } +/// +/// // set a different custom value in ms1 +/// if let Some(v) = ms1.map.get_mut(&Key { +/// name: "Alice", +/// age: 30, +/// }) { +/// *v = 4; +/// } +/// +/// assert_eq!( +/// Some(&3), +/// ms0.map.get(&Key { +/// name: "Alice", +/// age: 30 +/// }) +/// ); +/// +/// assert_eq!( +/// Some(&4), +/// ms1.map.get(&Key { +/// name: "Alice", +/// age: 30 +/// }) +/// ); +/// } +/// +/// fn main() { +/// variables(); +/// structs(); +/// } +/// ``` +pub use frozen_collections_macros::fz_hash_map; + +/// Creates an efficient set with a fixed set of hashable values. +/// +/// The concrete type used to implement the set is based on an analysis of the input you +/// provide. Although the types vary, they all implement the [`Set`] trait, so refer to the +/// trait for API documentation. +/// +/// # Alternate Choices +/// +/// If your values are integers or enum variants, you should use the [`fz_scalar_set!`] macro instead. +/// If your values are strings, you should use the [`fz_string_set`] macro instead. Both of these will +/// deliver better performance since they are specifically optimized for those value types. +/// +/// # Example +/// +/// ``` +/// use frozen_collections::*; +/// +/// // The value type we use to index into our example sets +/// #[derive(Eq, PartialEq, Hash, Clone, Debug)] +/// struct Key { +/// pub name: &'static str, +/// pub age: i32, +/// } +/// +/// // Declare a global static set. This results in a static variable called MY_SET_0 of type MySetType0. +/// fz_hash_set!(static MY_SET_0: MySetType0 = { +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// fn variables() { +/// // Declare a local static set. This results in a local variable called MY_SET_1 of type MySetType1. +/// fz_hash_set!(static MY_SET_1: MySetType1 = { +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// // Declare a local set. This results in a local variable called my_set_2 of type MySetType2. +/// fz_hash_set!(let my_set_2: MySetType2 = { +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// // Declare a mutable local set. This results in a local variable called my_set_3 of type MySetType3. +/// fz_hash_set!(let mut my_set_3: MySetType3 = { +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// // Declare a local set. This results in a local variable called my_set_4 of an unknown type. +/// let my_set_4 = fz_hash_set!({ +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// let v = vec![ +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// ]; +/// +/// // Declare a local set. This results in a local variable called my_set_5 of type MySetType4. +/// fz_hash_set!(let my_set_5: MySetType4 = v); +/// +/// let v = vec![ +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// ]; +/// +/// // Declare a local set. This results in a local variable called my_set_6 of an unknown type. +/// let my_set_6 = fz_hash_set!(v); +/// +/// // no matter how the sets are declared, no matter the type selected to implement the set, +/// // they all work the same way and have the same API surface and implement the `Set` trait. +/// assert_eq!(MY_SET_0, MY_SET_1); +/// assert_eq!(MY_SET_0, my_set_2); +/// assert_eq!(MY_SET_0, my_set_3); +/// assert_eq!(MY_SET_0, my_set_4); +/// assert_eq!(MY_SET_0, my_set_5); +/// assert_eq!(MY_SET_0, my_set_6); +/// +/// assert!( +/// MY_SET_0.contains(&Key { +/// name: "Alice", +/// age: 30 +/// }) +/// ); +/// +/// assert!( +/// MY_SET_0.contains(&Key { +/// name: "Bob", +/// age: 40 +/// }) +/// ); +/// +/// assert!( +/// !MY_SET_0.contains(&Key { +/// name: "Fred", +/// age: 50 +/// }) +/// ); +/// } +/// +/// fn main() { +/// variables(); +/// } +/// ``` +pub use frozen_collections_macros::fz_hash_set; + +/// Creates an efficient map with a fixed set of ordered keys. +/// +/// The concrete type used to implement the map is based on an analysis of the input you +/// provide. Although the types vary, they all implement the [`Map`] trait, so refer to the +/// trait for API documentation. +/// +/// # Alternate Choices +/// +/// If your keys are integers or enum variants, you should use the [`fz_scalar_map!`] macro instead. +/// If your keys are strings, you should use the [`fz_string_map`] macro instead. Both of these will +/// deliver better performance since they are specifically optimized for those key types. +/// +/// # Example +/// +/// ``` +/// use frozen_collections::*; +/// +/// // The key type we use to index into our example maps +/// #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)] +/// struct Key { +/// pub name: &'static str, +/// pub age: i32, +/// } +/// +/// // Declare a global static map. This results in a static variable called MY_MAP_0 of type MyMapType0. +/// fz_ordered_map!(static MY_MAP_0: MyMapType0 = { +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// fn variables() { +/// // Declare a local static map. This results in a local variable called MY_MAP_1 of type MyMapType1. +/// fz_ordered_map!(static MY_MAP_1: MyMapType1 = { +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// // Declare a local map. This results in a local variable called my_map_2 of type MyMapType2. +/// fz_ordered_map!(let my_map_2: MyMapType2 = { +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// // Declare a mutable local map. This results in a local variable called my_map_3 of type MyMapType3. +/// fz_ordered_map!(let mut my_map_3: MyMapType3 = { +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// // Declare a local map. This results in a local variable called my_map_4 of an unknown type. +/// let my_map_4 = fz_ordered_map!({ +/// Key { name: "Alice", age: 30}: 1, +/// Key { name: "Bob", age: 40}: 2, +/// }); +/// +/// let v = vec![ +/// (Key { name: "Alice", age: 30}, 1), +/// (Key { name: "Bob", age: 40}, 2), +/// ]; +/// +/// // Declare a local map. This results in a local variable called my_map_5 of type MyMapType4. +/// fz_ordered_map!(let my_map_5: MyMapType4 = v); +/// +/// let v = vec![ +/// (Key { name: "Alice", age: 30}, 1), +/// (Key { name: "Bob", age: 40}, 2), +/// ]; +/// +/// // Declare a local map. This results in a local variable called my_map_6 of an unknown type. +/// let my_map_6 = fz_ordered_map!(v); +/// +/// // no matter how the maps are declared, no matter the type selected to implement the map, +/// // they all work the same way and have the same API surface and implement the `Map` trait. +/// assert_eq!(MY_MAP_0, MY_MAP_1); +/// assert_eq!(MY_MAP_0, my_map_2); +/// assert_eq!(MY_MAP_0, my_map_3); +/// assert_eq!(MY_MAP_0, my_map_4); +/// assert_eq!(MY_MAP_0, my_map_5); +/// assert_eq!(MY_MAP_0, my_map_6); +/// +/// assert_eq!( +/// Some(&1), +/// MY_MAP_0.get(&Key { +/// name: "Alice", +/// age: 30 +/// }) +/// ); +/// +/// assert_eq!( +/// Some(&2), +/// MY_MAP_0.get(&Key { +/// name: "Bob", +/// age: 40 +/// }) +/// ); +/// +/// assert_eq!( +/// None, +/// MY_MAP_0.get(&Key { +/// name: "Fred", +/// age: 50 +/// }) +/// ); +/// } +/// +/// // How to embed a map into a struct using the map's type. +/// struct MyStruct0 { +/// map: MyMapType0, +/// } +/// +/// // how to embed a map into a struct using the `Map` trait. +/// struct MyStruct1 +/// where +/// M: Map, +/// { +/// map: M, +/// } +/// +/// // You may need to have specific instances of a map in your process where the keys are the same, but +/// // the values differ. For that case, you can create a static instance with placeholder values. +/// // Then you clone this static instance when needed and set the values that you need in each case. +/// // So you'll have a single set of keys, but different values in each map instance. +/// fn structs() { +/// let mut ms0 = MyStruct0 { +/// map: MY_MAP_0.clone(), +/// }; +/// +/// let mut ms1 = MyStruct1 { +/// map: MY_MAP_0.clone(), +/// }; +/// +/// // set a custom value in ms0 +/// if let Some(v) = ms0.map.get_mut(&Key { +/// name: "Alice", +/// age: 30, +/// }) { +/// *v = 3; +/// } +/// +/// // set a different custom value in ms1 +/// if let Some(v) = ms1.map.get_mut(&Key { +/// name: "Alice", +/// age: 30, +/// }) { +/// *v = 4; +/// } +/// +/// assert_eq!( +/// Some(&3), +/// ms0.map.get(&Key { +/// name: "Alice", +/// age: 30 +/// }) +/// ); +/// +/// assert_eq!( +/// Some(&4), +/// ms1.map.get(&Key { +/// name: "Alice", +/// age: 30 +/// }) +/// ); +/// } +/// +/// fn main() { +/// variables(); +/// structs(); +/// } +/// ``` +pub use frozen_collections_macros::fz_ordered_map; + +/// Creates an efficient set with a fixed set of ordered values. +/// +/// The concrete type used to implement the set is based on an analysis of the input you +/// provide. Although the types vary, they all implement the [`Set`] trait, so refer to the +/// trait for API documentation. +/// +/// # Alternate Choices +/// +/// If your values are integers or enum variants, you should use the [`fz_scalar_set!`] macro instead. +/// If your values are strings, you should use the [`fz_string_set`] macro instead. Both of these will +/// deliver better performance since they are specifically optimized for those value types. +/// +/// # Example +/// +/// ``` +/// use frozen_collections::*; +/// +/// // The value type we use to index into our example sets +/// #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)] +/// struct Key { +/// pub name: &'static str, +/// pub age: i32, +/// } +/// +/// // Declare a global static set. This results in a static variable called MY_SET_0 of type MySetType0. +/// fz_ordered_set!(static MY_SET_0: MySetType0 = { +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// fn variables() { +/// // Declare a local static set. This results in a local variable called MY_SET_1 of type MySetType1. +/// fz_ordered_set!(static MY_SET_1: MySetType1 = { +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// // Declare a local set. This results in a local variable called my_set_2 of type MySetType2. +/// fz_ordered_set!(let my_set_2: MySetType2 = { +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// // Declare a mutable local set. This results in a local variable called my_set_3 of type MySetType3. +/// fz_ordered_set!(let mut my_set_3: MySetType3 = { +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// // Declare a local set. This results in a local variable called my_set_4 of an unknown type. +/// let my_set_4 = fz_ordered_set!({ +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// }); +/// +/// let v = vec![ +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// ]; +/// +/// // Declare a local set. This results in a local variable called my_set_5 of type MySetType4. +/// fz_ordered_set!(let my_set_5: MySetType4 = v); +/// +/// let v = vec![ +/// Key { name: "Alice", age: 30}, +/// Key { name: "Bob", age: 40}, +/// ]; +/// +/// // Declare a local set. This results in a local variable called my_set_6 of an unknown type. +/// let my_set_6 = fz_ordered_set!(v); +/// +/// // no matter how the sets are declared, no matter the type selected to implement the set, +/// // they all work the same way and have the same API surface and implement the `Set` trait. +/// assert_eq!(MY_SET_0, MY_SET_1); +/// assert_eq!(MY_SET_0, my_set_2); +/// assert_eq!(MY_SET_0, my_set_3); +/// assert_eq!(MY_SET_0, my_set_4); +/// assert_eq!(MY_SET_0, my_set_5); +/// assert_eq!(MY_SET_0, my_set_6); +/// +/// assert!( +/// MY_SET_0.contains(&Key { +/// name: "Alice", +/// age: 30 +/// }) +/// ); +/// +/// assert!( +/// MY_SET_0.contains(&Key { +/// name: "Bob", +/// age: 40 +/// }) +/// ); +/// +/// assert!( +/// !MY_SET_0.contains(&Key { +/// name: "Fred", +/// age: 50 +/// }) +/// ); +/// } +/// +/// fn main() { +/// variables(); +/// } +/// ``` +pub use frozen_collections_macros::fz_ordered_set; + +/// Creates an efficient map with a fixed set of scalar keys. +/// +/// The concrete type used to implement the map is based on an analysis of the input you +/// provide. Although the types vary, they all implement the [`Map`] trait, so refer to the +/// trait for API documentation. +/// +/// # Example +/// +/// ``` +/// use frozen_collections::*; +/// +/// // The enum type we use to index into our example maps +/// #[derive(Scalar, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +/// enum Person { +/// Alice, +/// Bob, +/// Fred, +/// } +/// +/// // Declare a global static map. This results in a static variable called MY_MAP_0 of type MyMapType0. +/// fz_scalar_map!(static MY_MAP_0: MyMapType0 = { +/// Person::Alice: 1, +/// Person::Bob: 2, +/// }); +/// +/// // Scalar maps can also be used with any integer type as key. +/// // +/// // This declare a global static map. This results in a static variable called MY_INT_MAP of type MyIntMapType. +/// fz_scalar_map!(static MY_INT_MAP: MyIntMapType = { +/// 10: 1, +/// 20: 2, +/// }); +/// +/// fn variables() { +/// // Declare a local static map. This results in a local variable called MY_MAP_1 of type MyMapType1. +/// fz_scalar_map!(static MY_MAP_1: MyMapType1 = { +/// Person::Alice: 1, +/// Person::Bob: 2, +/// }); +/// +/// // Declare a local map. This results in a local variable called my_map_2 of type MyMapType2. +/// fz_scalar_map!(let my_map_2: MyMapType2 = { +/// Person::Alice: 1, +/// Person::Bob: 2, +/// }); +/// +/// // Declare a mutable local map. This results in a local variable called my_map_3 of type MyMapType3. +/// fz_scalar_map!(let mut my_map_3: MyMapType3 = { +/// Person::Alice: 1, +/// Person::Bob: 2, +/// }); +/// +/// // Declare a local map. This results in a local variable called my_map_4 of an unknown type. +/// let my_map_4 = fz_scalar_map!({ +/// Person::Alice: 1, +/// Person::Bob: 2, +/// }); +/// +/// let v = vec![ +/// (Person::Alice, 1), +/// (Person::Bob, 2), +/// ]; +/// +/// // Declare a local map. This results in a local variable called my_map_5 of type MyMapType4. +/// fz_scalar_map!(let my_map_5: MyMapType4 = v); +/// +/// let v = vec![ +/// (Person::Alice, 1), +/// (Person::Bob, 2), +/// ]; +/// +/// // Declare a local map. This results in a local variable called my_map_6 of an unknown type. +/// let my_map_6 = fz_scalar_map!(v); +/// +/// // no matter how the maps are declared, no matter the type selected to implement the map, +/// // they all work the same way and have the same API surface and implement the `Map` trait. +/// assert_eq!(MY_MAP_0, MY_MAP_1); +/// assert_eq!(MY_MAP_0, my_map_2); +/// assert_eq!(MY_MAP_0, my_map_3); +/// assert_eq!(MY_MAP_0, my_map_4); +/// assert_eq!(MY_MAP_0, my_map_5); +/// assert_eq!(MY_MAP_0, my_map_6); +/// +/// assert_eq!(Some(&1), MY_MAP_0.get(&Person::Alice)); +/// assert_eq!(Some(&2), MY_MAP_0.get(&Person::Bob)); +/// assert_eq!(None, MY_MAP_0.get(&Person::Fred)); +/// } +/// +/// // How to embed a map into a struct using the map's type. +/// struct MyStruct0 { +/// map: MyMapType0, +/// } +/// +/// // how to embed a map into a struct using the `Map` trait. +/// struct MyStruct1 +/// where +/// M: Map, +/// { +/// map: M, +/// } +/// +/// // You may need to have specific instances of a map in your process where the keys are the same, but +/// // the values differ. For that case, you can create a static instance with placeholder values. +/// // Then you clone this static instance when needed and set the values that you need in each case. +/// // So you'll have a single set of keys, but different values in each map instance. +/// fn structs() { +/// let mut ms0 = MyStruct0 { +/// map: MY_MAP_0.clone(), +/// }; +/// +/// let mut ms1 = MyStruct1 { +/// map: MY_MAP_0.clone(), +/// }; +/// +/// // set a custom value in ms0 +/// if let Some(v) = ms0.map.get_mut(&Person::Alice) { +/// *v = 3; +/// } +/// +/// // set a different custom value in ms1 +/// if let Some(v) = ms1.map.get_mut(&Person::Alice) { +/// *v = 4; +/// } +/// +/// assert_eq!( +/// Some(&3), +/// ms0.map.get(&Person::Alice) +/// ); +/// +/// assert_eq!( +/// Some(&4), +/// ms1.map.get(&Person::Alice) +/// ); +/// } +/// +/// fn main() { +/// variables(); +/// structs(); +/// } +/// ``` +pub use frozen_collections_macros::fz_scalar_map; + +/// Creates an efficient set with a fixed set of scalar values. +/// +/// The concrete type used to implement the set is based on an analysis of the input you +/// provide. Although the types vary, they all implement the [`Set`] trait, so refer to the +/// trait for API documentation. +/// +/// # Example +/// +/// ``` +/// use frozen_collections::*; +/// +/// // The enum type we use to index into our example sets +/// #[derive(Scalar, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +/// enum Person { +/// Alice, +/// Bob, +/// Fred, +/// } +/// +/// // Declare a global static set. This results in a static variable called MY_SET_0 of type MySetType0. +/// fz_scalar_set!(static MY_SET_0: MySetType0 = { +/// Person::Alice, +/// Person::Bob, +/// }); +/// +/// // Scalar sets can also be used with any integer type as value. +/// // +/// // This declare a global static set. This results in a static variable called MY_INT_SET of type MyIntSetType. +/// fz_scalar_set!(static MY_INT_SET: MyIntMapType = { +/// 10, +/// 20, +/// }); +/// +/// fn variables() { +/// // Declare a local static set. This results in a local variable called MY_SET_1 of type MySetType1. +/// fz_scalar_set!(static MY_SET_1: MySetType1 = { +/// Person::Alice, +/// Person::Bob, +/// }); +/// +/// // Declare a local set. This results in a local variable called my_set_2 of type MySetType2. +/// fz_scalar_set!(let my_set_2: MySetType2 = { +/// Person::Alice, +/// Person::Bob, +/// }); +/// +/// // Declare a mutable local set. This results in a local variable called my_set_3 of type MySetType3. +/// fz_scalar_set!(let mut my_set_3: MySetType3 = { +/// Person::Alice, +/// Person::Bob, +/// }); +/// +/// // Declare a local set. This results in a local variable called my_set_4 of an unknown type. +/// let my_set_4 = fz_scalar_set!({ +/// Person::Alice, +/// Person::Bob, +/// }); +/// +/// let v = vec![ +/// Person::Alice, +/// Person::Bob, +/// ]; +/// +/// // Declare a local set. This results in a local variable called my_set_5 of type MySetType4. +/// fz_scalar_set!(let my_set_5: MySetType4 = v); +/// +/// let v = vec![ +/// Person::Alice, +/// Person::Bob, +/// ]; +/// +/// // Declare a local set. This results in a local variable called my_set_6 of an unknown type. +/// let my_set_6 = fz_scalar_set!(v); +/// +/// // no matter how the sets are declared, no matter the type selected to implement the set, +/// // they all work the same way and have the same API surface and implement the `Set` trait. +/// assert_eq!(MY_SET_0, MY_SET_1); +/// assert_eq!(MY_SET_0, my_set_2); +/// assert_eq!(MY_SET_0, my_set_3); +/// assert_eq!(MY_SET_0, my_set_4); +/// assert_eq!(MY_SET_0, my_set_5); +/// assert_eq!(MY_SET_0, my_set_6); +/// +/// assert!(MY_SET_0.contains(&Person::Alice)); +/// assert!(MY_SET_0.contains(&Person::Bob)); +/// assert!(!MY_SET_0.contains(&Person::Fred)); +/// } +/// +/// fn main() { +/// variables(); +/// } +/// ``` +pub use frozen_collections_macros::fz_scalar_set; + +/// Creates an efficient map with a fixed set of string keys. +/// +/// The concrete type used to implement the map is based on an analysis of the input you +/// provide. Although the types vary, they all implement the [`Map`] trait, so refer to the +/// trait for API documentation. +/// +/// # Example +/// +/// ``` +/// use frozen_collections::*; +/// +/// // Declare a global static map. This results in a static variable called MY_MAP_0 of type MyMapType0. +/// fz_string_map!(static MY_MAP_0: MyMapType0<&str, i32> = { +/// "Alice": 1, +/// "Bob": 2, +/// }); +/// +/// fn variables() { +/// // Declare a local static map. This results in a local variable called MY_MAP_1 of type MyMapType1. +/// fz_string_map!(static MY_MAP_1: MyMapType1<&str, i32> = { +/// "Alice": 1, +/// "Bob": 2, +/// }); +/// +/// // Declare a local map. This results in a local variable called my_map_2 of type MyMapType2. +/// fz_string_map!(let my_map_2: MyMapType2<&str, i32> = { +/// "Alice": 1, +/// "Bob": 2, +/// }); +/// +/// // Declare a mutable local map. This results in a local variable called my_map_3 of type MyMapType3. +/// fz_string_map!(let mut my_map_3: MyMapType3<&str, i32> = { +/// "Alice": 1, +/// "Bob": 2, +/// }); +/// +/// // Declare a local map. This results in a local variable called my_map_4 of an unknown type. +/// let my_map_4 = fz_string_map!({ +/// "Alice": 1, +/// "Bob": 2, +/// }); +/// +/// let v = vec![ +/// ("Alice".to_string(), 1), +/// ("Bob".to_string(), 2), +/// ]; +/// +/// // Declare a local map. This results in a local variable called my_map_5 of type MyMapType4. +/// fz_string_map!(let my_map_5: MyMapType4<&str, i32> = v); +/// +/// let v = vec![ +/// ("Alice".to_string(), 1), +/// ("Bob".to_string(), 2), +/// ]; +/// +/// // Declare a local map. This results in a local variable called my_map_6 of an unknown type. +/// let my_map_6 = fz_string_map!(v); +/// +/// let _ = my_map_5; +/// let _ = my_map_6; +/// +/// // no matter how the maps are declared, no matter the type selected to implement the map, +/// // they all work the same way and have the same API surface and implement the `Map` trait. +/// assert_eq!(MY_MAP_0, MY_MAP_1); +/// assert_eq!(MY_MAP_0, my_map_2); +/// assert_eq!(MY_MAP_0, my_map_3); +/// assert_eq!(MY_MAP_0, my_map_4); +/// +/// assert_eq!( +/// Some(&1), +/// MY_MAP_0.get("Alice") +/// ); +/// +/// assert_eq!( +/// Some(&2), +/// MY_MAP_0.get("Bob") +/// ); +/// +/// assert_eq!( +/// None, +/// MY_MAP_0.get("Fred") +/// ); +/// } +/// +/// // How to embed a map into a struct using the map's type. +/// struct MyStruct0 { +/// map: MyMapType0, +/// } +/// +/// // how to embed a map into a struct using the `Map` trait. +/// struct MyStruct1 +/// where +/// M: Map<&'static str, i32>, +/// { +/// map: M, +/// } +/// +/// // You may need to have specific instances of a map in your process where the keys are the same, but +/// // the values differ. For that case, you can create a static instance with placeholder values. +/// // Then you clone this static instance when needed and set the values that you need in each case. +/// // So you'll have a single set of keys, but different values in each map instance. +/// fn structs() { +/// let mut ms0 = MyStruct0 { +/// map: MY_MAP_0.clone(), +/// }; +/// +/// let mut ms1 = MyStruct1 { +/// map: MY_MAP_0.clone(), +/// }; +/// +/// // set a custom value in ms0 +/// if let Some(v) = ms0.map.get_mut("Alice") { +/// *v = 3; +/// } +/// +/// // set a different custom value in ms1 +/// if let Some(v) = ms1.map.get_mut("Alice") { +/// *v = 4; +/// } +/// +/// assert_eq!( +/// Some(&3), +/// ms0.map.get(&"Alice") +/// ); +/// +/// assert_eq!( +/// Some(&4), +/// ms1.map.get("Alice") +/// ); +/// } +/// +/// fn main() { +/// variables(); +/// structs(); +/// } +/// ``` +pub use frozen_collections_macros::fz_string_map; + +/// Creates an efficient set with a fixed set of string values. +/// +/// The concrete type used to implement the set is based on an analysis of the input you +/// provide. Although the types vary, they all implement the [`Set`] trait, so refer to the +/// trait for API documentation. +/// +/// # Example +/// +/// ``` +/// use frozen_collections::*; +/// +/// // This example shows the various uses of a frozen set whose values are strings. +/// +/// // Declare a global static set. This results in a static variable called MY_SET_0 of type MySetType0. +/// fz_string_set!(static MY_SET_0: MySetType0<&str> = { +/// "Alice", +/// "Bob", +/// }); +/// +/// fn variables() { +/// // Declare a local static set. This results in a local variable called MY_SET_1 of type MySetType1. +/// fz_string_set!(static MY_SET_1: MySetType1<&str> = { +/// "Alice", +/// "Bob", +/// }); +/// +/// // Declare a local set. This results in a local variable called my_set_2 of type MySetType2. +/// fz_string_set!(let my_set_2: MySetType2<&str> = { +/// "Alice", +/// "Bob", +/// }); +/// +/// // Declare a mutable local set. This results in a local variable called my_set_3 of type MySetType3. +/// fz_string_set!(let mut my_set_3: MySetType3<&str> = { +/// "Alice", +/// "Bob", +/// }); +/// +/// // Declare a local set. This results in a local variable called my_set_4 of an unknown type. +/// let my_set_4 = fz_string_set!({ +/// "Alice", +/// "Bob", +/// }); +/// +/// let v = vec![ +/// "Alice".to_string(), +/// "Bob".to_string(), +/// ]; +/// +/// // Declare a local set. This results in a local variable called my_set_5 of type MySetType4. +/// fz_string_set!(let my_set_5: MySetType4<&str> = v); +/// +/// let v = vec![ +/// "Alice".to_string(), +/// "Bob".to_string(), +/// ]; +/// +/// // Declare a local set. This results in a local variable called my_set_6 of an unknown type. +/// let my_set_6 = fz_string_set!(v); +/// +/// let _ = my_set_5; +/// let _ = my_set_6; +/// +/// // no matter how the sets are declared, no matter the type selected to implement the set, +/// // they all work the same way and have the same API surface and implement the `Set` trait. +/// assert_eq!(MY_SET_0, MY_SET_1); +/// assert_eq!(MY_SET_0, my_set_2); +/// assert_eq!(MY_SET_0, my_set_3); +/// assert_eq!(MY_SET_0, my_set_4); +/// +/// assert!( +/// MY_SET_0.contains("Alice")); +/// +/// assert!( +/// MY_SET_0.contains("Bob") +/// ); +/// +/// assert!( +/// !MY_SET_0.contains("Fred") +/// ); +/// } +/// +/// fn main() { +/// variables(); +/// } +/// ``` +pub use frozen_collections_macros::fz_string_set; + +/// Implements the `Scalar` trait for an enum. +/// +/// Implementing the `Scalar` trait for an enum allows you to use the enum with the [`fz_scalar_map`] +/// and [`fz_scalar_set`] macros. The `Scalar` macro can only be used with enums that only include +/// unit variants without explicit discriminants. +pub use frozen_collections_macros::Scalar; + +#[doc(hidden)] +pub mod sets { + pub use frozen_collections_core::sets::*; +} + +#[doc(hidden)] +pub mod maps { + pub use frozen_collections_core::maps::*; +} + +#[doc(hidden)] +pub mod inline_maps { + pub use frozen_collections_core::inline_maps::*; +} + +#[doc(hidden)] +pub mod inline_sets { + pub use frozen_collections_core::inline_sets::*; +} + +#[doc(hidden)] +pub mod facade_maps { + pub use frozen_collections_core::facade_maps::*; +} + +#[doc(hidden)] +pub mod facade_sets { + pub use frozen_collections_core::facade_sets::*; +} + +#[doc(hidden)] +pub mod hashers { + pub use frozen_collections_core::hashers::*; +} + +#[doc(hidden)] +pub mod hash_tables { + pub use frozen_collections_core::hash_tables::*; +} + +#[doc(hidden)] +pub mod ahash { + pub use ahash::RandomState; +} diff --git a/frozen-collections/tests/common/map_testing.rs b/frozen-collections/tests/common/map_testing.rs new file mode 100644 index 0000000..1f991eb --- /dev/null +++ b/frozen-collections/tests/common/map_testing.rs @@ -0,0 +1,198 @@ +use core::fmt::Debug; +use frozen_collections_core::traits::Map; +use std::collections::HashMap as StdHashMap; +use std::collections::HashSet as StdHashSet; +use std::hash::Hash; +use std::ops::Index; + +pub fn test_map( + map: &MT, + reference: &StdHashMap, + other: &StdHashMap, +) where + K: Hash + Eq + Clone + Debug + Default, + V: Hash + Eq + Clone + Debug + Default, + MT: Map + Debug + Clone + Eq, +{ + assert_same(map, reference); + + let formatted_map = format!("{map:?}"); + for key in map.keys() { + let key_str = format!("{key:?}"); + assert!(formatted_map.contains(&key_str)); + } + + let m2 = map.clone(); + let r2 = reference.clone(); + assert_same(&m2, &r2); + + let v1: StdHashMap = map.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); + let v2: StdHashMap = reference + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + assert_eq!(v1, v2); + + let v1: StdHashMap = map + .clone() + .iter_mut() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + let v2: StdHashMap = reference + .clone() + .iter_mut() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + assert_eq!(v1, v2); + + let v1: StdHashMap = map.clone().into_iter().collect(); + let v2: StdHashMap = reference.clone().into_iter().collect(); + assert_eq!(v1, v2); + + let v1: StdHashSet<&K> = map.keys().collect(); + let v2: StdHashSet<&K> = reference.keys().collect(); + assert_eq!(v1, v2); + + let v1: StdHashSet = map.clone().into_keys().collect(); + let v2: StdHashSet = reference.clone().into_keys().collect(); + assert_eq!(v1, v2); + + let v1: StdHashSet<&V> = map.values().collect(); + let v2: StdHashSet<&V> = reference.values().collect(); + assert_eq!(v1, v2); + + let v1: StdHashSet = map.clone().values_mut().map(|v| v.clone()).collect(); + let v2: StdHashSet = reference.clone().values_mut().map(|v| v.clone()).collect(); + assert_eq!(v1, v2); + + let v1: StdHashSet = map.clone().into_values().collect(); + let v2: StdHashSet = reference.clone().into_values().collect(); + assert_eq!(v1, v2); + + for pair in other { + assert_eq!(map.get(pair.0), reference.get(pair.0)); + assert_eq!(map.get_key_value(pair.0), reference.get_key_value(pair.0)); + assert_eq!( + map.clone().get_mut(pair.0), + reference.clone().get_mut(pair.0) + ); + } + + if map.len() >= 2 { + let keys: Vec<_> = map.keys().collect(); + let mut cloned_map = map.clone(); + let values_from_map = cloned_map.get_many_mut([keys[0], keys[1]]).unwrap(); + + assert_eq!(values_from_map[0], &reference[keys[0]]); + assert_eq!(values_from_map[1], &reference[keys[1]]); + + let mut cloned_map = map.clone(); + let r = cloned_map.get_many_mut([keys[0], keys[0]]); + assert!(r.is_none()); + } +} + +pub fn test_map_default() +where + K: Hash + Eq + Clone + Debug + Default, + MT: Map + Debug + Clone + Default + Eq, +{ + let m = MT::default(); + let r = StdHashMap::default(); + assert_same(&m, &r); + assert!(!m.contains_key(&K::default())); + assert_eq!(0, m.len()); + assert!(m.is_empty()); +} + +pub fn test_map_ops<'a, MT, K, V>(map: &'a MT, reference: &'a StdHashMap) +where + K: 'a + Hash + Eq, + V: 'a + Hash + Eq + Debug, + MT: 'a + + Map + + Debug + + Clone + + PartialEq> + + Index<&'a K, Output = V>, +{ + assert!(map.eq(reference)); + + if !map.is_empty() { + assert!(!map.eq(&StdHashMap::default())); + + for pair in reference { + assert_eq!(&map[pair.0], pair.1); + } + } +} + +pub fn test_map_iter<'a, MT, K, V>(map: &'a MT, reference: &'a StdHashMap) +where + K: 'a + Hash + Eq + Clone + Debug, + V: Eq, + MT: 'a + Map + Debug + Clone, + &'a MT: IntoIterator, +{ + // operates on a &MT + assert_eq!(map.len(), map.iter().count()); + for pair in map.iter() { + assert!(reference.contains_key(pair.0)); + } + + // operates on a &MT + assert_eq!(map.len(), map.into_iter().count()); + for pair in map { + assert!(reference.contains_key(pair.0)); + } + + // operates on a MT + assert_eq!(map.len(), map.clone().into_iter().count()); + for pair in map.clone() { + assert!(map.contains_key(&pair.0)); + } +} + +pub fn test_map_iter_mut<'a, MT, K, V>( + map: &'a mut MT, + reference: &'a StdHashMap, +) where + K: 'a + Hash + Eq + Clone + Debug, + V: Eq, + MT: 'a + Map + Debug + Clone, + &'a mut MT: IntoIterator, +{ + // operates on a &mut MT + for pair in map { + assert!(reference.contains_key(pair.0)); + } +} + +pub fn test_map_empty(map: &MT) +where + MT: Map, + K: Default, +{ + assert_eq!(0, map.len()); + assert!(map.is_empty()); + assert_eq!(0, map.iter().count()); + assert!(!map.contains_key(&K::default())); +} + +fn assert_same(map: &MT, reference: &StdHashMap) +where + K: Hash + Eq + Clone + Debug, + V: Clone + Eq + Debug, + MT: Map + Clone + IntoIterator, +{ + assert_eq!(map.len(), reference.len()); + assert_eq!(map.is_empty(), reference.is_empty()); + + for pairs in reference { + assert!(map.contains_key(pairs.0)); + } + + for pairs in map.iter() { + assert!(reference.contains_key(pairs.0)); + } +} diff --git a/frozen-collections/tests/common/mod.rs b/frozen-collections/tests/common/mod.rs new file mode 100644 index 0000000..1726566 --- /dev/null +++ b/frozen-collections/tests/common/mod.rs @@ -0,0 +1,5 @@ +pub use map_testing::*; +pub use set_testing::*; + +mod map_testing; +mod set_testing; diff --git a/frozen-collections/tests/common/set_testing.rs b/frozen-collections/tests/common/set_testing.rs new file mode 100644 index 0000000..1e5ff42 --- /dev/null +++ b/frozen-collections/tests/common/set_testing.rs @@ -0,0 +1,156 @@ +use core::fmt::Debug; +use core::hash::Hash; +use core::ops::{BitAnd, BitOr, BitXor, Sub}; +use frozen_collections::Set; +use hashbrown::HashSet as HashbrownSet; + +pub fn test_set(set: &ST, reference: &HashbrownSet, other: &HashbrownSet) +where + T: Hash + Eq + Clone + Debug + Default, + ST: Set + Debug + Clone, +{ + assert_same(set, reference); + + let s2: HashbrownSet<&T> = set.symmetric_difference(other).collect(); + let r2: HashbrownSet<&T> = reference.symmetric_difference(other).collect(); + assert_same(&s2, &r2); + + let s2: HashbrownSet<&T> = set.difference(other).collect(); + let r2: HashbrownSet<&T> = reference.difference(other).collect(); + assert_same(&s2, &r2); + + let s2: HashbrownSet<&T> = set.union(other).collect(); + let r2: HashbrownSet<&T> = reference.union(other).collect(); + assert_same(&s2, &r2); + + let s2: HashbrownSet<&T> = set.intersection(other).collect(); + let r2: HashbrownSet<&T> = reference.intersection(other).collect(); + assert_same(&s2, &r2); + + assert_eq!(set.is_disjoint(other), reference.is_disjoint(other)); + assert_eq!(set.is_subset(other), reference.is_subset(other)); + assert_eq!(set.is_superset(other), reference.is_superset(other)); + + let formatted_set = format!("{set:?}"); + for value in set.iter() { + let value_str = format!("{value:?}"); + assert!(formatted_set.contains(&value_str)); + } + + let s2 = set.clone(); + let r2 = reference.clone(); + assert_same(&s2, &r2); + + let s2 = set.clone(); + let mut r2 = reference.clone(); + for value in s2 { + r2.remove(&value); + } + + assert!(r2.is_empty()); +} + +pub fn test_set_default() +where + T: Hash + Eq + Clone + Debug + Default, + ST: Set + Debug + Clone + Default, +{ + let s = ST::default(); + let r = HashbrownSet::default(); + assert_same(&s, &r); + assert!(!s.contains(&T::default())); + assert_eq!(0, s.len()); + assert!(s.is_empty()); +} + +pub fn test_set_ops<'a, ST, T>( + set: &'a ST, + reference: &'a HashbrownSet, + other: &'a HashbrownSet, +) where + T: 'a + Hash + Eq + Clone + Debug, + ST: 'a + Set + Debug + Clone + PartialEq>, + &'a ST: BitOr<&'a HashbrownSet, Output = HashbrownSet> + + BitAnd<&'a HashbrownSet, Output = HashbrownSet> + + BitXor<&'a HashbrownSet, Output = HashbrownSet> + + Sub<&'a HashbrownSet, Output = HashbrownSet>, +{ + assert!(set.eq(reference)); + + if !set.is_empty() { + assert!(!set.eq(&HashbrownSet::default())); + } + + let s2 = set.bitor(other); + let r2 = reference.bitor(other); + assert_eq!(&s2, &r2); + assert!(s2.eq(&r2)); + + let s2 = set.bitand(other); + let r2 = reference.bitand(other); + assert_eq!(s2, r2); + assert!(s2.eq(&r2)); + + let s2 = set.bitxor(other); + let r2 = reference.bitxor(other); + assert_eq!(s2, r2); + assert!(s2.eq(&r2)); + + let s2 = set.sub(other); + let r2 = reference.sub(other); + assert_eq!(s2, r2); + assert!(s2.eq(&r2)); +} + +pub fn test_set_iter<'a, ST, T>(set: &'a ST, reference: &'a HashbrownSet) +where + T: 'a + Hash + Eq + Clone + Debug, + ST: 'a + Set + Debug + Clone, + &'a ST: IntoIterator, +{ + // operates on a &ST + assert_eq!(set.len(), set.iter().count()); + for v in set.iter() { + assert!(reference.contains(v)); + } + + // operates on a &ST + assert_eq!(set.len(), set.into_iter().count()); + for v in set { + assert!(reference.contains(v)); + } + + // operates on a ST + assert_eq!(set.len(), set.clone().into_iter().count()); + for v in set.clone() { + assert!(set.contains(&v)); + } +} + +pub fn test_set_empty(set: &ST) +where + ST: Set, + T: Default, +{ + assert_eq!(0, set.len()); + assert!(set.is_empty()); + assert_eq!(0, set.iter().count()); + assert!(!set.contains(&T::default())); +} + +fn assert_same(set: &ST, reference: &HashbrownSet) +where + T: Hash + Eq + Clone, + ST: Set + Clone, +{ + assert_eq!(set.len(), reference.len()); + assert_eq!(set.is_empty(), reference.is_empty()); + + for value in reference { + assert!(set.contains(value)); + } + + for value in set.iter() { + assert!(reference.contains(value)); + } +} diff --git a/frozen-collections/tests/derive_scalar_macro_tests.rs b/frozen-collections/tests/derive_scalar_macro_tests.rs new file mode 100644 index 0000000..14e5e1d --- /dev/null +++ b/frozen-collections/tests/derive_scalar_macro_tests.rs @@ -0,0 +1,26 @@ +use frozen_collections::*; +use frozen_collections_core::macros::derive_scalar_macro; +use quote::quote; + +#[derive(Scalar, Copy, Ord, PartialOrd, Eq, PartialEq, Clone)] +enum Color { + Red, + Green, + Blue, +} + +#[test] +fn test_derive_scalar() { + _ = derive_scalar_macro(quote!( + enum Color { + Red, + Green, + Blue, + } + )) + .unwrap(); + + assert_eq!(0, Color::index(&Color::Red)); + assert_eq!(1, Color::index(&Color::Green)); + assert_eq!(2, Color::index(&Color::Blue)); +} diff --git a/frozen-collections/tests/hash_macro_tests.rs b/frozen-collections/tests/hash_macro_tests.rs new file mode 100644 index 0000000..d426877 --- /dev/null +++ b/frozen-collections/tests/hash_macro_tests.rs @@ -0,0 +1,484 @@ +use frozen_collections::*; +use frozen_collections_core::macros::{fz_hash_map_macro, fz_hash_set_macro}; +use quote::quote; +use std::collections::HashMap as StdHashMap; +use std::collections::HashSet as StdHashSet; + +#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] +struct Person { + name: &'static str, + age: i32, +} + +macro_rules! test_hash { + ( $type:ty, $( $arg:expr ),* $(,)?) => { + { + _ = fz_hash_set_macro(quote!({ + $( + $arg, + )* + })).unwrap(); + + let s0 = fz_hash_set!({ + $( + $arg, + )* + }); + + let v = vec![ + $( + $arg, + )* + ]; + + _ = fz_hash_set_macro(quote!(v)).unwrap(); + + let s1 = fz_hash_set!(v); + + let v = vec![ + $( + $arg, + )* + ]; + + let mut s2 = StdHashSet::new(); + for x in v.into_iter() { + s2.insert(x); + } + + _ = fz_hash_set_macro(quote!(static _S3: Foo< $type > = { + $( + $arg, + )* + })).unwrap(); + + fz_hash_set!(static _S3: Foo< $type > = { + $( + $arg, + )* + }); + + _ = fz_hash_set_macro(quote!(let s4: Bar< $type > = { + $( + $arg, + )* + })).unwrap(); + + fz_hash_set!(let s4: Bar< $type > = { + $( + $arg, + )* + }); + + assert_eq!(s0, s1); + assert_eq!(s0, s2); + // assert_eq!(s0, S3); + assert_eq!(s0, s4); + } + + { + _ = fz_hash_map_macro(quote!({ + $( + $arg:42, + )* + })).unwrap(); + + let m0 = fz_hash_map!({ + $( + $arg: 42, + )* + }); + + let v = vec![ + $( + ($arg, 42), + )* + ]; + + _ = fz_hash_map_macro(quote!(v)).unwrap(); + + let m1 = fz_hash_map!(v); + + let v = vec![ + $( + ($arg, 42), + )* + ]; + + let mut m2 = StdHashMap::new(); + for x in v.into_iter() { + m2.insert(x.0, x.1); + } + + _ = fz_hash_map_macro(quote!(static _M3: Foo< $type, i32 > = { + $( + $arg: 42, + )* + })).unwrap(); + + fz_hash_map!(static _M3: Foo< $type, i32 > = { + $( + $arg: 42, + )* + }); + + _ = fz_hash_map_macro(quote!(let m4: Bar< $type, i32 > = { + $( + $arg: 42, + )* + })).unwrap(); + + fz_hash_map!(let m4: Bar< $type, i32 > = { + $( + $arg: 42, + )* + }); + + assert_eq!(m0, m1); + assert_eq!(m0, m2); + // assert_eq!(m0, M3); + assert_eq!(m0, m4); + } + } +} + +#[test] +fn hash_complex() { + test_hash!(Person, Person { name: "A", age: 1 },); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + Person { name: "J", age: 10 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + Person { name: "J", age: 10 }, + Person { name: "K", age: 11 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + Person { name: "J", age: 10 }, + Person { name: "K", age: 11 }, + Person { name: "L", age: 12 }, + ); + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + Person { name: "J", age: 10 }, + Person { name: "K", age: 11 }, + Person { name: "L", age: 12 }, + Person { name: "M", age: 13 }, + ); + + // test duplicate logic + test_hash!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "A", age: 3 }, + Person { name: "A", age: 4 }, + ); +} + +#[test] +fn hash_i8() { + test_hash!(i8, 0i8); + test_hash!(i8, 0i8, 1,); + test_hash!(i8, 0i8, 1, 2,); + test_hash!(i8, 0i8, 1, 2, 3,); + test_hash!(i8, 0i8, 1, 2, 3, 4,); + test_hash!(i8, 0i8, 1, 2, 3, 4, 5,); + test_hash!(i8, 0i8, 1, 2, 3, 4, 5, 6,); + test_hash!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); + + // test duplicate logic + test_hash!(i8, 0i8, 1, 2, 1, 1); +} + +#[test] +fn hash_u8() { + test_hash!(u8, 0u8); + test_hash!(u8, 0u8, 1,); + test_hash!(u8, 0u8, 1, 2,); + test_hash!(u8, 0u8, 1, 2, 3,); + test_hash!(u8, 0u8, 1, 2, 3, 4,); + test_hash!(u8, 0u8, 1, 2, 3, 4, 5,); + test_hash!(u8, 0u8, 1, 2, 3, 4, 5, 6,); + test_hash!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn hash_i16() { + test_hash!(i16, 0i16); + test_hash!(i16, 0i16, 1,); + test_hash!(i16, 0i16, 1, 2,); + test_hash!(i16, 0i16, 1, 2, 3,); + test_hash!(i16, 0i16, 1, 2, 3, 4,); + test_hash!(i16, 0i16, 1, 2, 3, 4, 5,); + test_hash!(i16, 0i16, 1, 2, 3, 4, 5, 6,); + test_hash!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn hash_u16() { + test_hash!(u16, 0u16); + test_hash!(u16, 0u16, 1,); + test_hash!(u16, 0u16, 1, 2,); + test_hash!(u16, 0u16, 1, 2, 3,); + test_hash!(u16, 0u16, 1, 2, 3, 4,); + test_hash!(u16, 0u16, 1, 2, 3, 4, 5,); + test_hash!(u16, 0u16, 1, 2, 3, 4, 5, 6,); + test_hash!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn hash_i32() { + test_hash!(i32, 0i32); + test_hash!(i32, 0i32, 1,); + test_hash!(i32, 0i32, 1, 2,); + test_hash!(i32, 0i32, 1, 2, 3,); + test_hash!(i32, 0i32, 1, 2, 3, 4,); + test_hash!(i32, 0i32, 1, 2, 3, 4, 5,); + test_hash!(i32, 0i32, 1, 2, 3, 4, 5, 6,); + test_hash!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn hash_u32() { + test_hash!(u32, 0u32); + test_hash!(u32, 0u32, 1,); + test_hash!(u32, 0u32, 1, 2,); + test_hash!(u32, 0u32, 1, 2, 3,); + test_hash!(u32, 0u32, 1, 2, 3, 4,); + test_hash!(u32, 0u32, 1, 2, 3, 4, 5,); + test_hash!(u32, 0u32, 1, 2, 3, 4, 5, 6,); + test_hash!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn hash_i64() { + test_hash!(i64, 0i64); + test_hash!(i64, 0i64, 1,); + test_hash!(i64, 0i64, 1, 2,); + test_hash!(i64, 0i64, 1, 2, 3,); + test_hash!(i64, 0i64, 1, 2, 3, 4,); + test_hash!(i64, 0i64, 1, 2, 3, 4, 5,); + test_hash!(i64, 0i64, 1, 2, 3, 4, 5, 6,); + test_hash!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn hash_u64() { + test_hash!(u64, 0u64); + test_hash!(u64, 0u64, 1,); + test_hash!(u64, 0u64, 1, 2,); + test_hash!(u64, 0u64, 1, 2, 3,); + test_hash!(u64, 0u64, 1, 2, 3, 4,); + test_hash!(u64, 0u64, 1, 2, 3, 4, 5,); + test_hash!(u64, 0u64, 1, 2, 3, 4, 5, 6,); + test_hash!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn hash_isize() { + test_hash!(isize, 0isize); + test_hash!(isize, 0isize, 1,); + test_hash!(isize, 0isize, 1, 2,); + test_hash!(isize, 0isize, 1, 2, 3,); + test_hash!(isize, 0isize, 1, 2, 3, 4,); + test_hash!(isize, 0isize, 1, 2, 3, 4, 5,); + test_hash!(isize, 0isize, 1, 2, 3, 4, 5, 6,); + test_hash!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn hash_usize() { + test_hash!(usize, 0usize); + test_hash!(usize, 0usize, 1,); + test_hash!(usize, 0usize, 1, 2,); + test_hash!(usize, 0usize, 1, 2, 3,); + test_hash!(usize, 0usize, 1, 2, 3, 4,); + test_hash!(usize, 0usize, 1, 2, 3, 4, 5,); + test_hash!(usize, 0usize, 1, 2, 3, 4, 5, 6,); + test_hash!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7,); + test_hash!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8,); + test_hash!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_hash!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_hash!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_hash!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_hash!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn hash_string() { + test_hash!(&str, "0"); + test_hash!(&str, "0", "1"); + test_hash!(&str, "0", "1", "2"); + test_hash!(&str, "0", "1", "2", "3"); + test_hash!(&str, "0", "1", "2", "3", "4"); + test_hash!(&str, "0", "1", "2", "3", "4", "5"); + test_hash!(&str, "0", "1", "2", "3", "4", "5", "6"); + test_hash!(&str, "0", "1", "2", "3", "4", "5", "6", "7"); + test_hash!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8"); + test_hash!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + test_hash!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); + test_hash!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"); + test_hash!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"); + test_hash!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"); +} diff --git a/frozen-collections/tests/omni_tests.rs b/frozen-collections/tests/omni_tests.rs new file mode 100644 index 0000000..1c1d4b1 --- /dev/null +++ b/frozen-collections/tests/omni_tests.rs @@ -0,0 +1,404 @@ +mod common; + +use common::*; +use frozen_collections::*; +use frozen_collections_core::facade_maps::*; +use frozen_collections_core::facade_sets::*; +use frozen_collections_core::hashers::BridgeHasher; +use frozen_collections_core::maps::*; +use frozen_collections_core::sets::*; +use hashbrown::HashSet as HashbrownSet; +use std::collections::HashMap as StdHashMap; + +macro_rules! test_all { + ( $( $input:expr ),* ; $( $other:literal ),*) => { + let set_reference = HashbrownSet::from_iter(vec![ $( $input, )* ].into_iter()); + let set_other = HashbrownSet::from_iter(vec![ $( $other, )* ].into_iter()); + let set_input = vec![ $($input,)* ]; + + let map_reference = StdHashMap::<_, _, ahash::RandomState>::from_iter(vec![ $( ($input, ()), )* ].into_iter()); + let map_other = StdHashMap::<_, _, ahash::RandomState>::from_iter(vec![ $( ($other, ()), )* ].into_iter()); + let map_input = vec![ $( ($input, ()), )* ]; + + let mut m = fz_scalar_map!({ $( $input: (), )* }); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = fz_scalar_set!({ $($input,)* }); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = fz_scalar_map!(map_input.clone()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = fz_scalar_set!(set_input.clone()); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = fz_hash_map!({ $( $input: (), )* }); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = fz_hash_set!({ $($input,)* }); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = fz_hash_map!(map_input.clone()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = fz_hash_set!(set_input.clone()); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = fz_ordered_map!({ $( $input: (), )* }); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = fz_ordered_set!({ $($input,)* }); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = fz_ordered_map!(map_input.clone()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = fz_ordered_set!(set_input.clone()); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = BinarySearchMap::new(map_input.clone()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = BinarySearchSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = OrderedScanMap::new(map_input.clone()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = OrderedScanSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = ScanMap::new(map_input.clone()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = ScanSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + if let Ok(mut m) = DenseScalarLookupMap::new(map_input.clone()) { + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = DenseScalarLookupSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + } + + if let Ok(mut m) = SparseScalarLookupMap::<_, _>::new(map_input.clone()) { + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = SparseScalarLookupSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + } + + if let Ok(mut m) = HashMap::<_, _>::new(map_input.clone(), BridgeHasher::default()) { + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = HashSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + } + + let mut m = FacadeOrderedMap::new(map_input.clone()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = FacadeOrderedSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = FacadeScalarMap::new(map_input.clone()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = FacadeScalarSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = FacadeHashMap::<_, _>::new(map_input.clone(), BridgeHasher::default()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = FacadeHashSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let m = std::collections::HashMap::<_, _, ahash::RandomState>::from_iter(map_input.clone().into_iter()); + test_map(&m, &map_reference, &map_other); + + let s = std::collections::HashSet::<_, ahash::RandomState>::from_iter(set_input.clone().into_iter()); + test_set(&s, &set_reference, &set_other); + + let m = std::collections::BTreeMap::from_iter(map_input.clone().into_iter()); + test_map(&m, &map_reference, &map_other); + + let s = std::collections::BTreeSet::from_iter(set_input.clone().into_iter()); + test_set(&s, &set_reference, &set_other); + + let m = hashbrown::HashMap::<_, _, ahash::RandomState>::from_iter(map_input.clone().into_iter()); + test_map(&m, &map_reference, &map_other); + + let s = hashbrown::HashSet::<_, ahash::RandomState>::from_iter(set_input.clone().into_iter()); + test_set(&s, &set_reference, &set_other); + + // handle 8str cases + + let set_reference = HashbrownSet::<&str>::from_iter(vec![ $( stringify!($input), )* ].into_iter()); + let set_other = HashbrownSet::<&str>::from_iter(vec![ $( stringify!($other), )* ].into_iter()); + + let map_reference = StdHashMap::<_, _, ahash::RandomState>::from_iter(vec![ $( (stringify!($input), ()), )* ].into_iter()); + let map_other = StdHashMap::<_, _, ahash::RandomState>::from_iter(vec![ $( (stringify!($other), ()), )* ].into_iter()); + + let mut m = fz_string_map!({ $( stringify!($input): (),)* }); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = fz_string_set!({ $( stringify!($input),)* }); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + // handle String cases + + let set_reference = HashbrownSet::::from_iter(vec![ $( $input.to_string(), )* ].into_iter()); + let set_other = HashbrownSet::::from_iter(vec![ $( $other.to_string(), )* ].into_iter()); + let set_input = vec![ $( $input.to_string(), )* ]; + + let map_reference = StdHashMap::<_, _, ahash::RandomState>::from_iter(vec![ $( ($input.to_string(), ()), )* ].into_iter()); + let map_other = StdHashMap::<_, _, ahash::RandomState>::from_iter(vec![ $( ($other.to_string(), ()), )* ].into_iter()); + let map_input = vec![ $( ($input.to_string(), ()), )* ]; + + let mut m = fz_string_map!(map_input.clone()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = fz_string_set!(set_input.clone()); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + + let mut m = FacadeStringMap::new(map_input.clone(), ahash::RandomState::default()); + test_map(&m, &map_reference, &map_other); + test_map_ops(&m, &map_reference); + test_map_iter(&m, &map_reference); + test_map_iter_mut(&mut m, &map_reference); + + let s = FacadeStringSet::new(m); + test_set(&s, &set_reference, &set_other); + test_set_ops(&s, &set_reference, &set_other); + test_set_iter(&s, &set_reference); + } +} + +#[test] +fn test_common() { + test_all!(1, 2, 3 ; 3, 4, 5); + test_all!(0, 1 ; 0, 1); + test_all!(3, 1, 2, 3, 3 ; 3, 4, 5); + test_all!(1, 2, 3 ; 1, 2, 3, 4, 5); + test_all!(1, 2, 3 ; 1, 2); + test_all!(1, 2, 3 ; 2); + test_all!(1, 2, 4 ; 2); + test_all!(1, 2, 4 ; 3); + test_all!(1, 2, 4, 1500 ; 3); + test_all!(1, 2, 4, 1500 ; 2500); + + test_all!(1 ; 3); + test_all!(1, 2 ; 3); + test_all!(1, 2, 3 ; 3); + test_all!(1, 2, 3, 4 ; 3); + test_all!(1, 2, 3, 4, 5 ; 3); + test_all!(1, 2, 3, 4, 5, 6 ; 3); + test_all!(1, 2, 3, 4, 5, 6, 7 ; 3, 5); + test_all!(1, 2, 3, 4, 5, 6, 7, 8 ; 3); + test_all!(1, 2, 3, 4, 5, 6, 7, 8, 9 ; 3, 10); + test_all!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ; 3); + + test_all!(11_111, 11_112, 11_114, 11_115, 111_165, 111_175 ; 2_500, 333_333_333); + + let i = 1; + test_all!(i, 2, 3, 4, 5, 6, 7, 8, 9, 10 ; 3); +} + +#[test] +fn test_set_defaults() { + test_set_default::, i32>(); + test_set_default::, i32>(); + test_set_default::, i32>(); + test_set_default::, i32>(); + test_set_default::, i32>(); + test_set_default::, i32>(); + + test_set_default::, i32>(); + test_set_default::, i32>(); + test_set_default::, i32>(); + test_set_default::(); +} + +#[test] +fn test_map_defaults() { + test_map_default::, i32>(); + test_map_default::, i32>(); + test_map_default::, i32>(); + test_map_default::, i32>(); + test_map_default::, i32>(); + test_map_default::, i32>(); + + test_map_default::, i32>(); + test_map_default::, i32>(); + test_map_default::, i32>(); + test_map_default::, String>(); +} + +#[test] +fn test_set_empties() { + test_set_empty(&BinarySearchSet::::default()); + test_set_empty(&BinarySearchSet::::new(BinarySearchMap::new(vec![]))); + + test_set_empty(&OrderedScanSet::::default()); + test_set_empty(&OrderedScanSet::::new(OrderedScanMap::new(vec![]))); + + test_set_empty(&ScanSet::::default()); + test_set_empty(&ScanSet::::new(ScanMap::new(vec![]))); + + test_set_empty(&DenseScalarLookupSet::::default()); + test_set_empty(&DenseScalarLookupSet::::new( + DenseScalarLookupMap::new(vec![]).unwrap(), + )); + + test_set_empty(&SparseScalarLookupSet::::default()); + test_set_empty(&SparseScalarLookupSet::::new( + SparseScalarLookupMap::new(vec![]).unwrap(), + )); + + test_set_empty(&HashSet::::default()); + test_set_empty(&HashSet::::new( + HashMap::new(vec![], BridgeHasher::default()).unwrap(), + )); + + test_set_empty(&FacadeHashSet::::default()); + test_set_empty(&FacadeHashSet::::new(FacadeHashMap::new( + vec![], + BridgeHasher::default(), + ))); + + test_set_empty(&FacadeOrderedSet::::default()); + test_set_empty(&FacadeOrderedSet::::new(FacadeOrderedMap::new(vec![]))); + + test_set_empty(&FacadeScalarSet::::default()); + test_set_empty(&FacadeScalarSet::::new(FacadeScalarMap::new(vec![]))); + + test_set_empty(&FacadeStringSet::::default()); + test_set_empty(&FacadeStringSet::new(FacadeStringMap::new( + vec![], + ahash::RandomState::default(), + ))); +} + +#[test] +fn test_map_empties() { + test_map_empty(&BinarySearchMap::::default()); + test_map_empty(&BinarySearchMap::::new(vec![])); + + test_map_empty(&OrderedScanMap::::default()); + test_map_empty(&OrderedScanMap::::new(vec![])); + + test_map_empty(&ScanMap::::default()); + test_map_empty(&ScanMap::::new(vec![])); + + test_map_empty(&DenseScalarLookupMap::::default()); + test_map_empty(&DenseScalarLookupMap::::new(vec![]).unwrap()); + + test_map_empty(&SparseScalarLookupMap::::default()); + test_map_empty(&SparseScalarLookupMap::::new(vec![]).unwrap()); + + test_map_empty(&HashMap::::default()); + test_map_empty(&HashMap::::new(vec![], BridgeHasher::default()).unwrap()); + + test_map_empty(&FacadeHashMap::::default()); + test_map_empty(&FacadeHashMap::::new( + vec![], + BridgeHasher::default(), + )); + + test_map_empty(&FacadeOrderedMap::::default()); + test_map_empty(&FacadeOrderedMap::::new(vec![])); + + test_map_empty(&FacadeScalarMap::::default()); + test_map_empty(&FacadeScalarMap::::new(vec![])); + + test_map_empty(&FacadeStringMap::::default()); + test_map_empty(&FacadeStringMap::::new( + vec![], + ahash::RandomState::default(), + )); +} diff --git a/frozen-collections/tests/ordered_macro_tests.rs b/frozen-collections/tests/ordered_macro_tests.rs new file mode 100644 index 0000000..fdbfd14 --- /dev/null +++ b/frozen-collections/tests/ordered_macro_tests.rs @@ -0,0 +1,448 @@ +use frozen_collections::*; +use frozen_collections_core::macros::{fz_ordered_map_macro, fz_ordered_set_macro}; +use quote::quote; +use std::collections::BTreeMap as StdBTreeMap; +use std::collections::BTreeSet as StdBTreeSet; + +#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] +struct Person { + name: &'static str, + age: i32, +} + +macro_rules! test_ordered { + ( $type:ty, $( $arg:expr ),* $(,)?) => { + { + _ = fz_ordered_set_macro(quote!({ + $( + $arg, + )* + })).unwrap(); + + let s0 = fz_ordered_set!({ + $( + $arg, + )* + }); + + let v = vec![ + $( + $arg, + )* + ]; + + _ = fz_ordered_set_macro(quote!(v)).unwrap(); + + let s1 = fz_ordered_set!(v); + + let v = vec![ + $( + $arg, + )* + ]; + + let mut s2 = StdBTreeSet::new(); + for x in v.into_iter() { + s2.insert(x); + } + + _ = fz_ordered_set_macro(quote!(static _S3: Foo< $type > = { + $( + $arg, + )* + })).unwrap(); + + fz_ordered_set!(static _S3: Foo< $type > = { + $( + $arg, + )* + }); + + _ = fz_ordered_set_macro(quote!(let s4: Bar< $type > = { + $( + $arg, + )* + })).unwrap(); + + fz_ordered_set!(let s4: Bar< $type > = { + $( + $arg, + )* + }); + + assert_eq!(s0, s1); + assert_eq!(s0, s2); + // assert_eq!(s0, S3); + assert_eq!(s0, s4); + } + + { + _ = fz_ordered_map_macro(quote!({ + $( + $arg: 42, + )* + })).unwrap(); + + let m0 = fz_ordered_map!({ + $( + $arg: 42, + )* + }); + + let v = vec![ + $( + ($arg, 42), + )* + ]; + + _ = fz_ordered_map_macro(quote!(v)).unwrap(); + + let m1 = fz_ordered_map!(v); + + let v = vec![ + $( + ($arg, 42), + )* + ]; + + let mut m2 = StdBTreeMap::new(); + for x in v.into_iter() { + m2.insert(x.0, x.1); + } + + _ = fz_ordered_map_macro(quote!(static _M3: Foo< $type, i32 > = { + $( + $arg: 42, + )* + })).unwrap(); + + fz_ordered_map!(static _M3: Foo< $type, i32 > = { + $( + $arg: 42, + )* + }); + + _ = fz_ordered_map_macro(quote!(let m4: Bar< $type, i32 > = { + $( + $arg: 42, + )* + })).unwrap(); + + fz_ordered_map!(let m4: Bar< $type, i32 > = { + $( + $arg: 42, + )* + }); + + assert_eq!(m0, m1); + assert_eq!(m0, m2); + // assert_eq!(m0, M3); + assert_eq!(m0, m4); + } + } +} + +#[test] +fn ordered_complex() { + test_ordered!(Person, Person { name: "A", age: 1 },); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + Person { name: "J", age: 10 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + Person { name: "J", age: 10 }, + Person { name: "K", age: 11 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + Person { name: "J", age: 10 }, + Person { name: "K", age: 11 }, + Person { name: "L", age: 12 }, + ); + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "C", age: 3 }, + Person { name: "D", age: 4 }, + Person { name: "E", age: 5 }, + Person { name: "F", age: 6 }, + Person { name: "G", age: 7 }, + Person { name: "H", age: 8 }, + Person { name: "I", age: 9 }, + Person { name: "J", age: 10 }, + Person { name: "K", age: 11 }, + Person { name: "L", age: 12 }, + Person { name: "M", age: 13 }, + ); + + // test duplicate logic + test_ordered!( + Person, + Person { name: "A", age: 1 }, + Person { name: "B", age: 2 }, + Person { name: "A", age: 3 }, + Person { name: "A", age: 4 }, + ); +} + +#[test] +fn ordered_i8() { + test_ordered!(i8, 0i8); + test_ordered!(i8, 0i8, 1,); + test_ordered!(i8, 0i8, 1, 2,); + test_ordered!(i8, 0i8, 1, 2, 3,); + test_ordered!(i8, 0i8, 1, 2, 3, 4,); + test_ordered!(i8, 0i8, 1, 2, 3, 4, 5,); + test_ordered!(i8, 0i8, 1, 2, 3, 4, 5, 6,); + test_ordered!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7,); + test_ordered!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8,); + test_ordered!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_ordered!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_ordered!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_ordered!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_ordered!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); + + // test duplicate logic + test_ordered!(i8, 0i8, 1, 2, 1, 1); +} + +#[test] +fn ordered_u8() { + test_ordered!(u8, 0u8); + test_ordered!(u8, 0u8, 1,); + test_ordered!(u8, 0u8, 1, 2,); + test_ordered!(u8, 0u8, 1, 2, 3,); + test_ordered!(u8, 0u8, 1, 2, 3, 4,); + test_ordered!(u8, 0u8, 1, 2, 3, 4, 5,); + test_ordered!(u8, 0u8, 1, 2, 3, 4, 5, 6,); + test_ordered!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7,); + test_ordered!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8,); + test_ordered!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_ordered!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_ordered!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_ordered!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_ordered!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn ordered_i16() { + test_ordered!(i16, 0i16); + test_ordered!(i16, 0i16, 1,); + test_ordered!(i16, 0i16, 1, 2,); + test_ordered!(i16, 0i16, 1, 2, 3,); + test_ordered!(i16, 0i16, 1, 2, 3, 4,); + test_ordered!(i16, 0i16, 1, 2, 3, 4, 5,); + test_ordered!(i16, 0i16, 1, 2, 3, 4, 5, 6,); + test_ordered!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7,); + test_ordered!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8,); + test_ordered!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_ordered!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_ordered!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_ordered!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_ordered!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn ordered_u16() { + test_ordered!(u16, 0u16); + test_ordered!(u16, 0u16, 1,); + test_ordered!(u16, 0u16, 1, 2,); + test_ordered!(u16, 0u16, 1, 2, 3,); + test_ordered!(u16, 0u16, 1, 2, 3, 4,); + test_ordered!(u16, 0u16, 1, 2, 3, 4, 5,); + test_ordered!(u16, 0u16, 1, 2, 3, 4, 5, 6,); + test_ordered!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7,); + test_ordered!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8,); + test_ordered!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_ordered!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_ordered!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_ordered!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_ordered!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn ordered_i32() { + test_ordered!(i32, 0i32); + test_ordered!(i32, 0i32, 1,); + test_ordered!(i32, 0i32, 1, 2,); + test_ordered!(i32, 0i32, 1, 2, 3,); + test_ordered!(i32, 0i32, 1, 2, 3, 4,); + test_ordered!(i32, 0i32, 1, 2, 3, 4, 5,); + test_ordered!(i32, 0i32, 1, 2, 3, 4, 5, 6,); + test_ordered!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7,); + test_ordered!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8,); + test_ordered!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_ordered!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_ordered!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_ordered!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_ordered!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn ordered_u32() { + test_ordered!(u32, 0u32); + test_ordered!(u32, 0u32, 1,); + test_ordered!(u32, 0u32, 1, 2,); + test_ordered!(u32, 0u32, 1, 2, 3,); + test_ordered!(u32, 0u32, 1, 2, 3, 4,); + test_ordered!(u32, 0u32, 1, 2, 3, 4, 5,); + test_ordered!(u32, 0u32, 1, 2, 3, 4, 5, 6,); + test_ordered!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7,); + test_ordered!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8,); + test_ordered!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_ordered!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_ordered!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_ordered!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_ordered!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn ordered_i64() { + test_ordered!(i64, 0i64); + test_ordered!(i64, 0i64, 1,); + test_ordered!(i64, 0i64, 1, 2,); + test_ordered!(i64, 0i64, 1, 2, 3,); + test_ordered!(i64, 0i64, 1, 2, 3, 4,); + test_ordered!(i64, 0i64, 1, 2, 3, 4, 5,); + test_ordered!(i64, 0i64, 1, 2, 3, 4, 5, 6,); + test_ordered!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7,); + test_ordered!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8,); + test_ordered!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_ordered!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_ordered!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_ordered!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_ordered!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn ordered_u64() { + test_ordered!(u64, 0u64); + test_ordered!(u64, 0u64, 1,); + test_ordered!(u64, 0u64, 1, 2,); + test_ordered!(u64, 0u64, 1, 2, 3,); + test_ordered!(u64, 0u64, 1, 2, 3, 4,); + test_ordered!(u64, 0u64, 1, 2, 3, 4, 5,); + test_ordered!(u64, 0u64, 1, 2, 3, 4, 5, 6,); + test_ordered!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7,); + test_ordered!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8,); + test_ordered!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_ordered!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_ordered!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_ordered!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_ordered!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn ordered_string() { + test_ordered!(&str, "0"); + test_ordered!(&str, "0", "1"); + test_ordered!(&str, "0", "1", "2"); + test_ordered!(&str, "0", "1", "2", "3"); + test_ordered!(&str, "0", "1", "2", "3", "4"); + test_ordered!(&str, "0", "1", "2", "3", "4", "5"); + test_ordered!(&str, "0", "1", "2", "3", "4", "5", "6"); + test_ordered!(&str, "0", "1", "2", "3", "4", "5", "6", "7"); + test_ordered!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8"); + test_ordered!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + test_ordered!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); + test_ordered!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"); + test_ordered!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"); + test_ordered!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"); +} diff --git a/frozen-collections/tests/scalar_macro_tests.rs b/frozen-collections/tests/scalar_macro_tests.rs new file mode 100644 index 0000000..fe4d462 --- /dev/null +++ b/frozen-collections/tests/scalar_macro_tests.rs @@ -0,0 +1,345 @@ +use frozen_collections::*; +use frozen_collections_core::macros::{fz_scalar_map_macro, fz_scalar_set_macro}; +use quote::quote; +use std::collections::BTreeMap as StdBTreeMap; +use std::collections::BTreeSet as StdBTreeSet; + +macro_rules! test_scalar { + ( $type:ty, $( $arg:expr ),* $(,)?) => { + { + _ = fz_scalar_set_macro(quote!({ + $( + $arg, + )* + })).unwrap(); + + let s0 = fz_scalar_set!({ + $( + $arg, + )* + }); + + let v = vec![ + $( + $arg, + )* + ]; + + _ = fz_scalar_set_macro(quote!(v)).unwrap(); + + let s1 = fz_scalar_set!(v); + + let v = vec![ + $( + $arg, + )* + ]; + + let mut s2 = StdBTreeSet::new(); + for x in v.into_iter() { + s2.insert(x); + } + + _ = fz_scalar_set_macro(quote!(static _S3: Foo< $type > = { + $( + $arg, + )* + })).unwrap(); + + fz_scalar_set!(static _S3: Foo< $type > = { + $( + $arg, + )* + }); + + _ = fz_scalar_set_macro(quote!(let S4: Bar< $type > = { + $( + $arg, + )* + })).unwrap(); + + fz_scalar_set!(let s4: Bar< $type > = { + $( + $arg, + )* + }); + + assert_eq!(s0, s1); + assert_eq!(s0, s2); + // assert_eq!(s0, S3); + assert_eq!(s0, s4); + } + + { + _ = fz_scalar_map_macro(quote!({ + $( + $arg: 42, + )* + })).unwrap(); + + let m0 = fz_scalar_map!({ + $( + $arg: 42, + )* + }); + + let v = vec![ + $( + ($arg, 42), + )* + ]; + + _ = fz_scalar_map_macro(quote!(v)).unwrap(); + + let m1 = fz_scalar_map!(v); + + let v = vec![ + $( + ($arg, 42), + )* + ]; + + let mut m2 = StdBTreeMap::new(); + for x in v.into_iter() { + m2.insert(x.0, x.1); + } + + _ = fz_scalar_map_macro(quote!(static _M3: Foo< $type, i32 > = { + $( + $arg: 42, + )* + })).unwrap(); + + fz_scalar_map!(static _M3: Foo< $type, i32 > = { + $( + $arg: 42, + )* + }); + + _ = fz_scalar_map_macro(quote!(let m4: Bar< $type, i32 > = { + $( + $arg: 42, + )* + })).unwrap(); + + fz_scalar_map!(let m4: Bar< $type, i32 > = { + $( + $arg: 42, + )* + }); + + assert_eq!(m0, m1); + assert_eq!(m0, m2); + // assert_eq!(m0, M3); + assert_eq!(m0, m4); + } + } +} + +#[test] +fn scalar_i8() { + test_scalar!(i8, 0i8); + test_scalar!(i8, 0i8, 1,); + test_scalar!(i8, 0i8, 1, 2,); + test_scalar!(i8, 0i8, 1, 2, 3,); + test_scalar!(i8, 0i8, 1, 2, 3, 4,); + test_scalar!(i8, 0i8, 1, 2, 3, 4, 5,); + test_scalar!(i8, 0i8, 1, 2, 3, 4, 5, 6,); + test_scalar!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(i8, 0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); + + // test duplicate logic + test_scalar!(i8, 0i8, 1, 2, 1, 1); +} + +#[test] +fn scalar_u8() { + test_scalar!(u8, 0u8); + test_scalar!(u8, 0u8, 1,); + test_scalar!(u8, 0u8, 1, 2,); + test_scalar!(u8, 0u8, 1, 2, 3,); + test_scalar!(u8, 0u8, 1, 2, 3, 4,); + test_scalar!(u8, 0u8, 1, 2, 3, 4, 5,); + test_scalar!(u8, 0u8, 1, 2, 3, 4, 5, 6,); + test_scalar!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(u8, 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn scalar_i16() { + test_scalar!(i16, 0i16); + test_scalar!(i16, 0i16, 1,); + test_scalar!(i16, 0i16, 1, 2,); + test_scalar!(i16, 0i16, 1, 2, 3,); + test_scalar!(i16, 0i16, 1, 2, 3, 4,); + test_scalar!(i16, 0i16, 1, 2, 3, 4, 5,); + test_scalar!(i16, 0i16, 1, 2, 3, 4, 5, 6,); + test_scalar!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(i16, 0i16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn scalar_u16() { + test_scalar!(u16, 0u16); + test_scalar!(u16, 0u16, 1,); + test_scalar!(u16, 0u16, 1, 2,); + test_scalar!(u16, 0u16, 1, 2, 3,); + test_scalar!(u16, 0u16, 1, 2, 3, 4,); + test_scalar!(u16, 0u16, 1, 2, 3, 4, 5,); + test_scalar!(u16, 0u16, 1, 2, 3, 4, 5, 6,); + test_scalar!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(u16, 0u16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn scalar_i32() { + test_scalar!(i32, 0i32); + test_scalar!(i32, 0i32, 1,); + test_scalar!(i32, 0i32, 1, 2,); + test_scalar!(i32, 0i32, 1, 2, 3,); + test_scalar!(i32, 0i32, 1, 2, 3, 4,); + test_scalar!(i32, 0i32, 1, 2, 3, 4, 5,); + test_scalar!(i32, 0i32, 1, 2, 3, 4, 5, 6,); + test_scalar!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(i32, 0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn scalar_u32() { + test_scalar!(u32, 0u32); + test_scalar!(u32, 0u32, 1,); + test_scalar!(u32, 0u32, 1, 2,); + test_scalar!(u32, 0u32, 1, 2, 3,); + test_scalar!(u32, 0u32, 1, 2, 3, 4,); + test_scalar!(u32, 0u32, 1, 2, 3, 4, 5,); + test_scalar!(u32, 0u32, 1, 2, 3, 4, 5, 6,); + test_scalar!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(u32, 0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn scalar_i64() { + test_scalar!(i64, 0i64); + test_scalar!(i64, 0i64, 1,); + test_scalar!(i64, 0i64, 1, 2,); + test_scalar!(i64, 0i64, 1, 2, 3,); + test_scalar!(i64, 0i64, 1, 2, 3, 4,); + test_scalar!(i64, 0i64, 1, 2, 3, 4, 5,); + test_scalar!(i64, 0i64, 1, 2, 3, 4, 5, 6,); + test_scalar!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(i64, 0i64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn scalar_u64() { + test_scalar!(u64, 0u64); + test_scalar!(u64, 0u64, 1,); + test_scalar!(u64, 0u64, 1, 2,); + test_scalar!(u64, 0u64, 1, 2, 3,); + test_scalar!(u64, 0u64, 1, 2, 3, 4,); + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5,); + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5, 6,); + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn scalar_isize() { + test_scalar!(isize, 0isize); + test_scalar!(isize, 0isize, 1,); + test_scalar!(isize, 0isize, 1, 2,); + test_scalar!(isize, 0isize, 1, 2, 3,); + test_scalar!(isize, 0isize, 1, 2, 3, 4,); + test_scalar!(isize, 0isize, 1, 2, 3, 4, 5,); + test_scalar!(isize, 0isize, 1, 2, 3, 4, 5, 6,); + test_scalar!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(isize, 0isize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn scalar_usize() { + test_scalar!(usize, 0usize); + test_scalar!(usize, 0usize, 1,); + test_scalar!(usize, 0usize, 1, 2,); + test_scalar!(usize, 0usize, 1, 2, 3,); + test_scalar!(usize, 0usize, 1, 2, 3, 4,); + test_scalar!(usize, 0usize, 1, 2, 3, 4, 5,); + test_scalar!(usize, 0usize, 1, 2, 3, 4, 5, 6,); + test_scalar!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7,); + test_scalar!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(usize, 0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +} + +#[test] +fn scalar_extra() { + // test sparse case + test_scalar!(u64, 0u64); + test_scalar!(u64, 0u64, 1,); + test_scalar!(u64, 0u64, 2,); + test_scalar!(u64, 0u64, 2, 3,); + test_scalar!(u64, 0u64, 2, 3, 4,); + test_scalar!(u64, 0u64, 2, 3, 4, 5,); + test_scalar!(u64, 0u64, 2, 3, 4, 5, 6,); + test_scalar!(u64, 0u64, 2, 3, 4, 5, 6, 7,); + test_scalar!(u64, 0u64, 2, 3, 4, 5, 6, 7, 8,); + test_scalar!(u64, 0u64, 2, 3, 4, 5, 6, 7, 8, 9,); + test_scalar!(u64, 0u64, 2, 3, 4, 5, 6, 7, 8, 9, 10,); + test_scalar!(u64, 0u64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + test_scalar!(u64, 0u64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + test_scalar!(u64, 0u64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); + + // test defaulting to hash table + test_scalar!(u64, 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1500); + + // test default to scan + test_scalar!(u64, 0u64, 1500); +} diff --git a/frozen-collections/tests/string_macro_tests.rs b/frozen-collections/tests/string_macro_tests.rs new file mode 100644 index 0000000..2745123 --- /dev/null +++ b/frozen-collections/tests/string_macro_tests.rs @@ -0,0 +1,216 @@ +use frozen_collections::*; +use frozen_collections_core::macros::{fz_string_map_macro, fz_string_set_macro}; +use quote::quote; +use std::collections::BTreeMap as StdBTreeMap; +use std::collections::BTreeSet as StdBTreeSet; + +macro_rules! test_string { + ( $type:ty, $( $arg:expr ),* $(,)?) => { + { + _ = fz_string_set_macro(quote!({ + $( + $arg, + )* + })).unwrap(); + + let s0 = fz_string_set!({ + $( + $arg, + )* + }); + + let v = vec![ + $( + $arg.to_string(), + )* + ]; + + _ = fz_string_set_macro(quote!(v)).unwrap(); + + let _s1 = fz_string_set!(v); + + let v = vec![ + $( + $arg, + )* + ]; + + let mut s2 = StdBTreeSet::new(); + for x in v.into_iter() { + s2.insert(x); + } + + _ = fz_string_set_macro(quote!(static _S3: Foo< $type > = { + $( + $arg, + )* + })).unwrap(); + + fz_string_set!(static _S3: Foo< $type > = { + $( + $arg, + )* + }); + + _ = fz_string_set_macro(quote!(let s4: Bar< $type > = { + $( + $arg, + )* + })).unwrap(); + + fz_string_set!(let s4: Bar< $type > = { + $( + $arg, + )* + }); + + _ = fz_string_set_macro(quote!(let mut s5: Baz< $type > = { + $( + $arg, + )* + })).unwrap(); + + fz_string_set!(let mut s5: Baz< $type > = { + $( + $arg, + )* + }); + + // assert_eq!(s0, s1); + assert_eq!(s0, s2); + // assert_eq!(s0, S3); + assert_eq!(s0, s4); + assert_eq!(s0, s5); + } + + { + _ = fz_string_map_macro(quote!({ + $( + $arg: 42, + )* + })).unwrap(); + + let m0 = fz_string_map!({ + $( + $arg: 42, + )* + }); + + let v = vec![ + $( + ($arg.to_string(), 42), + )* + ]; + + _ = fz_string_map_macro(quote!(v)).unwrap(); + + let _m1 = fz_string_map!(v); + + let v = vec![ + $( + ($arg, 42), + )* + ]; + + let mut m2 = StdBTreeMap::new(); + for x in v.into_iter() { + m2.insert(x.0, x.1); + } + + _ = fz_string_map_macro(quote!(static _M3: Foo< $type, i32 > = { + $( + $arg: 42, + )* + })).unwrap(); + + fz_string_map!(static _M3: Foo< $type, i32 > = { + $( + $arg: 42, + )* + }); + + _ = fz_string_map_macro(quote!(let m4: Bar< $type, i32 > = { + $( + $arg: 42, + )* + })).unwrap(); + + fz_string_map!(let m4: Bar< $type, i32 > = { + $( + $arg: 42, + )* + }); + + _ = fz_string_map_macro(quote!(let mut m5: Baz< $type, i32 > = { + $( + $arg: 42, + )* + })).unwrap(); + + fz_string_map!(let mut m5: Baz< $type, i32 > = { + $( + $arg: 42, + )* + }); + + // assert_eq!(m0, m1); + assert_eq!(m0, m2); + // assert_eq!(m0, M3); + assert_eq!(m0, m4); + assert_eq!(m0, m5); + } + } +} + +#[test] +fn string() { + test_string!(&str, "0"); + test_string!(&str, "0", "1"); + test_string!(&str, "0", "1", "2"); + test_string!(&str, "0", "1", "2", "3"); + test_string!(&str, "0", "1", "2", "3", "4"); + test_string!(&str, "0", "1", "2", "3", "4", "5"); + test_string!(&str, "0", "1", "2", "3", "4", "5", "6"); + test_string!(&str, "0", "1", "2", "3", "4", "5", "6", "7"); + test_string!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8"); + test_string!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + test_string!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); + test_string!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"); + test_string!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"); + test_string!(&str, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"); + + // test duplicate logic + test_string!(&str, "0", "1", "0", "0"); + + test_string!( + &str, + "ColorRed", + "ColorGreen", + "ColorBlue", + "ColorYellow", + "ColorCyan", + "ColorMagenta" + ); + + test_string!( + &str, + "RedColor", + "GreenColor", + "BlueColor", + "YellowColor", + "CyanColor", + "MagentaColor" + ); + + test_string!( + &str, + "ColorRed1111", + "ColorGreen22", + "ColorBlue333", + "ColorYellow4", + "ColorCyan555", + "ColorMagenta" + ); + + test_string!(&str, "XXA", "XXB", "XXC", "XXD", "XXE", "XXF", "XXG", "XXH", "XXHI"); +} diff --git a/tables.toml b/tables.toml new file mode 100644 index 0000000..8a8c844 --- /dev/null +++ b/tables.toml @@ -0,0 +1,42 @@ +[top_comments] +Overview = """ +These compare the various map implementation types available in frozen collections. + +The map implemnentation type `StdHashMap` represents the normal `std::collections::HashMap`, +while `StdHashMap(ahash)` represents the same map type but using the much faster `ahash` crate +for hashing. + +The benchmarks assume a 50% hit rate when probing for keys, meaning that +half the queries are for non-existing keys. Some algorithms perform differently between +present vs. non-existing keys, so real world performance of these algorithms depends on the +real world hit rate you experience. +""" + +[table_comments] +string_keys_length = """ +String maps where the length of the strings may be used as hash code for each key. +""" + +string_keys_subslice = """ +String maps where the subslices of the strings may be used to compute the hash code for each key. +""" + +string_keys = """ +String maps of small sizes. +""" + +misc_keys = """ +Maps of small sizes using a complex key type. +""" + +int_keys_dense_lookup = """ +Integer maps where the keys are in a contiguous range. +""" + +int_keys_sparse_lookup = """ +Integer maps where the keys are in a non-contiguous range. +""" + +int_keys = """ +Integer maps of small sizes. +"""