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 using PHP.
In our initial task, we created a simple voting system in PHP with a set of basic functionalities:
function registerCandidate(string $candidateId): bool
: This function is used to add new candidates to our system.function vote(int $timestamp, string $voterId, string $candidateId): bool
: This function facilitates users casting their votes. Each vote is given a timestamp.function getVotes(string $candidateId): ?int
: This function retrieves the total number of votes for a given candidate.function topNCandidates(int $n): array
: We also want to add a leaderboard functionality to our system. This function returns the topn
candidates sorted by the number of votes.
Let's jump into the PHP code and begin the implementation of our starter task. Here, we use PHP's arrays as the core of our design. Associative arrays allow us to have dynamic structures keyed based on candidate IDs and voter IDs, which will greatly simplify our design.
php1class VotingSystem { 2 private array $candidates; // Stores candidate_id as key and votes as value 3 private array $voters; // Tracks each voter's voting history 4 5 public function __construct() { 6 $this->candidates = []; // Initialize candidates array 7 $this->voters = []; // Initialize voters array 8 } 9 10 public function registerCandidate(string $candidateId): bool { 11 if (array_key_exists($candidateId, $this->candidates)) { 12 return false; // Candidate is already registered 13 } 14 $this->candidates[$candidateId] = 0; // Initialize candidates with 0 votes 15 return true; 16 } 17 18 public function vote(int $timestamp, string $voterId, string $candidateId): bool { 19 if (!array_key_exists($candidateId, $this->candidates)) { 20 return false; // Return false if candidate is not registered 21 } 22 if (!isset($this->voters[$voterId])) { 23 $this->voters[$voterId] = new VotingHistory(); 24 } 25 $voterHistory = $this->voters[$voterId]; 26 $voterHistory->votes[] = $candidateId; // Record the vote 27 $voterHistory->timestamps[] = $timestamp; // Record the time of the vote 28 $this->candidates[$candidateId]++; // Increment vote count for the candidate 29 return true; 30 } 31 32 public function getVotes(string $candidateId): ?int { 33 return $this->candidates[$candidateId] ?? null; // Retrieve vote count for a candidate or null if not found 34 } 35 36 public function topNCandidates(int $n): array { 37 arsort($this->candidates); 38 return array_slice(array_keys($this->candidates), 0, $n); // Return top n candidates based on votes 39 } 40} 41 42class VotingHistory { 43 public array $votes; 44 public array $timestamps; 45 46 public function __construct() { 47 $this->votes = []; 48 $this->timestamps = []; 49 } 50}
Now that we have a basic voting system, our goal is to enhance this system with additional functionalities:
function getVotingHistory(string $voterId): ?array
: Provides a detailed voting history for a specified voter, returning an associative array with candidates and the number of votes they received from the voter. Returnsnull
if the voter ID does not exist.function blockVoterRegistration(int $timestamp): bool
: Implements a mechanism to halt any new voter registrations past a specified timestamp, effectively freezing the voter list as of that moment.function changeVote(int $timestamp, string $voterId, string $oldCandidateId, string $newCandidateId): 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.
We proceed to enhance our existing VotingSystem
class to accommodate the new functionalities.
First, let's incorporate the methods to get the voting history and to block further voter registrations:
php1private ?int $blockTime = null; 2 3public function getVotingHistory(string $voterId): ?array { 4 if (!isset($this->voters[$voterId])) { 5 return null; 6 } 7 $voterHistory = $this->voters[$voterId]; 8 $votingHistory = []; 9 10 foreach ($voterHistory->votes as $candidateId) { 11 if (!isset($votingHistory[$candidateId])) { 12 $votingHistory[$candidateId] = 0; 13 } 14 $votingHistory[$candidateId]++; 15 } 16 return $votingHistory; // Returns an array with each candidate voted for and the number of times they were voted for 17} 18 19public function blockVoterRegistration(int $timestamp): bool { 20 $this->blockTime = $timestamp; // Records the timestamp after which no registration is allowed 21 return true; 22}
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:
php1public function vote(int $timestamp, string $voterId, string $candidateId): bool { 2 // Check if blockTime is set and if the vote attempt is after the block timestamp 3 if ($this->blockTime !== null && $timestamp >= $this->blockTime) { 4 return false; // Vote attempt is blocked due to the registration freeze 5 } 6 7 if (!array_key_exists($candidateId, $this->candidates)) { 8 return false; // Return false if the candidate is not registered 9 } 10 11 if (!isset($this->voters[$voterId])) { 12 $this->voters[$voterId] = new VotingHistory(); 13 } 14 15 $voterHistory = $this->voters[$voterId]; 16 $voterHistory->votes[] = $candidateId; // Record the vote 17 $voterHistory->timestamps[] = $timestamp; // Record the time of the vote 18 $this->candidates[$candidateId]++; // Increment vote count for the candidate 19 20 return true; 21}
This update ensures that our voting system behaves as expected, even with the new functionality to block further voter registrations beyond a certain timestamp. It's a perfect demonstration of how new features can necessitate revisits and revisions to existing code to enhance functionality while ensuring backward compatibility.
The changeVote
method 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 time frame 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.
php1public function changeVote(int $timestamp, string $voterId, string $oldCandidateId, string $newCandidateId): bool { 2 // Verify existence in the voting system 3 if (!isset($this->candidates[$oldCandidateId]) || !isset($this->candidates[$newCandidateId])) { 4 return false; 5 } 6 7 // Check if the voter_id is valid and has previously voted 8 if (!isset($this->voters[$voterId]) || empty($this->voters[$voterId]->votes)) { 9 return false; // Voter is either not registered or has not voted yet 10 } 11 12 $voterHistory = $this->voters[$voterId]; 13 // Confirm voter has voted for the old candidate 14 $lastVoteIndex = array_search($oldCandidateId, array_reverse($voterHistory->votes, true)); 15 if ($lastVoteIndex === false) { 16 return false; 17 } 18 19 // Ensure the operation is within the permitted timeframe 20 if ($timestamp - $voterHistory->timestamps[$lastVoteIndex] > 86400) { // 24 hours in seconds 21 return false; 22 } 23 24 // Perform the vote change 25 // Remove the old vote 26 unset($voterHistory->votes[$lastVoteIndex]); 27 unset($voterHistory->timestamps[$lastVoteIndex]); 28 29 // Append the new vote 30 $voterHistory->votes[] = $newCandidateId; 31 $voterHistory->timestamps[] = $timestamp; 32 33 // Update candidates' vote counts 34 $this->candidates[$oldCandidateId]--; 35 $this->candidates[$newCandidateId]++; 36 37 return true; 38}
Note: In the implementation of the changeVote
method, we don't use blockTime
because blockTime
is intended to halt new voter registrations and votes after the specified timestamp. However, the changeVote
method is focused on changing an existing vote. Since it's assumed that the voter has already registered and cast their vote before the blockTime
, the focus here is on validating the vote change within the 24-hour window, irrespective of blockTime
. Therefore, blockTime
does not need to be considered in this context where changes to existing votes are allowed.
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 in a PHP context. Continue exploring with practice sessions and further experimentation to refine your skills in developing complex, functionality-rich applications in PHP. Happy coding!