Forum: Ruby Unexpected behavior with multidimensional arrays

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
655d6e0d3d21759e6894111c61b3b5e8?d=identicon&s=25 Armin Armbruster (swabianeagle)
on 2007-07-31 17:14
Hi,

I need a 3-dim array. The following attempt shows some unexpected
results:

irb(main):001:0> a = Array.new(3)
=> [nil, nil, nil]
irb(main):002:0> a.map!{Array.new(3,[])}
=> [[[], [], []], [[], [], []], [[], [], []]]
irb(main):003:0> a[1][1] << 3.0
=> [3.0]
irb(main):004:0> a
=> [[[], [], []], [[3.0], [3.0], [3.0]], [[], [], []]]

(my expectation would be:
[[[], [], []], [[], [3.0], []], [[], [], []]]
)


After some searching in the forum I found another method:
irb(main):007:0* b = Array.new(3) {Array.new(3,[])}
=> [[[], [], []], [[], [], []], [[], [], []]]
irb(main):008:0> b[1][1] << 3.0
=> [3.0]
irb(main):009:0> b
=> [[[], [], []], [[3.0], [3.0], [3.0]], [[], [], []]]

again, not the expected behavior.

However:
irb(main):016:0> c = Array.new(3) {Array.new(3) {[]}}
=> [[[], [], []], [[], [], []], [[], [], []]]
irb(main):017:0> c[1][1] << 3.0
=> [3.0]
irb(main):018:0> c
=> [[[], [], []], [[], [3.0], []], [[], [], []]]

gives me what I want.

Could anybody please explain to me why my expectations are wrong in the
first two cases?

Thanks,
Armin
4d5b5dd4e263d780a5dfe7ac8b8ac98c?d=identicon&s=25 Tim Pease (Guest)
on 2007-07-31 19:51
(Received via mailing list)
On 7/31/07, Armin Armbruster <aarmbruster@ndigital.com> wrote:
> Hi,
>
> I need a 3-dim array. The following attempt shows some unexpected
> results:
>
> Could anybody please explain to me why my expectations are wrong in the
> first two cases?
>

This is a very good question -- a classic in the ruby-talk archives.

If you do a search through the ruby-talk archives, you will find some
very enlightening answers. Please post back the thread number that was
most informative for you (just to help others who will ask this same
question again in the future).

Also, read the documentation for Array.new until you grok what it is
saying. Meditate until enlightenment is achieved.

Blessings,
TwP
655d6e0d3d21759e6894111c61b3b5e8?d=identicon&s=25 Armin Armbruster (swabianeagle)
on 2007-07-31 20:22
Tim Pease wrote:
> This is a very good question -- a classic in the ruby-talk archives.
>
> If you do a search through the ruby-talk archives, you will find some
> very enlightening answers. Please post back the thread number that was
> most informative for you (just to help others who will ask this same
> question again in the future).
>
> Also, read the documentation for Array.new until you grok what it is
> saying. Meditate until enlightenment is achieved.
>
> Blessings,
> TwP

Thanks Tim,

I found (among others)
http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/rub...

Also, I read the documentation for Array.new again. In fact, I was
reading it before posting, but I guess the human brain (or at least
mine) is pre-conditioned to read what it "believes".

Frankly, just as the poster in ruby-talk I have my troubles accepting
this not being a violation of the principle of least surprise (and yes,
I come from a C programming background).
Anyhow, now I know it and (hopefully) will remember it the next time. I
still have not glue why one would want an array whose elements are
referencing the same object. Does anybody know an example where this is
actually useful?

