Created
September 21, 2011 16:41
-
-
Save jlapier/1232593 to your computer and use it in GitHub Desktop.
sort objects with mixed alpha and numerical strings so that "A 10" follows "A 9"
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// a simpler version in javascript that actually zero-pads numbers in | |
// the strings so we can sort properly | |
// this one uses underscore.js, but you get the picture | |
// in this example, we have a bunch of text_document objects to sort | |
sorted = _(text_documents).sortBy( | |
// the zero padding is to make "Chap 9" come before "Chap 10" | |
function(td) { return [td.name.replace(/\d+/, function(m) { return zeroPad(m, 99) } )]; } | |
); | |
function zeroPad( number, width ) { | |
width -= number.toString().length; | |
if ( width > 0 ) { | |
return new Array( width + (/\./.test( number ) ? 2 : 1) ).join( '0' ) + number; | |
} | |
return number; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MyObject | |
# make sure you know what field you're sorting on - in this case we're using 'name' | |
attr_accessor :name | |
# this will sort at multiple levels, for example: "Plan 12:12", "Plan 12:9", "Plan 6:3" will | |
# sort as "Plan 6:3", "Plan 12:9", "Plan 12:12" instead of ruby's default string sort | |
def <=>(other) | |
if name.match(/\d/) and other.name.match(/\d/) and name.gsub(/\d+/,'') == other.name.gsub(/\d+/,'') | |
my_nums = name.scan(/\d+/).map(&:to_i) | |
other_nums = other.name.scan(/\d+/).map(&:to_i) | |
my_nums.each_with_index do |n, i| | |
n < other_nums[i] && (return -1) | |
n > other_nums[i] && (return 1) | |
end | |
else | |
name < other.name && (return -1) | |
name > other.name && (return 1) | |
end | |
0 | |
end | |
end |
Actually I have to admit that I posted this a while ago, and at some point since then I realized that the javascript version works a lot better with certain cases. So I ended up porting the js to ruby and it comes out a lot cleaner (and works more consistently).
def <=>(other)
name.gsub(/\d+/){ |n| "%099d" % n.to_i } <=>
other.name.gsub(/\d+/){ |n| "%099d" % n.to_i }
end
Nice that's much cleaner. I was just in the process of making that a lot shorter.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Works great.
Only change for ruby is:
if name.match(/\d/) and other.name.match(/\d/) and name.gsub(/\d+/,'') != other.name.gsub(/\d+/,'')
Notice the is not equal when comparing the integers (digits), other wise you could compare normally.