Module: Philiprehberger::NaturalSort
- Defined in:
- lib/philiprehberger/natural_sort.rb,
lib/philiprehberger/natural_sort/version.rb,
lib/philiprehberger/natural_sort/comparator.rb
Defined Under Namespace
Modules: ArrayRefinement Classes: Error
Constant Summary collapse
- VERSION =
'0.6.0'
Class Method Summary collapse
-
.between?(value, min, max, case_sensitive: false) ⇒ Boolean
Returns true if value falls within the natural sort range [min, max] inclusive.
-
.collate(a, b, case_sensitive: false) ⇒ Integer
Spaceship-style comparator returning -1, 0, or 1.
-
.comparator(case_sensitive: false) ⇒ Proc
Returns a Proc suitable for use with Array#sort.
-
.compare(a, b, case_sensitive: false) ⇒ Integer
Compares two strings using natural sort order.
-
.group_by_prefix(array, case_sensitive: false) ⇒ Hash<String, Array<String>>
Splits each string at the first digit boundary, groups by the non-numeric prefix.
-
.max(array, case_sensitive: false) ⇒ String?
Finds the naturally largest element without full sort.
-
.min(array, case_sensitive: false) ⇒ String?
Finds the naturally smallest element without full sort.
-
.natural_key(str, case_sensitive: false) ⇒ Array
Returns a sort key array usable with Ruby’s built-in sort_by, min_by, max_by, etc.
-
.sort(array, case_sensitive: false, reverse: false, ignore_case: false) ⇒ Array<String, nil>
Sorts an array of strings in natural order.
-
.sort_by(array, case_sensitive: false, reverse: false, ignore_case: false) {|element| ... } ⇒ Array
Sorts an array by the natural order of values returned by the block.
-
.sort_by_stable(array, case_sensitive: false) {|element| ... } ⇒ Array
Stable sort by block result, preserving original order for equal elements.
-
.sort_stable(array, case_sensitive: false) ⇒ Array<String, nil>
Stable sort that preserves original order for equal elements.
-
.tokenize(str, case_sensitive: false) ⇒ Array<String, Integer>
Splits a string into chunks of text and numbers for natural comparison.
Class Method Details
.between?(value, min, max, case_sensitive: false) ⇒ Boolean
Returns true if value falls within the natural sort range [min, max] inclusive.
190 191 192 193 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 190 def self.between?(value, min, max, case_sensitive: false) compare(min, value, case_sensitive: case_sensitive) <= 0 && compare(value, max, case_sensitive: case_sensitive) <= 0 end |
.collate(a, b, case_sensitive: false) ⇒ Integer
Spaceship-style comparator returning -1, 0, or 1.
Suitable for use with Array#sort:
array.sort { |a, b| NaturalSort.collate(a, b) }
179 180 181 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 179 def self.collate(a, b, case_sensitive: false) compare(a, b, case_sensitive: case_sensitive) end |
.comparator(case_sensitive: false) ⇒ Proc
Returns a Proc suitable for use with Array#sort.
71 72 73 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 71 def self.comparator(case_sensitive: false) ->(a, b) { compare(a, b, case_sensitive: case_sensitive) } end |
.compare(a, b, case_sensitive: false) ⇒ Integer
Compares two strings using natural sort order.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 23 def self.compare(a, b, case_sensitive: false) return 0 if a.nil? && b.nil? return -1 if a.nil? return 1 if b.nil? a_str = a.to_s b_str = b.to_s tokens_a = tokenize(a_str, case_sensitive: case_sensitive) tokens_b = tokenize(b_str, case_sensitive: case_sensitive) max_len = [tokens_a.length, tokens_b.length].max max_len.times do |i| chunk_a = tokens_a[i] chunk_b = tokens_b[i] # Shorter token list comes first return -1 if chunk_a.nil? return 1 if chunk_b.nil? # Both numeric if chunk_a.is_a?(Integer) && chunk_b.is_a?(Integer) cmp = chunk_a <=> chunk_b return cmp unless cmp.zero? next end # Both strings if chunk_a.is_a?(String) && chunk_b.is_a?(String) cmp = chunk_a <=> chunk_b return cmp unless cmp.zero? next end # Mixed: numbers sort before strings return chunk_a.is_a?(Integer) ? -1 : 1 end 0 end |
.group_by_prefix(array, case_sensitive: false) ⇒ Hash<String, Array<String>>
Splits each string at the first digit boundary, groups by the non-numeric prefix. Each group’s values are naturally sorted.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 201 def self.group_by_prefix(array, case_sensitive: false) groups = {} array.each do |str| s = str.to_s match = s.match(/\A([^\d]*)/) prefix = match ? match[1] : '' groups[prefix] ||= [] groups[prefix] << str end groups.each_value do |values| values.replace(sort(values, case_sensitive: case_sensitive)) end groups end |
.max(array, case_sensitive: false) ⇒ String?
Finds the naturally largest element without full sort.
132 133 134 135 136 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 132 def self.max(array, case_sensitive: false) return nil if array.empty? array.max { |a, b| compare(a, b, case_sensitive: case_sensitive) } end |
.min(array, case_sensitive: false) ⇒ String?
Finds the naturally smallest element without full sort.
121 122 123 124 125 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 121 def self.min(array, case_sensitive: false) return nil if array.empty? array.min { |a, b| compare(a, b, case_sensitive: case_sensitive) } end |
.natural_key(str, case_sensitive: false) ⇒ Array
Returns a sort key array usable with Ruby’s built-in sort_by, min_by, max_by, etc.
The key is an array of [type_flag, value] pairs where type_flag ensures correct ordering between numeric and string chunks (numbers sort before strings).
146 147 148 149 150 151 152 153 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 146 def self.natural_key(str, case_sensitive: false) return [[-1, '']] if str.nil? tokens = tokenize(str.to_s, case_sensitive: case_sensitive) tokens.map do |chunk| chunk.is_a?(Integer) ? [0, chunk] : [1, chunk] end end |
.sort(array, case_sensitive: false, reverse: false, ignore_case: false) ⇒ Array<String, nil>
Sorts an array of strings in natural order.
82 83 84 85 86 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 82 def self.sort(array, case_sensitive: false, reverse: false, ignore_case: false) effective_case_sensitive = ignore_case ? false : case_sensitive result = array.sort { |a, b| compare(a, b, case_sensitive: effective_case_sensitive) } reverse ? result.reverse : result end |
.sort_by(array, case_sensitive: false, reverse: false, ignore_case: false) {|element| ... } ⇒ Array
Sorts an array by the natural order of values returned by the block.
96 97 98 99 100 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 96 def self.sort_by(array, case_sensitive: false, reverse: false, ignore_case: false, &block) effective_case_sensitive = ignore_case ? false : case_sensitive result = array.sort { |a, b| compare(block.call(a), block.call(b), case_sensitive: effective_case_sensitive) } reverse ? result.reverse : result end |
.sort_by_stable(array, case_sensitive: false) {|element| ... } ⇒ Array
Stable sort by block result, preserving original order for equal elements.
161 162 163 164 165 166 167 168 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 161 def self.sort_by_stable(array, case_sensitive: false, &block) array.each_with_index .sort_by do |element, index| key_str = block.call(element) [natural_key(key_str, case_sensitive: case_sensitive), index] end .map(&:first) end |
.sort_stable(array, case_sensitive: false) ⇒ Array<String, nil>
Stable sort that preserves original order for equal elements.
107 108 109 110 111 112 113 114 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 107 def self.sort_stable(array, case_sensitive: false) array.each_with_index .sort_by do |element, index| [tokenize(element.nil? ? '' : element.to_s, case_sensitive: case_sensitive), element.nil? ? 0 : 1, index] end .map(&:first) end |
.tokenize(str, case_sensitive: false) ⇒ Array<String, Integer>
Splits a string into chunks of text and numbers for natural comparison.
10 11 12 13 14 15 |
# File 'lib/philiprehberger/natural_sort/comparator.rb', line 10 def self.tokenize(str, case_sensitive: false) normalized = case_sensitive ? str : str.downcase normalized.scan(/\d+|[^\d]+/).map do |chunk| chunk.match?(/\A\d+\z/) ? chunk.to_i : chunk end end |