Armin
Ad7805c9fcc1f13efc6ed11251a6c4d2?d=identicon&s=25 Alex Young (regularfry)
on 2007-07-31 20:36
(Received via mailing list)
Armin Armbruster wrote:
<snip>
> Anyhow, now I know it and (hopefully) will remember it the next time. I
> still have not glue why one would want an array whose elements are
> referencing the same object. Does anybody know an example where this is
> actually useful?
If it's the object is an immediate (like a Fixnum, or a symbol) you
don't have this problem.
4d5b5dd4e263d780a5dfe7ac8b8ac98c?d=identicon&s=25 Tim Pease (Guest)
on 2007-07-31 20:41
(Received via mailing list)
On 7/31/07, Armin Armbruster <aarmbruster@ndigital.com> wrote:
>
> Frankly, just as the poster in ruby-talk I have my troubles accepting
> this not being a violation of the principle of least surprise (and yes,
> I come from a C programming background).
> Anyhow, now I know it and (hopefully) will remember it the next time. I
> still have not glue why one would want an array whose elements are
> referencing the same object. Does anybody know an example where this is
> actually useful?
>

In ruby, constructing an array and adding the same object multiple
times is the least surprising behavior. Creating several copies of the
object would be "hidden behavior" inside the array initializer, and
hidden behavior is usually a bad thing.

The hidden behavior would force all objects to have valid dup/clone
methods (which is not the case for Fixnum).

As an example consider creating an array of classes using some default
class. The problem here is that we now have several clones of the
default class that are independent of the original. Adding new methods
to the default class is not reflected in the clones.

The current implementation of Array allows the user to choose how
objects are populated in the array. The user can explicitly use the
same object, or the user can create duplicates or new objects per
index using the block format of the initializer. Each form is explicit
which is why the implementation adheres the principle of least
surprise.

Blessings,
TwP
Caf38c89d40443a858741b61ac6d82de?d=identicon&s=25 Dan Zwell (Guest)
on 2007-07-31 21:15
(Received via mailing list)
Tim Pease wrote:
> In ruby, constructing an array and adding the same object multiple
> times is the least surprising behavior. Creating several copies of the
> object would be "hidden behavior" inside the array initializer, and
> hidden behavior is usually a bad thing.
>

I agree with you on this point. However, I can't think of a situation
where the second argument is useful. For example,
   ary = Array.new(n, 0)
can be replaced with
   ary = Array.new(n) {0}

So I ask, is this functionality (creating an array with 2 arguments)
useful to people? Because I think everybody makes this mistake once, and
that certainly seems to qualify as being counter-intuitive.

In fact, anything that in done with Array.new(n, obj) can be done with
Array.new(n) {obj}. Am I mistaken? See the following:
 >> inner = []
=> []
 >> ary = Array.new(3) {inner}
=> [[], [], []]
 >> inner << "hi"
=> ["hi"]
 >> ary
=> [["hi"], ["hi"], ["hi"]]

The above does the same thing as ary = Array.new(3, []), but needs one
more line of code.

What does everyone think?

Dan
655d6e0d3d21759e6894111c61b3b5e8?d=identicon&s=25 Armin Armbruster (swabianeagle)
on 2007-07-31 21:43
Dan Zwell wrote:
>
> The above does the same thing as ary = Array.new(3, []), but needs one
> more line of code.
>
> What does everyone think?
>
> Dan

Hmm,

I guess my brain is still too much C-wired, but as Dan mentioned so are
the brains of many others. Looks like a pretty frequent mistake to me.
I can't think of a real world example where the current implementation
of Array.new(3,[]) is usefull, but then I don't use arrays of Classes.
It's like in C having an array of pointers that all point to the same
address.

To use Dan's example, if you continue with

irb(main):005:0> ary[1] << "by"
=> ["hi", "by"]
irb(main):006:0> ary
=> [["hi", "by"], ["hi", "by"], ["hi", "by"]]

but obviously that's not what I intended to do (and we already discussed
the solution how to do it right).

Again, if anybody can come up with a simple and halfway applied example
in which the current behavior is useful I would appreciate the
enlightenment very much.

Thanks,
Armin
Caf38c89d40443a858741b61ac6d82de?d=identicon&s=25 Dan Zwell (Guest)
on 2007-08-01 02:24
(Received via mailing list)
What would you guys think about removing the second parameter from
Array.new ? I suggest this for 2 reasons:

