[
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_brace_style = K&R\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\nmax_line_length = 80\ntrim_trailing_whitespace = true\n\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Override core.autocrlf settings to keep line endings consistent (LF)\n* text=auto\n\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [dguo, maxbachmann]\nko_fi: dannyguo\nliberapay: dannyguo\ncustom: [\"https://www.paypal.com/donate/?hosted_button_id=VGWQBBD5CTWJU\", \"https://www.paypal.com/paypalme/DannyGuo\", \"https://www.buymeacoffee.com/dannyguo\"]\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  build:\n    strategy:\n      matrix:\n        os: [\"macos-latest\", \"ubuntu-latest\", \"windows-latest\"]\n        rust: [\"1.56\", \"beta\", \"stable\"]\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.rust }}\n      - name: Build the code\n        run: cargo build --verbose\n      - name: Test the code\n        run: cargo test --verbose\n  check-formatting:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n      - name: Check formatting\n        run: cargo fmt --check\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n/Cargo.lock\n*.swp\n*.swo\n.DS_Store\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\nThis project attempts to adhere to [Semantic Versioning](http://semver.org).\n\n## [Unreleased]\n\n## [0.11.1] - (2024-04-03)\n\n### Fixed\n\n- Drop MSRV down to 1.56 which was mistakenly changed in 0.11.0\n\n## [0.11.0] - (2024-01-07)\n\n### Changed\n\n- improve OSA implementation\n  - reduce runtime\n  - reduce binary size by more than `25%`\n\n- reduce binary size of Levenshtein distance\n\n- improve Damerau-Levenshtein implementation\n  - reduce memory usage from `O(N*M)` to `O(N+M)`\n  - reduce runtime in our own benchmark by more than `70%`\n  - reduce binary size by more than `25%`\n\n- only boost similarity in Jaro-Winkler once the Jaro similarity exceeds 0.7\n\n### Fixed\n\n- Fix transposition counting in Jaro and Jaro-Winkler.\n- Limit common prefix in Jaro-Winkler to 4 characters\n\n## [0.10.0] - (2020-01-31)\n\n### Added\n\n- Sørensen-Dice implementation (thanks [@robjtede](https://github.com/robjtede))\n\n## [0.9.3] - (2019-12-12)\n\n### Fixed\n\n- Fix Jaro and Jaro-Winkler when the arguments have lengths of 1 and are equal.\n  Previously, the functions would erroneously return 0 instead of 1. Thanks to\n  [@vvrably](https://github.com/vvrably) for pointing out the issue.\n\n## [0.9.2] - (2019-05-09)\n\n### Changed\n\n- Revert back to the standard library hashmap because it will use hashbrown very\n  soon\n- Remove ndarray in favor of using a single vector to represent the 2d grid in\n  Damerau-Levenshtein\n\n## [0.9.1] - (2019-04-08)\n\n### Changed\n\n- Faster Damerau-Levenshtein implementation (thanks [@lovasoa](https://github.com/lovasoa))\n\n## [0.9.0] - (2019-04-06)\n\n### Added\n\n- Generic distance functions (thanks [@lovasoa](https://github.com/lovasoa))\n\n## [0.8.0] - (2018-08-19)\n\n### Added\n\n- Normalized versions of Levenshtein and Damerau-Levenshtein (thanks [@gentoid](https://github.com/gentoid))\n\n## [0.7.0] - (2018-01-17)\n\n### Changed\n\n- Faster Levenshtein implementation (thanks [@wdv4758h](https://github.com/wdv4758h))\n\n### Removed\n\n- Remove the \"against_vec\" functions. They are one-liners now, so they don't\n  seem to add enough value to justify making the API larger. I didn't find\n  anybody using them when I skimmed through a GitHub search. If you do use them,\n  you can change the calls to something like:\n```rust\nlet distances = strings.iter().map(|a| jaro(target, a)).collect();\n```\n\n## [0.6.0] - (2016-12-26)\n\n### Added\n\n- Add optimal string alignment distance\n\n### Fixed\n\n- Fix Damerau-Levenshtein implementation (previous implementation was actually\n  optimal string alignment; see this [Damerau-Levenshtein explanation])\n\n## [0.5.2] - (2016-11-21)\n\n### Changed\n\n- Remove Cargo generated documentation in favor of a [docs.rs] link\n\n## [0.5.1] - (2016-08-23)\n\n### Added\n\n- Add Cargo generated documentation\n\n### Fixed\n\n- Fix panic when Jaro or Jaro-Winkler are given strings both with a length of\n  one\n\n## [0.5.0] - (2016-08-11)\n\n### Changed\n\n- Make Hamming faster (thanks @IBUzPE9) when the two strings have the same\n  length but slower when they have different lengths\n\n## [0.4.1] - (2016-04-18)\n\n### Added\n\n- Add Vagrant setup for development\n- Add AppVeyor configuration for Windows CI\n\n### Fixed\n\n- Fix metrics when given strings with multibyte characters (thanks @WanzenBug)\n\n## [0.4.0] - (2015-06-10)\n\n### Added\n\n- For each metric, add a function that takes a vector of strings and returns a\nvector of results (thanks @ovarene)\n\n## [0.3.0] - (2015-04-30)\n\n### Changed\n\n- Remove usage of unstable Rust features\n\n## [0.2.5] - (2015-04-24)\n\n### Fixed\n\n- Remove unnecessary `Float` import from doc tests\n\n## [0.2.4] - (2015-04-15)\n\n### Fixed\n\n- Remove unused `core` feature flag\n\n## [0.2.3] - (2015-04-01)\n\n### Fixed\n\n- Remove now unnecessary `Float` import\n\n## [0.2.2] - (2015-03-29)\n\n### Fixed\n\n- Remove usage of `char_at` (marked as unstable)\n\n## [0.2.1] - (2015-02-20)\n\n### Fixed\n\n- Update bit vector import to match Rust update\n\n## [0.2.0] - (2015-02-19)\n\n### Added\n\n- Implement Damerau-Levenshtein\n- Add tests in docs\n\n## [0.1.1] - (2015-02-10)\n\n### Added\n\n- Configure Travis for CI\n- Add rustdoc comments\n\n### Fixed\n\n- Limit Jaro-Winkler return value to a maximum of 1.0\n- Fix float comparisons in tests\n\n## [0.1.0] - (2015-02-09)\n\n### Added\n\n- Implement Hamming, Jaro, Jaro-Winkler, and Levenshtein\n\n[Unreleased]: https://github.com/rapidfuzz/strsim-rs/compare/0.11.1...HEAD\n[0.11.1]: https://github.com/rapidfuzz/strsim-rs/compare/0.11.0...0.11.1\n[0.11.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.10.0...0.11.0\n[0.10.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.9.3...0.10.0\n[0.9.3]: https://github.com/rapidfuzz/strsim-rs/compare/0.9.2...0.9.3\n[0.9.2]: https://github.com/rapidfuzz/strsim-rs/compare/0.9.1...0.9.2\n[0.9.1]: https://github.com/rapidfuzz/strsim-rs/compare/0.9.0...0.9.1\n[0.9.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.8.0...0.9.0\n[0.8.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.7.0...0.8.0\n[0.7.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.6.0...0.7.0\n[0.6.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.5.2...0.6.0\n[0.5.2]: https://github.com/rapidfuzz/strsim-rs/compare/0.5.1...0.5.2\n[0.5.1]: https://github.com/rapidfuzz/strsim-rs/compare/0.5.0...0.5.1\n[0.5.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.4.1...0.5.0\n[0.4.1]: https://github.com/rapidfuzz/strsim-rs/compare/0.4.0...0.4.1\n[0.4.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.3.0...0.4.0\n[0.3.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.2.5...0.3.0\n[0.2.5]: https://github.com/rapidfuzz/strsim-rs/compare/0.2.4...0.2.5\n[0.2.4]: https://github.com/rapidfuzz/strsim-rs/compare/0.2.3...0.2.4\n[0.2.3]: https://github.com/rapidfuzz/strsim-rs/compare/0.2.2...0.2.3\n[0.2.2]: https://github.com/rapidfuzz/strsim-rs/compare/0.2.1...0.2.2\n[0.2.1]: https://github.com/rapidfuzz/strsim-rs/compare/0.2.0...0.2.1\n[0.2.0]: https://github.com/rapidfuzz/strsim-rs/compare/0.1.1...0.2.0\n[0.1.1]: https://github.com/rapidfuzz/strsim-rs/compare/0.1.0...0.1.1\n[0.1.0]: https://github.com/rapidfuzz/strsim-rs/compare/fabad4...0.1.0\n[docs.rs]: https://docs.rs/strsim/\n[Damerau-Levenshtein explanation]:\nhttp://scarcitycomputing.blogspot.com/2013/04/damerau-levenshtein-edit-distance.html\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"strsim\"\nversion = \"0.11.1\"\nauthors = [\"Danny Guo <danny@dannyguo.com>\", \"maxbachmann <oss@maxbachmann.de>\"]\ndescription = \"\"\"\nImplementations of string similarity metrics. Includes Hamming, Levenshtein,\nOSA, Damerau-Levenshtein, Jaro, Jaro-Winkler, and Sørensen-Dice.\n\"\"\"\nedition = \"2021\"\nlicense = \"MIT\"\nrust-version = \"1.56\"\nreadme = \"README.md\"\nkeywords = [\"string\", \"similarity\", \"Hamming\", \"Levenshtein\", \"Jaro\"]\nhomepage = \"https://github.com/rapidfuzz/strsim-rs\"\nrepository = \"https://github.com/rapidfuzz/strsim-rs\"\ndocumentation = \"https://docs.rs/strsim/\"\nexclude = [\"/.github\", \"/dev\"]\ncategories = [\"text-processing\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Danny Guo\nCopyright (c) 2016 Titus Wormer <tituswormer@gmail.com>\nCopyright (c) 2018 Akash Kurdekar\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# strsim-rs\n\n[![Crates.io](https://img.shields.io/crates/v/strsim.svg)](https://crates.io/crates/strsim)\n[![Crates.io](https://img.shields.io/crates/l/strsim.svg?maxAge=2592000)](https://github.com/rapidfuzz/strsim-rs/blob/main/LICENSE)\n[![CI status](https://github.com/rapidfuzz/strsim-rs/workflows/CI/badge.svg)](https://github.com/rapidfuzz/strsim-rs/actions?query=branch%3Amain)\n[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)\n\n[Rust](https://www.rust-lang.org) implementations of [string similarity metrics]:\n  - [Hamming]\n  - [Levenshtein] - distance & normalized\n  - [Optimal string alignment]\n  - [Damerau-Levenshtein] - distance & normalized\n  - [Jaro and Jaro-Winkler]\n  - [Sørensen-Dice]\n\nThe normalized versions return values between `0.0` and `1.0`, where `1.0` means\nan exact match.\n\nThere are also generic versions of the functions for non-string inputs.\n\n## Installation\n\n`strsim` is available on [crates.io](https://crates.io/crates/strsim). Add it to\nyour project:\n\n```sh\ncargo add strsim\n```\n\n## Usage\n\nGo to [Docs.rs](https://docs.rs/strsim/) for the full documentation. You can\nalso clone the repo, and run `$ cargo doc --open`.\n\n### Examples\n\n```rust\nextern crate strsim;\n\nuse strsim::{hamming, levenshtein, normalized_levenshtein, osa_distance,\n             damerau_levenshtein, normalized_damerau_levenshtein, jaro,\n             jaro_winkler, sorensen_dice};\n\nfn main() {\n    match hamming(\"hamming\", \"hammers\") {\n        Ok(distance) => assert_eq!(3, distance),\n        Err(why) => panic!(\"{:?}\", why)\n    }\n\n    assert_eq!(levenshtein(\"kitten\", \"sitting\"), 3);\n\n    assert!((normalized_levenshtein(\"kitten\", \"sitting\") - 0.571).abs() < 0.001);\n\n    assert_eq!(osa_distance(\"ac\", \"cba\"), 3);\n\n    assert_eq!(damerau_levenshtein(\"ac\", \"cba\"), 2);\n\n    assert!((normalized_damerau_levenshtein(\"levenshtein\", \"löwenbräu\") - 0.272).abs() <\n            0.001);\n\n    assert!((jaro(\"Friedrich Nietzsche\", \"Jean-Paul Sartre\") - 0.392).abs() <\n            0.001);\n\n    assert!((jaro_winkler(\"cheeseburger\", \"cheese fries\") - 0.911).abs() <\n            0.001);\n\n    assert_eq!(sorensen_dice(\"web applications\", \"applications of the web\"),\n        0.7878787878787878);\n}\n```\n\nUsing the generic versions of the functions:\n\n```rust\nextern crate strsim;\n\nuse strsim::generic_levenshtein;\n\nfn main() {\n    assert_eq!(2, generic_levenshtein(&[1, 2, 3], &[0, 2, 5]));\n}\n```\n\n## Contributing\n\nIf you don't want to install Rust itself, you can run `$ ./dev` for a\ndevelopment CLI if you have [Docker] installed.\n\nBenchmarks require a Nightly toolchain. Run `$ cargo +nightly bench`.\n\n## License\n\n[MIT](https://github.com/rapidfuzz/strsim-rs/blob/main/LICENSE)\n\n[string similarity metrics]:http://en.wikipedia.org/wiki/String_metric\n[Damerau-Levenshtein]:http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance\n[Jaro and Jaro-Winkler]:http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance\n[Levenshtein]:http://en.wikipedia.org/wiki/Levenshtein_distance\n[Hamming]:http://en.wikipedia.org/wiki/Hamming_distance\n[Optimal string alignment]:https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance#Optimal_string_alignment_distance\n[Sørensen-Dice]:http://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient\n[Docker]:https://docs.docker.com/engine/installation/\n"
  },
  {
    "path": "SECURITY.md",
    "content": "## Reporting Security Issues\n\nIf you believe you have found a security vulnerability in the project, please report it to us through coordinated disclosure.\n\n**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**\n\nInstead, please send an email to oss@maxbachmann.de.\n\nPlease include as much of the information listed below as you can to help us better understand and resolve the issue:\n\n  * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting)\n  * Full paths of source file(s) related to the manifestation of the issue\n  * The location of the affected source code (tag/branch/commit or direct URL)\n  * Any special configuration required to reproduce the issue\n  * Step-by-step instructions to reproduce the issue\n  * Proof-of-concept or exploit code (if possible)\n  * Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n"
  },
  {
    "path": "benches/benches.rs",
    "content": "//! Benchmarks for strsim.\n\n#![feature(test)]\n\nextern crate strsim;\nextern crate test;\nuse self::test::Bencher;\n\n#[bench]\nfn bench_hamming(bencher: &mut Bencher) {\n    let a = \"ACAAGATGCCATTGTCCCCCGGCCTCCTGCTGCTGCTGCTCTCCGGGG\";\n    let b = \"CCTGGAGGGTGGCCCCACCGGCCGAGACAGCGAGCATATGCAGGAAGC\";\n    bencher.iter(|| {\n        strsim::hamming(a, b).unwrap();\n    })\n}\n\n#[bench]\nfn bench_jaro(bencher: &mut Bencher) {\n    let a = \"Philosopher Friedrich Nietzsche\";\n    let b = \"Philosopher Jean-Paul Sartre\";\n    bencher.iter(|| {\n        strsim::jaro(a, b);\n    })\n}\n\n#[bench]\nfn bench_jaro_winkler(bencher: &mut Bencher) {\n    let a = \"Philosopher Friedrich Nietzsche\";\n    let b = \"Philosopher Jean-Paul Sartre\";\n    bencher.iter(|| {\n        strsim::jaro_winkler(a, b);\n    })\n}\n\n#[bench]\nfn bench_levenshtein(bencher: &mut Bencher) {\n    let a = \"Philosopher Friedrich Nietzsche\";\n    let b = \"Philosopher Jean-Paul Sartre\";\n    bencher.iter(|| {\n        strsim::levenshtein(a, b);\n    })\n}\n\n#[bench]\nfn bench_levenshtein_on_u8(bencher: &mut Bencher) {\n    bencher.iter(|| {\n        strsim::generic_levenshtein(&vec![0u8; 30], &vec![7u8; 31]);\n    })\n}\n\n#[bench]\nfn bench_normalized_levenshtein(bencher: &mut Bencher) {\n    let a = \"Philosopher Friedrich Nietzsche\";\n    let b = \"Philosopher Jean-Paul Sartre\";\n    bencher.iter(|| {\n        strsim::normalized_levenshtein(a, b);\n    })\n}\n\n#[bench]\nfn bench_osa_distance(bencher: &mut Bencher) {\n    let a = \"Philosopher Friedrich Nietzsche\";\n    let b = \"Philosopher Jean-Paul Sartre\";\n    bencher.iter(|| {\n        strsim::osa_distance(a, b);\n    })\n}\n\n#[bench]\nfn bench_damerau_levenshtein(bencher: &mut Bencher) {\n    let a = \"Philosopher Friedrich Nietzsche\";\n    let b = \"Philosopher Jean-Paul Sartre\";\n    bencher.iter(|| {\n        strsim::damerau_levenshtein(a, b);\n    })\n}\n\n#[bench]\nfn bench_normalized_damerau_levenshtein(bencher: &mut Bencher) {\n    let a = \"Philosopher Friedrich Nietzsche\";\n    let b = \"Philosopher Jean-Paul Sartre\";\n    bencher.iter(|| {\n        strsim::normalized_damerau_levenshtein(a, b);\n    })\n}\n\n#[bench]\nfn bench_sorensen_dice(bencher: &mut Bencher) {\n    let a = \"Philosopher Friedrich Nietzsche\";\n    let b = \"Philosopher Jean-Paul Sartre\";\n    bencher.iter(|| {\n        strsim::sorensen_dice(a, b);\n    })\n}\n"
  },
  {
    "path": "dev",
    "content": "#!/usr/bin/env python3\n# ./dev --help\n\nimport argparse\nimport os\nfrom subprocess import run\nimport sys\n\nparser = argparse.ArgumentParser(prog='./dev')\nsubparsers = parser.add_subparsers(metavar='<command>', title='commands')\ncommand = [\n    'docker', 'run', '-it', '--rm', '-v', os.getcwd() + ':/src:cached',\n    '-w=/src', 'rust:1.31.0'\n]\n\ndef cargo(args, remaining):\n    sys.exit(run(command + ['cargo'] + remaining or []).returncode)\n\nparser_cargo = subparsers.add_parser('cargo', help='run a cargo command')\nparser_cargo.set_defaults(func=cargo)\n\ndef sh(args, remaining):\n    sys.exit(run(command + ['bash']).returncode)\n\nparser_sh = subparsers.add_parser('sh', help='bring up a shell')\nparser_sh.set_defaults(func=sh)\n\ndef test(args, remaining):\n    sys.exit(run(command + ['cargo', 'test']).returncode)\n\nparser_test = subparsers.add_parser('test', help='run tests')\nparser_test.set_defaults(func=test)\n\nif len(sys.argv) > 1:\n    args, remaining = parser.parse_known_args()\n    try:\n        args.func(args, remaining)\n    except FileNotFoundError:\n        sys.exit('Please install Docker.')\nelse:\n    parser.print_help()\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! This library implements string similarity metrics.\n\n#![forbid(unsafe_code)]\n#![warn(rust_2018_idioms)]\n#![allow(\n    // these casts are sometimes needed. They restrict the length of input iterators\n    // but there isn't really any way around this except for always working with\n    // 128 bit types\n    clippy::cast_possible_wrap,\n    clippy::cast_sign_loss,\n    clippy::cast_precision_loss,\n    // not practical\n    clippy::similar_names,\n    // noisy\n    clippy::missing_errors_doc,\n    clippy::missing_panics_doc,\n    clippy::must_use_candidate,\n    // todo https://github.com/rapidfuzz/strsim-rs/issues/59\n    clippy::range_plus_one\n)]\n\nuse std::char;\nuse std::cmp::{max, min};\nuse std::collections::HashMap;\nuse std::convert::TryFrom;\nuse std::error::Error;\nuse std::fmt::{self, Display, Formatter};\nuse std::hash::Hash;\nuse std::mem;\nuse std::str::Chars;\n\n#[derive(Debug, PartialEq)]\npub enum StrSimError {\n    DifferentLengthArgs,\n}\n\nimpl Display for StrSimError {\n    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {\n        let text = match self {\n            StrSimError::DifferentLengthArgs => \"Differing length arguments provided\",\n        };\n\n        write!(fmt, \"{}\", text)\n    }\n}\n\nimpl Error for StrSimError {}\n\npub type HammingResult = Result<usize, StrSimError>;\n\n/// Calculates the number of positions in the two sequences where the elements\n/// differ. Returns an error if the sequences have different lengths.\npub fn generic_hamming<Iter1, Iter2, Elem1, Elem2>(a: Iter1, b: Iter2) -> HammingResult\nwhere\n    Iter1: IntoIterator<Item = Elem1>,\n    Iter2: IntoIterator<Item = Elem2>,\n    Elem1: PartialEq<Elem2>,\n{\n    let (mut ita, mut itb) = (a.into_iter(), b.into_iter());\n    let mut count = 0;\n    loop {\n        match (ita.next(), itb.next()) {\n            (Some(x), Some(y)) => {\n                if x != y {\n                    count += 1;\n                }\n            }\n            (None, None) => return Ok(count),\n            _ => return Err(StrSimError::DifferentLengthArgs),\n        }\n    }\n}\n\n/// Calculates the number of positions in the two strings where the characters\n/// differ. Returns an error if the strings have different lengths.\n///\n/// ```\n/// use strsim::{hamming, StrSimError::DifferentLengthArgs};\n///\n/// assert_eq!(Ok(3), hamming(\"hamming\", \"hammers\"));\n///\n/// assert_eq!(Err(DifferentLengthArgs), hamming(\"hamming\", \"ham\"));\n/// ```\npub fn hamming(a: &str, b: &str) -> HammingResult {\n    generic_hamming(a.chars(), b.chars())\n}\n\n/// Calculates the Jaro similarity between two sequences. The returned value\n/// is between 0.0 and 1.0 (higher value means more similar).\npub fn generic_jaro<'a, 'b, Iter1, Iter2, Elem1, Elem2>(a: &'a Iter1, b: &'b Iter2) -> f64\nwhere\n    &'a Iter1: IntoIterator<Item = Elem1>,\n    &'b Iter2: IntoIterator<Item = Elem2>,\n    Elem1: PartialEq<Elem2>,\n{\n    let a_len = a.into_iter().count();\n    let b_len = b.into_iter().count();\n\n    if a_len == 0 && b_len == 0 {\n        return 1.0;\n    } else if a_len == 0 || b_len == 0 {\n        return 0.0;\n    }\n\n    let mut search_range = max(a_len, b_len) / 2;\n    search_range = search_range.saturating_sub(1);\n\n    // combine memory allocations to reduce runtime\n    let mut flags_memory = vec![false; a_len + b_len];\n    let (a_flags, b_flags) = flags_memory.split_at_mut(a_len);\n\n    let mut matches = 0_usize;\n\n    for (i, a_elem) in a.into_iter().enumerate() {\n        // prevent integer wrapping\n        let min_bound = i.saturating_sub(search_range);\n\n        let max_bound = min(b_len, i + search_range + 1);\n\n        for (j, b_elem) in b.into_iter().enumerate().take(max_bound) {\n            if min_bound <= j && a_elem == b_elem && !b_flags[j] {\n                a_flags[i] = true;\n                b_flags[j] = true;\n                matches += 1;\n                break;\n            }\n        }\n    }\n\n    let mut transpositions = 0_usize;\n    if matches != 0 {\n        let mut b_iter = b_flags.iter().zip(b);\n        for (a_flag, ch1) in a_flags.iter().zip(a) {\n            if *a_flag {\n                loop {\n                    if let Some((b_flag, ch2)) = b_iter.next() {\n                        if !*b_flag {\n                            continue;\n                        }\n\n                        if ch1 != ch2 {\n                            transpositions += 1;\n                        }\n                        break;\n                    }\n                }\n            }\n        }\n    }\n    transpositions /= 2;\n\n    if matches == 0 {\n        0.0\n    } else {\n        ((matches as f64 / a_len as f64)\n            + (matches as f64 / b_len as f64)\n            + ((matches - transpositions) as f64 / matches as f64))\n            / 3.0\n    }\n}\n\nstruct StringWrapper<'a>(&'a str);\n\nimpl<'b> IntoIterator for &StringWrapper<'b> {\n    type Item = char;\n    type IntoIter = Chars<'b>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        self.0.chars()\n    }\n}\n\n/// Calculates the Jaro similarity between two strings. The returned value\n/// is between 0.0 and 1.0 (higher value means more similar).\n///\n/// ```\n/// use strsim::jaro;\n///\n/// assert!((0.392 - jaro(\"Friedrich Nietzsche\", \"Jean-Paul Sartre\")).abs() <\n///         0.001);\n/// ```\npub fn jaro(a: &str, b: &str) -> f64 {\n    generic_jaro(&StringWrapper(a), &StringWrapper(b))\n}\n\n/// Like Jaro but gives a boost to sequences that have a common prefix.\npub fn generic_jaro_winkler<'a, 'b, Iter1, Iter2, Elem1, Elem2>(a: &'a Iter1, b: &'b Iter2) -> f64\nwhere\n    &'a Iter1: IntoIterator<Item = Elem1>,\n    &'b Iter2: IntoIterator<Item = Elem2>,\n    Elem1: PartialEq<Elem2>,\n{\n    let sim = generic_jaro(a, b);\n\n    if sim > 0.7 {\n        let prefix_length = a\n            .into_iter()\n            .take(4)\n            .zip(b)\n            .take_while(|(a_elem, b_elem)| a_elem == b_elem)\n            .count();\n\n        sim + 0.1 * prefix_length as f64 * (1.0 - sim)\n    } else {\n        sim\n    }\n}\n\n/// Like Jaro but gives a boost to strings that have a common prefix.\n///\n/// ```\n/// use strsim::jaro_winkler;\n///\n/// assert!((0.866 - jaro_winkler(\"cheeseburger\", \"cheese fries\")).abs() <\n///         0.001);\n/// ```\npub fn jaro_winkler(a: &str, b: &str) -> f64 {\n    generic_jaro_winkler(&StringWrapper(a), &StringWrapper(b))\n}\n\n/// Calculates the minimum number of insertions, deletions, and substitutions\n/// required to change one sequence into the other.\n///\n/// ```\n/// use strsim::generic_levenshtein;\n///\n/// assert_eq!(3, generic_levenshtein(&[1,2,3], &[1,2,3,4,5,6]));\n/// ```\npub fn generic_levenshtein<'a, 'b, Iter1, Iter2, Elem1, Elem2>(a: &'a Iter1, b: &'b Iter2) -> usize\nwhere\n    &'a Iter1: IntoIterator<Item = Elem1>,\n    &'b Iter2: IntoIterator<Item = Elem2>,\n    Elem1: PartialEq<Elem2>,\n{\n    let b_len = b.into_iter().count();\n\n    let mut cache: Vec<usize> = (1..b_len + 1).collect();\n\n    let mut result = b_len;\n\n    for (i, a_elem) in a.into_iter().enumerate() {\n        result = i + 1;\n        let mut distance_b = i;\n\n        for (j, b_elem) in b.into_iter().enumerate() {\n            let cost = usize::from(a_elem != b_elem);\n            let distance_a = distance_b + cost;\n            distance_b = cache[j];\n            result = min(result + 1, min(distance_a, distance_b + 1));\n            cache[j] = result;\n        }\n    }\n\n    result\n}\n\n/// Calculates the minimum number of insertions, deletions, and substitutions\n/// required to change one string into the other.\n///\n/// ```\n/// use strsim::levenshtein;\n///\n/// assert_eq!(3, levenshtein(\"kitten\", \"sitting\"));\n/// ```\npub fn levenshtein(a: &str, b: &str) -> usize {\n    generic_levenshtein(&StringWrapper(a), &StringWrapper(b))\n}\n\n/// Calculates a normalized score of the Levenshtein algorithm between 0.0 and\n/// 1.0 (inclusive), where 1.0 means the strings are the same.\n///\n/// ```\n/// use strsim::normalized_levenshtein;\n///\n/// assert!((normalized_levenshtein(\"kitten\", \"sitting\") - 0.57142).abs() < 0.00001);\n/// assert!((normalized_levenshtein(\"\", \"\") - 1.0).abs() < 0.00001);\n/// assert!(normalized_levenshtein(\"\", \"second\").abs() < 0.00001);\n/// assert!(normalized_levenshtein(\"first\", \"\").abs() < 0.00001);\n/// assert!((normalized_levenshtein(\"string\", \"string\") - 1.0).abs() < 0.00001);\n/// ```\npub fn normalized_levenshtein(a: &str, b: &str) -> f64 {\n    if a.is_empty() && b.is_empty() {\n        return 1.0;\n    }\n    1.0 - (levenshtein(a, b) as f64) / (a.chars().count().max(b.chars().count()) as f64)\n}\n\n/// Like Levenshtein but allows for adjacent transpositions. Each substring can\n/// only be edited once.\n///\n/// ```\n/// use strsim::osa_distance;\n///\n/// assert_eq!(3, osa_distance(\"ab\", \"bca\"));\n/// ```\npub fn osa_distance(a: &str, b: &str) -> usize {\n    let b_len = b.chars().count();\n    // 0..=b_len behaves like 0..b_len.saturating_add(1) which could be a different size\n    // this leads to significantly worse code gen when swapping the vectors below\n    let mut prev_two_distances: Vec<usize> = (0..b_len + 1).collect();\n    let mut prev_distances: Vec<usize> = (0..b_len + 1).collect();\n    let mut curr_distances: Vec<usize> = vec![0; b_len + 1];\n\n    let mut prev_a_char = char::MAX;\n    let mut prev_b_char = char::MAX;\n\n    for (i, a_char) in a.chars().enumerate() {\n        curr_distances[0] = i + 1;\n\n        for (j, b_char) in b.chars().enumerate() {\n            let cost = usize::from(a_char != b_char);\n            curr_distances[j + 1] = min(\n                curr_distances[j] + 1,\n                min(prev_distances[j + 1] + 1, prev_distances[j] + cost),\n            );\n            if i > 0 && j > 0 && a_char != b_char && a_char == prev_b_char && b_char == prev_a_char\n            {\n                curr_distances[j + 1] = min(curr_distances[j + 1], prev_two_distances[j - 1] + 1);\n            }\n\n            prev_b_char = b_char;\n        }\n\n        mem::swap(&mut prev_two_distances, &mut prev_distances);\n        mem::swap(&mut prev_distances, &mut curr_distances);\n        prev_a_char = a_char;\n    }\n\n    // access prev_distances instead of curr_distances since we swapped\n    // them above. In case a is empty this would still contain the correct value\n    // from initializing the last element to b_len\n    prev_distances[b_len]\n}\n\n/* Returns the final index for a value in a single vector that represents a fixed\n2d grid */\nfn flat_index(i: usize, j: usize, width: usize) -> usize {\n    j * width + i\n}\n\n/// Like optimal string alignment, but substrings can be edited an unlimited\n/// number of times, and the triangle inequality holds.\n///\n/// ```\n/// use strsim::generic_damerau_levenshtein;\n///\n/// assert_eq!(2, generic_damerau_levenshtein(&[1,2], &[2,3,1]));\n/// ```\npub fn generic_damerau_levenshtein<Elem>(a_elems: &[Elem], b_elems: &[Elem]) -> usize\nwhere\n    Elem: Eq + Hash + Clone,\n{\n    let a_len = a_elems.len();\n    let b_len = b_elems.len();\n\n    if a_len == 0 {\n        return b_len;\n    }\n    if b_len == 0 {\n        return a_len;\n    }\n\n    let width = a_len + 2;\n    let mut distances = vec![0; (a_len + 2) * (b_len + 2)];\n    let max_distance = a_len + b_len;\n    distances[0] = max_distance;\n\n    for i in 0..(a_len + 1) {\n        distances[flat_index(i + 1, 0, width)] = max_distance;\n        distances[flat_index(i + 1, 1, width)] = i;\n    }\n\n    for j in 0..(b_len + 1) {\n        distances[flat_index(0, j + 1, width)] = max_distance;\n        distances[flat_index(1, j + 1, width)] = j;\n    }\n\n    let mut elems: HashMap<Elem, usize> = HashMap::with_capacity(64);\n\n    for i in 1..(a_len + 1) {\n        let mut db = 0;\n\n        for j in 1..(b_len + 1) {\n            let k = match elems.get(&b_elems[j - 1]) {\n                Some(&value) => value,\n                None => 0,\n            };\n\n            let insertion_cost = distances[flat_index(i, j + 1, width)] + 1;\n            let deletion_cost = distances[flat_index(i + 1, j, width)] + 1;\n            let transposition_cost =\n                distances[flat_index(k, db, width)] + (i - k - 1) + 1 + (j - db - 1);\n\n            let mut substitution_cost = distances[flat_index(i, j, width)] + 1;\n            if a_elems[i - 1] == b_elems[j - 1] {\n                db = j;\n                substitution_cost -= 1;\n            }\n\n            distances[flat_index(i + 1, j + 1, width)] = min(\n                substitution_cost,\n                min(insertion_cost, min(deletion_cost, transposition_cost)),\n            );\n        }\n\n        elems.insert(a_elems[i - 1].clone(), i);\n    }\n\n    distances[flat_index(a_len + 1, b_len + 1, width)]\n}\n\n#[derive(Clone, Copy, PartialEq, Eq)]\nstruct RowId {\n    val: isize,\n}\n\nimpl Default for RowId {\n    fn default() -> Self {\n        Self { val: -1 }\n    }\n}\n\n#[derive(Default, Clone)]\nstruct GrowingHashmapMapElemChar<ValueType> {\n    key: u32,\n    value: ValueType,\n}\n\n/// specialized hashmap to store user provided types\n/// this implementation relies on a couple of base assumptions in order to simplify the implementation\n/// - the hashmap does not have an upper limit of included items\n/// - the default value for the `ValueType` can be used as a dummy value to indicate an empty cell\n/// - elements can't be removed\n/// - only allocates memory on first write access.\n///   This improves performance for hashmaps that are never written to\nstruct GrowingHashmapChar<ValueType> {\n    used: i32,\n    fill: i32,\n    mask: i32,\n    map: Option<Vec<GrowingHashmapMapElemChar<ValueType>>>,\n}\n\nimpl<ValueType> Default for GrowingHashmapChar<ValueType>\nwhere\n    ValueType: Default + Clone + Eq,\n{\n    fn default() -> Self {\n        Self {\n            used: 0,\n            fill: 0,\n            mask: -1,\n            map: None,\n        }\n    }\n}\n\nimpl<ValueType> GrowingHashmapChar<ValueType>\nwhere\n    ValueType: Default + Clone + Eq + Copy,\n{\n    fn get(&self, key: u32) -> ValueType {\n        self.map\n            .as_ref()\n            .map_or_else(|| Default::default(), |map| map[self.lookup(key)].value)\n    }\n\n    fn get_mut(&mut self, key: u32) -> &mut ValueType {\n        if self.map.is_none() {\n            self.allocate();\n        }\n\n        let mut i = self.lookup(key);\n        if self\n            .map\n            .as_ref()\n            .expect(\"map should have been created above\")[i]\n            .value\n            == Default::default()\n        {\n            self.fill += 1;\n            // resize when 2/3 full\n            if self.fill * 3 >= (self.mask + 1) * 2 {\n                self.grow((self.used + 1) * 2);\n                i = self.lookup(key);\n            }\n\n            self.used += 1;\n        }\n\n        let elem = &mut self\n            .map\n            .as_mut()\n            .expect(\"map should have been created above\")[i];\n        elem.key = key;\n        &mut elem.value\n    }\n\n    fn allocate(&mut self) {\n        self.mask = 8 - 1;\n        self.map = Some(vec![GrowingHashmapMapElemChar::default(); 8]);\n    }\n\n    /// lookup key inside the hashmap using a similar collision resolution\n    /// strategy to `CPython` and `Ruby`\n    fn lookup(&self, key: u32) -> usize {\n        let hash = key;\n        let mut i = hash as usize & self.mask as usize;\n\n        let map = self\n            .map\n            .as_ref()\n            .expect(\"callers have to ensure map is allocated\");\n\n        if map[i].value == Default::default() || map[i].key == key {\n            return i;\n        }\n\n        let mut perturb = key;\n        loop {\n            i = (i * 5 + perturb as usize + 1) & self.mask as usize;\n\n            if map[i].value == Default::default() || map[i].key == key {\n                return i;\n            }\n\n            perturb >>= 5;\n        }\n    }\n\n    fn grow(&mut self, min_used: i32) {\n        let mut new_size = self.mask + 1;\n        while new_size <= min_used {\n            new_size <<= 1;\n        }\n\n        self.fill = self.used;\n        self.mask = new_size - 1;\n\n        let old_map = std::mem::replace(\n            self.map\n                .as_mut()\n                .expect(\"callers have to ensure map is allocated\"),\n            vec![GrowingHashmapMapElemChar::<ValueType>::default(); new_size as usize],\n        );\n\n        for elem in old_map {\n            if elem.value != Default::default() {\n                let j = self.lookup(elem.key);\n                let new_elem = &mut self.map.as_mut().expect(\"map created above\")[j];\n                new_elem.key = elem.key;\n                new_elem.value = elem.value;\n                self.used -= 1;\n                if self.used == 0 {\n                    break;\n                }\n            }\n        }\n\n        self.used = self.fill;\n    }\n}\n\nstruct HybridGrowingHashmapChar<ValueType> {\n    map: GrowingHashmapChar<ValueType>,\n    extended_ascii: [ValueType; 256],\n}\n\nimpl<ValueType> HybridGrowingHashmapChar<ValueType>\nwhere\n    ValueType: Default + Clone + Copy + Eq,\n{\n    fn get(&self, key: char) -> ValueType {\n        let value = key as u32;\n        if value <= 255 {\n            let val_u8 = u8::try_from(value).expect(\"we check the bounds above\");\n            self.extended_ascii[usize::from(val_u8)]\n        } else {\n            self.map.get(value)\n        }\n    }\n\n    fn get_mut(&mut self, key: char) -> &mut ValueType {\n        let value = key as u32;\n        if value <= 255 {\n            let val_u8 = u8::try_from(value).expect(\"we check the bounds above\");\n            &mut self.extended_ascii[usize::from(val_u8)]\n        } else {\n            self.map.get_mut(value)\n        }\n    }\n}\n\nimpl<ValueType> Default for HybridGrowingHashmapChar<ValueType>\nwhere\n    ValueType: Default + Clone + Copy + Eq,\n{\n    fn default() -> Self {\n        HybridGrowingHashmapChar {\n            map: GrowingHashmapChar::default(),\n            extended_ascii: [Default::default(); 256],\n        }\n    }\n}\n\nfn damerau_levenshtein_impl<Iter1, Iter2>(s1: Iter1, len1: usize, s2: Iter2, len2: usize) -> usize\nwhere\n    Iter1: Iterator<Item = char> + Clone,\n    Iter2: Iterator<Item = char> + Clone,\n{\n    // The implementations is based on the paper\n    // `Linear space string correction algorithm using the Damerau-Levenshtein distance`\n    // from Chunchun Zhao and Sartaj Sahni\n    //\n    // It has a runtime complexity of `O(N*M)` and a memory usage of `O(N+M)`.\n    let max_val = max(len1, len2) as isize + 1;\n\n    let mut last_row_id = HybridGrowingHashmapChar::<RowId>::default();\n\n    let size = len2 + 2;\n    let mut fr = vec![max_val; size];\n    let mut r1 = vec![max_val; size];\n    let mut r: Vec<isize> = (max_val..max_val + 1)\n        .chain(0..(size - 1) as isize)\n        .collect();\n\n    for (i, ch1) in s1.enumerate().map(|(i, ch1)| (i + 1, ch1)) {\n        mem::swap(&mut r, &mut r1);\n        let mut last_col_id: isize = -1;\n        let mut last_i2l1 = r[1];\n        r[1] = i as isize;\n        let mut t = max_val;\n\n        for (j, ch2) in s2.clone().enumerate().map(|(j, ch2)| (j + 1, ch2)) {\n            let diag = r1[j] + isize::from(ch1 != ch2);\n            let left = r[j] + 1;\n            let up = r1[j + 1] + 1;\n            let mut temp = min(diag, min(left, up));\n\n            if ch1 == ch2 {\n                last_col_id = j as isize; // last occurence of s1_i\n                fr[j + 1] = r1[j - 1]; // save H_k-1,j-2\n                t = last_i2l1; // save H_i-2,l-1\n            } else {\n                let k = last_row_id.get(ch2).val;\n                let l = last_col_id;\n\n                if j as isize - l == 1 {\n                    let transpose = fr[j + 1] + (i as isize - k);\n                    temp = min(temp, transpose);\n                } else if i as isize - k == 1 {\n                    let transpose = t + (j as isize - l);\n                    temp = min(temp, transpose);\n                }\n            }\n\n            last_i2l1 = r[j + 1];\n            r[j + 1] = temp;\n        }\n        last_row_id.get_mut(ch1).val = i as isize;\n    }\n\n    r[len2 + 1] as usize\n}\n\n/// Like optimal string alignment, but substrings can be edited an unlimited\n/// number of times, and the triangle inequality holds.\n///\n/// ```\n/// use strsim::damerau_levenshtein;\n///\n/// assert_eq!(2, damerau_levenshtein(\"ab\", \"bca\"));\n/// ```\npub fn damerau_levenshtein(a: &str, b: &str) -> usize {\n    damerau_levenshtein_impl(a.chars(), a.chars().count(), b.chars(), b.chars().count())\n}\n\n/// Calculates a normalized score of the Damerau–Levenshtein algorithm between\n/// 0.0 and 1.0 (inclusive), where 1.0 means the strings are the same.\n///\n/// ```\n/// use strsim::normalized_damerau_levenshtein;\n///\n/// assert!((normalized_damerau_levenshtein(\"levenshtein\", \"löwenbräu\") - 0.27272).abs() < 0.00001);\n/// assert!((normalized_damerau_levenshtein(\"\", \"\") - 1.0).abs() < 0.00001);\n/// assert!(normalized_damerau_levenshtein(\"\", \"flower\").abs() < 0.00001);\n/// assert!(normalized_damerau_levenshtein(\"tree\", \"\").abs() < 0.00001);\n/// assert!((normalized_damerau_levenshtein(\"sunglasses\", \"sunglasses\") - 1.0).abs() < 0.00001);\n/// ```\npub fn normalized_damerau_levenshtein(a: &str, b: &str) -> f64 {\n    if a.is_empty() && b.is_empty() {\n        return 1.0;\n    }\n\n    let len1 = a.chars().count();\n    let len2 = b.chars().count();\n    let dist = damerau_levenshtein_impl(a.chars(), len1, b.chars(), len2);\n    1.0 - (dist as f64) / (max(len1, len2) as f64)\n}\n\n/// Returns an Iterator of char tuples.\nfn bigrams(s: &str) -> impl Iterator<Item = (char, char)> + '_ {\n    s.chars().zip(s.chars().skip(1))\n}\n\n/// Calculates a Sørensen-Dice similarity distance using bigrams.\n/// See <https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient>.\n///\n/// ```\n/// use strsim::sorensen_dice;\n///\n/// assert_eq!(1.0, sorensen_dice(\"\", \"\"));\n/// assert_eq!(0.0, sorensen_dice(\"\", \"a\"));\n/// assert_eq!(0.0, sorensen_dice(\"french\", \"quebec\"));\n/// assert_eq!(1.0, sorensen_dice(\"ferris\", \"ferris\"));\n/// assert_eq!(0.8888888888888888, sorensen_dice(\"feris\", \"ferris\"));\n/// ```\npub fn sorensen_dice(a: &str, b: &str) -> f64 {\n    // implementation guided by\n    // https://github.com/aceakash/string-similarity/blob/f83ba3cd7bae874c20c429774e911ae8cff8bced/src/index.js#L6\n\n    let a: String = a.chars().filter(|&x| !char::is_whitespace(x)).collect();\n    let b: String = b.chars().filter(|&x| !char::is_whitespace(x)).collect();\n\n    if a == b {\n        return 1.0;\n    }\n\n    if a.len() < 2 || b.len() < 2 {\n        return 0.0;\n    }\n\n    let mut a_bigrams: HashMap<(char, char), usize> = HashMap::new();\n\n    for bigram in bigrams(&a) {\n        *a_bigrams.entry(bigram).or_insert(0) += 1;\n    }\n\n    let mut intersection_size = 0_usize;\n\n    for bigram in bigrams(&b) {\n        a_bigrams.entry(bigram).and_modify(|bi| {\n            if *bi > 0 {\n                *bi -= 1;\n                intersection_size += 1;\n            }\n        });\n    }\n\n    (2 * intersection_size) as f64 / (a.len() + b.len() - 2) as f64\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    macro_rules! assert_delta {\n        ($x:expr, $y:expr) => {\n            assert_delta!($x, $y, 1e-5);\n        };\n        ($x:expr, $y:expr, $d:expr) => {\n            if ($x - $y).abs() > $d {\n                panic!(\n                    \"assertion failed: actual: `{}`, expected: `{}`: \\\n                    actual not within < {} of expected\",\n                    $x, $y, $d\n                );\n            }\n        };\n    }\n\n    #[test]\n    fn bigrams_iterator() {\n        let mut bi = bigrams(\"abcde\");\n\n        assert_eq!(Some(('a', 'b')), bi.next());\n        assert_eq!(Some(('b', 'c')), bi.next());\n        assert_eq!(Some(('c', 'd')), bi.next());\n        assert_eq!(Some(('d', 'e')), bi.next());\n        assert_eq!(None, bi.next());\n    }\n\n    fn assert_hamming_dist(dist: usize, str1: &str, str2: &str) {\n        assert_eq!(Ok(dist), hamming(str1, str2));\n    }\n\n    #[test]\n    fn hamming_empty() {\n        assert_hamming_dist(0, \"\", \"\")\n    }\n\n    #[test]\n    fn hamming_same() {\n        assert_hamming_dist(0, \"hamming\", \"hamming\")\n    }\n\n    #[test]\n    fn hamming_numbers() {\n        assert_eq!(Ok(1), generic_hamming(&[1, 2, 4], &[1, 2, 3]));\n    }\n\n    #[test]\n    fn hamming_diff() {\n        assert_hamming_dist(3, \"hamming\", \"hammers\")\n    }\n\n    #[test]\n    fn hamming_diff_multibyte() {\n        assert_hamming_dist(2, \"hamming\", \"h香mmüng\");\n    }\n\n    #[test]\n    fn hamming_unequal_length() {\n        assert_eq!(\n            Err(StrSimError::DifferentLengthArgs),\n            generic_hamming(\"ham\".chars(), \"hamming\".chars())\n        );\n    }\n\n    #[test]\n    fn hamming_names() {\n        assert_hamming_dist(14, \"Friedrich Nietzs\", \"Jean-Paul Sartre\")\n    }\n\n    #[test]\n    fn jaro_both_empty() {\n        assert_eq!(1.0, jaro(\"\", \"\"));\n    }\n\n    #[test]\n    fn jaro_first_empty() {\n        assert_eq!(0.0, jaro(\"\", \"jaro\"));\n    }\n\n    #[test]\n    fn jaro_second_empty() {\n        assert_eq!(0.0, jaro(\"distance\", \"\"));\n    }\n\n    #[test]\n    fn jaro_same() {\n        assert_eq!(1.0, jaro(\"jaro\", \"jaro\"));\n    }\n\n    #[test]\n    fn jaro_multibyte() {\n        assert_delta!(0.818, jaro(\"testabctest\", \"testöঙ香test\"), 0.001);\n        assert_delta!(0.818, jaro(\"testöঙ香test\", \"testabctest\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_diff_short() {\n        assert_delta!(0.767, jaro(\"dixon\", \"dicksonx\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_diff_one_character() {\n        assert_eq!(0.0, jaro(\"a\", \"b\"));\n    }\n\n    #[test]\n    fn jaro_same_one_character() {\n        assert_eq!(1.0, jaro(\"a\", \"a\"));\n    }\n\n    #[test]\n    fn generic_jaro_diff() {\n        assert_eq!(0.0, generic_jaro(&[1, 2], &[3, 4]));\n    }\n\n    #[test]\n    fn jaro_diff_one_and_two() {\n        assert_delta!(0.83, jaro(\"a\", \"ab\"), 0.01);\n    }\n\n    #[test]\n    fn jaro_diff_two_and_one() {\n        assert_delta!(0.83, jaro(\"ab\", \"a\"), 0.01);\n    }\n\n    #[test]\n    fn jaro_diff_no_transposition() {\n        assert_delta!(0.822, jaro(\"dwayne\", \"duane\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_diff_with_transposition() {\n        assert_delta!(0.944, jaro(\"martha\", \"marhta\"), 0.001);\n        assert_delta!(0.6, jaro(\"a jke\", \"jane a k\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_names() {\n        assert_delta!(\n            0.392,\n            jaro(\"Friedrich Nietzsche\", \"Jean-Paul Sartre\"),\n            0.001\n        );\n    }\n\n    #[test]\n    fn jaro_winkler_both_empty() {\n        assert_eq!(1.0, jaro_winkler(\"\", \"\"));\n    }\n\n    #[test]\n    fn jaro_winkler_first_empty() {\n        assert_eq!(0.0, jaro_winkler(\"\", \"jaro-winkler\"));\n    }\n\n    #[test]\n    fn jaro_winkler_second_empty() {\n        assert_eq!(0.0, jaro_winkler(\"distance\", \"\"));\n    }\n\n    #[test]\n    fn jaro_winkler_same() {\n        assert_eq!(1.0, jaro_winkler(\"Jaro-Winkler\", \"Jaro-Winkler\"));\n    }\n\n    #[test]\n    fn jaro_winkler_multibyte() {\n        assert_delta!(0.89, jaro_winkler(\"testabctest\", \"testöঙ香test\"), 0.001);\n        assert_delta!(0.89, jaro_winkler(\"testöঙ香test\", \"testabctest\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_winkler_diff_short() {\n        assert_delta!(0.813, jaro_winkler(\"dixon\", \"dicksonx\"), 0.001);\n        assert_delta!(0.813, jaro_winkler(\"dicksonx\", \"dixon\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_winkler_diff_one_character() {\n        assert_eq!(0.0, jaro_winkler(\"a\", \"b\"));\n    }\n\n    #[test]\n    fn jaro_winkler_same_one_character() {\n        assert_eq!(1.0, jaro_winkler(\"a\", \"a\"));\n    }\n\n    #[test]\n    fn jaro_winkler_diff_no_transposition() {\n        assert_delta!(0.84, jaro_winkler(\"dwayne\", \"duane\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_winkler_diff_with_transposition() {\n        assert_delta!(0.961, jaro_winkler(\"martha\", \"marhta\"), 0.001);\n        assert_delta!(0.6, jaro_winkler(\"a jke\", \"jane a k\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_winkler_names() {\n        assert_delta!(\n            0.452,\n            jaro_winkler(\"Friedrich Nietzsche\", \"Fran-Paul Sartre\"),\n            0.001\n        );\n    }\n\n    #[test]\n    fn jaro_winkler_long_prefix() {\n        assert_delta!(0.866, jaro_winkler(\"cheeseburger\", \"cheese fries\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_winkler_more_names() {\n        assert_delta!(0.868, jaro_winkler(\"Thorkel\", \"Thorgier\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_winkler_length_of_one() {\n        assert_delta!(0.738, jaro_winkler(\"Dinsdale\", \"D\"), 0.001);\n    }\n\n    #[test]\n    fn jaro_winkler_very_long_prefix() {\n        assert_delta!(\n            0.98519,\n            jaro_winkler(\"thequickbrownfoxjumpedoverx\", \"thequickbrownfoxjumpedovery\")\n        );\n    }\n\n    #[test]\n    fn levenshtein_empty() {\n        assert_eq!(0, levenshtein(\"\", \"\"));\n    }\n\n    #[test]\n    fn levenshtein_same() {\n        assert_eq!(0, levenshtein(\"levenshtein\", \"levenshtein\"));\n    }\n\n    #[test]\n    fn levenshtein_diff_short() {\n        assert_eq!(3, levenshtein(\"kitten\", \"sitting\"));\n    }\n\n    #[test]\n    fn levenshtein_diff_with_space() {\n        assert_eq!(5, levenshtein(\"hello, world\", \"bye, world\"));\n    }\n\n    #[test]\n    fn levenshtein_diff_multibyte() {\n        assert_eq!(3, levenshtein(\"öঙ香\", \"abc\"));\n        assert_eq!(3, levenshtein(\"abc\", \"öঙ香\"));\n    }\n\n    #[test]\n    fn levenshtein_diff_longer() {\n        let a = \"The quick brown fox jumped over the angry dog.\";\n        let b = \"Lorem ipsum dolor sit amet, dicta latine an eam.\";\n        assert_eq!(37, levenshtein(a, b));\n    }\n\n    #[test]\n    fn levenshtein_first_empty() {\n        assert_eq!(7, levenshtein(\"\", \"sitting\"));\n    }\n\n    #[test]\n    fn levenshtein_second_empty() {\n        assert_eq!(6, levenshtein(\"kitten\", \"\"));\n    }\n\n    #[test]\n    fn normalized_levenshtein_diff_short() {\n        assert_delta!(0.57142, normalized_levenshtein(\"kitten\", \"sitting\"));\n    }\n\n    #[test]\n    fn normalized_levenshtein_for_empty_strings() {\n        assert_delta!(1.0, normalized_levenshtein(\"\", \"\"));\n    }\n\n    #[test]\n    fn normalized_levenshtein_first_empty() {\n        assert_delta!(0.0, normalized_levenshtein(\"\", \"second\"));\n    }\n\n    #[test]\n    fn normalized_levenshtein_second_empty() {\n        assert_delta!(0.0, normalized_levenshtein(\"first\", \"\"));\n    }\n\n    #[test]\n    fn normalized_levenshtein_identical_strings() {\n        assert_delta!(1.0, normalized_levenshtein(\"identical\", \"identical\"));\n    }\n\n    #[test]\n    fn osa_distance_empty() {\n        assert_eq!(0, osa_distance(\"\", \"\"));\n    }\n\n    #[test]\n    fn osa_distance_same() {\n        assert_eq!(0, osa_distance(\"damerau\", \"damerau\"));\n    }\n\n    #[test]\n    fn osa_distance_first_empty() {\n        assert_eq!(7, osa_distance(\"\", \"damerau\"));\n    }\n\n    #[test]\n    fn osa_distance_second_empty() {\n        assert_eq!(7, osa_distance(\"damerau\", \"\"));\n    }\n\n    #[test]\n    fn osa_distance_diff() {\n        assert_eq!(3, osa_distance(\"ca\", \"abc\"));\n    }\n\n    #[test]\n    fn osa_distance_diff_short() {\n        assert_eq!(3, osa_distance(\"damerau\", \"aderua\"));\n    }\n\n    #[test]\n    fn osa_distance_diff_reversed() {\n        assert_eq!(3, osa_distance(\"aderua\", \"damerau\"));\n    }\n\n    #[test]\n    fn osa_distance_diff_multibyte() {\n        assert_eq!(3, osa_distance(\"öঙ香\", \"abc\"));\n        assert_eq!(3, osa_distance(\"abc\", \"öঙ香\"));\n    }\n\n    #[test]\n    fn osa_distance_diff_unequal_length() {\n        assert_eq!(6, osa_distance(\"damerau\", \"aderuaxyz\"));\n    }\n\n    #[test]\n    fn osa_distance_diff_unequal_length_reversed() {\n        assert_eq!(6, osa_distance(\"aderuaxyz\", \"damerau\"));\n    }\n\n    #[test]\n    fn osa_distance_diff_comedians() {\n        assert_eq!(5, osa_distance(\"Stewart\", \"Colbert\"));\n    }\n\n    #[test]\n    fn osa_distance_many_transpositions() {\n        assert_eq!(4, osa_distance(\"abcdefghijkl\", \"bacedfgihjlk\"));\n    }\n\n    #[test]\n    fn osa_distance_diff_longer() {\n        let a = \"The quick brown fox jumped over the angry dog.\";\n        let b = \"Lehem ipsum dolor sit amet, dicta latine an eam.\";\n        assert_eq!(36, osa_distance(a, b));\n    }\n\n    #[test]\n    fn osa_distance_beginning_transposition() {\n        assert_eq!(1, osa_distance(\"foobar\", \"ofobar\"));\n    }\n\n    #[test]\n    fn osa_distance_end_transposition() {\n        assert_eq!(1, osa_distance(\"specter\", \"spectre\"));\n    }\n\n    #[test]\n    fn osa_distance_restricted_edit() {\n        assert_eq!(4, osa_distance(\"a cat\", \"an abct\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_empty() {\n        assert_eq!(0, damerau_levenshtein(\"\", \"\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_same() {\n        assert_eq!(0, damerau_levenshtein(\"damerau\", \"damerau\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_first_empty() {\n        assert_eq!(7, damerau_levenshtein(\"\", \"damerau\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_second_empty() {\n        assert_eq!(7, damerau_levenshtein(\"damerau\", \"\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_diff() {\n        assert_eq!(2, damerau_levenshtein(\"ca\", \"abc\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_diff_short() {\n        assert_eq!(3, damerau_levenshtein(\"damerau\", \"aderua\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_diff_reversed() {\n        assert_eq!(3, damerau_levenshtein(\"aderua\", \"damerau\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_diff_multibyte() {\n        assert_eq!(3, damerau_levenshtein(\"öঙ香\", \"abc\"));\n        assert_eq!(3, damerau_levenshtein(\"abc\", \"öঙ香\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_diff_unequal_length() {\n        assert_eq!(6, damerau_levenshtein(\"damerau\", \"aderuaxyz\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_diff_unequal_length_reversed() {\n        assert_eq!(6, damerau_levenshtein(\"aderuaxyz\", \"damerau\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_diff_comedians() {\n        assert_eq!(5, damerau_levenshtein(\"Stewart\", \"Colbert\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_many_transpositions() {\n        assert_eq!(4, damerau_levenshtein(\"abcdefghijkl\", \"bacedfgihjlk\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_diff_longer() {\n        let a = \"The quick brown fox jumped over the angry dog.\";\n        let b = \"Lehem ipsum dolor sit amet, dicta latine an eam.\";\n        assert_eq!(36, damerau_levenshtein(a, b));\n    }\n\n    #[test]\n    fn damerau_levenshtein_beginning_transposition() {\n        assert_eq!(1, damerau_levenshtein(\"foobar\", \"ofobar\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_end_transposition() {\n        assert_eq!(1, damerau_levenshtein(\"specter\", \"spectre\"));\n    }\n\n    #[test]\n    fn damerau_levenshtein_unrestricted_edit() {\n        assert_eq!(3, damerau_levenshtein(\"a cat\", \"an abct\"));\n    }\n\n    #[test]\n    fn normalized_damerau_levenshtein_diff_short() {\n        assert_delta!(\n            0.27272,\n            normalized_damerau_levenshtein(\"levenshtein\", \"löwenbräu\")\n        );\n    }\n\n    #[test]\n    fn normalized_damerau_levenshtein_for_empty_strings() {\n        assert_delta!(1.0, normalized_damerau_levenshtein(\"\", \"\"));\n    }\n\n    #[test]\n    fn normalized_damerau_levenshtein_first_empty() {\n        assert_delta!(0.0, normalized_damerau_levenshtein(\"\", \"flower\"));\n    }\n\n    #[test]\n    fn normalized_damerau_levenshtein_second_empty() {\n        assert_delta!(0.0, normalized_damerau_levenshtein(\"tree\", \"\"));\n    }\n\n    #[test]\n    fn normalized_damerau_levenshtein_identical_strings() {\n        assert_delta!(\n            1.0,\n            normalized_damerau_levenshtein(\"sunglasses\", \"sunglasses\")\n        );\n    }\n\n    #[test]\n    fn sorensen_dice_all() {\n        // test cases taken from\n        // https://github.com/aceakash/string-similarity/blob/f83ba3cd7bae874c20c429774e911ae8cff8bced/src/spec/index.spec.js#L11\n\n        assert_delta!(1.0, sorensen_dice(\"a\", \"a\"));\n        assert_delta!(0.0, sorensen_dice(\"a\", \"b\"));\n        assert_delta!(1.0, sorensen_dice(\"\", \"\"));\n        assert_delta!(0.0, sorensen_dice(\"a\", \"\"));\n        assert_delta!(0.0, sorensen_dice(\"\", \"a\"));\n        assert_delta!(1.0, sorensen_dice(\"apple event\", \"apple    event\"));\n        assert_delta!(0.90909, sorensen_dice(\"iphone\", \"iphone x\"));\n        assert_delta!(0.0, sorensen_dice(\"french\", \"quebec\"));\n        assert_delta!(1.0, sorensen_dice(\"france\", \"france\"));\n        assert_delta!(0.2, sorensen_dice(\"fRaNce\", \"france\"));\n        assert_delta!(0.8, sorensen_dice(\"healed\", \"sealed\"));\n        assert_delta!(\n            0.78788,\n            sorensen_dice(\"web applications\", \"applications of the web\")\n        );\n        assert_delta!(\n            0.92,\n            sorensen_dice(\n                \"this will have a typo somewhere\",\n                \"this will huve a typo somewhere\"\n            )\n        );\n        assert_delta!(\n            0.60606,\n            sorensen_dice(\n                \"Olive-green table for sale, in extremely good condition.\",\n                \"For sale: table in very good  condition, olive green in colour.\"\n            )\n        );\n        assert_delta!(\n            0.25581,\n            sorensen_dice(\n                \"Olive-green table for sale, in extremely good condition.\",\n                \"For sale: green Subaru Impreza, 210,000 miles\"\n            )\n        );\n        assert_delta!(\n            0.14118,\n            sorensen_dice(\n                \"Olive-green table for sale, in extremely good condition.\",\n                \"Wanted: mountain bike with at least 21 gears.\"\n            )\n        );\n        assert_delta!(\n            0.77419,\n            sorensen_dice(\"this has one extra word\", \"this has one word\")\n        );\n    }\n}\n"
  },
  {
    "path": "tests/lib.rs",
    "content": "extern crate strsim;\n\nuse strsim::{\n    damerau_levenshtein, hamming, jaro, jaro_winkler, levenshtein, normalized_damerau_levenshtein,\n    normalized_levenshtein, osa_distance,\n};\n\nmacro_rules! assert_delta {\n    ($x:expr, $y:expr) => {\n        assert_delta!($x, $y, 1e-5);\n    };\n    ($x:expr, $y:expr, $d:expr) => {\n        if ($x - $y).abs() > $d {\n            panic!(\n                \"assertion failed: actual: `{}`, expected: `{}`: \\\n                actual not within < {} of expected\",\n                $x, $y, $d\n            );\n        }\n    };\n}\n\n#[test]\nfn hamming_works() {\n    match hamming(\"hamming\", \"hammers\") {\n        Ok(distance) => assert_eq!(3, distance),\n        Err(why) => panic!(\"{:?}\", why),\n    }\n}\n\n#[test]\nfn levenshtein_works() {\n    assert_eq!(3, levenshtein(\"kitten\", \"sitting\"));\n}\n\n#[test]\nfn normalized_levenshtein_works() {\n    assert_delta!(0.57142, normalized_levenshtein(\"kitten\", \"sitting\"));\n}\n\n#[test]\nfn osa_distance_works() {\n    assert_eq!(3, osa_distance(\"ac\", \"cba\"));\n}\n\n#[test]\nfn damerau_levenshtein_works() {\n    assert_eq!(2, damerau_levenshtein(\"ac\", \"cba\"));\n}\n\n#[test]\nfn normalized_damerau_levenshtein_works() {\n    assert_delta!(\n        0.27272,\n        normalized_damerau_levenshtein(\"levenshtein\", \"löwenbräu\")\n    );\n}\n\n#[test]\nfn jaro_works() {\n    assert_delta!(\n        0.392,\n        jaro(\"Friedrich Nietzsche\", \"Jean-Paul Sartre\"),\n        0.001\n    );\n}\n\n#[test]\nfn jaro_winkler_works() {\n    assert_delta!(0.866, jaro_winkler(\"cheeseburger\", \"cheese fries\"), 0.001);\n}\n"
  }
]