Skip to content

Commit

Permalink
Add option to accept missing votes in weighted elections
Browse files Browse the repository at this point in the history
  • Loading branch information
ermshiperete committed Apr 8, 2024
1 parent 4c67fe4 commit 3ee96ed
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 62 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added

- DigiTally, a tool to count the ballots
- `FehlendOk` option to allow missing votes in weighted elections

### Fixed

Expand Down
3 changes: 3 additions & 0 deletions DigitaleBriefwahl/Model/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class Configuration
internal const string NomineeLimitKey = "LimitKandidat";
internal const string PublicKeyKey = "PublicKey";
internal const string Email = "Email";
internal const string MissingOk = "FehlendOk";
internal const string Abstention = "<Enthaltung>";


public const string ConfigName = "wahl.ini";

Expand Down
3 changes: 3 additions & 0 deletions DigitaleBriefwahl/Model/ElectionModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ public virtual string GetResultString(Dictionary<string, CandidateResult> result
{
return $"({BallotsProcessed} ballots, thereof {Invalid} invalid)";
}

public abstract bool SkipNominee(string name, int iVote);
public abstract HashSet<int> GetInvalidVotes(List<string> electedNominees);
}
}

64 changes: 61 additions & 3 deletions DigitaleBriefwahl/Model/WeightedElectionModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,21 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Text.RegularExpressions;
using DigitaleBriefwahl.ExceptionHandling;
using IniParser.Model;
using SIL.Extensions;

namespace DigitaleBriefwahl.Model
{
public class WeightedElectionModel: ElectionModel
{
private bool MissingOk { get; }
public WeightedElectionModel(string name, IniData data) : base(name, data)
{
MissingOk = data[name].ContainsKey(Configuration.MissingOk) && data[name][Configuration.MissingOk] == "true";
}

public override string GetResult(List<string> electedNominees, bool writeEmptyBallot)
Expand Down Expand Up @@ -216,5 +215,64 @@ public override string GetResultString(Dictionary<string, CandidateResult> resul
bldr.AppendLine(base.GetResultString(results));
return bldr.ToString();
}

public override bool SkipNominee(string name, int iVote)
{
var vote = iVote + 1;
if (!NomineeLimits.TryGetValue(name, out var limit))
return false;

return vote < limit.Item1 || vote > limit.Item2;
}

public override HashSet<int> GetInvalidVotes(List<string> electedNominees)
{
var invalid = new HashSet<int>();
var lim = Math.Min(Votes, electedNominees.Count);
for (int i = lim; i < Votes; i++)
{
if (!MissingOk)
{
// Missing votes
invalid.Add(i);
}

}

for (var i = 0; i < lim; i++)
{
var electedNominee = electedNominees[i].Trim();
if (string.IsNullOrEmpty(electedNominee))
{
if (!MissingOk)
invalid.Add(i);
continue;
}

if (!Nominees.Contains(electedNominee))
{
invalid.Add(i);
continue;
}

for (var j = i + 1; j < lim; j++)
{
if (electedNominee != electedNominees[j] ||
electedNominee == Configuration.Abstention)
{
continue;
}

invalid.Add(i);
invalid.Add(j);
}

if (!SkipNominee(electedNominee, i))
continue;

invalid.Add(i);
}
return invalid;
}
}
}
11 changes: 11 additions & 0 deletions DigitaleBriefwahl/Model/YesNoElectionModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,5 +183,16 @@ public override string GetResultString(Dictionary<string, CandidateResult> resul
bldr.AppendLine(base.GetResultString(results));
return bldr.ToString();
}

public override bool SkipNominee(string name, int iVote)
{
return false;
}

