{"version":3,"sources":["../../../src/election/attribution/orderingFactory.ts","../../../src/election/attribution/base.ts","../../../src/election/attribution/transform.ts","../../../src/election/attribution/proportionalBase.ts","../../../src/election/attribution/majorityFactory.ts","../../../src/election/attribution/scoreFactory.ts","../../../src/election/ballots.ts","../../../src/election/attribution/proportionalFactory.ts","../../../src/election/attribution/randomFactory.ts","../../../src/utils.ts"],"sourcesContent":["import { enumerate, max, min } from \"@gouvernathor/python\";\nimport { Counter, DefaultMap } from \"@gouvernathor/python/collections\";\nimport { Order } from \"../ballots\";\nimport { type Attribution, AttributionFailure, type HasNSeats } from \"../attribution\";\n\n/**\n * Creates an attribution method in which the party with the least votes is eliminated,\n * and its votes are redistributed to the other parties according to the voters' preferences.\n * Repeats until a party reaches a majority of the remaining votes, winning all the seats.\n *\n * The ballots are not required to rank all the candidates.\n */\nexport function instantRunoff<Party>(\n    { nSeats }: {\n        nSeats: number\n    }\n): Attribution<Party, Order<Party>> & HasNSeats {\n    const attrib = (votes: Order<Party>, rest = {}): Counter<Party> => {\n        const blacklisted = new Set<Party>();\n\n        const nParties = new Set(votes.flat()).size;\n        for (let pn = 0; pn < nParties; pn++) {\n            const firstPlaces = new Counter<Party>();\n            for (const ballot of votes) {\n                for (const party of ballot) {\n                    if (!blacklisted.has(party)) {\n                        firstPlaces.increment(party);\n                        break;\n                    }\n                }\n            }\n\n            const total = firstPlaces.total;\n            for (const [party, score] of firstPlaces) {\n                if (score / total > .5) {\n                    return new Counter([[party, nSeats]]);\n                }\n            }\n            blacklisted.add(min(firstPlaces.keys(), p => firstPlaces.get(p)!));\n        }\n        throw new Error(\"Should not happen\");\n    };\n    attrib.nSeats = nSeats;\n    return attrib;\n}\n\n/**\n * Creates an attribution method in which each party receives points according\n * to the position it occupies on each ballot, and the party with the most points wins all the seats.\n *\n * Uses the Modified Borda Count, in which the least-ranked candidate gets 1 point,\n * and unranked candidates get 0 points.\n * So, the ballots are not required to rank all the candidates.\n */\nexport function bordaCount<Party>(\n    { nSeats }: {\n        nSeats: number\n    }\n): Attribution<Party, Order<Party>> & HasNSeats {\n    const attrib = (votes: Order<Party>, rest = {}): Counter<Party> => {\n        const scores = new Counter<Party>();\n        for (const ballot of votes) {\n            for (const [i, party] of enumerate(ballot.slice().reverse(), 1)) {\n                scores.increment(party, i);\n            }\n        }\n        return new Counter([[max(scores.keys(), p => scores.get(p)!), nSeats]]);\n    };\n    attrib.nSeats = nSeats;\n    return attrib;\n}\n\n/**\n * Creates an attribution method in which each party is matched against each other party,\n * and the party winning each of its matchups wins all the seats.\n * If no party wins against all others, the attribution fails.\n *\n * Doesn't support candidates with equal ranks, due to the Order type format.\n * This implementation also doesn't support incomplete ballots.\n *\n * @param contingency An optional contingency attribution method to use in case of a standoff.\n * If not provided (or null), the attribution will fail with a condorcet.Standoff error,\n * which is a subclass of AttributionFailure.\n */\nexport function condorcet<Party>(\n    { nSeats, contingency = null }: {\n        nSeats: number,\n        contingency?: Attribution<Party, Order<Party>> | null,\n    }\n): Attribution<Party, Order<Party>> & HasNSeats {\n    const attrib = (votes: Order<Party>, rest = {}): Counter<Party> => {\n        const counts = new DefaultMap<Party, Counter<Party>>(() => new Counter());\n        const majority= votes.length / 2;\n\n        for (const ballot of votes) {\n            for (const [i, party1] of enumerate(ballot)) {\n                for (const party2 of ballot.slice(i + 1)) {\n                    counts.get(party1)!.increment(party2);\n                }\n            }\n        }\n\n        const win = new Set(counts.keys());\n        for (const [party, partyCounter] of counts) {\n            for (const value of partyCounter.pos.values()) {\n                if (value > majority) {\n                    win.delete(party);\n                    break;\n                }\n            }\n        }\n\n        if (win.size !== 1) {\n            if (win.size !== 0) {\n                throw new Error(\"Bad attribution\");\n            }\n            if (contingency === null) {\n                throw new condorcet.Standoff(\"No Condorcet winner\");\n            }\n            return contingency(votes, rest);\n        }\n        const [winner] = win;\n        return new Counter([[winner, nSeats]]);\n    };\n    attrib.nSeats = nSeats;\n    return attrib;\n}\ncondorcet.Standoff = class CondorcetStandoff extends AttributionFailure {};\n","import { type Counter } from \"@gouvernathor/python/collections\";\r\nimport { type Ballots } from \"../ballots\";\r\n\r\n/**\r\n * To be thrown when an attribution fails to attribute seats,\r\n * but only in a case where it's an expected limitation of the attribution method.\r\n * The typical example being the Condorcet standoff, or a majority threshold,\r\n * but a tie could be another example (though they are usually not checked in provided implementations).\r\n *\r\n * Other errors may and will be raised by the attribution methods for other reasons,\r\n * such as invalid input data, division by zero...\r\n */\r\nexport class AttributionFailure extends Error { }\r\n\r\n/**\r\n * A function to manage how the results from the ballots\r\n * translate into an allocation of seats.\r\n *\r\n * If the attribution method is expected, by design, to fail under expected conditions,\r\n * such as a majority threshold not being reached,\r\n * this method should raise an AttributionFailure error.\r\n *\r\n * @param votes The ballots.\r\n * @param rest This parameter, ignored in most cases,\r\n * forces the attributions taking more required or optional parameters\r\n * to take them as an options object.\r\n * Attribution transforms (functions that take an attribution method and return another one)\r\n * should generally let that parameter pass through\r\n * so as to avoid breaking features in the wrapped attributions.\r\n * Attributions should avoid taking other parameters, for the same reason.\r\n *\r\n * @returns The attribution of seats to the parties based upon the votes.\r\n * It is a Counter mapping each party to the number of seats it won.\r\n * The return value's total() should be equal to the nSeats attributes - if any.\r\n */\r\nexport interface Attribution<Party, B extends Ballots<Party>> {\r\n    (votes: B, rest?: Record<string, any>): Counter<Party>;\r\n}\r\n// TODO: for all attributions where it can make sense to start with some apportionned seats,\r\n// add an option to pass some.\r\n\r\n/**\r\n * Most attributions should generally implement this interface,\r\n * which reflects that the number of seats they allocate is always the same.\r\n *\r\n * However, some attributions may yield variable numbers of seats,\r\n * for instance the pre-2024 federal German system.\r\n */\r\nexport interface HasNSeats {\r\n    readonly nSeats: number;\r\n}\r\n","import { Counter } from \"@gouvernathor/python/collections\";\nimport { type Simple } from \"../ballots\";\nimport { type Attribution, AttributionFailure } from \"../attribution\";\n\n/**\n * Transforms a standard proportional attribution method into one that requires a certain threshold\n * (percentage of the total votes) to be reached by the parties in order to receive seats.\n * Mostly useful for proportional attribution methods -\n * but works for any simple-ballot-based attribution method.\n *\n * Replaces the Proportional class implementation.\n *\n * If the attribution implements a certain type\n * that extends Attribution<Party, Simple<Party>>\n * without adding any new method or property,\n * such as Proportional,\n * and if the contingency implements the same type, null, or undefined / not passed,\n * then it can be considered that the returned value implements that type too.\n *\n * @param threshold The threshold, between 0 and 1\n * @param contingency The fallback attribution in case the threshold is not reached by any party.\n * It takes an Attribution, or undefined (the default), or null.\n * Null in this case has the specific behavior that if no party reaches the threshold,\n * an AttributionFailure error will be raised.\n * If undefined (or nothing is passed), the contingency will default to running\n * the same attribution method without the threshold.\n * @param attribution The votes passed to this attribution method are guaranteed to be above the threshold,\n * except if the contingency is undefined and no party reached the threshold\n * (in that case, all parties will be passed).\n */\nexport function addThresholdToSimpleAttribution<Party>(\n    { threshold, attribution, contingency = attribution }: {\n        threshold: number,\n        attribution: Attribution<Party, Simple<Party>>,\n        contingency?: Attribution<Party, Simple<Party>> | null,\n    }\n): Attribution<Party, Simple<Party>> {\n    const attrib = (votes: Simple<Party>, rest = {}): Counter<Party> => {\n        if (threshold > 0) {\n            const original_votes = votes;\n            const votes_threshold = threshold * votes.total;\n            votes = new Counter<Party>([...votes.entries()].filter(([_, v]) => v >= votes_threshold));\n            if (votes.size === 0) {\n                if (contingency === null) {\n                    throw new AttributionFailure(\"No party reached the threshold\");\n                }\n                return contingency(original_votes, rest);\n            }\n        }\n        return attribution(votes, rest);\n    };\n    return attrib;\n}\n","import { Counter } from \"@gouvernathor/python/collections\";\nimport { type Simple } from \"../ballots\";\nimport { type Attribution, type HasNSeats } from \"../attribution\";\nimport { defaultMetric, type DisproportionMetric } from \"./metrics\";\n\n/**\n * An attribution method that allocates seats proportionally\n * to the number of votes received by each party.\n */\nexport interface Proportional<Party> extends Attribution<Party, Simple<Party>> {};\n\n// TODO: doesn't it always implement HasNSeats ?\n/**\n * An specific kind of proportional attribution method,\n * based on a rank-index function, and which\n */\nexport interface RankIndexMethod<Party> extends Proportional<Party> {};\n\n/**\n * A function that should be pure : it should not take into account\n * any value other than the arguments passed to it.\n *\n * @param t The fraction of votes received by a candidate\n * @param a The number of seats already allocated to that candidate\n * @returns A value that should be increasing as t rises, and decreasing as a rises.\n * The higher the return value, the more likely the candidate is to receive another seat.\n */\nexport interface RankIndexFunction {\n    (t: number, a: number): number;\n};\n\n/**\n * A function creating a fixed-seats, proportional, rank-index attribution method\n * from a rank-index function.\n *\n * The implementation is optimized so as to call rankIndexFunction as few times as possible.\n *\n * Replaces the RankIndexMethod class implementation.\n */\nexport function proportionalFromRankIndexFunction<Party>(\n    { nSeats, rankIndexFunction }: {\n        nSeats: number,\n        rankIndexFunction: RankIndexFunction,\n    }\n): RankIndexMethod<Party> & HasNSeats {\n    const attrib = (votes: Simple<Party>, rest = {}): Counter<Party> => {\n        const allVotes = votes.total;\n        const fractions = new Map([...votes.entries()].map(([party, v]) => [party, v / allVotes]));\n\n        const rankIndexValues = new Map([...fractions.entries()].map(([party, f]) => [party, rankIndexFunction(f, 0)]));\n\n        // the parties, sorted by increasing rankIndex value\n        const parties = [...votes.keys()].sort((a, b) => rankIndexValues.get(a)! - rankIndexValues.get(b)!);\n\n        const seats = new Counter<Party>();\n\n        s: for (let sn = 0; sn < nSeats; sn++) {\n            // take the most deserving party\n            const winner = parties.pop()!;\n            // give it a seat\n            seats.increment(winner);\n            // update the rankIndex value of the party\n            rankIndexValues.set(winner, rankIndexFunction(fractions.get(winner)!, seats.get(winner)!));\n\n            // insert it in its (new) place in the sorted list of parties\n            for (let pn = 0; pn < parties.length; pn++) {\n                if (rankIndexValues.get(parties[pn])! >= rankIndexValues.get(winner)!) {\n                    parties.splice(pn, 0, winner);\n                    continue s;\n                }\n            }\n            parties.push(winner);\n        }\n\n        return seats;\n    };\n    attrib.nSeats = nSeats;\n    return attrib;\n}\n\n/**\n * Creates a rank-index (proportional) attribution method in which\n * the total number of seats is NOT fixed.\n * Instead, it takes a metric of disproportionality\n * between the entitlements (the votes) and the numbers of seats,\n * and a range of valid number of seats,\n * and the attribution method will return the allocation of seats\n * that minimizes the metric, while respecting the range of valid number of seats.\n *\n * The attribution will only return a 0-seats attribution when the maxNSeats is 0,\n * otherwise, a minNSeats value of 0 will be treated as 1.\n *\n * The metric has a reasonable default.\n *\n * The implementation is still optimized so as to call rankIndexFunction as few times as possible.\n *\n * @param minNSeats The minimum number of seats to be allocated, inclusive.\n * @param maxNSeats The maximum number of seats to be allocated, inclusive.\n */\nexport function boundedRankIndexMethod<Party>(\n    { minNSeats, maxNSeats, rankIndexFunction, metric = defaultMetric }: {\n        minNSeats: number,\n        maxNSeats: number,\n        rankIndexFunction: RankIndexFunction,\n        metric?: DisproportionMetric<Party>,\n    }\n): RankIndexMethod<Party> {\n    const attrib = (votes: Simple<Party>, rest = {}): Counter<Party> => {\n        const allVotes = votes.total;\n        const fractions = new Map([...votes.entries()].map(([party, v]) => [party, v / allVotes]));\n\n        const rankIndexValues = new Map([...fractions.entries()].map(([party, f]) => [party, rankIndexFunction(f, 0)]));\n\n        // the parties, sorted by increasing rankIndex value\n        const parties = [...votes.keys()].sort((a, b) => rankIndexValues.get(a)! - rankIndexValues.get(b)!);\n\n        const seats = new Counter<Party>();\n\n        let bestSeats = seats.pos;\n        // technically, most metrics give 0 for a 0-seats attribution\n        // but we have to patch that out otherwise the 0-seats attribution will always be returned\n        let bestSeatsMetric = Infinity;\n\n        s: for (let sn = 1; sn <= maxNSeats; sn++) {\n            // take the most deserving party\n            const winner = parties.pop()!;\n            // give it a seat\n            seats.increment(winner);\n\n            if (sn >= minNSeats) {\n                // compute the metric of the new attribution\n                const newMetric = metric({ votes, seats });\n                if (newMetric < bestSeatsMetric) { // when above the min, favor fewer seats\n                    bestSeats = seats.pos;\n                    bestSeatsMetric = newMetric;\n                }\n            }\n\n            // update the rankIndex value of the party\n            rankIndexValues.set(winner, rankIndexFunction(fractions.get(winner)!, seats.get(winner)!));\n\n            // insert it in its (new) place in the sorted list of parties\n            for (let pn = 0; pn < parties.length; pn++) {\n                if (rankIndexValues.get(parties[pn])! >= rankIndexValues.get(winner)!) {\n                    parties.splice(pn, 0, winner);\n                    continue s;\n                }\n            }\n            parties.push(winner);\n        }\n\n        return bestSeats;\n    }\n    attrib.minNSeats = minNSeats;\n    attrib.maxNSeats = maxNSeats;\n    return attrib;\n}\n\nexport interface DivisorMethod<Party> extends RankIndexMethod<Party> {}\n\n/**\n * A function that should be pure.\n * @param k The number of seats already allocated to a party\n * @returns A value that should be increasing as k rises\n */\nexport interface DivisorFunction {\n    (k: number): number;\n}\n\nexport function stationaryDivisorFunction(r: number): DivisorFunction {\n    return (k: number) => k + r;\n}\n\nexport function rankIndexFunctionFromDivisorFunction(\n    divisorFunction: DivisorFunction\n): RankIndexFunction {\n    return (t, a) => t / divisorFunction(a);\n}\n\n/**\n * A function creating a divisor method -\n * one kind of rank-index attribution, itself a kind of proportional attribution.\n */\nexport function proportionalFromDivisorFunction<Party>(\n    { nSeats, divisorFunction }: {\n        nSeats: number,\n        divisorFunction: DivisorFunction,\n    }\n): DivisorMethod<Party> & HasNSeats {\n    return proportionalFromRankIndexFunction({\n        nSeats,\n        rankIndexFunction: rankIndexFunctionFromDivisorFunction(divisorFunction),\n    });\n}\n","import { max } from \"@gouvernathor/python\";\nimport { Counter } from \"@gouvernathor/python/collections\";\nimport { type Simple } from \"../ballots\";\nimport { type Attribution, AttributionFailure, type HasNSeats } from \"../attribution\";\n\n/**\n * Creates an attribution method in which\n * the party with the most votes wins all the seats.\n *\n * Will throw an AttributionFailure error if no party wins any vote.\n */\nexport function plurality<Party>(\n    { nSeats }: {\n        nSeats: number,\n    }\n): Attribution<Party, Simple<Party>> & HasNSeats {\n    const attrib = (votes: Simple<Party>, rest = {}): Counter<Party> => {\n        const win = max(votes.keys(), p => votes.get(p)!);\n\n        if (votes.get(win)! > 0) {\n            return new Counter([[win, nSeats]]);\n        }\n        throw new AttributionFailure(\"No party won any vote\");\n    };\n    attrib.nSeats = nSeats;\n    return attrib;\n}\n\n/**\n * Creates an attribution method in which a candidate needs to reach\n * a certain percentage of the votes in order to win all the seats.\n *\n * If not party reaches the threshold, the contingency attribution method is called,\n * or if no contingency is provided, an AttributionFailure error is thrown.\n */\nexport function superMajority<Party>(\n    { nSeats, threshold, contingency = null }: {\n        nSeats: number,\n        threshold: number,\n        contingency?: Attribution<Party, Simple<Party>> | null,\n    }\n): Attribution<Party, Simple<Party>> & HasNSeats {\n    const attrib = (votes: Simple<Party>, rest = {}): Counter<Party> => {\n        const win = max(votes.keys(), p => votes.get(p)!);\n\n        if ((votes.get(win)! / votes.total) > threshold) {\n            return new Counter([[win, nSeats]]);\n        }\n        if (contingency === null) {\n            throw new AttributionFailure(\"No party reached the threshold\");\n        }\n        return contingency(votes, rest);\n    };\n    attrib.nSeats = nSeats;\n    return attrib;\n}\n","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","import { divmod } from \"@gouvernathor/python\";\nimport { Counter } from \"@gouvernathor/python/collections\";\nimport { type Simple } from \"../ballots\";\nimport { type Attribution, type HasNSeats } from \"../attribution\";\nimport { addThresholdToSimpleAttribution } from \"../attribution/transform\";\nimport { type DivisorFunction, type DivisorMethod, type Proportional, proportionalFromDivisorFunction, proportionalFromRankIndexFunction, rankIndexFunctionFromDivisorFunction, type RankIndexMethod, stationaryDivisorFunction } from \"./proportionalBase\";\n\nconst divisor1 = stationaryDivisorFunction(1);\nexport function jefferson<Party>(\n    { nSeats }: {\n        nSeats: number,\n    }\n): DivisorMethod<Party> & HasNSeats {\n    return proportionalFromDivisorFunction<Party>({\n        nSeats,\n        divisorFunction: divisor1,\n    });\n}\nexport const dHondt = jefferson;\n\nconst divisorPoint5: DivisorFunction = k => 2 * k + 1; // int math is better than k + .5\nexport function webster<Party>(\n    { nSeats }: {\n        nSeats: number,\n    }\n): DivisorMethod<Party> & HasNSeats {\n    return proportionalFromDivisorFunction<Party>({\n        nSeats,\n        divisorFunction: divisorPoint5,\n    });\n}\nexport const sainteLague = webster;\n\nexport function hamilton<Party>(\n    { nSeats }: {\n        nSeats: number,\n    }\n): Proportional<Party> & HasNSeats {\n    const attrib = (votes: Simple<Party>, rest = {}): Counter<Party> => {\n        const seats = new Counter<Party>();\n        const remainders = new Map<Party, number>();\n        const sumVotes = votes.total;\n\n        for (const [party, scores] of votes) {\n            const [i, r] = divmod(scores * nSeats, sumVotes);\n            seats.set(party, i);\n            remainders.set(party, r);\n        }\n\n        seats.update([...remainders.keys()]\n            .sort((a, b) => remainders.get(b)! - remainders.get(a)!)\n            .slice(0, nSeats - seats.total));\n        return seats;\n    };\n    attrib.nSeats = nSeats;\n    return attrib;\n}\nexport const hareLargestRemainders = hamilton;\n\nconst huntingtonHillBaseRankIndexFunction = rankIndexFunctionFromDivisorFunction(k => Math.sqrt(k * (k + 1)));\nconst huntingtonHillRankIndexFunction = (t: number, a: number) => {\n    if (a <= 0) {\n        return Infinity;\n    }\n    return huntingtonHillBaseRankIndexFunction(t, a);\n};\n/**\n * This attribution method required some creativity and tweaks,\n * since the standard divisor won't work without an initial seats value,\n * causing a division by 0.\n * As a result, a threshold is required in this case.\n *\n * In a situation where the candidates are already limited by other means,\n * (for instance when distributing representatives between a fixed number of states)\n * to be less or equal to the number of seats, then a threhold of 0 can be passed.\n * In that case, the method will allocate one seat to each candidate\n * until all candidates have a seat,\n * or (but that would be a bug) all seats are allocated.\n *\n * The order in which the first seats are allocated is an implementation detail.\n */\nexport function huntingtonHill<Party>(\n    { nSeats, threshold }: {\n        nSeats: number,\n        threshold: 0,\n    }\n): DivisorMethod<Party> & HasNSeats;\nexport function huntingtonHill<Party>(\n    { nSeats, threshold, contingency }: {\n        nSeats: number,\n        threshold: number,\n        contingency?: DivisorMethod<Party> | null,\n    }\n): DivisorMethod<Party> & HasNSeats;\nexport function huntingtonHill<Party>(\n    { nSeats, threshold, contingency }: {\n        nSeats: number,\n        threshold: number,\n        contingency?: RankIndexMethod<Party> | null,\n    }\n): RankIndexMethod<Party> & HasNSeats;\nexport function huntingtonHill<Party>(\n    { nSeats, threshold, contingency }: {\n        nSeats: number,\n        threshold: number,\n        contingency?: Attribution<Party, Simple<Party>> | null,\n    }\n): Attribution<Party, Simple<Party>> & HasNSeats;\nexport function huntingtonHill<Party>(\n    { nSeats, threshold, contingency = null }: {\n        nSeats: number,\n        threshold: number,\n        contingency?: Attribution<Party, Simple<Party>> | null,\n    }\n) {\n    const attrib = addThresholdToSimpleAttribution({\n        threshold,\n        contingency,\n        attribution: proportionalFromRankIndexFunction({\n            nSeats,\n            rankIndexFunction: huntingtonHillRankIndexFunction,\n        }),\n    }) as Attribution<Party, Simple<Party>> & { nSeats?: number };\n    attrib.nSeats = nSeats;\n    return attrib;\n}\n\n/**\n * Creates a highest-averages proportional attribution method.\n *\n * The exact method used is an implementation detail and may change between versions.\n */\nexport const highestAverages = webster;\n\n/**\n * Creates a largest-remainders proportional attribution method.\n *\n * Implementation detail: the quota used is the Hare/Hamilton quota.\n */\nexport const largestRemainders = hamilton;\n","import { Counter } from \"@gouvernathor/python/collections\";\nimport { createRandomObj, type RandomObjParam } from \"../../utils\";\nimport { type Simple } from \"../ballots\";\nimport { type Attribution, type HasNSeats } from \"../attribution\";\n\n/**\n * Randomized attribution.\n * Everyone votes, then one ballot is selected at random.\n * (One ballot per seat to fill, and assuming there's\n * enough ballots that the picking is with replacement.)\n *\n * The randomization is based on the given parameters. If a RNG object\n * is passed, it is used without reseeding across all calls of the attribution.\n * If a seed is passed, the random object is reseeded\n * at each call of the attribution.\n */\nexport function randomize<Party>(\n    { nSeats, ...randomParam }: {\n        nSeats: number,\n    } & RandomObjParam\n): Attribution<Party, Simple<Party>> & HasNSeats {\n    const attrib = (votes: Simple<Party>, rest = {}): Counter<Party> => {\n        const randomObj = createRandomObj(randomParam);\n        return new Counter(randomObj.choices([...votes.keys()], { weights: [...votes.values()], k: nSeats }));\n    };\n    attrib.nSeats = nSeats;\n    return attrib;\n}\n","import RNG from \"@gouvernathor/rng\";\n\nexport type RandomObjParam = {randomObj: RNG} | {randomSeed?: number|string};\n\nexport function createRandomObj(param?: RandomObjParam): RNG;\nexport function createRandomObj({ randomObj, randomSeed }:\n    { randomObj?: RNG, randomSeed?: number | string } = {},\n): RNG {\n    if (randomObj === undefined) {\n        randomObj = new RNG(randomSeed);\n    }\n    return randomObj;\n}\n\n\n// https://stackoverflow.com/a/71700658\n\n/**\n * Mutable tuple of a single element type and a given length.\n *\n * Warning: due to limitations in TypeScript, when N is unknown/generic,\n * the typing system does not recognize that the type extends array.\n * Using methods such as map will require a (double) cast first to any then to T[].\n */\nexport type Tuple<\n  T,\n  N extends number,\n  R extends T[] = [],\n> = R['length'] extends N ? R : Tuple<T, N, [T, ...R]>;\n\n/**\n * Immutable version of Tuple.\n *\n * Warning: due to limitations in TypeScript, when N is unknown/generic,\n * the typing system does not recognize that the type extends readonly array.\n * Using methods such as map will require a (double) cast first to any then to readonly T[].\n */\nexport type ReadonlyTuple<T, N extends number> = Readonly<Tuple<T, N>>;\n"],"mappings":"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","names":["import_python","import_collections","import_collections","import_collections","import_python","import_collections","Scores","import_python","import_collections","import_collections"]}