Skip to content

Instantly share code, notes, and snippets.

@jlapier
Created September 21, 2011 16:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jlapier/1232593 to your computer and use it in GitHub Desktop.
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"
// 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;
}
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
@jrissler
Copy link

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.

@jlapier
Copy link
Author

jlapier commented Apr 19, 2012

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

@jrissler
Copy link

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