First of all, your my_array
is JavaScript/JSON so I'll assume that you really have this:
my_array = [
{"id" => "some-server-1", "foo" => "bar"},
{"id" => "some-server-2", "foo" => "bat"},
{"id" => "some-server-10", "foo" => "baz"}
]
Then you just need to sort_by
the numeric suffix of the 'id'
values:
my_array.sort_by { |e| e['id'].sub(/^some-server-/, '').to_i }
If the "some-server-" prefixes aren't always "some-server-" then you could try something like this:
my_array.sort_by { |e| e['id'].scan(/\D+|\d+/).map { |x| x =~ /\d/ ? x.to_i : x } }
That would split the 'id'
values into numeric and non-numeric pieces, convert the numeric pieces to integers, and then compare the mixed string/integers arrays using the Array <=>
operator (which compares component-wise); this will work as long as the numeric and non-numeric components always match up. This approach would handle this:
my_array = [
{"id" => "some-server-1", "foo" => "bar"},
{"id" => "xxx-10", "foo" => "baz"}
]
but not this:
my_array = [
{"id" => "11-pancakes-23", "foo" => "baz"},
{"id" => "some-server-1", "foo" => "bar"}
]
If you need to handle this last case then you'd need to compare the arrays entry-by-entry by hand and adjust the comparison based on what you have. You could still get some of the advantages of the sort_by
Schwartzian Transform with something like this (not very well tested code):
class NaturalCmp
include Comparable
attr_accessor :chunks
def initialize(s)
@chunks = s.scan(/\D+|\d+/).map { |x| x =~ /\d/ ? x.to_i : x }
end
def <=>(other)
i = 0
@chunks.inject(0) do |cmp, e|
oe = other.chunks[i]
i += 1
if(cmp == 0)
cmp = e.class == oe.class \
? e <=> oe \
: e.to_s <=> oe.to_s
end
cmp
end
end
end
my_array.sort_by { |e| NaturalCmp.new(e['id']) }
The basic idea here is to push the comparison noise off to another class to keep the sort_by
from degenerating into an incomprehensible mess. Then we use the same scanning as before to break the strings into pieces and implement the array <=>
comparator by hand. If we have two things of the same class then we let that class's <=>
deal with it otherwise we force both components to String and compare them as such. And we only care about the first non-0 result.