0

I have a few hundred images in an S3 bucket which I write into an array and want them being sorted and displayed alphabetically. The images all start with the same pattern

#1 <some name>.jpg
#2 <some name>.jpg
...
#10 <some name>.jpg
#11 <some name>.jpg
...

after using

@images.each do |image|
@image[i] = image.url_for(:read).to_s
    i = i + 1
end

@image.sort

however, the @image array is being sorted this way:

<s3 bucket URL>/#1 <some name>.jpg
<s3 bucket URL>/#10 <some name>.jpg
<s3 bucket URL>/#11 <some name>.jpg
...
<s3 bucket URL>/#2 <some name>.jpg
<s3 bucket URL>/#20 <some name>.jpg
<s3 bucket URL>/#21 <some name>.jpg
...

It is my understanding that the "alphabet" and therefore the sorting should sort #1, #2, #3 etc. but that does not seem to be the case. Obviously, I want it being sorted this way:

<s3 bucket URL>/#1 <some name>.jpg
<s3 bucket URL>/#2 <some name>.jpg
<s3 bucket URL>/#3 <some name>.jpg
...
<s3 bucket URL>/#10 <some name>.jpg
<s3 bucket URL>/#11 <some name>.jpg
<s3 bucket URL>/#12 <some name>.jpg
...

How could I reach this with a sort algorithm in this case? Thanks for any help.

2 Answers 2

2

It is my understanding that the "alphabet" and therefore the sorting should sort #1, #2, #3 etc.

That sentence doesn't make any sense, but in the computer programming alphabet "1" comes before "2", and when Ruby compares strings, it compares the strings character by character until it finds a difference. So when Ruby is comparing "11" and "2", it starts by comparing the first characters, which means it compares "1" and "2". Because the characters are different, it won't compare any more characters; and because "1" comes before "2", the string "11" comes before the string "2".

fnames = [
  "#1 <some name>.jpg",
  "#2 <some name>.jpg",
  "#10 <some name>.jpg",
  "#11 <some name>.jpg",
]


results = fnames.sort_by do |fname|
  match_data = fname.match(/#(\d+)/)
  match_data[1].to_i
end

p results

--output:--
["#1 <some name>.jpg", 
 "#2 <some name>.jpg", 
 "#10 <some name>.jpg", 
 "#11 <some name>.jpg"]

And if you are into one liners:

results = fnames.sort_by {|fname| fname[/#(\d+)/, 1].to_i }

If there can be file names with identical numbers, then you need to do some more work:

fnames = [
  "<s3 bucket URL>/#1 <some name>.jpg",
  "<s3 bucket URL>/#10 <some name>.jpg",
  "<s3 bucket URL>/#11 xyz.jpg",
  "<s3 bucket URL>/#11 abc.jpg",
  "<s3 bucket URL>/#2 <some name>.jpg", 
  "<s3 bucket URL>/#20 <some name>.jpg",
  "<s3 bucket URL>/#21 <some name>.jpg",
]

results = fnames.sort_by do |fname| 
  [
    fname[/#(\d+)/, 1].to_i, 
    $'    #The rest of the string after the match
  ]
end


p results

--output:--
["<s3 bucket URL>/#1 <some name>.jpg", 
 "<s3 bucket URL>/#2 <some name>.jpg", 
 "<s3 bucket URL>/#10 <some name>.jpg", 
 "<s3 bucket URL>/#11 abc.jpg", 
 "<s3 bucket URL>/#11 xyz.jpg", 
 "<s3 bucket URL>/#20 <some name>.jpg", 
 "<s3 bucket URL>/#21 <some name>.jpg"]

Ruby sorts arrays similarly to strings: It compares the first elements in the arrays, and if they are the same, then it compares the second elements, etc. until it finds a difference. The sort_by() block tells Ruby to pretend that each filename is actually a two element array.

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

2 Comments

Does not work unfortunately, the array is still sorted #1, #10, #11 .... #2, #21, ... Here is my code: images = s3.buckets['mybucket'].objects i = 0 fnames = Array.new images.each do |fname| fnames[i] = fname.url_for(:read).to_s i = i + 1 end @results = fnames.sort_by do |fname| [ fname[/#(\d+)/, 1].to_i, $' #The rest of the string after the match ] end
@MartinBraun, You posted the file names you wanted to sort. My code sorts those filenames. It works the way you requested--look at the results. The code you use to produce those filenames is irrelevant.
1
@images.sort_by{ |filename| filename[1..-1].to_i }

6 Comments

thanks for your answer! guess you mean @image (singular) since this is the array I want to sort. using your code does not help - it is still sorted the same way.
ok, @image.sort_by{ |filename| filename[(n + 1)..-1].to_i }, where n is the length of the s3 bucket url up to and including the /
@bgates, You don't understand the problem. Sorting strings does not produce the same result as sorting numbers. Try sorting the strings "1", "2", "10", "11". The op wants the sorted order to be "1", "2", "10", "11".
@7stud, you aren't reading carefully. That or you don't know what #to_i does.
@Martin Braun you have to assign the result to a variable (sort_bygives you a sorted copy of the original). sorted = @images.sort_by{|...etc.
|

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.