How I can sort array data alphanumerically in ruby?
Suppose my array is a = [test_0_1, test_0_2, test_0_3, test_0_4, test_0_5, test_0_6, test_0_7, test_0_8, te
Posting here a more general way to perform a natural decimal sort in Ruby. The following is inspired by my code for sorting "like Xcode" from https://github.com/CocoaPods/Xcodeproj/blob/ca7b41deb38f43c14d066f62a55edcd53876cd07/lib/xcodeproj/project/object/helpers/sort_helper.rb, itself loosely inspired by https://rosettacode.org/wiki/Natural_sorting#Ruby.
Even if it's clear that we want "10" to be after "2" for a natural decimal sort, there are other aspects to consider with multiple possible alternative behaviors wanted:
With those considerations:
scan instead of split, because we're going to have potentially three kinds of substrings to compare (digits, spaces, all-the-rest).Comparable class and with def <=>(other) because it's not possible to simply map each substring to something else that would have two distinct behaviors depending on context (the first pass and the equality pass).This results in a bit lengthy implementation, but it works nicely for edge situations:
# Wrapper for a string that performs a natural decimal sort (alphanumeric).
# @example
# arrayOfFilenames.sort_by { |s| NaturalSortString.new(s) }
class NaturalSortString
include Comparable
attr_reader :str_fallback, :ints_and_strings, :ints_and_strings_fallback, :str_pattern
def initialize(str)
# fallback pass: case is inverted
@str_fallback = str.swapcase
# first pass: digits are used as integers, spaces are compacted, case is ignored
@ints_and_strings = str.scan(/\d+|\s+|[^\d\s]+/).map do |s|
case s
when /\d/ then Integer(s, 10)
when /\s/ then ' '
else s.downcase
end
end
# second pass: digits are inverted, case is inverted
@ints_and_strings_fallback = @str_fallback.scan(/\d+|\D+/).map do |s|
case s
when /\d/ then Integer(s.reverse, 10)
else s
end
end
# comparing patterns
@str_pattern = @ints_and_strings.map { |el| el.is_a?(Integer) ? :i : :s }.join
end
def <=>(other)
if str_pattern.start_with?(other.str_pattern) || other.str_pattern.start_with?(str_pattern)
compare = ints_and_strings <=> other.ints_and_strings
if compare != 0
# we sort naturally (literal ints, spaces simplified, case ignored)
compare
else
# natural equality, we use the fallback sort (int reversed, case swapped)
ints_and_strings_fallback <=> other.ints_and_strings_fallback
end
else
# type mismatch, we sort alphabetically (case swapped)
str_fallback <=> other.str_fallback
end
end
end
Example 1:
arrayOfFilenames.sort_by { |s| NaturalSortString.new(s) }
Example 2:
arrayOfFilenames.sort! do |x, y|
NaturalSortString.new(x) <=> NaturalSortString.new(y)
end
You may find my test case at https://github.com/CocoaPods/Xcodeproj/blob/ca7b41deb38f43c14d066f62a55edcd53876cd07/spec/project/object/helpers/sort_helper_spec.rb, where I used this reference for ordering: [ ' a', ' a', '0.1.1', '0.1.01', '0.1.2', '0.1.10', '1', '01', '1a', '2', '2 a', '10', 'a', 'A', 'a ', 'a 2', 'a1', 'A1B001', 'A01B1', ]
Of course, feel free to customize your own sorting logic now.