Lesson 1
Developing a Potluck Dinner System with C# for Backward Compatibility
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 C# 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:

  • bool AddParticipant(string memberId): This method adds a participant. If a participant with the given memberId already exists, it won't create a new one but will return false. Otherwise, it will add the member and return true.
  • bool RemoveParticipant(string memberId): This method removes a participant with the given memberId. 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.
  • bool AddDish(string memberId, string dishName): 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 memberId 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 C# code, which implements the functions as per our initial state:

C#
1using System; 2using System.Collections.Generic; 3 4public class Potluck 5{ 6 private readonly HashSet<string> participants; 7 private readonly Dictionary<string, string> dishes; 8 9 public Potluck() 10 { 11 participants = new HashSet<string>(); 12 dishes = new Dictionary<string, string>(); 13 } 14 15 public bool AddParticipant(string memberId) 16 { 17 if (participants.Contains(memberId)) 18 { 19 return false; 20 } 21 else 22 { 23 participants.Add(memberId); 24 return true; 25 } 26 } 27 28 public bool RemoveParticipant(string memberId) 29 { 30 if (!participants.Contains(memberId)) 31 { 32 return false; 33 } 34 else 35 { 36 participants.Remove(memberId); 37 dishes.Remove(memberId); 38 return true; 39 } 40 } 41 42 public bool AddDish(string memberId, string dishName) 43 { 44 if (!participants.Contains(memberId) || dishes.ContainsKey(memberId)) 45 { 46 return false; 47 } 48 else 49 { 50 dishes[memberId] = dishName; 51 return true; 52 } 53 } 54}

In this code, we employed a C# HashSet<string> to store unique participant IDs and a Dictionary<string, string> to store the participant's ID and their respective dish name. 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:

  • bool Vote(string memberId, string voteId): 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 memberId isn't valid, it should return false.
  • string DishOfTheDay(): 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 null.
Building Advanced Features. Step 1: Constructor Change

Next, we will extend our existing Potluck class to accommodate these new features:

C#
1using System.Collections.Generic; 2 3public class Potluck 4{ 5 private Dictionary<string, long> participants; 6 private Dictionary<string, string> dishes; 7 private Dictionary<string, string> votes; 8 9 public Potluck() 10 { 11 participants = new Dictionary<string, long>(); 12 dishes = new Dictionary<string, string>(); 13 votes = new Dictionary<string, string>(); 14 } 15}

Firstly, we altered our participants data structure into a Dictionary<string, long>, where the key is the participant's ID and the value is the time of joining (using the time of adding to the system as the join time).

We also added a votes Dictionary to store the votes, where the key is the participant ID and the value is the participant ID for whom they have cast the vote.

Step 2: Implementing the `vote` method
C#
1public bool Vote(string memberId, string voteId) 2{ 3 if (participants.ContainsKey(memberId) && !votes.ContainsKey(memberId)) 4 { 5 votes[memberId] = voteId; 6 return true; 7 } 8 else 9 { 10 return false; 11 } 12}

In the Vote function, we check whether the memberId exists and whether the vote hasn't been cast before. If both conditions are fulfilled, we add the vote to our votes and return true; otherwise, we return false.

Step 3: Implementing the `dishOfTheDay` method
C#
1public string DishOfTheDay() 2{ 3 if (votes.Count == 0) 4 { 5 return null; 6 } 7 8 var voteCount = new Dictionary<string, int>(); 9 foreach (var vote in votes.Values) 10 { 11 if (voteCount.ContainsKey(vote)) 12 { 13 voteCount[vote]++; 14 } 15 else 16 { 17 voteCount[vote] = 1; 18 } 19 } 20 21 int maxVotes = 0; 22 foreach (var count in voteCount.Values) 23 { 24 if (count > maxVotes) 25 { 26 maxVotes = count; 27 } 28 } 29 30 var maxVoteDishes = new HashSet<string>(); 31 foreach (var entry in voteCount) 32 { 33 if (entry.Value == maxVotes) 34 { 35 maxVoteDishes.Add(entry.Key); 36 } 37 } 38 39 long earliestJoinTime = long.MaxValue; 40 string dishOfTheDayMember = null; 41 foreach (var memberId in maxVoteDishes) 42 { 43 if (participants[memberId] < earliestJoinTime) 44 { 45 earliestJoinTime = participants[memberId]; 46 dishOfTheDayMember = memberId; 47 } 48 } 49 50 return "Participant: '" + dishOfTheDayMember + "', Dish: '" + dishes[dishOfTheDayMember] + "'"; 51}

In the DishOfTheDay function, we start by ensuring we perform the calculation only if there are votes. Afterward, we calculate the count of votes for each dish, determine the dish or dishes that have received the maximum votes, and if there's a tie, we select the dish brought by the participant who joined the earliest. Finally, we return the selected "Dish of the Day."

Step 4: Updating Previous Methods for Compatibility and Integration

As we introduce the two advanced functionalities, Vote and DishOfTheDay, our existing system needs to be adeptly updated to ensure seamless integration while maintaining backward compatibility. Here's how we've refined the previous methods:

The AddParticipant method initially just added a participant to a set. With the introduction of the "Dish of the Day" feature, we now store the participant's ID along with their join time in a Dictionary<string, long>. This timestamp is crucial for determining precedence in case of a tie in votes.

