Lesson 2
Enhancing Voting Systems with Advanced Go Features
Introduction

Welcome back to another exciting session where we learn about enhancing existing functionality without causing regressions. Today, our scenario involves designing a voting system. We'll start with the basic implementation of the voting system and gradually introduce additional elements of complexity.

Starter Task Review

In our initial task, we created a simple voting system in Go with a set of basic functionalities:

  • func RegisterCandidate(candidateId string) bool: This function is used to add new candidates to our system.
  • func Vote(timestamp int64, voterId string, candidateId string) bool: This function facilitates users casting their votes. Each vote is given a timestamp.
  • func GetVotes(candidateId string) *int: This function retrieves the total number of votes for a given candidate.
  • func TopNCandidates(n int) []string: We also want to add a leaderboard functionality to our system. This function returns the top n candidates sorted by the number of votes.
Initial Solution Development

Let's jump into the Go code and begin the implementation of our starter task. Here, we use Go's built-in map and slice as the core of our design. These collections allow us to have dynamic lists keyed based on candidate IDs and voter IDs, which will greatly simplify our design.

Go
1package main 2 3import ( 4 "sort" 5) 6 7type VotingSystem struct { 8 candidates map[string]int 9 voters map[string]*VotingHistory 10} 11 12type VotingHistory struct { 13 Votes []string 14 Timestamps []int64 15} 16 17func NewVotingSystem() *VotingSystem { 18 return &VotingSystem{ 19 candidates: make(map[string]int), 20 voters: make(map[string]*VotingHistory), 21 } 22} 23 24func (vs *VotingSystem) RegisterCandidate(candidateId string) bool { 25 if _, exists := vs.candidates[candidateId]; exists { 26 return false // Candidate is already registered 27 } 28 vs.candidates[candidateId] = 0 // Initialize candidates with 0 votes 29 return true 30} 31 32func (vs *VotingSystem) Vote(timestamp int64, voterId string, candidateId string) bool { 33 if _, exists := vs.candidates[candidateId]; !exists { 34 return false // Return false if the candidate is not registered 35 } 36 voterHistory, exists := vs.voters[voterId] 37 if !exists { 38 voterHistory = &VotingHistory{} 39 vs.voters[voterId] = voterHistory 40 } 41 voterHistory.Votes = append(voterHistory.Votes, candidateId) 42 voterHistory.Timestamps = append(voterHistory.Timestamps, timestamp) 43 vs.candidates[candidateId]++ 44 return true 45} 46 47func (vs *VotingSystem) GetVotes(candidateId string) *int { 48 if votes, exists := vs.candidates[candidateId]; exists { 49 return &votes 50 } 51 return nil 52} 53 54func (vs *VotingSystem) TopNCandidates(n int) []string { 55 type candidateVote struct { 56 id string 57 votes int 58 } 59 60 var candidateList []candidateVote 61 for id, votes := range vs.candidates { 62 candidateList = append(candidateList, candidateVote{id, votes}) 63 } 64 65 sort.Slice(candidateList, func(i, j int) bool { 66 return candidateList[i].votes > candidateList[j].votes 67 }) 68 69 var topCandidates []string 70 for i := 0; i < n && i < len(candidateList); i++ { 71 topCandidates = append(topCandidates, candidateList[i].id) 72 } 73 return topCandidates 74}
Introduce New Methods

Now that we have a basic voting system, our goal is to enhance this system with additional functionalities:

  • func GetVotingHistory(voterId string) map[string]int: Provides a detailed voting history for a specified voter, returning a map with candidates and the number of votes they received from the voter. Returns nil if the voter ID does not exist.
  • func BlockVoterRegistration(timestamp int64) bool: Implements a mechanism to halt any new voter registrations past a specified timestamp, effectively freezing the voter list as of that moment.
  • func ChangeVote(timestamp int64, voterId string, oldCandidateId string, newCandidateId string) bool: Enables voters to change their vote from one candidate to another, given the change is made within a 24-hour window from their last vote, ensuring both the old and new candidates are registered, and that the voter initially voted for the old candidate.
Implementing the getVotingHistory Method

We proceed to enhance our existing VotingSystem struct to accommodate the new functionalities.

First, let's incorporate the functions to get the voting history and to block further voter registrations:

Go
1var blockTime *int64 2 3func (vs *VotingSystem) GetVotingHistory(voterId string) map[string]int { 4 voterHistory, exists := vs.voters[voterId] 5 if !exists { 6 return nil 7 } 8 votingHistory := make(map[string]int) 9 for _, candidateId := range voterHistory.Votes { 10 votingHistory[candidateId]++ 11 } 12 return votingHistory 13} 14 15func (vs *VotingSystem) BlockVoterRegistration(timestamp int64) bool { 16 blockTime = &timestamp 17 return true 18}
Updating the vote Method

With the introduction of the BlockVoterRegistration functionality, we must revisit our Vote function to ensure it respects the new rules set by this feature. Specifically, we need to ensure that no votes are cast after the voter registration has been blocked. This is critical in maintaining the integrity of the voting system, especially in scenarios where registration deadlines are enforced. Here's how we modify the Vote function to incorporate this change:

Go
1func (vs *VotingSystem) Vote(timestamp int64, voterId string, candidateId string) bool { 2 if blockTime != nil && timestamp >= *blockTime { 3 return false // Vote attempt is blocked due to the registration freeze 4 } 5 6 if _, exists := vs.candidates[candidateId]; !exists { 7 return false // Return false if the candidate is not registered 8 } 9 10 voterHistory, exists := vs.voters[voterId] 11 if !exists { 12 voterHistory = &VotingHistory{} 13 vs.voters[voterId] = voterHistory 14 } 15 16 voterHistory.Votes = append(voterHistory.Votes, candidateId) 17 voterHistory.Timestamps = append(voterHistory.Timestamps, timestamp) 18 vs.candidates[candidateId]++ 19 20 return true 21}
Implementing changeVote Method

The ChangeVote function allows voters to change their vote, adhering to specific rules. Here's a step-by-step explanation of implementing this functionality:

  • Verify Candidate and Voter Validity: Check if both the old and new candidate IDs exist in the system, and verify that the voter has previously voted for the old candidate.

  • Timestamp Constraints: Ensure that the voter is trying to change their vote within an allowable timeframe after their initial vote.

  • Update Votes: If all conditions are met, subtract one vote from the old candidate, add one vote to the new candidate, and update the voter's voting record.

Go
1func (vs *VotingSystem) ChangeVote(timestamp int64, voterId string, oldCandidateId string, newCandidateId string) bool { 2 if blockTime != nil && timestamp >= *blockTime { 3 return false // Vote change attempt is blocked due to the registration freeze 4 } 5 6 if _, exists := vs.candidates[oldCandidateId]; !exists { 7 return false // Old candidate is not registered 8 } 9 10 if _, exists := vs.candidates[newCandidateId]; !exists { 11 return false // New candidate is not registered 12 } 13 14 voterHistory, exists := vs.voters[voterId] 15 if !exists || len(voterHistory.Votes) == 0 { 16 return false // Voter is either not registered or has not voted yet 17 } 18 19 lastVoteIndex := -1 20 for i, candidate := range voterHistory.Votes { 21 if candidate == oldCandidateId { 22 lastVoteIndex = i 23 } 24 } 25 if lastVoteIndex == -1 { 26 return false // Voter has not voted for the old candidate 27 } 28 29 if timestamp-voterHistory.Timestamps[lastVoteIndex] > 86400 { 30 return false // Vote change exceeds the 24-hour window 31 } 32 33 voterHistory.Votes[lastVoteIndex] = newCandidateId 34 voterHistory.Timestamps[lastVoteIndex] = timestamp 35 36 vs.candidates[oldCandidateId]-- 37 vs.candidates[newCandidateId]++ 38 39 return true 40}
Lesson Summary

Congratulations! You've successfully enhanced the voting system by adding functionalities to view voting history, block new candidate registrations, and, most importantly, enable vote changes under specific conditions. Each of these features was developed with careful consideration to maintain the integrity and backward compatibility of the system. Continue exploring with practice sessions and further experimentation to refine your skills in developing complex, functionality-rich applications. Happy coding!

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