Is there a natural_sort_by method for Ruby?

As long as files are always named "file #", you could do

files.sort_by{|f| f.name.split(" ")[1].to_i }

This splits on the space, and grabs the number to do the sorting.


Here's another take on a "natural" sort method:

class String
  def naturalized
    scan(/[^\d\.]+|[\d\.]+/).collect { |f| f.match(/\d+(\.\d+)?/) ? f.to_f : f }
  end
end

This converts something like "Filename 10" into a simple array with floats in place of numbers [ "Filename", 10.0 ]

You can use this on your list:

files.sort_by! { |file| file.name.to_s.naturalized }

This has the advantage of working on arbitrary numbers in unpredictable positions. The paranoid .to_s call in that block is to ensure that there is a string and not an inadvertent nil when sorting.


I've created a natural sort gem. It can sort by an attribute like this:

# Sort an array of objects by the 'number' attribute
Thing = Struct.new(:number, :name)
objects = [
  Thing.new('1.1', 'color'),
  Thing.new('1.2', 'size'),
  Thing.new('1.1.1', 'opacity'),
  Thing.new('1.1.2', 'lightness'),
  Thing.new('1.10', 'hardness'),
  Thing.new('2.1', 'weight'),
  Thing.new('1.3', 'shape')
  ]
Naturally.sort_by(objects, :number)

# => [#<struct Thing number="1.1", name="color">,
      #<struct Thing number="1.1.1", name="opacity">,
      #<struct Thing number="1.1.2", name="lightness">,
      #<struct Thing number="1.2", name="size">,
      #<struct Thing number="1.3", name="shape">,
      #<struct Thing number="1.10", name="hardness">,
      #<struct Thing number="2.1", name="weight">]

generic answer for strings natural sort

array.sort_by {|e| e.split(/(\d+)/).map {|a| a =~ /\d+/ ? a.to_i : a }}