ecclesia
Version:
Framework for political and electoral simulations
1 lines • 7.67 kB
Source Map (JSON)
{"version":3,"sources":["../../../src/election/attribution/scoreFactory.ts","../../../src/election/ballots.ts"],"sourcesContent":["import { enumerate, max } from \"@gouvernathor/python\";\nimport { Counter, DefaultMap } from \"@gouvernathor/python/collections\";\nimport { fmean, median } from \"@gouvernathor/python/statistics\";\nimport { Scores } from \"../ballots\";\nimport { type Attribution, type HasNSeats } from \"../attribution\";\n\n/**\n * Creates an attribution method in which all the seats go to the candidate with the highest average score.\n *\n * The ballots are not required to grade all the candidates.\n */\nexport function averageScore<Party>(\n { nSeats }: {\n nSeats: number\n }\n): Attribution<Party, Scores<Party>> & HasNSeats {\n const attrib = (votes: Scores<Party>, rest = {}): Counter<Party> => {\n const counts = new DefaultMap<Party, number[]>(() => []);\n for (const [party, grades] of votes) {\n for (const [grade, qty] of enumerate(grades)) {\n counts.get(party).push(...Array(qty).fill(grade));\n }\n }\n\n return new Counter([[max(counts.keys(), party => fmean(counts.get(party)!)), nSeats]]);\n }\n attrib.nSeats = nSeats;\n return attrib;\n}\n\n/**\n * Creates an attribution method in which all the seats go to the candidate with the highest median score.\n *\n * If there is a tie, the contingency method is called on the candidates that are tied.\n * The default contingency is to take the maximum average score.\n *\n * The ballots are not required to grade all the candidates.\n */\nexport function medianScore<Party>(\n { nSeats, contingency }: {\n nSeats: number,\n contingency?: Attribution<Party, Scores<Party>>,\n }\n): Attribution<Party, Scores<Party>> & HasNSeats {\n if (contingency === undefined) {\n contingency = averageScore({ nSeats });\n }\n\n const attrib = (votes: Scores<Party>, rest = {}): Counter<Party> => {\n const counts = new DefaultMap<Party, number[]>(() => []);\n for (const [party, grades] of votes) {\n for (const [grade, qty] of enumerate(grades)) {\n counts.get(party).push(...Array(qty).fill(grade));\n }\n }\n\n const medians = new Map([...counts.entries()]\n .map(([party, partigrades]) => [party, median(partigrades)]));\n\n const winScore = Math.max(...medians.values());\n const [winner, ...winners] = [...medians.keys()].filter(p => medians.get(p) === winScore);\n\n if (winners.length === 0) { // no tie\n return new Counter([[winner, nSeats]]);\n }\n\n winners.unshift(winner);\n const trimmedResults = Scores.fromEntries(winners.map(party => [party, counts.get(party)!]));\n return contingency(trimmedResults, rest);\n };\n attrib.nSeats = nSeats;\n return attrib;\n}\n","import { ReadonlyCounter } from \"@gouvernathor/python/collections\";\n\n/**\n * A counter, mapping each party to its number of ballots.\n *\n * [[PS, 5], [LR: 7]] -> 5 ballots for PS, 7 for LR.\n */\nexport interface Simple<Party> extends ReadonlyCounter<Party> { }\n\n/**\n * A list of ballots, each ballot ordering parties by decreasing preference.\n *\n * [[LR, PS, LFI], [LFI, PS,], ] -> one voter prefers LR then PS then LFI,\n * another prefers LFI then PS and didn't rank LR.\n *\n * Math.max(result.map(ballot => ballot.length)) <= number of candidates-1\n * == if the voting method requires a full ranking\n *\n * There can be no tie between candidates within a ballot.\n * Note that not ranking all the candidates is permitted by this type,\n * although some attribution methods may not support it.\n */\nexport interface Order<Party> extends ReadonlyArray<ReadonlyArray<Party>> { }\n\n/**\n * A mapping from each party to a list of number of ballots, one for each grade.\n *\n * [[PS, [0, 2, 5, 9, 1]], ] -> PS received the worst grade 0 times, the best grade 1 time and so on.\n *\n * result.get(p).length is constant, equal to the number of grades of the voting method.\n *\n * If the voter must grade all the candidates, then sum(result.get(p)) is constant\n * and equal to the number of voters.\n *\n * Any party not mapped will be assumed to have\n * an array of zeros (of length ngrades).\n */\nexport interface Scores<Party> extends ReadonlyMap<Party, ReadonlyArray<number>> {\n readonly ngrades: number;\n get(key: Party): ReadonlyArray<number>;\n}\n\nexport namespace Scores {\n function get<Party>(this: Scores<Party>, key: Party): ReadonlyArray<number> {\n const value = this.get(key);\n if (value === undefined) {\n return Array(this.ngrades).fill(0);\n }\n return value!;\n }\n\n export function fromEntries<Party>(\n elements: readonly [Party, readonly number[]][],\n ): Scores<Party> {\n if (elements.length === 0) {\n throw new Error(\"Use the fromGrades method to create an empty Scores instance\");\n }\n const ths = new Map(elements) as Partial<Scores<Party>> & { ngrades?: number };\n ths.ngrades = elements[0][1].length;\n ths.get = get.bind(ths as any);\n return ths as Scores<Party>;\n }\n\n export function fromGrades<Party>(ngrades: number): Scores<Party> {\n const ths = new Map() as Partial<Scores<Party>> & { ngrades?: number };\n ths.ngrades = ngrades;\n ths.get = get.bind(ths as any);\n return ths as Scores<Party>;\n }\n}\n\n\nexport type Ballots<Party> = Simple<Party> | Order<Party> | Scores<Party>;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA+B;AAC/B,yBAAoC;AACpC,wBAA8B;;;ACwCvB,IAAU;AAAA,CAAV,CAAUA,YAAV;AACH,WAAS,IAAgC,KAAmC;AACxE,UAAM,QAAQ,KAAK,IAAI,GAAG;AAC1B,QAAI,UAAU,QAAW;AACrB,aAAO,MAAM,KAAK,OAAO,EAAE,KAAK,CAAC;AAAA,IACrC;AACA,WAAO;AAAA,EACX;AAEO,WAAS,YACZ,UACa;AACb,QAAI,SAAS,WAAW,GAAG;AACvB,YAAM,IAAI,MAAM,8DAA8D;AAAA,IAClF;AACA,UAAM,MAAM,IAAI,IAAI,QAAQ;AAC5B,QAAI,UAAU,SAAS,CAAC,EAAE,CAAC,EAAE;AAC7B,QAAI,MAAM,IAAI,KAAK,GAAU;AAC7B,WAAO;AAAA,EACX;AAVO,EAAAA,QAAS;AAYT,WAAS,WAAkB,SAAgC;AAC9D,UAAM,MAAM,oBAAI,IAAI;AACpB,QAAI,UAAU;AACd,QAAI,MAAM,IAAI,KAAK,GAAU;AAC7B,WAAO;AAAA,EACX;AALO,EAAAA,QAAS;AAAA,GArBH;;;AD/BV,SAAS,aACZ,EAAE,OAAO,GAGoC;AAC7C,QAAM,SAAS,CAAC,OAAsB,OAAO,CAAC,MAAsB;AAChE,UAAM,SAAS,IAAI,8BAA4B,MAAM,CAAC,CAAC;AACvD,eAAW,CAAC,OAAO,MAAM,KAAK,OAAO;AACjC,iBAAW,CAAC,OAAO,GAAG,SAAK,yBAAU,MAAM,GAAG;AAC1C,eAAO,IAAI,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,EAAE,KAAK,KAAK,CAAC;AAAA,MACpD;AAAA,IACJ;AAEA,WAAO,IAAI,2BAAQ,CAAC,KAAC,mBAAI,OAAO,KAAK,GAAG,eAAS,yBAAM,OAAO,IAAI,KAAK,CAAE,CAAC,GAAG,MAAM,CAAC,CAAC;AAAA,EACzF;AACA,SAAO,SAAS;AAChB,SAAO;AACX;AAUO,SAAS,YACZ,EAAE,QAAQ,YAAY,GAIuB;AAC7C,MAAI,gBAAgB,QAAW;AAC3B,kBAAc,aAAa,EAAE,OAAO,CAAC;AAAA,EACzC;AAEA,QAAM,SAAS,CAAC,OAAsB,OAAO,CAAC,MAAsB;AAChE,UAAM,SAAS,IAAI,8BAA4B,MAAM,CAAC,CAAC;AACvD,eAAW,CAAC,OAAO,MAAM,KAAK,OAAO;AACjC,iBAAW,CAAC,OAAO,GAAG,SAAK,yBAAU,MAAM,GAAG;AAC1C,eAAO,IAAI,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,EAAE,KAAK,KAAK,CAAC;AAAA,MACpD;AAAA,IACJ;AAEA,UAAM,UAAU,IAAI,IAAI,CAAC,GAAG,OAAO,QAAQ,CAAC,EACvC,IAAI,CAAC,CAAC,OAAO,WAAW,MAAM,CAAC,WAAO,0BAAO,WAAW,CAAC,CAAC,CAAC;AAEhE,UAAM,WAAW,KAAK,IAAI,GAAG,QAAQ,OAAO,CAAC;AAC7C,UAAM,CAAC,QAAQ,GAAG,OAAO,IAAI,CAAC,GAAG,QAAQ,KAAK,CAAC,EAAE,OAAO,OAAK,QAAQ,IAAI,CAAC,MAAM,QAAQ;AAExF,QAAI,QAAQ,WAAW,GAAG;AACtB,aAAO,IAAI,2BAAQ,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC;AAAA,IACzC;AAEA,YAAQ,QAAQ,MAAM;AACtB,UAAM,iBAAiB,OAAO,YAAY,QAAQ,IAAI,WAAS,CAAC,OAAO,OAAO,IAAI,KAAK,CAAE,CAAC,CAAC;AAC3F,WAAO,YAAY,gBAAgB,IAAI;AAAA,EAC3C;AACA,SAAO,SAAS;AAChB,SAAO;AACX;","names":["Scores"]}