PHP-like Array/Hash creation in Ruby

J

Jan Reitz

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
 
J

Jules

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"]
 
J

Jan Reitz

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....
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"]

Jan said:
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
 
L

Logan Capaldo

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")
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).
 
A

ara.t.howard

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 ?

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][email protected](*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.

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']

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
 
J

Jan Reitz

Logan said:
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.
 
A

ara.t.howard

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.

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 = []) = 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
 
J

Jan Reitz

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 ?
 
A

ara.t.howard

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 ?

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

the name says it all ;-)

-a
 
L

Logan Capaldo

Logan said:
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.
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 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.
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.
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.
 
J

Jan Reitz

i will take a look at this

Logan said:
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 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".
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.
 
T

Tomasz Wegrzanowski

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 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.
 

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

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top