Sorting an array with numbers niggly issue!

I have an array, that I want to sort by its numeric values.

choices = [“test1_banana”, “test2_hamster”, “test9_apple”, “test10_egg”,
“test4_jam”, “test11_cheese”]

=> choices.sort
=> [“test10_egg”, “test11_cheese”, “test1_banana”, “test2_hamster”,
“test4_jam”, “test9_apple”]

not sorted, 1 should come before 10.

I tried regex’ing the values
choices.each do |choice|
num = choice.slice(/\d+/)
puts num
end

But how can I sort this simple array by its num value above?

Any help much appreciated!

A newbie member of El Rug!

Bradley S. wrote in post #1003478:

I have an array, that I want to sort by its numeric values.

choices = [“test1_banana”, “test2_hamster”, “test9_apple”, “test10_egg”,
“test4_jam”, “test11_cheese”]

=> choices.sort
=> [“test10_egg”, “test11_cheese”, “test1_banana”, “test2_hamster”,
“test4_jam”, “test9_apple”]

not sorted, 1 should come before 10.

Actually, the comparison is “test1_” to “test10”. Computers can only
store numbers–not letters. The numbers that ruby uses to represent
letters are by default ascii codes. An underscore has an ascii code of
95 while the ascii code for zero is 48. When you compare strings, you
are comparing the characters’ numeric codes, so your strings are
correctly sorted in ascending order. To sort according to numeric
values, you have to convert the numeric portion of the string to a
number:

choices = %w[
test1_banana
test2_hamster
test9_apple
test10_egg
test4_jam
test11_cheese
]

results = choices.sort_by do |word|
word.match(/(\d+)/)
$1.to_i
end

p results

–output:–
[“test1_banana”, “test2_hamster”, “test4_jam”, “test9_apple”,
“test10_egg”, “test11_cheese”]

Bradley S. wrote in post #1003492:

Thank you. I understand the logic of the non-numeric characters being
read as number values. But, in your explanation, and I recognise it
works completely, what is the shorthand:

$1.to_i

??

When you do a regex match, you can enclose portions of the pattern in
parentheses and the matches for those portions of the regex will be
assigned to $1, $2, $3, …$n:

“hello10world”.match(/(\d+)(.)orld/)
puts $1, $2

–output:–
10
w

But remember regexs match strings, so you have to convert the match to
a number, which is the reason for to_i.

I could have also left out the parentheses, and done this:

“hello10world”.match(/\d+/)
$&.to_i # $& contains the whole match

or

md = “hello10world”.match(/\d+/)

md[0].to_i #md[0] is the whole match

If you are wondering what is the whole purpose of the line:

$1.to_i

…it is the return value of the block. sort_by() associates every
element of the array with the return value of the block–, i.e. it
creates a bunch of two element arrays. Then sort_by sorts those two
element arrays only looking at the integer value.

Thats brilliant, and very powerful.

As you know I’m a newbie to Ruby, but I really enjoy programming, and I
suppose tricks like the above get learned with time and experience.

Can you recommend me any sources which can enhance my programming with
Ruby. So far I have completed all the exercises in Chris P.'s, Learn
to Program book and have now started reading the Pickaxe, can you
recommend anything else?

Thank you for your replies, and of course, for someone like me, its an
honour to learn from someone with your capabilities.

Thank you. I understand the logic of the non-numeric characters being
read as number values. But, in your explanation, and I recognise it
works completely, what is the shorthand:

$1.to_i

??

I think I just need more programming experience. Like today I read
through the pickaxe book, for the Fibonnaci sequence using blocks:

def fib_up_to(max)
i1, i2 = 1, 1
while i1 <= max
yield i1
i1, i2 = i2, i1+i2
end
end

fib_up_to(1000) {|f| printf, " "}

My question is in here, when I read through it, it appears to me i1
always remains as 1, so how can it increase each time yield is called. I
dont see any addition assignments to i1.

I presume the i1+i2 is the increment for max?

Simple but tricky to trace through visually.

Bradley S. wrote in post #1003497:

Thats brilliant, and very powerful.

Yes. You could also do the sorting using sort():