C#
1public bool AddParticipant(string memberId) 2{ 3 if (participants.ContainsKey(memberId)) 4 { 5 return false; 6 } 7 else 8 { 9 participants[memberId] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); 10 return true; 11 } 12}

This adjustment ensures that every new participant is tracked with a join time, allowing us to leverage this information for the "Dish of the Day" feature.

Step 5: Adjustments in `removeParticipant` and `addDish`

The original RemoveParticipant method did not consider the impact of removing a participant who might have already voted or added a dish. To maintain the integrity of our voting and dish tracking, we now clean up all related entries across dishes and votes dictionaries when a participant is removed.

C#
1public bool RemoveParticipant(string memberId) 2{ 3 if (!participants.ContainsKey(memberId)) 4 { 5 return false; 6 } 7 else 8 { 9 participants.Remove(memberId); 10 dishes.Remove(memberId); 11 votes.Remove(memberId); 12 return true; 13 } 14}

The AddDish method's logic remained mostly unchanged since it primarily interacts with the dishes dictionary. However, ensuring that the method checks for the validity of the memberId against the updated participants' storage is essential:

C#
1public bool AddDish(string memberId, string dishName) 2{ 3 if (!participants.ContainsKey(memberId) || dishes.ContainsKey(memberId)) 4 { 5 return false; 6 } 7 else 8 { 9 dishes[memberId] = dishName; 10 return true; 11 } 12}
Ensuring Backward Compatibility
  • Participants Data Structure Change: By transitioning from a HashSet to a Dictionary<string, long> for storing participant details, we've inherently kept the unique identification trait while adding the functionality for storing join times. This change is backward compatible as it requires no modification in how participant IDs are provided or handled externally.

  • Method Signature Consistency: All method signatures (AddParticipant, RemoveParticipant, AddDish) remain unchanged. This deliberate decision ensures that existing integrations with our system interface seamlessly without modifications from client code.

Final Implementation

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

C#
1using System; 2using System.Collections.Generic; 3 4public class Potluck 5{ 6 private Dictionary<string, long> participants; 7 private Dictionary<string, string> dishes; 8 private Dictionary<string, string> votes; 9 10 public Potluck() 11 { 12 participants = new Dictionary<string, long>(); 13 dishes = new Dictionary<string, string>(); 14 votes = new Dictionary<string, string>(); 15 } 16 17 public bool AddParticipant(string memberId) 18 { 19 if (participants.ContainsKey(memberId)) 20 { 21 return false; 22 } 23 else 24 { 25 participants[memberId] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); 26 return true; 27 } 28 } 29 30 public bool RemoveParticipant(string memberId) 31 { 32 if (!participants.ContainsKey(memberId)) 33 { 34 return false; 35 } 36 else 37 { 38 participants.Remove(memberId); 39 dishes.Remove(memberId); 40 votes.Remove(memberId); 41 return true; 42 } 43 } 44 45 public bool AddDish(string memberId, string dishName) 46 { 47 if (!participants.ContainsKey(memberId) || dishes.ContainsKey(memberId)) 48 { 49 return false; 50 } 51 else 52 { 53 dishes[memberId] = dishName; 54 return true; 55 } 56 } 57 58 public bool Vote(string memberId, string voteId) 59 { 60 if (participants.ContainsKey(memberId) && !votes.ContainsKey(memberId)) 61 { 62 votes[memberId] = voteId; 63 return true; 64 } 65 else 66 { 67 return false; 68 } 69 } 70 71 public string DishOfTheDay() 72 { 73 if (votes.Count == 0) 74 { 75 return null; 76 } 77 78 var voteCount = new Dictionary<string, int>(); 79 foreach (var vote in votes.Values) 80 { 81 if (voteCount.ContainsKey(vote)) 82 { 83 voteCount[vote]++; 84 } 85 else 86 { 87 voteCount[vote] = 1; 88 } 89 } 90 91 int maxVotes = 0; 92 foreach (var count in voteCount.Values) 93 { 94 if (count > maxVotes) 95 { 96 maxVotes = count; 97 } 98 } 99 100 var maxVoteDishes = new HashSet<string>(); 101 foreach (var entry in voteCount) 102 { 103 if (entry.Value == maxVotes) 104 { 105 maxVoteDishes.Add(entry.Key); 106 } 107 } 108 109 long earliestJoinTime = long.MaxValue; 110 string dishOfTheDayMember = null; 111 foreach (var memberId in maxVoteDishes) 112 { 113 if (participants[memberId] < earliestJoinTime) 114 { 115 earliestJoinTime = participants[memberId]; 116 dishOfTheDayMember = memberId; 117 } 118 } 119 120 return "Participant: '" + dishOfTheDayMember + "', Dish: '" + dishes[dishOfTheDayMember] + "'"; 121 } 122}
Lesson Summary

Bravo! You've successfully completed the task of introducing complex features while preserving backward compatibility using C#. This skill is invaluable in real-life software engineering scenarios, where existing codebases can be extensive, and breaking changes can be catastrophic. Strengthen this ability with even more practice and explore similar challenges. I'll see you in the next lesson! Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.