I have a list of files with a bunch of attributes. One of the attributes is the file name which is how I would like to sort the list. However, the list goes something like t
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.
gem "natural_sort"
list = ["a10", "a", "a20", "a1b", "a1a", "a2", "a0", "a1"]
list.sort(&NaturalSort) # => ["a", "a0", "a1", "a1a", "a1b", "a2", "a10", "a20"]
array.sort_by{|x| ( x.class == Array ? x.join(" ") : x.to_s ).split(/(\d+)/).map{|x| x.to_s.strip }.select{|x| x.to_s != "" }.map{|x| x =~ /\d+/ ? x.to_s.rjust(30) : x }}
This can compare arrays by arrays in the sort_by method even if the type of the matching items differ. Even if there are deeper nested arrays. Example:
[ "3 a 22", "b 22 1", " b 5 ", [11, 2, [4, 5]] ] #=>
[ "3 a 22", [11, 2, [4, 5]], " b 5 ", "b 22 1" ]
The point here is that during the sort if an item is a nested array then we convert it to a string beforehand. And if parts of the string contain digits only then we do not convert them to numeric values but instead extend them with spaces, like:
30 #=> " 30"
This way all objects will be compatible strings and the sorting will be able to compare them resulting in a numeric sort if the matching objects at their positions are numbers only.
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.
generic answer for strings natural sort
array.sort_by {|e| e.split(/(\d+)/).map {|a| a =~ /\d+/ ? a.to_i : a }}
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">]