[NUBY] Ruby Way code for shuffling an array doesn't behave asexpected

A

Alexey Verkhovsky

One example in The Ruby Way claims that in the following snippet second
line shuffles the a (i.e., makes an array consisting of the same
elements as a, but in a different order).

a = [1, 2, 3, 4, 5]
puts a.collect { a.slice!(rand(a.length)) }.inspect

The claim sounds reasonable, but in reality Ruby 1.9 (CVS HEAD) always
returns an array of three random elements, not of five as expected.

Why is that so?

Best regards,
Alexey Verkhovsky
 
M

Mark Sparshatt

Alexey said:
One example in The Ruby Way claims that in the following snippet second
line shuffles the a (i.e., makes an array consisting of the same
elements as a, but in a different order).

a = [1, 2, 3, 4, 5]
puts a.collect { a.slice!(rand(a.length)) }.inspect

The claim sounds reasonable, but in reality Ruby 1.9 (CVS HEAD) always
returns an array of three random elements, not of five as expected.

Why is that so?
This is due to changes in Ruby between v1.6.8 and 1.8

It's because the block passed to collect is changing the contents of a,
so it doesn't reach all the elements of a.

Changing the line to

puts a.dup.collect {a.slice!(rand(a.length)) }.inspect

fixes it

HTH
 
M

Mark Hubbart

One example in The Ruby Way claims that in the following snippet second
line shuffles the a (i.e., makes an array consisting of the same
elements as a, but in a different order).

a = [1, 2, 3, 4, 5]
puts a.collect { a.slice!(rand(a.length)) }.inspect

The claim sounds reasonable, but in reality Ruby 1.9 (CVS HEAD) always
returns an array of three random elements, not of five as expected.

Why is that so?

It appears that #each is being affected by the slicing of the array
that it is acting on. By the time it gets to the third element, it's
the last one. So it stops.

It doesn't seem to do that in 1.6.x; I assume #each worked on a copy of
the array back then...

RUBY_VERSION
==>"1.6.8"
a=[1,2,3,4,5]
==>[1, 2, 3, 4, 5]
p a.collect{a.slice!(rand(a.length))}
[4, 3, 5, 2, 1]
==>nil

To fix it in later versions, use a.dup.collect instead. That should fix
the odd behavior.

HTH,
Mark
 
A

Alexey Verkhovsky

It's because the block passed to collect is changing the contents of a,
so it doesn't reach all the elements of a.

So, Ruby iterators operate on the original collection and let you modify
the collection in the process. Another fact useful to remember.

This is, by the way, one thing I like about Java iterators: they are
fail-safe in such scenarios. E.g.

List list = new ArrayList(2);
list.add(new Object());
list.add(new Object());
Iterator i = list.iterator();
i.next(); // this is OK
list.add(new Object()); // list gets changed
i.next(); // throws ConcurrentModificationException

IMHO, an exception here is The Right Thing (TM). At least, it saved me
from my own stupid mistakes more than once.

Best regards,
Alex
 
M

Mark Hubbart

So, Ruby iterators operate on the original collection and let you
modify
the collection in the process. Another fact useful to remember.

This is, by the way, one thing I like about Java iterators: they are
fail-safe in such scenarios. E.g.

List list = new ArrayList(2);
list.add(new Object());
list.add(new Object());
Iterator i = list.iterator();
i.next(); // this is OK
list.add(new Object()); // list gets changed
i.next(); // throws ConcurrentModificationException

IMHO, an exception here is The Right Thing (TM). At least, it saved me
from my own stupid mistakes more than once.

I'm not sure that an exception would be The Right Thing, but I do
wonder why it no longer works on a copy of the array? Allowing
modification of the actual array as you iterate over it seems
dangerous.

But there must have been a reason for abandoning the original
behavior... Perhaps it was a performance issue?

cheers,
Mark
 
A

Alexey Verkhovsky

I'm not sure that an exception would be The Right Thing
A smilie was inadvertently omitted in my earlier post. It should have
said "The Right Thing (TM) :)"

:)

Life is full of compromises, and on the issue of what to do with stale
iterators, there are at least three choices:

1. Nothing (fast and dangerous)
2. Iterate over a copy (safe and slow)
3. Iterate over the original, but detect stale iterators (the middle
way, much safer than 1 and somewhat faster than 2).

Each of the three choices has pros and cons.

All I'm saying is that in my personal experience Java's
ConcurrentModificationException has prevented or exposed many bugs that
otherwise would be subtle and probably very costly affairs. Which is why
I like it.

But then, the most important difference between Ruby and Java is that
the former lets you do anything, and that includes shooting yourself in
the foot, too. The latter, meantime, does not believe that you REALLY
know what you are doing. :)

Best regards,
Alex
 
G

Gully Foyle

Alexey said:
A smilie was inadvertently omitted in my earlier post. It should have
said "The Right Thing (TM) :)"

:)

Life is full of compromises, and on the issue of what to do with stale
iterators, there are at least three choices:

1. Nothing (fast and dangerous)
2. Iterate over a copy (safe and slow)
3. Iterate over the original, but detect stale iterators (the middle
way, much safer than 1 and somewhat faster than 2).

Each of the three choices has pros and cons.

All I'm saying is that in my personal experience Java's
ConcurrentModificationException has prevented or exposed many bugs that
otherwise would be subtle and probably very costly affairs. Which is why
I like it.

But then, the most important difference between Ruby and Java is that
the former lets you do anything, and that includes shooting yourself in
the foot, too. The latter, meantime, does not believe that you REALLY
know what you are doing. :)

Best regards,
Alex

One alternative solution is to provide for all three options using a
resettable flag. Kinda like $SAFE for security.
 
F

Florian Gross

Alexey said:
One example in The Ruby Way claims that in the following snippet second
line shuffles the a (i.e., makes an array consisting of the same
elements as a, but in a different order).

By the way, if you just want to shuffle an Array you can also use the
much simpler ary.sort_by { rand }. (It will get minimally biased with
containers containing millions of elements, but that can be fixed by
using Array.new(32) { rand } instead of rand.)
Best regards,
Alexey Verkhovsky

More regards,
Florian Gross
 
H

Hal Fulton

Florian said:
By the way, if you just want to shuffle an Array you can also use the
much simpler ary.sort_by { rand }. (It will get minimally biased with
containers containing millions of elements, but that can be fixed by
using Array.new(32) { rand } instead of rand.)

And by the way, the code which modified the array while iterating
was probably ill-advised from the beginning, although it did work
in 1.6.8.


Hal
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top