Back to js-functions
12-panchayat-election.js
JavaScript
1/**
2 * 🗳️ Panchayat Election System - Capstone
3 *
4 * Village ki panchayat election ka system bana! Yeh CAPSTONE challenge hai
5 * jisme saare function concepts ek saath use honge:
6 * closures, callbacks, HOF, factory, recursion, pure functions.
7 *
8 * Functions:
9 *
10 * 1. createElection(candidates)
11 * - CLOSURE: private state (votes object, registered voters set)
12 * - candidates: array of { id, name, party }
13 * - Returns object with methods:
14 *
15 * registerVoter(voter)
16 * - voter: { id, name, age }
17 * - Add to private registered set. Return true.
18 * - Agar already registered or voter invalid, return false.
19 * - Agar age < 18, return false.
20 *
21 * castVote(voterId, candidateId, onSuccess, onError)
22 * - CALLBACKS: call onSuccess or onError based on result
23 * - Validate: voter registered? candidate exists? already voted?
24 * - If valid: record vote, call onSuccess({ voterId, candidateId })
25 * - If invalid: call onError("reason string")
26 * - Return the callback's return value
27 *
28 * getResults(sortFn)
29 * - HOF: takes optional sort comparator function
30 * - Returns array of { id, name, party, votes: count }
31 * - If sortFn provided, sort results using it
32 * - Default (no sortFn): sort by votes descending
33 *
34 * getWinner()
35 * - Returns candidate object with most votes
36 * - If tie, return first candidate among tied ones
37 * - If no votes cast, return null
38 *
39 * 2. createVoteValidator(rules)
40 * - FACTORY: returns a validation function
41 * - rules: { minAge: 18, requiredFields: ["id", "name", "age"] }
42 * - Returned function takes a voter object and returns { valid, reason }
43 *
44 * 3. countVotesInRegions(regionTree)
45 * - RECURSION: count total votes in nested region structure
46 * - regionTree: { name, votes: number, subRegions: [...] }
47 * - Sum votes from this region + all subRegions (recursively)
48 * - Agar regionTree null/invalid, return 0
49 *
50 * 4. tallyPure(currentTally, candidateId)
51 * - PURE FUNCTION: returns NEW tally object with incremented count
52 * - currentTally: { "cand1": 5, "cand2": 3, ... }
53 * - Return new object where candidateId count is incremented by 1
54 * - MUST NOT modify currentTally
55 * - If candidateId not in tally, add it with count 1
56 *
57 * @example
58 * const election = createElection([
59 * { id: "C1", name: "Sarpanch Ram", party: "Janata" },
60 * { id: "C2", name: "Pradhan Sita", party: "Lok" }
61 * ]);
62 * election.registerVoter({ id: "V1", name: "Mohan", age: 25 });
63 * election.castVote("V1", "C1", r => "voted!", e => "error: " + e);
64 * // => "voted!"
65 */
66export function createElection(candidates) {
67 const votes = {};
68 const registeredVoters = new Map();
69 const votedVoters = new Set();
70
71 candidates.forEach((c) => {
72 votes[c.id] = 0;
73 });
74 function isValidVoter(voter) {
75 return (
76 voter !== null &&
77 typeof voter === "object" &&
78 voter.id &&
79 voter.name &&
80 typeof voter.age === "number"
81 );
82 }
83
84 function candidateExists(candidateId) {
85 return candidates.some((c) => c.id === candidateId);
86 }
87
88 return {
89 registerVoter(voter) {
90 if (!isValidVoter(voter)) return false;
91 if (voter.age < 18) return false;
92 if (registeredVoters.has(voter.id)) return false;
93
94 registeredVoters.set(voter.id, voter);
95 return true;
96 },
97
98 castVote(voterId, candidateId, onSuccess, onError) {
99 if (!registeredVoters.has(voterId)) {
100 return onError("Voter not registered");
101 }
102 if (!candidateExists(candidateId)) {
103 return onError("Candidate does not exist");
104 }
105 if (votedVoters.has(voterId)) {
106 return onError("Voter has already voted");
107 }
108 votes[candidateId]++;
109 votedVoters.add(voterId);
110
111 return onSuccess({ voterId, candidateId });
112 },
113
114 getResults(sortFn) {
115 const results = candidates.map((c) => ({
116 id: c.id,
117 name: c.name,
118 party: c.party,
119 votes: votes[c.id],
120 }));
121 if (sortFn) {
122 return results.sort(sortFn);
123 }
124
125 return results.sort((a, b) => b.votes - a.votes);
126 },
127
128 getWinner() {
129 const totalVotes = Object.values(votes).reduce((a, b) => a + b, 0);
130 if (totalVotes === 0) return null;
131 const results = this.getResults();
132 return results[0];
133 },
134 };
135}
136
137export function createVoteValidator(rules) {
138
139 return function (voter) {
140
141 for (const field of rules.requiredFields) {
142 if (!voter || voter[field] === undefined || voter[field] === null) {
143 return { valid: false, reason: `Missing required field: ${field}` };
144 }
145 }
146
147 if (typeof voter.age !== "number") {
148 return { valid: false, reason: "Age must be a number" };
149 }
150
151 if (voter.age < rules.minAge) {
152 return { valid: false, reason: `Age must be at least ${rules.minAge}` };
153 }
154
155 return { valid: true, reason: null };
156 };
157}
158
159export function countVotesInRegions(regionTree) {
160 if (!regionTree || typeof regionTree !== "object") return 0;
161
162 const currentVotes = typeof regionTree.votes === "number"
163 ? regionTree.votes
164 : 0;
165 if (!Array.isArray(regionTree.subRegions) || regionTree.subRegions.length === 0) {
166 return currentVotes;
167 }
168
169 const subVotes = regionTree.subRegions.reduce((total, sub) => {
170 return total + countVotesInRegions(sub);
171 }, 0);
172
173 return currentVotes + subVotes;
174}
175
176export function tallyPure(currentTally, candidateId) {
177 return {
178 ...currentTally,
179 [candidateId]: (currentTally[candidateId] || 0) + 1,
180 };
181}