Forum: Ruby PHP-like Array/Hash creation in Ruby

644b27decd74d292490db0c5248a77a6?d=identicon&s=25 Jan Reitz (Guest)
on 2006-10-11 13:41
(Received via mailing list)
I used PHP for a long time, a little bit C# and recently started to
learn about Ruby.
I do like Ruby very much from the first looks
It has "my_array.sort" like in c#, not that ugly (imo) "sort($myarray)"
And it has q'n'd qualitys like php for example: definition at first
assignment, no need for interfaces and so on.

But! one thing i really miss from PHP just writing :
  $my_array[4][5] = "moep"; (no initialize before)
this does not work in ruby, i had to
  my_array = Array.new(16) { Array.new 16 }
first.
and if the array wasn't great enough i end up with element 16 (the
17th) complaining:
  NoMethodError: undefined method `[]=' for nil:NilClass
(this only occurs at "my_array[16][5] = 1",
not at "my_array[4][16] = 1")

hm, so i thought, if NilClass has no []= method, why dont i define it,
(i read very often that this is rubys great adv. over most languages
out there, that u can extend even the basic classes), so it becomes an
array in that place... but that included "self = Array.new" which
caused another complaint: "Can't change the value of self"

I read alot of topics about cant change value of self, and i think that
i have a different problem than these discussed before.
i dont want to 1.next! => 2 so please dont tell me that i can't do
that, i know that, and i know its junk
i want just:
 hans = nil (or even omit this line)
 hans[1] = "test"
or
 my_array = Array.new(16) { Array.new 16 }
 my_array[16][1] = 1
not:
 nil[1] = "test"

I hope my English was well enough to be understood.

- Jan Reitz
71f1b6b2c3fd9af2e8c52618fb91caa6?d=identicon&s=25 Jules Jacobs (jules)
on 2006-10-11 14:11
(Received via mailing list)
Could you post some real php code that uses this feature? It isn't a
problem if you code it right. Instead of:

a = nil
a[12] = "something"

You can just do:

a = []
a[12] = "something"

and instead of:

a = nil
a[12][0] = "foo"
a[12][1] = "bar"
a[12][2] = "baz"

You use:

a = []
a[12] = ["foo", "bar", "baz"]
644b27decd74d292490db0c5248a77a6?d=identicon&s=25 Jan Reitz (Guest)
on 2006-10-11 15:21
(Received via mailing list)
single dimension arrays are simple, because i just need that "a = []",
thats true and not much additional work. and they grow for themselves
too even if i initialize them with eg. Array.new(16).
all great.

but the autogrowing is limited to the last dimension.
in a[x][y] the y will grow when i assign something to them, the x too
but it would contain nil instead if "Array", thats because the a[x]
dimension is a "Array of Objects" and not an "Array of Arrays", the
a[x] does not know that autoresizes should spawn new "Array"s in the
newly generated spaces.

i dont have constant size of elements that could be added in just one
constant line, i have data comming in from DB's, files and network.

take 3-dimensional (points in space) data that i read from a file and i
want to store them in a 3 dimensional array.

without autogrowing i have to read the file completely and get the mins
and maxs and have an array created with this dimensions, then i read
the file again (ok i could have stored it, but i have to go over it
again) and put the contents in the array
OR
file.each { |x,y,z,data|
 a[x] = [] if a[x].nil ?
 a[x][y] = [] if a[x][y].nil ?
 a[x][y][z] = data
}
OR
file.each { |x,y,z,data|
 a[x][y][z] = data
}

method 2 might look ok, but why not have it easier with method 3?
the a = [] is a shortcut.
the h = {} too.
why not have another shortcut if its technically possible ?

you could even determine if the key is not a fixnum and create a Hash
instead

in php the Arrays are Hashes too (i dont know if all the Arrays are
Hashes).
we (at my company) sometimes use them like xml, meaning no common size
(how great can x be) and depth ([x][y][z][n...]) among the elements.

machines['VP21']['status'] = 'running'
machines['VP21']['rotation_speed'] = 210
machines['VP21']['workers'] = ['Peter','Paul','Mary']

or better
machines['VP21'][:status] = :running
machines['VP21'][:rotation_speed] = 210
machines['VP21'][:workers] = ['Peter','Paul','Mary']


and no, i dont need this to be a class, that would double/tripple my
work, because 'VP22' has nothing that rotates....
E34b5cae57e0dd170114dba444e37852?d=identicon&s=25 Logan Capaldo (Guest)
on 2006-10-11 16:19
(Received via mailing list)
On Wed, Oct 11, 2006 at 08:40:06PM +0900, Jan Reitz wrote:
>   my_array = Array.new(16) { Array.new 16 }
> first.
> and if the array wasn't great enough i end up with element 16 (the
> 17th) complaining:
>   NoMethodError: undefined method `[]=' for nil:NilClass
> (this only occurs at "my_array[16][5] = 1",
> not at "my_array[4][16] = 1")
>
Yes, because since you can define [] for anything, ruby has no way of
"guessing" what kind of container you want like PHP does.
> hm, so i thought, if NilClass has no []= method, why dont i define it,
> (i read very often that this is rubys great adv. over most languages
> out there, that u can extend even the basic classes), so it becomes an
> array in that place... but that included "self = Array.new" which
> caused another complaint: "Can't change the value of self"
defining []= for nil is probably not a good idea. What I would suggest
you do is create your own container that has the semantics you want, if
you want to go down the route of writing additional code.
>
> I read alot of topics about cant change value of self, and i think that
> i have a different problem than these discussed before.
> i dont want to 1.next! => 2 so please dont tell me that i can't do
> that, i know that, and i know its junk
> i want just:
>  hans = nil (or even omit this line)
>  hans[1] = "test"
Like I said previously, since anything can have a [] method, ruby can't
determine that you wanted an array.
> or
>  my_array = Array.new(16) { Array.new 16 }
>  my_array[16][1] = 1
> not:
>  nil[1] = "test"
>
> I hope my English was well enough to be understood.
Try this:

a = Hash.new { |hash, key| hash[key] = [] }
a[16][1] = "Whee"

or like I said, create your own container. (Creating your own may also
give you a better understanding of why the choices made in ruby are what
they are).
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-10-11 17:06
(Received via mailing list)
On Wed, 11 Oct 2006, Jan Reitz wrote:

> OR
> file.each { |x,y,z,data|
> a[x][y][z] = data
> }
>
> method 2 might look ok, but why not have it easier with method 3?
> the a = [] is a shortcut.
> the h = {} too.
> why not have another shortcut if its technically possible ?

because it doesn't eliminate the problem - if you read in arbitrary
dimensions
into some auto-array the very next thing you'll have to do is figure out
those
dimensions so you can use the thing - and dynamically creating array
indicies
is not straightforward.  you see, having the array size itself doesn't
prevent
you from needing to know the shape of it.  narray solves this to some
degree,
but you still need some info and introspection.  consider you example
above -
how would you know to use __three__ indicies (x,y,z)?  how would you
know the
shape of the array after reading?  how would you know if it were sparse
or
complete?  autogrowing solves non of this.

in any case, it's easy to auto-grow arrays:


     harp:~ > cat a.rb
     class AutoArray < ::Array
       def initialize(&auto)
         @auto = auto
         super()
       end
       def [](*k) super(*k) or self[*k]=@auto.call(*k) end
     end
     def aa(*a, &b) AutoArray.new(*a, &b) end

     a = aa{|i| 2 ** i}
     p a[0]
     p a[1]
     p a[2]

     a = aa{ aa{|i| 2 ** i} }
     p a[0][0]
     p a[0][1]
     p a[0][2]


     harp:~ > ruby a.rb
     1
     2
     4
     1
     2
     4


note that i think this is an astoudingly bad idea, and i don't think
there is a
useful code example, in php even, which could use such a feature and
then not
immediately turn around and determine the shape of this auto array in
order to
index it...

> you could even determine if the key is not a fixnum and create a Hash
> instead

but fixnums are valid hash keys?  and quite useful for representing
sparse arrays...

this would be quite a bug

    hash[2**128] = 42  # ok
    array[2**128] = 42  # out of memory!

this sort of thing is ok for web programming - but not general purpose
coding.

> in php the Arrays are Hashes too (i dont know if all the Arrays are
> Hashes).

   harp: ~> gem install arrayfields


   harp: ~> cat a.rb
   require 'arrayfields'
   a = [0,1,2]
   a.fields = %w( zero one two )
   p a['two']


   harp: ~> ruby a.rb
   2


it does much more than than too and has seen heavy production use.


> machines['VP21'][:workers] = ['Peter','Paul','Mary']
harp:~ > cat a.rb
     require 'alib'

     machines = Alib::OrderedAutoHash.new

     machines['VP21']['status'] = 'running'
     machines['VP21']['rotation_speed'] = 210
     machines['VP21']['workers'] = ['Peter','Paul','Mary']

     y machines



     harp:~ > ruby a.rb
     VP21:
       status: running
       rotation_speed: 210
       workers:
       - Peter
       - Paul
       - Mary



> and no, i dont need this to be a class, that would double/tripple my
> work, because 'VP22' has nothing that rotates....

i wouldn't rule out classes though.  i've probably debugged about 10,000
perl
scripts which rekoned a hash would be fine... errors go un-noticed with
such
designs..  that said, they are sometimes quite useful.

regards.

-a
644b27decd74d292490db0c5248a77a6?d=identicon&s=25 Jan Reitz (Guest)
on 2006-10-11 17:22
(Received via mailing list)
Logan Capaldo wrote:
> Yes, because since you can define [] for anything, ruby has no way of
> "guessing" what kind of container you want like PHP does.

i know that not only an array can have [], strings can have them too in
php, but for ease of use someone defined that if you use [] on an
undefinded variable it would become an array not a string, and since
noone uses []= on NilClass atm it could be used, to do just that in
ruby.
i don't ask "why not?", because most ruby users are not used to it and
wouldnt want it, but i do. i started to dislike php more and more, but
not because i can write $ary[1] = 'hans'; but because phps oo sucks
compared to ruby, the part with the $ary[1] = 'hans'; is better there.

you use the "a = []" shortcut too, its a shortcut by definition, it
says "make an Array", not a string, or something else that supports [].
just Array.new

> defining []= for nil is probably not a good idea. What I would suggest
> you do is create your own container that has the semantics you want, if
> you want to go down the route of writing additional code.

i just want an array, nothing more
ary = nil
ary[1] = 'moep'
ary.class
=> Array

OR

ary = Array.new(16) { Array.new 16 }
ary[99][99] = 'moep'
ary[99].class
=> Array

with a little typechecking on the parameter inside the brackets you
could even:

moep[:test_symbol] = "hans"
moep.class
=> Hash

moep2["string as key"] = "hans"
moep2.class
=> Hash

my problem is that if i have an multi dimensional array, and it needs
to grow (by itself if i assign values outside of the allocated space),
the newly created will be "nil" so i cant use my own class for that.

in c# 2.0 i would write
List<List<string>> mda = new List<List<string>>()
mda[99][99] = "test";

Tada, tested it and it does _not_ work, lol.....

BUT what hinders ruby to be better than that....

the question i ask is is it doable ?
can someone help solving ?
im stuck with this "cant reassign self" or whatever it said.
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-10-11 18:12
(Received via mailing list)
On Thu, 12 Oct 2006, Jan Reitz wrote:

> BUT what hinders ruby to be better than that....
>
> the question i ask is is it doable ?  can someone help solving ?  im stuck
> with this "cant reassign self" or whatever it said.

harp:~ > cat a.rb
class MArray < Array
   class DimProxy
     def initialize(d, i) @d, @i = d, i end
     def inspect () @d.inspect << " [[ #{ @i } ]]" end
     def dp(i) DimProxy.new self, i end
     def [](i) dp i end
     def []= i, v
       (a = [])[i] = v
       @d[@i] = a
       v
     end
   end

   def [](i) super or dp i end
   def dp(i) DimProxy.new self, i end
