||= [] idiom

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

Leslie V. wrote:

@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

On Wed, 20 Feb 2008 07:11:31 +0900, “Leslie V.”
[email protected] wrote:

… 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

Sebastian H. wrote:

@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]}

On Wed, 20 Feb 2008 07:15:52 +0900, Sebastian H.
[email protected] wrote:

… 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

On Wed, Feb 20, 2008 at 07:11:31AM +0900, Leslie V. wrote:

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

On 2/19/08, Leslie V. [email protected] wrote:

… 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

MenTaLguY wrote:

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.

On Feb 19, 4:11 pm, Leslie V. [email protected] wrote:

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

2008/2/19, MenTaLguY [email protected]:

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

On Feb 20, 2008 12:26 AM, MenTaLguY [email protected] wrote:

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!

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 :slight_smile:

On Feb 19, 2008, at 2:11 PM, Leslie V. wrote:

… 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 :slight_smile:

On Feb 20, 2008 11:44 AM, Robert K. [email protected]
wrote:

Although it is specific to hashes, when this is applicable I do

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.

On Thu, 21 Feb 2008 00:06:39 +0900, “Leslie V.”
[email protected] wrote:

Doesn’t seem to work though:

That’s right; see Joel’s email for the correction.

-mental

2008/2/20, Leslie V. [email protected]:

On Feb 20, 2008 11:44 AM, Robert K. [email protected] wrote:

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… :slight_smile:

Thanks for letting us know!

Cheers

robert

On Feb 20, 2008 8:34 PM, MenTaLguY [email protected] wrote:

On Thu, 21 Feb 2008 00:06:39 +0900, “Leslie V.” [email protected] wrote:

Doesn’t seem to work though:

That’s right; see Joel’s email for the correction.

Ah sorry, missed that.

On Wed, 20 Feb 2008 07:21:15 +0900, MenTaLguY wrote:

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

category_for(resource.tag) << resource

-mental

What’s the real-world application of this?

thanks,

Thufir

On Fri, Feb 22, 2008 at 2:33 PM, Thufir [email protected] wrote:

Often it can make sense to factor the ||= portion into a separate
-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

2008/2/22, Leslie V. [email protected]:

-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

On Fri, 22 Feb 2008 21:33:22 +0900, Thufir [email protected]
wrote:

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