If the object is a pass-by-copy class such as
   Array.new(n, true), or Array.new(n, 23)
then this syntax is exactly the same as
   Array.new(n) {true}, or Array.new(n) {23}

If the object is a pass-by-reference, such as
   Array.new(n, Klass.new)
this can be represented by
   k=Klass.new
   Array(n) {k}
This syntax is less pretty, but does anybody even Array.new this way?
It's a sort of nice piece of syntactic sugar, but I don't see how it's
actually useful. Please correct me if I'm wrong about this.

If all of the above is true, I would suggest removing the second
parameter from this method, because it confuses plenty of newcomers.

Thanks,
Dan
4d5b5dd4e263d780a5dfe7ac8b8ac98c?d=identicon&s=25 Tim Pease (Guest)
on 2007-08-01 16:09
(Received via mailing list)
On 7/31/07, Dan Zwell <dzwell@gmail.com> wrote:
>
> If all of the above is true, I would suggest removing the second
> parameter from this method, because it confuses plenty of newcomers.
>

Submit an RCR  => http://rcrchive.net/

TwP
5dbb7a37b65f9e4ed415a97ab39300e5?d=identicon&s=25 Kaldrenon (Guest)
on 2007-08-01 16:50
(Received via mailing list)
On Jul 31, 8:23 pm, Dan Zwell <dzw...@gmail.com> wrote:
> If all of the above is true, I would suggest removing the second
> parameter from this method, because it confuses plenty of newcomers.

Anecdotal evidence doesn't exactly merit a real rebuttal, but I found
the optional second parameter very intuitive and helpful. In Java I
hated having to write a for loop every time I wanted to initialize the
elements in an array, even when I wanted them to all have the same
contents, and I really like that I can just use one argument in Ruby.

So I agree with you that it's not necessary, but I say don't get rid
of something that's good just because there are other ways to do it.
If all methods are equally valid and easy to understand (as I consider
them to be in this case) then leaving in variety makes it more fun.

-$0.02
Regards,
Andrew
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 unknown (Guest)
on 2007-08-01 17:11
(Received via mailing list)
Hi --

On Wed, 1 Aug 2007, Dan Zwell wrote:

> this can be represented by
>  k=Klass.new
>  Array(n) {k}
> This syntax is less pretty, but does anybody even Array.new this way? It's a
> sort of nice piece of syntactic sugar, but I don't see how it's actually
> useful. Please correct me if I'm wrong about this.
>
> If all of the above is true, I would suggest removing the second parameter
> from this method, because it confuses plenty of newcomers.

I love not confusing newcomers, but at the same time I don't think
that something orthogonal and purposeful should be removed from the
language just for that reason.  In the end, the main target audience
for Ruby is non-newcomers.

Think of it as a rite of passage :-)  Sort of like realizing that:

   array.each {|a| puts a }

can be written as:

   puts array

or that ^ and $ operate per line rather than per string in regular
expressions.


David
655d6e0d3d21759e6894111c61b3b5e8?d=identicon&s=25 Armin Armbruster (swabianeagle)
on 2007-08-01 17:52
unknown wrote:
> Hi --
>
>
> Think of it as a rite of passage :-)  Sort of like realizing that:
>
>    array.each {|a| puts a }
>
> can be written as:
>
>    puts array
>
> or that ^ and $ operate per line rather than per string in regular
> expressions.
>
>
> David

I have no problem with Array.new(size, obj), in fact I think it is a
syntactically nice form. What I have a problem with is that it behaves
differently, depending what the object is.

> a = Array.new(3,0)
=> [0, 0, 0]
> a[1] = 1
=> 1
> a
=> [0, 1, 0]

> b = Array.new(3,[])
=> [[], [], []]
> b[1] << 1
=> [1]
> b
=> [[1], [1], [1]]

