Created
April 12, 2022 08:23
-
-
Save francois-durand/c022fb2001bdbeeb023dcbfb8bcbcfb5 to your computer and use it in GitHub Desktop.
architecture_in_oop.ipynb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "slide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "# Architecture in Object-Oriented Programming: \n\n# A Case Study " | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "What is often done:\n* Explain basics of OOP (e.g. syntax),\n* Give principles of clean programming (e.g. SOLID) with simplified examples, more or less realistic." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "What I want to do today:\n\n* Take a more realistic example, based on my personal experience,\n* See cases where application of principles is not as obvious as in didactic examples,\n* Show how understanding the \"spirit\" of the principles is the most important, to be able to adapt them to real problems." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Words of caution:\n* Examples related to my **personal experience**, not as universal as didactic examples.\n* Code architecture results from a series of **choices**. Some are quite clearly good or bad, others are more debatable... Maybe we will not agree on all choices. But the interesting part is to **discuss** them and improve our general **understanding** of the consequences of these choices, in order to make more **educated decisions**.\n* This session will be much more interesting if it is **interactive**. Please share your ideas!" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T06:48:03.824058Z", | |
| "end_time": "2022-04-12T06:48:03.832059Z" | |
| }, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Our case study: design a package to implement **voting rules**.\n \nBased on my experience to code Whalrus (Which Alternative Represents Us): https://github.com/francois-durand/whalrus.\n\n", | |
| "attachments": { | |
| "image.png": { | |
| "image/png": "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AayUUuccAR97/1dePmPi3Y5vkO41KQ1gpZS6CAj4xPu/8rgL5s65lroMKWUawEopdSEQ8JH3fy3yCZiIIvERSMvQKdLTkJRS6qJIatGe2RBPQaZZ7ms3OD3aA1ZKqQuEgLve/2/xXxDldCJ0qrQHrJRSFwsBd700IBuiu2GlSQNYKaUuoi+8Vp9TpiVopZRSKgUawEoppVQKNICVUkqpFGgAK6WUUinQAFZKKaVSoAGslFJKpUADWCmllEqBBrBSSimVAg1gpZRSKgUawEoppVQKNICVUkqpFGgAK6WUUinQAFZKKaVSoAGslFJKpUADWCmllEqBBrBSSimVAg1gpZRSKgUawEoppVQKNICVUkqpFGgAK6WUUinQAFZKKaVSoAGslFJKpUADWCmllEqBBrBSSimVAg1gpZRSKgUawEoppVQKNICVUkqpFGgAK6WUUinQAFZKKaVSoAGslFJKpUADWCmllEqBBrBSSimVAg1gpZRSKgUawEoppVQKNICVUkqpFNi9z4QQEwlRilejlFJKnVtEsu93nQCOvUelMrNTklY7latSSimlzjfvY7RaIp0U7gTw2MTE0Ge/nfzgl9779K5NKaWUOre8943xcReGyW87AfxHf/zHU9PTztqj/6JSSimluici/AevXZqaSn5Le31hpZRSSvWNzoJWSimlUvD/ARN9npJrP62pAAAAAElFTkSuQmCC" | |
| } | |
| } | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "slide" | |
| } | |
| }, | |
| "id": "c6a76dac", | |
| "cell_type": "markdown", | |
| "source": "## Ballots" | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "7ca41e57", | |
| "cell_type": "markdown", | |
| "source": "Several kinds of ballots:\n\n* (Possibly weak) order: $d \\sim b > a > c$.\n* Linear order: $d > b > a > c$.\n* Grades: $d: 10, b: 7, a: 1, c: 0$.\n* Maybe we will add other kinds of ballots during the life of the project." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "slide" | |
| } | |
| }, | |
| "id": "c83d613a", | |
| "cell_type": "markdown", | |
| "source": "### Classes or not?" | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "196c9962", | |
| "cell_type": "markdown", | |
| "source": "**Option 1:** no classes.\n \n* (Possibly weak) order: ``[{'d', 'b'}, 'a', 'c']``.\n* Linear order: ``['d', 'b', 'a', 'c']``.\n* Grades: ``{'d': 10, 'b': 7, 'a': 1, 'c': 0}``.\n\n**Option 2:** with classes BallotOrder, BallotLinearOrder, BallotGrades..." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T07:40:19.612860Z", | |
| "end_time": "2022-04-12T07:40:19.632861Z" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Which one would you recommend? Why?\n\n* ...\n* ...\n* ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "f4485064", | |
| "cell_type": "markdown", | |
| "source": "Consider option 1 (no class). You will probably add functions to display ballots, extract the top candidates, etc." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:16:22.435370Z", | |
| "end_time": "2022-04-12T08:16:22.442370Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "6c2d4821", | |
| "cell_type": "code", | |
| "source": "def ballot_to_str(ballot):\n if isinstance(ballot, dict):\n return ', '.join([\n f\"Candidate {k}: grade {v}\"\n for k, v in ballot.items()\n ])\n if isinstance(ballot, list):\n return ' > '.join([\n ' ~ '.join([str(c) for c in sorted(x)]) if isinstance(x, set) else str(x)\n for x in ballot\n ])\n raise NotImplementedError", | |
| "execution_count": 1, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:16:22.775857Z", | |
| "end_time": "2022-04-12T08:16:22.797570Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "4829176c", | |
| "cell_type": "code", | |
| "source": "ballot_to_str(ballot={'d': 10, 'b': 7, 'a': 1, 'c': 0})", | |
| "execution_count": 2, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 2, | |
| "data": { | |
| "text/plain": "'Candidate d: grade 10, Candidate b: grade 7, Candidate a: grade 1, Candidate c: grade 0'" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:16:23.017049Z", | |
| "end_time": "2022-04-12T08:16:23.031150Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "ec7b74d6", | |
| "cell_type": "code", | |
| "source": "ballot_to_str(ballot=[{'d', 'b'}, 'a', 'c'])", | |
| "execution_count": 3, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 3, | |
| "data": { | |
| "text/plain": "'b ~ d > a > c'" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:16:26.571461Z", | |
| "end_time": "2022-04-12T08:16:26.576473Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "81e644e9", | |
| "cell_type": "code", | |
| "source": "def top_candidates(ballot):\n if isinstance(ballot, dict):\n max_grade = max(ballot.values())\n return {k for k, v in ballot.items() if v == max_grade}\n if isinstance(ballot, list):\n top_equivalence_class = ballot[0]\n return top_equivalence_class if isinstance(top_equivalence_class, set) else {top_equivalence_class}\n raise NotImplementedError", | |
| "execution_count": 4, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:16:26.803078Z", | |
| "end_time": "2022-04-12T08:16:26.809066Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "0ad87759", | |
| "cell_type": "code", | |
| "source": "ballot={'d': 10, 'b': 7, 'a': 1, 'c': 0}\ntop_candidates(ballot)", | |
| "execution_count": 5, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 5, | |
| "data": { | |
| "text/plain": "{'d'}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:16:27.177978Z", | |
| "end_time": "2022-04-12T08:16:27.182991Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "e8d0dbdd", | |
| "cell_type": "code", | |
| "source": "ballot=[{'d', 'b'}, 'a', 'c']\ntop_candidates(ballot)", | |
| "execution_count": 6, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 6, | |
| "data": { | |
| "text/plain": "{'b', 'd'}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "4982f277", | |
| "cell_type": "markdown", | |
| "source": "Drawbacks: your ideas?\n\n* ...\n* ...\n* ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Drawbacks (continued):\n \n* Later in the project, if we **add another type of ballot**, we will need to do a \"treasure hunt\" to find all the functions that need to be updated: `ballot_to_str`, `top_candidates`, etc. $\\Rightarrow$ not convenient and prone to mistakes." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Drawbacks (continued):\n \n* Sometimes, the type of ballot may not be so **easily recognizable**. Consider the following ones:\n\n * **Verbal evaluations:** $d \\to \\text{Excellent}, b \\to \\text{Good}, a \\to \\text{Fair}, c \\to \\text{Poor}$. Naturally represented by a dictionary, so we may need an intricate \"if\" clause to distinguish it from a ballot with grades.\n * **Partial order from the top:** $d > b > \\text{others}$ (not meaning that others are equivalent, like in a weak order, but simply that the voter did not want to rank them). Or **partial order from the bottom:** $c < a < \\text{others}$ (same remark). These ballots are naturally represented by lists, but not with the same meaning, so it will be difficult to treat them correctly in the above functions!" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T07:12:43.477049Z", | |
| "end_time": "2022-04-12T07:12:43.484052Z" | |
| }, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Drawbacks (continued):\n\n \n* Imagine that I want to apply my code to a case where **candidates are not strings**, but sets... Consider preferences like $\\{a, c\\} > \\{a, b\\} > \\{b, c\\}$, represented by ``[{'a', 'c'}, {'a', 'b'}, {'b', 'c'}]``. My code will fail because it will think it means $a \\sim c > a \\sim b > b \\sim c$, which makes no sense!" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:01.318949Z", | |
| "end_time": "2022-04-12T08:17:01.328958Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "cell_type": "code", | |
| "source": "ballot_to_str([{'a', 'c'}, {'a', 'b'}, {'b', 'c'}])", | |
| "execution_count": 7, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 7, | |
| "data": { | |
| "text/plain": "'a ~ c > a ~ b > b ~ c'" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "5e7ec17f", | |
| "cell_type": "markdown", | |
| "source": "All these problems are easily solved if you choose the option with **classes**.\n\nNow, let us see how to do that exactly!" | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "slide" | |
| } | |
| }, | |
| "id": "429c71cc", | |
| "cell_type": "markdown", | |
| "source": "### Who is a subclass of who?" | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "ffcad7f3", | |
| "cell_type": "markdown", | |
| "source": "For the moment, just consider **BallotGrades** and **BallotOrder** (representing orders that may be weak).\n\nWhich one should be the parent class? The child class? Your arguments?\n\n* ...\n* ...\n* ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "9829b93c", | |
| "cell_type": "markdown", | |
| "source": "Some (more or less good) arguments...\n\n**Option 1:** Parent = BallotGrades, child = BallotOrder.\n\n* Internally, an order $d \\sim b > a > c$ can be represented by grades: ``{'d': 2, 'b': 2, 'a': 1, 'c': 0}``. Hence it can be a subclass of BallotGrades.\n* Ballots with an order give \"less information\" than grades, so it is a subset of ballots with grades, hence it should be a subclass.\n\n**Option 2:** Parent = BallotOrder, child = BallotGrades.\n\n* A ballot with grades $d \\to 10, b \\to 7, a \\to 1, c \\to 0$ naturally induces a (possibly weak) order $d > b > a > c$. Grades being more specific, they should be the child class.\n\nWhich one would you recommend? Why? ..." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "end_time": "2022-02-22T08:34:58.949124Z", | |
| "start_time": "2022-02-22T08:34:58.937164Z" | |
| }, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "eb377b5d", | |
| "cell_type": "markdown", | |
| "source": "Let us expand on option 1: parent = BallotGrades, child = BallotOrder." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:19.658121Z", | |
| "end_time": "2022-04-12T08:17:19.666131Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "33833623", | |
| "cell_type": "code", | |
| "source": "class BallotGrades:\n def __init__(self, d_candidate_grade):\n self.d_candidate_grade = d_candidate_grade\nclass BallotOrder(BallotGrades):\n def __init__(self, equivalence_classes):\n self.equivalence_classes = [\n equiv_class if isinstance(equiv_class, set) else {equiv_class}\n for equiv_class in equivalence_classes\n ]\n d_candidate_grade = {\n c: len(self.equivalence_classes) - i - 1\n for i, equiv_class in enumerate(self.equivalence_classes)\n for c in equiv_class\n }\n super().__init__(d_candidate_grade)", | |
| "execution_count": 8, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:19.932806Z", | |
| "end_time": "2022-04-12T08:17:19.945987Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "b8bc71b4", | |
| "cell_type": "code", | |
| "source": "ballot = BallotOrder([{'d', 'b'}, 'a', 'c'])\nballot.d_candidate_grade", | |
| "execution_count": 9, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 9, | |
| "data": { | |
| "text/plain": "{'b': 2, 'd': 2, 'a': 1, 'c': 0}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Now let us implement Range voting." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:21.805993Z", | |
| "end_time": "2022-04-12T08:17:21.812978Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "-" | |
| } | |
| }, | |
| "id": "ab444682", | |
| "cell_type": "code", | |
| "source": "from collections import Counter\ndef range_voting(ballots):\n scores = Counter()\n for ballot in ballots:\n scores.update(ballot.d_candidate_grade)\n return dict(scores)", | |
| "execution_count": 10, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:22.081799Z", | |
| "end_time": "2022-04-12T08:17:22.092796Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "c01a7395", | |
| "cell_type": "code", | |
| "source": "range_voting(ballots=[\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0}),\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0})\n])", | |
| "execution_count": 11, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 11, | |
| "data": { | |
| "text/plain": "{'d': 20, 'b': 14, 'a': 2, 'c': 0}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:22.330609Z", | |
| "end_time": "2022-04-12T08:17:22.341619Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "51941b36", | |
| "cell_type": "code", | |
| "source": "range_voting(ballots=[\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0}),\n BallotOrder([{'d', 'b'}, 'a', 'c'])\n])", | |
| "execution_count": 12, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 12, | |
| "data": { | |
| "text/plain": "{'d': 12, 'b': 9, 'a': 2, 'c': 0}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": "Is it reasonable? What should be the result in your opinion? ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "(Reminder from last slide:)" | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "-" | |
| }, | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:25.222712Z", | |
| "end_time": "2022-04-12T08:17:25.239703Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "cell_type": "code", | |
| "source": "range_voting(ballots=[\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0}),\n BallotOrder([{'d', 'b'}, 'a', 'c'])\n])", | |
| "execution_count": 13, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 13, | |
| "data": { | |
| "text/plain": "{'d': 12, 'b': 9, 'a': 2, 'c': 0}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": {}, | |
| "id": "e173ee1a", | |
| "cell_type": "markdown", | |
| "source": "This is **bad** because the result relies on an \"implementation detail\" of BallotOrder.\n\nThe code above would better **raise an error**." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "d921d516", | |
| "cell_type": "markdown", | |
| "source": "Now let us expand on option 2: parent = BallotOrder, child = BallotGrades." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:28.321769Z", | |
| "end_time": "2022-04-12T08:17:28.341029Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "a6182302", | |
| "cell_type": "code", | |
| "source": "class BallotOrder:\n def __init__(self, equivalence_classes):\n self.equivalence_classes = [\n equiv_class if isinstance(equiv_class, set) else {equiv_class}\n for equiv_class in equivalence_classes\n ]\n @property\n def top_candidate(self):\n if not self.equivalence_classes or len(self.equivalence_classes[0]) > 1:\n return None\n return list(self.equivalence_classes[0])[0]\nclass BallotGrades(BallotOrder):\n def __init__(self, d_candidate_grade):\n self.d_candidate_grade = d_candidate_grade\n self.equivalence_classes = [\n {k for k in self.d_candidate_grade.keys() if self.d_candidate_grade[k] == v}\n for v in sorted(set(self.d_candidate_grade.values()), reverse=True)\n ]", | |
| "execution_count": 14, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| }, | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T07:28:15.173426Z", | |
| "end_time": "2022-04-12T07:28:15.180435Z" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Now let us implement Plurality voting." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:30.745300Z", | |
| "end_time": "2022-04-12T08:17:30.749301Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "-" | |
| } | |
| }, | |
| "id": "56c9a2e2", | |
| "cell_type": "code", | |
| "source": "from collections import defaultdict\ndef plurality_voting(ballots):\n scores = defaultdict(int)\n for ballot in ballots:\n top_candidate = ballot.top_candidate\n if top_candidate is not None:\n scores[top_candidate] += 1\n return dict(scores)", | |
| "execution_count": 15, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:30.986814Z", | |
| "end_time": "2022-04-12T08:17:30.997813Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "-" | |
| } | |
| }, | |
| "id": "3b85d5cf", | |
| "cell_type": "code", | |
| "source": "plurality_voting([\n BallotOrder([{'a', 'b'}, 'c', 'd']),\n BallotOrder(['c', {'a', 'b'}, 'd']),\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0})\n])", | |
| "execution_count": 16, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 16, | |
| "data": { | |
| "text/plain": "{'c': 1, 'd': 1}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": {}, | |
| "id": "4b0a9b07", | |
| "cell_type": "markdown", | |
| "source": "Is is reasonable? What should be the result in your opinion? ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Let us test Range voting (we did not change the implementation):" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:33.124269Z", | |
| "end_time": "2022-04-12T08:17:33.208179Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "-" | |
| } | |
| }, | |
| "id": "74440f04", | |
| "cell_type": "code", | |
| "source": "range_voting(ballots=[\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0}),\n BallotOrder([{'d', 'b'}, 'a', 'c'])\n])", | |
| "execution_count": 17, | |
| "outputs": [ | |
| { | |
| "output_type": "error", | |
| "ename": "AttributeError", | |
| "evalue": "'BallotOrder' object has no attribute 'd_candidate_grade'", | |
| "traceback": [ | |
| "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", | |
| "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", | |
| "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_2932/1091531290.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m range_voting(ballots=[\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[0mBallotGrades\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m{\u001b[0m\u001b[1;34m'd'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m10\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'b'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m7\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'a'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'c'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mBallotOrder\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m{\u001b[0m\u001b[1;34m'd'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'b'\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'a'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'c'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m ])\n", | |
| "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_2932/2117891113.py\u001b[0m in \u001b[0;36mrange_voting\u001b[1;34m(ballots)\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mscores\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mCounter\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mballot\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mballots\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mscores\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mballot\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0md_candidate_grade\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 6\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mscores\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", | |
| "\u001b[1;31mAttributeError\u001b[0m: 'BallotOrder' object has no attribute 'd_candidate_grade'" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": {}, | |
| "id": "0db3bf78", | |
| "cell_type": "markdown", | |
| "source": "Is it reasonable? ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "122c7acd", | |
| "cell_type": "markdown", | |
| "source": "Remark: for implementation reasons, it can be useful to have a attribute ``_d_candidate_pseudograde`` in BallotOrder." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:41.377054Z", | |
| "end_time": "2022-04-12T08:17:41.387751Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "c2b3df68", | |
| "cell_type": "code", | |
| "source": "from functools import cached_property\nclass BallotOrder:\n def __init__(self, equivalence_classes):\n self.equivalence_classes = [\n equiv_class if isinstance(equiv_class, set) else {equiv_class}\n for equiv_class in equivalence_classes\n ]\n @cached_property\n def _d_candidate_pseudograde(self):\n return {\n c: len(self.equivalence_classes) - i - 1\n for i, equiv_class in enumerate(self.equivalence_classes)\n for c in equiv_class\n }\n def strictly_prefers(self, c, d):\n return self._d_candidate_pseudograde[c] > self._d_candidate_pseudograde[d]\nclass BallotGrades(BallotOrder):\n def __init__(self, d_candidate_grade):\n self.d_candidate_grade = d_candidate_grade\n self.equivalence_classes = [\n {k for k in self.d_candidate_grade.keys() if self.d_candidate_grade[k] == v}\n for v in sorted(set(self.d_candidate_grade.values()), reverse=True)\n ]\n @cached_property\n def _d_candidate_pseudograde(self):\n return self.d_candidate_grade", | |
| "execution_count": 18, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Examples that (indirectly) rely on these pseudogrades:" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:43.815351Z", | |
| "end_time": "2022-04-12T08:17:43.823349Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "-" | |
| } | |
| }, | |
| "id": "8ec2a71d", | |
| "cell_type": "code", | |
| "source": "ballot = BallotOrder([{'d', 'b'}, 'a', 'c'])\nballot.strictly_prefers('d', 'a')", | |
| "execution_count": 19, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 19, | |
| "data": { | |
| "text/plain": "True" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:44.015548Z", | |
| "end_time": "2022-04-12T08:17:44.025531Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "215f8783", | |
| "cell_type": "code", | |
| "source": "ballot = BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0})\nballot.strictly_prefers('d', 'a')", | |
| "execution_count": 20, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 20, | |
| "data": { | |
| "text/plain": "True" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "-" | |
| } | |
| }, | |
| "id": "20c521dd", | |
| "cell_type": "markdown", | |
| "source": "Remark that it is an \"implementation detail\", hence it is not part of the API." | |
| }, | |
| { | |
| "metadata": {}, | |
| "id": "9cc4fa97", | |
| "cell_type": "markdown", | |
| "source": "Final remark: actually, we could choose \"standard\" pseudogrades (like Borda scores), and make it part of the API. Instead of ``ballot._d_candidate_pseudograde``, we would then have ``ballot.d_candidate_borda_score`` (with no leading underscore ``_``)." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "slide" | |
| } | |
| }, | |
| "id": "88e11180", | |
| "cell_type": "markdown", | |
| "source": "### What about linear orders?" | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "506b09fe", | |
| "cell_type": "markdown", | |
| "source": "Reminder: parent class = BallotOrder, child class = BallotGrades. A **linear order** is a particular case of BallotOrder, where all equivalence classes are of size 1.\n\n* Should it be a subclass of BallotOrder?\n* Should it be a subclass of BallotGrades?\n* Other ideas of design?\n\nYour answers and ideas:\n\n* ...\n* ...\n* ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "75dd76d1", | |
| "cell_type": "markdown", | |
| "source": "**Option 1:** subclass of BallotOrder (and whether subclass of BallotGrades or not)." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:50.228515Z", | |
| "end_time": "2022-04-12T08:17:50.238524Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "f192ae44", | |
| "cell_type": "code", | |
| "source": "class BallotOrder:\n def __init__(self, equivalence_classes):\n self.equivalence_classes = [\n equiv_class if isinstance(equiv_class, set) else {equiv_class}\n for equiv_class in equivalence_classes\n ]\nclass BallotLinearOrder(BallotOrder):\n def __init__(self, ranking):\n self.ranking = ranking\n equivalence_classes = [{candidate} for candidate in ranking]\n super().__init__(equivalence_classes=equivalence_classes)", | |
| "execution_count": 21, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:50.440061Z", | |
| "end_time": "2022-04-12T08:17:50.454180Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "2c4ba886", | |
| "cell_type": "code", | |
| "source": "ballot = BallotLinearOrder(['d', 'b', 'a', 'c'])\nballot.equivalence_classes", | |
| "execution_count": 22, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 22, | |
| "data": { | |
| "text/plain": "[{'d'}, {'b'}, {'a'}, {'c'}]" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Let us implement a positional scoring rule (assigning weights to each rank):" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:52.913340Z", | |
| "end_time": "2022-04-12T08:17:52.920562Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "-" | |
| } | |
| }, | |
| "id": "2e89c548", | |
| "cell_type": "code", | |
| "source": "def positional_scoring_rule(ballots, weights):\n scores = defaultdict(int)\n for ballot in ballots:\n for candidate, weight in zip(ballot.ranking, weights):\n scores[candidate] += weight\n return dict(scores)", | |
| "execution_count": 23, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:53.095129Z", | |
| "end_time": "2022-04-12T08:17:53.106407Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "d6bc2402", | |
| "cell_type": "code", | |
| "source": "positional_scoring_rule(ballots=[\n BallotLinearOrder(['d', 'b', 'a', 'c']),\n BallotLinearOrder(['c', 'a', 'd', 'b'])\n], weights=[4, 2, 1, 0])", | |
| "execution_count": 24, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 24, | |
| "data": { | |
| "text/plain": "{'d': 5, 'b': 2, 'a': 3, 'c': 4}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": "Is it reasonable? What should be the result? ..." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:17:59.781213Z", | |
| "end_time": "2022-04-12T08:17:59.800208Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "f363e975", | |
| "cell_type": "code", | |
| "source": "positional_scoring_rule(ballots=[\n BallotLinearOrder(['d', 'b', 'a', 'c']),\n BallotOrder(['c', 'a', 'd', 'b'])\n], weights=[4, 2, 1, 0])", | |
| "execution_count": 25, | |
| "outputs": [ | |
| { | |
| "output_type": "error", | |
| "ename": "AttributeError", | |
| "evalue": "'BallotOrder' object has no attribute 'ranking'", | |
| "traceback": [ | |
| "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", | |
| "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", | |
| "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_2932/3280533472.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m positional_scoring_rule(ballots=[\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[0mBallotLinearOrder\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'd'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'b'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'a'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'c'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mBallotOrder\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'c'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'a'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'd'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'b'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m ], weights=[4, 2, 1, 0])\n", | |
| "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_2932/3415144726.py\u001b[0m in \u001b[0;36mpositional_scoring_rule\u001b[1;34m(ballots, weights)\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0mscores\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdefaultdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mint\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mballot\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mballots\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[1;32mfor\u001b[0m \u001b[0mcandidate\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mweight\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mballot\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mranking\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mweights\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[0mscores\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mcandidate\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m+=\u001b[0m \u001b[0mweight\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mscores\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", | |
| "\u001b[1;31mAttributeError\u001b[0m: 'BallotOrder' object has no attribute 'ranking'" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T07:46:42.590613Z", | |
| "end_time": "2022-04-12T07:46:42.600612Z" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Is it reasonable? What should be the result? ..." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:18:02.460668Z", | |
| "end_time": "2022-04-12T08:18:02.471664Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "fcfea548", | |
| "cell_type": "code", | |
| "source": "positional_scoring_rule(ballots=[\n BallotLinearOrder(['d', 'b', 'a', 'c']),\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0})\n], weights=[4, 2, 1, 0])", | |
| "execution_count": 26, | |
| "outputs": [ | |
| { | |
| "output_type": "error", | |
| "ename": "AttributeError", | |
| "evalue": "'BallotGrades' object has no attribute 'ranking'", | |
| "traceback": [ | |
| "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", | |
| "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", | |
| "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_2932/1625634434.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m positional_scoring_rule(ballots=[\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[0mBallotLinearOrder\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'd'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'b'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'a'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'c'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mBallotGrades\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m{\u001b[0m\u001b[1;34m'd'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m10\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'b'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m7\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'a'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'c'\u001b[0m\u001b[1;33m:\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m}\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m ], weights=[4, 2, 1, 0])\n", | |
| "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_2932/3415144726.py\u001b[0m in \u001b[0;36mpositional_scoring_rule\u001b[1;34m(ballots, weights)\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0mscores\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdefaultdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mint\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mballot\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mballots\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[1;32mfor\u001b[0m \u001b[0mcandidate\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mweight\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mballot\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mranking\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mweights\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[0mscores\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mcandidate\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m+=\u001b[0m \u001b[0mweight\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mscores\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", | |
| "\u001b[1;31mAttributeError\u001b[0m: 'BallotGrades' object has no attribute 'ranking'" | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T07:46:42.590613Z", | |
| "end_time": "2022-04-12T07:46:42.600612Z" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Is it reasonable? What should be the result? ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "How to solve the problems we just saw? Your ideas:\n\n* ...\n* ...\n* ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "c4a94caa", | |
| "cell_type": "markdown", | |
| "source": "**Option 2:** being a strict linear order is just something that we can test about a BallotOrder." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:18:18.108424Z", | |
| "end_time": "2022-04-12T08:18:18.123373Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "e652a134", | |
| "cell_type": "code", | |
| "source": "class BallotOrder:\n def __init__(self, equivalence_classes):\n self.equivalence_classes = [\n equiv_class if isinstance(equiv_class, set) else {equiv_class}\n for equiv_class in equivalence_classes\n ]\n @property\n def is_strict(self):\n return all([len(equiv_class) == 1 for equiv_class in self.equivalence_classes])\n @property\n def ranking(self):\n if not self.is_strict:\n raise ValueError('The order is not strict.')\n return [list(equiv_class)[0] for equiv_class in self.equivalence_classes]\nclass BallotGrades(BallotOrder):\n def __init__(self, d_candidate_grade):\n self.d_candidate_grade = d_candidate_grade\n self.equivalence_classes = [\n {k for k in self.d_candidate_grade.keys() if self.d_candidate_grade[k] == v}\n for v in sorted(set(self.d_candidate_grade.values()), reverse=True)\n ]", | |
| "execution_count": 27, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Example of application:" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:18:20.465012Z", | |
| "end_time": "2022-04-12T08:18:20.484008Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "9f2a7f3e", | |
| "cell_type": "code", | |
| "source": "positional_scoring_rule(ballots=[\n BallotOrder(['c', 'a', 'd', 'b']),\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0})\n], weights=[4, 2, 1, 0])", | |
| "execution_count": 28, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 28, | |
| "data": { | |
| "text/plain": "{'c': 4, 'a': 3, 'd': 5, 'b': 2}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T07:46:42.590613Z", | |
| "end_time": "2022-04-12T07:46:42.600612Z" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Is it reasonable? What should be the result? ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "slide" | |
| } | |
| }, | |
| "id": "bfd64d45", | |
| "cell_type": "markdown", | |
| "source": "## Voting rules" | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "What kind of architecture would you consider for the voting rules? Your ideas:\n\n* ...\n* ...\n* ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "9dd1b98e", | |
| "cell_type": "markdown", | |
| "source": "**Option 1:** design the voting rules as functions.\n\n* Input: a list of (relevant) ballots.\n* Output: Winner? Scores? Ranking of all the candidates?\n\nWhat do you think?\n\n* ...\n* ...\n* ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "b59948c5", | |
| "cell_type": "markdown", | |
| "source": "**Suboption 1:** return the scores, since it conveys more information." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:18:29.682094Z", | |
| "end_time": "2022-04-12T08:18:29.693086Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "9c8c519c", | |
| "cell_type": "code", | |
| "source": "def some_scoring_rule(ballots):\n scores = dict()\n # Some code...\n return scores\ndef winner(scores):\n # We break ties using the alphabetical order on candidates.\n return sorted(candidate for candidate, score in scores.items() if score == max(scores.values()))[0]\ndef ranking(scores):\n # For the sake of simplicity, we do not care about ties here.\n return sorted(scores.keys(), key=scores.__getitem__, reverse=True)", | |
| "execution_count": 29, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:18:29.890184Z", | |
| "end_time": "2022-04-12T08:18:29.894182Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "a1e4fc07", | |
| "cell_type": "code", | |
| "source": "scores = {'a': 4, 'b': 2, 'c': 12, 'd': 12}\nwinner(scores)", | |
| "execution_count": 30, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 30, | |
| "data": { | |
| "text/plain": "'c'" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:18:30.075433Z", | |
| "end_time": "2022-04-12T08:18:30.080438Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "222817ee", | |
| "cell_type": "code", | |
| "source": "ranking(scores)", | |
| "execution_count": 31, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 31, | |
| "data": { | |
| "text/plain": "['c', 'd', 'a', 'b']" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "924858c5", | |
| "cell_type": "markdown", | |
| "source": "But not all voting rules rely on a score! Output the winner in that case?" | |
| }, | |
| { | |
| "metadata": { | |
| "trusted": true, | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:18:46.097569Z", | |
| "end_time": "2022-04-12T08:18:46.105569Z" | |
| } | |
| }, | |
| "id": "82d26d7f", | |
| "cell_type": "code", | |
| "source": "def some_non_scoring_rule(ballots):\n winner = None\n # Compute the winner...\n return winner", | |
| "execution_count": 32, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": {}, | |
| "id": "087e0f7c", | |
| "cell_type": "markdown", | |
| "source": "$\\Rightarrow$ inconsistency in the code." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "fbfc9d18", | |
| "cell_type": "markdown", | |
| "source": "**Suboption 2:** return the winner, because a voting rule must always be able to do that." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:18:50.771476Z", | |
| "end_time": "2022-04-12T08:18:50.777467Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "130c547d", | |
| "cell_type": "code", | |
| "source": "def some_voting_rule(ballots):\n winner = None\n # 1. Compute the scores, since we need them anyway.\n # 2. Deduce the winner...\n return winner", | |
| "execution_count": 33, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": {}, | |
| "id": "be47df14", | |
| "cell_type": "markdown", | |
| "source": "This is stupid, because we do not have access to the scores!" | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "843a5776", | |
| "cell_type": "markdown", | |
| "source": "**Suboption 3:** return a dictionary (or a tuple), with all the relevant information." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:02.500889Z", | |
| "end_time": "2022-04-12T08:19:02.511830Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "88552ed7", | |
| "cell_type": "code", | |
| "source": "def some_voting_rule(ballots):\n scores = dict()\n winner = None\n # 1. Compute the scores, since we need them anyway.\n # 2. Deduce the winner...\n return {'scores': scores, 'winner': winner}", | |
| "execution_count": 34, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "trusted": true, | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:02.700558Z", | |
| "end_time": "2022-04-12T08:19:02.714010Z" | |
| } | |
| }, | |
| "id": "0122283a", | |
| "cell_type": "code", | |
| "source": "def some_non_scoring_rule(ballots):\n winner = None\n # Compute the winner\n return {'winner': winner}", | |
| "execution_count": 35, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": {}, | |
| "id": "f22689ae", | |
| "cell_type": "markdown", | |
| "source": "This is less bad, but not ideal: for example, we lose auto-completion, there are risks of typos, etc. Your ideas of drawbacks?\n\n* ...\n* ...\n* ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "d1a31da2", | |
| "cell_type": "markdown", | |
| "source": "**Suboption 4:** return an object of a custom class, with all the relevant information." | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:07.236945Z", | |
| "end_time": "2022-04-12T08:19:07.245946Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "28d860fb", | |
| "cell_type": "code", | |
| "source": "class ElectionResult:\n def __init__(self, winner=None, scores=None, ranking=None):\n self._winner = winner\n self._scores = scores\n self._ranking = ranking\n @cached_property\n def scores(self):\n if self._scores is not None: return self._scores\n raise ValueError('No scores')\n @cached_property\n def ranking(self):\n if self._ranking is not None: return self._ranking\n if self._scores is not None:\n return sorted(self._scores.keys(), key=self._scores.__getitem__, reverse=True)\n raise ValueError('No ranking')\n @cached_property\n def winner(self):\n if self._winner is not None: return self._winner\n if self._ranking is not None: return self._ranking[0]\n if self._scores is not None:\n return sorted(\n candidate \n for candidate, score in self._scores.items() \n if score == max(self._scores.values())\n )[0]\n raise ValueError('No winner')", | |
| "execution_count": 36, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| }, | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T07:59:59.113958Z", | |
| "end_time": "2022-04-12T07:59:59.128954Z" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Let us check that it has the desired behavior:" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:11.307812Z", | |
| "end_time": "2022-04-12T08:19:11.316747Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "-" | |
| } | |
| }, | |
| "id": "8ebb5254", | |
| "cell_type": "code", | |
| "source": "result = ElectionResult(scores={'a': 4, 'b': 2, 'c': 12, 'd': 12})\nprint(f\"{result.scores=}\")\nprint(f\"{result.ranking=}\")\nprint(f\"{result.winner=}\")", | |
| "execution_count": 37, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "text": "result.scores={'a': 4, 'b': 2, 'c': 12, 'd': 12}\nresult.ranking=['c', 'd', 'a', 'b']\nresult.winner='c'\n", | |
| "name": "stdout" | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:11.480561Z", | |
| "end_time": "2022-04-12T08:19:11.489472Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "acfb8695", | |
| "cell_type": "code", | |
| "source": "result = ElectionResult(ranking=['c', 'd', 'a', 'b'])\nprint(f\"{result.ranking=}\")\nprint(f\"{result.winner=}\")", | |
| "execution_count": 38, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "text": "result.ranking=['c', 'd', 'a', 'b']\nresult.winner='c'\n", | |
| "name": "stdout" | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Let us examine the implementation of voting rules with this design:" | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:15.170040Z", | |
| "end_time": "2022-04-12T08:19:15.183034Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "55e59dbe", | |
| "cell_type": "code", | |
| "source": "def some_scoring_rule(ballots):\n scores = dict()\n # Compute the scores here...\n return ElectionResult(scores=scores)", | |
| "execution_count": 39, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:15.360897Z", | |
| "end_time": "2022-04-12T08:19:15.370091Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "bb30663b", | |
| "cell_type": "code", | |
| "source": "def some_non_scoring_rule(ballots):\n ranking = []\n # Compute the ranking here...\n return ElectionResult(ranking=ranking)", | |
| "execution_count": 40, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "Pros and cons of the design with ElectionResult? Your ideas:\n\n* ...\n* ...\n* ..." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "f78df191", | |
| "cell_type": "markdown", | |
| "source": "**Option 2:** classes for algorithms (my personal preference).\n\nCf. my talk in the Python Workshop: [\"Some Architectural Considerations for Algorithms in Python\"](https://www.lincs.fr/events/some-architectural-considerations-for-algorithms-in-python/)." | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| }, | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:22.772696Z", | |
| "end_time": "2022-04-12T08:19:22.786986Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "cell_type": "code", | |
| "source": "def _cache(f):\n \"\"\"Auxiliary decorator used by :meth:`cached_property`.\n\n Parameters\n ----------\n f : callable\n A method with no argument (except ``self``).\n\n Returns\n -------\n callable\n The same function, but with a `caching` behavior.\n \"\"\"\n name = f.__name__\n\n def _f(*args):\n try:\n return args[0]._cached_properties[name]\n except (KeyError, AttributeError):\n value = f(*args)\n try:\n # Not stored in cache\n args[0]._cached_properties[name] = value\n except AttributeError:\n # Cache does not even exist\n args[0]._cached_properties = {name: value}\n return value\n _f.__doc__ = f.__doc__\n return _f", | |
| "execution_count": 41, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| }, | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:24.337314Z", | |
| "end_time": "2022-04-12T08:19:24.348596Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "cell_type": "code", | |
| "source": "def cached_property(f):\n \"\"\"Decorator used in replacement of ``@property`` to put the value in cache automatically.\n\n Notes\n -----\n The first time the attribute is used, it is computed on-demand and put in cache. Later accesses to the\n attributes will use the cached value.\n\n Examples\n --------\n Cf. :class:`DeleteCacheMixin`.\n \"\"\"\n return property(_cache(f))", | |
| "execution_count": 42, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:25.893433Z", | |
| "end_time": "2022-04-12T08:19:25.906439Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "d1730dbf", | |
| "cell_type": "code", | |
| "source": "class DeleteCacheMixin:\n \"\"\"Mixin used to delete cached properties.\n\n Notes\n -----\n Cf. decorator :meth:`cached_property`.\n\n Examples\n --------\n >>> class Example(DeleteCacheMixin):\n ... @cached_property\n ... def x(self):\n ... print('Big computation...')\n ... return 6 * 7\n >>> a = Example()\n >>> a.x\n Big computation...\n 42\n >>> a.x\n 42\n >>> a.delete_cache()\n >>> a.x\n Big computation...\n 42\n \"\"\"\n def delete_cache(self) -> None:\n self._cached_properties = dict()", | |
| "execution_count": 43, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:28.367118Z", | |
| "end_time": "2022-04-12T08:19:28.380581Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "71a9b81c", | |
| "cell_type": "code", | |
| "source": "class Rule(DeleteCacheMixin):\n def __call__(self, ballots):\n self.delete_cache()\n self.ballots_ = ballots\n return self\n @cached_property\n def winner_(self):\n raise NotImplementedError", | |
| "execution_count": 44, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:29.780732Z", | |
| "end_time": "2022-04-12T08:19:29.794733Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "c022bb0a", | |
| "cell_type": "code", | |
| "source": "class RuleScore(Rule):\n @cached_property\n def scores_(self):\n raise NotImplementedError\n @cached_property\n def winner_(self):\n return sorted(\n candidate \n for candidate, score in self.scores_.items() \n if score == max(self.scores_.values())\n )[0]", | |
| "execution_count": 45, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:31.656917Z", | |
| "end_time": "2022-04-12T08:19:31.665212Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "9ae8b0ab", | |
| "cell_type": "code", | |
| "source": "class RuleRangeVoting(RuleScore):\n @cached_property\n def scores_(self):\n scores = Counter()\n for ballot in self.ballots_:\n scores.update(ballot.d_candidate_grade)\n return dict(scores)", | |
| "execution_count": 46, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:33.540201Z", | |
| "end_time": "2022-04-12T08:19:33.558763Z" | |
| }, | |
| "trusted": true, | |
| "slideshow": { | |
| "slide_type": "subslide" | |
| } | |
| }, | |
| "id": "354269a6", | |
| "cell_type": "code", | |
| "source": "election = RuleRangeVoting()(ballots=[\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0}),\n BallotGrades({'d': 10, 'b': 7, 'a': 1, 'c': 0})\n])", | |
| "execution_count": 48, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:33.783335Z", | |
| "end_time": "2022-04-12T08:19:33.791676Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "a32f4cea", | |
| "cell_type": "code", | |
| "source": "election.scores_", | |
| "execution_count": 49, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 49, | |
| "data": { | |
| "text/plain": "{'d': 20, 'b': 14, 'a': 2, 'c': 0}" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "ExecuteTime": { | |
| "start_time": "2022-04-12T08:19:34.233924Z", | |
| "end_time": "2022-04-12T08:19:34.238953Z" | |
| }, | |
| "trusted": true | |
| }, | |
| "id": "bdb8beba", | |
| "cell_type": "code", | |
| "source": "election.winner_", | |
| "execution_count": 50, | |
| "outputs": [ | |
| { | |
| "output_type": "execute_result", | |
| "execution_count": 50, | |
| "data": { | |
| "text/plain": "'d'" | |
| }, | |
| "metadata": {} | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "slideshow": { | |
| "slide_type": "slide" | |
| } | |
| }, | |
| "cell_type": "markdown", | |
| "source": "## Conclusion" | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": "Thank you for your attention!" | |
| }, | |
| { | |
| "metadata": {}, | |
| "cell_type": "markdown", | |
| "source": "Questions?" | |
| } | |
| ], | |
| "metadata": { | |
| "kernelspec": { | |
| "name": "python3", | |
| "display_name": "Python 3 (ipykernel)", | |
| "language": "python" | |
| }, | |
| "language_info": { | |
| "name": "python", | |
| "version": "3.9.7", | |
| "mimetype": "text/x-python", | |
| "codemirror_mode": { | |
| "name": "ipython", | |
| "version": 3 | |
| }, | |
| "pygments_lexer": "ipython3", | |
| "nbconvert_exporter": "python", | |
| "file_extension": ".py" | |
| }, | |
| "toc": { | |
| "nav_menu": {}, | |
| "number_sections": true, | |
| "sideBar": true, | |
| "skip_h1_title": true, | |
| "base_numbering": 1, | |
| "title_cell": "Table of Contents", | |
| "title_sidebar": "Contents", | |
| "toc_cell": false, | |
| "toc_position": { | |
| "height": "calc(100% - 180px)", | |
| "width": "279.263px", | |
| "left": "10px", | |
| "top": "150px" | |
| }, | |
| "toc_section_display": true, | |
| "toc_window_display": false | |
| }, | |
| "celltoolbar": "Diaporama", | |
| "gist": { | |
| "id": "", | |
| "data": { | |
| "description": "architecture_in_oop.ipynb", | |
| "public": true | |
| } | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 5 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment