Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sort file names in ruby? [duplicate]

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) }
like image 731
lucaortis Avatar asked Jul 14 '18 16:07

lucaortis


Video Answer


2 Answers

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:

  1. Integer value of digits if any are found with a regex name[/\d+/].to_i then
  2. Name if no digits or same digits name.

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"]
like image 174
dawg Avatar answered Oct 19 '22 04:10

dawg


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"]
like image 4
Eric Duminil Avatar answered Oct 19 '22 05:10

Eric Duminil