> c = [[],[],[]]
=> [[], [], []]
> c[1] << 1
=> [1]
> c
=> [[], [1], []]


Why can't b behave like a in this case ( Tim I know you tried to explain
it to me, but I just can't wrap my mind around it where the current
behavior of b is actually useful). If anybody can show me a real-world
example I will be quiet forever (on this subject). Promised :)
In my opinion b should behave like c.

Regards,
Armin
Caf38c89d40443a858741b61ac6d82de?d=identicon&s=25 Dan Zwell (Guest)
on 2007-08-02 04:47
(Received via mailing list)
Armin Armbruster wrote:
> I have no problem with Array.new(size, obj), in fact I think it is a
> syntactically nice form. What I have a problem with is that it behaves
> differently, depending what the object is.
>

You'll find the same behavior in every type of function, and you'll get
used to it. Numbers, booleans, and at least one other class that I don't
remember at the moment are passed this way. I'm not sure about
subclasses of the aforementioned, but I think they are still passed by
value (i.e., all Numeric classes are so passed). If you need to pass a
different class by value, just pass my_variable.dup instead.

Dan
3f38541776a2513a20ea2f1c2d522cb3?d=identicon&s=25 Perry Smith (pedz)
on 2007-08-02 06:43
Armin Armbruster wrote:
> unknown wrote:
>> Hi --
>>
>>
>> Think of it as a rite of passage :-)  Sort of like realizing that:
>>
>>    array.each {|a| puts a }
>>
>> can be written as:
>>
>>    puts array
>>
>> or that ^ and $ operate per line rather than per string in regular
>> expressions.
>>
>>
>> David
>
> I have no problem with Array.new(size, obj), in fact I think it is a
> syntactically nice form. What I have a problem with is that it behaves
> differently, depending what the object is.
>
>> a = Array.new(3,0)
> => [0, 0, 0]
>> a[1] = 1
> => 1
>> a
> => [0, 1, 0]
>
>> b = Array.new(3,[])
> => [[], [], []]
>> b[1] << 1
> => [1]
>> b
> => [[1], [1], [1]]
>
>> c = [[],[],[]]
> => [[], [], []]
>> c[1] << 1
> => [1]
>> c
> => [[], [1], []]
>
>
> Why can't b behave like a in this case

This seems really obvious to me and it must seem really non-obvious to
you.  Interesting (I think).

The second form creates an array.  Each element in that array is a
pointer (this is how I think of it at least) to the same array -- lets
call it b.  You put something into b, and you will see it from each of
the pointers to b.

In the first example, if you could put a tag on the 1 (somehow) or, like
in lisp, if you could put a property on it.  Then it too would show up
in all three views of that object.

Oh, how about this:

In the b example, instead of:

b[1] << 1

(which adds an element to b), you need to replace the second element:
irb(main):003:0> b = Array.new(3, [])
=> [[], [], []]
irb(main):004:0> b[1] = [ 1 ]
=> [1]
irb(main):005:0> b
=> [[], [1], []]
irb(main):006:0>

Does any of that help?
Caf38c89d40443a858741b61ac6d82de?d=identicon&s=25 Dan Zwell (Guest)
on 2007-08-02 08:13
(Received via mailing list)
dblack@rubypal.com wrote:
>
>   puts array
>
> or that ^ and $ operate per line rather than per string in regular
> expressions.
>
>
> David
>

I do like more than one way to do things, but the confusion created by
this particular piece of syntactic sugar trumps TIMTOWTDI, in my book.
However, I'm gonna drop this (and not make an RCR), because I realized
that the performance hit due to running a block is significant--one of
the following eats up my CPU and memory, the other doesn't:

$ time ruby -e 'Array.new(9999999) {0}'
$ time ruby -e 'Array.new(9999999, 0)'

We all know that the point of Ruby is to save programmer time, as
opposed to processor time, but I care a lot about performance (I've
written a couple really heavy programs), so I withdraw my suggestion.

Dan
This topic is locked and can not be replied to.