Class: NaturalSort::Key

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/natural_sort/key.rb

Overview

A precomputed, comparable sort key. Wrap a value with NaturalSort.key (or the NaturalSort() helper) and use it as a sort_by key:

array.sort_by { |string| NaturalSort.key(string) }

The string is split once, on construction: digit runs with no leading zero become Integers (compared by value); everything else — text, and digit runs with a leading zero — stays a String (compared by byte value). Whitespace is skipped. This reproduces Martin Pool’s strnatcmp ordering.

Splitting runs over the raw bytes, so any input sorts by byte value rather than raising — including malformed encodings (e.g. Latin-1 bytes mislabeled UTF-8) and ASCII-incompatible ones (UTF-16/UTF-32). For valid UTF-8, byte order and codepoint order agree, so this changes ordering for no one.

Instance Method Summary collapse

Constructor Details

#initialize(input) ⇒ Key

Returns a new instance of Key.



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/natural_sort/key.rb', line 30

def initialize(input)
  @input = input.to_s.dup.freeze
  @segments = @input.b.scan(TOKENIZER).filter_map do |token|
    if token.match?(WHITESPACE)
      nil
    elsif NUMERIC.match?(token)
      Integer(token)
    else
      token
    end
  end.freeze
  freeze
end

Instance Method Details

#<=>(other) ⇒ Integer?

Three-way comparison. Numeric segments (Integers) compare by value when paired with another numeric segment; in every other pairing both sides compare by byte value. A non-zero-leading integer’s #to_s is its original digits, so the cross-type byte comparison stays exact.

Returns:

  • (Integer, nil)

    -1, 0, or 1, or nil when other is not a Key



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/natural_sort/key.rb', line 54

def <=>(other)
  return nil unless other.is_a?(Key)

  mine = segments
  theirs = other.segments
  index = 0

  while index < mine.length
    right = theirs[index]
    return 1 if right.nil?

    left = mine[index]
    result =
      if left.is_a?(Integer)
        right.is_a?(Integer) ? left <=> right : left.to_s <=> right
      else
        right.is_a?(Integer) ? left <=> right.to_s : left <=> right
      end
    return result unless result.zero?

    index += 1
  end

  theirs.length > mine.length ? -1 : 0
end

#eql?(other) ⇒ Boolean

Equal keys (same token list) hash alike and collapse in a Hash, Set, or #uniq — consistent with #== / #<=>, since segments are equal exactly when the comparison is 0.

Returns:

  • (Boolean)


85
86
87
# File 'lib/natural_sort/key.rb', line 85

def eql?(other)
  other.is_a?(Key) && segments == other.segments
end

#hashInteger

Returns:

  • (Integer)


90
91
92
# File 'lib/natural_sort/key.rb', line 90

def hash
  segments.hash
end

#to_sObject



44
45
46
# File 'lib/natural_sort/key.rb', line 44

def to_s
  @input
end