Human-friendly natural sorting —
gem install philiprehberger-natural_sortHuman-friendly natural sorting — "file2" before "file10"
Add to your Gemfile:
gem "philiprehberger-natural_sort"
Or install directly:
gem install philiprehberger-natural_sort
require "philiprehberger/natural_sort"
sorted = Philiprehberger::NaturalSort.sort(["file10", "file2", "file1"])
# => ["file1", "file2", "file10"]
items = [{ name: "img10" }, { name: "img2" }, { name: "img1" }]
sorted = Philiprehberger::NaturalSort.sort_by(items) { |x| x[:name] }
# => [{ name: "img1" }, { name: "img2" }, { name: "img10" }]
Use the ArrayRefinement to call sort_naturally_by directly on arrays:
using Philiprehberger::NaturalSort::ArrayRefinement
items = [
OpenStruct.new(name: "img10"),
OpenStruct.new(name: "img2"),
OpenStruct.new(name: "img1")
]
items.sort_naturally_by { |x| x.name }
# => [#<OpenStruct name="img1">, #<OpenStruct name="img2">, #<OpenStruct name="img10">]
Philiprehberger::NaturalSort.compare("file2", "file10")
# => -1
Philiprehberger::NaturalSort.compare("file10", "file2")
# => 1
cmp = Philiprehberger::NaturalSort.comparator
["file10", "file2", "file1"].sort(&cmp)
# => ["file1", "file2", "file10"]
Philiprehberger::NaturalSort.sort(["file10", "file2", "file1"], reverse: true)
# => ["file10", "file2", "file1"]
items = [{ name: "img10" }, { name: "img2" }, { name: "img1" }]
sorted = Philiprehberger::NaturalSort.sort_by(items, reverse: true) { |x| x[:name] }
# => [{ name: "img10" }, { name: "img2" }, { name: "img1" }]
Preserves original order for elements that compare as equal:
Philiprehberger::NaturalSort.sort_stable(["file1", "FILE1", "file2"])
# => ["file1", "FILE1", "file2"]
Find the naturally smallest or largest element without a full sort:
Philiprehberger::NaturalSort.min(["file10", "file2", "file1"])
# => "file1"
Philiprehberger::NaturalSort.max(["file10", "file2", "file1"])
# => "file10"
Use natural_key with Ruby's standard sort_by, min_by, max_by, group_by, and other Enumerable methods:
files = ["file10.txt", "file2.txt", "file1.txt"]
files.sort_by { |f| Philiprehberger::NaturalSort.natural_key(f) }
# => ["file1.txt", "file2.txt", "file10.txt"]
files.min_by { |f| Philiprehberger::NaturalSort.natural_key(f) }
# => "file1.txt"
Preserves original order for elements whose block values compare as equal:
items = [
{ name: "file1", id: "a" },
{ name: "FILE1", id: "b" },
{ name: "file2", id: "c" }
]
sorted = Philiprehberger::NaturalSort.sort_by_stable(items) { |x| x[:name] }
# => [{ name: "file1", id: "a" }, { name: "FILE1", id: "b" }, { name: "file2", id: "c" }]
Philiprehberger::NaturalSort.sort(["Banana", "apple"], case_sensitive: true)
# => ["Banana", "apple"]
Use ignore_case: true to force case-insensitive comparison, overriding case_sensitive:
Philiprehberger::NaturalSort.sort(["Banana", "apple", "Cherry"], ignore_case: true)
# => ["apple", "Banana", "Cherry"]
Philiprehberger::NaturalSort.sort(["Banana", "apple"], case_sensitive: true, ignore_case: true)
# => ["apple", "Banana"]
Split strings at the first digit boundary and group by the non-numeric prefix. Each group's values are naturally sorted:
Philiprehberger::NaturalSort.group_by_prefix(["file1", "file2", "file10", "img3", "img20"])
# => { "file" => ["file1", "file2", "file10"], "img" => ["img3", "img20"] }
Check if a value falls within a natural sort range (inclusive):
Philiprehberger::NaturalSort.between?("v1.5", "v1.0", "v2.0")
# => true
Philiprehberger::NaturalSort.between?("v3.0", "v1.0", "v2.0")
# => false
Philiprehberger::NaturalSort.between?("v1.0", "v1.0", "v2.0")
# => true (boundaries are inclusive)
Returns an array of original indices representing the natural-sort permutation. Useful when you need the sorted order without rearranging the original array:
Philiprehberger::NaturalSort.sort_index(["file10", "file2", "file1"])
# => [2, 1, 0]
Philiprehberger::NaturalSort.sort_index(["file10", "file2", "file1"], reverse: true)
# => [0, 1, 2]
Philiprehberger::NaturalSort.sort_index(["banana", "Apple", "cherry"], case_sensitive: true)
# => [1, 0, 2]
Spaceship-style comparator returning -1, 0, or 1. Suitable for use with Array#sort:
["file10", "file2", "file1"].sort { |a, b| Philiprehberger::NaturalSort.collate(a, b) }
# => ["file1", "file2", "file10"]
Remove duplicates where two strings compare equal under natural ordering (e.g. "a1" and "a01"). Preserves first-occurrence order:
Philiprehberger::NaturalSort.uniq(["a1", "A1", "a01"])
# => ["a1"]
Philiprehberger::NaturalSort.uniq(["a1", "A1", "a01"], case_sensitive: true)
# => ["a1", "A1"]
Mutate an array directly when you do not need the original order:
files = ["file10", "file2", "file1"]
Philiprehberger::NaturalSort.sort!(files)
files # => ["file1", "file2", "file10"]
Natural-sort an array of paths by their separator-delimited segments. Useful for
directory listings where 'a/2/x' should come before 'a/10/x':
Philiprehberger::NaturalSort.sort_paths(["a/10/x", "a/2/x", "a/2/y"])
# => ["a/2/x", "a/2/y", "a/10/x"]
Philiprehberger::NaturalSort.sort_paths(["C:\\docs\\file10", "C:\\docs\\file2"], separator: "\\")
# => ["C:\\docs\\file2", "C:\\docs\\file10"]
| Method | Description |
|---|---|
NaturalSort.sort(array, case_sensitive: false, reverse: false, ignore_case: false) | Sort an array of strings in natural order |
NaturalSort.sort!(array, case_sensitive: false, reverse: false) | In-place natural sort; returns the same array |
NaturalSort.sort_by(array, case_sensitive: false, reverse: false, ignore_case: false) { |x| ... } | Sort by block result in natural order |
NaturalSort.sort_stable(array, case_sensitive: false) | Stable sort preserving original order for equal elements |
NaturalSort.min(array, case_sensitive: false) | Find the naturally smallest element |
NaturalSort.max(array, case_sensitive: false) | Find the naturally largest element |
NaturalSort.first(array, n: 1, case_sensitive: false) | Return the n naturally-smallest elements (single value when n==1) |
NaturalSort.last(array, n: 1, case_sensitive: false) | Return the n naturally-largest elements (single value when n==1) |
NaturalSort.compare(a, b, case_sensitive: false) | Compare two strings, returns -1, 0, or 1 |
NaturalSort.collate(a, b, case_sensitive: false) | Spaceship-style comparator returning -1, 0, or 1 |
NaturalSort.comparator(case_sensitive: false) | Returns a reusable comparison Proc |
NaturalSort.natural_key(str, case_sensitive: false) | Returns a sort key for use with sort_by, min_by, etc. |
NaturalSort.sort_by_stable(array, case_sensitive: false) { |x| ... } | Stable sort by block result preserving order for equal elements |
NaturalSort.between?(value, min, max, case_sensitive: false) | Check if value falls within [min, max] in natural sort order |
NaturalSort.sort_index(array, case_sensitive: false, reverse: false) | Return original indices in natural sort order |
NaturalSort.group_by_prefix(array, case_sensitive: false) | Group strings by non-numeric prefix with naturally sorted values |
NaturalSort.uniq(array, case_sensitive: false) | Deduplicate preserving first-occurrence order using natural equality |
.sort_paths(paths, separator:, case_sensitive:, reverse:) | Natural sort with path separator awareness |
array.sort_naturally_by { |x| ... } | Sort array by block result (via ArrayRefinement) |
bundle install
bundle exec rspec
bundle exec rubocop
If you find this project useful: