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.

ndnenkov
  • 35,425
  • 9
  • 72
  • 104
sam
  • 372
  • 2
  • 12
  • Possible duplicate of [How to sort an alphanumeric array in ruby](http://stackoverflow.com/questions/5480703/how-to-sort-an-alphanumeric-array-in-ruby) – Ajay Barot Feb 22 '17 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. – Cary Swoveland Feb 22 '17 at 21:12

3 Answers3

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.

ndnenkov
  • 35,425
  • 9
  • 72
  • 104
  • @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. – eiko Feb 22 '17 at 14:41
  • 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. – Cary Swoveland Feb 22 '17 at 20:54
  • Note that your method doesn't sort `%w(b c a)` at all, and sort `%w(a1a0 z1)` in a potentially surprising way. – Eric Duminil Feb 22 '17 at 22:34
  • @CarySwoveland, added a high level explanation. – ndnenkov Feb 23 '17 at 07:11
  • @EricDuminil, true, but the question doesn't seem to be concerned with such cases. – ndnenkov Feb 23 '17 at 07:13
  • Just yesterday, you wrote : "a lot of things might work with the example's values. It doesn't mean they are correct in the general case". Please make up your mind and be consistent with your quality standards. ;) – Eric Duminil Feb 23 '17 at 08:39
  • @EricDuminil, it is consistent. Here OP's general case is just different numbers, not different formats. In the other question it was also different numbers, it didn't need to be exactly **two** **consecutive** numbers. Context is important. – ndnenkov Feb 23 '17 at 08:57
  • How do you know it won't be used with different formats? How did you know it would be used with non-consecutive numbers? – Eric Duminil Feb 23 '17 at 09:46
  • @EricDuminil, I'm not sure if you are trolling me right now. xd I don't possess absolute metacognition so I'm not able to define *context* as a set of *rules*. Intuitively, the pattern in OP's example is consistent. Even if the format changes, the important property here (all file names have the same number of numbers in the same order of importance) should still be consistent. If there are exceptions, the question should have stated them. As there doesn't seem to be obvious defaults for these situations, not doing so would border the expectation of mind reading capabilities. – ndnenkov Feb 23 '17 at 10:18
  • @EricDuminil, In the other question, the other OP stated exactly what the pattern was - the *age* being *one of* some *ages*. Yet the answer was trying to add more constraints that the pattern didn't include - falling into a range, just based on the example. For any example, you can impose additional constraints and still get the correct answer. – ndnenkov Feb 23 '17 at 10:18
  • @EricDuminil, So in short - my answer here is just as generic as needed for the pattern. I inferred the pattern from the example provided as it was not clearly defined. In the other question, the answer was less generic than the pattern. The pattern was defined by the question description itself. – ndnenkov Feb 23 '17 at 10:18
  • I'm not trolling. It's true I'm finicky with your answers though, simply because you usually are with others ;). I just wanted to point out that this answer works for the given example but would fail with very slight variations of the given format, e.g. with folders or `page_a_1, page_b_1`. – Eric Duminil Feb 23 '17 at 10:31
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>] 
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
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
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124