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.
Armin A. (Guest)
on 2007-07-31 19: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
Tim P. (Guest)
on 2007-07-31 21:51
(Received via mailing list)
On 7/31/07, Armin A. <removed_email_address@domain.invalid> 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
Armin A. (Guest)
on 2007-07-31 22:22
Tim P. 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
Alex Y. (Guest)
on 2007-07-31 22:36
(Received via mailing list)
Armin A. 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.
Tim P. (Guest)
on 2007-07-31 22:41
(Received via mailing list)
On 7/31/07, Armin A. <removed_email_address@domain.invalid> 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
Dan Z. (Guest)
on 2007-07-31 23:15
(Received via mailing list)
Tim P. 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
Armin A. (Guest)
on 2007-07-31 23:43
Dan Z. 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
Dan Z. (Guest)
on 2007-08-01 04: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
Tim P. (Guest)
on 2007-08-01 18:09
(Received via mailing list)
On 7/31/07, Dan Z. <removed_email_address@domain.invalid> 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
Kaldrenon (Guest)
on 2007-08-01 18:50
(Received via mailing list)
On Jul 31, 8:23 pm, Dan Z. <removed_email_address@domain.invalid> 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
unknown (Guest)
on 2007-08-01 19:11
(Received via mailing list)
Hi --

On Wed, 1 Aug 2007, Dan Z. 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
Armin A. (Guest)
on 2007-08-01 19: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
Dan Z. (Guest)
on 2007-08-02 06:47
(Received via mailing list)
Armin A. 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
Perry S. (Guest)
on 2007-08-02 08:43
Armin A. 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?
Dan Z. (Guest)
on 2007-08-02 10:13
(Received via mailing list)
removed_email_address@domain.invalid 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.