Sort strings and numbers in Ruby
Solution 1
A general trick for solving tricky sorts is to use #sort_by, with the block returning an array having the primary and secondary sort order (and, if you need it, tertiary, etc.)
a = ['foo', 'bar', '1', '2', '10']
b = a.sort_by do |s|
if s =~ /^\d+$/
[2, $&.to_i]
else
[1, s]
end
end
p b # => ["bar", "foo", "1", "2", "10"]
This works because of the way array comparison is defined by Ruby. The comparison is defined by the Array#<=> method:
Arrays are compared in an “element-wise” manner; the first element of ary is compared with the first one of other_ary using the <=> operator, then each of the second elements, etc… As soon as the result of any such comparison is non zero (i.e. the two corresponding elements are not equal), that result is returned for the whole array comparison.
Solution 2
Sort an array of mixed numbers and strings by putting the numbers first, and in order, followed by the strings second, and in order.
>> a = [1, 2, "b", "a"]
>> a.partition{|x| x.is_a? String}.map(&:sort).flatten
=> ["a", "b", 1, 2]
Solution 3
a = ['1', '10', '100', '2', '42', 'hello', 'x1', 'x20', 'x100', '42x', '42y', '10.1.2', '10.10.2', '10.8.2']
a.map {|i| i.gsub(/\d+/) {|s| "%08d" % s.to_i } }.zip(a).sort.map{|x,y| y}
# => ["1", "2", "10", "10.1.2", "10.8.2", "10.10.2", "42", "42x", "42y", "100", "hello", "x1", "x20", "x100"]
Solution 4
Normally, alphabetization is done with numbers first. If you want to alphabetize something where letters are alphabetized before numbers, you will need to alter the compare function used.
# I realize this function could be done with less if-then-else logic,
# but I thought this would be clearer for teaching purposes.
def String.mysort(other)
length = (self.length < other.length) ? self.length : other.length
0.upto(length-1) do |i|
# normally we would just return the result of self[i] <=> other[i]. But
# you need a custom sorting function.
if self[i] == other[i]
continue # characters the same, skip to next character.
else
if self[i] ~= /[0-9]/
if other[i] ~= /[0-9]/
return self[i] <=> other[i] # both numeric, sort normally.
else
return 1 # self is numeric, other is not, so self is sorted after.
end
elsif other[i] ~= /[0-9]/
return -1 # self is not numeric, other is, so self is sorted before.
else
return self[i] <=> other[i] # both non-numeric, sort normally.
end
end
end
# if we got this far, the segments were identical. However, they may
# not be the same length. Short sorted before long.
return self.length <=> other.length
end
['0','b','1','a'].sort{|x,y| x.mysort(y) } # => ['a', 'b', '0', '1']
Solution 5
Here is a somewhat verbose answers. Divide the array into two sub arrays: strings and numbers, sort them and concat them.
array = [1, 'b', 'a', 'c', 'd', 2, 4, 3]
strings = []
numbers = []
array.each do |element|
if element.is_a? String
strings << element
else
numbers << element
end
end
sorted_array = strings.sort + numbers.sort
sorted_array # ['a', 'b', 'c', 'd', 1, 2, 3, 4]
thenengah
I taught ESL after university, then I started making things for the internet. My favorite tools are mac, ubuntu, vim, tmux, bash, git, javascript/node/express, ruby/rails, react, redux, bootstrap, sass, webpack, gulp, babel, jest, mysql, mongodb, redis, neo4j, rabbitMQ, ELK, nginx, jenkins, AWS.
Updated on June 06, 2022Comments
-
thenengah almost 2 years
I want to sort an array by strings first and then numbers. How do I do this?