||= [] idiom

Discussion in 'Ruby' started by Leslie Viljoen, Feb 19, 2008.

  1. 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 Viljoen, Feb 19, 2008
    #1
    1. Advertising

  2. Leslie Viljoen wrote:
    > @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:
    ICQ: 205544826
     
    Sebastian Hungerecker, Feb 19, 2008
    #2
    1. Advertising

  3. Leslie Viljoen

    MenTaLguY Guest

    On Wed, 20 Feb 2008 07:11:31 +0900, "Leslie Viljoen" <> 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
    >
    > .. 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
     
    MenTaLguY, Feb 19, 2008
    #3
  4. Sebastian Hungerecker wrote:
    > Leslie Viljoen 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


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

    --
    vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
     
    Joel VanderWerf, Feb 19, 2008
    #4
  5. On Feb 19, 4:11 pm, Leslie Viljoen <> 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
    >
    > . 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]}
     
    William James, Feb 19, 2008
    #5
  6. Leslie Viljoen

    MenTaLguY Guest

    On Wed, 20 Feb 2008 07:15:52 +0900, Sebastian Hungerecker <> 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
     
    MenTaLguY, Feb 19, 2008
    #6
  7. Leslie Viljoen

    Jos Backus Guest

    On Wed, Feb 20, 2008 at 07:11:31AM +0900, Leslie Viljoen 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

    --
    Jos Backus
    jos at catnook.com
     
    Jos Backus, Feb 19, 2008
    #7
  8. [Note: parts of this message were removed to make it a legal post.]

    On 2/19/08, Leslie Viljoen <> 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
    >
    >
    > .. 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
     
    Christopher Swasey, Feb 19, 2008
    #8
  9. 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.

    --
    vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
     
    Joel VanderWerf, Feb 19, 2008
    #9
  10. Leslie Viljoen

    s.ross Guest

    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 Viljoen 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
    >
    >
    > .. 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 :)
     
    s.ross, Feb 19, 2008
    #10
  11. 2008/2/19, MenTaLguY <>:
    > On Wed, 20 Feb 2008 07:15:52 +0900, Sebastian Hungerecker <> 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.


    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

    --
    use.inject do |as, often| as.you_can - without end
     
    Robert Klemme, Feb 20, 2008
    #11
  12. On Feb 20, 2008 12:26 AM, MenTaLguY <> wrote:
    > On Wed, 20 Feb 2008 07:15:52 +0900, Sebastian Hungerecker <> 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



    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!
     
    Leslie Viljoen, Feb 20, 2008
    #12
  13. On Feb 20, 2008 11:44 AM, Robert Klemme <> wrote:
    > 2008/2/19, MenTaLguY <>:
    >
    > > On Wed, 20 Feb 2008 07:15:52 +0900, Sebastian Hungerecker <> 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.

    >
    > 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.
     
    Leslie Viljoen, Feb 20, 2008
    #13
  14. 2008/2/20, Leslie Viljoen <>:
    > On Feb 20, 2008 11:44 AM, Robert Klemme <> wrote:


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


    --
    use.inject do |as, often| as.you_can - without end
     
    Robert Klemme, Feb 20, 2008
    #14
  15. Leslie Viljoen

    MenTaLguY Guest

    On Thu, 21 Feb 2008 00:06:39 +0900, "Leslie Viljoen" <> wrote:
    > Doesn't seem to work though:


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

    -mental
     
    MenTaLguY, Feb 20, 2008
    #15
  16. On Feb 20, 2008 8:34 PM, MenTaLguY <> wrote:
    > On Thu, 21 Feb 2008 00:06:39 +0900, "Leslie Viljoen" <> wrote:
    > > Doesn't seem to work though:

    >
    > That's right; see Joel's email for the correction.


    Ah sorry, missed that.
     
    Leslie Viljoen, Feb 20, 2008
    #16
  17. Leslie Viljoen

    Thufir Guest

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

    > 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
     
    Thufir, Feb 22, 2008
    #17
  18. On Fri, Feb 22, 2008 at 2:33 PM, Thufir <> wrote:
    > On Wed, 20 Feb 2008 07:21:15 +0900, MenTaLguY wrote:
    >
    > > 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
     
    Leslie Viljoen, Feb 22, 2008
    #18
  19. 2008/2/22, Leslie Viljoen <>:
    > On Fri, Feb 22, 2008 at 2:33 PM, Thufir <> wrote:
    > > On Wed, 20 Feb 2008 07:21:15 +0900, MenTaLguY wrote:
    > >
    > > > 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

    --
    use.inject do |as, often| as.you_can - without end
     
    Robert Klemme, Feb 22, 2008
    #19
  20. Leslie Viljoen

    MenTaLguY Guest

    On Fri, 22 Feb 2008 21:33:22 +0900, Thufir <> 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

    >
    > 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
     
    MenTaLguY, Feb 22, 2008
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Rhino
    Replies:
    5
    Views:
    558
    Jon A. Cruz
    Feb 18, 2004
  2. Replies:
    6
    Views:
    355
  3. Steve Jorgensen
    Replies:
    4
    Views:
    441
    Soren Kuula
    Aug 28, 2005
  4. Ivan Vecerina

    Re: Extendable envelope/letter idiom

    Ivan Vecerina, Jun 24, 2003, in forum: C++
    Replies:
    0
    Views:
    1,140
    Ivan Vecerina
    Jun 24, 2003
  5. Icosahedron

    Pimpl Idiom

    Icosahedron, Nov 20, 2003, in forum: C++
    Replies:
    7
    Views:
    748
    Icosahedron
    Nov 22, 2003
Loading...

Share This Page