├── examples ├── files │ ├── test-1.txt │ ├── test-10.txt │ ├── test-11.txt │ ├── test-2.txt │ ├── test-21.txt │ └── test-3.txt └── sort_files_by_file_names.rs ├── .github ├── dependabot.yml └── workflows │ ├── ci-version.yml │ └── ci.yml ├── Cargo.toml ├── LICENSE ├── rustfmt.toml ├── README.md ├── benches └── bench.rs ├── .gitignore ├── src ├── lib.rs └── std_functions.rs └── tests └── tests.rs /examples/files/test-1.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/files/test-10.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/files/test-11.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/files/test-2.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/files/test-21.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/files/test-3.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /examples/sort_files_by_file_names.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | const FILES_PATH: &str = "examples/files"; 4 | 5 | fn main() { 6 | let files = Path::new(FILES_PATH); 7 | 8 | let dir_files_iter = files.read_dir().unwrap(); 9 | 10 | let mut sub_files: Vec = dir_files_iter.map(|f| f.unwrap().path()).collect(); 11 | 12 | sub_files.sort(); 13 | 14 | println!("Native Sort: {:#?}", sub_files); 15 | 16 | sub_files.sort(); 17 | 18 | alphanumeric_sort::sort_path_slice(&mut sub_files); 19 | 20 | println!("Alphanumeric Sort: {:#?}", sub_files); 21 | } 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alphanumeric-sort" 3 | version = "1.5.5" 4 | authors = ["Magic Len "] 5 | edition = "2021" 6 | rust-version = "1.56" 7 | repository = "https://github.com/magiclen/alphanumeric-sort" 8 | homepage = "https://magiclen.org/alphanumeric-sort" 9 | keywords = ["sort", "alphanumeric", "path", "file", "number"] 10 | categories = ["no-std", "algorithms"] 11 | description = "This crate can help you sort order for files and folders whose names contain numerals." 12 | license = "MIT" 13 | include = ["src/**/*", "Cargo.toml", "README.md", "LICENSE", "benches/bench.rs", "examples/sort_files_by_file_names.rs"] 14 | 15 | [dependencies] 16 | 17 | [dev-dependencies] 18 | bencher = "0.1.5" 19 | 20 | [features] 21 | default = ["std"] 22 | 23 | std = [] 24 | 25 | [[example]] 26 | name = "sort_files_by_file_names" 27 | required-features = ["std"] 28 | 29 | [[bench]] 30 | name = "bench" 31 | required-features = ["std"] 32 | harness = false 33 | 34 | [package.metadata.docs.rs] 35 | all-features = true 36 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 magiclen.org (Ron Li) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci-version.yml: -------------------------------------------------------------------------------- 1 | name: CI-version 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | tests: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | - macos-latest 19 | - windows-latest 20 | toolchain: 21 | - stable 22 | - nightly 23 | features: 24 | - 25 | - --no-default-features 26 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - uses: actions/checkout@v6 30 | - uses: actions-rust-lang/setup-rust-toolchain@v1 31 | with: 32 | toolchain: ${{ matrix.toolchain }} 33 | - run: cargo test --release ${{ matrix.features }} 34 | - run: cargo doc --release ${{ matrix.features }} 35 | 36 | MSRV: 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | os: 41 | - ubuntu-latest 42 | - macos-latest 43 | - windows-latest 44 | toolchain: 45 | - 1.56 46 | features: 47 | - 48 | - --no-default-features 49 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 50 | runs-on: ${{ matrix.os }} 51 | steps: 52 | - uses: actions/checkout@v6 53 | - uses: actions-rust-lang/setup-rust-toolchain@v1 54 | with: 55 | toolchain: ${{ matrix.toolchain }} 56 | - run: cargo test --release --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # array_width = 60 2 | # attr_fn_like_width = 70 3 | binop_separator = "Front" 4 | blank_lines_lower_bound = 0 5 | blank_lines_upper_bound = 1 6 | brace_style = "PreferSameLine" 7 | # chain_width = 60 8 | color = "Auto" 9 | # comment_width = 100 10 | condense_wildcard_suffixes = true 11 | control_brace_style = "AlwaysSameLine" 12 | empty_item_single_line = true 13 | enum_discrim_align_threshold = 80 14 | error_on_line_overflow = false 15 | error_on_unformatted = false 16 | # fn_call_width = 60 17 | fn_params_layout = "Tall" 18 | fn_single_line = false 19 | force_explicit_abi = true 20 | force_multiline_blocks = false 21 | format_code_in_doc_comments = true 22 | doc_comment_code_block_width = 80 23 | format_generated_files = true 24 | format_macro_matchers = true 25 | format_macro_bodies = true 26 | skip_macro_invocations = [] 27 | format_strings = true 28 | hard_tabs = false 29 | hex_literal_case = "Upper" 30 | imports_indent = "Block" 31 | imports_layout = "Mixed" 32 | indent_style = "Block" 33 | inline_attribute_width = 0 34 | match_arm_blocks = true 35 | match_arm_leading_pipes = "Never" 36 | match_block_trailing_comma = true 37 | max_width = 100 38 | merge_derives = true 39 | imports_granularity = "Crate" 40 | newline_style = "Unix" 41 | normalize_comments = false 42 | normalize_doc_attributes = true 43 | overflow_delimited_expr = true 44 | remove_nested_parens = true 45 | reorder_impl_items = true 46 | reorder_imports = true 47 | group_imports = "StdExternalCrate" 48 | reorder_modules = true 49 | short_array_element_width_threshold = 10 50 | # single_line_if_else_max_width = 50 51 | space_after_colon = true 52 | space_before_colon = false 53 | spaces_around_ranges = false 54 | struct_field_align_threshold = 80 55 | struct_lit_single_line = false 56 | # struct_lit_width = 18 57 | # struct_variant_width = 35 58 | tab_spaces = 4 59 | trailing_comma = "Vertical" 60 | trailing_semicolon = true 61 | type_punctuation_density = "Wide" 62 | use_field_init_shorthand = true 63 | use_small_heuristics = "Max" 64 | use_try_shorthand = true 65 | where_single_line = false 66 | wrap_comments = false -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [ push, pull_request ] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | rustfmt: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v6 13 | - uses: actions-rust-lang/setup-rust-toolchain@v1 14 | with: 15 | toolchain: nightly 16 | components: rustfmt 17 | - uses: actions-rust-lang/rustfmt@v1 18 | 19 | clippy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v6 23 | - uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | components: clippy 26 | - run: cargo clippy --all-targets --all-features -- -D warnings 27 | 28 | tests: 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: 33 | - ubuntu-latest 34 | - macos-latest 35 | - windows-latest 36 | toolchain: 37 | - stable 38 | - nightly 39 | features: 40 | - 41 | - --no-default-features 42 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 43 | runs-on: ${{ matrix.os }} 44 | steps: 45 | - uses: actions/checkout@v6 46 | - uses: actions-rust-lang/setup-rust-toolchain@v1 47 | with: 48 | toolchain: ${{ matrix.toolchain }} 49 | - run: cargo test ${{ matrix.features }} 50 | - run: cargo doc ${{ matrix.features }} 51 | 52 | MSRV: 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | os: 57 | - ubuntu-latest 58 | - macos-latest 59 | - windows-latest 60 | toolchain: 61 | - 1.56 62 | features: 63 | - 64 | - --no-default-features 65 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 66 | runs-on: ${{ matrix.os }} 67 | steps: 68 | - uses: actions/checkout@v6 69 | - uses: actions-rust-lang/setup-rust-toolchain@v1 70 | with: 71 | toolchain: ${{ matrix.toolchain }} 72 | - run: cargo test --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Alphanumeric Sort 2 | ==================== 3 | 4 | [![CI](https://github.com/magiclen/alphanumeric-sort/actions/workflows/ci.yml/badge.svg)](https://github.com/magiclen/alphanumeric-sort/actions/workflows/ci.yml) 5 | 6 | This crate can help you sort order for files and folders whose names contain numerals. 7 | 8 | ## Motives and Examples 9 | 10 | With the Rust native `sort` method, strings and paths are arranged into lexicographical order. In some cases, it is not so intuitive. For example, there are screen snap shots named by **shot-%N** like **shot-2**, **shot-1**, **shot-11**. After a lexicographical sorting, they will be ordered into **shot-1**, **shot-11**, **shot-2**. However, we would prefer **shot-1**, **shot-2**, **shot-11** mostly. 11 | 12 | ```rust 13 | let mut names = ["shot-2", "shot-1", "shot-11"]; 14 | 15 | names.sort(); 16 | 17 | assert_eq!(["shot-1", "shot-11", "shot-2"], names); 18 | ``` 19 | 20 | Thus, in this kind of case, an alphanumeric sort might come in handy. 21 | 22 | ```rust 23 | let mut names = ["shot-2", "shot-1", "shot-11"]; 24 | 25 | alphanumeric_sort::sort_str_slice(&mut names); 26 | 27 | assert_eq!(["shot-1", "shot-2", "shot-11"], names); 28 | ``` 29 | 30 | ```rust 31 | use std::path::Path; 32 | 33 | let mut paths = [Path::new("shot-2"), Path::new("shot-1"), Path::new("shot-11")]; 34 | 35 | alphanumeric_sort::sort_path_slice(&mut paths); 36 | 37 | assert_eq!([Path::new("shot-1"), Path::new("shot-2"), Path::new("shot-11")], paths); 38 | ``` 39 | 40 | ## About the `compare_*` Functions and the `sort_*` Functions 41 | 42 | To sort a slice, the code can also be written like, 43 | 44 | ```rust 45 | use std::path::Path; 46 | 47 | let mut paths = [Path::new("shot-2"), Path::new("shot-1"), Path::new("shot-11")]; 48 | 49 | paths.sort_by(|a, b| alphanumeric_sort::compare_path(a, b)); 50 | 51 | assert_eq!([Path::new("shot-1"), Path::new("shot-2"), Path::new("shot-11")], paths); 52 | ``` 53 | 54 | But it is not recommended because the `compare_*` functions try to convert data (e.g `Path`, `CStr`) to `&str` every time in its execution and thus they are slower than the `sort_*` functions when sorting a slice. 55 | 56 | ## Version `1.3` to `1.4` 57 | 58 | No breaking change in API is made, though the order has some changes. 59 | 60 | * `"0001"` is greater than `"001"` instead of being equal. 61 | * `"中"` is greater than `"1"` instead of being less. `"第1章"` is still less than `"第1-2章"`, even though `"章"` is greater than `"-"`. 62 | 63 | ## No Std 64 | 65 | Disable the default features to compile this crate without std. 66 | 67 | ```toml 68 | [dependencies.alphanumeric-sort] 69 | version = "*" 70 | default-features = false 71 | ``` 72 | 73 | ## Benchmark 74 | 75 | ```bash 76 | cargo bench 77 | ``` 78 | 79 | ## Crates.io 80 | 81 | https://crates.io/crates/alphanumeric-sort 82 | 83 | ## Documentation 84 | 85 | https://docs.rs/alphanumeric-sort 86 | 87 | ## License 88 | 89 | [MIT](LICENSE) -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use bencher::{benchmark_group, benchmark_main, Bencher}; 4 | 5 | #[derive(Debug)] 6 | struct P(&'static Path); 7 | 8 | impl From<&'static str> for P { 9 | #[inline] 10 | fn from(s: &'static str) -> Self { 11 | P(Path::new(s)) 12 | } 13 | } 14 | 15 | fn sort_slice_by_path_key(bencher: &mut Bencher) { 16 | bencher.iter(|| { 17 | let mut array = [ 18 | P::from("第10-15-2章"), 19 | P::from("第1-2章"), 20 | P::from("第2-4章"), 21 | P::from("第2-33章"), 22 | P::from("第1章"), 23 | P::from("第1-4章"), 24 | P::from("第2-3章"), 25 | P::from("第1-11章"), 26 | P::from("第10-1章"), 27 | P::from("第3-1章"), 28 | P::from("第2-10章"), 29 | P::from("第2-2章"), 30 | P::from("第1-3章"), 31 | P::from("第10-15章"), 32 | P::from("第10-2章"), 33 | P::from("第10-15-1章"), 34 | P::from("第2-1章"), 35 | P::from("第2-12章"), 36 | P::from("第1-10章"), 37 | P::from("第3-10章"), 38 | ]; 39 | 40 | alphanumeric_sort::sort_slice_by_path_key(&mut array, |e| e.0); 41 | 42 | array 43 | }); 44 | } 45 | 46 | fn sort_path_slice(bencher: &mut Bencher) { 47 | bencher.iter(|| { 48 | let mut array = [ 49 | Path::new("第10-15-2章"), 50 | Path::new("第1-2章"), 51 | Path::new("第2-4章"), 52 | Path::new("第2-33章"), 53 | Path::new("第1章"), 54 | Path::new("第1-4章"), 55 | Path::new("第2-3章"), 56 | Path::new("第1-11章"), 57 | Path::new("第10-1章"), 58 | Path::new("第3-1章"), 59 | Path::new("第2-10章"), 60 | Path::new("第2-2章"), 61 | Path::new("第1-3章"), 62 | Path::new("第10-15章"), 63 | Path::new("第10-2章"), 64 | Path::new("第10-15-1章"), 65 | Path::new("第2-1章"), 66 | Path::new("第2-12章"), 67 | Path::new("第1-10章"), 68 | Path::new("第3-10章"), 69 | ]; 70 | 71 | alphanumeric_sort::sort_path_slice(&mut array); 72 | 73 | array 74 | }); 75 | } 76 | 77 | fn compare_path(bencher: &mut Bencher) { 78 | bencher.iter(|| { 79 | let mut array = [ 80 | Path::new("第10-15-2章"), 81 | Path::new("第1-2章"), 82 | Path::new("第2-4章"), 83 | Path::new("第2-33章"), 84 | Path::new("第1章"), 85 | Path::new("第1-4章"), 86 | Path::new("第2-3章"), 87 | Path::new("第1-11章"), 88 | Path::new("第10-1章"), 89 | Path::new("第3-1章"), 90 | Path::new("第2-10章"), 91 | Path::new("第2-2章"), 92 | Path::new("第1-3章"), 93 | Path::new("第10-15章"), 94 | Path::new("第10-2章"), 95 | Path::new("第10-15-1章"), 96 | Path::new("第2-1章"), 97 | Path::new("第2-12章"), 98 | Path::new("第1-10章"), 99 | Path::new("第3-10章"), 100 | ]; 101 | 102 | array.sort_by(|a, b| alphanumeric_sort::compare_path(a, b)); 103 | 104 | array 105 | }); 106 | } 107 | 108 | benchmark_group!(sort_path_compare_path, sort_slice_by_path_key, sort_path_slice, compare_path); 109 | benchmark_main!(sort_path_compare_path); 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Intellij+all ### 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # AWS User-specific 13 | .idea/**/aws.xml 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/artifacts 36 | # .idea/compiler.xml 37 | # .idea/jarRepositories.xml 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | # *.iml 42 | # *.ipr 43 | 44 | # CMake 45 | cmake-build-*/ 46 | 47 | # Mongo Explorer plugin 48 | .idea/**/mongoSettings.xml 49 | 50 | # File-based project format 51 | *.iws 52 | 53 | # IntelliJ 54 | out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # SonarLint plugin 66 | .idea/sonarlint/ 67 | 68 | # Crashlytics plugin (for Android Studio and IntelliJ) 69 | com_crashlytics_export_strings.xml 70 | crashlytics.properties 71 | crashlytics-build.properties 72 | fabric.properties 73 | 74 | # Editor-based Rest Client 75 | .idea/httpRequests 76 | 77 | # Android studio 3.1+ serialized cache file 78 | .idea/caches/build_file_checksums.ser 79 | 80 | ### Intellij+all Patch ### 81 | # Ignore everything but code style settings and run configurations 82 | # that are supposed to be shared within teams. 83 | 84 | .idea/* 85 | 86 | !.idea/codeStyles 87 | !.idea/runConfigurations 88 | 89 | ### Rust ### 90 | # Generated by Cargo 91 | # will have compiled files and executables 92 | debug/ 93 | target/ 94 | 95 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 96 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 97 | Cargo.lock 98 | 99 | # These are backup files generated by rustfmt 100 | **/*.rs.bk 101 | 102 | # MSVC Windows builds of rustc generate these, which store debugging information 103 | *.pdb 104 | 105 | ### Vim ### 106 | # Swap 107 | [._]*.s[a-v][a-z] 108 | !*.svg # comment out if you don't need vector files 109 | [._]*.sw[a-p] 110 | [._]s[a-rt-v][a-z] 111 | [._]ss[a-gi-z] 112 | [._]sw[a-p] 113 | 114 | # Session 115 | Session.vim 116 | Sessionx.vim 117 | 118 | # Temporary 119 | .netrwhist 120 | *~ 121 | # Auto-generated tag files 122 | tags 123 | # Persistent undo 124 | [._]*.un~ 125 | 126 | ### VisualStudioCode ### 127 | .vscode/* 128 | !.vscode/settings.json 129 | !.vscode/tasks.json 130 | !.vscode/launch.json 131 | !.vscode/extensions.json 132 | !.vscode/*.code-snippets 133 | 134 | # Local History for Visual Studio Code 135 | .history/ 136 | 137 | # Built Visual Studio Code Extensions 138 | *.vsix 139 | 140 | ### VisualStudioCode Patch ### 141 | # Ignore all local history of files 142 | .history 143 | .ionide -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # Alphanumeric Sort 3 | 4 | This crate can help you sort order for files and folders whose names contain numerals. 5 | 6 | ## Motives and Examples 7 | 8 | With the Rust native `sort` method, strings and paths are arranged into lexicographical order. In some cases, it is not so intuitive. For example, there are screen snap shots named by **shot-%N** like **shot-2**, **shot-1**, **shot-11**. After a lexicographical sorting, they will be ordered into **shot-1**, **shot-11**, **shot-2**. However, we would prefer **shot-1**, **shot-2**, **shot-11** mostly. 9 | 10 | ```rust 11 | let mut names = ["shot-2", "shot-1", "shot-11"]; 12 | 13 | names.sort(); 14 | 15 | assert_eq!(["shot-1", "shot-11", "shot-2"], names); 16 | ``` 17 | 18 | Thus, in this kind of case, an alphanumeric sort might come in handy. 19 | 20 | ```rust 21 | let mut names = ["shot-2", "shot-1", "shot-11"]; 22 | 23 | alphanumeric_sort::sort_str_slice(&mut names); 24 | 25 | assert_eq!(["shot-1", "shot-2", "shot-11"], names); 26 | ``` 27 | 28 | ```rust 29 | # #[cfg(feature = "std")] { 30 | use std::path::Path; 31 | 32 | let mut paths = [Path::new("shot-2"), Path::new("shot-1"), Path::new("shot-11")]; 33 | 34 | alphanumeric_sort::sort_path_slice(&mut paths); 35 | 36 | assert_eq!([Path::new("shot-1"), Path::new("shot-2"), Path::new("shot-11")], paths); 37 | # } 38 | ``` 39 | 40 | ## About the `compare_*` Functions and the `sort_*` Functions 41 | 42 | To sort a slice, the code can also be written like, 43 | 44 | ```rust 45 | # #[cfg(feature = "std")] { 46 | use std::path::Path; 47 | 48 | let mut paths = [Path::new("shot-2"), Path::new("shot-1"), Path::new("shot-11")]; 49 | 50 | paths.sort_by(|a, b| alphanumeric_sort::compare_path(a, b)); 51 | 52 | assert_eq!([Path::new("shot-1"), Path::new("shot-2"), Path::new("shot-11")], paths); 53 | # } 54 | ``` 55 | 56 | But it is not recommended because the `compare_*` functions try to convert data (e.g `Path`, `CStr`) to `&str` every time in its execution and thus they are slower than the `sort_*` functions when sorting a slice. 57 | 58 | ## Version `1.3` to `1.4` 59 | 60 | No breaking change in API is made, though the order has some changes. 61 | 62 | * `"0001"` is greater than `"001"` instead of being equal. 63 | * `"中"` is greater than `"1"` instead of being less. `"第1章"` is still less than `"第1-2章"`, even though `"章"` is greater than `"-"`. 64 | 65 | ## No Std 66 | 67 | Disable the default features to compile this crate without std. 68 | 69 | ```toml 70 | [dependencies.alphanumeric-sort] 71 | version = "*" 72 | default-features = false 73 | ``` 74 | 75 | ## Benchmark 76 | 77 | ```bash 78 | cargo bench 79 | ``` 80 | */ 81 | 82 | #![cfg_attr(not(feature = "std"), no_std)] 83 | #![cfg_attr(docsrs, feature(doc_cfg))] 84 | 85 | extern crate alloc; // used for sorting 86 | 87 | #[cfg(feature = "std")] 88 | mod std_functions; 89 | 90 | use core::{cmp::Ordering, str::Chars}; 91 | 92 | #[cfg(feature = "std")] 93 | pub use std_functions::*; 94 | 95 | /// Compare two strings. 96 | pub fn compare_str, B: AsRef>(a: A, b: B) -> Ordering { 97 | let mut c1 = a.as_ref().chars(); 98 | let mut c2 = b.as_ref().chars(); 99 | 100 | // this flag is to handle something like "1點" < "1-1點" 101 | let mut last_is_number = false; 102 | 103 | // this flag is to handle something like "1a" > "01" 104 | let mut pre_anwser = Ordering::Equal; 105 | 106 | let mut v1: Option = None; 107 | let mut v2: Option = None; 108 | 109 | loop { 110 | let mut ca = { 111 | match v1.take() { 112 | Some(c) => c, 113 | None => match c1.next() { 114 | Some(c) => c, 115 | None => { 116 | if v2.take().is_some() || c2.next().is_some() { 117 | return Ordering::Less; 118 | } else { 119 | return pre_anwser; 120 | } 121 | }, 122 | }, 123 | } 124 | }; 125 | 126 | let mut cb = { 127 | match v2.take() { 128 | Some(c) => c, 129 | None => match c2.next() { 130 | Some(c) => c, 131 | None => { 132 | return Ordering::Greater; 133 | }, 134 | }, 135 | } 136 | }; 137 | 138 | if ca.is_ascii_digit() && cb.is_ascii_digit() { 139 | // count the digit length, but ignore the leading zeros and the following same part (prefix) 140 | let mut la = 1usize; 141 | let mut lb = 1usize; 142 | 143 | // this counter is to handle something like "001" > "01" 144 | let mut lc = 0isize; 145 | 146 | // find the first non-zero digit in c1 147 | while ca == '0' { 148 | lc += 1; 149 | if let Some(c) = c1.next() { 150 | if c.is_ascii_digit() { 151 | ca = c; 152 | } else { 153 | v1 = Some(c); 154 | la = 0; 155 | break; 156 | } 157 | } else { 158 | la = 0; 159 | break; 160 | } 161 | } 162 | 163 | // find the first non-zero digit in c2 164 | while cb == '0' { 165 | lc -= 1; 166 | if let Some(c) = c2.next() { 167 | if c.is_ascii_digit() { 168 | cb = c; 169 | } else { 170 | v2 = Some(c); 171 | lb = 0; 172 | break; 173 | } 174 | } else { 175 | lb = 0; 176 | break; 177 | } 178 | } 179 | 180 | // consume the remaining ascii digit 181 | let consume_ascii_digit = |chars: &mut Chars, store: &mut Option| { 182 | let mut counter = 0; 183 | 184 | for c in chars.by_ref() { 185 | if c.is_ascii_digit() { 186 | counter += 1; 187 | } else { 188 | *store = Some(c); 189 | break; 190 | } 191 | } 192 | 193 | counter 194 | }; 195 | 196 | let mut ordering = Ordering::Equal; 197 | 198 | if la == 0 { 199 | if lb == 0 { 200 | // e.g. 000 vs 000, 000 vs 0000, 0000 vs 000 201 | } else { 202 | // e.g. 0000 vs 001 203 | 204 | return Ordering::Less; 205 | } 206 | } else if lb == 0 { 207 | // e.g. 001 vs 0000 208 | 209 | return Ordering::Greater; 210 | } else { 211 | // e.g. 1 vs 12, 001 vs 0012 212 | 213 | // skip the same prefix and compare the next ascii digit 214 | loop { 215 | ordering = ca.cmp(&cb); 216 | 217 | if ordering == Ordering::Equal { 218 | if let Some(c) = c1.next() { 219 | if c.is_ascii_digit() { 220 | if let Some(cc) = c2.next() { 221 | if cc.is_ascii_digit() { 222 | ca = c; 223 | cb = cc; 224 | } else { 225 | return Ordering::Greater; 226 | } 227 | } else { 228 | return Ordering::Greater; 229 | } 230 | } else { 231 | let n = consume_ascii_digit(&mut c2, &mut v2); 232 | v1 = Some(c); 233 | 234 | if n > 0 { 235 | return Ordering::Less; 236 | } 237 | 238 | break; 239 | } 240 | } else if c2.next().is_some() { 241 | return Ordering::Less; 242 | } else { 243 | break; 244 | } 245 | } else { 246 | la += consume_ascii_digit(&mut c1, &mut v1); 247 | lb += consume_ascii_digit(&mut c2, &mut v2); 248 | 249 | if la != lb { 250 | ordering = la.cmp(&lb); 251 | } 252 | 253 | break; 254 | } 255 | } 256 | } 257 | 258 | if ordering == Ordering::Equal { 259 | match lc.cmp(&0) { 260 | Ordering::Equal => { 261 | last_is_number = true; 262 | }, 263 | Ordering::Greater => { 264 | if pre_anwser == Ordering::Equal { 265 | pre_anwser = Ordering::Greater; 266 | } else { 267 | // ignore 268 | } 269 | }, 270 | Ordering::Less => { 271 | if pre_anwser == Ordering::Equal { 272 | pre_anwser = Ordering::Less; 273 | } else { 274 | // ignore 275 | } 276 | }, 277 | } 278 | } else { 279 | return ordering; 280 | } 281 | } else { 282 | match ca.cmp(&cb) { 283 | Ordering::Equal => last_is_number = false, 284 | Ordering::Greater => { 285 | return if last_is_number && (ca > (255 as char)) ^ (cb > (255 as char)) { 286 | Ordering::Less 287 | } else { 288 | Ordering::Greater 289 | }; 290 | }, 291 | Ordering::Less => { 292 | return if last_is_number && (ca > (255 as char)) ^ (cb > (255 as char)) { 293 | Ordering::Greater 294 | } else { 295 | Ordering::Less 296 | }; 297 | }, 298 | } 299 | } 300 | } 301 | } 302 | 303 | // TODO ----------- 304 | 305 | /// Sort a slice by a `str` key, but may not preserve the order of equal elements. 306 | #[inline] 307 | pub fn sort_slice_unstable_by_str_key, F: FnMut(&A) -> &T>( 308 | slice: &mut [A], 309 | mut f: F, 310 | ) { 311 | slice.sort_unstable_by(|a, b| compare_str(f(a), f(b))); 312 | } 313 | 314 | /// Sort a slice by a `str` key. 315 | #[inline] 316 | pub fn sort_slice_by_str_key, F: FnMut(&A) -> &T>( 317 | slice: &mut [A], 318 | mut f: F, 319 | ) { 320 | slice.sort_by(|a, b| compare_str(f(a), f(b))); 321 | } 322 | 323 | /// Reversely sort a slice by a `str` key, but may not preserve the order of equal elements. 324 | #[inline] 325 | pub fn sort_slice_rev_unstable_by_str_key, F: FnMut(&A) -> &T>( 326 | slice: &mut [A], 327 | mut f: F, 328 | ) { 329 | slice.sort_unstable_by(|a, b| compare_str(f(b), f(a))); 330 | } 331 | 332 | /// Reversely sort a slice by a `str` key. 333 | #[inline] 334 | pub fn sort_slice_rev_by_str_key, F: FnMut(&A) -> &T>( 335 | slice: &mut [A], 336 | mut f: F, 337 | ) { 338 | slice.sort_by(|a, b| compare_str(f(b), f(a))); 339 | } 340 | 341 | // TODO ----------- 342 | 343 | /// Sort a `str` slice. 344 | #[inline] 345 | pub fn sort_str_slice>(slice: &mut [S]) { 346 | slice.sort_unstable_by(|a, b| compare_str(a, b)); 347 | } 348 | 349 | /// Reversely sort a `str` slice. 350 | #[inline] 351 | pub fn sort_str_slice_rev>(slice: &mut [S]) { 352 | slice.sort_unstable_by(|a, b| compare_str(b, a)); 353 | } 354 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | #[cfg(feature = "std")] 3 | use std::path::Path; 4 | 5 | #[test] 6 | fn compare_lv0_1() { 7 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("0", "1")); 8 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("1", "0")); 9 | } 10 | 11 | #[test] 12 | fn compare_lv0_2() { 13 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("1", "2")); 14 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("2", "1")); 15 | } 16 | 17 | #[test] 18 | fn compare_lv0_3() { 19 | assert_eq!(Ordering::Equal, alphanumeric_sort::compare_str("0", "0")); 20 | assert_eq!(Ordering::Equal, alphanumeric_sort::compare_str("1", "1")); 21 | } 22 | 23 | #[test] 24 | fn compare_lv1_1() { 25 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abc", "bbb")); 26 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("bbb", "abc")); 27 | } 28 | 29 | #[test] 30 | fn compare_lv1_2() { 31 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abcd", "bbb")); 32 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("bbb", "abcd")); 33 | } 34 | 35 | #[test] 36 | fn compare_lv1_3() { 37 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abcd", "bbbbb")); 38 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("bbbbb", "abcd")); 39 | } 40 | 41 | #[test] 42 | fn compare_lv1_5() { 43 | assert_eq!(Ordering::Equal, alphanumeric_sort::compare_str("abcd", "abcd")); 44 | } 45 | 46 | #[test] 47 | fn compare_lv2_1() { 48 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abc321", "abc3210")); 49 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("abc3210", "abc321")); 50 | } 51 | 52 | #[test] 53 | fn compare_lv2_2() { 54 | assert_eq!(Ordering::Equal, alphanumeric_sort::compare_str("abc321", "abc321")); 55 | } 56 | 57 | #[test] 58 | fn compare_lv3_1() { 59 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abc320", "abc321")); 60 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("abc321", "abc320")); 61 | } 62 | 63 | #[test] 64 | fn compare_lv3_2() { 65 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("a1b1", "a1b2")); 66 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("a1b2", "a1b1")); 67 | } 68 | 69 | #[test] 70 | fn compare_lv3_3() { 71 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("a0b1", "a0b2")); 72 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("a0b2", "a0b1")); 73 | } 74 | 75 | #[test] 76 | fn compare_lv4_1() { 77 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abc1", "abc321")); 78 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("abc321", "abc1")); 79 | } 80 | 81 | #[test] 82 | fn compare_lv4_2() { 83 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abc5", "abc321")); 84 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("abc321", "abc5")); 85 | } 86 | 87 | #[test] 88 | fn compare_lv4_3() { 89 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abc321", "abc567")); 90 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("abc567", "abc321")); 91 | } 92 | 93 | #[test] 94 | fn compare_lv4_4() { 95 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abc5d67", "abc321")); 96 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("abc321", "abc5d67")); 97 | } 98 | 99 | #[test] 100 | fn compare_lv5_1() { 101 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("abc123d1", "abc123d123")); 102 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("abc123d123", "abc123d1")); 103 | } 104 | 105 | #[test] 106 | fn compare_lv5_2() { 107 | assert_eq!(Ordering::Equal, alphanumeric_sort::compare_str("abc123d123", "abc123d123")); 108 | } 109 | 110 | #[test] 111 | fn compare_lv6_1() { 112 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("1", "1a")); 113 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("1a", "1")); 114 | } 115 | 116 | #[test] 117 | fn compare_lv7_1() { 118 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("1", "中")); 119 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("中", "1")); 120 | } 121 | 122 | #[test] 123 | fn compare_lv8_1() { 124 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("1個", "1.1個")); 125 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("1.1個", "1個")); 126 | } 127 | 128 | #[test] 129 | fn compare_lv9_1() { 130 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("1", "01")); 131 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("01", "1")); 132 | } 133 | 134 | #[test] 135 | fn compare_lv9_2() { 136 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("1章", "01章")); 137 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("01章", "1章")); 138 | } 139 | 140 | #[test] 141 | fn compare_lv9_3() { 142 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("1", "10")); 143 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("10", "1")); 144 | } 145 | 146 | #[test] 147 | fn compare_lv9_4() { 148 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("1章", "10章")); 149 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("10章", "1章")); 150 | } 151 | 152 | #[test] 153 | fn compare_lv9_5() { 154 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("01", "1a")); 155 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("1a", "01")); 156 | } 157 | 158 | #[test] 159 | fn compare_lv9_6() { 160 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("1a章", "01章")); 161 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("01章", "1a章")); 162 | } 163 | 164 | #[test] 165 | fn compare_lv10_1() { 166 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("00001", "000001")); 167 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("000001", "00001")); 168 | } 169 | 170 | #[test] 171 | fn compare_lv11_1() { 172 | assert_eq!(alphanumeric_sort::compare_str( 173 | "23478435345672365487236435437465873645736452658734658734653645872542736437465365487326548734658736457265345736458735", 174 | "23478435345672365487236435437465873645736452658734658734653645872542736437465365487326548734658736457265345736458736" 175 | ), Ordering::Less); 176 | 177 | assert_eq!(alphanumeric_sort::compare_str( 178 | "23478435345672365487236435437465873645736452658734658734653645872542736437465365487326548734658736457265345736458736", 179 | "23478435345672365487236435437465873645736452658734658734653645872542736437465365487326548734658736457265345736458735", 180 | ), Ordering::Greater); 181 | } 182 | 183 | #[test] 184 | fn compare_lv12_1() { 185 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("01a01b01", "01a01b02")); 186 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("01a01b02", "01a01b01")); 187 | } 188 | 189 | #[test] 190 | fn compare_lv12_2() { 191 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("01a01b01", "01a01b001")); 192 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("01a01b001", "01a01b01")); 193 | } 194 | 195 | #[test] 196 | fn compare_lv12_3() { 197 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("01a01b01", "01a001b01")); 198 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("01a001b02", "01a01b01")); 199 | } 200 | 201 | #[test] 202 | fn compare_lv12_4() { 203 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("01a01b01", "001a01b01")); 204 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("01a01b02", "001a01b01")); 205 | } 206 | 207 | #[test] 208 | fn compare_lv12_5() { 209 | assert_eq!(Ordering::Less, alphanumeric_sort::compare_str("01a001b01", "001a01b01")); 210 | assert_eq!(Ordering::Greater, alphanumeric_sort::compare_str("01a001b02", "001a01b01")); 211 | } 212 | 213 | #[test] 214 | fn sort_str_slice() { 215 | let mut array = [ 216 | "第10-15-2章", 217 | "第1-2章", 218 | "第2-4章", 219 | "第2-33章", 220 | "第1章", 221 | "第1-4章", 222 | "第2-3章", 223 | "第1-11章", 224 | "第10-1章", 225 | "第3-1章", 226 | "第2-10章", 227 | "第2-2章", 228 | "第1-3章", 229 | "第10-15章", 230 | "第10-2章", 231 | "第10-15-1章", 232 | "第2-1章", 233 | "第2-12章", 234 | "第1-10章", 235 | "第3-10章", 236 | ]; 237 | 238 | alphanumeric_sort::sort_str_slice(&mut array); 239 | 240 | assert_eq!( 241 | [ 242 | "第1章", 243 | "第1-2章", 244 | "第1-3章", 245 | "第1-4章", 246 | "第1-10章", 247 | "第1-11章", 248 | "第2-1章", 249 | "第2-2章", 250 | "第2-3章", 251 | "第2-4章", 252 | "第2-10章", 253 | "第2-12章", 254 | "第2-33章", 255 | "第3-1章", 256 | "第3-10章", 257 | "第10-1章", 258 | "第10-2章", 259 | "第10-15章", 260 | "第10-15-1章", 261 | "第10-15-2章" 262 | ], 263 | array 264 | ); 265 | } 266 | 267 | #[test] 268 | fn sort_slice_by_str_key() { 269 | #[derive(Debug)] 270 | struct A(&'static str); 271 | 272 | impl From<&'static str> for A { 273 | #[inline] 274 | fn from(s: &'static str) -> Self { 275 | A(s) 276 | } 277 | } 278 | 279 | impl PartialEq for &str { 280 | #[inline] 281 | fn eq(&self, other: &A) -> bool { 282 | self == &other.0 283 | } 284 | } 285 | 286 | let mut array = [ 287 | A::from("第10-15-2章"), 288 | A::from("第1-2章"), 289 | A::from("第2-4章"), 290 | A::from("第2-33章"), 291 | A::from("第1章"), 292 | A::from("第1-4章"), 293 | A::from("第2-3章"), 294 | A::from("第1-11章"), 295 | A::from("第10-1章"), 296 | A::from("第3-1章"), 297 | A::from("第2-10章"), 298 | A::from("第2-2章"), 299 | A::from("第1-3章"), 300 | A::from("第10-15章"), 301 | A::from("第10-2章"), 302 | A::from("第10-15-1章"), 303 | A::from("第2-1章"), 304 | A::from("第2-12章"), 305 | A::from("第1-10章"), 306 | A::from("第3-10章"), 307 | ]; 308 | 309 | alphanumeric_sort::sort_slice_by_str_key(&mut array, |e| e.0); 310 | 311 | assert_eq!( 312 | [ 313 | "第1章", 314 | "第1-2章", 315 | "第1-3章", 316 | "第1-4章", 317 | "第1-10章", 318 | "第1-11章", 319 | "第2-1章", 320 | "第2-2章", 321 | "第2-3章", 322 | "第2-4章", 323 | "第2-10章", 324 | "第2-12章", 325 | "第2-33章", 326 | "第3-1章", 327 | "第3-10章", 328 | "第10-1章", 329 | "第10-2章", 330 | "第10-15章", 331 | "第10-15-1章", 332 | "第10-15-2章" 333 | ], 334 | array 335 | ); 336 | } 337 | 338 | #[cfg(feature = "std")] 339 | #[test] 340 | fn sort_path_slice() { 341 | let mut array = [ 342 | Path::new("第10-15-2章"), 343 | Path::new("第1-2章"), 344 | Path::new("第2-4章"), 345 | Path::new("第2-33章"), 346 | Path::new("第1章"), 347 | Path::new("第1-4章"), 348 | Path::new("第2-3章"), 349 | Path::new("第1-11章"), 350 | Path::new("第10-1章"), 351 | Path::new("第3-1章"), 352 | Path::new("第2-10章"), 353 | Path::new("第2-2章"), 354 | Path::new("第1-3章"), 355 | Path::new("第10-15章"), 356 | Path::new("第10-2章"), 357 | Path::new("第10-15-1章"), 358 | Path::new("第2-1章"), 359 | Path::new("第2-12章"), 360 | Path::new("第1-10章"), 361 | Path::new("第3-10章"), 362 | ]; 363 | 364 | alphanumeric_sort::sort_path_slice(&mut array); 365 | 366 | assert_eq!( 367 | [ 368 | Path::new("第1章"), 369 | Path::new("第1-2章"), 370 | Path::new("第1-3章"), 371 | Path::new("第1-4章"), 372 | Path::new("第1-10章"), 373 | Path::new("第1-11章"), 374 | Path::new("第2-1章"), 375 | Path::new("第2-2章"), 376 | Path::new("第2-3章"), 377 | Path::new("第2-4章"), 378 | Path::new("第2-10章"), 379 | Path::new("第2-12章"), 380 | Path::new("第2-33章"), 381 | Path::new("第3-1章"), 382 | Path::new("第3-10章"), 383 | Path::new("第10-1章"), 384 | Path::new("第10-2章"), 385 | Path::new("第10-15章"), 386 | Path::new("第10-15-1章"), 387 | Path::new("第10-15-2章") 388 | ], 389 | array 390 | ); 391 | } 392 | 393 | #[cfg(feature = "std")] 394 | #[test] 395 | fn sort_slice_by_path_key() { 396 | #[derive(Debug)] 397 | struct A(&'static Path); 398 | 399 | impl From<&'static str> for A { 400 | #[inline] 401 | fn from(s: &'static str) -> Self { 402 | A(Path::new(s)) 403 | } 404 | } 405 | 406 | impl PartialEq for &Path { 407 | #[inline] 408 | fn eq(&self, other: &A) -> bool { 409 | self == &other.0 410 | } 411 | } 412 | 413 | let mut array = [ 414 | A::from("第10-15-2章"), 415 | A::from("第1-2章"), 416 | A::from("第2-4章"), 417 | A::from("第2-33章"), 418 | A::from("第1章"), 419 | A::from("第1-4章"), 420 | A::from("第2-3章"), 421 | A::from("第1-11章"), 422 | A::from("第10-1章"), 423 | A::from("第3-1章"), 424 | A::from("第2-10章"), 425 | A::from("第2-2章"), 426 | A::from("第1-3章"), 427 | A::from("第10-15章"), 428 | A::from("第10-2章"), 429 | A::from("第10-15-1章"), 430 | A::from("第2-1章"), 431 | A::from("第2-12章"), 432 | A::from("第1-10章"), 433 | A::from("第3-10章"), 434 | ]; 435 | 436 | alphanumeric_sort::sort_slice_by_path_key(&mut array, |e| e.0); 437 | 438 | assert_eq!( 439 | [ 440 | Path::new("第1章"), 441 | Path::new("第1-2章"), 442 | Path::new("第1-3章"), 443 | Path::new("第1-4章"), 444 | Path::new("第1-10章"), 445 | Path::new("第1-11章"), 446 | Path::new("第2-1章"), 447 | Path::new("第2-2章"), 448 | Path::new("第2-3章"), 449 | Path::new("第2-4章"), 450 | Path::new("第2-10章"), 451 | Path::new("第2-12章"), 452 | Path::new("第2-33章"), 453 | Path::new("第3-1章"), 454 | Path::new("第3-10章"), 455 | Path::new("第10-1章"), 456 | Path::new("第10-2章"), 457 | Path::new("第10-15章"), 458 | Path::new("第10-15-1章"), 459 | Path::new("第10-15-2章") 460 | ], 461 | array 462 | ); 463 | } 464 | -------------------------------------------------------------------------------- /src/std_functions.rs: -------------------------------------------------------------------------------- 1 | use core::cmp::Ordering; 2 | use std::{ 3 | ffi::{CStr, OsStr}, 4 | path::Path, 5 | }; 6 | 7 | use crate::compare_str; 8 | 9 | /// Compare two `OsStr`. 10 | #[inline] 11 | pub fn compare_os_str, B: AsRef>(a: A, b: B) -> Ordering { 12 | let sa = match a.as_ref().to_str() { 13 | Some(s) => s, 14 | None => { 15 | return compare_os_str_fallback(a, b); 16 | }, 17 | }; 18 | 19 | let sb = match b.as_ref().to_str() { 20 | Some(s) => s, 21 | None => { 22 | return compare_os_str_fallback(a, b); 23 | }, 24 | }; 25 | 26 | compare_str(sa, sb) 27 | } 28 | 29 | #[inline] 30 | fn compare_os_str_fallback, B: AsRef>(a: A, b: B) -> Ordering { 31 | a.as_ref().cmp(b.as_ref()) 32 | } 33 | 34 | /// Compare two `CStr`. 35 | #[inline] 36 | pub fn compare_c_str, B: AsRef>(a: A, b: B) -> Ordering { 37 | let sa = match a.as_ref().to_str() { 38 | Ok(s) => s, 39 | Err(_) => { 40 | return compare_c_str_fallback(a, b); 41 | }, 42 | }; 43 | 44 | let sb = match b.as_ref().to_str() { 45 | Ok(s) => s, 46 | Err(_) => { 47 | return compare_c_str_fallback(a, b); 48 | }, 49 | }; 50 | 51 | compare_str(sa, sb) 52 | } 53 | 54 | #[inline] 55 | fn compare_c_str_fallback, B: AsRef>(a: A, b: B) -> Ordering { 56 | a.as_ref().cmp(b.as_ref()) 57 | } 58 | 59 | /// Compare two `Path`. 60 | #[inline] 61 | pub fn compare_path, B: AsRef>(a: A, b: B) -> Ordering { 62 | compare_os_str(a.as_ref(), b.as_ref()) 63 | } 64 | 65 | /// Sort a slice by an `OsStr` key, but may not preserve the order of equal elements. 66 | #[inline] 67 | pub fn sort_slice_unstable_by_os_str_key, F: FnMut(&A) -> &T>( 68 | slice: &mut [A], 69 | f: F, 70 | ) { 71 | sort_slice_by_os_str_key_inner( 72 | slice, 73 | f, 74 | ref_index_str_pairs_to_ref_indexes_unstable, 75 | sort_slice_unstable_by_os_str_key_fallback, 76 | ) 77 | } 78 | 79 | /// Sort a slice by an `OsStr` key. 80 | #[inline] 81 | pub fn sort_slice_by_os_str_key, F: FnMut(&A) -> &T>( 82 | slice: &mut [A], 83 | f: F, 84 | ) { 85 | sort_slice_by_os_str_key_inner( 86 | slice, 87 | f, 88 | ref_index_str_pairs_to_ref_indexes, 89 | sort_slice_by_os_str_key_fallback, 90 | ) 91 | } 92 | 93 | /// Reversely sort a slice by an `OsStr` key, but may not preserve the order of equal elements. 94 | #[inline] 95 | pub fn sort_slice_rev_unstable_by_os_str_key, F: FnMut(&A) -> &T>( 96 | slice: &mut [A], 97 | f: F, 98 | ) { 99 | sort_slice_by_os_str_key_inner( 100 | slice, 101 | f, 102 | ref_index_str_pairs_to_ref_indexes_rev_unstable, 103 | sort_slice_rev_unstable_by_os_str_key_fallback, 104 | ) 105 | } 106 | 107 | /// Reversely sort a slice by an `OsStr` key. 108 | #[inline] 109 | pub fn sort_slice_rev_by_os_str_key, F: FnMut(&A) -> &T>( 110 | slice: &mut [A], 111 | f: F, 112 | ) { 113 | sort_slice_by_os_str_key_inner( 114 | slice, 115 | f, 116 | ref_index_str_pairs_to_ref_indexes_rev, 117 | sort_slice_rev_by_os_str_key_fallback, 118 | ) 119 | } 120 | 121 | fn sort_slice_by_os_str_key_inner, F: FnMut(&A) -> &T>( 122 | slice: &mut [A], 123 | mut f: F, 124 | ref_index_str_pairs_to_ref_indexes: impl Fn(Vec<(usize, &str)>) -> Vec<(usize, usize)>, 125 | fallback: impl Fn(&mut [A], F), 126 | ) { 127 | let mut use_str = true; 128 | 129 | let mut ref_index_str_pairs = Vec::with_capacity(slice.len()); 130 | 131 | for (i, p) in slice.iter().enumerate() { 132 | let s = match f(p).as_ref().to_str() { 133 | Some(s) => s, 134 | None => { 135 | use_str = false; 136 | break; 137 | }, 138 | }; 139 | 140 | ref_index_str_pairs.push((i, s)); 141 | } 142 | 143 | if use_str { 144 | let ref_indexes = ref_index_str_pairs_to_ref_indexes(ref_index_str_pairs); 145 | 146 | sort_slice_ref_indexes(slice, ref_indexes); 147 | } else { 148 | fallback(slice, f); 149 | } 150 | } 151 | 152 | #[inline] 153 | fn sort_slice_unstable_by_os_str_key_fallback, F: FnMut(&A) -> &T>( 154 | slice: &mut [A], 155 | mut f: F, 156 | ) { 157 | slice.sort_unstable_by(|a, b| compare_os_str_fallback(f(a), f(b))); 158 | } 159 | 160 | #[inline] 161 | fn sort_slice_by_os_str_key_fallback, F: FnMut(&A) -> &T>( 162 | slice: &mut [A], 163 | mut f: F, 164 | ) { 165 | slice.sort_by(|a, b| compare_os_str_fallback(f(a), f(b))); 166 | } 167 | 168 | #[inline] 169 | fn sort_slice_rev_unstable_by_os_str_key_fallback< 170 | A, 171 | T: ?Sized + AsRef, 172 | F: FnMut(&A) -> &T, 173 | >( 174 | slice: &mut [A], 175 | mut f: F, 176 | ) { 177 | slice.sort_unstable_by(|a, b| compare_os_str_fallback(f(b), f(a))); 178 | } 179 | 180 | #[inline] 181 | fn sort_slice_rev_by_os_str_key_fallback, F: FnMut(&A) -> &T>( 182 | slice: &mut [A], 183 | mut f: F, 184 | ) { 185 | slice.sort_by(|a, b| compare_os_str_fallback(f(b), f(a))); 186 | } 187 | 188 | /// Sort a slice by a `CStr` key, but may not preserve the order of equal elements. 189 | #[inline] 190 | pub fn sort_slice_unstable_by_c_str_key, F: FnMut(&A) -> &T>( 191 | slice: &mut [A], 192 | f: F, 193 | ) { 194 | sort_slice_by_c_str_key_inner( 195 | slice, 196 | f, 197 | ref_index_str_pairs_to_ref_indexes_unstable, 198 | sort_slice_unstable_by_c_str_key_fallback, 199 | ) 200 | } 201 | 202 | /// Sort a slice by a `CStr` key. 203 | #[inline] 204 | pub fn sort_slice_by_c_str_key, F: FnMut(&A) -> &T>( 205 | slice: &mut [A], 206 | f: F, 207 | ) { 208 | sort_slice_by_c_str_key_inner( 209 | slice, 210 | f, 211 | ref_index_str_pairs_to_ref_indexes, 212 | sort_slice_by_c_str_key_fallback, 213 | ) 214 | } 215 | 216 | /// Reversely sort a slice by a `CStr` key, but may not preserve the order of equal elements. 217 | #[inline] 218 | pub fn sort_slice_rev_unstable_by_c_str_key, F: FnMut(&A) -> &T>( 219 | slice: &mut [A], 220 | f: F, 221 | ) { 222 | sort_slice_by_c_str_key_inner( 223 | slice, 224 | f, 225 | ref_index_str_pairs_to_ref_indexes_rev_unstable, 226 | sort_slice_rev_unstable_by_c_str_key_fallback, 227 | ) 228 | } 229 | 230 | /// Reversely sort a slice by a `CStr` key. 231 | #[inline] 232 | pub fn sort_slice_rev_by_c_str_key, F: FnMut(&A) -> &T>( 233 | slice: &mut [A], 234 | f: F, 235 | ) { 236 | sort_slice_by_c_str_key_inner( 237 | slice, 238 | f, 239 | ref_index_str_pairs_to_ref_indexes_rev, 240 | sort_slice_rev_by_c_str_key_fallback, 241 | ) 242 | } 243 | 244 | fn sort_slice_by_c_str_key_inner, F: FnMut(&A) -> &T>( 245 | slice: &mut [A], 246 | mut f: F, 247 | ref_index_str_pairs_to_ref_indexes: impl Fn(Vec<(usize, &str)>) -> Vec<(usize, usize)>, 248 | fallback: impl Fn(&mut [A], F), 249 | ) { 250 | let mut use_str = true; 251 | 252 | let mut ref_index_str_pairs = Vec::with_capacity(slice.len()); 253 | 254 | for (i, p) in slice.iter().enumerate() { 255 | let s = match f(p).as_ref().to_str() { 256 | Ok(s) => s, 257 | Err(_) => { 258 | use_str = false; 259 | break; 260 | }, 261 | }; 262 | 263 | ref_index_str_pairs.push((i, s)); 264 | } 265 | 266 | if use_str { 267 | let ref_indexes = ref_index_str_pairs_to_ref_indexes(ref_index_str_pairs); 268 | 269 | sort_slice_ref_indexes(slice, ref_indexes); 270 | } else { 271 | fallback(slice, f); 272 | } 273 | } 274 | 275 | #[inline] 276 | fn sort_slice_unstable_by_c_str_key_fallback, F: FnMut(&A) -> &T>( 277 | slice: &mut [A], 278 | mut f: F, 279 | ) { 280 | slice.sort_unstable_by(|a, b| compare_c_str_fallback(f(a), f(b))); 281 | } 282 | 283 | #[inline] 284 | fn sort_slice_by_c_str_key_fallback, F: FnMut(&A) -> &T>( 285 | slice: &mut [A], 286 | mut f: F, 287 | ) { 288 | slice.sort_by(|a, b| compare_c_str_fallback(f(a), f(b))); 289 | } 290 | 291 | #[inline] 292 | fn sort_slice_rev_unstable_by_c_str_key_fallback, F: FnMut(&A) -> &T>( 293 | slice: &mut [A], 294 | mut f: F, 295 | ) { 296 | slice.sort_unstable_by(|a, b| compare_c_str_fallback(f(b), f(a))); 297 | } 298 | 299 | #[inline] 300 | fn sort_slice_rev_by_c_str_key_fallback, F: FnMut(&A) -> &T>( 301 | slice: &mut [A], 302 | mut f: F, 303 | ) { 304 | slice.sort_by(|a, b| compare_c_str_fallback(f(b), f(a))); 305 | } 306 | 307 | /// Sort a slice by a `Path` key, but may not preserve the order of equal elements. 308 | #[inline] 309 | pub fn sort_slice_unstable_by_path_key, F: FnMut(&A) -> &T>( 310 | slice: &mut [A], 311 | f: F, 312 | ) { 313 | sort_slice_by_path_key_inner( 314 | slice, 315 | f, 316 | ref_index_str_pairs_to_ref_indexes_unstable, 317 | sort_slice_unstable_by_path_key_fallback, 318 | ) 319 | } 320 | 321 | /// Sort a slice by a `Path` key. 322 | #[inline] 323 | pub fn sort_slice_by_path_key, F: FnMut(&A) -> &T>( 324 | slice: &mut [A], 325 | f: F, 326 | ) { 327 | sort_slice_by_path_key_inner( 328 | slice, 329 | f, 330 | ref_index_str_pairs_to_ref_indexes, 331 | sort_slice_by_path_key_fallback, 332 | ) 333 | } 334 | 335 | /// Reversely sort a slice by a `Path` key, but may not preserve the order of equal elements. 336 | #[inline] 337 | pub fn sort_slice_rev_unstable_by_path_key, F: FnMut(&A) -> &T>( 338 | slice: &mut [A], 339 | f: F, 340 | ) { 341 | sort_slice_by_path_key_inner( 342 | slice, 343 | f, 344 | ref_index_str_pairs_to_ref_indexes_rev_unstable, 345 | sort_slice_rev_unstable_by_path_key_fallback, 346 | ) 347 | } 348 | 349 | /// Reversely sort a slice by a `Path` key. 350 | #[inline] 351 | pub fn sort_slice_rev_by_path_key, F: FnMut(&A) -> &T>( 352 | slice: &mut [A], 353 | f: F, 354 | ) { 355 | sort_slice_by_path_key_inner( 356 | slice, 357 | f, 358 | ref_index_str_pairs_to_ref_indexes_rev, 359 | sort_slice_rev_by_path_key_fallback, 360 | ) 361 | } 362 | 363 | fn sort_slice_by_path_key_inner, F: FnMut(&A) -> &T>( 364 | slice: &mut [A], 365 | mut f: F, 366 | ref_index_str_pairs_to_ref_indexes: impl Fn(Vec<(usize, &str)>) -> Vec<(usize, usize)>, 367 | fallback: impl Fn(&mut [A], F), 368 | ) { 369 | let mut use_str = true; 370 | 371 | let mut ref_index_str_pairs = Vec::with_capacity(slice.len()); 372 | 373 | for (i, p) in slice.iter().enumerate() { 374 | let s = match f(p).as_ref().to_str() { 375 | Some(s) => s, 376 | None => { 377 | use_str = false; 378 | break; 379 | }, 380 | }; 381 | 382 | ref_index_str_pairs.push((i, s)); 383 | } 384 | 385 | if use_str { 386 | let ref_indexes = ref_index_str_pairs_to_ref_indexes(ref_index_str_pairs); 387 | 388 | sort_slice_ref_indexes(slice, ref_indexes); 389 | } else { 390 | fallback(slice, f); 391 | } 392 | } 393 | 394 | #[inline] 395 | fn sort_slice_unstable_by_path_key_fallback, F: FnMut(&A) -> &T>( 396 | slice: &mut [A], 397 | mut f: F, 398 | ) { 399 | slice.sort_unstable_by(|a, b| { 400 | compare_os_str_fallback(f(a).as_ref().as_os_str(), f(b).as_ref().as_os_str()) 401 | }); 402 | } 403 | 404 | #[inline] 405 | fn sort_slice_by_path_key_fallback, F: FnMut(&A) -> &T>( 406 | slice: &mut [A], 407 | mut f: F, 408 | ) { 409 | slice.sort_by(|a, b| { 410 | compare_os_str_fallback(f(a).as_ref().as_os_str(), f(b).as_ref().as_os_str()) 411 | }); 412 | } 413 | 414 | #[inline] 415 | fn sort_slice_rev_unstable_by_path_key_fallback, F: FnMut(&A) -> &T>( 416 | slice: &mut [A], 417 | mut f: F, 418 | ) { 419 | slice.sort_unstable_by(|a, b| { 420 | compare_os_str_fallback(f(b).as_ref().as_os_str(), f(a).as_ref().as_os_str()) 421 | }); 422 | } 423 | 424 | #[inline] 425 | fn sort_slice_rev_by_path_key_fallback, F: FnMut(&A) -> &T>( 426 | slice: &mut [A], 427 | mut f: F, 428 | ) { 429 | slice.sort_by(|a, b| { 430 | compare_os_str_fallback(f(b).as_ref().as_os_str(), f(a).as_ref().as_os_str()) 431 | }); 432 | } 433 | 434 | // TODO ----------- 435 | 436 | /// Sort an `OsStr` slice. 437 | #[inline] 438 | pub fn sort_os_str_slice>(slice: &mut [S]) { 439 | sort_slice_unstable_by_os_str_key(slice, |e| e.as_ref()) 440 | } 441 | 442 | /// Reversely sort an `OsStr` slice. 443 | #[inline] 444 | pub fn sort_os_str_slice_rev>(slice: &mut [S]) { 445 | sort_slice_rev_unstable_by_os_str_key(slice, |e| e.as_ref()) 446 | } 447 | 448 | /// Sort a `CStr` slice. 449 | #[inline] 450 | pub fn sort_c_str_slice>(slice: &mut [S]) { 451 | sort_slice_unstable_by_c_str_key(slice, |e| e.as_ref()) 452 | } 453 | 454 | /// Reversely sort a `CStr` slice. 455 | #[inline] 456 | pub fn sort_c_str_slice_rev>(slice: &mut [S]) { 457 | sort_slice_rev_unstable_by_c_str_key(slice, |e| e.as_ref()) 458 | } 459 | 460 | /// Sort a `Path` slice. 461 | #[inline] 462 | pub fn sort_path_slice>(slice: &mut [P]) { 463 | sort_slice_unstable_by_path_key(slice, |e| e.as_ref()) 464 | } 465 | 466 | /// Reversely sort a `Path` slice. 467 | #[inline] 468 | pub fn sort_path_slice_rev>(slice: &mut [P]) { 469 | sort_slice_rev_unstable_by_path_key(slice, |e| e.as_ref()) 470 | } 471 | 472 | // TODO ----------- 473 | 474 | #[inline] 475 | fn ref_index_str_pairs_to_ref_indexes_unstable( 476 | mut ref_index_str_pairs: Vec<(usize, &str)>, 477 | ) -> Vec<(usize, usize)> { 478 | ref_index_str_pairs.sort_unstable_by(|a, b| compare_str(a.1, b.1)); 479 | 480 | ref_index_str_pairs_to_ref_indexes_inner(ref_index_str_pairs) 481 | } 482 | 483 | #[inline] 484 | fn ref_index_str_pairs_to_ref_indexes( 485 | mut ref_index_str_pairs: Vec<(usize, &str)>, 486 | ) -> Vec<(usize, usize)> { 487 | ref_index_str_pairs.sort_by(|a, b| compare_str(a.1, b.1)); 488 | 489 | ref_index_str_pairs_to_ref_indexes_inner(ref_index_str_pairs) 490 | } 491 | 492 | #[inline] 493 | fn ref_index_str_pairs_to_ref_indexes_rev_unstable( 494 | mut ref_index_str_pairs: Vec<(usize, &str)>, 495 | ) -> Vec<(usize, usize)> { 496 | ref_index_str_pairs.sort_unstable_by(|a, b| compare_str(b.1, a.1)); 497 | 498 | ref_index_str_pairs_to_ref_indexes_inner(ref_index_str_pairs) 499 | } 500 | 501 | #[inline] 502 | fn ref_index_str_pairs_to_ref_indexes_rev( 503 | mut ref_index_str_pairs: Vec<(usize, &str)>, 504 | ) -> Vec<(usize, usize)> { 505 | ref_index_str_pairs.sort_by(|a, b| compare_str(b.1, a.1)); 506 | 507 | ref_index_str_pairs_to_ref_indexes_inner(ref_index_str_pairs) 508 | } 509 | 510 | #[inline] 511 | fn ref_index_str_pairs_to_ref_indexes_inner( 512 | ref_index_str_pairs: Vec<(usize, &str)>, 513 | ) -> Vec<(usize, usize)> { 514 | ref_index_str_pairs 515 | .into_iter() 516 | .enumerate() 517 | .filter_map(|(j, (i, _))| if i != j { Some((i, j)) } else { None }) 518 | .collect() 519 | } 520 | 521 | #[inline] 522 | fn sort_slice_ref_indexes(slice: &mut [S], mut ref_indexes: Vec<(usize, usize)>) { 523 | let length = ref_indexes.len(); 524 | 525 | for index in 0..length { 526 | let (i, j) = ref_indexes[index]; 527 | 528 | slice.swap(i, j); 529 | 530 | for (t, _) in ref_indexes[index + 1..].iter_mut() { 531 | if *t == j { 532 | *t = i; 533 | break; 534 | } 535 | } 536 | } 537 | } 538 | --------------------------------------------------------------------------------