choices = %w[
test1_banana
test2_hamster
test9_apple
test10_egg
test4_jam
test11_cheese
]

results = choices.sort do |a, b|
a.match(/\d+/)[0].to_i <=> b.match(/\d+/)[0].to_i
end

p results

–output:–
[“test1_banana”, “test2_hamster”, “test4_jam”, “test9_apple”,
“test10_egg”, “test11_cheese”]

However, sorting requires comparing each item several times with other
items. As the calculation specified in the sort() block gets more
complex–in this case the regex matching and the to_i()'ing–it takes
more time to execute, and doing that calculation many times for the same
item becomes too costly. That is where sort_by() enters the picture.

Here are the steps sort_by goes through:

choices = %w[
test1_banana
test2_hamster
test9_apple
test10_egg
test4_jam
test11_cheese
]

arr = choices.map do |word|
md = word.match(/\d+/)
[md[0].to_i, word]
end

–output:–
[[1, “test1_banana”], [2, “test2_hamster”], [9, “test9_apple”], [10,
“test10_egg”], [4, “test4_jam”], [11, “test11_cheese”]]

sorted = arr.sort do |a, b|
a.first <=> b.first
end

p sorted

–output:–
[[1, “test1_banana”], [2, “test2_hamster”], [4, “test4_jam”], [9,
“test9_apple”], [10, “test10_egg”], [11, “test11_cheese”]]

results = sorted.map do |arr|
arr.last
end

p results

–output:–
[“test1_banana”, “test2_hamster”, “test4_jam”, “test9_apple”,
“test10_egg”, “test11_cheese”]

Note that when sorting, a given item still needs to be compared to other
items multiple times, but the complex calculation is only done once for
each item, with the result of the complex calculation stored as the
first element of the sub-array. The comparisons are then done without
having to do the complex calculation ever again–no matter how many
times an item is compared to another item.

As you know I’m a newbie to Ruby, but I really enjoy programming, and I
suppose tricks like the above get learned with time and experience.

Yes, the steps taken by sort_by() are a famous idiom in computer
programming for making sorting more efficient.

Can you recommend me any sources which can enhance my programming with
Ruby. So far I have completed all the exercises in Chris P.'s, Learn
to Program book and have now started reading the Pickaxe, can you
recommend anything else?

Read these forums and try to answer as many of the questions as you can
without looking at other people’s answers.

Thank you for your replies, and of course, for someone like me, its an
honour to learn from someone with your capabilities.

Thanks for the compliment, but I feel like a beginner myself!

Bradley S. wrote in post #1003633:

I think I just need more programming experience. Like today I read
through the pickaxe book, for the Fibonnaci sequence using blocks:

def fib_up_to(max)
i1, i2 = 1, 1
while i1 <= max
yield i1
i1, i2 = i2, i1+i2
end
end

fib_up_to(1000) {|f| printf, " "}

My question is in here, when I read through it, it appears to me i1
always remains as 1, so how can it increase each time yield is called. I
dont see any addition assignments to i1.

I presume the i1+i2 is the increment for max?

Initially, the method sets some values:

i1, i2 = 1, 1

The first thing to realize is that in computer programming ‘=’ means
‘assignment’, which means take the values on the right of the equals
sign and assign them to the variables on the left of the equals sign.

So the parallel assignment above is the same as writing this:

i1 = 1
i2 = 2

Inside the while loop, the yield() to the block does nothing to those
values, so you can ignore the yield and the block completely, giving you
this:

i1 = 1
i2 = 2

while i1 < 1000
i1, i2 = i2, i1+i2
end

Note that the assignments above the while loop only happen once. Then
the code inside the while loop repeatedly executes. The first time the
parallel assignment inside the while loop executes,
you plug in the values for the variables on the right side of the equals
sign, and you get this:

i1, i2 = 2, 3

which is the same as

i1 = 2
i2 = 3

The next time the parallel assignment inside the while loop executes,
you get this:

i1, i2 = 3, 5

which is the same as

i1 = 3
i2 = 5

And that continues until the while loop condition is false, which
happens when i1 becomes greater than 1,000.