views:

27

answers:

2

I have any array of structs. Each struct in the array has the following attributes:

  1. user_id
  2. num_hot_dogs_eaten
  3. date_last_pigged_out

Here's what I want to do:

  1. Find the structs with matching user_id's, and merge them into one struct record where num_hot_dogs_eaten is the sum of all matching records and date_last_pigged_out is the most-recent date the user pigged out.

  2. Sort the array of structs by num_hot_dogs_eaten (first order of priority) and by date_last_pigged_out (second order of priority...most-recent first).

  3. Return a new sorted array of structs.

+1  A: 

Use this:

def f(users)
  r = []
  users.each do |u|
    new_match = false
    match = r.find {|x| x.user_id == u.user_id }
    unless match
      match = u.dup
      r << match
      new_match = true
    end
    match.num_hot_dogs_eaten += u.num_hot_dogs_eaten unless new_match
    match.date_last_pigged_out =
      [match, u].max_by(&:date_last_pigged_out).date_last_pigged_out
  end
  r.sort_by {|u| [u.num_hot_dogs_eaten, u.date_last_pigged_out] }.
    reverse
end
Adrian
A: 

A more functional programming approach:

User    = Struct.new(:user_id, :num_hot_dogs_eaten, :date_last_pigged_out)
ONE_DAY = 60 * 60 * 24

class Object
  def returning(object)
    yield object
    object
  end
end

users = [
  User.new(1, 3, Time.now),
  User.new(1, 2, Time.now + ONE_DAY),
  User.new(1, 1, Time.now - ONE_DAY),
  User.new(2, 2, Time.now - ONE_DAY),
  User.new(2, 3, Time.now),
  User.new(3, 5, Time.now - ONE_DAY),
]

users.inject(Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = [] } }) do |collection, user|
  returning(collection) do
    collection[user.user_id][:num_hot_dogs_eaten]   << user.num_hot_dogs_eaten
    collection[user.user_id][:date_last_pigged_out] << user.date_last_pigged_out
  end
end.map do |user_id, stats|
  User.new(user_id, stats[:num_hot_dogs_eaten].inject(&:+), stats[:date_last_pigged_out].max)
end.sort_by { |user| [user.num_hot_dogs_eaten, user.date_last_pigged_out] }.reverse

The actual implementation is (assuming you have returning defined):

users.inject(Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = [] } }) do |collection, user|
  returning(collection) do
    collection[user.user_id][:num_hot_dogs_eaten]   << user.num_hot_dogs_eaten
    collection[user.user_id][:date_last_pigged_out] << user.date_last_pigged_out
  end
end.map do |user_id, stats|
  User.new(user_id, stats[:num_hot_dogs_eaten].inject(&:+), stats[:date_last_pigged_out].max)
end.sort_by { |user| [user.num_hot_dogs_eaten, user.date_last_pigged_out] }.reverse
Evan Senter