From ce916ddcccb48cae27603311807a97f90cf923f0 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 28 Jan 2020 17:26:39 +0100 Subject: [PATCH 01/66] Use element now. --- src/Main.elm | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/Main.elm b/src/Main.elm index dfd0d3c8..e99ee319 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -5,7 +5,7 @@ import Browser import Element exposing (Element) import Http -main = Browser.document { init = init +main = Browser.element { init = init , view = view , update = update , subscription = subscription @@ -30,7 +30,7 @@ type Page | Feed (List Message) | Vote Election -type alias Model = { user : Maybe User +type alias Model = { user : User , page : Page , readings : List Message , writings : List Message @@ -46,17 +46,32 @@ init _ = ( { user = Nothing -- UPDATE type Msg - = Read Message + = Writes Writing_Msg + | To_Feed + | Read Message | Write Message - | Feed - | Write_Change_Title String - | Write_Change_Content String - | Login_Username_Change String - | Login_Password_Change String + +type Writing_Msg + = Change Field String + | Publish + +type Field + = Title + | Content + | To update : Msg -> Model -> ( Model, Cmd Msg ) update msg model - = case model.user of - Just user -> case msg of - Read msg -> - Nothing -> + = case model.page of + Writing message -> + case msg of + Writes writing_msg -> + case writing_msg of + Change field new -> + let new_message = case field of + Title -> { message | title = new } + Content -> { message | content = new } + To -> { message | to = new } + in ({ model | page = Writing new_message }, Cmd.none) + Publish -> (model, publish message) + To_Feed -> ( { model | page = Feed model.feed }) From 748aa1faed407139b15e473183da0f301b6e0b04 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 28 Jan 2020 17:39:44 +0100 Subject: [PATCH 02/66] Published and Saved Msg defined, that'll be send, once a Messages has been published or saved. --- src/Main.elm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Main.elm b/src/Main.elm index e99ee319..d9d3f88d 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -35,6 +35,7 @@ type alias Model = { user : User , readings : List Message , writings : List Message , feed : List Message + , notices : List String -- Short Messages for the user. } init : flags -> ( Model, Cmd Msg) @@ -50,6 +51,8 @@ type Msg | To_Feed | Read Message | Write Message + | Saved Message + | Published Message type Writing_Msg = Change Field String @@ -73,5 +76,5 @@ update msg model Content -> { message | content = new } To -> { message | to = new } in ({ model | page = Writing new_message }, Cmd.none) - Publish -> (model, publish message) - To_Feed -> ( { model | page = Feed model.feed }) + Publish -> (model, publish message <| Published message) + To_Feed -> ( { model | page = Feed model.feed, writings = remove_duplicates <| model.writings ++ message }, save message (Saved message)) From 374e7c0db5083bdf5f2557291bf15658cff71302 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 28 Jan 2020 18:09:05 +0100 Subject: [PATCH 03/66] Branches fro Read, Write, Saved and Published in update. --- src/Main.elm | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Main.elm b/src/Main.elm index d9d3f88d..e79e221d 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -38,6 +38,9 @@ type alias Model = { user : User , notices : List String -- Short Messages for the user. } +save_page : Model -> Model +save_page model = + case model.page of init : flags -> ( Model, Cmd Msg) init _ = ( { user = Nothing , readings = [] @@ -65,16 +68,21 @@ type Field update : Msg -> Model -> ( Model, Cmd Msg ) update msg model - = case model.page of - Writing message -> - case msg of - Writes writing_msg -> - case writing_msg of - Change field new -> - let new_message = case field of - Title -> { message | title = new } - Content -> { message | content = new } - To -> { message | to = new } - in ({ model | page = Writing new_message }, Cmd.none) - Publish -> (model, publish message <| Published message) - To_Feed -> ( { model | page = Feed model.feed, writings = remove_duplicates <| model.writings ++ message }, save message (Saved message)) + = case msg of + Writes writing_msg -> + case model.page of + Writing message -> + case writing_msg of + Change field new -> + let new_message = case field of + Title -> { message | title = new } + Content -> { message | content = new } + To -> { message | to = new } + in ({ (save_page model) | page = Writing new_message }, Cmd.none) + Publish -> (model, publish message <| Published message) + _ -> ( model, Cmd.none ) + To_Feed -> ( { (save_page model) | page = Feed model.feed }, save model.message (Saved model.message)) + Read other_message -> ( { (save_page model) | page = Reading other_message }, save message (Saved model.message)) + Write other_message -> ( { (save_page model) | page = Writing other_message }, save message (Saved message)) + Saved message -> ( { model | notices = ("Saved: " ++ message.title)::model.notices }, Cmd.none) + Published message -> ( { model | notices = ("Saved: " ++ message.title)::model.notices }, Cmd.none) From 2eb2020a6283bd9884ad228a49fd138ead82c6a2 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 28 Jan 2020 18:18:30 +0100 Subject: [PATCH 04/66] Created save_page function This function saves the content of page in the respective cache, if it isn't already a member of those caches. --- src/Main.elm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Main.elm b/src/Main.elm index e79e221d..848d4412 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -35,12 +35,17 @@ type alias Model = { user : User , readings : List Message , writings : List Message , feed : List Message + , elections : List Election , notices : List String -- Short Messages for the user. } save_page : Model -> Model save_page model = case model.page of + Reading message -> { model | readings = add_if_not_member message model.readings } + Writing message -> { model | writings = add_if_not_member message model.writings } + Feed messages -> { model | feed = remove_duplicates <| messages ++ model.feed } + Vote election -> { model | elections = add_if_not_member election} init : flags -> ( Model, Cmd Msg) init _ = ( { user = Nothing , readings = [] From 5a26b8746a3139186fbde5e30a485ef9830e9795 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 28 Jan 2020 18:22:22 +0100 Subject: [PATCH 05/66] Added utility functions: add_if_not_member and remove_duplicates --- src/Main.elm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Main.elm b/src/Main.elm index 848d4412..34086976 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -39,6 +39,15 @@ type alias Model = { user : User , notices : List String -- Short Messages for the user. } +add_if_not_member : a -> List a -> List a +add_if_not_member element list + = if List.member element list + then list + else element:list + +remove_duplicates : List a -> List a +remove_duplicates list = List.foldl add_if_not_member [] list + save_page : Model -> Model save_page model = case model.page of From 6b72e0096e7f130f2e89bd661a64c8cddcc76b79 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 28 Jan 2020 18:30:46 +0100 Subject: [PATCH 06/66] Use save_page in update --- src/Main.elm | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Main.elm b/src/Main.elm index 34086976..57db9249 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -43,7 +43,7 @@ add_if_not_member : a -> List a -> List a add_if_not_member element list = if List.member element list then list - else element:list + else element::list remove_duplicates : List a -> List a remove_duplicates list = List.foldl add_if_not_member [] list @@ -54,7 +54,8 @@ save_page model = Reading message -> { model | readings = add_if_not_member message model.readings } Writing message -> { model | writings = add_if_not_member message model.writings } Feed messages -> { model | feed = remove_duplicates <| messages ++ model.feed } - Vote election -> { model | elections = add_if_not_member election} + Vote election -> { model | elections = add_if_not_member election } + init : flags -> ( Model, Cmd Msg) init _ = ( { user = Nothing , readings = [] @@ -82,9 +83,10 @@ type Field update : Msg -> Model -> ( Model, Cmd Msg ) update msg model - = case msg of + = let saved_model = save_page model + in case msg of Writes writing_msg -> - case model.page of + case saved_model.page of Writing message -> case writing_msg of Change field new -> @@ -92,11 +94,11 @@ update msg model Title -> { message | title = new } Content -> { message | content = new } To -> { message | to = new } - in ({ (save_page model) | page = Writing new_message }, Cmd.none) + in ({ (save_page saved_model) | page = Writing new_message }, Cmd.none) Publish -> (model, publish message <| Published message) - _ -> ( model, Cmd.none ) - To_Feed -> ( { (save_page model) | page = Feed model.feed }, save model.message (Saved model.message)) - Read other_message -> ( { (save_page model) | page = Reading other_message }, save message (Saved model.message)) - Write other_message -> ( { (save_page model) | page = Writing other_message }, save message (Saved message)) + _ -> ( saved_model, Cmd.none ) + To_Feed -> ( { saved_model | page = Feed saved_model.feed }, Cmd.none) + Read other_message -> ( { saved_model | page = Reading other_message }, Cmd.none) + Write other_message -> ( { saved_model | page = Writing other_message }, Cmd.none) Saved message -> ( { model | notices = ("Saved: " ++ message.title)::model.notices }, Cmd.none) Published message -> ( { model | notices = ("Saved: " ++ message.title)::model.notices }, Cmd.none) From 1cf49673f852feacf92296c63e91de84c93b1466 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 28 Jan 2020 18:33:07 +0100 Subject: [PATCH 07/66] Added type alias Election. --- src/Main.elm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Main.elm b/src/Main.elm index 57db9249..02cf37d8 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -24,6 +24,8 @@ type alias Message , content : String } +type alias Election = { options : List String } + type Page = Reading Message | Writing Message @@ -94,7 +96,7 @@ update msg model Title -> { message | title = new } Content -> { message | content = new } To -> { message | to = new } - in ({ (save_page saved_model) | page = Writing new_message }, Cmd.none) + in ({ saved_model | page = Writing new_message }, Cmd.none) Publish -> (model, publish message <| Published message) _ -> ( saved_model, Cmd.none ) To_Feed -> ( { saved_model | page = Feed saved_model.feed }, Cmd.none) From 5b3b67e0bed72e68e4443f392ebe54ebea9a6bb6 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 28 Jan 2020 18:50:55 +0100 Subject: [PATCH 08/66] Added Messages.elm with decoder for Message type. And moved Message type to Messages.elm --- Messages.elm | 26 ++++++++++++++++++++++++++ src/Main.elm | 7 ------- 2 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 Messages.elm diff --git a/Messages.elm b/Messages.elm new file mode 100644 index 00000000..0460c981 --- /dev/null +++ b/Messages.elm @@ -0,0 +1,26 @@ +module Messages exposing ( message_decoder + , message_encoder + , publish + , save + , request_message + , request_messages + ) + +import Json.Decode as D +import Json.Encode as E + +type alias Message + = { from : User + , to : User + , title : String + , content : String + } + + +decoder : D.Decoder Message +decoder = + D.map4 Message + (D.field "from" D.string) + (D.field "to" <| D.list D.string) + (D.at ["body", "title"] D.string) + (D.at ["body", "content"] D.string) diff --git a/src/Main.elm b/src/Main.elm index 02cf37d8..03b4f7a9 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -17,13 +17,6 @@ type alias User = { username : String , last_name : String } -type alias Message - = { from : User - , to : User - , title : String - , content : String - } - type alias Election = { options : List String } type Page From 1d0c4b25e8368c619aa57d64ebee38e108995688 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Fri, 31 Jan 2020 16:42:02 +0100 Subject: [PATCH 09/66] Moved Messages.elm to src/ --- Messages.elm => src/Messages.elm | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) rename Messages.elm => src/Messages.elm (53%) diff --git a/Messages.elm b/src/Messages.elm similarity index 53% rename from Messages.elm rename to src/Messages.elm index 0460c981..ff629c46 100644 --- a/Messages.elm +++ b/src/Messages.elm @@ -1,10 +1,10 @@ -module Messages exposing ( message_decoder - , message_encoder - , publish - , save - , request_message - , request_messages - ) +module Messages exposing ( decoder + , encoder + , publish + , save + , request_one + , request_many + ) import Json.Decode as D import Json.Encode as E @@ -24,3 +24,7 @@ decoder = (D.field "to" <| D.list D.string) (D.at ["body", "title"] D.string) (D.at ["body", "content"] D.string) + +encode : Message -> E.Value +encode message = + E.object [] From a4c17bb354af2b53b9eb0389177ddcd77fbb6336 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 4 Feb 2020 18:06:43 +0100 Subject: [PATCH 10/66] Added Location of gunicorn installation to path for the execution of the start script. Thus we don't need root to execute ./start --- BUILD.txt | 2 ++ start | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/BUILD.txt b/BUILD.txt index 469b289b..3f5af276 100644 --- a/BUILD.txt +++ b/BUILD.txt @@ -5,3 +5,5 @@ 01/27/20-17:17:06-joris 01/27/20-17:18:06-joris 01/27/20-17:22:43-joris +02/04/20-16:52:22- +02/04/20-16:53:02- diff --git a/start b/start index c5d3f94f..e55b8cd1 100755 --- a/start +++ b/start @@ -1,4 +1,8 @@ #!/usr/bin/env bash + +# Add Location of gunicorn installation to PATH +PATH=$HOME/.local/bin:$PATH + # Install pip3 Packages pip3 install -r requirements.txt From 16e79e4ce1ba5a225833eb6d310c1d39614b8d12 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Tue, 4 Feb 2020 18:59:51 +0100 Subject: [PATCH 11/66] mypy and static testing. --- Server/election.py | 49 +++++----------------------------------------- requirements.txt | 2 ++ start | 9 +++++---- 3 files changed, 12 insertions(+), 48 deletions(-) diff --git a/Server/election.py b/Server/election.py index 125a2532..198f0ff7 100644 --- a/Server/election.py +++ b/Server/election.py @@ -1,47 +1,8 @@ import functools +from typing import List, Tuple -def count_votes( votes, participant_count, options ): - ballot = map( lambda option : { 'option' : option, 'support' : [] }, options ) - winner = distribute_votes( votes, participant_count, list(ballot) ) - return { "ballot": winner, "participants" : participant_count, "options" : options } +Vote = List[str] +Option = str -def distribute_votes( votes, participant_count, ballot ): - if len(ballot[0]['support']) > participant_count * 0.5 or len(ballot) <= 1: - return ballot[0] # Winner! - else: - # Distribute votes - for option in ballot: - option['support'] = option['support'] + list(filter(lambda v: v[-1] == option['option'], votes)) - - # Eliminate another low ranking option - # Sort by Support - ballot = sorted(ballot, key = lambda option : len(option['support']), reverse=True) - - looser = least_popular(ballot) - ballot.remove(looser) - looser['support'] = list(map(lambda v: v[:-1], looser['support'])) - return distribute_votes(looser['support'], participant_count, ballot) - -def least_popular( ballot ): - # If there is a tie, look at the option, that is more popular in the alternative votes. - if len(ballot[-1]['support']) == len(ballot[-2]['support']): - option1 = ballot[-1] - option2 = ballot[-2] - alternative_votes = list( map( lambda option: option['support'][:-1], ballot ) ) - alternative_votes = functools.reduce( lambda x, y : x + y, alternative_votes ) - - i = -1 - least_popular = False - - while(not least_popular): - is_tie = alternative_votes[i].count(option1['option']) - alternative_votes.count(option2['option']) - - if is_tie < 0: - least_popular = option1 - elif is_tie > 0: - least_popular = option2 - else: - least_popular = False - return least_popular - else: - return ballot[-1] +def count_votes( votes : List[Vote], participant_count : int, options : List[Option]) -> Tuple[Option, List[Vote]]: + return (options[0],votes) diff --git a/requirements.txt b/requirements.txt index fa7d88f4..0bd434a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,5 @@ MarkupSafe==1.1.1 pycryptodome==3.9.4 pymongo==3.10.1 Werkzeug==0.16.0 +mypy==0.761 +mypy-extensions==0.4.3 diff --git a/start b/start index e55b8cd1..35a6a2be 100755 --- a/start +++ b/start @@ -1,10 +1,7 @@ #!/usr/bin/env bash -# Add Location of gunicorn installation to PATH -PATH=$HOME/.local/bin:$PATH - # Install pip3 Packages -pip3 install -r requirements.txt +pip3 install -U -r requirements.txt # BUILD=Variable denoting current build string: BUILD=`date -u +"%D-%T"` @@ -12,6 +9,10 @@ BUILD="$BUILD-$BUILDER" echo $BUILD >> BUILD.txt +# Test if the static analyses pases +if ! mypy main.py Server/*.py --ignore-issing-imports; +then exit 1 + # Build Elm frontend elm make --output=output/main.js --optimize src/Main.elm From 6374e2fcc0d9d730daa629e52051cfa8cc07551e Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 6 Feb 2020 17:18:20 +0100 Subject: [PATCH 12/66] Trying to implement a secure vote function, where the vote can be published signed twice. This is due so you can publish them encrypted and unencrypted to check if a user has voted and see all the votes, without associating them to a username. --- Server/Elections.py | 16 ++++++---------- Server/Users.py | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Server/Elections.py b/Server/Elections.py index 35d3acca..4d781546 100644 --- a/Server/Elections.py +++ b/Server/Elections.py @@ -5,7 +5,7 @@ from Server.election import count_votes from pymongo import MongoClient from Crypto.Hash import SHA256 - +from typing import List, Tuple, Mapping """ Utility function to get the MongoClient.demnet[] @@ -93,17 +93,13 @@ def create(type,deadline,proposals): elections.insert_one(election) return election['hash'] -""" -a vote is a list of all options ranked by -how much a voter wants them to win. (see alternative vote). -**This function call cannot leave any trace of the association between -username and vote.** -""" -def vote(election_hash,vote,username): + +Encrypted_Vote = str +def vote(election_hash : str, username : str, vote : Encrypted_Vote ): elections = collection("elections") election = elections.find_one({ "hash" : election_hash }) - if username in election["participants"] and not election['closed']: + if username in election["participants"]: return False else: elections.update_one({ "hash" : election_hash }, { "$push" : { "participants" : username }}) @@ -124,7 +120,7 @@ def close(election_hash): if election: if election.get('deadline') <= time.time(): - winner = count_votes(election.votes, len(election.participants), range(0,len(election.proposals)+1))["ballot"] + winner = count_votes(election["votes"], len(election.participants), range(0,len(election.proposals)+1))["ballot"] winner = election.proposals[winner] elections.update_one({ "hash" : election_hash }, { "$set" : { "winner" : winner, "closed" : True } }) diff --git a/Server/Users.py b/Server/Users.py index ba7349f3..93ea5670 100644 --- a/Server/Users.py +++ b/Server/Users.py @@ -10,7 +10,7 @@ """ from Crypto.PublicKey import RSA from Crypto.Hash import SHA256 -from Crypto.Cipher import AES +from Crypto.Cipher import PKCS1_OAEP from pymongo import MongoClient import datetime, sys, json From db324e3f4693311bf016625f329ab8e05dd54208 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 6 Feb 2020 17:42:30 +0100 Subject: [PATCH 13/66] Implemented two step encryption in vote() --- Server/Elections.py | 22 ++++++++++++++++++++-- main.py | 3 ++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Server/Elections.py b/Server/Elections.py index 4d781546..fe01fa48 100644 --- a/Server/Elections.py +++ b/Server/Elections.py @@ -5,6 +5,8 @@ from Server.election import count_votes from pymongo import MongoClient from Crypto.Hash import SHA256 +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_OAEP from typing import List, Tuple, Mapping """ @@ -94,14 +96,30 @@ def create(type,deadline,proposals): return election['hash'] -Encrypted_Vote = str -def vote(election_hash : str, username : str, vote : Encrypted_Vote ): +"""Stores a vote byte string, that is encrypted like this: +Given Keys: +- Private Key of the User (private_key_of_user) +- Public Key of the Election Authority (public_key_of_ea) +Encryption of the vote as string: +vote_e = E(private_key_of_user, E(public_key_of_ea, vote)) +If you only have the public key of the user you can only +tell, that a user has voted but not how. +And only the EA can read the vote, with their private key. +If you don't trust the EA or the EA has been compromised, +the election is invalid to you, but this could easily +be decentralised. +""" +def vote(election_hash : str, username : str, vote : List[str], private_key_of_user : RSA.RsaKey, public_key_of_ea : RSA.RsaKey) -> bool: elections = collection("elections") election = elections.find_one({ "hash" : election_hash }) + cipher_ea = PKCS1_OAEP.new(public_key_of_ea) + cipher_user = PKCS1_OAEP.new(private_key_of_user) + if username in election["participants"]: return False else: + vote = cipher_user(cipher_ea.encrypt(json.dumps(vote).encode('utf-8'))) elections.update_one({ "hash" : election_hash }, { "$push" : { "participants" : username }}) elections.update_one({ "hash" : election_hash }, { "$push" : { "votes" : vote }}) return True diff --git a/main.py b/main.py index 7caef903..f03539c0 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,8 @@ from flask import Flask, request, render_template, send_file import json, os from Crypto.Hash import SHA3_256 - +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_OAEP app = Flask(__name__, static_url_path="/static", static_folder="/static") app.secret_key = os.environ["SECRET_KEY"] From 92140e11c6beadf12d467b751b46c790f1a93407 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Fri, 7 Feb 2020 08:26:07 +0100 Subject: [PATCH 14/66] votes field in elections --- Database.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Database.md b/Database.md index 6c96c5f9..1bacfef8 100644 --- a/Database.md +++ b/Database.md @@ -21,6 +21,7 @@ A sample Election Document looks like this: , "closed" : , "winner" : , "type" : "" +, "votes" : [{ "username" : "", "vote" : b"" }] } ``` There can be two kinds of proposals: From 85759343c274d38ccbb28c6f79ed1ac78b0d98cc Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Fri, 7 Feb 2020 08:26:30 +0100 Subject: [PATCH 15/66] Encryption of Votes --- Server/Elections.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Server/Elections.py b/Server/Elections.py index fe01fa48..89e2e03c 100644 --- a/Server/Elections.py +++ b/Server/Elections.py @@ -120,8 +120,7 @@ def vote(election_hash : str, username : str, vote : List[str], private_key_of_u return False else: vote = cipher_user(cipher_ea.encrypt(json.dumps(vote).encode('utf-8'))) - elections.update_one({ "hash" : election_hash }, { "$push" : { "participants" : username }}) - elections.update_one({ "hash" : election_hash }, { "$push" : { "votes" : vote }}) + elections.update_one({ "hash" : election_hash }, { "$push" : { "votes" : { "username" : username, "vote" : vote_e }}} return True @@ -132,12 +131,13 @@ def vote(election_hash : str, username : str, vote : List[str], private_key_of_u Publishing the votes, to make the independent control possible. """ -def close(election_hash): +def close(election_hash : str, private_key_ea : RSA.RsaKey): elections = collection('elections') election = elections.find_one({ "hash" : election_hash }) if election: if election.get('deadline') <= time.time(): + votes = encrypt_votes(election["votes"], private_key_ea) winner = count_votes(election["votes"], len(election.participants), range(0,len(election.proposals)+1))["ballot"] winner = election.proposals[winner] elections.update_one({ "hash" : election_hash }, { "$set" : { "winner" : winner, "closed" : True } }) @@ -146,7 +146,7 @@ def close(election_hash): patches = collection('patches') patch = patches.find_one({ "hash" : winner['patch_id'] }) Patches.close(patch['name'], patch['name'], patch['hash'], merge=True) - elif election['type'] == "human": + else: laws = collection("laws") # Append ammendments to laws for ammendment in winner["ammendment"]: @@ -164,6 +164,18 @@ def close(election_hash): laws.remove_one({ "title" : removal }) - return True + return True else: return False + +def encrypt_votes(votes : List[bytes], private_key_ea : RSA.RsaKey) -> List[List[str]]: + users = collection("users") + cipher_ea = PKCS1_OAEP.new(private_key_ea) + for vote in votes: + user = users.find_one({ "username" : vote["username"] }) + public_key_of_user = RSA.import_key(user['public_key']) + cipher_user = PKCS1_OAEP.new(public_key_of_user) + try: + vote = cipher_ea.decrypt(cipher_user.decrypt(user["vote"])) + except Exception as e: + raise From 52fc6bf95520ad7c7cef7dbbe89f036c1db3703e Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Fri, 7 Feb 2020 09:20:54 +0100 Subject: [PATCH 16/66] count_votes rewritten. --- Server/election.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Server/election.py b/Server/election.py index 198f0ff7..56114e6e 100644 --- a/Server/election.py +++ b/Server/election.py @@ -5,4 +5,20 @@ Option = str def count_votes( votes : List[Vote], participant_count : int, options : List[Option]) -> Tuple[Option, List[Vote]]: - return (options[0],votes) + turn = map(lambda v: v[-1], votes) + for i in range(1,len(votes)) + # If one option > 50 % support, it wins + maximum_option = max([(turn.count(o), o) for o in options]) + if maximum_option[0] > len(votes)/2: + return (maximum_option[1],filter(lambda v: v[-i] == max(turn)[-i]))# Winner + else: + # If no option receives > 50 % support, delete the least popular ones. + least_popular_indicies = find_least_popular(turn) + turn.pop(least_popular_indicies) + for least_popular_index in least_popular_indicies: + turn.append(votes[least_popular_index][-i-1]) + + +def find_least_popular( turn : List[Option] ): + # If the least popular is a singular, just return that: + turn.index(min(turn)) From 7aa1161b4c645549a859637939957ae0e10dd37c Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Fri, 7 Feb 2020 20:17:02 +0100 Subject: [PATCH 17/66] class Turn created. --- Server/election.py | 62 +++++++++++++++++++++++++++----------- Server/election_example.py | 2 +- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/Server/election.py b/Server/election.py index 56114e6e..b813619b 100644 --- a/Server/election.py +++ b/Server/election.py @@ -4,21 +4,47 @@ Vote = List[str] Option = str -def count_votes( votes : List[Vote], participant_count : int, options : List[Option]) -> Tuple[Option, List[Vote]]: - turn = map(lambda v: v[-1], votes) - for i in range(1,len(votes)) - # If one option > 50 % support, it wins - maximum_option = max([(turn.count(o), o) for o in options]) - if maximum_option[0] > len(votes)/2: - return (maximum_option[1],filter(lambda v: v[-i] == max(turn)[-i]))# Winner - else: - # If no option receives > 50 % support, delete the least popular ones. - least_popular_indicies = find_least_popular(turn) - turn.pop(least_popular_indicies) - for least_popular_index in least_popular_indicies: - turn.append(votes[least_popular_index][-i-1]) - - -def find_least_popular( turn : List[Option] ): - # If the least popular is a singular, just return that: - turn.index(min(turn)) +""" +Turn class, +a class to collect the current state of the counting. +First it sorts all votes into the options. +If an option is deleted, the votes removed from it +are resorted into the new options. +""" +class Turn(): + def __init__(self,participants : int, votes : List[Vote], options : List[Option]): + self.__options__ = { key : [] for key in options } + self.__resort(votes) + self.participants = participants + + + def __resort(self,votes : List[Vote]): + for vote in votes: + vote_old = vote + print(f"Changed from {vote.pop()} to {vote[-1]} in {vote_old}") + + self.__options__[vote[-1]].append(vote) + + def count(self): + winner = None + while winner == None: + least = [] + for option in self.__options__: + option = (option,len(self.__options__[option])) + if option[1] > self.participants/2: + winner = option + elif option[1] < sum([s[1] for s in least]): + least = option + elif option[1] == sum([s[1] for s in least]): + least.append(option) + + for s in least: + self.__resort(self.__options__[s[0]]) + print(s[0]) + del self.__options__[s[0]] + + return winner + +def count_votes(participants : int, votes : List[Vote], options : List[Option]) -> Tuple[Option, int]: + turn = Turn(participants,votes,options) + return turn.count() diff --git a/Server/election_example.py b/Server/election_example.py index a069deb0..fc6c0007 100644 --- a/Server/election_example.py +++ b/Server/election_example.py @@ -18,7 +18,7 @@ def generate_elections( options, participants ): votes.append( generate_random_vote(options) ) participants -= 1 - winner = count_votes( votes, len(votes), options ) + winner = count_votes( len(votes), votes, options ) winners = [] for i in range(len(options)): From a250b544ddda58099e473ddb510210505b46c924 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 11:04:14 +0100 Subject: [PATCH 18/66] Documentation for .el File Format --- Server/ELECTION.el | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Server/ELECTION.el diff --git a/Server/ELECTION.el b/Server/ELECTION.el new file mode 100644 index 00000000..6788c3ab --- /dev/null +++ b/Server/ELECTION.el @@ -0,0 +1,8 @@ +# File to represent an election result + +Votes: +# List of votes as python lists +Thrown: +# All the options, that were thrown out after each other +Winner: +# The Option, that won + the number of votes in support of it. From b4873a6c6e3e68e430f6fdfc1e94cba8a6326f79 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 11:05:01 +0100 Subject: [PATCH 19/66] Changed name from generate_elections to generate_election. --- Server/election_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/election_example.py b/Server/election_example.py index fc6c0007..bc0730eb 100644 --- a/Server/election_example.py +++ b/Server/election_example.py @@ -12,7 +12,7 @@ def load_sample_votes(): sample_votes = list(map( lambda v: v.split(';'), sample_votes )) return sample_votes -def generate_elections( options, participants ): +def generate_election( options, participants ): votes = [] while( participants > 0 ): votes.append( generate_random_vote(options) ) @@ -35,5 +35,5 @@ def generate_elections( options, participants ): if __name__ == '__main__': i = 10 while(i > 0): - generate_elections(['A','B','C','D', 'E', 'F', 'G', 'H', 'I'],random.randint(10**2,10**3)) + generate_election(['A','B','C','D', 'E', 'F', 'G', 'H', 'I'],random.randint(10**2,10**3)) i -= 1 From 8580baa13026bb4b72220c321684f4b2d2bdee6e Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 11:08:36 +0100 Subject: [PATCH 20/66] Added Log File mechanic --- Server/election.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/Server/election.py b/Server/election.py index b813619b..8564df1c 100644 --- a/Server/election.py +++ b/Server/election.py @@ -1,5 +1,5 @@ -import functools -from typing import List, Tuple +from typing import List, Tuple, TextIO +import sys Vote = List[str] Option = str @@ -12,18 +12,33 @@ are resorted into the new options. """ class Turn(): - def __init__(self,participants : int, votes : List[Vote], options : List[Option]): + def __init__(self,participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None): + if len(votes) < participants: + votes.extend([["NoneOfTheOtherOptions"] for x in range(participants-len(votes))]) self.__options__ = { key : [] for key in options } + + if fs != None: + self.result_file = fs + else: + self.result_file = sys.stdout + + print(f"Votes:\n {votes}\nThrown:", file=self.result_file) + self.__resort(votes) self.participants = participants + def __resort(self,votes : List[Vote]): for vote in votes: - vote_old = vote - print(f"Changed from {vote.pop()} to {vote[-1]} in {vote_old}") - self.__options__[vote[-1]].append(vote) + old = vote.pop() + if old == "NoneOfTheOtherOptions": + break + elif len(vote) == 0: + self.__options__["NoneOfTheOtherOptions"].append(["NoneOfTheOtherOptions"]) + else: + self.__options__[vote[-1]].append(vote) def count(self): winner = None @@ -39,12 +54,13 @@ def count(self): least.append(option) for s in least: + print(f"{s[0]}",file=self.result_file) self.__resort(self.__options__[s[0]]) - print(s[0]) - del self.__options__[s[0]] + self.__options__.pop(s[0],None) + print(f"Winner:\n{winner[0]}, {winner[1]}", file=self.result_file) return winner -def count_votes(participants : int, votes : List[Vote], options : List[Option]) -> Tuple[Option, int]: - turn = Turn(participants,votes,options) +def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None ) -> Tuple[Option, int]: + turn = Turn(participants,votes,options, fs) return turn.count() From 8c77aa29d4ba06d239ddb45250a8fb317e087848 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 11:09:36 +0100 Subject: [PATCH 21/66] Added mechanic for log file in election_example.py --- Server/election_example.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Server/election_example.py b/Server/election_example.py index bc0730eb..d8819114 100644 --- a/Server/election_example.py +++ b/Server/election_example.py @@ -14,26 +14,18 @@ def load_sample_votes(): def generate_election( options, participants ): votes = [] + result_file = open("sample_election.el","r+") while( participants > 0 ): votes.append( generate_random_vote(options) ) participants -= 1 - winner = count_votes( len(votes), votes, options ) + winner = count_votes( len(votes), votes, options, fs=result_file ) - winners = [] - for i in range(len(options)): - votes_in_i = list(map( lambda vote: vote[-i],votes )) - winner_votes = votes_in_i.count(winner['ballot']['option']) - winners.append( ( winner_votes, len(votes_in_i) )) - print(f"Result:{winner['ballot']['option']}") - for i in range(len(winners)): - percentage = round((winners[i][0]/winners[i][1]) * 100, ndigits=2) - print(f"Winner' votes in Vote #{i}:\t{winners[i][0]} of {winners[i][1]},\t{ percentage }% ") if __name__ == '__main__': i = 10 while(i > 0): - generate_election(['A','B','C','D', 'E', 'F', 'G', 'H', 'I'],random.randint(10**2,10**3)) + generate_elections(['A','B','C','D', 'E', 'F', 'G', 'H', 'I'],random.randint(10**2,10**3)) i -= 1 From 370be143052c21bd267b1fcf3651599dadd62858 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 13:21:55 +0100 Subject: [PATCH 22/66] Writing to log file perfected. --- Server/election.py | 7 ++++++- Server/election_example.py | 4 ++-- Server/sample_election.el | 7 +++++++ 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 Server/sample_election.el diff --git a/Server/election.py b/Server/election.py index 8564df1c..9b70473b 100644 --- a/Server/election.py +++ b/Server/election.py @@ -63,4 +63,9 @@ def count(self): def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None ) -> Tuple[Option, int]: turn = Turn(participants,votes,options, fs) - return turn.count() + result = turn.count() + + if fs != None: + fs.close() + + return result diff --git a/Server/election_example.py b/Server/election_example.py index d8819114..c8f08b82 100644 --- a/Server/election_example.py +++ b/Server/election_example.py @@ -14,7 +14,7 @@ def load_sample_votes(): def generate_election( options, participants ): votes = [] - result_file = open("sample_election.el","r+") + result_file = open("sample_election.el","w") while( participants > 0 ): votes.append( generate_random_vote(options) ) participants -= 1 @@ -27,5 +27,5 @@ def generate_election( options, participants ): if __name__ == '__main__': i = 10 while(i > 0): - generate_elections(['A','B','C','D', 'E', 'F', 'G', 'H', 'I'],random.randint(10**2,10**3)) + generate_election(['A','B','C','D', 'E', 'F', 'G', 'H', 'I'],random.randint(10**2,10**3)) i -= 1 diff --git a/Server/sample_election.el b/Server/sample_election.el new file mode 100644 index 00000000..df326be8 --- /dev/null +++ b/Server/sample_election.el @@ -0,0 +1,7 @@ +Votes: + [['A', 'B', 'C'], ['C', 'B', 'A'], ['D', 'A', 'B', 'C'], ['D', 'A', 'C', 'B'], ['NoneOfTheOtherOptions']] +Thrown: +A +D +Winner: +B, 3 From 2afda38ec43b91a2a4e6c7a26c66e817098ee61f Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 13:49:08 +0100 Subject: [PATCH 23/66] Stopping counting, if two options both have 50%. --- Server/election.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Server/election.py b/Server/election.py index 9b70473b..1f7774d8 100644 --- a/Server/election.py +++ b/Server/election.py @@ -48,11 +48,15 @@ def count(self): option = (option,len(self.__options__[option])) if option[1] > self.participants/2: winner = option + elif len(self.__options__) == 2: # another election must be called. + keys = list(self.__options__) + winner = [(key,self.__options__[key]) for key in keys] elif option[1] < sum([s[1] for s in least]): least = option elif option[1] == sum([s[1] for s in least]): least.append(option) + for s in least: print(f"{s[0]}",file=self.result_file) self.__resort(self.__options__[s[0]]) From 57e709df7d4ddb83ce8519c762f60a4a2e42c85b Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 14:03:01 +0100 Subject: [PATCH 24/66] Introduced variable victory threshold for election. --- Server/election.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Server/election.py b/Server/election.py index 1f7774d8..3c4a0e56 100644 --- a/Server/election.py +++ b/Server/election.py @@ -40,15 +40,24 @@ def __resort(self,votes : List[Vote]): else: self.__options__[vote[-1]].append(vote) - def count(self): + """count all votes in self.__options__. + To win the election an option has to have more than limit*100 % support. + Parameters: + - limit : float = 0.5 := Percentages necessary to win for an election + Returns: + Either + - (winner_name,support) : Tuple[str,List[Vote] - If one option has won + - [(one_winner,support),(other_winner,support)] : List[Tuple[str,List[Vote]]] - If no side could unite the necessary support + """ + def count(self, limit : float =0.5): winner = None while winner == None: least = [] for option in self.__options__: option = (option,len(self.__options__[option])) - if option[1] > self.participants/2: + if option[1] > self.participants*limit: winner = option - elif len(self.__options__) == 2: # another election must be called. + elif len(self.__options__) == (1/limit): # another election must be called. keys = list(self.__options__) winner = [(key,self.__options__[key]) for key in keys] elif option[1] < sum([s[1] for s in least]): From 9781cfbbbd14752b183fcf74b7e46e09c3368430 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 14:04:48 +0100 Subject: [PATCH 25/66] Made threshold parameter available through count_votes()as positional argument. --- Server/election.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/election.py b/Server/election.py index 3c4a0e56..fe9b23f8 100644 --- a/Server/election.py +++ b/Server/election.py @@ -43,19 +43,19 @@ def __resort(self,votes : List[Vote]): """count all votes in self.__options__. To win the election an option has to have more than limit*100 % support. Parameters: - - limit : float = 0.5 := Percentages necessary to win for an election + - threshold : float = 0.5 := Percentages necessary to win for an election Returns: Either - (winner_name,support) : Tuple[str,List[Vote] - If one option has won - [(one_winner,support),(other_winner,support)] : List[Tuple[str,List[Vote]]] - If no side could unite the necessary support """ - def count(self, limit : float =0.5): + def count(self, threshold : float =0.5): winner = None while winner == None: least = [] for option in self.__options__: option = (option,len(self.__options__[option])) - if option[1] > self.participants*limit: + if option[1] > self.participants*threshold: winner = option elif len(self.__options__) == (1/limit): # another election must be called. keys = list(self.__options__) @@ -74,9 +74,9 @@ def count(self, limit : float =0.5): print(f"Winner:\n{winner[0]}, {winner[1]}", file=self.result_file) return winner -def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None ) -> Tuple[Option, int]: +def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None, threshold : float = 0.5) -> Tuple[Option, int]: turn = Turn(participants,votes,options, fs) - result = turn.count() + result = turn.count(threshold=threshold) if fs != None: fs.close() From 720363901c940130f9d175d9dc4d660c1cd52fe1 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 16:48:17 +0100 Subject: [PATCH 26/66] Solved a bug about how self.__resort pops the last element of the list automatically, thus deleting the first choice of the vote, if called on the original vote. --- Server/election.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Server/election.py b/Server/election.py index fe9b23f8..3f79bd30 100644 --- a/Server/election.py +++ b/Server/election.py @@ -14,8 +14,10 @@ class Turn(): def __init__(self,participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None): if len(votes) < participants: - votes.extend([["NoneOfTheOtherOptions"] for x in range(participants-len(votes))]) - self.__options__ = { key : [] for key in options } + votes.extend([["NoneOfTheOtherOptions","NoneOfTheOtherOptions"] for x in range(participants-len(votes))]) + + + self.__options__ = { key : list(filter(lambda v:v[-1] == key, votes)) for key in options } if fs != None: self.result_file = fs @@ -24,7 +26,7 @@ def __init__(self,participants : int, votes : List[Vote], options : List[Option] print(f"Votes:\n {votes}\nThrown:", file=self.result_file) - self.__resort(votes) + self.participants = participants @@ -34,7 +36,7 @@ def __resort(self,votes : List[Vote]): old = vote.pop() if old == "NoneOfTheOtherOptions": - break + continue elif len(vote) == 0: self.__options__["NoneOfTheOtherOptions"].append(["NoneOfTheOtherOptions"]) else: @@ -48,8 +50,13 @@ def __resort(self,votes : List[Vote]): Either - (winner_name,support) : Tuple[str,List[Vote] - If one option has won - [(one_winner,support),(other_winner,support)] : List[Tuple[str,List[Vote]]] - If no side could unite the necessary support + - False - If Error has occured and invalid data been provided. """ def count(self, threshold : float =0.5): + # If threshold were less than 50%, there could be multiple + # winners, which is impossible under this setup. + if threshold < 0.5 or threshold > 1: + return False winner = None while winner == None: least = [] @@ -57,12 +64,13 @@ def count(self, threshold : float =0.5): option = (option,len(self.__options__[option])) if option[1] > self.participants*threshold: winner = option - elif len(self.__options__) == (1/limit): # another election must be called. + elif len(self.__options__) == 2: # another election must be called. keys = list(self.__options__) winner = [(key,self.__options__[key]) for key in keys] elif option[1] < sum([s[1] for s in least]): least = option elif option[1] == sum([s[1] for s in least]): + print(self.__options__) least.append(option) From 0e27741277ad9bd5d22ab22e491dc2869350f728 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 16:51:36 +0100 Subject: [PATCH 27/66] Removed attempt at fixing bug in previous commit, that is unnecessary. --- Server/election.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/election.py b/Server/election.py index 3f79bd30..a26aa2a0 100644 --- a/Server/election.py +++ b/Server/election.py @@ -14,7 +14,7 @@ class Turn(): def __init__(self,participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None): if len(votes) < participants: - votes.extend([["NoneOfTheOtherOptions","NoneOfTheOtherOptions"] for x in range(participants-len(votes))]) + votes.extend([["NoneOfTheOtherOptions"] for x in range(participants-len(votes))]) self.__options__ = { key : list(filter(lambda v:v[-1] == key, votes)) for key in options } From 2621fc43c72fbc91d4e5d6a358ab4bbcb4606e55 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 16:54:27 +0100 Subject: [PATCH 28/66] Removed election_example.py because it will be replaced by a test file. --- Server/election_example.py | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 Server/election_example.py diff --git a/Server/election_example.py b/Server/election_example.py deleted file mode 100644 index c8f08b82..00000000 --- a/Server/election_example.py +++ /dev/null @@ -1,31 +0,0 @@ -from election import count_votes -import random, os - -random.seed( a=os.environ['SEED'] ) - -def generate_random_vote(options): - vote = random.sample( options, k = len(options) ) - return vote - -def load_sample_votes(): - sample_votes = open('sample_votes.txt').read().split('\n') - sample_votes = list(map( lambda v: v.split(';'), sample_votes )) - return sample_votes - -def generate_election( options, participants ): - votes = [] - result_file = open("sample_election.el","w") - while( participants > 0 ): - votes.append( generate_random_vote(options) ) - participants -= 1 - - winner = count_votes( len(votes), votes, options, fs=result_file ) - - - - -if __name__ == '__main__': - i = 10 - while(i > 0): - generate_election(['A','B','C','D', 'E', 'F', 'G', 'H', 'I'],random.randint(10**2,10**3)) - i -= 1 From 44f8556acbd09d83b07af32bccc5f8b9cad5e9a8 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 17:20:13 +0100 Subject: [PATCH 29/66] Added test/election.py with some test cases for Server/election.py for random ballots. --- Server/election.py | 2 +- Server/test/election.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 Server/test/election.py diff --git a/Server/election.py b/Server/election.py index a26aa2a0..60e7a488 100644 --- a/Server/election.py +++ b/Server/election.py @@ -79,7 +79,7 @@ def count(self, threshold : float =0.5): self.__resort(self.__options__[s[0]]) self.__options__.pop(s[0],None) - print(f"Winner:\n{winner[0]}, {winner[1]}", file=self.result_file) + print(f"Winner:\n{winner}", file=self.result_file) return winner def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None, threshold : float = 0.5) -> Tuple[Option, int]: diff --git a/Server/test/election.py b/Server/test/election.py new file mode 100644 index 00000000..5b8cfb60 --- /dev/null +++ b/Server/test/election.py @@ -0,0 +1,40 @@ +import election +import random, os, json + +random.seed(os.environ["SEED"]) + +"""Utility function to easily create new election ballots, if necessary +""" +def generate_ballot(): + options_population = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] + options = random.choices( + options_population + , k=random.randint(1,len(options_population)) + ) + + votes = [random.sample(options,random.randint(1,len(options))) for i in range(random.randint(100,1000))] + + participants = random.randint(len(votes), len(votes)+1000) + return (votes,participants,options) + +def test_count_votes(): + for i in range(1000): + ballot = generate_ballot() + test_file = open("test_election.el","w") + result = election.count_votes(ballot[0],ballot[1],ballot[2],fs=test_file) + + assert test_file.closed + + election_log = open("test_election.el","r").read() + + # Format checks in the log: + + # Votes, Thrown and Winner fields must exist + assert election_log.startswith("Votes:\n") + (votes,_,rest) = election_log.partition("\nThrown:\n") + (thrown,_,winner) = election_log.partition("\nWinner:\n") + (_,_,votes) = votes.partition("Votes:\n") + votes = json.loads(votes) + + assert ballot[0] == votes + assert winner == str(result) From 4c4a5826cebc39cecaa28eae6ec5e287e6cf101d Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 17:33:02 +0100 Subject: [PATCH 30/66] Unit Test for Server/election.py --- Server/test_election.el | 0 Server/{test/election.py => test_election.py} | 11 ++++ Server/test_patches.py | 66 ------------------- 3 files changed, 11 insertions(+), 66 deletions(-) create mode 100644 Server/test_election.el rename Server/{test/election.py => test_election.py} (79%) delete mode 100644 Server/test_patches.py diff --git a/Server/test_election.el b/Server/test_election.el new file mode 100644 index 00000000..e69de29b diff --git a/Server/test/election.py b/Server/test_election.py similarity index 79% rename from Server/test/election.py rename to Server/test_election.py index 5b8cfb60..4e1513ea 100644 --- a/Server/test/election.py +++ b/Server/test_election.py @@ -18,6 +18,7 @@ def generate_ballot(): return (votes,participants,options) def test_count_votes(): + # Random ballot test cases for i in range(1000): ballot = generate_ballot() test_file = open("test_election.el","w") @@ -38,3 +39,13 @@ def test_count_votes(): assert ballot[0] == votes assert winner == str(result) + + # Testing on one special ballot + options = ["A","B","C"] + votes = [["A","B","C"],["A","C","B"],["A","C","B"]] + participants = len(votes) + + assert ("B", 2) == election.count_votes(votes,participants,options) + + participants = 10 + assert ("NoneOfTheOtherOptions", 8) == election.count_votes(votes,participants,options) diff --git a/Server/test_patches.py b/Server/test_patches.py deleted file mode 100644 index 3de2ec5a..00000000 --- a/Server/test_patches.py +++ /dev/null @@ -1,66 +0,0 @@ -import Patches -import random, os, string, subprocess -from pymongo import MongoClient - -def random_string(): - return ''.join(random.choice(string.ascii_letters) for i in range(random.randint(0,125))) - -def generate_random_patch(): - patcher = random.choice(['joris', 'abbashan', 'martin', 'brummel']) - patch = random.choice(['www-x','user-support', 'generate-random', 'postings']) - - options = { "is_user" : random.choice([True, False]) - , "simple_description" : random_string() - , "technical_description" : random_string() - , "hold_pre_election" : random.choice([True, False]) - , "references" : [ random_string() for i in range(random.randint(0,5)) ] - } - return (patcher, patch, options) - -def test_patch(): - os.environ['PATCHES'] = "/tmp/demnet_test" - os.environ['ORIGIN_REPOSITORY'] = "/tmp/demnet_origin" - - - (patcher, patch, options) = generate_random_patch() - - patch_hash = Patches.create(patcher, patch, options) - - assert os.path.isdir(f"{os.environ['PATCHES']}/{patcher}-{patch}") - - client = MongoClient() - db = client.demnet - patches = db.patches - - patch_formula = patches.find_one({ "hash" : patch_hash }) - - assert patch_formula['patcher'] == patcher - assert patch_formula['is_user'] == options['is_user'] - assert patch_formula['name'] == patch - assert patch_formula['simple_description'] == options['simple_description'] - assert patch_formula['technical_description'] == options['technical_description'] - assert patch_formula['hold_pre_election'] == options['hold_pre_election'] - assert patch_formula['references'] == options['references'] - assert patch_formula['closed'] == False - - (patcher_2, patch_2, options_2) = generate_random_patch() - - patch_2_hash = Patches.create(patcher_2, patch_2, options_2) - - # Close Patches without merging - assert Patches.close(patcher_2, patch_2, patch_2_hash) == True - print(f"Patcher:\t{patcher_2}\nPatch:\t{patch_2}") - assert not os.path.isdir(f"{ os.environ['PATCHES'] }/{patcher_2}-{patch_2}") - - # Create a Commit and Change to Patch - pwd = os.environ['PWD'] - subprocess.run([ f"cd { os.environ['PATCHES'] }/{patcher}-{patch}" ], shell=True) - subprocess.run([ f"echo \"Hello, World\" > README" ], shell=True) - subprocess.run([ f"git commit -m \"Test Commit\" && cd {pwd}" ], shell=True) - subprocess.run([ "cd ", pwd ], shell=True) - assert Patches.close(patcher, patch, patch_hash, merge=True) == True - assert not os.isdir(f"{os.environ['PATCHES']}/{patcher}-{patch}") - - subprocess.run([ "cd ", os.environ["ORIGIN_REPOSITORY"] ]) - log_res = subprocess.run([ "git log | grep \"Test Commit\"" ], capture_output=True, text=True) - assert log_res.stdout == "Test Commit" From c20f84000e42ab0a631abcd213eb2d87da8595a3 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 8 Feb 2020 18:10:16 +0100 Subject: [PATCH 31/66] Using json.dumps as a converter list -> string. Swapped arguments in __init__ of Turn. --- Server/election.py | 6 +++--- Server/test_election.el | 11 +++++++++++ Server/test_election.py | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Server/election.py b/Server/election.py index 60e7a488..9f9281fb 100644 --- a/Server/election.py +++ b/Server/election.py @@ -1,5 +1,5 @@ from typing import List, Tuple, TextIO -import sys +import sys, json Vote = List[str] Option = str @@ -12,7 +12,7 @@ are resorted into the new options. """ class Turn(): - def __init__(self,participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None): + def __init__(self, votes : List[Vote], participants : int, options : List[Option], fs : TextIO = None): if len(votes) < participants: votes.extend([["NoneOfTheOtherOptions"] for x in range(participants-len(votes))]) @@ -24,7 +24,7 @@ def __init__(self,participants : int, votes : List[Vote], options : List[Option] else: self.result_file = sys.stdout - print(f"Votes:\n {votes}\nThrown:", file=self.result_file) + print(f"Votes:\n {json.dumps(votes)}\nThrown:", file=self.result_file) self.participants = participants diff --git a/Server/test_election.el b/Server/test_election.el index e69de29b..cc851094 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -0,0 +1,11 @@ +Votes: + [["L", "U", "W", "V", "R", "L", "S", "H", "R", "X", "T", "E"], ["A", "Z", "K", "L", "B", "Y", "D", "C", "V", "J", "U"], ["R", "T", "U", "E", "H", "L", "R", "D", "V", "S", "X", "K", "U", "J", "Z", "L"], ["W", "R", "B", "X", "T", "S", "E", "Y"], ["W", "E", "U", "T", "Y", "R", "S", "K", "A", "R"], ["U", "R", "L", "T", "R", "H"], ["H", "J", "Y", "X", "A", "L", "Z", "V", "X", "T"], ["L", "A", "U", "X", "R", "D", "X"], ["C", "K", "V", "J", "T", "Z", "L", "T", "E", "D", "Y", "W", "R", "H", "U", "R", "U", "X", "L", "Z"], ["R", "C", "Y", "S", "R", "B", "A", "T", "Z", "W", "J", "U"], ["Y", "B", "A"], ["U", "V", "Y", "B", "R", "X", "E", "A", "D", "J", "Z", "S", "X", "R", "W", "U", "Z", "K", "C", "T", "H"], ["Z", "J", "X", "D", "Z", "L", "B", "U"], ["X", "R", "K", "A", "V", "Z", "B", "Y", "R", "U"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] +Thrown: +J +B +C +S +V +D +K +W diff --git a/Server/test_election.py b/Server/test_election.py index 4e1513ea..ccf3e4e1 100644 --- a/Server/test_election.py +++ b/Server/test_election.py @@ -12,9 +12,9 @@ def generate_ballot(): , k=random.randint(1,len(options_population)) ) - votes = [random.sample(options,random.randint(1,len(options))) for i in range(random.randint(100,1000))] + votes = [random.sample(options,random.randint(1,len(options))) for i in range(random.randint(10,100))] - participants = random.randint(len(votes), len(votes)+1000) + participants = random.randint(len(votes), len(votes)+10) return (votes,participants,options) def test_count_votes(): From 1142ba7f45ea98a5c3f7eb9dda1c8c13b125ffa5 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sun, 9 Feb 2020 15:04:06 +0100 Subject: [PATCH 32/66] Seems like this algorithem has a O(n(v^n)), where v is the amount of time necessary to change a vote and n the number of votes. --- Server/election.py | 51 +++++++++++++++++++++++++++-------------- Server/test_election.el | 10 +------- Server/test_election.py | 2 +- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/Server/election.py b/Server/election.py index 9f9281fb..668507f5 100644 --- a/Server/election.py +++ b/Server/election.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, TextIO +from typing import List, Tuple, TextIO, Any import sys, json Vote = List[str] @@ -34,15 +34,23 @@ def __init__(self, votes : List[Vote], participants : int, options : List[Option def __resort(self,votes : List[Vote]): for vote in votes: - old = vote.pop() + old = vote[-1] if old == "NoneOfTheOtherOptions": continue - elif len(vote) == 0: + elif len(vote) == 1: self.__options__["NoneOfTheOtherOptions"].append(["NoneOfTheOtherOptions"]) else: + vote.pop() self.__options__[vote[-1]].append(vote) """count all votes in self.__options__. + It follows the following principle: + Each turn one or more options are selected + to be eliminated. + In elimination, all votes, that are voting for the option are reallocated + to the next alternative option choice. + Once an option reached a threshold of support, the counting is over. + To win the election an option has to have more than limit*100 % support. Parameters: - threshold : float = 0.5 := Percentages necessary to win for an election @@ -58,30 +66,39 @@ def count(self, threshold : float =0.5): if threshold < 0.5 or threshold > 1: return False winner = None + is_infinite_loop = 0 while winner == None: - least = [] - for option in self.__options__: - option = (option,len(self.__options__[option])) - if option[1] > self.participants*threshold: - winner = option - elif len(self.__options__) == 2: # another election must be called. - keys = list(self.__options__) - winner = [(key,self.__options__[key]) for key in keys] - elif option[1] < sum([s[1] for s in least]): - least = option - elif option[1] == sum([s[1] for s in least]): - print(self.__options__) - least.append(option) - + winners = filter(lambda o: (o/self.participants > threshold),self.__options__) + if len(winners) == 1: + winner = winners[0] + + least = self.least() for s in least: print(f"{s[0]}",file=self.result_file) self.__resort(self.__options__[s[0]]) self.__options__.pop(s[0],None) + print(f"Winner:\n{winner}", file=self.result_file) return winner + def least(self): + least = [] + options = list(self.__options__) + for o in options: + sub_support_of_o_from_least_support = (len(self.__options__[o])/self.participants) - (sum([len(self.__options__[least_option]) for least_option in least])) + + if (sub_support_of_o_from_least_support > 0) or (o == "NoneOfTheOtherOptions"): + # Ignore this option + continue + elif sub_support_of_o_from_least_support == 0: + least.append(o) + elif sub_support_of_o_from_least_support < 0: + least = [o] + + return least + def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None, threshold : float = 0.5) -> Tuple[Option, int]: turn = Turn(participants,votes,options, fs) result = turn.count(threshold=threshold) diff --git a/Server/test_election.el b/Server/test_election.el index cc851094..bba4e2fc 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -1,11 +1,3 @@ Votes: - [["L", "U", "W", "V", "R", "L", "S", "H", "R", "X", "T", "E"], ["A", "Z", "K", "L", "B", "Y", "D", "C", "V", "J", "U"], ["R", "T", "U", "E", "H", "L", "R", "D", "V", "S", "X", "K", "U", "J", "Z", "L"], ["W", "R", "B", "X", "T", "S", "E", "Y"], ["W", "E", "U", "T", "Y", "R", "S", "K", "A", "R"], ["U", "R", "L", "T", "R", "H"], ["H", "J", "Y", "X", "A", "L", "Z", "V", "X", "T"], ["L", "A", "U", "X", "R", "D", "X"], ["C", "K", "V", "J", "T", "Z", "L", "T", "E", "D", "Y", "W", "R", "H", "U", "R", "U", "X", "L", "Z"], ["R", "C", "Y", "S", "R", "B", "A", "T", "Z", "W", "J", "U"], ["Y", "B", "A"], ["U", "V", "Y", "B", "R", "X", "E", "A", "D", "J", "Z", "S", "X", "R", "W", "U", "Z", "K", "C", "T", "H"], ["Z", "J", "X", "D", "Z", "L", "B", "U"], ["X", "R", "K", "A", "V", "Z", "B", "Y", "R", "U"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] + [["M", "S", "L", "C", "J", "L", "E", "K", "A", "T"], ["M", "K", "T", "L", "E", "S", "A", "C", "J", "L"], ["C", "S", "T", "K", "J", "A", "L"], ["C", "L", "M", "L", "E", "K", "A", "J"], ["L", "E", "A", "C", "L", "M", "K", "S", "J", "T"], ["C", "E", "L", "J", "S"], ["K", "C"], ["C", "K", "E", "S", "A", "M"], ["L", "C", "M", "A", "S", "K", "E", "J"], ["S", "M"], ["L"], ["J", "C", "E", "L"], ["L", "C", "L", "E", "T", "M", "J", "K"], ["E", "J", "K", "A", "L", "S", "C", "L", "M"], ["T"], ["K", "T", "C", "M", "E", "L", "L"], ["C", "T", "S", "A", "L", "M", "K"], ["M"], ["S", "A", "E", "L", "J", "C", "L"], ["S", "L", "K"], ["J"], ["T", "J", "L", "K", "E"], ["T"], ["E", "J", "L", "C", "S", "A", "K", "M", "T", "L"], ["K", "S", "E", "L", "C", "A", "T", "M", "L"], ["L", "J", "K", "T", "C", "E", "L", "S", "M"], ["S", "M"], ["S", "L"], ["S", "L", "M", "E", "T", "C", "A", "L", "J", "K"], ["E", "M", "S", "C", "J", "L", "L"], ["L", "S", "T"], ["E", "M", "L", "L", "K", "J", "A", "S", "C"], ["A", "C", "J", "L", "M"], ["L", "C", "E", "A", "J", "T"], ["A", "L", "S", "K", "M", "L", "E", "T", "C", "J"], ["T"], ["K", "S", "J", "T", "L"], ["L", "L", "E", "S", "K", "A", "C"], ["L", "A", "T", "C", "S", "J", "E", "K", "L", "M"], ["C", "L", "L", "A", "K", "T", "J", "E", "M", "S"], ["M", "S", "L", "K", "L", "A", "C", "T", "J"], ["S", "T", "J"], ["S", "L", "K", "A", "T", "L", "E"], ["J", "M", "A", "L", "K", "S"], ["T", "L", "M"], ["K", "T", "A"], ["L", "L"], ["T", "K"], ["T"], ["J", "L", "T", "E"], ["L"], ["M", "C", "S", "L"], ["E", "L", "J"], ["M", "S", "A", "K", "T", "L", "J", "L", "E", "C"], ["J", "M", "S", "C", "A"], ["L", "L", "M", "C", "E", "T"], ["T", "C", "S", "M", "L", "K", "A", "E", "L", "J"], ["M", "E", "C", "S", "A", "J", "L", "L"], ["S", "L", "C", "K", "A"], ["A", "S", "M", "C", "E", "L", "T", "J", "K", "L"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] Thrown: -J -B -C -S -V -D -K -W diff --git a/Server/test_election.py b/Server/test_election.py index ccf3e4e1..1247a2b6 100644 --- a/Server/test_election.py +++ b/Server/test_election.py @@ -38,7 +38,7 @@ def test_count_votes(): votes = json.loads(votes) assert ballot[0] == votes - assert winner == str(result) + assert winner.strip() == str(result) # Testing on one special ballot options = ["A","B","C"] From a66857bd0e5daa22e3e3255bdeface93295d4143 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sun, 9 Feb 2020 16:03:40 +0100 Subject: [PATCH 33/66] Removed variable threshold. --- Server/election.el | 5 +++++ Server/election.py | 42 +++++++++++++++++++----------------------- 2 files changed, 24 insertions(+), 23 deletions(-) create mode 100644 Server/election.el diff --git a/Server/election.el b/Server/election.el new file mode 100644 index 00000000..36765aa8 --- /dev/null +++ b/Server/election.el @@ -0,0 +1,5 @@ +Votes: + [["A", "B", "C"], ["C", "A", "B"], ["A", "B", "C"]] +Thrown: +Winner: +C diff --git a/Server/election.py b/Server/election.py index 668507f5..77ab3c3e 100644 --- a/Server/election.py +++ b/Server/election.py @@ -16,7 +16,7 @@ def __init__(self, votes : List[Vote], participants : int, options : List[Option if len(votes) < participants: votes.extend([["NoneOfTheOtherOptions"] for x in range(participants-len(votes))]) - + self.threshold = 0.5 self.__options__ = { key : list(filter(lambda v:v[-1] == key, votes)) for key in options } if fs != None: @@ -52,43 +52,39 @@ def __resort(self,votes : List[Vote]): Once an option reached a threshold of support, the counting is over. To win the election an option has to have more than limit*100 % support. - Parameters: - - threshold : float = 0.5 := Percentages necessary to win for an election Returns: Either - (winner_name,support) : Tuple[str,List[Vote] - If one option has won - [(one_winner,support),(other_winner,support)] : List[Tuple[str,List[Vote]]] - If no side could unite the necessary support - False - If Error has occured and invalid data been provided. """ - def count(self, threshold : float =0.5): - # If threshold were less than 50%, there could be multiple - # winners, which is impossible under this setup. - if threshold < 0.5 or threshold > 1: - return False + def count(self): + threshold = 0.5 winner = None is_infinite_loop = 0 while winner == None: - winners = filter(lambda o: (o/self.participants > threshold),self.__options__) + winners = list(filter(lambda o: (len(self.__options__[o])/self.participants > threshold),self.__options__)) if len(winners) == 1: winner = winners[0] - - least = self.least() - - for s in least: - print(f"{s[0]}",file=self.result_file) - self.__resort(self.__options__[s[0]]) - self.__options__.pop(s[0],None) + elif len(winners) == 2: + winner = winners + else: + least = self.least() + for s in least: + print(f"{s[0]}",file=self.result_file) + self.__resort(self.__options__[s[0]]) + self.__options__.pop(s[0],None) print(f"Winner:\n{winner}", file=self.result_file) - return winner + return (winner, len(self.__options__[winner])/self.participants) def least(self): - least = [] options = list(self.__options__) - for o in options: - sub_support_of_o_from_least_support = (len(self.__options__[o])/self.participants) - (sum([len(self.__options__[least_option]) for least_option in least])) - + least = [options[0]] + for o in options[1:]: + sub_support_of_o_from_least_support = (len(self.__options__[o])/self.participants) - sum([len(self.__options__[least_option]) for least_option in least]) + print(sub_support_of_o_from_least_support) if (sub_support_of_o_from_least_support > 0) or (o == "NoneOfTheOtherOptions"): # Ignore this option continue @@ -99,9 +95,9 @@ def least(self): return least -def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None, threshold : float = 0.5) -> Tuple[Option, int]: +def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None) -> Tuple[Option, int]: turn = Turn(participants,votes,options, fs) - result = turn.count(threshold=threshold) + result = turn.count() if fs != None: fs.close() From 99b5fb9d3964ee20f70ce4c0008ec88f3924812c Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sun, 9 Feb 2020 16:08:29 +0100 Subject: [PATCH 34/66] Solved a bug with the nonexistent NoneOfTheOtherOptions, if nobody chosen it in the first term. NoneOfTheOtherOptions must always be an option to select. --- Server/election.py | 3 ++- Server/test_election.el | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Server/election.py b/Server/election.py index 77ab3c3e..3d3e67a1 100644 --- a/Server/election.py +++ b/Server/election.py @@ -18,7 +18,8 @@ def __init__(self, votes : List[Vote], participants : int, options : List[Option self.threshold = 0.5 self.__options__ = { key : list(filter(lambda v:v[-1] == key, votes)) for key in options } - + if "NoneOfTheOtherOptions" not in list(self.__options__): + self.__options__["NoneOfTheOtherOptions"] = [] if fs != None: self.result_file = fs else: diff --git a/Server/test_election.el b/Server/test_election.el index bba4e2fc..b47c0368 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -1,3 +1,7 @@ Votes: - [["M", "S", "L", "C", "J", "L", "E", "K", "A", "T"], ["M", "K", "T", "L", "E", "S", "A", "C", "J", "L"], ["C", "S", "T", "K", "J", "A", "L"], ["C", "L", "M", "L", "E", "K", "A", "J"], ["L", "E", "A", "C", "L", "M", "K", "S", "J", "T"], ["C", "E", "L", "J", "S"], ["K", "C"], ["C", "K", "E", "S", "A", "M"], ["L", "C", "M", "A", "S", "K", "E", "J"], ["S", "M"], ["L"], ["J", "C", "E", "L"], ["L", "C", "L", "E", "T", "M", "J", "K"], ["E", "J", "K", "A", "L", "S", "C", "L", "M"], ["T"], ["K", "T", "C", "M", "E", "L", "L"], ["C", "T", "S", "A", "L", "M", "K"], ["M"], ["S", "A", "E", "L", "J", "C", "L"], ["S", "L", "K"], ["J"], ["T", "J", "L", "K", "E"], ["T"], ["E", "J", "L", "C", "S", "A", "K", "M", "T", "L"], ["K", "S", "E", "L", "C", "A", "T", "M", "L"], ["L", "J", "K", "T", "C", "E", "L", "S", "M"], ["S", "M"], ["S", "L"], ["S", "L", "M", "E", "T", "C", "A", "L", "J", "K"], ["E", "M", "S", "C", "J", "L", "L"], ["L", "S", "T"], ["E", "M", "L", "L", "K", "J", "A", "S", "C"], ["A", "C", "J", "L", "M"], ["L", "C", "E", "A", "J", "T"], ["A", "L", "S", "K", "M", "L", "E", "T", "C", "J"], ["T"], ["K", "S", "J", "T", "L"], ["L", "L", "E", "S", "K", "A", "C"], ["L", "A", "T", "C", "S", "J", "E", "K", "L", "M"], ["C", "L", "L", "A", "K", "T", "J", "E", "M", "S"], ["M", "S", "L", "K", "L", "A", "C", "T", "J"], ["S", "T", "J"], ["S", "L", "K", "A", "T", "L", "E"], ["J", "M", "A", "L", "K", "S"], ["T", "L", "M"], ["K", "T", "A"], ["L", "L"], ["T", "K"], ["T"], ["J", "L", "T", "E"], ["L"], ["M", "C", "S", "L"], ["E", "L", "J"], ["M", "S", "A", "K", "T", "L", "J", "L", "E", "C"], ["J", "M", "S", "C", "A"], ["L", "L", "M", "C", "E", "T"], ["T", "C", "S", "M", "L", "K", "A", "E", "L", "J"], ["M", "E", "C", "S", "A", "J", "L", "L"], ["S", "L", "C", "K", "A"], ["A", "S", "M", "C", "E", "L", "T", "J", "K", "L"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] + [["K", "H", "I", "P", "S", "A", "N", "E", "L", "G", "Z", "P", "M", "N", "R", "P", "I"], ["N", "Z"], ["G", "I", "Z", "H", "N", "I", "P", "S", "K", "R", "P", "E", "N", "Z", "H", "M", "G", "A", "P", "P", "L", "P"], ["N", "P", "M", "A", "I", "G", "P", "P", "S", "Z", "P", "I", "H", "R", "R", "G", "E", "N", "P", "L", "K", "H", "Z"], ["A", "P", "K", "M", "I", "P", "N", "R", "P"], ["L", "P", "G", "H", "M", "R", "Z"], ["N", "I"], ["H", "S", "P", "M", "K", "Z", "P", "N", "L", "P", "R", "R", "P", "Z", "G", "A", "E", "P", "H", "I"], ["H", "A", "R", "I", "M", "P", "H", "G", "S", "E"], ["P", "H", "R", "P", "L"], ["L", "K", "R", "G", "M", "S", "Z", "G", "H", "P", "P", "H", "P", "P", "P", "Z", "E", "A", "I", "R", "N", "N"], ["I", "A", "G"], ["P"], ["H", "N", "I", "E", "R", "P", "R", "A", "P", "S", "L", "G", "G", "K"], ["N", "M", "N", "R", "L", "Z", "G", "G", "H", "I", "I", "P", "R", "E", "P", "P", "A", "P", "P"], ["S", "I", "N", "Z", "M", "I", "N", "P", "K", "P", "E", "P", "G", "L", "Z", "H"], ["A", "P", "I", "H", "N", "P", "G", "H", "I", "P", "P", "S", "G", "P", "Z", "N", "Z", "K", "M", "R", "R", "L", "E"], ["H", "Z", "N", "N", "E"], ["R", "E", "P", "P", "P", "M", "Z", "K", "A", "L", "N", "H", "Z", "G", "I", "P"], ["R", "Z", "G", "H", "N", "K", "P", "M", "E", "Z", "P", "H", "N", "P", "R", "L", "I", "G", "S", "P"], ["P"], ["P", "G", "P", "R", "M", "G", "H", "Z", "R", "E", "P"], ["G", "H", "S", "P", "E"], ["I", "L", "H", "Z", "G", "S", "P", "I", "P", "N", "M", "Z", "N", "H", "P", "E", "P"], ["R", "P", "I", "K", "Z", "I", "L", "P", "Z", "N", "P", "N"], ["K", "G", "I", "P", "P", "P", "H", "S", "Z", "R", "Z", "P", "E", "R", "A", "N", "G", "L", "M", "N"], ["P", "M", "H", "E", "R", "I", "P", "P", "A", "I", "Z", "P", "P", "G"], ["L"], ["R", "R", "I", "P", "N", "P", "P", "P", "Z", "H", "A", "G", "P", "G", "N"], ["P", "Z", "H", "E", "M", "L", "K", "H", "G", "P", "R", "I", "N", "G", "P", "Z", "R", "S", "N", "I", "A", "P", "P"], ["Z", "P"], ["E", "N", "R", "H", "P", "P", "M"], ["R", "I", "K", "N", "Z", "S", "E", "L", "P", "H", "H", "G", "P"], ["K", "E", "I", "Z", "P"], ["H", "G", "R", "H"], ["P", "N", "Z", "Z", "P", "I", "R", "P", "G", "S", "H", "G", "I", "P", "M", "A", "E"], ["K", "A", "S", "M", "Z", "P", "L", "Z", "I", "H", "E", "P", "I"], ["A", "H", "M", "Z", "R", "Z", "G", "P", "P", "I", "R", "P", "L"], ["N", "R", "H", "P", "S", "R", "E", "I", "M", "P", "N", "P", "P", "A", "Z", "G"], ["H", "M", "G", "Z", "P", "R", "P", "N", "L", "P"], ["H", "I", "P", "P", "P", "I", "N", "G", "K", "R", "G", "R", "M", "A", "P", "P", "L", "Z", "S"], ["G", "L", "P", "N", "P", "G", "E", "I", "I", "Z", "H", "M", "Z", "N", "R"], ["K", "S", "I", "H", "N", "P", "P", "N"], ["Z", "P", "E", "P", "K", "H", "I", "N", "N", "R", "P", "S", "I", "M", "G", "P", "H"], ["E", "P", "Z", "L", "N", "P", "H", "K", "I", "P", "P"], ["H", "M", "S", "Z", "G", "P", "H", "P", "E", "R", "I", "Z", "L", "I", "N", "K", "N", "P", "P", "R", "P"], ["L", "E", "R", "K", "P", "Z", "Z", "M", "N", "S", "R", "P", "P"], ["H", "I", "P", "K", "R", "G", "Z", "N", "H", "P", "A", "L", "I", "P", "P", "N", "P", "E", "G", "Z"], ["A", "P", "H", "P", "S", "H", "P", "P", "K", "R", "Z", "I", "G", "N", "E", "I", "M", "N", "G", "Z"], ["Z", "E", "P", "N", "N", "P", "P", "P", "R", "K", "P"], ["P", "P", "H", "M", "Z", "H", "P", "R", "A", "P", "S", "N"], ["I", "P", "G", "R"], ["P", "N", "H", "M", "A", "Z", "L", "Z", "N", "S", "I", "R", "I", "E", "H", "R", "P", "P"], ["K", "P", "N", "R", "A", "Z", "I", "I", "S", "P", "H", "E", "Z", "M", "P", "R", "G", "H", "P", "P", "L", "G", "N"], ["P", "A", "P", "Z", "H", "E", "I", "R", "P", "P", "N", "P", "L", "M", "Z", "I", "S", "G", "H", "K", "G", "N", "R"], ["P", "M", "I", "G", "H", "P", "H", "S", "I", "Z", "P", "R", "N"], ["K", "P", "P", "N", "Z", "P"], ["Z", "P", "R", "G", "Z", "S", "I", "G", "N", "A", "P", "L", "R", "M", "P", "H", "E", "K", "H"], ["A", "K", "H", "P"], ["A", "H", "R", "K", "N", "Z", "P", "Z", "R", "P", "G", "M", "E", "S", "N", "P", "H", "P"], ["I", "R", "P", "P", "H", "R", "I", "L", "A", "H", "G", "K", "G", "E", "N", "Z"], ["M", "P", "Z", "G", "I", "G", "P", "K", "N", "H", "S", "L", "P", "P", "H", "N", "R"], ["M", "Z", "N", "L", "H", "K", "I", "I", "P", "G", "G", "A", "P", "R", "E", "N", "S"], ["S", "H", "I", "G", "G", "M", "Z"], ["E", "H", "R", "A", "I", "S", "Z", "M", "N", "I", "P", "Z", "N", "H", "L", "K", "P"], ["M", "P", "L", "I", "N", "H", "P", "G", "G", "P", "H", "A", "K", "N", "R", "I", "E"], ["Z", "P", "R", "G", "H", "I", "E"], ["K", "P", "M", "I", "A", "P", "L", "P", "P", "N"], ["I", "H", "M", "K", "P", "Z", "P", "E", "N", "P", "S", "H", "R", "L", "P", "N", "G", "P", "R", "G", "A", "I"], ["P", "Z", "M", "R", "E", "A", "N", "H", "Z", "L", "G", "H", "R", "K", "N", "P", "I", "G"], ["H", "P", "R", "H", "S", "Z", "M", "Z", "K", "N", "I", "P", "N", "G", "P", "P", "E"], ["G", "R", "P", "Z", "L", "H", "P", "Z", "P", "E", "N", "G"], ["R", "G", "P", "A", "I", "G", "P", "M", "P", "P"], ["P", "S", "I", "E", "Z", "I", "R", "P", "H", "N", "N", "K", "Z", "R", "M", "H", "L", "G", "P"], ["P", "I", "G", "P", "P", "R", "Z", "L", "K", "H", "A", "P", "Z", "M", "N", "G", "I", "P", "H", "E", "S", "N"], ["R", "Z", "S", "G", "H", "P"], ["A", "P", "G", "K", "M", "P", "Z", "N"], ["H", "R", "I", "R", "P", "Z", "P", "M", "P", "H", "N", "Z", "K", "P", "I"], ["S", "M", "G", "Z", "E", "H", "G", "H", "P"], ["E", "A", "G", "M", "S", "N", "L", "N"], ["P", "P", "I", "N", "I", "Z", "A", "P", "R", "H", "G", "P", "E"], ["P", "I", "P", "Z", "G", "P", "R", "G", "R", "I", "N", "A", "E", "H", "L", "Z", "N", "S", "P"], ["R", "P", "N", "L", "G", "S", "A", "I", "H", "I", "M", "G", "R"], ["P", "I", "P", "H", "N", "M"], ["I", "Z", "P", "E", "P", "P", "I", "R"], ["G", "L", "P", "I", "R", "P", "Z", "S", "P", "I", "H"], ["P", "S", "N", "G", "H", "G"], ["L", "I", "S", "I", "Z", "Z", "K", "P", "N", "H", "P", "A", "R", "G", "M", "H", "G", "P", "P"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] Thrown: +A +L +S +Z From dbdedb2755241cbaa98f94e88ff6ddc8b38c0588 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sun, 9 Feb 2020 16:50:28 +0100 Subject: [PATCH 35/66] Removing all instances of an elimanted options. --- Server/election.py | 10 ++++++++-- Server/test_election.el | 17 +++++++++++++---- Server/test_election.py | 3 +-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Server/election.py b/Server/election.py index 3d3e67a1..5abd0fd4 100644 --- a/Server/election.py +++ b/Server/election.py @@ -74,18 +74,24 @@ def count(self): for s in least: print(f"{s[0]}",file=self.result_file) self.__resort(self.__options__[s[0]]) + + # If not all instances of this option are removed, KeyErrors + # would be thrown once count() meet them again. + for option in self.__options__: + self.__options__[option] = [[alternative for alternative in v if alternative != s] for v in self.__options__[option]] self.__options__.pop(s[0],None) + if type(winner) == type(""): + winner = (winner, len(self.__options__[winner])/self.participants) print(f"Winner:\n{winner}", file=self.result_file) - return (winner, len(self.__options__[winner])/self.participants) + return winner def least(self): options = list(self.__options__) least = [options[0]] for o in options[1:]: sub_support_of_o_from_least_support = (len(self.__options__[o])/self.participants) - sum([len(self.__options__[least_option]) for least_option in least]) - print(sub_support_of_o_from_least_support) if (sub_support_of_o_from_least_support > 0) or (o == "NoneOfTheOtherOptions"): # Ignore this option continue diff --git a/Server/test_election.el b/Server/test_election.el index b47c0368..a74e5ac0 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -1,7 +1,16 @@ Votes: - [["K", "H", "I", "P", "S", "A", "N", "E", "L", "G", "Z", "P", "M", "N", "R", "P", "I"], ["N", "Z"], ["G", "I", "Z", "H", "N", "I", "P", "S", "K", "R", "P", "E", "N", "Z", "H", "M", "G", "A", "P", "P", "L", "P"], ["N", "P", "M", "A", "I", "G", "P", "P", "S", "Z", "P", "I", "H", "R", "R", "G", "E", "N", "P", "L", "K", "H", "Z"], ["A", "P", "K", "M", "I", "P", "N", "R", "P"], ["L", "P", "G", "H", "M", "R", "Z"], ["N", "I"], ["H", "S", "P", "M", "K", "Z", "P", "N", "L", "P", "R", "R", "P", "Z", "G", "A", "E", "P", "H", "I"], ["H", "A", "R", "I", "M", "P", "H", "G", "S", "E"], ["P", "H", "R", "P", "L"], ["L", "K", "R", "G", "M", "S", "Z", "G", "H", "P", "P", "H", "P", "P", "P", "Z", "E", "A", "I", "R", "N", "N"], ["I", "A", "G"], ["P"], ["H", "N", "I", "E", "R", "P", "R", "A", "P", "S", "L", "G", "G", "K"], ["N", "M", "N", "R", "L", "Z", "G", "G", "H", "I", "I", "P", "R", "E", "P", "P", "A", "P", "P"], ["S", "I", "N", "Z", "M", "I", "N", "P", "K", "P", "E", "P", "G", "L", "Z", "H"], ["A", "P", "I", "H", "N", "P", "G", "H", "I", "P", "P", "S", "G", "P", "Z", "N", "Z", "K", "M", "R", "R", "L", "E"], ["H", "Z", "N", "N", "E"], ["R", "E", "P", "P", "P", "M", "Z", "K", "A", "L", "N", "H", "Z", "G", "I", "P"], ["R", "Z", "G", "H", "N", "K", "P", "M", "E", "Z", "P", "H", "N", "P", "R", "L", "I", "G", "S", "P"], ["P"], ["P", "G", "P", "R", "M", "G", "H", "Z", "R", "E", "P"], ["G", "H", "S", "P", "E"], ["I", "L", "H", "Z", "G", "S", "P", "I", "P", "N", "M", "Z", "N", "H", "P", "E", "P"], ["R", "P", "I", "K", "Z", "I", "L", "P", "Z", "N", "P", "N"], ["K", "G", "I", "P", "P", "P", "H", "S", "Z", "R", "Z", "P", "E", "R", "A", "N", "G", "L", "M", "N"], ["P", "M", "H", "E", "R", "I", "P", "P", "A", "I", "Z", "P", "P", "G"], ["L"], ["R", "R", "I", "P", "N", "P", "P", "P", "Z", "H", "A", "G", "P", "G", "N"], ["P", "Z", "H", "E", "M", "L", "K", "H", "G", "P", "R", "I", "N", "G", "P", "Z", "R", "S", "N", "I", "A", "P", "P"], ["Z", "P"], ["E", "N", "R", "H", "P", "P", "M"], ["R", "I", "K", "N", "Z", "S", "E", "L", "P", "H", "H", "G", "P"], ["K", "E", "I", "Z", "P"], ["H", "G", "R", "H"], ["P", "N", "Z", "Z", "P", "I", "R", "P", "G", "S", "H", "G", "I", "P", "M", "A", "E"], ["K", "A", "S", "M", "Z", "P", "L", "Z", "I", "H", "E", "P", "I"], ["A", "H", "M", "Z", "R", "Z", "G", "P", "P", "I", "R", "P", "L"], ["N", "R", "H", "P", "S", "R", "E", "I", "M", "P", "N", "P", "P", "A", "Z", "G"], ["H", "M", "G", "Z", "P", "R", "P", "N", "L", "P"], ["H", "I", "P", "P", "P", "I", "N", "G", "K", "R", "G", "R", "M", "A", "P", "P", "L", "Z", "S"], ["G", "L", "P", "N", "P", "G", "E", "I", "I", "Z", "H", "M", "Z", "N", "R"], ["K", "S", "I", "H", "N", "P", "P", "N"], ["Z", "P", "E", "P", "K", "H", "I", "N", "N", "R", "P", "S", "I", "M", "G", "P", "H"], ["E", "P", "Z", "L", "N", "P", "H", "K", "I", "P", "P"], ["H", "M", "S", "Z", "G", "P", "H", "P", "E", "R", "I", "Z", "L", "I", "N", "K", "N", "P", "P", "R", "P"], ["L", "E", "R", "K", "P", "Z", "Z", "M", "N", "S", "R", "P", "P"], ["H", "I", "P", "K", "R", "G", "Z", "N", "H", "P", "A", "L", "I", "P", "P", "N", "P", "E", "G", "Z"], ["A", "P", "H", "P", "S", "H", "P", "P", "K", "R", "Z", "I", "G", "N", "E", "I", "M", "N", "G", "Z"], ["Z", "E", "P", "N", "N", "P", "P", "P", "R", "K", "P"], ["P", "P", "H", "M", "Z", "H", "P", "R", "A", "P", "S", "N"], ["I", "P", "G", "R"], ["P", "N", "H", "M", "A", "Z", "L", "Z", "N", "S", "I", "R", "I", "E", "H", "R", "P", "P"], ["K", "P", "N", "R", "A", "Z", "I", "I", "S", "P", "H", "E", "Z", "M", "P", "R", "G", "H", "P", "P", "L", "G", "N"], ["P", "A", "P", "Z", "H", "E", "I", "R", "P", "P", "N", "P", "L", "M", "Z", "I", "S", "G", "H", "K", "G", "N", "R"], ["P", "M", "I", "G", "H", "P", "H", "S", "I", "Z", "P", "R", "N"], ["K", "P", "P", "N", "Z", "P"], ["Z", "P", "R", "G", "Z", "S", "I", "G", "N", "A", "P", "L", "R", "M", "P", "H", "E", "K", "H"], ["A", "K", "H", "P"], ["A", "H", "R", "K", "N", "Z", "P", "Z", "R", "P", "G", "M", "E", "S", "N", "P", "H", "P"], ["I", "R", "P", "P", "H", "R", "I", "L", "A", "H", "G", "K", "G", "E", "N", "Z"], ["M", "P", "Z", "G", "I", "G", "P", "K", "N", "H", "S", "L", "P", "P", "H", "N", "R"], ["M", "Z", "N", "L", "H", "K", "I", "I", "P", "G", "G", "A", "P", "R", "E", "N", "S"], ["S", "H", "I", "G", "G", "M", "Z"], ["E", "H", "R", "A", "I", "S", "Z", "M", "N", "I", "P", "Z", "N", "H", "L", "K", "P"], ["M", "P", "L", "I", "N", "H", "P", "G", "G", "P", "H", "A", "K", "N", "R", "I", "E"], ["Z", "P", "R", "G", "H", "I", "E"], ["K", "P", "M", "I", "A", "P", "L", "P", "P", "N"], ["I", "H", "M", "K", "P", "Z", "P", "E", "N", "P", "S", "H", "R", "L", "P", "N", "G", "P", "R", "G", "A", "I"], ["P", "Z", "M", "R", "E", "A", "N", "H", "Z", "L", "G", "H", "R", "K", "N", "P", "I", "G"], ["H", "P", "R", "H", "S", "Z", "M", "Z", "K", "N", "I", "P", "N", "G", "P", "P", "E"], ["G", "R", "P", "Z", "L", "H", "P", "Z", "P", "E", "N", "G"], ["R", "G", "P", "A", "I", "G", "P", "M", "P", "P"], ["P", "S", "I", "E", "Z", "I", "R", "P", "H", "N", "N", "K", "Z", "R", "M", "H", "L", "G", "P"], ["P", "I", "G", "P", "P", "R", "Z", "L", "K", "H", "A", "P", "Z", "M", "N", "G", "I", "P", "H", "E", "S", "N"], ["R", "Z", "S", "G", "H", "P"], ["A", "P", "G", "K", "M", "P", "Z", "N"], ["H", "R", "I", "R", "P", "Z", "P", "M", "P", "H", "N", "Z", "K", "P", "I"], ["S", "M", "G", "Z", "E", "H", "G", "H", "P"], ["E", "A", "G", "M", "S", "N", "L", "N"], ["P", "P", "I", "N", "I", "Z", "A", "P", "R", "H", "G", "P", "E"], ["P", "I", "P", "Z", "G", "P", "R", "G", "R", "I", "N", "A", "E", "H", "L", "Z", "N", "S", "P"], ["R", "P", "N", "L", "G", "S", "A", "I", "H", "I", "M", "G", "R"], ["P", "I", "P", "H", "N", "M"], ["I", "Z", "P", "E", "P", "P", "I", "R"], ["G", "L", "P", "I", "R", "P", "Z", "S", "P", "I", "H"], ["P", "S", "N", "G", "H", "G"], ["L", "I", "S", "I", "Z", "Z", "K", "P", "N", "H", "P", "A", "R", "G", "M", "H", "G", "P", "P"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] + [["N", "V", "S", "K", "I"], ["A", "A", "K", "I", "A", "V"], ["V", "X", "I", "A", "P", "V", "A", "R", "I", "K", "Y"], ["S", "A", "A"], ["A", "S"], ["R", "I", "V", "X", "A", "K", "Y", "I", "P", "A", "B", "S", "N", "P", "V", "Z"], ["A", "P", "X", "R", "V", "S", "I", "N", "A", "K", "A", "Z", "V", "B"], ["A", "A", "I", "V", "Y", "A", "I", "P"], ["B", "I", "A", "P", "P", "A", "R", "V"], ["I", "N", "I", "A", "X", "R", "P"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] Thrown: -A -L -S +N +K +R +X +B +Y Z +S +I +V +A +P +N diff --git a/Server/test_election.py b/Server/test_election.py index 1247a2b6..2d7d297a 100644 --- a/Server/test_election.py +++ b/Server/test_election.py @@ -37,7 +37,6 @@ def test_count_votes(): (_,_,votes) = votes.partition("Votes:\n") votes = json.loads(votes) - assert ballot[0] == votes assert winner.strip() == str(result) # Testing on one special ballot @@ -45,7 +44,7 @@ def test_count_votes(): votes = [["A","B","C"],["A","C","B"],["A","C","B"]] participants = len(votes) - assert ("B", 2) == election.count_votes(votes,participants,options) + assert ("B", 2/3) == election.count_votes(votes,participants,options) participants = 10 assert ("NoneOfTheOtherOptions", 8) == election.count_votes(votes,participants,options) From d7348701eb49d0fa28d1aa05972773352ded640c Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sun, 9 Feb 2020 19:59:22 +0100 Subject: [PATCH 36/66] Trying to filter all occurences of s out if it occures in least. Currently gets into an infinite loop because of NoneOfTheOtherOptions --- Server/election.el | 2 +- Server/election.py | 14 +++++++++++--- Server/test_election.el | 18 ++++++------------ Server/test_election.py | 5 ++--- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Server/election.el b/Server/election.el index 36765aa8..fec61085 100644 --- a/Server/election.el +++ b/Server/election.el @@ -2,4 +2,4 @@ Votes: [["A", "B", "C"], ["C", "A", "B"], ["A", "B", "C"]] Thrown: Winner: -C +('C', 1.0) diff --git a/Server/election.py b/Server/election.py index 5abd0fd4..d0f4be81 100644 --- a/Server/election.py +++ b/Server/election.py @@ -69,17 +69,23 @@ def count(self): winner = winners[0] elif len(winners) == 2: winner = winners + elif is_infinite_loop >= 10**7: + winner = self.__options__ + break else: least = self.least() for s in least: + print(s) print(f"{s[0]}",file=self.result_file) self.__resort(self.__options__[s[0]]) # If not all instances of this option are removed, KeyErrors - # would be thrown once count() meet them again. + # would be thrown once count() met them again. for option in self.__options__: - self.__options__[option] = [[alternative for alternative in v if alternative != s] for v in self.__options__[option]] - self.__options__.pop(s[0],None) + self.__options__[option] = [list(filter(lambda a:a != s,vote)) for vote in self.__options__[option]] + + self.__options__.pop(s,None) + if type(winner) == type(""): winner = (winner, len(self.__options__[winner])/self.participants) @@ -100,6 +106,8 @@ def least(self): elif sub_support_of_o_from_least_support < 0: least = [o] + # Never make "NoneOfTheOtherOptions" not an option. + print(least) return least def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None) -> Tuple[Option, int]: diff --git a/Server/test_election.el b/Server/test_election.el index a74e5ac0..e4cbe029 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -1,16 +1,10 @@ Votes: - [["N", "V", "S", "K", "I"], ["A", "A", "K", "I", "A", "V"], ["V", "X", "I", "A", "P", "V", "A", "R", "I", "K", "Y"], ["S", "A", "A"], ["A", "S"], ["R", "I", "V", "X", "A", "K", "Y", "I", "P", "A", "B", "S", "N", "P", "V", "Z"], ["A", "P", "X", "R", "V", "S", "I", "N", "A", "K", "A", "Z", "V", "B"], ["A", "A", "I", "V", "Y", "A", "I", "P"], ["B", "I", "A", "P", "P", "A", "R", "V"], ["I", "N", "I", "A", "X", "R", "P"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] + [["R", "I", "G"], ["P", "V", "G"], ["G", "V", "I", "P"], ["V", "I"], ["R", "I", "G", "Z", "V", "P"], ["G", "I", "R", "V"], ["P"], ["V", "Z", "R"], ["V", "G", "R", "P", "I"], ["V", "I", "P", "R", "G", "Z"], ["V", "R", "P", "Z", "G", "I"], ["Z", "G"], ["I", "V", "G", "R"], ["I", "Z", "G"], ["R", "V", "P", "G", "Z"], ["Z", "P"], ["P", "Z", "R", "G", "V", "I"], ["V"], ["G"], ["I", "G", "V", "R", "P"], ["P", "Z"], ["P", "I", "V", "Z", "G", "R"], ["V", "I", "P"], ["R"], ["P", "Z", "I", "R", "G", "V"], ["G"], ["Z", "V", "R"], ["P"], ["Z"], ["V", "G", "Z"], ["G", "Z", "R", "V", "P", "I"], ["I", "Z"], ["P", "V"], ["I", "G", "V", "Z", "P"], ["I", "Z", "G", "R"], ["Z", "I"], ["Z", "I", "V", "R", "P", "G"], ["G", "P", "Z", "V", "I"], ["P", "V"], ["G", "I", "R"], ["R", "G", "P", "V", "I", "Z"], ["V", "Z"], ["P", "Z", "G", "I", "V"], ["R", "I", "V", "P", "Z", "G"], ["R", "P", "G"], ["R", "Z", "V", "G", "P"], ["V", "I", "G", "P", "Z", "R"], ["V", "Z", "R", "G"], ["I", "G", "R", "P"], ["G"], ["Z", "G", "R", "I", "P"], ["G", "P", "R", "Z", "V"], ["V", "Z", "R", "G"], ["Z", "V", "P"], ["V"], ["R", "Z"], ["P", "G"], ["P", "Z", "V", "G"], ["R", "I", "Z"], ["Z", "P", "G", "V", "R"], ["G", "P", "I"], ["P", "R", "V"], ["I"], ["V", "R", "Z", "I", "P"], ["G"], ["P"], ["V", "Z", "I", "G", "R"], ["Z", "V", "I", "R", "G", "P"], ["P", "Z", "R", "I"], ["Z", "R"], ["G", "P", "V", "R"], ["R"], ["V", "Z", "P", "R", "G"], ["V"], ["R", "G"], ["V"], ["V", "I", "R"], ["Z", "P"], ["V"], ["R"], ["R", "P", "V", "Z"], ["P", "G"], ["G", "I", "R", "P", "V"], ["G", "P", "R", "Z"], ["V", "P", "Z"], ["V", "G", "R"], ["I", "P", "Z"], ["V", "R", "G"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] Thrown: -N -K -R -X -B -Y -Z -S I -V -A +G P -N +R +V +Winner: +('Z', 0.5052631578947369) diff --git a/Server/test_election.py b/Server/test_election.py index 2d7d297a..5f54b32f 100644 --- a/Server/test_election.py +++ b/Server/test_election.py @@ -19,7 +19,7 @@ def generate_ballot(): def test_count_votes(): # Random ballot test cases - for i in range(1000): + for i in range(100): ballot = generate_ballot() test_file = open("test_election.el","w") result = election.count_votes(ballot[0],ballot[1],ballot[2],fs=test_file) @@ -36,15 +36,14 @@ def test_count_votes(): (thrown,_,winner) = election_log.partition("\nWinner:\n") (_,_,votes) = votes.partition("Votes:\n") votes = json.loads(votes) - assert winner.strip() == str(result) # Testing on one special ballot options = ["A","B","C"] votes = [["A","B","C"],["A","C","B"],["A","C","B"]] participants = len(votes) - assert ("B", 2/3) == election.count_votes(votes,participants,options) + participants = 10 assert ("NoneOfTheOtherOptions", 8) == election.count_votes(votes,participants,options) From b0d84b0e119e222b2038dcd9e43222da8a721486 Mon Sep 17 00:00:00 2001 From: abbashan03 Date: Mon, 10 Feb 2020 17:30:31 +0100 Subject: [PATCH 37/66] If one of least is NoneOfTheOtherOptions, those votes currently having that as primary alternative, are resorted but NoneOfTheOptions as an Option isn't removed and if a vote has no further alternatives, NoneOfTheOtherOptions is the default. Thus nobody can rest their victory of an election on a silent majority, because an election won by NoneOfTheOtherOptions, keeps the status quo. --- Server/election.py | 30 +++++++++++++++--------------- Server/test_election.el | 9 ++------- requirements.txt | 17 +++++++++++++++-- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/Server/election.py b/Server/election.py index d0f4be81..3dd7234a 100644 --- a/Server/election.py +++ b/Server/election.py @@ -35,13 +35,12 @@ def __init__(self, votes : List[Vote], participants : int, options : List[Option def __resort(self,votes : List[Vote]): for vote in votes: - old = vote[-1] - if old == "NoneOfTheOtherOptions": - continue - elif len(vote) == 1: + vote.pop() + # The silent majority doen't support anybody, when they don't vote + # for them. + if vote == []: self.__options__["NoneOfTheOtherOptions"].append(["NoneOfTheOtherOptions"]) else: - vote.pop() self.__options__[vote[-1]].append(vote) """count all votes in self.__options__. @@ -62,29 +61,30 @@ def __resort(self,votes : List[Vote]): def count(self): threshold = 0.5 winner = None - is_infinite_loop = 0 while winner == None: winners = list(filter(lambda o: (len(self.__options__[o])/self.participants > threshold),self.__options__)) if len(winners) == 1: winner = winners[0] elif len(winners) == 2: winner = winners - elif is_infinite_loop >= 10**7: - winner = self.__options__ - break else: least = self.least() for s in least: print(s) - print(f"{s[0]}",file=self.result_file) - self.__resort(self.__options__[s[0]]) + print(f"{s}",file=self.result_file) + self.__resort(self.__options__[s) # If not all instances of this option are removed, KeyErrors # would be thrown once count() met them again. - for option in self.__options__: - self.__options__[option] = [list(filter(lambda a:a != s,vote)) for vote in self.__options__[option]] - - self.__options__.pop(s,None) + # Only remove those NoneOfTheOtherOptions, that are currently in the minority + # in the first rank. + # This is, so one can always choose NoneOfTheOtherOptions as an Option, + # even if it is the 100. alternative + if s != "NoneOfTheOtherOptions": + for option in self.__options__: + self.__options__[option] = [list(filter(lambda a:a != s,vote)) for vote in self.__options__[option]] + + self.__options__.pop(s,None) if type(winner) == type(""): diff --git a/Server/test_election.el b/Server/test_election.el index e4cbe029..300be351 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -1,10 +1,5 @@ Votes: - [["R", "I", "G"], ["P", "V", "G"], ["G", "V", "I", "P"], ["V", "I"], ["R", "I", "G", "Z", "V", "P"], ["G", "I", "R", "V"], ["P"], ["V", "Z", "R"], ["V", "G", "R", "P", "I"], ["V", "I", "P", "R", "G", "Z"], ["V", "R", "P", "Z", "G", "I"], ["Z", "G"], ["I", "V", "G", "R"], ["I", "Z", "G"], ["R", "V", "P", "G", "Z"], ["Z", "P"], ["P", "Z", "R", "G", "V", "I"], ["V"], ["G"], ["I", "G", "V", "R", "P"], ["P", "Z"], ["P", "I", "V", "Z", "G", "R"], ["V", "I", "P"], ["R"], ["P", "Z", "I", "R", "G", "V"], ["G"], ["Z", "V", "R"], ["P"], ["Z"], ["V", "G", "Z"], ["G", "Z", "R", "V", "P", "I"], ["I", "Z"], ["P", "V"], ["I", "G", "V", "Z", "P"], ["I", "Z", "G", "R"], ["Z", "I"], ["Z", "I", "V", "R", "P", "G"], ["G", "P", "Z", "V", "I"], ["P", "V"], ["G", "I", "R"], ["R", "G", "P", "V", "I", "Z"], ["V", "Z"], ["P", "Z", "G", "I", "V"], ["R", "I", "V", "P", "Z", "G"], ["R", "P", "G"], ["R", "Z", "V", "G", "P"], ["V", "I", "G", "P", "Z", "R"], ["V", "Z", "R", "G"], ["I", "G", "R", "P"], ["G"], ["Z", "G", "R", "I", "P"], ["G", "P", "R", "Z", "V"], ["V", "Z", "R", "G"], ["Z", "V", "P"], ["V"], ["R", "Z"], ["P", "G"], ["P", "Z", "V", "G"], ["R", "I", "Z"], ["Z", "P", "G", "V", "R"], ["G", "P", "I"], ["P", "R", "V"], ["I"], ["V", "R", "Z", "I", "P"], ["G"], ["P"], ["V", "Z", "I", "G", "R"], ["Z", "V", "I", "R", "G", "P"], ["P", "Z", "R", "I"], ["Z", "R"], ["G", "P", "V", "R"], ["R"], ["V", "Z", "P", "R", "G"], ["V"], ["R", "G"], ["V"], ["V", "I", "R"], ["Z", "P"], ["V"], ["R"], ["R", "P", "V", "Z"], ["P", "G"], ["G", "I", "R", "P", "V"], ["G", "P", "R", "Z"], ["V", "P", "Z"], ["V", "G", "R"], ["I", "P", "Z"], ["V", "R", "G"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] + [["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"]] Thrown: -I -G -P -R -V Winner: -('Z', 0.5052631578947369) +('T', 1.0) diff --git a/requirements.txt b/requirements.txt index 0bd434a6..24a5d4eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,24 @@ +attrs==19.3.0 Click==7.0 Flask==1.1.1 gunicorn==20.0.4 +importlib-metadata==1.5.0 itsdangerous==1.1.0 Jinja2==2.10.3 MarkupSafe==1.1.1 +more-itertools==8.2.0 +mypy==0.761 +mypy-extensions==0.4.3 +packaging==20.1 +pluggy==0.13.1 +py==1.8.1 pycryptodome==3.9.4 pymongo==3.10.1 +pyparsing==2.4.6 +pytest==5.3.5 +six==1.14.0 +typed-ast==1.4.1 +typing-extensions==3.7.4.1 +wcwidth==0.1.8 Werkzeug==0.16.0 -mypy==0.761 -mypy-extensions==0.4.3 +zipp==2.2.0 From 656cceea936d731f7f22992a08b79610a2a70c64 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Mon, 10 Feb 2020 17:50:36 +0100 Subject: [PATCH 38/66] Removing any special treatment of NoneOfTheOtherOptions for now --- Server/election.py | 26 ++++++++++---------------- Server/test_election.el | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/Server/election.py b/Server/election.py index 3dd7234a..33ee8e90 100644 --- a/Server/election.py +++ b/Server/election.py @@ -18,8 +18,7 @@ def __init__(self, votes : List[Vote], participants : int, options : List[Option self.threshold = 0.5 self.__options__ = { key : list(filter(lambda v:v[-1] == key, votes)) for key in options } - if "NoneOfTheOtherOptions" not in list(self.__options__): - self.__options__["NoneOfTheOtherOptions"] = [] + if fs != None: self.result_file = fs else: @@ -38,9 +37,7 @@ def __resort(self,votes : List[Vote]): vote.pop() # The silent majority doen't support anybody, when they don't vote # for them. - if vote == []: - self.__options__["NoneOfTheOtherOptions"].append(["NoneOfTheOtherOptions"]) - else: + if vote != []: self.__options__[vote[-1]].append(vote) """count all votes in self.__options__. @@ -72,19 +69,16 @@ def count(self): for s in least: print(s) print(f"{s}",file=self.result_file) - self.__resort(self.__options__[s) + self.__resort(self.__options__[s]) # If not all instances of this option are removed, KeyErrors - # would be thrown once count() met them again. - # Only remove those NoneOfTheOtherOptions, that are currently in the minority - # in the first rank. - # This is, so one can always choose NoneOfTheOtherOptions as an Option, - # even if it is the 100. alternative - if s != "NoneOfTheOtherOptions": - for option in self.__options__: - self.__options__[option] = [list(filter(lambda a:a != s,vote)) for vote in self.__options__[option]] - - self.__options__.pop(s,None) + # would be thrown once self.count() met them again. + + for option in self.__options__: + self.__options__[option] = [list(filter(lambda a:a != s,vote)) for vote in self.__options__[option]] + + + self.__options__.pop(s,None) if type(winner) == type(""): diff --git a/Server/test_election.el b/Server/test_election.el index 300be351..a80da38b 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -1,5 +1,15 @@ Votes: - [["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"], ["T"]] + [["U", "W", "T", "B", "T", "Q"], ["B", "S", "R", "T", "W", "D", "J"], ["G", "T", "Q", "R"], ["G", "R", "T", "E", "T", "T", "D", "B", "J", "W", "U", "S"], ["D", "B", "E", "J", "T"], ["F", "T", "U", "R", "D", "E", "T", "Q"], ["W", "S", "Q", "E"], ["F", "B", "G", "U", "Q", "E", "W", "T", "R", "T", "J", "T"], ["D", "T", "R", "J", "T", "B", "F", "E", "W"], ["T", "Q", "B", "D", "T", "S", "U", "T", "E", "G", "J", "W"], ["T", "T", "T", "G", "W", "B"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] Thrown: -Winner: -('T', 1.0) +G +D +U +F +B +R +W +E +S +Q +T +J From d327e6afefdf5750fa524392e22f917ba7a606f1 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Mon, 10 Feb 2020 18:12:01 +0100 Subject: [PATCH 39/66] Realized: Turn.least() makes it possible that no options are left. --- Server/election.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Server/election.py b/Server/election.py index 33ee8e90..e02c79b6 100644 --- a/Server/election.py +++ b/Server/election.py @@ -64,6 +64,8 @@ def count(self): winner = winners[0] elif len(winners) == 2: winner = winners + elif self.__options__ == {}: + return {} else: least = self.least() for s in least: From 5293ddcedaab630e37b39cfb9dab6a011ffd0755 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Wed, 12 Feb 2020 11:20:04 +0100 Subject: [PATCH 40/66] Moved check for NoneOfTheOtherOptions into count. Rewrote body while loop in count. --- Server/election.py | 44 ++++++++++++++++++++--------------------- Server/test_election.el | 15 ++------------ 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/Server/election.py b/Server/election.py index e02c79b6..86eb090e 100644 --- a/Server/election.py +++ b/Server/election.py @@ -16,6 +16,7 @@ def __init__(self, votes : List[Vote], participants : int, options : List[Option if len(votes) < participants: votes.extend([["NoneOfTheOtherOptions"] for x in range(participants-len(votes))]) + options.append("NoneOfTheOtherOptions") self.threshold = 0.5 self.__options__ = { key : list(filter(lambda v:v[-1] == key, votes)) for key in options } @@ -39,6 +40,8 @@ def __resort(self,votes : List[Vote]): # for them. if vote != []: self.__options__[vote[-1]].append(vote) + else: + self.__options__["NoneOfTheOtherOptions"].append(["NoneOfTheOtherOptions"]) """count all votes in self.__options__. It follows the following principle: @@ -60,28 +63,27 @@ def count(self): winner = None while winner == None: winners = list(filter(lambda o: (len(self.__options__[o])/self.participants > threshold),self.__options__)) + least = self.least() + if len(winners) == 1: + # This case is the most obvious, but also leat likely winner = winners[0] - elif len(winners) == 2: - winner = winners - elif self.__options__ == {}: - return {} + elif least == list(self.__options__): + # If all the least are all the options, + # then there are only two left. + # And both of them have 50% support. + # This is equivalent to len(winners) == 2 + winner = least else: - least = self.least() - for s in least: - print(s) - print(f"{s}",file=self.result_file) - self.__resort(self.__options__[s]) - - # If not all instances of this option are removed, KeyErrors - # would be thrown once self.count() met them again. - - for option in self.__options__: - self.__options__[option] = [list(filter(lambda a:a != s,vote)) for vote in self.__options__[option]] - - - self.__options__.pop(s,None) - + # Neither case, we have to remove the least and try again. + for l in least: + self.__resort(self.__options__[l]) + # Only those, that aren't NoneOfTheOtherOptions are removed permanently from the race. + if l != "NoneOfTheOtherOptions": + self.__options__(l, None) + for o in list(self.__options__): + self.__options__[o] = list(filter(lambda a: a != l, self.__options__[o])) + if type(winner) == type(""): winner = (winner, len(self.__options__[winner])/self.participants) @@ -94,7 +96,7 @@ def least(self): least = [options[0]] for o in options[1:]: sub_support_of_o_from_least_support = (len(self.__options__[o])/self.participants) - sum([len(self.__options__[least_option]) for least_option in least]) - if (sub_support_of_o_from_least_support > 0) or (o == "NoneOfTheOtherOptions"): + if (sub_support_of_o_from_least_support > 0): # Ignore this option continue elif sub_support_of_o_from_least_support == 0: @@ -102,8 +104,6 @@ def least(self): elif sub_support_of_o_from_least_support < 0: least = [o] - # Never make "NoneOfTheOtherOptions" not an option. - print(least) return least def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None) -> Tuple[Option, int]: diff --git a/Server/test_election.el b/Server/test_election.el index a80da38b..77239852 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -1,15 +1,4 @@ Votes: - [["U", "W", "T", "B", "T", "Q"], ["B", "S", "R", "T", "W", "D", "J"], ["G", "T", "Q", "R"], ["G", "R", "T", "E", "T", "T", "D", "B", "J", "W", "U", "S"], ["D", "B", "E", "J", "T"], ["F", "T", "U", "R", "D", "E", "T", "Q"], ["W", "S", "Q", "E"], ["F", "B", "G", "U", "Q", "E", "W", "T", "R", "T", "J", "T"], ["D", "T", "R", "J", "T", "B", "F", "E", "W"], ["T", "Q", "B", "D", "T", "S", "U", "T", "E", "G", "J", "W"], ["T", "T", "T", "G", "W", "B"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] + [["R"], ["E", "U", "I", "T"], ["E", "E", "R", "I", "U", "D", "T"], ["I", "E", "D", "R", "T", "E", "R", "U"], ["E", "R", "E", "D"], ["E", "D", "R", "U", "T", "R", "E"], ["U", "E", "T"], ["R"], ["E", "R", "D", "I", "T", "U"], ["E", "U", "T", "R", "D", "I"], ["T", "D", "E", "E", "R", "U", "I", "R"], ["I", "U", "T", "E"], ["R"], ["U", "D", "T", "R"], ["T", "I", "R", "R", "D"], ["R", "R", "U", "I", "E", "D"], ["R", "E", "I", "E", "U", "D"], ["E", "R"], ["T"], ["E"], ["I", "D", "T", "E", "R"], ["R", "D", "I", "R", "T", "U", "E"], ["U"], ["U", "R", "D", "T"], ["R", "U", "E"], ["R", "E", "E", "T", "R", "D"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] Thrown: -G -D -U -F -B -R -W -E -S -Q -T -J +NoneOfTheOtherOptions From 4bf9557de251ef90b6fcad10ab6fdae94f91c6cd Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Wed, 12 Feb 2020 15:56:11 +0100 Subject: [PATCH 41/66] Rewrote Turn.count and Turn.__init__ in a more comprehensive and bug free way - I hope. --- Server/election.py | 88 ++++++++++++++--------------------------- Server/test_election.el | 1 - 2 files changed, 30 insertions(+), 59 deletions(-) diff --git a/Server/election.py b/Server/election.py index 86eb090e..62922696 100644 --- a/Server/election.py +++ b/Server/election.py @@ -1,5 +1,5 @@ from typing import List, Tuple, TextIO, Any -import sys, json +import sys, json, functools Vote = List[str] Option = str @@ -13,35 +13,20 @@ """ class Turn(): def __init__(self, votes : List[Vote], participants : int, options : List[Option], fs : TextIO = None): - if len(votes) < participants: - votes.extend([["NoneOfTheOtherOptions"] for x in range(participants-len(votes))]) - - options.append("NoneOfTheOtherOptions") - self.threshold = 0.5 - self.__options__ = { key : list(filter(lambda v:v[-1] == key, votes)) for key in options } - - if fs != None: - self.result_file = fs - else: - self.result_file = sys.stdout - - print(f"Votes:\n {json.dumps(votes)}\nThrown:", file=self.result_file) - - self.participants = participants + self.__options__ = { key : list(filter(lambda v: v[-1] == key)) for key in options } + def order(self,reverse=False): + return sorted(list(self.__options__), key = lambda k: len(self.__options__[k]), reverse=reverse) def __resort(self,votes : List[Vote]): for vote in votes: - vote.pop() - # The silent majority doen't support anybody, when they don't vote - # for them. if vote != []: self.__options__[vote[-1]].append(vote) else: - self.__options__["NoneOfTheOtherOptions"].append(["NoneOfTheOtherOptions"]) + self.potential_voters += 1 """count all votes in self.__options__. It follows the following principle: @@ -60,51 +45,38 @@ def __resort(self,votes : List[Vote]): """ def count(self): threshold = 0.5 - winner = None - while winner == None: - winners = list(filter(lambda o: (len(self.__options__[o])/self.participants > threshold),self.__options__)) - least = self.least() - - if len(winners) == 1: - # This case is the most obvious, but also leat likely - winner = winners[0] - elif least == list(self.__options__): - # If all the least are all the options, - # then there are only two left. - # And both of them have 50% support. - # This is equivalent to len(winners) == 2 - winner = least + winner = False + while type(winner) != "" or winner != None: + ordered = self.order(reverse=True) + if len(self.__options__[ordered[0]]) > (participants*threshold): + winner = ordered[0] + elif len(self.__options__[ordered[o]]) == (participants*threshold) and len(self.__options__[ordered[1]]) == (participants*threshold): + winner = None + elif self.potential_voters/self.participants > threshold: + winner = None else: - # Neither case, we have to remove the least and try again. + least = self.least() for l in least: self.__resort(self.__options__[l]) - # Only those, that aren't NoneOfTheOtherOptions are removed permanently from the race. - if l != "NoneOfTheOtherOptions": - self.__options__(l, None) - for o in list(self.__options__): - self.__options__[o] = list(filter(lambda a: a != l, self.__options__[o])) - - - if type(winner) == type(""): - winner = (winner, len(self.__options__[winner])/self.participants) - - print(f"Winner:\n{winner}", file=self.result_file) - return winner + self.__options__.pop(l) def least(self): - options = list(self.__options__) - least = [options[0]] - for o in options[1:]: - sub_support_of_o_from_least_support = (len(self.__options__[o])/self.participants) - sum([len(self.__options__[least_option]) for least_option in least]) - if (sub_support_of_o_from_least_support > 0): - # Ignore this option + ordered = self.order() + least = ([ordered[0]], len(self.__options__[ordered[0]])) + + for o in ordered: + support_for_o = len(self.__options__[o]) + if support_for_o < least[1]: + least = (o,support_for_o) + elif support_for_o == least[1]: + least[0].append(o) + least[1] += support_for_o + elif support_for_o > least[1]: continue - elif sub_support_of_o_from_least_support == 0: - least.append(o) - elif sub_support_of_o_from_least_support < 0: - least = [o] - return least + return least[0] + + def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None) -> Tuple[Option, int]: turn = Turn(participants,votes,options, fs) diff --git a/Server/test_election.el b/Server/test_election.el index 77239852..d7d148ee 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -1,4 +1,3 @@ Votes: [["R"], ["E", "U", "I", "T"], ["E", "E", "R", "I", "U", "D", "T"], ["I", "E", "D", "R", "T", "E", "R", "U"], ["E", "R", "E", "D"], ["E", "D", "R", "U", "T", "R", "E"], ["U", "E", "T"], ["R"], ["E", "R", "D", "I", "T", "U"], ["E", "U", "T", "R", "D", "I"], ["T", "D", "E", "E", "R", "U", "I", "R"], ["I", "U", "T", "E"], ["R"], ["U", "D", "T", "R"], ["T", "I", "R", "R", "D"], ["R", "R", "U", "I", "E", "D"], ["R", "E", "I", "E", "U", "D"], ["E", "R"], ["T"], ["E"], ["I", "D", "T", "E", "R"], ["R", "D", "I", "R", "T", "U", "E"], ["U"], ["U", "R", "D", "T"], ["R", "U", "E"], ["R", "E", "E", "T", "R", "D"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] Thrown: -NoneOfTheOtherOptions From d1b6fbf6b87cc7c727072c00123808c979a3f283 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Wed, 12 Feb 2020 15:57:03 +0100 Subject: [PATCH 42/66] Added field potential_voters to Turn. --- Server/election.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/election.py b/Server/election.py index 62922696..7e434e20 100644 --- a/Server/election.py +++ b/Server/election.py @@ -14,6 +14,7 @@ class Turn(): def __init__(self, votes : List[Vote], participants : int, options : List[Option], fs : TextIO = None): self.participants = participants + self.potential_voters = participants - len(votes) self.__options__ = { key : list(filter(lambda v: v[-1] == key)) for key in options } From a85d28daece318ad7401e954d59fa81012f8be28 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Wed, 12 Feb 2020 16:36:48 +0100 Subject: [PATCH 43/66] Made filtering for occurences of alternatives valid. --- Server/election.py | 13 ++++++++----- Server/test_election.el | 3 --- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Server/election.py b/Server/election.py index 7e434e20..5f37290b 100644 --- a/Server/election.py +++ b/Server/election.py @@ -15,7 +15,7 @@ class Turn(): def __init__(self, votes : List[Vote], participants : int, options : List[Option], fs : TextIO = None): self.participants = participants self.potential_voters = participants - len(votes) - self.__options__ = { key : list(filter(lambda v: v[-1] == key)) for key in options } + self.__options__ = { key : list(filter(lambda v: v[-1] == key, votes)) for key in options } def order(self,reverse=False): @@ -49,9 +49,9 @@ def count(self): winner = False while type(winner) != "" or winner != None: ordered = self.order(reverse=True) - if len(self.__options__[ordered[0]]) > (participants*threshold): + if len(self.__options__[ordered[0]]) > (self.participants*threshold): winner = ordered[0] - elif len(self.__options__[ordered[o]]) == (participants*threshold) and len(self.__options__[ordered[1]]) == (participants*threshold): + elif len(self.__options__[ordered[0]]) == (self.participants*threshold) and len(self.__options__[ordered[1]]) == (participants*threshold): winner = None elif self.potential_voters/self.participants > threshold: winner = None @@ -60,18 +60,21 @@ def count(self): for l in least: self.__resort(self.__options__[l]) self.__options__.pop(l) + for o in list(self.__options__): + self.__options__[o] = [list(filter(lambda a: a != l, v)) for v in self.__options__[o]] + def least(self): ordered = self.order() least = ([ordered[0]], len(self.__options__[ordered[0]])) - for o in ordered: + for o in ordered[1:]: support_for_o = len(self.__options__[o]) if support_for_o < least[1]: least = (o,support_for_o) elif support_for_o == least[1]: least[0].append(o) - least[1] += support_for_o + least = (least[0],least[1]+support_for_o) elif support_for_o > least[1]: continue diff --git a/Server/test_election.el b/Server/test_election.el index d7d148ee..e69de29b 100644 --- a/Server/test_election.el +++ b/Server/test_election.el @@ -1,3 +0,0 @@ -Votes: - [["R"], ["E", "U", "I", "T"], ["E", "E", "R", "I", "U", "D", "T"], ["I", "E", "D", "R", "T", "E", "R", "U"], ["E", "R", "E", "D"], ["E", "D", "R", "U", "T", "R", "E"], ["U", "E", "T"], ["R"], ["E", "R", "D", "I", "T", "U"], ["E", "U", "T", "R", "D", "I"], ["T", "D", "E", "E", "R", "U", "I", "R"], ["I", "U", "T", "E"], ["R"], ["U", "D", "T", "R"], ["T", "I", "R", "R", "D"], ["R", "R", "U", "I", "E", "D"], ["R", "E", "I", "E", "U", "D"], ["E", "R"], ["T"], ["E"], ["I", "D", "T", "E", "R"], ["R", "D", "I", "R", "T", "U", "E"], ["U"], ["U", "R", "D", "T"], ["R", "U", "E"], ["R", "E", "E", "T", "R", "D"], ["NoneOfTheOtherOptions"], ["NoneOfTheOtherOptions"]] -Thrown: From ac06dbbb3f56b55ebc567d0f8eeb61b580c655ae Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Wed, 12 Feb 2020 19:47:57 +0100 Subject: [PATCH 44/66] Removed Turn class, as it seems to be impossible to implement AV. --- Server/election.py | 79 ++-------------------------------------------- 1 file changed, 3 insertions(+), 76 deletions(-) diff --git a/Server/election.py b/Server/election.py index 5f37290b..bc1e4633 100644 --- a/Server/election.py +++ b/Server/election.py @@ -4,87 +4,14 @@ Vote = List[str] Option = str + """ -Turn class, -a class to collect the current state of the counting. -First it sorts all votes into the options. -If an option is deleted, the votes removed from it -are resorted into the new options. """ -class Turn(): - def __init__(self, votes : List[Vote], participants : int, options : List[Option], fs : TextIO = None): - self.participants = participants - self.potential_voters = participants - len(votes) - self.__options__ = { key : list(filter(lambda v: v[-1] == key, votes)) for key in options } - - - def order(self,reverse=False): - return sorted(list(self.__options__), key = lambda k: len(self.__options__[k]), reverse=reverse) - - def __resort(self,votes : List[Vote]): - for vote in votes: - vote.pop() - if vote != []: - self.__options__[vote[-1]].append(vote) - else: - self.potential_voters += 1 - - """count all votes in self.__options__. - It follows the following principle: - Each turn one or more options are selected - to be eliminated. - In elimination, all votes, that are voting for the option are reallocated - to the next alternative option choice. - Once an option reached a threshold of support, the counting is over. - - To win the election an option has to have more than limit*100 % support. - Returns: - Either - - (winner_name,support) : Tuple[str,List[Vote] - If one option has won - - [(one_winner,support),(other_winner,support)] : List[Tuple[str,List[Vote]]] - If no side could unite the necessary support - - False - If Error has occured and invalid data been provided. - """ - def count(self): - threshold = 0.5 - winner = False - while type(winner) != "" or winner != None: - ordered = self.order(reverse=True) - if len(self.__options__[ordered[0]]) > (self.participants*threshold): - winner = ordered[0] - elif len(self.__options__[ordered[0]]) == (self.participants*threshold) and len(self.__options__[ordered[1]]) == (participants*threshold): - winner = None - elif self.potential_voters/self.participants > threshold: - winner = None - else: - least = self.least() - for l in least: - self.__resort(self.__options__[l]) - self.__options__.pop(l) - for o in list(self.__options__): - self.__options__[o] = [list(filter(lambda a: a != l, v)) for v in self.__options__[o]] - - - def least(self): - ordered = self.order() - least = ([ordered[0]], len(self.__options__[ordered[0]])) - - for o in ordered[1:]: - support_for_o = len(self.__options__[o]) - if support_for_o < least[1]: - least = (o,support_for_o) - elif support_for_o == least[1]: - least[0].append(o) - least = (least[0],least[1]+support_for_o) - elif support_for_o > least[1]: - continue - - return least[0] - def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None) -> Tuple[Option, int]: - turn = Turn(participants,votes,options, fs) - result = turn.count() + election = Election(participants,votes,options, fs=fs) + result = election.count() if fs != None: fs.close() From f996302a0a7e3f1191c9a1fbbd0e24d0094341ae Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sat, 15 Feb 2020 23:37:05 +0100 Subject: [PATCH 45/66] Working counting function, as it seems. --- Server/election.py | 47 +++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/Server/election.py b/Server/election.py index bc1e4633..a8cf50aa 100644 --- a/Server/election.py +++ b/Server/election.py @@ -1,19 +1,36 @@ -from typing import List, Tuple, TextIO, Any -import sys, json, functools +import random, pyrankvote, pprint +from typing import List, Dict +from pyrankvote import Candidate, Ballot -Vote = List[str] -Option = str +def vote(votes : List[List[str]], options : List[str]): + options = { key : list(filter(lambda v: v[-1] == key, votes)) for key in list(options) } + votes = len(votes) + result = { "removed_votes" : 0 + , "winner" : False + , "rounds" : [] + } + while result["winner"] == False: + winners = list(filter(lambda o:len(o)/votes > 0.5, options)) + result["rounds"].append(options) + if winners != []: + if len(winners) == 1: + result["winner"] = winners[0] + else: + result["winner"] = None + break + else: + least = sorted(list(options), key=lambda o: len(options[o])) + print(least) + least = options.pop(least[-1]) - -""" -""" - - -def count_votes(participants : int, votes : List[Vote], options : List[Option], fs : TextIO = None) -> Tuple[Option, int]: - election = Election(participants,votes,options, fs=fs) - result = election.count() - - if fs != None: - fs.close() + options = { key : list(filter(lambda a: a != least, options[key])) for key in list(options) } + result["removed_votes"] = votes - sum([len(option) for option in options]) + votes = sum([len(option) for option in options]) + continue return result + +def test(n): + options = ["A","B","C","D"] + votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] + result = vote(votes,options)["winner"] From ee75ec70cba96a8bea80996a72b37180550753a0 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sun, 16 Feb 2020 17:38:15 +0100 Subject: [PATCH 46/66] It isn't yet perfect, but good enough. --- Server/election.py | 53 +++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/Server/election.py b/Server/election.py index a8cf50aa..b75df2a1 100644 --- a/Server/election.py +++ b/Server/election.py @@ -5,32 +5,47 @@ def vote(votes : List[List[str]], options : List[str]): options = { key : list(filter(lambda v: v[-1] == key, votes)) for key in list(options) } votes = len(votes) - result = { "removed_votes" : 0 - , "winner" : False + thrown_out = 0 + result = { "winner" : False , "rounds" : [] } while result["winner"] == False: - winners = list(filter(lambda o:len(o)/votes > 0.5, options)) - result["rounds"].append(options) - if winners != []: - if len(winners) == 1: - result["winner"] = winners[0] - else: - result["winner"] = None + # Does a candidate have more than 50%? + winners = list(filter(lambda o: len(options[o]) > 0.5*votes, list(options))) + if len(winners) == 1: + result["winner"] = winners[0] break - else: - least = sorted(list(options), key=lambda o: len(options[o])) - print(least) - least = options.pop(least[-1]) - - options = { key : list(filter(lambda a: a != least, options[key])) for key in list(options) } - result["removed_votes"] = votes - sum([len(option) for option in options]) - votes = sum([len(option) for option in options]) - continue + elif len(winners) >= 2: + result["winner"] = None + break + elif len(winners) == 0: + # Is there only one candidate left? + if len(list(options)) == 1: + result["winner"] = options(list(options)[0]) + break + elif len(list(options)) == 0: + result["winner"] = None + break + else: + # Drop worst candidate and find out who voters liked next best + sorted_options = sorted(list(options), key=lambda o: len(options[o])) + worst = sorted_options[-1] + worsts_votes = options.pop(worst) + options = { key : [list(filter(lambda a: a != worst, vote)) for vote in options[key]] for key in list(options)} + thrown_out += (votes - sum([len(options[o]) for o in list(options)])) + votes = sum([len(o) for o in options]) + continue + result["thrown_out"] = thrown_out return result def test(n): options = ["A","B","C","D"] votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] - result = vote(votes,options)["winner"] + result = vote(votes,options) + print(f"""{result["winner"]} +Options : {options} +Votes: {votes} +Thrown out: {(result["thrown_out"]/len(votes))*100}% +Popular winner: {"None of the other options" if (result["thrown_out"]/len(votes)) > 0.5 else result["winner"]} + """) From cc65db4d7fd89a42e69c55fcb8e0b85121223f8f Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sun, 16 Feb 2020 19:02:18 +0100 Subject: [PATCH 47/66] Testing for remie. Tested a little bit. But can't find a way to determine an automated test, without just copying the code from vote. Also put the test for NoneOfTheOtherOptions into vote. --- Server/election.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Server/election.py b/Server/election.py index b75df2a1..68836337 100644 --- a/Server/election.py +++ b/Server/election.py @@ -5,13 +5,14 @@ def vote(votes : List[List[str]], options : List[str]): options = { key : list(filter(lambda v: v[-1] == key, votes)) for key in list(options) } votes = len(votes) + all_participants = votes thrown_out = 0 result = { "winner" : False , "rounds" : [] } while result["winner"] == False: # Does a candidate have more than 50%? - winners = list(filter(lambda o: len(options[o]) > 0.5*votes, list(options))) + winners = list(filter(lambda o: len(options[o]) >= 0.5*votes, list(options))) if len(winners) == 1: result["winner"] = winners[0] break @@ -32,20 +33,23 @@ def vote(votes : List[List[str]], options : List[str]): worst = sorted_options[-1] worsts_votes = options.pop(worst) options = { key : [list(filter(lambda a: a != worst, vote)) for vote in options[key]] for key in list(options)} + options = { key : options[key] for key in list(options) if options[key] != []} thrown_out += (votes - sum([len(options[o]) for o in list(options)])) votes = sum([len(o) for o in options]) continue result["thrown_out"] = thrown_out + result["winner"] = "NoneOfTheOtherOptions" if (result["thrown_out"]/all_participants) > 0.5 else result["winner"] return result -def test(n): - options = ["A","B","C","D"] - votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] - result = vote(votes,options) - print(f"""{result["winner"]} +def test(n, repeat_for=10**3,seed="A"): + random.seed("A") + for i in range(repeat_for): + options = ["A","B","C","D"] + votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] + result = vote(votes,options) + print(f"""{result["winner"]} Options : {options} Votes: {votes} Thrown out: {(result["thrown_out"]/len(votes))*100}% -Popular winner: {"None of the other options" if (result["thrown_out"]/len(votes)) > 0.5 else result["winner"]} - """) + """) From 789bd7ec58c3e5725f2698fffb86c7254f4e947f Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sun, 16 Feb 2020 19:03:24 +0100 Subject: [PATCH 48/66] vote -> count Removed imports --- Server/election.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Server/election.py b/Server/election.py index 68836337..4c8d2b2f 100644 --- a/Server/election.py +++ b/Server/election.py @@ -1,8 +1,7 @@ -import random, pyrankvote, pprint +import random, pprint from typing import List, Dict -from pyrankvote import Candidate, Ballot -def vote(votes : List[List[str]], options : List[str]): +def count(votes : List[List[str]], options : List[str]): options = { key : list(filter(lambda v: v[-1] == key, votes)) for key in list(options) } votes = len(votes) all_participants = votes From 9cc1443b6a3e10ea6314e189c78db6782658d102 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Sun, 16 Feb 2020 19:05:24 +0100 Subject: [PATCH 49/66] Using count in test function. --- Server/election.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/election.py b/Server/election.py index 4c8d2b2f..92d4040a 100644 --- a/Server/election.py +++ b/Server/election.py @@ -46,7 +46,7 @@ def test(n, repeat_for=10**3,seed="A"): for i in range(repeat_for): options = ["A","B","C","D"] votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] - result = vote(votes,options) + result = count(votes,options) print(f"""{result["winner"]} Options : {options} Votes: {votes} From dc346056dca0552751358fc2248bd5bcc8f8acd5 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Tue, 18 Feb 2020 16:42:22 +0000 Subject: [PATCH 50/66] removed outdated tests --- Server/.goutputstream-9HC4F0 | 47 ++++++++++++++++++++++++++++++++++ Server/test_election.el | 0 Server/test_election.py | 49 ------------------------------------ 3 files changed, 47 insertions(+), 49 deletions(-) create mode 100644 Server/.goutputstream-9HC4F0 delete mode 100644 Server/test_election.el delete mode 100644 Server/test_election.py diff --git a/Server/.goutputstream-9HC4F0 b/Server/.goutputstream-9HC4F0 new file mode 100644 index 00000000..e5c7cfe1 --- /dev/null +++ b/Server/.goutputstream-9HC4F0 @@ -0,0 +1,47 @@ +import functools,random +from typing import List, Dict + +def vote(votes : List[List[str]], options : List[str]): + options = {key : list(filter(lambda v: v[-1] == key or (key == "NoneOfTheOtherOptions" and v == []), votes)) for key in options} + winner = False + while(winner == False): + result = _vote(options,len(votes)) + if type(result) == type(""): + winner = (result,len(options[result])) + elif type(result) == type(None): + winner = (None, None) + elif type(result) == type({}): + options = result + else: + raise "VotingError: Unknow type retuned by _vote" + return winner + +def _vote(options : Dict[str,List[List[str]]], votes : int): + ordered = sorted(list(options), key=lambda o: options[o], reverse=True) + + if len(options[ordered[0]])/votes > 0.5: + return ordered[0] + elif len(options[ordered[0]]) == len(options[ordered[1]]) and len(options[ordered[0]]) == 0.5: + return None + else: + least = options[ordered[-1]] + print(least) + least = options.pop(ordered[-1]) + options = { key : [a for a in least if a != ordered[-1]] for key in list(options)} + + + for vote in least: + if len(vote) > 1: + vote.pop() + options[vote[-1]].append(vote) + print(vote[-1]) + elif len(vote) == 1: + options["NoneOfTheOtherOptions"].append([]) + else: + raise "VotingError: NoneOfTheOtherOptions seems to be removed from options, because empty lists are supposed to be reallocated, which only appear in NoneOfTheOtherOptions" + return options + +def test(n): + options = [str(x) for x in range(100)] + votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] + return vote(votes,options) diff --git a/Server/test_election.el b/Server/test_election.el deleted file mode 100644 index e69de29b..00000000 diff --git a/Server/test_election.py b/Server/test_election.py deleted file mode 100644 index 5f54b32f..00000000 --- a/Server/test_election.py +++ /dev/null @@ -1,49 +0,0 @@ -import election -import random, os, json - -random.seed(os.environ["SEED"]) - -"""Utility function to easily create new election ballots, if necessary -""" -def generate_ballot(): - options_population = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] - options = random.choices( - options_population - , k=random.randint(1,len(options_population)) - ) - - votes = [random.sample(options,random.randint(1,len(options))) for i in range(random.randint(10,100))] - - participants = random.randint(len(votes), len(votes)+10) - return (votes,participants,options) - -def test_count_votes(): - # Random ballot test cases - for i in range(100): - ballot = generate_ballot() - test_file = open("test_election.el","w") - result = election.count_votes(ballot[0],ballot[1],ballot[2],fs=test_file) - - assert test_file.closed - - election_log = open("test_election.el","r").read() - - # Format checks in the log: - - # Votes, Thrown and Winner fields must exist - assert election_log.startswith("Votes:\n") - (votes,_,rest) = election_log.partition("\nThrown:\n") - (thrown,_,winner) = election_log.partition("\nWinner:\n") - (_,_,votes) = votes.partition("Votes:\n") - votes = json.loads(votes) - assert winner.strip() == str(result) - - # Testing on one special ballot - options = ["A","B","C"] - votes = [["A","B","C"],["A","C","B"],["A","C","B"]] - participants = len(votes) - assert ("B", 2/3) == election.count_votes(votes,participants,options) - - - participants = 10 - assert ("NoneOfTheOtherOptions", 8) == election.count_votes(votes,participants,options) From 7f7f7cb392089ea9a1c6809ecbadde34dc29edad Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Tue, 18 Feb 2020 17:40:32 +0000 Subject: [PATCH 51/66] Wrote unit test and got an error on SEED ABCDEFGHIJIkkmmMMMMMMMMMMMMMMM because thrown_out is negative. I suspect that the calculation of the thrown_out is invalid or the votes somehow increased during counting, which seems quite impossible. --- Server/__pycache__/election.cpython-37.pyc | Bin 1861 -> 2311 bytes Server/election.py | 15 +++----------- Server/test_election.py | 23 +++++++++++++++++++++ Server/test_election.py~ | 22 ++++++++++++++++++++ 4 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 Server/test_election.py create mode 100644 Server/test_election.py~ diff --git a/Server/__pycache__/election.cpython-37.pyc b/Server/__pycache__/election.cpython-37.pyc index c919e012923d154ff3d14064ad7a3e3c029b22d0..e87aeb330e1a8e9c7b080844127419ed84d97969 100644 GIT binary patch literal 2311 zcma)8&u<$=6rP#=VK;SJH%UueDiwut@PQbiNL-?{LJ_CRsT3q6OAr~)#94X0> P z60J{;6z70c;MxOl;~(LV*i!|koVoP^-?#wu|Mfyd^xyzhL%t#ndBjB7Ba` zU-Dg6^{?QC)vF?Np9hJ_-0h%c@Z=rGMkQGx?wKHtl9r%lxQb{#8lgyj<&0hMeJ;5a z=S9PYY{0kCE}7v&juCxdNT=^ex6P#YO7z{A{JUqVD}7*bJK`nR+At$7$d8Z(SseQr zhym|=hNs?u%Ud0x&r(l16-Kt3>;*gA7`jw zE6aQ1yotX7UuB=MzGFPOgg4+dpS|RV8;2WTvD5+bTm+p+HdQMHCP857|%RkQ|5^dy;~+FX1M| z`U(Jc1g)!I{f+B4R^=H#M(1?Ysit^uKCK-AG2DpV1X7FUHPEEcR~&O+6x77#c2}m|$p?UQ7st$ViOVoT80I_T>M> z%M-}`TiuS;F&jgd;dsjVOle5m&HG(9?diW literal 1861 zcmZ{l&u<$=6vyYs?s}6nB?*P%O3(;Jl@?X8+H({TAjHX+AXTzjqK#)#wqARknQ=&} z^~phn1N;R!w4C6=i8Fu5o{%`@%nc#n`(|zD7aeP6-oBlAZ|1$vK7YKtyvWeL{O#{2 zFB**fK@YPgAbf;wH!vusc+NUZ2_;WhhpRw^n1zZ|1G7|ohb8eDRZZ=E@&Q^{3bQTL_KOI&pDznaHdn2c7TUC%llY~|1O;@WIw(okMhHhLGTUSmUUDUGv0h*| zNIyZId)(ZwSM0E(%~E{Ip4|J6ee2gdWbXyr=PFduNnElCcHH*ww%EbLN6bm=Y~USJ zv0C_Ej)e=z>KOW$VJTm6&R`=_i?pVc{tS3e(iU@#T0>M!z=Q$;$x_=84~8!J6dmpN z56?(-jh8o}v?~}&(9)b9xkuE<_SwH^=Zqx0;)j=@p{N=P;!P4LV$a{dnz-u_Tswnj z_6Bw%ocpNl9w@5(SDXYZK}xPqtdEc&UI?t?ywk+@)T;L`vnfOL>uUi zNnm`m-XI0h+KKX_uv1!R*Yr+lIjyjCL&i6FEEoB`U`@n)k>BCo-293ZecI*g$gdD) z!K|6}PjW_ZPne(TP>)ex6!f4DoYobqW)!tYz6?;iP{TkWYLVjOKsCnU(dT7oe=KDg zG#LoxDsHk@;1RN@<#*UPDkHVfW9EmFkHdq<{wXl0rJyIWa`5;OE5n^>%BSQmwvA5( zk$d(J@SET7Qh_@M7HJgIG|C#7+5jOk3#P~AGBfYtbq(y@OZoaATB$}-qnWy=mC{0m z`8M&x+ci3(;zt!fzEXDl2XYtdy}!@u?}=`cMCTZJKB|TN(V**!BDchi8g+FQ`kIwA zYE-6W@ 0.5 else result["winner"] return result -def test(n, repeat_for=10**3,seed="A"): - random.seed("A") - for i in range(repeat_for): - options = ["A","B","C","D"] - votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] - result = count(votes,options) - print(f"""{result["winner"]} -Options : {options} -Votes: {votes} -Thrown out: {(result["thrown_out"]/len(votes))*100}% - """) + + diff --git a/Server/test_election.py b/Server/test_election.py new file mode 100644 index 00000000..d84cc9e7 --- /dev/null +++ b/Server/test_election.py @@ -0,0 +1,23 @@ +from election import count +import random,os + +def test_count(n=50, repeat_for=10**7,seed=None): + random.seed("A" if seed != None else os.environ["SEED"]) + for i in range(repeat_for): + options = [str(i) for i in range(random.randint(4,20))] + votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] + result = count(votes,options) + + # On AV there is one option deleted every round + # thus after len(options) rounds, AV has to stop. + assert len(result["rounds"]) <= len(options) + assert len(result["rounds"]) > 0 + + assert result["thrown_out"] >= 0 + + if result["winner"] != "NoneOfTheOtherOptions": + assert result["thrown_out"]/len(votes) >= 0.5 + else: + assert result["thrown_out"] >= 0.5*len(votes) + + diff --git a/Server/test_election.py~ b/Server/test_election.py~ new file mode 100644 index 00000000..84563f65 --- /dev/null +++ b/Server/test_election.py~ @@ -0,0 +1,22 @@ +from election import count +import random,os + +def test_count(n=50, repeat_for=10**7,seed=None): + random.seed("A" if seed != None else os.environ["SEED"]) + for i in range(repeat_for): + options = [str(i) for i in range(random.randint(4,20))] + votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] + result = count(votes,options) + + # On AV there is one option deleted every round + # thus after len(options) rounds, AV has to stop. + assert len(result["rounds"]) <= len(options) + + assert result["thrown_out"] >= 0 + + if result["winner"] != "NoneOfTheOtherOptions": + assert result["thrown_out"]/len(votes) >= 0.5 + else: + assert result["thrown_out"] >= 0.5*len(votes) + + From 4d5dd7a11e014139deed65a728995eefad78b91b Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Tue, 18 Feb 2020 22:07:03 +0100 Subject: [PATCH 52/66] Corrected the calculation of votes and thrown_out and solved the issue present in the previous commit. I am going to check for three to four further SEEDs and then declare this branch finished, if all of them passed their tests for I have to return to the master branch soon enough, as this is the most important of all the branches if I am to say so myself. If no further issues arise from these tests, I am glad to count this as the last commit on this branch. --- Server/election.py | 14 +++++++++----- Server/test_election.py | 8 +++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Server/election.py b/Server/election.py index 351c91be..3425f9f7 100644 --- a/Server/election.py +++ b/Server/election.py @@ -11,6 +11,7 @@ def count(votes : List[List[str]], options : List[str]): } while result["winner"] == False: # Does a candidate have more than 50%? + result["rounds"].append(options) winners = list(filter(lambda o: len(options[o]) >= 0.5*votes, list(options))) if len(winners) == 1: result["winner"] = winners[0] @@ -33,13 +34,16 @@ def count(votes : List[List[str]], options : List[str]): worsts_votes = options.pop(worst) options = { key : [list(filter(lambda a: a != worst, vote)) for vote in options[key]] for key in list(options)} options = { key : options[key] for key in list(options) if options[key] != []} - thrown_out += (votes - sum([len(options[o]) for o in list(options)])) - votes = sum([len(o) for o in options]) + sum_votes = sum([len(options[o]) for o in list(options)]) + thrown_out += (votes - sum_votes) + votes = sum_votes continue result["thrown_out"] = thrown_out + if result["thrown_out"]/all_participants > 0.5: + result["winner"] = "NoneOfTheOtherOptions" + elif len(result["winner"])/all_participants == 0.5 and (result["thrown_out"]/all_participants == 0.5): + result["winner"] = None + result["winner"] = "NoneOfTheOtherOptions" if (result["thrown_out"]/all_participants) > 0.5 else result["winner"] return result - - - diff --git a/Server/test_election.py b/Server/test_election.py index d84cc9e7..e630f017 100644 --- a/Server/test_election.py +++ b/Server/test_election.py @@ -12,12 +12,10 @@ def test_count(n=50, repeat_for=10**7,seed=None): # thus after len(options) rounds, AV has to stop. assert len(result["rounds"]) <= len(options) assert len(result["rounds"]) > 0 - + assert result["thrown_out"] >= 0 if result["winner"] != "NoneOfTheOtherOptions": - assert result["thrown_out"]/len(votes) >= 0.5 + assert result["thrown_out"]/len(votes) < 0.5 else: - assert result["thrown_out"] >= 0.5*len(votes) - - + assert result["thrown_out"] > 0.5*len(votes) From fcf6a544cc422f97a1dd00e4ceaee41ed55af7bd Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Tue, 18 Feb 2020 22:12:52 +0100 Subject: [PATCH 53/66] =?UTF-8?q?At=20last,=20it=20seems=20like=20I=20had?= =?UTF-8?q?=20overestimated=20my=20machine.=20Four=20millions=20tests=20ar?= =?UTF-8?q?e=20wildly=20beyond=20the=20reach=20of=20it,=20so=20I=20regard?= =?UTF-8?q?=20it=20as=20a=20better=20solution=20for=20now=20to=20reduce=20?= =?UTF-8?q?the=20tests=20to=2010=E2=81=B5=20each.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/test_election.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/test_election.py b/Server/test_election.py index e630f017..ecf177d5 100644 --- a/Server/test_election.py +++ b/Server/test_election.py @@ -1,7 +1,7 @@ from election import count import random,os -def test_count(n=50, repeat_for=10**7,seed=None): +def test_count(n=50, repeat_for=10**5,seed=None): random.seed("A" if seed != None else os.environ["SEED"]) for i in range(repeat_for): options = [str(i) for i in range(random.randint(4,20))] From bd89c62aa9087bd838f0ea2981280e758cdc85c2 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:23:02 +0100 Subject: [PATCH 54/66] Added template for config.yml for python projects from circleci. --- .circleci/config.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..12a49189 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,21 @@ +version: 2.1 + +orbs: + python: circleci/python@0.2.1 + +jobs: + build-and-test: + executor: python/default + steps: + - checkout + - python/load-cache + - python/install-deps + - python/save-cache + - run: + command: ./manage.py test + name: Test + +workflows: + main: + jobs: + - build-and-test From f380832afc217f736570c5e7b8463c3c4d17e262 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:29:49 +0100 Subject: [PATCH 55/66] Added pytest to requirements.txt and manage.py as a file to execute tests. --- manage.py | 6 ++++++ requirements.txt | 11 +++++++++++ 2 files changed, 17 insertions(+) create mode 100755 manage.py diff --git a/manage.py b/manage.py new file mode 100755 index 00000000..b956b86e --- /dev/null +++ b/manage.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +import sys, subprocess + + +if sys[1] == "test": + subprocess.run("pytest") diff --git a/requirements.txt b/requirements.txt index fa7d88f4..dec454be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,20 @@ +attrs==19.3.0 Click==7.0 Flask==1.1.1 gunicorn==20.0.4 +importlib-metadata==1.5.0 itsdangerous==1.1.0 Jinja2==2.10.3 MarkupSafe==1.1.1 +more-itertools==8.2.0 +packaging==20.1 +pluggy==0.13.1 +py==1.8.1 pycryptodome==3.9.4 pymongo==3.10.1 +pyparsing==2.4.6 +pytest==5.3.5 +six==1.14.0 +wcwidth==0.1.8 Werkzeug==0.16.0 +zipp==3.0.0 From 059d81b8d59965594527218c03ddd98de2b5175a Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:35:25 +0100 Subject: [PATCH 56/66] manage.py is now either going to run the server or run a test. Updated start, removing unnecessary build instructions for Elm. --- ROADMAP.md | 18 ------------------ manage.py | 11 +++++++++-- start | 2 -- 3 files changed, 9 insertions(+), 22 deletions(-) delete mode 100644 ROADMAP.md diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index 996c5b9e..00000000 --- a/ROADMAP.md +++ /dev/null @@ -1,18 +0,0 @@ -# ROADMAP for the next half a year (the pre-alpha phase) -TODO: -1. HTTPS Server -2. Election.py implemented with: create(); close(); vote(); -3. Patches.py implemented with: create(); close(); request_election() -4. Users.py implemented with: login(); register(); - -# After three weeks: - -4. Design and Interactivity -5. Test in Deployment -6. Prealpha Run for 4 weeks - -# After Summer -7. Ratification by ten original users -8. Release the Beta Version -9. Beta Run for half a year. -10. Continous Run diff --git a/manage.py b/manage.py index b956b86e..5ef11b26 100755 --- a/manage.py +++ b/manage.py @@ -1,6 +1,13 @@ #!/usr/bin/env python3 -import sys, subprocess +import sys, subprocess, os -if sys[1] == "test": +if sys.argv[1] == "test": subprocess.run("pytest") +elif sys.argv[1] == "run": + os.environ["BUILDER"] = sys.argv[2] + subprocess.run("./start") +else: + print("""Please provide a command: +test : To run the tests +run : To run the Server""", file=sys.stderr) diff --git a/start b/start index c5d3f94f..647dd6da 100755 --- a/start +++ b/start @@ -8,8 +8,6 @@ BUILD="$BUILD-$BUILDER" echo $BUILD >> BUILD.txt -# Build Elm frontend -elm make --output=output/main.js --optimize src/Main.elm # Start Server gunicorn main:app From bd02d2540c65e2e59a6178a6ebac2599978bb1ed Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:50:02 +0100 Subject: [PATCH 57/66] Added exiting with the returncode of pytest or start, so CircleCI knows if something went wrong. --- manage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manage.py b/manage.py index 5ef11b26..f98529f6 100755 --- a/manage.py +++ b/manage.py @@ -3,10 +3,10 @@ if sys.argv[1] == "test": - subprocess.run("pytest") + exit(subprocess.run("pytest").returncode) elif sys.argv[1] == "run": os.environ["BUILDER"] = sys.argv[2] - subprocess.run("./start") + exit(subprocess.run("./start").returncode) else: print("""Please provide a command: test : To run the tests From cc9525ef8068f8a610d4f41ead13d397c2272e3b Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:52:33 +0100 Subject: [PATCH 58/66] Changed way of executing pytest. --- manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manage.py b/manage.py index f98529f6..578afea6 100755 --- a/manage.py +++ b/manage.py @@ -3,7 +3,7 @@ if sys.argv[1] == "test": - exit(subprocess.run("pytest").returncode) + exit(subprocess.run("python3 -m pytest").returncode) elif sys.argv[1] == "run": os.environ["BUILDER"] = sys.argv[2] exit(subprocess.run("./start").returncode) From a5b4eda81629cd0c75277ac8a771348a6f847b6c Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:53:57 +0100 Subject: [PATCH 59/66] removed proxy in manage.py to execute pytest. --- .circleci/config.yml | 2 +- manage.py | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100755 manage.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 12a49189..80ad2834 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: - python/install-deps - python/save-cache - run: - command: ./manage.py test + command: pytest name: Test workflows: diff --git a/manage.py b/manage.py deleted file mode 100755 index 578afea6..00000000 --- a/manage.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 -import sys, subprocess, os - - -if sys.argv[1] == "test": - exit(subprocess.run("python3 -m pytest").returncode) -elif sys.argv[1] == "run": - os.environ["BUILDER"] = sys.argv[2] - exit(subprocess.run("./start").returncode) -else: - print("""Please provide a command: -test : To run the tests -run : To run the Server""", file=sys.stderr) From c18b0bcb0b7df0fdf119921e4e401572f05c86d9 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:54:52 +0100 Subject: [PATCH 60/66] Changed invoking of pytest. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 80ad2834..86da8045 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: - python/install-deps - python/save-cache - run: - command: pytest + command: python -m pytest name: Test workflows: From 6906be960bbb7c73b5d1f3aba95823a946ed55d3 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:56:36 +0100 Subject: [PATCH 61/66] Removed test_patches.py --- Server/test_patches.py | 66 ------------------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 Server/test_patches.py diff --git a/Server/test_patches.py b/Server/test_patches.py deleted file mode 100644 index 3de2ec5a..00000000 --- a/Server/test_patches.py +++ /dev/null @@ -1,66 +0,0 @@ -import Patches -import random, os, string, subprocess -from pymongo import MongoClient - -def random_string(): - return ''.join(random.choice(string.ascii_letters) for i in range(random.randint(0,125))) - -def generate_random_patch(): - patcher = random.choice(['joris', 'abbashan', 'martin', 'brummel']) - patch = random.choice(['www-x','user-support', 'generate-random', 'postings']) - - options = { "is_user" : random.choice([True, False]) - , "simple_description" : random_string() - , "technical_description" : random_string() - , "hold_pre_election" : random.choice([True, False]) - , "references" : [ random_string() for i in range(random.randint(0,5)) ] - } - return (patcher, patch, options) - -def test_patch(): - os.environ['PATCHES'] = "/tmp/demnet_test" - os.environ['ORIGIN_REPOSITORY'] = "/tmp/demnet_origin" - - - (patcher, patch, options) = generate_random_patch() - - patch_hash = Patches.create(patcher, patch, options) - - assert os.path.isdir(f"{os.environ['PATCHES']}/{patcher}-{patch}") - - client = MongoClient() - db = client.demnet - patches = db.patches - - patch_formula = patches.find_one({ "hash" : patch_hash }) - - assert patch_formula['patcher'] == patcher - assert patch_formula['is_user'] == options['is_user'] - assert patch_formula['name'] == patch - assert patch_formula['simple_description'] == options['simple_description'] - assert patch_formula['technical_description'] == options['technical_description'] - assert patch_formula['hold_pre_election'] == options['hold_pre_election'] - assert patch_formula['references'] == options['references'] - assert patch_formula['closed'] == False - - (patcher_2, patch_2, options_2) = generate_random_patch() - - patch_2_hash = Patches.create(patcher_2, patch_2, options_2) - - # Close Patches without merging - assert Patches.close(patcher_2, patch_2, patch_2_hash) == True - print(f"Patcher:\t{patcher_2}\nPatch:\t{patch_2}") - assert not os.path.isdir(f"{ os.environ['PATCHES'] }/{patcher_2}-{patch_2}") - - # Create a Commit and Change to Patch - pwd = os.environ['PWD'] - subprocess.run([ f"cd { os.environ['PATCHES'] }/{patcher}-{patch}" ], shell=True) - subprocess.run([ f"echo \"Hello, World\" > README" ], shell=True) - subprocess.run([ f"git commit -m \"Test Commit\" && cd {pwd}" ], shell=True) - subprocess.run([ "cd ", pwd ], shell=True) - assert Patches.close(patcher, patch, patch_hash, merge=True) == True - assert not os.isdir(f"{os.environ['PATCHES']}/{patcher}-{patch}") - - subprocess.run([ "cd ", os.environ["ORIGIN_REPOSITORY"] ]) - log_res = subprocess.run([ "git log | grep \"Test Commit\"" ], capture_output=True, text=True) - assert log_res.stdout == "Test Commit" From d9d5aeb34276a3222aba36b8c217d4255ad318b1 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:58:02 +0100 Subject: [PATCH 62/66] Added stupid test. --- Server/test_patches.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 Server/test_patches.py diff --git a/Server/test_patches.py b/Server/test_patches.py new file mode 100644 index 00000000..f8cc332e --- /dev/null +++ b/Server/test_patches.py @@ -0,0 +1 @@ +assert True From 9565712cb0047d4f739ec4d93923a8f7e70a3492 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 20:59:13 +0100 Subject: [PATCH 63/66] Made test into function. --- Server/test_patches.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Server/test_patches.py b/Server/test_patches.py index f8cc332e..16606183 100644 --- a/Server/test_patches.py +++ b/Server/test_patches.py @@ -1 +1,3 @@ -assert True +import Patches +def test_patch(): + assert True From a80892d79fa93bb2fb09a562809b5f1013efe715 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 21:11:21 +0100 Subject: [PATCH 64/66] Removed merge signatures. --- requirements.txt | 9 --------- start | 3 --- 2 files changed, 12 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0c236ae8..24a5d4eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,11 +7,8 @@ itsdangerous==1.1.0 Jinja2==2.10.3 MarkupSafe==1.1.1 more-itertools==8.2.0 -<<<<<<< HEAD mypy==0.761 mypy-extensions==0.4.3 -======= ->>>>>>> ContinousIntegration packaging==20.1 pluggy==0.13.1 py==1.8.1 @@ -20,14 +17,8 @@ pymongo==3.10.1 pyparsing==2.4.6 pytest==5.3.5 six==1.14.0 -<<<<<<< HEAD typed-ast==1.4.1 typing-extensions==3.7.4.1 wcwidth==0.1.8 Werkzeug==0.16.0 zipp==2.2.0 -======= -wcwidth==0.1.8 -Werkzeug==0.16.0 -zipp==3.0.0 ->>>>>>> ContinousIntegration diff --git a/start b/start index 51513f0b..35a6a2be 100755 --- a/start +++ b/start @@ -9,15 +9,12 @@ BUILD="$BUILD-$BUILDER" echo $BUILD >> BUILD.txt -<<<<<<< HEAD # Test if the static analyses pases if ! mypy main.py Server/*.py --ignore-issing-imports; then exit 1 # Build Elm frontend elm make --output=output/main.js --optimize src/Main.elm -======= ->>>>>>> ContinousIntegration # Start Server gunicorn main:app From 751514763d717e0c3ab2cb1aaced5e126b612491 Mon Sep 17 00:00:00 2001 From: Joris Gutjahr Date: Thu, 20 Feb 2020 21:12:44 +0100 Subject: [PATCH 65/66] Removed SEED env variables. --- Server/test_election.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Server/test_election.py b/Server/test_election.py index ecf177d5..75fe0917 100644 --- a/Server/test_election.py +++ b/Server/test_election.py @@ -2,7 +2,6 @@ import random,os def test_count(n=50, repeat_for=10**5,seed=None): - random.seed("A" if seed != None else os.environ["SEED"]) for i in range(repeat_for): options = [str(i) for i in range(random.randint(4,20))] votes = [random.sample(options,k=random.randint(1,len(options))) for i in range(n)] From fb7beb7bf1a9ac6b2448d2c2f62eaf35a8a7469f Mon Sep 17 00:00:00 2001 From: Joris <31551856+CSDUMMI@users.noreply.github.com> Date: Thu, 27 Feb 2020 18:15:54 +0000 Subject: [PATCH 66/66] Got it. fi --- start | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start b/start index 35a6a2be..c39b3970 100755 --- a/start +++ b/start @@ -12,7 +12,7 @@ echo $BUILD >> BUILD.txt # Test if the static analyses pases if ! mypy main.py Server/*.py --ignore-issing-imports; then exit 1 - +fi # Build Elm frontend elm make --output=output/main.js --optimize src/Main.elm