Tim Brunsmo
Tim Brunsmo

Reputation: 601

Advanced grouping in Ruby

I want to group events by their days. Prefact:

   Available days are 1,2,3,4,5,6,7
   One event can not contain duplicates. e.g [1,1,2,3]
   Setup is Ruby 1.9.2 with Rails 3.2

An event is containing like 8-10 more attributes(unnecessary to include in example, but should be considered, these attributes should still be there after grouping.) In other words the event objects should not be altered only grouped as is.

Consider an array with objects:

events = [
  {
    :name => "event1",
    :days => [1,2,3,4,5]
  },
  {
    :name => "event2",
    :days => [1,4,5]
  },
  {
    :name => "event3",
    :days => [1]
  },
  {
    :name => "event4",
    :days => [2]
  },
  {
    :name => "event5",
    :days => [3]
  },
  {
    :name => "event6",
    :days => [4]
  },
  {
    :name => "event7",
    :days => [5]
  },
  {
    :name => "event8",
    :days => [1,2,3]
  },
  {
    :name => "event9",
    :days => [1,5]
  },
  {
    :name => "event10",
    :days => [1,2]
  },
  {
    :name => "event11",
    :days => [1,2,3,4,5]
  }
]

To be grouped an event must have at least 3 days. And these days should be in numerically order.

Example(should be grouped): [1,2,3]

Example(should not be grouped): [1,4,5]

Events that's not suitable for grouping should be placed in each day they contain.

Example: [1,4,5] should be placed in 1,4 and 5.

The desired result of events array above:

[
  {
    :heading => "1",
    :events => [
      {
        :name => "event3",
        :days => [1]
      },
      {
        :name => "event9",
        :days => [1,5]
      },
      {
        :name => "event10",
        :days => [1,2]
      },
      {
        :name => "event2",
        :days => [1,4,5]
      }
    ]
  },
  {
    :heading => "2", 
    :events => [
      {
        :name => "event4",
        :days => [2]
      },
      {
        :name => "event10",
        :days => [1,2]
      }
    ]
  },
  {
    :heading => "3",
    :events => [
      {
        :name => "event5",
        :days => [3]
      }
    ]
  },
  {
    :heading => "4",
    :events => [
      {
        :name => "event6",
        :days => [4]
      },
      {
        :name => "event2",
        :days => [1,4,5]
      }
    ]
  },
  {
    :heading => "5",
    :events => [
      {
        :name => "event7",
        :days => [5]
      },
      {
        :name => "event9",
        :days => [1,5]
      },
      {
        :name => "event2",
        :days => [1,4,5]
      }
    ]
  },
  {
    :heading => "1-3",
    :events => [
      {
        :name => "event8"
      }
    ]
  },
  {
    :heading => "1.5",
    :events => [
      {
        :name => "event1"
      },
      {
        :name => "event11"
      }
    ]
  }
]

It is very advanced Ruby here. Maybe too advanced for me, everything I've tried ends up missing one part of the equation. But hey it's Ruby, it shouldn't be that hard?

EDIT: Updated example with clarification and corrected expected output

Upvotes: 0

Views: 155

Answers (2)

DigitalRoss
DigitalRoss

Reputation: 146093

require 'pp'
pp(events.inject(Hash.new { |h, k| h[k] = [] }) do |m, e|
  days = e[:days]
  event = { :name => e[:name] }
  if days.size >= 3 && days.last - days.first + 1 == days.size
    m["%d-%d" % [days.first, days.last]] << event
  else
    days.each { |d| m[d.to_s] << event }
  end
  m
end)

Upvotes: 2

Ben Miller
Ben Miller

Reputation: 1484

I would modify DigitalRoss's answer slightly. I would change

    if days.size >= 3 && days.last - days.first + 1 == days.size

to

    if days.size >= 3 && (days.first..days.last).to_a == days

This will catch [1,1,3,4]...

It would also be wise to sort the days array before testing!

Upvotes: 1

Related Questions