Reputation: 5740
For scoping purposes, I'm looking for an unusual sorting mechanism.
Let's suppose we have the following array:
arr = ["33", "a30", "b333", "44", "22", "a15"]
How can I sort it so that non-numeric strings sort before numeric ones?
This is the result I wish to achieve
sorted_arr = ["a15", "a30", "b333", "22", "33", "44"]
I have tried various combinations of sort_by
and sort
but I've not achieved the correct ordering yet... So, any help will be appreciated. Thanks.
PS: I know I've written "for scoping purposes", but the answer does not need to be for using in a default_scope
block. I'll try to adjust it later, once I've found the right sorting technique.
Upvotes: 0
Views: 195
Reputation: 110675
Here's another way to do it.
Code
def sort_em(arr)
a = arr.sort
return a if a.first =~ /\D/ || a.last !~ /\D/
a.rotate(a.index(a.find { |e| e =~ /\D/ }))
end
Example
arr = ["33", "a30", "b333", "44", "22", "a15"]
sort_em(arr)
#=> ["a15", "a30", "b333", "22", "33", "44"]
Notes
sort
that intermixes numeric and non-numeric entries. " 1"
or "1 "
may be present and are to be treated as "numeric", add .strip
before =~
and ~=~
. arr
is empty, [].first =~ /^\d+$/ #=> nil
Gentlemen, start your engines
require 'benchmark'
n = 10 # Example
arr = (Array.new(n) {rand(n).to_s } +
Array.new(n) { (97+rand(26)).chr + rand(n).to_s }).shuffle
#=> ["9", "5", "a4", "u6", "u3", "g6", "5", "l0", "9", "9",
# "3", "8", "t1", "3", "o5", "l6", "3", "i6", "l1", "0"]
n = 200_000
arr = (Array.new(n) {rand(n).to_s } +
Array.new(n) { (97+rand(26)).chr + rand(n).to_s }).shuffle
def sawa(arr) arr.sort_by{ |e| [e =~ /\A\d/ ? 1 : 0, e] } end
def darse(arr) arr.partition { |x| x =~ /\D/ }.flat_map(&:sort) end
def uri(arr) arr.sort.partition { |x| x[/^[^\d]/] }.inject(:+) end
def cary(arr)
a = arr.sort
return a if a.first =~ /\D/ || a.last !~ /\D/
a.rotate(a.index(a.find { |e| e =~ /\D/ }))
end
Benchmark.bm(12) do |bm|
bm.report('@sawa' ) { sawa(arr) }
bm.report('@theDarse' ) { darse(arr) }
bm.report('@UriAgassi') { uri(arr) }
bm.report('@Cary' ) { cary(arr) }
end
user system total real
@sawa 3.340000 0.020000 3.360000 ( 3.364740)
@theDarse 0.530000 0.020000 0.550000 ( 0.547846)
@UriAgassi 0.700000 0.010000 0.710000 ( 0.711513)
@Cary 0.430000 0.010000 0.440000 ( 0.438975)
Admittedly, it's a very limited benchmark.
Upvotes: 1
Reputation: 37409
You can do this:
arr.sort.partition { |x| x[/^[^\d]/] }.inject(:+)
# => ["a15", "a30", "b333", "22", "33", "44"]
This sorts the array, then it groups all the elements not starting with a digit, and all the elements that do:
arr.sort.partition { |x| x[/^[^\d]/] }
# => [["a15", "a30", "b333"], ["22", "33", "44"]]
and then it joins the two arrays together.
Upvotes: 0
Reputation: 737
It's not the most elegant solution but one way to do this would be to create two arrays, one holding all the elements starting with a non-numeric, one holding all the elements starting with a numeric item. Sort the two arrays and then concatenate the arrays
Upvotes: 0
Reputation: 168081
arr.sort_by{|e| [e =~ /\A\d/ ? 1 : 0, e.your_original_sort_condition]}
Upvotes: 4