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.
···
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"
--
Tomasz Wegrzanowski [ http://t-a-w.blogspot.com/ ]