3

I am working on ruby on rails and i am new here as well ruby on rails. I am working on one project and find some issue of sorting an array of string which contains images name. I have tried many algorithm which i know doesn't help me. When i call service for image extraction it gives me like this array.

Example:

["page-1_1.png",
 "page-1_10.png",
 "page-1_11.png",
 "page-1_2.png",
 "page-1_3.png",
 "page-1_4.png",
 "page-1_5.png",
 "page-1_6.png",
 "page-1_7.png",
 "page-1_8.png",
 "page-1_9.png"]

I want to sort this array like this:

["page-1_1.png",
 "page-1_2.png",
 "page-1_3.png",
 "page-1_4.png",
 "page-1_5.png",
 "page-1_6.png",
 "page-1_7.png",
 "page-1_8.png",
 "page-1_9.png",
 "page-1_10.png",
 "page-1_11.png"]

I had tried many things to sort but can't get any solutions. please help me.

2
  • Possible duplicate of How to sort an alphanumeric array in ruby Commented Feb 22, 2017 at 13:20
  • When you give an example (generally a good thing) it's helpful to assign a variable to each input object (e.g., arr = ["page-1_1.png",...]). That way, readers can refer to those variables in answers and comments without having to define them. Moreover, all readers will refer to the same variables, which is also helpful. Commented Feb 22, 2017 at 21:12

3 Answers 3

10
names.sort_by { |name| name.scan(/\d+/).map(&:to_i) }

This finds all numbers in each file name and sorts by said numbers. Note that arrays are ordered by comparing the first element, in case of equality - the second and so on.

Sign up to request clarification or add additional context in comments.

12 Comments

@sam if this answer worked for you, you should mark it as the correct answer, otherwise this question will be marked as 'unanswered' in searches on the site.
Good answer, ✅. By way of explanation, it might be helpful to show the return values for names.map { |name| name.scan(/\d+/) } and names.map { |name| name.scan(/\d+/).map(&:to_i) }, and explain that you are comparing arrays.
Note that your method doesn't sort %w(b c a) at all, and sort %w(a1a0 z1) in a potentially surprising way.
@CarySwoveland, added a high level explanation.
@EricDuminil, true, but the question doesn't seem to be concerned with such cases.
|
2

If you know that the number of pages is bounded (e.g. less than 1000), this method has the advantage of sorting alphabetically and numerically :

arr =
  ['page-1_1.png',
   'page-1_10.png',
   'page-1_11.png',
   'page-1_3.png',
   'page-1_9.png']

puts arr.sort_by{ |str| str.gsub(/\d+/){ |number| number.rjust(3, '0') } }
# page-1_1.png
# page-1_3.png
# page-1_9.png
# page-1_10.png
# page-1_11.png

puts %w(b c a).sort_by{ |str| str.gsub(/\d+/){ |number| number.rjust(3, '0') } }
# a
# b
# c

Here are the temporary strings that are being sorted :

puts arr.map{ |str| str.gsub(/\d+/){ |number| number.rjust(3, '0') } }
# page-001_001.png
# page-001_010.png
# page-001_011.png
# page-001_003.png
# page-001_009.png

Comments

2

For the given array, arr, here's an off-the-wall solution.

require 'ipaddr'

arr.sort_by { |s| IPAddr.new(s.gsub(/\D/) { |c| c == '_' ? '.' : '' } + '.0.0') }
  #=> ["page-1_1.png",
  #    "page-1_2.png",
  #    "page-1_3.png",
  #    "page-1_4.png",
  #    "page-1_5.png",
  #    "page-1_6.png",
  #    "page-1_7.png",
  #    "page-1_10.png",
  #    "page-1_11.png",
  #    "page-3_92.png",
  #    "page-11_8.png"] 

Note that

arr.map { |s| IPAddr.new(s.gsub(/\D/) { |c| c == '_' ? '.' : '' } + '.0.0') }
  #=> [#<IPAddr: IPv4:1.1.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:1.10.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:1.11.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:1.2.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:1.3.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:1.4.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:1.5.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:1.6.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:1.7.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:11.8.0.0/255.255.255.255>,
  #    #<IPAddr: IPv4:3.92.0.0/255.255.255.255>] 

8 Comments

It's probably not the desired behaviour. %w(1_35 12_0 2_0).sort_by { |s| s.delete('_')[/\d+/].to_i }
@Eric, yes, of course. Thanks. I fixed it with what I think is a better approach anyway.
Sorry, still not the desired behaviour. 1_10 and 1_1 are treated the same and 1_11 should be after 1_7
@Eric, that's clever! You should post it. I decided to go with something less mainstream. I await your counterexample...
@Eric, I give up (but will let my answer stand for the possible entertainment value)! LOL!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.