Introduction

Welcome to today's lesson, where we will tackle a common challenge in software engineering: the introduction of complex features while preserving backward compatibility. Our focus will be on a Potluck Dinner organization system, where we will manage participants and their respective dishes for each round. Get ready for an exciting journey through Ruby programming, step-by-step analysis, and strategic thinking. Let's dive into our adventure!

Starter Task Review

Initially, our Potluck Dinner organization system allows us to add and remove participants and manage their respective dishes for each round. There are three essential methods:

  • add_participant(member_id): This method adds a participant. If a participant with the given member_id already exists, it won't create a new one but will return false. Otherwise, it will add the member and return true.
  • remove_participant(member_id): This method removes a participant with the given member_id. If the participant exists, the system will remove them and return true. Otherwise, it will return false. When removing a participant, you need to remove their dish if they brought one.
  • add_dish(member_id, dish_name): This method enables each participant to add their dishes for every round. If a participant has already added a dish for this round OR if the member_id isn't valid, the method will return false. Otherwise, it will add the dish for the respective participant's round and return true.

Let's write our Ruby code, which implements the functions as per our initial state:

class Potluck
  def initialize
    @participants = {}
    @dishes = {}
  end

  def add_participant(member_id)
    return false if @participants.key?(member_id)

    @participants[member_id] = true
    true
  end

  def remove_participant(member_id)
    return false unless @participants.key?(member_id)

    @participants.delete(member_id)
    @dishes.delete(member_id)
    true
  end

  def add_dish(member_id, dish_name)
    return false unless @participants.key?(member_id)
    return false if @dishes.key?(member_id)

    @dishes[member_id] = dish_name
    true
  end
end

In this code, we employed Ruby hashes to store unique participant IDs and their respective dish names. With this foundation laid, let's introduce some advanced functionalities.

Introducing Advanced Functionalities

Our Potluck Dinner organization system is currently simple but practical. To make it even more exciting, we're going to introduce a "Dish of the Day" feature. This feature will enable participants to vote, and the dish receiving the most votes will be declared the "Dish of the Day."

To add this feature, we will define two new methods:

  • vote(member_id, vote_id): This method will allow a participant to cast a vote for a dish. Each participant can cast a vote only once per round. If a participant tries to vote again or if the member_id isn't valid, it should return false.
  • dish_of_the_day: This method will calculate and return the "Dish of the Day" based on the votes received. If multiple dishes tie for the highest number of votes, the dish brought by the participant who joined first acquires precedence. If there are no votes, the function returns nil.
Step 1: Constructor and Updating Existing Methods

First, we update our Potluck class to store the participant's joining time directly in the participants hash, and ensure compatibility across other methods.

Constructor Update:

class Potluck
  def initialize
    @participants = {}
    @dishes = {}
    @votes = {}
  end
end

Updating Methods to Incorporate Join Time and Consistent Removal:

def add_participant(member_id)
  return false if @participants.key?(member_id)

  @participants[member_id] = { join_time: Time.now.to_f }
  true
end

def remove_participant(member_id)
  return false unless @participants.key?(member_id)

  @participants.delete(member_id)
  @dishes.delete(member_id)
  @votes.delete(member_id)
  true
end

def add_dish(member_id, dish_name)
  return false unless @participants.key?(member_id)
  return false if @dishes.key?(member_id)

  @dishes[member_id] = dish_name
  true
end

By storing a join_time in the participants hash, we keep track of who joined first. We also ensure removal from all related structures (@dishes and @votes) for consistency.

Step 2: Implementing the vote Method

Next, we add a method to allow participants to vote for the dishes:

def vote(member_id, vote_id)
  if @participants.key?(member_id) && !@votes.key?(member_id)
    @votes[member_id] = vote_id
    true
  else
    false
  end
end

This ensures each participant can only vote once per round, returning false if the participant has already voted or if the participant doesn't exist.

Step 3: Implementing the dish_of_the_day Method

Finally, we implement the logic to determine the "Dish of the Day":

def dish_of_the_day
  return nil if @votes.empty?

  vote_count = Hash.new(0)
  @votes.values.each { |vote| vote_count[vote] += 1 }

  max_votes = vote_count.values.max
  max_vote_dishes = vote_count.select { |_, count| count == max_votes }.keys

  earliest_join_time = Float::INFINITY
  dish_of_the_day_member = nil
  max_vote_dishes.each do |member_id|
    if @participants[member_id][:join_time] < earliest_join_time
      earliest_join_time = @participants[member_id][:join_time]
      dish_of_the_day_member = member_id
    end
  end

  "Participant: '#{dish_of_the_day_member}', Dish: '#{@dishes[dish_of_the_day_member]}'"
end

If there are no votes, the method returns nil. In case of a tie, the participant with the earliest join time wins.

Ensuring Backward Compatibility
  • Participants Data Structure Change: By using a hash for participant details, we ensure each participant remains uniquely identifiable while enabling additional functionality like join time.
  • Method Signature Consistency: All method signatures remain unchanged to ensure existing interactions remain unaffected.
Final Implementation

Combining all our steps, this is the final implementation of our Potluck class with all the required methods:

class Potluck
  def initialize
    @participants = {}
    @dishes = {}
    @votes = {}
  end

  def add_participant(member_id)
    return false if @participants.key?(member_id)

    @participants[member_id] = { join_time: Time.now.to_f }
    true
  end

  def remove_participant(member_id)
    return false unless @participants.key?(member_id)

    @participants.delete(member_id)
    @dishes.delete(member_id)
    @votes.delete(member_id)
    true
  end

  def add_dish(member_id, dish_name)
    return false unless @participants.key?(member_id)
    return false if @dishes.key?(member_id)

    @dishes[member_id] = dish_name
    true
  end

  def vote(member_id, vote_id)
    if @participants.key?(member_id) && !@votes.key?(member_id)
      @votes[member_id] = vote_id
      true
    else
      false
    end
  end

  def dish_of_the_day
    return nil if @votes.empty?

    vote_count = Hash.new(0)
    @votes.values.each { |vote| vote_count[vote] += 1 }

    max_votes = vote_count.values.max
    max_vote_dishes = vote_count.select { |_, count| count == max_votes }.keys

    earliest_join_time = Float::INFINITY
    dish_of_the_day_member = nil
    max_vote_dishes.each do |member_id|
      if @participants[member_id][:join_time] < earliest_join_time
        earliest_join_time = @participants[member_id][:join_time]
        dish_of_the_day_member = member_id
      end
    end

    "Participant: '#{dish_of_the_day_member}', Dish: '#{@dishes[dish_of_the_day_member]}'"
  end
end
Lesson Summary

Bravo! You've successfully completed the task of introducing complex features while preserving backward compatibility using Ruby. This skill is invaluable in real-life software engineering scenarios, where maintaining existing functionality while enhancing systems is crucial. Strengthen this ability with even more practice and explore similar challenges. I'll see you in the next lesson! Happy coding with Ruby!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal