Yes it is a duplicate of Is there a natural_sort_by method for Ruby? , but i think dawg and Eric made it clearer here, at least to me these answers are more exhaustive..
I have an array like this:
arr = ["file1.txt", "file11.txt", "file12.txt", "file2.txt", "file3.txt"]
And i want it to be sorted like this:
arr = ["file1.txt", "file2.txt", "file3.txt", "file11.txt", "file12.txt"]
How can i do this? I tried with sort
, but it's not very clear to me..
I sorted the files by size like this:
files=Dir.entries("./").sort { |f| File.size(f) }.select { |f| File.file?(f) }
Eric's answer is great way of doing a Natural Sort Order for the digits only in file names. Works if all file names have the same prefix.
If you want to add a second element (for example, file names that do not have digits in them) you can great a multi-element sort_by be creating a list:
filenames = ["file1.txt", "file11.txt", "file12.txt", "file2.txt", "file3.txt","file.txt", "File.txt"]
filenames.sort_by{ |name| [name[/\d+/].to_i, name] }
=> ["File.txt", "file.txt", "file1.txt", "file2.txt", "file3.txt", "file11.txt", "file12.txt"]
The two element of the sort_by
implements:
name[/\d+/].to_i
thenname
.More robustly, you can split the entire string by digits and convert each to an int:
> "abc123def456gh".split(/(\d+)/).map{ |e| Integer(e) rescue e}
=> ["abc", 123, "def", 456, "gh"]
So your Natural Sort becomes:
arr.sort_by{ |s| s.split(/(\d+)/).map{ |e| Integer(e) rescue e}}
So now names and numbers (even multiples names and numbers) are handled correctly:
> arr = ["file1.txt", "file11.txt", "file12.txt", "file2.txt", "file3.txt", "gfile10.txt", "gfile1.txt", "gfile.txt", "file.txt", "afile.txt","afile10.txt","afile2.txt" ]
> arr.sort_by{ |s| s.split(/(\d+)/).map{ |e| Integer(e) rescue e}}
=> ["afile2.txt", "afile10.txt", "afile.txt", "file1.txt", "file2.txt", "file3.txt", "file11.txt", "file12.txt", "file.txt", "gfile1.txt", "gfile10.txt", "gfile.txt"]
You can extract the first number out of the filename, convert it to an integer and use it inside sort_by
:
filenames = ["file1.txt", "file11.txt", "file12.txt", "file2.txt", "file3.txt"]
filenames.sort_by{ |name| name[/\d+/].to_i }
# ["file1.txt", "file2.txt", "file3.txt", "file11.txt", "file12.txt"]
/\d+/
is a regex which means "a sequence of 1 or more digits":
"test123"[/\d+/]
# => "123"
"test"[/\d+/]
# => nil
Note that it only sorts by number and ignores the rest:
["a2", "b1", "z3"].sort_by{ |name| name[/\d+/].to_i }
# => ["b1", "a2", "z3"]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With