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 
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 
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… 
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:
-
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.
-
“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