end

ma = MArray.new

ma[0][1][2] = 42

puts "ma[0][1][2] : #{ ma[0][1][2].inspect }"
puts "ma[0][1]    : #{ ma[0][1].inspect }"
puts "ma[0]       : #{ ma[0].inspect }"
puts "ma          : #{ ma.inspect }"


harp:~ > ruby a.rb
ma[0][1][2] : 42
ma[0][1]    : [nil, nil, 42]
ma[0]       : [nil, [nil, nil, 42]]
ma          : [[nil, [nil, nil, 42]]]


-a
644b27decd74d292490db0c5248a77a6?d=identicon&s=25 Jan Reitz (Guest)
on 2006-10-11 19:11
(Received via mailing list)
thanks for your insights response ara.
i did my last post before reading your first...

the Alib::OrderedAutoHash looks very nice and is one of the things that
i searched.

i think i allways used sparse arrays|hashes before, with some little
exceptions while programming in C#....

your example with 2**128 showed me that php uses sparse arrays/hashes
because when i was using something like $serial_number[7099144]='blah'
i would have instantly beaten the allowed ram limit.

i think i became a little savaged while using php (hope this is proper
english)
maybe i can still be saved, i'm programming a board game (carcasonne)
in gtk to learn ruby.

back on track about auto-re-initialization of nil (or empty variable is
better, i think) to Hash (forget about Array) i found that the nils all
have the same id, so by changing one of them to an Array i would turn
them all.
i dont want something like this powerfull "become" from smalltalk some
other ppl talked about, it can (infact must) get a new id, and does not
need to retain its interior (whats in nil that i wanted to retain)

anyone having an idea of how to do this ?
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-10-11 21:00
(Received via mailing list)
On Thu, 12 Oct 2006, Jan Reitz wrote:

> because when i was using something like $serial_number[7099144]='blah'
> them all.
> i dont want something like this powerfull "become" from smalltalk some
> other ppl talked about, it can (infact must) get a new id, and does not
> need to retain its interior (whats in nil that i wanted to retain)
>
> anyone having an idea of how to do this ?

http://rubyforge.org/projects/evil/

the name says it all ;-)

-a
E34b5cae57e0dd170114dba444e37852?d=identicon&s=25 Logan Capaldo (Guest)
on 2006-10-12 03:47
(Received via mailing list)
On Thu, Oct 12, 2006 at 12:20:10AM +0900, Jan Reitz wrote:
> i don't ask "why not?", because most ruby users are not used to it and
> wouldnt want it, but i do. i started to dislike php more and more, but
> not because i can write $ary[1] = 'hans'; but because phps oo sucks
> compared to ruby, the part with the $ary[1] = 'hans'; is better there.
>
In PHP $ary[1] = 'hans' must be an array. I can't create something else
that supports the same syntax.

In Ruby, ary[1] = 'hans' could be an array, could be a hash, could be a
string, could be any user created class. It could also mask errors,
Like for instance:
b = 7
b[3] = 2
Should 'b' now magically be turned into [nil, nil, nil, 2] ? (I don't
know, I don't think so, but maybe it should).

When asking for magic, you must always consider the impact it will have
on the rest of the language and the rest of the programs out there.
> you use the "a = []" shortcut too, its a shortcut by definition, it
> says "make an Array", not a string, or something else that supports [].
> just Array.new
>
Ahh, but [] is different from []. ;) Rather, when I say a = [] it has
nothing to do with a[i] but rather it's writing an array literal (like
PHP's array()).
[1,2,3] would be the same as array(1,2,3) in PHP
[1,2] would be the same as array(1,2) in PHP
[1] would be the same as array(1) in PHP
and [] is the same as array() in PHP.
It's just an (un?)happy coincidence that it looks the same as the index
operator.
> OR
> moep.class
> => Hash
>
> moep2["string as key"] = "hans"
> moep2.class
> => Hash
>
> my problem is that if i have an multi dimensional array, and it needs
> to grow (by itself if i assign values outside of the allocated space),
> the newly created will be "nil" so i cant use my own class for that.
>
It doesn't have to be, that's why you write your own class.
> in c# 2.0 i would write
> List<List<string>> mda = new List<List<string>>()
> mda[99][99] = "test";
>
> Tada, tested it and it does _not_ work, lol.....
>
> BUT what hinders ruby to be better than that....
>
Better is a question of opinion in this case.
644b27decd74d292490db0c5248a77a6?d=identicon&s=25 Jan Reitz (Guest)
on 2006-10-12 12:07
(Received via mailing list)
i will take a look at this