public override HashSet<int> GetInvalidVotes(List<string> electedNominees)
{
// Unused - handled by the UI
return null;
}
}
}
2 changes: 0 additions & 2 deletions DigitaleBriefwahl/Views/ElectionViewBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ internal abstract class ElectionViewBase
{
private TabPage _page;

public const string Abstention = "<Enthaltung>";

protected ElectionModel Election { get; }

protected ElectionViewBase(ElectionModel election)
Expand Down
77 changes: 24 additions & 53 deletions DigitaleBriefwahl/Views/WeightedElectionView.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2016 Eberhard Beilharz
// Copyright (c) 2016-2024 Eberhard Beilharz
// This software is licensed under the GNU General Public License version 3
// (https://opensource.org/licenses/GPL-3.0)
using System;
Expand All @@ -12,7 +12,7 @@ namespace DigitaleBriefwahl.Views
{
internal class WeightedElectionView: ElectionViewBase
{
private List<ComboBox> _ComboBoxes;
private List<ComboBox> _comboBoxes;
private Color _defaultTextColor;

public WeightedElectionView(ElectionModel election)
Expand All @@ -25,7 +25,7 @@ public override TabPage Layout()
var page = base.Layout();

var layout = page.Content as StackLayout;
_ComboBoxes = new List<ComboBox>(Election.Votes);
_comboBoxes = new List<ComboBox>(Election.Votes);
for (var i = 0; i < Election.Votes; i++)
{
if (Election.TextBefore[i] != null)
Expand All @@ -45,84 +45,55 @@ public override TabPage Layout()
}
};
var combo = new ComboBox();
_ComboBoxes.Add(combo);
_comboBoxes.Add(combo);
foreach (var nominee in Election.Nominees)
{
if (SkipNominee(nominee, i))
if (Election.SkipNominee(nominee, i))
continue;
combo.Items.Add(new ListItem { Text = nominee });
}
combo.Items.Add(new ListItem { Text = Abstention });
combo.Items.Add(new ListItem { Text = Configuration.Abstention });
voteLine.Items.Add(combo);
layout.Items.Add(new StackLayoutItem(voteLine));
}
if (Election.Votes > 0)
_defaultTextColor = _ComboBoxes[0].TextColor;
_defaultTextColor = _comboBoxes[0].TextColor;
return page;
}

private bool SkipNominee(string name, int iVote)
{
var vote = iVote + 1;
if (!Election.NomineeLimits.ContainsKey(name))
return false;

var limit = Election.NomineeLimits[name];
return vote < limit.Item1 || vote > limit.Item2;
}

public override bool VerifyOk()
{
var allOk = true;
for (var i = 0; i < Election.Votes; i++)
_ComboBoxes[i].TextColor = _defaultTextColor;
_comboBoxes[i].TextColor = _defaultTextColor;

for (var i = 0; i < Election.Votes; i++)
var invalids = Election.GetInvalidVotes(GetResultList());
var isFirst = true;
foreach (var invalid in invalids)
{
if (string.IsNullOrEmpty(_ComboBoxes[i].SelectedKey))
_comboBoxes[invalid].TextColor = Colors.Red;
if (isFirst)
{
_ComboBoxes[i].TextColor = Colors.Red;
if (allOk)
_ComboBoxes[i].Focus();
allOk = false;
continue;
_comboBoxes[invalid].Focus();
isFirst = false;
}

for (var j = i + 1; j < Election.Votes; j++)
{
if (_ComboBoxes[i].SelectedKey != _ComboBoxes[j].SelectedKey ||
_ComboBoxes[i].SelectedKey == Abstention)
{
continue;
}

_ComboBoxes[i].TextColor = Colors.Red;
_ComboBoxes[j].TextColor = Colors.Red;
if (allOk)
_ComboBoxes[i].Focus();
allOk = false;
}

if (!SkipNominee(_ComboBoxes[i].SelectedKey, i))
continue;

_ComboBoxes[i].TextColor = Colors.Red;
if (allOk)
_ComboBoxes[i].Focus();
allOk = false;
}
return allOk;
return invalids.Count == 0;
}

public override string GetResult(bool writeEmptyBallot)
private List<string> GetResultList()
{
var electedNominees = new List<string>();
for (var i = 0; i < Election.Votes; i++)
{
electedNominees.Add(_ComboBoxes[i].SelectedKey);
electedNominees.Add(_comboBoxes[i].SelectedKey);
}

return Election.GetResult(electedNominees, writeEmptyBallot);
return electedNominees;
}

public override string GetResult(bool writeEmptyBallot)
{
return Election.GetResult(GetResultList(), writeEmptyBallot);
}
}
}
9 changes: 7 additions & 2 deletions DigitaleBriefwahl/Views/YesNoElectionView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ private void SetTextColorForRadioButtonGroup(int i, Color color)
_radioButtons[i][2].TextColor = color;
}

public override string GetResult(bool writeEmptyBallot)
private List<string> GetResultList()
{
var votes = new List<string>();
for (var i = 0; i < Election.Nominees.Count; i++)
Expand All @@ -96,7 +96,12 @@ public override string GetResult(bool writeEmptyBallot)
votes.Add(YesNoElectionModel.Abstention);
}

return Election.GetResult(votes, writeEmptyBallot);
return votes;
}

public override string GetResult(bool writeEmptyBallot)
{
return Election.GetResult(GetResultList(), writeEmptyBallot);
}
}
}
Loading

0 comments on commit 3ee96ed

Please sign in to comment.