What is the ruby way to do this?


#1

hello,

i am still learning. in the below code @attributes is a hash table. i
want to add a new value ‘a’ for a key represented by ‘a.key’. i also
want to group values with the same keys in to arrays. thanks for any
help.

konstantin

if @attributes.has_key?(a.key)
if @attributes[a.key].is_a?(Array)
@attributes[a.key] << a
else
@attributes[a.key] = [@attributes[a.key], a]
end
else
@attributes[a.key] = a
end


#2

sorry, forgot to phrase the question. the question is is there a way to
make this code more compact and pretty?


#3

On 11/22/05, ako… removed_email_address@domain.invalid wrote:

if @attributes.has_key?(a.key)

Pardon my question, but isn’t the whole point of hashes that every key
is
unique?


#4

$hash = {}

def add(key, val)
if $hash.has_key?(key)
if $hash[key].is_a?(Array)
$hash[key] << val
else
$hash[key] = [$hash[key], val]
end
else
$hash[key] = val
end
end

def add2(key, val)
if $hash.has_key?(key)
$hash[key].to_a << val
else
$hash[key] = val.to_a
end
end

key = “a”
val = “aval”
add2(key, val)
puts $hash.inspect

key = “a”
val = “aval2”
add2(key, val)
puts $hash.inspect


#5

and this monstrosity, which isn’t in the spirit of ruby… :slight_smile:

def add3(key, val)
$hash[key] = ( $hash[key] == nil ) ? val.to_a : $hash[key] << val
end


#6

yes, this is the point of hashes. in my program, i have attributes with
names (keys). i want to collect these attributes in to a data
structure. attributes with the same names form an array. like in a HTTP
POST form, if you have fields with the same names they are treated as
an array.


#7

On 11/22/05, ako… removed_email_address@domain.invalid wrote:

if @attributes[a.key].is_a?(Array)
@attributes[a.key] << a
else
@attributes[a.key] = [@attributes[a.key], a]
end
else
@attributes[a.key] = a
end

This is a fairly standard Ruby idiom (a hash of arrays):

(@attributes[a.key]||=[])<<a

What the above is doing is setting the entry for a.key to an empty
array if nothing exists, otherwise it will return the array already at
that location. Then the element a is added to that array.

Ryan


#8

And finally I get to

def add4(key, val)
$hash[key]= $hash[key].to_a << val
end

These mean your values are allways arrays, but thats no problem right?


#9

On 11/22/05, ako… removed_email_address@domain.invalid wrote:

yes, this is the point of hashes. in my program, i have attributes with
names (keys). i want to collect these attributes in to a data
structure. attributes with the same names form an array. like in a HTTP
POST form, if you have fields with the same names they are treated as
an array.

In my opinion it is better to just put all the attributes into an
array (which is what my code does), even the single ones, that way you
can treat all the hashtable entries the same way (since they will all
be arrays.)

Ryan


#10

Konstantin,

When you have a single attribute associated with a key, is it
acceptable to have it in a one-element array? If so, you could do
something like:

@attributes = {}

@attributes[a.key] ||= []
@attributes[a.key] << a

Or, if you don’t mind having empty keys return an empty array instead
of nil, you could do:

@attributes = Hash.new([])

@attributes[a.key] << a

Cheers,
Ken


#11

thank you. this assumes that the hash’s default value is nil. but this
is a good enough assumption for my purposes. i was hoping to avoid
creating one-element arrays and still have the simplicity of a single
line solution but i guess this cant be done. thanks for your help.

konstantin


#12

Hi,

From: “ako…” removed_email_address@domain.invalid

@attributes[a.key] = [@attributes[a.key], a]
end
else
@attributes[a.key] = a
end

An alternate approach might be:

@attributes = Hash.new {|h,k| h[k] = Array.new}

This will automatically create arrays to hold your keys, on demand.

Now you can just say:

@attributes[“whatever”] << “something”
@attributes[“foo”] << 123
@attributes[“foo”] << 456

@attributes.inspect
{“foo”=>[123, 456], “whatever”=>[“something”]}

Hope this helps,

Regards,

Bill


#13

thanks. this form of Hash creation with blocks is not documented in
http://www.rubycentral.com/book/ref_c_hash.html. would you point me to
the documentation for this?


#14

Note that a “Ruby” hint would be to get rid of your “is_a?”,
preferring to use “respond_to?” instead.

You could get fancier and just call “<<” on everything, catching
exceptions and handling them for objects that don’t implement that
method.

Caleb


#15

ako… wrote:

thanks. this form of Hash creation with blocks is not documented in
http://www.rubycentral.com/book/ref_c_hash.html. would you point me to
the documentation for this?

See ri Hash.new.

Regards,
Matthew


#16

Personally, I would keep every element of the hash an array. You can do
that
like so…
def add4( key, value )
@hsh[key] = @hsh[key].to_a<<value
end

if you must keep entries in the hash that only have one value as single
elements and not as arrays…
def add5( key, value )
@hsh[key] = @hsh[key]?@hsh[key].to_a<<value:value
end


#17

Quoting Caleb T. removed_email_address@domain.invalid:

You could get fancier and just call “<<” on everything, catching
exceptions and handling them for objects that don’t implement
that method.

It’s better to use respond_to? – if you get a NameError, you can’t
distinguish between the object itself not responding to :"<<" and
something deep in the bowels of its :"<<" implementation not
responding to :"<<".

-mental


#18

sorry, what is ‘ri’?


#19

Ken Kunz wrote:

Careful with this one. When you do this:

Hash.new([])

you set the default valued returned by the hash to an array. The
problem is that it will always return the same array. So in the example
provided above you will end up with an empty hash for @attributes.

What you really want is the default block style:
Hash.new{|hash, key| hash[key] = []}

This will create a new array for each key.

It’s an extremely sneaky little problem.

Regards,
Matthew


#20

Bill K. wrote:

@attributes[“foo”] << 456

@attributes.inspect
{“foo”=>[123, 456], “whatever”=>[“something”]}

And, mixing some of the approaches shown:

@attributes = {}

Assumes the given object quacks on ‘key’

def @attributes.<<( keyed_obj )
(self[keyed_obj.key]||=[])<<keyed_obj
end

@attributes << a1
@attributes << a2
@attributes << a3

James

http://www.ruby-doc.org - Ruby Help & Documentation
http://www.artima.com/rubycs/ - Ruby Code & Style: Writers wanted
http://www.rubystuff.com - The Ruby Store for Ruby Stuff
http://www.jamesbritt.com - Playing with Better Toys
http://www.30secondrule.com - Building Better Tools