||= [] idiom

L

Leslie Viljoen

I often make use of this idiom to add something to an array in a hash of arrays:

@categories = {}

pagelist.each do |resource|
@categories[resource.tag] ||= []
@categories[resource.tag] << resource
end


.. is there a better way? Can the two @categories lines
be made into one?


Les
 
S

Sebastian Hungerecker

Leslie said:
@categories =3D {}

pagelist.each do |resource|
=A0 =A0 @categories[resource.tag] ||=3D []
=A0 =A0 @categories[resource.tag] << resource
end


.. is there a better way?

@categories =3D Hash.new {Array.new}
pagelist.each do |resource|
@categories[resource.tag] << resource
end



=2D-=20
Jabber: (e-mail address removed)
ICQ: 205544826
 
M

MenTaLguY

I often make use of this idiom to add something to an array in a hash of
arrays:

@categories = {}

pagelist.each do |resource|
@categories[resource.tag] ||= []
@categories[resource.tag] << resource
end

.. is there a better way? Can the two @categories lines
be made into one?

I sometimes do this sort of thing:

( @categories[resource.tag] ||= [] ) << resource

Although this might possibly be more readable:

category = ( @categories[resource.tag] ||= [] )
category << resource

Often it can make sense to factor the ||= portion into a
separate method:

def category_for(tag)
@categories[tag] ||= []
end

# ...

category_for(resource.tag) << resource

-mental
 
J

Joel VanderWerf

Sebastian said:
Leslie said:
@categories = {}

pagelist.each do |resource|
@categories[resource.tag] ||= []
@categories[resource.tag] << resource
end


.. is there a better way?

@categories = Hash.new {Array.new}
pagelist.each do |resource|
@categories[resource.tag] << resource
end

That one still bites me from time to time...

irb(main):001:0> h = Hash.new {Array.new}
=> {}
irb(main):002:0> h[4]
=> []
irb(main):003:0> h[4] << 5
=> [5]
irb(main):004:0> h
=> {}


Try this...

irb(main):005:0> h = Hash.new {|h,k| h[k]=Array.new}
=> {}
irb(main):006:0> h[4] << 5
=> [5]
irb(main):007:0> h
=> {4=>[5]}
 
W

William James

I often make use of this idiom to add something to an array in a hash of arrays:

@categories = {}

pagelist.each do |resource|
@categories[resource.tag] ||= []
@categories[resource.tag] << resource
end

. is there a better way? Can the two @categories lines
be made into one?

Les

irb(main):001:0> h = Hash.new { |hash, key| hash[key] =[] }
=> {}
irb(main):002:0> h['foo'] << 33
=> [33]
irb(main):003:0> h['foo'] << 44
=> [33, 44]
irb(main):004:0> h
=> {"foo"=>[33, 44]}
 
M

MenTaLguY

.. is there a better way?

@categories = Hash.new {Array.new}
pagelist.each do |resource|
@categories[resource.tag] << resource
end

Although it is specific to hashes, when this is applicable I do
think it can be a better approach than the more general approach
I outlined in my other email.

-mental
 
J

Jos Backus

I often make use of this idiom to add something to an array in a hash of arrays:

@categories = {}

pagelist.each do |resource|
@categories[resource.tag] ||= []
@categories[resource.tag] << resource
end

@categories = {}

pagelist.each do |resource|
(@categories[resource.tag] ||= []) << resource
end

or

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

pagelist.each do |resource|
@categories[resource.tag] << resource
end
 
C

Christopher Swasey

[Note: parts of this message were removed to make it a legal post.]

I often make use of this idiom to add something to an array in a hash of
arrays:

@categories = {}

pagelist.each do |resource|
@categories[resource.tag] ||= []
@categories[resource.tag] << resource
end


.. is there a better way? Can the two @categories lines
be made into one?


@categories = {}

pagelist.each do |resource|
(@catetegories[resource.tag] ||= []) << resource
end



Christopher
 
J

Joel VanderWerf

MenTaLguY said:
Often it can make sense to factor the ||= portion into a
separate method:

def category_for(tag)
@categories[tag] ||= []
end

# ...

category_for(resource.tag) << resource

That's something I've used frequently. It's kind of a poor man's
dependency injection.
 
S

s.ross

If you don't mind being really cryptic about it:

@categories = resource.inject({}) {|a, v| (a[v.tag] ||= []) << v; a}

I think this works, but I'm not sure the "next guy" would ever figure
out what it does :)

I often make use of this idiom to add something to an array in a
hash of arrays:

@categories = {}

pagelist.each do |resource|
@categories[resource.tag] ||= []
@categories[resource.tag] << resource
end


.. is there a better way? Can the two @categories lines
be made into one?


Les

If you don't mind being really cryptic about it:

@categories = resource.inject({}) {|a, v| (a[v.tag] ||= []) << v; a}

I think this works, but I'm not sure the "next guy" would ever figure
out what it does :)
 
R

Robert Klemme

2008/2/19 said:
.. is there a better way?

@categories = Hash.new {Array.new}
pagelist.each do |resource|
@categories[resource.tag] << resource
end

Although it is specific to hashes, when this is applicable I do
think it can be a better approach than the more general approach
I outlined in my other email.

But it needs the fix Joel pointed out:

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

Theoretically this could even be more efficient than

(@categories[k] ||= []) << v

because the check is done only once per missing key.

Kind regards

robert
 
L

Leslie Viljoen

.. is there a better way?

@categories = Hash.new {Array.new}
pagelist.each do |resource|
@categories[resource.tag] << resource
end

Although it is specific to hashes, when this is applicable I do
think it can be a better approach than the more general approach
I outlined in my other email.

-mental


Doesn't seem to work though:

irb(main):030:0> k=Hash.new{Array.new}
=> {}
irb(main):031:0> k
=> {}
irb(main):032:0> k[1] << 1
=> [1]
irb(main):033:0> k[1] << 1
=> [1]
irb(main):034:0> k[1] << 1
=> [1]
irb(main):035:0> k[1] << 1
=> [1]
irb(main):036:0> k
=> {}
irb(main):037:0> k[1]
=> []

This works:
irb(main):038:0> h = Hash.new{|h, k| h[k] = []}
=> {}
irb(main):039:0> h[1] << 1
=> [1]
irb(main):040:0> h[1] << 1
=> [1, 1]
irb(main):041:0> h[1] << 1
=> [1, 1, 1]
irb(main):042:0> h
=> {1=>[1, 1, 1]}
irb(main):043:0> h[1] << 2
=> [1, 1, 1, 2]
irb(main):044:0> h[2] << 2
=> [2]
irb(main):045:0> h[2] << 2
=> [2, 2]
irb(main):046:0> h
=> {1=>[1, 1, 1, 2], 2=>[2, 2]}

Great!
 
L

Leslie Viljoen

2008/2/19 said:
.. is there a better way?

@categories = Hash.new {Array.new}
pagelist.each do |resource|
@categories[resource.tag] << resource
end

Although it is specific to hashes, when this is applicable I do
think it can be a better approach than the more general approach
I outlined in my other email.

But it needs the fix Joel pointed out:

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

Theoretically this could even be more efficient than

(@categories[k] ||= []) << v

because the check is done only once per missing key.

A benchmark does indeed show it to be faster, though you need truly huge arrays
to see the difference.
 
R

Robert Klemme

2008/2/20 said:
But it needs the fix Joel pointed out:

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

Theoretically this could even be more efficient than

(@categories[k] ||= []) << v

because the check is done only once per missing key.

A benchmark does indeed show it to be faster, though you need truly huge arrays
to see the difference.

As long as it's not slower for small number of values per key... :)

Thanks for letting us know!

Cheers

robert
 
T

Thufir

I sometimes do this sort of thing:

( @categories[resource.tag] ||= [] ) << resource

Although this might possibly be more readable:

category = ( @categories[resource.tag] ||= [] ) category << resource

Often it can make sense to factor the ||= portion into a separate
method:

def category_for(tag)
@categories[tag] ||= []
end

# ...

category_for(resource.tag) << resource

-mental

What's the real-world application of this?



thanks,

Thufir
 
L

Leslie Viljoen

I sometimes do this sort of thing:

( @categories[resource.tag] ||= [] ) << resource

Although this might possibly be more readable:

category = ( @categories[resource.tag] ||= [] ) category << resource

Often it can make sense to factor the ||= portion into a separate
method:

def category_for(tag)
@categories[tag] ||= []
end

# ...

category_for(resource.tag) << resource

-mental

What's the real-world application of this?



It's for making hashes of arrays:

class Article
attr_accessor :name, :month

def initialize(name, month)
@name = name
@month = month
end
end

articles = [
Article.new("Article1", "Feb"),
Article.new("Article2", "Feb"),
Article.new("Article3", "Feb"),
Article.new("Article4", "Mar"),
Article.new("Article5", "Mar"),
]

months = {}
articles.each do |a|
months[a.month] ||= []
months[a.month] << a.name
end

p months
{"Feb"=>["Article1", "Article2", "Article3"], "Mar"=>["Article4",
"Article5"]}

Now that is easy to iterate through and produce a tree:

Feb:
Article1
Article2
Article3

Mar:
Article4
Article5


...
This sets an element for key "a.month" (the article's month) to an
empty array if it is currently set to nil:
months[a.month] ||= []

This adds "a.name" (the article's name) to the array that already
exists or has just been
created above:
months[a.month] << a.name




Les
 
R

Robert Klemme

2008/2/22 said:
I sometimes do this sort of thing:

( @categories[resource.tag] ||= [] ) << resource

Although this might possibly be more readable:

category = ( @categories[resource.tag] ||= [] ) category << resource

Often it can make sense to factor the ||= portion into a separate
method:

def category_for(tag)
@categories[tag] ||= []
end

# ...

category_for(resource.tag) << resource

-mental

What's the real-world application of this?

It's for making hashes of arrays:

I may be wrong here but I read Thufir's question differently: it seems
he is asking for the application of refactoring this into a separate
method.

Kind regards

robert
 
M

MenTaLguY

Often it can make sense to factor the ||= portion into a separate
method:

def category_for(tag)
@categories[tag] ||= []
end

# ...

category_for(resource.tag) << resource

What's the real-world application of this?

There are two reasons to do it:

1. If you're doing this in non-trivial code, you're likely to end up
with multiple @categories[something] ||= [] scattered around; it
only makes sense to factor them into a single method at that point.
This is an advantage for maintainability.

2. "category_for" has a more direct meaning in the problem domain:
category_for(tag) will simply give you the category array for a
particular tag, without needing to expose on the spot the details
of how that category array is obtained. This is an advantage for
readability.

Also, regarding maintainability again, let's say that you decide to
start using objects of class Category instead of arrays; assuming
there is a Category#<< for adding a resource to a category, you can
simply change this:

def category_for(tag)
@categories[tag] ||= []
end

to this:

def category_for(tag)
@categories[tag] ||= Category.new
end

It's the DRY principle: Don't Repeat Yourself.

If there is repetition in your code, it is harder to read (because
you have to mentally filter out the repeated parts to see the
differences -- which are the important part), and you make more work
for yourself (because you must then edit many different places to
effect a single change, and are more likely to make mistakes).

-mental
 

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,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top