From 70372a4c3834ca252ce46f5ce72b11ba9839a0d5 Mon Sep 17 00:00:00 2001 From: David Florness Date: Sun, 7 Jun 2020 01:15:12 -0600 Subject: [PATCH] Create preferences matrix for ballot from voting UI --- main.go | 4 +-- merkle.go | 8 +++--- ui.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 8e1ffc7..079e384 100644 --- a/main.go +++ b/main.go @@ -25,7 +25,7 @@ var ( logger = log.Logger("tallyard") protocolID = protocol.ID("/tallyard/0.0.0") - candidates []ElectionOption + candidates []Candidate optionsMerkle *merkletree.MerkleTree rendezvousNonce Nonce @@ -155,7 +155,7 @@ func bootstrap() { } str = stripNewline(str) if str != "" { - candidates = append(candidates, ElectionOption(str)) + candidates = append(candidates, Candidate(str)) fmt.Println(str) } if err == io.EOF { diff --git a/merkle.go b/merkle.go index 8a2f947..b9b8e4f 100644 --- a/merkle.go +++ b/merkle.go @@ -11,9 +11,9 @@ import ( "github.com/mr-tron/base58/base58" ) -type ElectionOption string +type Candidate string -func (eo ElectionOption) CalculateHash() ([]byte, error) { +func (eo Candidate) CalculateHash() ([]byte, error) { h := sha256.New() if _, err := h.Write([]byte(eo)); err != nil { return nil, err @@ -21,8 +21,8 @@ func (eo ElectionOption) CalculateHash() ([]byte, error) { return h.Sum(nil), nil } -func (eo ElectionOption) Equals(other merkletree.Content) (bool, error) { - return eo == other.(ElectionOption), nil +func (eo Candidate) Equals(other merkletree.Content) (bool, error) { + return eo == other.(Candidate), nil } type Nonce string diff --git a/ui.go b/ui.go index 38b4233..be62ba6 100644 --- a/ui.go +++ b/ui.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "sort" "strconv" "strings" "unicode" @@ -37,7 +38,7 @@ func createElection() { rendezvousNonce = NewNonce() content := []merkletree.Content{rendezvousNonce} for i := 0; i < n-1; i++ { - eo := ElectionOption(form.GetFormItem(i).(*tview.InputField).GetText()) + eo := Candidate(form.GetFormItem(i).(*tview.InputField).GetText()) candidates = append(candidates, eo) content = append(content, eo) } @@ -80,10 +81,12 @@ func joinElection() { } } -func vote(candidates []ElectionOption) []int { - result := make([]int, len(candidates)) +// displays a voting UI to the user and returns the encoded ballot +func vote(candidates []Candidate) []byte { + ranks := make([]int, len(candidates)) app := tview.NewApplication() form := tview.NewForm() + for _, eo := range candidates { // TODO: support more than 99 candidates form.AddInputField(string(eo), "", 2, @@ -91,6 +94,7 @@ func vote(candidates []ElectionOption) []int { return len(textToCheck) < 3 && unicode.IsDigit(lastChar) }, nil) } + form.AddButton("Submit", func() { app.Stop() for i := 0; i < len(candidates); i++ { @@ -99,11 +103,72 @@ func vote(candidates []ElectionOption) []int { if err != nil { panic(err) } - result[i] = rank + ranks[i] = rank } }) + if err := app.SetRoot(form, true).EnableMouse(true).Run(); err != nil { panic(err) } - return result + + return GetBallotFromRankings(ranks) +} + +func GetBallotFromRankings(ranks []int) []byte { + n := len(ranks) + candidates := make([]int, n) + + for i := 0; i < n; i++ { + candidates[i] = i + } + + // sort candidates by ranking + cr := CandidateRanking{candidates, ranks} + sort.Sort(&cr) + + // TODO: support more than 255 voters (limit from usage of byte) + prefMatrix := make([][]byte, n) + + for len(candidates) > 0 { + r := ranks[candidates[0]] + i := 1 + for ; i < len(candidates) && ranks[candidates[i]] == r; i++ {} + // i is now index of the first candidate with worse (i.e. higher + // in value) rank + row := make([]byte, n) + for j := i; j < len(candidates); j++ { + row[candidates[j]] = 1 + } + for j := 0; j < i; j++ { + prefMatrix[candidates[j]] = row + } + candidates = candidates[i:] + } + + // convert 2D array into 1D array + barray := make([]byte, 0, n*n) + for _, row := range prefMatrix { + barray = append(barray, row...) + } + + return barray +} + +type CandidateRanking struct { + candidates []int // becomes list of candidate IDs sorted by rank + ranks []int // maps candidate ID to rank +} + +func (cr *CandidateRanking) Len() int { + return len(cr.ranks) +} + +func (cr *CandidateRanking) Less(i, j int) bool { + return cr.ranks[cr.candidates[i]] < cr.ranks[cr.candidates[j]] +} + +func (cr *CandidateRanking) Swap(i, j int) { + tmp := cr.candidates[i] + cr.candidates[i] = cr.candidates[j] + cr.candidates[j] = tmp } -- 2.38.4