Logan Capaldo wrote:
> In PHP $ary[1] = 'hans' must be an array. I can't create something else
> that supports the same syntax.
>
> In Ruby, ary[1] = 'hans' could be an array, could be a hash, could be a
> string, could be any user created class. It could also mask errors,
in ruby, ary[1] = 'hans' does not do a thing, it only complains that
nil has no []= method defined.
in php, where someone has defined that using []= on a undefined
variable would result in the creation of an (sparse) array, he could
have defined that it would result in creation of a string, but he
didnt.

> Like for instance:
> b = 7
> b[3] = 2
> Should 'b' now magically be turned into [nil, nil, nil, 2] ? (I don't
> know, I don't think so, but maybe it should).

no, that would mean that i overloaded the []= method in Fixnum, but im
only talking about NilClass...
the [] at Fixnum shows you the value of the bit at this position, maybe
it makes sense to have a []= as a bit setter/unsetter, but its not in
there, i dont mind, i dont care, i dont want to define it.

> When asking for magic, you must always consider the impact it will have
> on the rest of the language and the rest of the programs out there.

like said before im not overloading it for anything but nil, and
everybody that uses []= on nil now gets errors, and most likely
"repairs" his code.

> Ahh, but [] is different from []. ;) Rather, when I say a = [] it has
> nothing to do with a[i] but rather it's writing an array literal (like
> PHP's array()).
> [1,2,3] would be the same as array(1,2,3) in PHP
> [1,2] would be the same as array(1,2) in PHP
> [1] would be the same as array(1) in PHP
> and [] is the same as array() in PHP.
> It's just an (un?)happy coincidence that it looks the same as the index
> operator.

i know its different, and in my opinion its a happy "coincidence", if
it was coincidence at all, using "ยงยง" for example would not be so
inuitive for "shortcut array creation".

> > my problem is that if i have an multi dimensional array, and it needs
> > to grow (by itself if i assign values outside of the allocated space),
> > the newly created will be "nil" so i cant use my own class for that.
> >
> It doesn't have to be, that's why you write your own class.

i already see the light, in php i dont think that something like this
OrderedAutoHash would be possible to implement by the "user" because
the [] is a language construct there and a method call here.
98e5e1f8fb2da8322a032e3ff2d7e668?d=identicon&s=25 Tomasz Wegrzanowski (Guest)
on 2006-10-12 14:55
(Received via mailing list)
On 10/11/06, Jan Reitz <jan@sordid.de> wrote:
> i want just:
>  hans = nil (or even omit this line)
>  hans[1] = "test"
> or
>  my_array = Array.new(16) { Array.new 16 }
>  my_array[16][1] = 1
> not:
>  nil[1] = "test"

That's one of the things where Ruby is less convenient than PHP/Perl.

Variables in PHP and Perl are "smart" and you can
make references to variables and pass them as function arguments.
So when you say:
$x[1] = "test"
PHP/Perl do something like STORE(variable $x, 1, "test")

Ruby variables are very simple and cannot be passed around.
When you say:
x[1] = "test"

Ruby does something like (value of x).send(:[]=, 1, "test")

So no matter what is implementation of []=, it doesn't have
any way of knowing where does self come from.
self= or become (from Evil Ruby) would have to change all
nils in whole program to [], and that would be bad ;-)

It goes on. If you say:
x = []
x[1][2] = "test"

Ruby does:
(  x.send(:[], 1)   ).send([]=, 2, "test")
that is:
nil.send([]=, 2, "test")

and []=, no matter what it does, cannot change x[2] because it doesn't
have any way of knowing where does self come from.

So you have to do two things:
* initialize the container variable to some "magic" container
* x[i] must return "magic" container if it doesn't have know i

For technical reasons it's better to use hashes than arrays for this.
(PHP "arrays" are actually hashes too).

Hash constructor accepts a block argument for default values,
and we want to use it.

If you want a single level of magic, you can say:
a = Hash.new{|ht,k| ht[k] = {} }
a[1][2] = "test"
p a # => {1=>{2=>"test"}}
p a[1] # => {2=>"test"}
p a[1][2] # => "test"

If you want two levels you can do:
a = Hash.new{|ht,k| ht[k] = Hash.new{|ht,k| ht[k] = {} } }

For arbitrary number of levels use:

class Hash
    def self.new_magic
        Hash.new{|ht,k| ht[k] = Hash.new_magic }
    end
end

a = Hash.new_magic
a[1][2][3] = "test"
p a # => {1=>{2=>{3=>"test"}}}

If you know how many levels you want, it's better to use
level-specific version.
In magic hashes in Ruby you cannot easily test for presence of keys:

if a[2][3][4] # Magically creates a[2], a[2][3], and a[2][3][4] anyway
    ...# Always runs
end

Perl/PHP would only create a, a[2] and a[2][3] by magic, so the test
would still work.

Key presence test must do something like extremely ugly:
if a[2][3].include? 4 # Works but it's ugly. It also magically creates
a[2], a[2][3] (like PHP/Perl)
    ...
end

Changing Ruby to support PHP/Perl-style magic arrays would
require really huge changes to it, so I doubt it will ever happen.

But we can limit magic creation to cases when it's necessary.
The implementation most similar to PHP/Perl-style magic is here:

class HashRef
    def initialize(ht,k)
        @ht = ht
        @k = k
        @obj = nil
    end
    def [](k)
        if @obj
            @obj[k]
        else
            HashRef.new(self, k)
        end
    end
    def []=(k,v)
        unless @obj
            @obj = Hash.new_magic
            @ht[@k] = @obj
        end
        @obj[k] = v
    end
    # This is a really ugly hack
    def nil?
        @obj.nil?
    end
end

class Hash
    def self.new_magic
        Hash.new{|ht,k| HashRef.new(ht,k) }
    end
end

a = Hash.new_magic
a[1][2][3] = "test"
p a # => {1=>{2=>{3=>"test"}}}

puts ":-(" if a[2][3][4] # You cannot test by if foo[...]
puts ":-)" unless a[3][4][5].nil? # But you can test by unless
foo[...].nil?
# Tests did not change a:
p a # => {1=>{2=>{3=>"test"}}}

This is pretty much as far as one can go.

Another option is using multi-element keys instead of nested containers:
a = {}
a[[1, 2, 3]] = "test" # Array [1, 2, 3] is a key
p a # => {[1, 2, 3]=>"test"}

It is not exactly the same thing, and is probably a bit slower,
but it's usually the most convenient Ruby-way solution.
7e190a8529e75912abb8474118927f1f?d=identicon&s=25 Brian Katzung (bkatzung)
on 2014-04-01 04:38
XKeys::Hash (gem xkeys) will traverse and create nested hashes.

XKeys::Auto will traverse and create nested arrays for integer keys and
hashes for everything else.

In the latest release, XKeys can also create nodes of your choosing if
your root node responds to :xkeys_new (like a default_proc, but only for
node traversal).

New nodes are only created on set, not get, so you won't have a bunch of
spurious nodes hanging around "just for looking".

If you like hashes that support more of the array-like behaviors, you
might consider Sarah (gem sarah). It's a sequential array, a sparse
array, and a hash all in one. It's an extension of Ruby's own mixed
array/hash metaphor of passing keyed values in a hash at the end of an
array. There are probably better options for most use cases, but it's
there if you want it.
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.