gopolls

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2020 License: Apache-2.0 Imports: 13 Imported by: 0

README

gopolls

A library for Go (Golang) to support different polling procedures.

About This Library

This library supports three different kind of polls: Normal polls (Aye, No, Abstention,), Median Polls (the highest value in a sequence of numbers that has a majority wins) and Schulze Polls with the Schulze method.

Note: There is no extensive documentation / usage examples yet. This library is still in an early stage and likely to change with changes that are not backwards compatible. So use it with care!

For now you can hav a look at the docs on godocs.

Simple App

This project comes with a demo app that builds a simple (and not good / production ready) webserver. The code can be found in cmd/poll.

There are binary releases of this app for linux, mac and Windows. Just download the .zip file and execute the binary.

As already mentioned there is no documentation yet, so usage examples will follow once the API is more or less stable.

The Wiki will most likely contain this documentation.

License

Copyright 2020 Fabian Wenzelmann fabianwen@posteo.eu

Licensed under the Apache License, Version 2.0

Third-Party Licenses

This tool is built with only the Golang standard library (License). It uses however pure-css (contained in the distributions of the demo app). pure-css is licensed under the Yahoo BSD License License.

It also uses a template from the pure-css Layouts page (also contained in this distribution).

All these files are contained here and here and are not covered but my copyright but the copyrights mentioned above.

Documentation

Index

Constants

View Source
const (
	MedianPollType  = "median-poll"
	SchulzePollType = "schulze-poll"
	BasicPollType   = "basic-poll"
)
View Source
const (
	MoneyPollSkeletonType   = "money-skeleton"
	GeneralPollSkeletonType = "basic-skeleton"
)
View Source
const (
	BasicVoteType   = "basic-vote"
	MedianVoteType  = "median-vote"
	SchulzeVoteType = "schulze-vote"
)
View Source
const DefaultCSVSeparator = ','

Variables

View Source
var (
	FiftyPercentMajority = big.NewRat(1, 2)
	TwoThirdsMajority    = big.NewRat(2, 3)
)
View Source
var DefaultParserTemplateMap = GenerateDefaultParserTemplateMap()

DefaultParserTemplateMap contains default templates for BasicPollType, MedianPollType and SchulzePollType. Of course it can be extended. The easiest way to extend the default parsers is use to either insert values directly here or, if you don't want that, generate a fresh map with GenerateDefaultParserTemplateMap.

View Source
var DefaultSkeletonConverter = NewDefaultSkeletonConverter(true)

DefaultSkeletonConverter is the default implementation of SkeletonConverter. It does the following translations: A MoneyPollSkel gets translated to a MedianPol, it checks if the value described is >= (< 0 is not allowed). A PollSkeleton is translated to a BasicPoll or SchulzePoll. A BasicPoll is returned if the PollSkeleton has exactly two options,otherwise a SchulzePoll is created. If the number of options in the PollSkeleton is < 2 an error is returned.

It is just NewDefaultSkeletonConverter(true).

View Source
var ErrEmptyPollPolicy = NewPollTypeError("empty votes are not allowed")

ErrEmptyPollPolicy is an error used if a policy is set to RaiseErrorEmptyVote and an empty vote was found. GenerateEmptyVoteForVoter will in this case return an error e s.t. errors.Is(e, ErrEmptyPollPolicy) returns true. This should of course be checked before errors.Is(e, ErrPoll) because this is true for all internal errors.

View Source
var ErrInvalidEncoding = NewParserValidationError("invalid utf-8 encoding in input")

ErrInvalidEncoding is an error used to signal that an input string is not encoded with valid utf-8.

View Source
var ErrPoll = internalErrorSentinelType{}

ErrPoll is a constant that can be used with a type check. All internal errors (such as syntax error) can be used in a statement like errors.Is(err, ErrPoll) and return true. This can be useful when you want to distinguish between an error from gopolls and an "outside" error. If you want to dig deeper, for example find out if an error is a syntax error, you should use errors.As(err, *ERROR_TYPE).

Functions

func ComputePercentage

func ComputePercentage(votes, votesSum Weight) *big.Rat

ComputePercentage is used to calculate how many percent of the voters (or given their weight) voted for a certain option. To remain as exact as possible we use big.Rat values. The computed percentage is simply votes/votesSum. If votesSum is 0 the result will always be zero.

func CustomizeParsersToMap

func CustomizeParsersToMap(polls PollMap, templates map[string]ParserCustomizer) (map[string]ParserCustomizer, error)

CustomizeParsersToMap customizes parser templates for each poll.

For details see CustomizeParsers. This function will return one entry in the result map for each poll in polls.

func DumpAbstractPollSkeleton

func DumpAbstractPollSkeleton(skel AbstractPollSkeleton, w io.Writer, currencyFormatter CurrencyFormatter) (int, error)

DumpAbstractPollSkeleton writes a skeleton description to a writer. It works only with the two "default" implementations.

It returns the number of bytes written and any error that occurred writing to w.

It needs a currencyFormatter to write MoneyPollSkeleton instances.

func FormatPercentage

func FormatPercentage(percent *big.Rat) string

FormatPercentage is used to format a percent value (usually this value should be 0 <= value <= 1). You can use this function to get consistent output throughout your application. The returned percentage is always with three precision points after the comma.

The percent value is multiplied with 100, so 1/2 gets formatted to "50.000".

func GenerateDefaultParserTemplateMap

func GenerateDefaultParserTemplateMap() map[string]ParserCustomizer

func HasDuplicateVoters

func HasDuplicateVoters(voters []*Voter) (string, bool)

HasDuplicateVoters tests if there are duplicate names in a given voters list. It returns false if there are no duplicates, otherwise the first name that was found multiple times is returned together with true.

Types

type AbstractPoll

type AbstractPoll interface {
	PollType() string
	AddVote(vote AbstractVote) error
}

AbstractPoll describes any poll. It has a method PollType which returns the type as a string. Most operations dealing with polls do type assertions / switches are operate depending on the string of PollType().

Constants are defined for implemented poll types: MedianPollType, SchulzePollType and BasicPollType.

The method AddVote should add a would to the poll and return an error if the type is not supported (of type PollError). This method is not allowed to be called concurrently by multiple goroutines for the same poll instance.

It is also recommended to implement the VoteGenerator interface to create votes for Aye, No and Abstention for a given poll.

func ConvertSkeletonsToPolls

func ConvertSkeletonsToPolls(skeletons []AbstractPollSkeleton, converterFunction SkeletonConverter) ([]AbstractPoll, error)

ConvertSkeletonsToPolls does the translation from a list of skeletons to a list of (empty) polls. It uses a SkeletonConverter function to do the actual conversion and returns an error if any of the skeletons in the list is not "valid". If converterFunction is nil DefaultSkeletonConverter is used.

ConvertSkeletonsToPolls is a function that does the same for maps.

type AbstractPollSkeleton

type AbstractPollSkeleton interface {
	SkeletonType() string
	GetName() string
}

AbstractPollSkeleton describes a poll skeleton or "framework". Skeletons can be mapped to a poll instance with a SkeletonConverter. The reason for having skeletons and polls is that a description might be interpreted differently and there are various methods for evaluating a poll For example the Schulze method or any other procedure.

Thus a skeleton is the description (which options are there etc.) while the poll itself contains the logic of evaluating it.

We have two different skeletons at the moment (but three poll implementations):

MoneyPollSkeleton, has a name and a money value, is usually converted to a MedianPoll.

PollSkeleton, has a name and a list of options. It is usually converted to either a SchulzePoll (> 2 options) or a BasicPoll (two options). The converter implemented at the moment, DefaultSkeletonConverter, will do the translations mentioned above, but other ones are possible too.

type AbstractVote

type AbstractVote interface {
	GetVoter() *Voter
	VoteType() string
}

AbstractVote describes an abstract vote (usually each poll has one vote type).

It can retrieve the voter that voted and a type string which must be unique for all implementations.

type BasicPoll

type BasicPoll struct {
	Votes []*BasicVote
}

BasicPoll is a poll with the options No, Yes and Abstention, for details see BasicPollAnswer. It implements the interface AbstractPoll.

This type also implements VoteGenerator.

func NewBasicPoll

func NewBasicPoll(votes []*BasicVote) *BasicPoll

NewBasicPoll returns a new BasicPoll with the given votes.

func (*BasicPoll) AddVote

func (poll *BasicPoll) AddVote(vote AbstractVote) error

AddVote adds a vote to the poll, the vote must be of type *BasicVote.

func (*BasicPoll) GenerateVoteFromBasicAnswer

func (poll *BasicPoll) GenerateVoteFromBasicAnswer(voter *Voter, answer BasicPollAnswer) (AbstractVote, error)

GenerateVoteFromBasicAnswer implements VoteGenerator and returns a BasicVote.

func (*BasicPoll) PollType

func (poll *BasicPoll) PollType() string

PollType returns the constant BasicPollType.

func (*BasicPoll) Tally

func (poll *BasicPoll) Tally() *BasicPollResult

Tally counts how often a certain answer was taken. Note that invalid votes might occur and will be counted in the NumInvalid fields.

func (*BasicPoll) TruncateVoters

func (poll *BasicPoll) TruncateVoters() []*BasicVote

TruncateVoters is one of the truncate methods that exist for nearly every poll (really for everyone implemented directly in this library).

It finds votes that are "invalid". By invalid we mean votes whose answer is none of the constants No, Aye or Abstention.

In general instead of using this method it would probably be easier to evaluate the poll with Tally and just look if there are invalid votes in the result.

type BasicPollAnswer

type BasicPollAnswer int8

BasicPollAnswer is the answer for a poll with the options "No", "Aye / Yes" and "Abstention".

const (
	No BasicPollAnswer = iota
	Aye
	Abstention
)

func (BasicPollAnswer) IsValid

func (a BasicPollAnswer) IsValid() bool

IsValid tests if the answer is valid, i.e. one of the constants No, Aye, Abstention.

func (BasicPollAnswer) String

func (a BasicPollAnswer) String() string

type BasicPollCounter

type BasicPollCounter struct {
	NumNoes, NumAyes, NumAbstention, NumInvalid Weight
}

BasicPollCounter is used to count how often a certain option was chosen.

func NewBasicPollCounter

func NewBasicPollCounter() *BasicPollCounter

NewBasicPollCounter returns a new BasicPollCounter with all counters set to 0.

func (*BasicPollCounter) Equals

func (counter *BasicPollCounter) Equals(other *BasicPollCounter) bool

Equals tests if two counter objects store the same state.

func (*BasicPollCounter) Increase

func (counter *BasicPollCounter) Increase(choice BasicPollAnswer, inc Weight)

Increase increases the counter given the choice, the counter increased depends on choice. inc is the value by which the counter is increased.

type BasicPollResult

type BasicPollResult struct {
	NumberVoters  *BasicPollCounter
	WeightedVotes *BasicPollCounter
	VotersCount   Weight
	VotesSum      Weight
}

BasicPollResult is the result of evaluating a BasicPoll.

It stores two instances of BasicPollCounter: NumberVoters counts how often an answer was taken, independent of the weight of the voter. WeightedVotes counts how often an answer was taken, by summing up not the number of voters but the weight of these voters.

WeightSum is the sum of the weights of all votes in the poll, VotersCount the number of voters (as a weight).

func NewBasicPollResult

func NewBasicPollResult() *BasicPollResult

NewBasicPollResult returns a new BasicPollResult with all values set to 0.

type BasicVote

type BasicVote struct {
	Voter  *Voter
	Choice BasicPollAnswer
}

BasicVote is a vote for a BasicPoll. It is described by the answer and the voter. It implements the interface AbstractVote.

func NewBasicVote

func NewBasicVote(voter *Voter, choice BasicPollAnswer) *BasicVote

NewBasicVote returns a new BasicVote.

func (*BasicVote) GetVoter

func (vote *BasicVote) GetVoter() *Voter

GetVoter returns the voter of the vote.

func (*BasicVote) VoteType

func (vote *BasicVote) VoteType() string

VoteType returns the constant BasicVoteType.

type BasicVoteParser

type BasicVoteParser struct {
	NoValues          LowerStringSet
	AyeValues         LowerStringSet
	AbstentionValues  LowerStringSet
	AllowRankingStyle bool
}

BasicVoteParser implements VoteParser and returns an instance of BasicVote in its ParseFromString method.

It allows two styles of strings:

First a simple string that describes No, Aye/Yes and Abstention. The lists of valid strings for these options can be configured with the NoValues, AyeValues and AbstentionValues sets. These sets must be all lower case strings defining the valid options. The defaults, as created by NewBasicVoteParser, are (English and German words): {"+", "n", "no", "nein", "dagegen"} for NoValues, {"-", "a", "aye", "y", "yes", "ja", "dafür"} for AyeValues and {"/", "abstention", "enthaltung"} for AbstentionValues.

The second style is in the form of Schulze vote, i.e. two integers in the form "a, b". These two are the ranking positions if this vote would be interpreted as in a Schulze poll (see Schulze method for details). The number a is the sorting position for the Yes/Aye option, b the sorting position for the No option. Thus a string "a, b" (for valid integers a and b) is translated to: Aye if a < b, No if b < a and Abstention if a = b.

Because this style might be confusing for people not familiar with the Schulze method the acceptance of the ranking style can be disabled with AllowRankingStyle = false.

It also implements ParserCustomizer.

func NewBasicVoteParser

func NewBasicVoteParser() *BasicVoteParser

NewBasicVoteParser returns a new BasicVoteParser with the default strings as described in the type description and AllowRankingStyle set to true.

func (*BasicVoteParser) CustomizeForPoll

func (parser *BasicVoteParser) CustomizeForPoll(poll AbstractPoll) (ParserCustomizer, error)

CustomizeForPoll implements ParserCustomizer and returns just the same parser again if a *BasicPoll is given.

func (*BasicVoteParser) ParseFromString

func (parser *BasicVoteParser) ParseFromString(s string, voter *Voter) (AbstractVote, error)

ParseFromString implements the VoteParser interface, for details see type description.

type CurrencyFormatter

type CurrencyFormatter interface {
	Format(value CurrencyValue) string
}

CurrencyFormatter formats a currency value to a string.

type CurrencyHandler

type CurrencyHandler interface {
	CurrencyFormatter
	CurrencyParser
}

CurrencyHandler combines formatter and parser in one interface.

A general rule of thumb is: If a formatter returns a string representation for a currency value that same currency value should be parsed correctly back without errors.

var (
	// DefaultCurrencyHandler is the default CurrencyHandler, it is a SimpleEuroHandler, but it is not guaranteed
	// that this never changes.
	DefaultCurrencyHandler CurrencyHandler = SimpleEuroHandler{}
)

type CurrencyParser

type CurrencyParser interface {
	Parse(s string) (CurrencyValue, error)
}

CurrencyParser parses a currency value from a string, error should be of type PollingSyntaxError or PollingSemanticError.

type CurrencyValue

type CurrencyValue struct {
	ValueCents int
	Currency   string
}

CurrencyValue represents a money value in a certain currency. The value is always represented as "cents", for example 1.23 € would be represented as ValueCents=123 and currency "€".

There are also interfaces defined for formatting / parsing currency values.

func NewCurrencyValue

func NewCurrencyValue(valueCents int, currency string) CurrencyValue

NewCurrencyValue returns a new CurrencyValue.

func (CurrencyValue) Copy

func (value CurrencyValue) Copy() CurrencyValue

Copy creates a copy of the value with exactly the same content.

func (CurrencyValue) DefaultFormatString

func (value CurrencyValue) DefaultFormatString(sep string) string

DefaultFormatString returns a standard format and might be useful for formatters. It returns strings of the form 0.09, 0.21, 21.42 €. The separator (in the examples the dot) can be configured with sep.

func (CurrencyValue) Equals

func (value CurrencyValue) Equals(other CurrencyValue) bool

Equals tests if two CurrencyValue objects are identical.

Not that this method does not do "semantic" comparison on the currency, for example one could say that {42, "€"} is equal to {42, "EUR"}. This function however directly compares ValueCents and Currency.

func (CurrencyValue) String

func (value CurrencyValue) String() string

type DuplicateError

type DuplicateError struct {
	PollError
	Msg string
}

DuplicateError is an error returned if somewhere a duplicate name is found.

For example two voter objects with the same name.

func NewDuplicateError

func NewDuplicateError(msg string) DuplicateError

NewDuplicateError returns a new DuplicateError.

func (DuplicateError) Error

func (err DuplicateError) Error() string

type EmptyVotePolicy

type EmptyVotePolicy int8

EmptyVotePolicy describes the behavior if an "empty" vote is found.

By empty vote we mean that a certain voter just didn't cast a vote for a poll. If this is the case there are different things to do, depending on the application.

The option that is chosen most often will probably be IgnoreEmptyVote: For a voter there is no vote so just assume that the voter wasn't there when the poll took place and ignore it (IgnoreEmptyVote).

It is also possible to have polls where each voter has to cast a vote, in this case an error should be returned (RaiseErrorEmptyVote).

In certain cases even voters who didn't cast a vote should be considered, for example in absolute majorities. The empty votes can then be treated as "No", "Aye" or "Abstention" (No and Abstention or the most likely options here). These are described by the policies AddAsAyeEmptyVote, AddAsNoEmptyVote and AddAsAbstentionEmptyVote.

const (
	IgnoreEmptyVote EmptyVotePolicy = iota
	RaiseErrorEmptyVote
	AddAsAyeEmptyVote
	AddAsNoEmptyVote
	AddAsAbstentionEmptyVote
)

func GeneratePoliciesList

func GeneratePoliciesList(policy EmptyVotePolicy, num int) []EmptyVotePolicy

GeneratePoliciesList is just a small helper function that returns a list of num elements, each entry is set to the given policy. GeneratePoliciesMap does the same for a map.

func (EmptyVotePolicy) GenerateEmptyVoteForVoter

func (policy EmptyVotePolicy) GenerateEmptyVoteForVoter(voter *Voter, poll AbstractPoll) (AbstractVote, error)

GenerateEmptyVoteForVoter can be called to generate a vote for a poll if the input was empty. By empty we mean that the voter simple didn't cast a vote. If this method is called it will chose the action depending on the policy.

If it is IgnoreEmptyVote it returns nil for the vote and nil as an error. So be aware that a vote can be nil even if the error is nil.

If the policy is RaiseErrorEmptyVote an error will be returned.

In all other cases poll must implement VoteGenerator (if it does not an error is returned) and the return value depends on the poll which is responsible to create an Aye, No or Abstention vote.

For the implemented types note that MedianPoll does not support abstention.

type LowerStringSet

type LowerStringSet map[string]struct{}

LowerStringSet is a set of lower case strings.

func NewLowerStringSet

func NewLowerStringSet(elements []string) LowerStringSet

NewStringSet returns a new set given its elements, all elements are transformed to lower case.

func (LowerStringSet) Contains

func (s LowerStringSet) Contains(element string) bool

Contains returns true if the lowercase version of s is contained within s. The difference to ContainsLowercase is that this method will always convert s to lower case.

func (LowerStringSet) ContainsLowercase

func (s LowerStringSet) ContainsLowercase(element string) bool

ContainsLowercase returns true if element is contained within s. Note that element must already be lower case, otherwise this method will not work correctly!

func (LowerStringSet) Extend

func (s LowerStringSet) Extend(elements []string)

Extend adds all elements to the set, all elements are transformed to lower case.

func (LowerStringSet) Insert

func (s LowerStringSet) Insert(element string)

Insert inserts a new element, the element is transformed to lower case.

func (LowerStringSet) String

func (s LowerStringSet) String() string

type MedianPoll

type MedianPoll struct {
	Value  MedianUnit
	Votes  []*MedianVote
	Sorted bool
}

MedianPoll is a poll that can be evaluated with the median method. It implements the interface AbstractPoll.

The median method for polls works as follows: The value that "wins" the poll is the highest value that has a majority, taking into account the weight of the voters. See Tally for details.

Note: If a voter voted for a value > poll.Value this value could be chosen as the winner. Because this doesn't make much sense you should take care to "truncate" the votes. You can use TruncateVoters for this.

It also has a Sorted attribute which is set to true once the votes are sorted according to value, s.t. the highest votes come first. See SortVotes and AssureSorted for this. You can set Sorted to true if you have already sorted them (for example in a database query). The SortVotes method will in-place sort the Votes, thus changing the original slice. The Tally method always calls AssureSorted.

This type also implements VoteGenerator.

func NewMedianPoll

func NewMedianPoll(value MedianUnit, votes []*MedianVote) *MedianPoll

NewMedianPoll returns a new poll given the value in question and the votes for the poll. Note: Read the type documentation carefully! This method will set Sorted to False and will not truncate the voters.

func (*MedianPoll) AddVote

func (poll *MedianPoll) AddVote(vote AbstractVote) error

AddVote adds a vote to the poll, the vote must be of type *MedianVote.

Note that no vote validation is happening here! I.e. the vote can have an "invalid" value, for example a value that is too large. We do this because in general it is also allowed to append any vote, it is the job of the user of this library to deal with invalid votes.

func (*MedianPoll) AssureSorted

func (poll *MedianPoll) AssureSorted()

AssureSorted makes sure that the votes are sorted, if they're not sorted (according to poll.Sorted) they will be sorted.

func (*MedianPoll) GenerateVoteFromBasicAnswer

func (poll *MedianPoll) GenerateVoteFromBasicAnswer(voter *Voter, answer BasicPollAnswer) (AbstractVote, error)

GenerateVoteFromBasicAnswer implements VoteGenerator and returns a MedianVote.

Abstention is not an allowed value here! It will return a vote for 0 for No, a vote for poll.Value for Yes.

func (*MedianPoll) PollType

func (poll *MedianPoll) PollType() string

PollType returns the constant MedianPollType.

func (*MedianPoll) SortVotes

func (poll *MedianPoll) SortVotes()

SortVotes sorts the votes list in-place according to vote.Value (highest votes first).

func (*MedianPoll) Tally

func (poll *MedianPoll) Tally(majority Weight) *MedianResult

Tally computes the result of a median poll.

Majority can be set to the majority that the result requires. It defaults to the sum of all voter weights divided by two if set to NoWeight. It wins the highest value that can accumulate a weight > (strictly!) majority. For computing majorities see ComputeMajority.

An example: If there are 10 voters, each with weight one, the highest value that reaches > 5 (meaning at least 6) votes wins. If there were 7 such voters > 3 (meaning 4) voters a required.

Note that usually the value 0 should have a majority (because it is the smallest one allowed). If there are no voters or majority is incorrect (for example > total weight sum) MajorityValue might be set to NoMedianUnitValue.

This method will also make sure that the polls are sorted (AssureSorted). The runtime of this method is (for n = number of voters) O(n) if already sorted and O(n * log n) if not sorted.

func (*MedianPoll) TruncateVoters

func (poll *MedianPoll) TruncateVoters() []*MedianVote

TruncateVoters identifies all votes that contain a value > poll.Value.

It could lead to "weird" results if the value the voters agreed upon was > poll.Value. This way the poll gets filtered by updating the value of such a vote to poll.Value. The result returned contains the original entries with a value > poll.Value (for logging purposes). The returned result contains shallow copies of the culprit (i.e. Value is copied and the *Voter object is re-used).

Note: If you use this method the sorting order should be maintained, everyone who voted with a value > poll.Value should be at the beginning of the slice and are now set to poll.Value. Because all other votes have a value <= poll.Value this should be fine. Thus if the votes are already sorted they should be sorted afterwards too.

func (*MedianPoll) WeightSum

func (poll *MedianPoll) WeightSum() Weight

WeightSum returns the sum of all voters weights.

type MedianResult

type MedianResult struct {
	WeightSum        Weight
	RequiredMajority Weight
	MajorityValue    MedianUnit
	ValueDetails     map[MedianUnit][]*Voter
}

MedianResult is the result of evaluating a median poll, see Tally method.

The result contains the following information: WeightSum is the sum of all weights from the votes. RequiredMajority is the majority that was required for the winning value. MajorityValue is the highest value that had the RequiredMajority. ValueDetails maps all values that occurred in at least one vote and maps it to the voters that voted for this value. This map can be further analyzed with GetVotersForValue.

func NewMedianResult

func NewMedianResult() *MedianResult

NewMedianResult returns a new MedianResult.

The returned instance has WeightSum and RequiredMajority set to NoWeight, MajorityValue set to NoMedianUnitValue and ValueDetails to an empty map.

func (*MedianResult) GetVotersForValue

func (result *MedianResult) GetVotersForValue(referenceValue MedianUnit) []*Voter

GetVotersForValue can be used to analyze ValueDetails.

Given a referenceValue it returns a list of all voters that voted for a value >= referenceValue. Not that the runtime is in O(#voters).

type MedianUnit

type MedianUnit uint64

MedianUnit is the unit used in median polls and votes (the value the poll is about).

const NoMedianUnitValue MedianUnit = math.MaxUint64

NoMedianUnitValue is used to signal that a value is not a valid MedianUnit, for example as default argument.

func ParseMedianUnit

func ParseMedianUnit(s string) (MedianUnit, error)

ParseMedianUnit parses a MedianUnit from a string.

A PollingSyntaxError is returned if s is no valid uint or is NoMedianUnitValue.

type MedianVote

type MedianVote struct {
	Voter *Voter
	Value MedianUnit
}

MedianVote is a vote for a MedianPoll.

The vote has a voter (weight taken into account) and the Value the voter voted for. It implements the interface AbstractVote.

func NewMedianVote

func NewMedianVote(voter *Voter, value MedianUnit) *MedianVote

NewMedianVote returns a new median vote given the voter and the value the voter voted for.

func (*MedianVote) GetVoter

func (vote *MedianVote) GetVoter() *Voter

GetVoter returns the voter of the vote.

func (*MedianVote) VoteType

func (vote *MedianVote) VoteType() string

VoteType returns the constant MedianVoteType.

type MedianVoteParser

type MedianVoteParser struct {
	// contains filtered or unexported fields
}

MedianVoteParser implements VoteParser and returns an instance of MedianVote in its ParseFromString method.

It allows a currency value to be parsed. The currency value must be > 0, otherwise an error is returned.

The currency is not directly parsed, instead it uses any CurrencyParser, this way the style of the string can be adapted to your needs.

It also allows to set a maxValue, that is every vote with a value > maxValue will return an error when parsed.

func NewMedianVoteParser

func NewMedianVoteParser(currencyParser CurrencyParser) *MedianVoteParser

NewMedianVoteParser returns a new MedianVoteParser given the currency parser.

The maxValue is set to NoMedianUnitValue, meaning that it is disabled and doesn't check for a max value. To enable it use WithMaxValue.

It also implements ParserCustomizer.

func (*MedianVoteParser) CustomizeForPoll

func (parser *MedianVoteParser) CustomizeForPoll(poll AbstractPoll) (ParserCustomizer, error)

CustomizeForPoll implements ParserCustomizer and returns a new parser with maxValue set if a *MedianPoll is given.

func (*MedianVoteParser) ParseFromString

func (parser *MedianVoteParser) ParseFromString(s string, voter *Voter) (AbstractVote, error)

ParseFromString implements the VoteParser interface, for details see type description.

func (*MedianVoteParser) WithMaxValue

func (parser *MedianVoteParser) WithMaxValue(maxValue MedianUnit) *MedianVoteParser

WithMaxValue returns a shallow copy of the parser with only maxValue set to the new value.

type MoneyPollSkeleton

type MoneyPollSkeleton struct {
	Name  string
	Value CurrencyValue
}

MoneyPollSkeleton is an AbstractPollSkeleton for a poll about some currency value (money).

func NewMoneyPollSkeleton

func NewMoneyPollSkeleton(name string, value CurrencyValue) *MoneyPollSkeleton

NewMoneyPollSkeleton returns a new MoneyPollSkeleton.

func (*MoneyPollSkeleton) Dump

func (skel *MoneyPollSkeleton) Dump(w io.Writer, currencyFormatter CurrencyFormatter) (int, error)

Dump writes the skeleton to some writer w, it needs a currencyFormatter to write currency values.

It returns the number of bytes written as well as any error writing to w.

func (*MoneyPollSkeleton) GetName

func (skel *MoneyPollSkeleton) GetName() string

GetName returns the name of the poll description.

func (*MoneyPollSkeleton) SkeletonType

func (skel *MoneyPollSkeleton) SkeletonType() string

SkeletonType returns the constant MoneyPollSkeletonType.

type ParserCustomizer

type ParserCustomizer interface {
	VoteParser
	CustomizeForPoll(poll AbstractPoll) (ParserCustomizer, error)
}

ParserCustomizer is a parser that allows customization based on a poll.

For example a median poll can be customized by setting a max value, that is the maximal value this parser will parse. The workflow then is this: Create a parser "template" with the default options you want to use and then customize it for each poll with CustomizeForPoll. An example and a helper function is given CustomizeParsers.

In the median example: The template consists of a parser that allows all valid numbers / integers. It is then customized for a certain poll by setting the max value of that poll.

The CustomizeForPoll method should return the customized parser, if poll is of the wrong type or the operation is in some way not allowed a PollTypeError should be returned.

All parsers from this package also implement this interface.

func CustomizeParsers

func CustomizeParsers(polls []AbstractPoll, templates map[string]ParserCustomizer) ([]ParserCustomizer, error)

CustomizeParsers customizes parser templates for each poll.

As discussed in the documentation for ParserCustomizer each parser can be customized for a specific poll. This method will apply CustomizeForPoll on a list of polls.

The templates map must have an entry for each poll type string. For example a BasicPoll returns BasicPollType in PollType(). This string must be mapped to a ParserCustomizer that works as the template for all BasicPolls.

DefaultParserTemplateMap contains some default templates for BasicPollType, MedianPollType and SchulzePollType.

Of course there are other ways to do the conversion, but this is a nice helper function.

It returns a PollTypeError if a template is not found in templates and returns any error from calls to CustomizeForPoll.

CustomizeParsersToMap is a function that has the same functionality but for maps.

type ParserValidationError

type ParserValidationError struct {
	PollError
	Message string
}

ParserValidationError is an error returned if a validation of the input files. Such errors include: invalid utf-8 encoding (see ErrInvalidEncoding) or a line was longer than allowed.

func NewParserValidationError

func NewParserValidationError(msg string) *ParserValidationError

func (ParserValidationError) Error

func (err ParserValidationError) Error() string

func (ParserValidationError) Unwrap

func (err ParserValidationError) Unwrap() error

type PolicyMap

type PolicyMap map[string]EmptyVotePolicy

PolicyMap defines a mapping from poll name to an empty vote policy.

func GeneratePoliciesMap

func GeneratePoliciesMap(policy EmptyVotePolicy, polls PollMap) PolicyMap

GeneratePoliciesMap is just a small helper function that returns a PolicyMap for all polls in the given map. The map returned maps each poll name to the given policy.

type PollCollectionParser

type PollCollectionParser struct {
	MaxNumLines        int
	MaxNumPolls        int
	MaxLineLength      int
	MaxTitleLength     int
	MaxGroupNameLength int
	MaxPollNameLength  int
	MaxNumOptions      int
	MaxOptionLength    int
	MaxCurrencyValue   int
}

PollCollectionParser parses a poll collection from a file / string. See ParseCollectionSkeletons and ParseCollectionSkeletonsFromString.

Furthermore the parser can be configured to read only a certain amount of lines / put restrictions on the polls parsed. This is the same idea as in VotersParser, see there for details of when you would want to use restrictions.

A new parser from NewPollCollectionParser sets all values to -1, meaning no restrictions.

The following restrictions can be configured: MaxNumLines is the number of lines that are allowed in a polls file. MaxNumPolls is the maximal number of polls allowed in that file. MaxLineLength is the maximal number of bytes (not runes) allowed in a single line of the file. MaxTitleLength is the maximal length the title / heading is allowed to have. MaxGroupNameLength is the maximal length a group is allowed to have. MaxPollNameLength is the maximal length a poll name is allowed to have. MaxNumOptions should be set to at least two, it describes how many options in a basic poll are allowed. MaxOptionLength is the maximal length a single option is allowed to have. MaxCurrencyValue is the maximal currency value (in cents) that is allowed. This can be useful to avoid overflows / database limitations.

Again, some combinations would not make sense, like setting MaxNumLines=21 and MaxTitleLength=42.

func NewPollCollectionParser

func NewPollCollectionParser() *PollCollectionParser

NewPollCollectionParser returns a new parser with all limitations / restrictions disabled.

func (*PollCollectionParser) ParseCollectionSkeletons

func (parser *PollCollectionParser) ParseCollectionSkeletons(r io.Reader, currencyParser CurrencyParser) (*PollSkeletonCollection, error)

ParseCollectionSkeletons parses a collection of poll descriptions and returns them as skeletons. See wiki and example files for format details.

func (*PollCollectionParser) ParseCollectionSkeletonsFromString

func (parser *PollCollectionParser) ParseCollectionSkeletonsFromString(currencyParser CurrencyParser, s string) (*PollSkeletonCollection, error)

ParseCollectionSkeletonsFromString works as ParseCollectionSkeletons but parses the input from a string.

type PollError

type PollError struct{}

PollError is an error used for errors that should be considered a polling error, such as syntax error, evaluation errors for your own poll types etc. The type itself does not implement the error interface, but only the method Is(err error) from the error package. This way you can just embed this type in your own error type and Is(err, ErrPoll) will return true.

func (PollError) Is

func (pollErr PollError) Is(err error) bool

Is returns true if err == ErrPoll.

type PollGroup

type PollGroup struct {
	Title     string
	Skeletons []AbstractPollSkeleton
}

PollGroup is a group (collection) of votes.

Polls are put into groups and a list of groups describes a poll collection.

func NewPollGroup

func NewPollGroup(title string) *PollGroup

NewPollGroup returns a new PollGroup with an empty list of skeletons.

func (*PollGroup) Dump

func (group *PollGroup) Dump(w io.Writer, currencyFormatter CurrencyFormatter) (int, error)

Dump writes this group to a writer, it needs a currencyFormatter to write money polls.

It returns the number of bytes written as well as any error writing to w.

func (*PollGroup) NumSkeletons

func (group *PollGroup) NumSkeletons() int

NumSkeletons returns the number of skeletons in this group.

type PollMap

type PollMap map[string]AbstractPoll

PollMap is a mapping from poll name to the poll with that name.

func ConvertSkeletonMapToEmptyPolls

func ConvertSkeletonMapToEmptyPolls(skeletons PollSkeletonMap, converterFunction SkeletonConverter) (PollMap, error)

ConvertSkeletonMapToEmptyPolls does the translation from a skeleton mapping to a map of (empty) polls. It uses a SkeletonConverter function to do the actual conversion and returns an error if any of the skeletons in the map is not "valid". If converterFunction is nil DefaultSkeletonConverter is used.

ConvertSkeletonsToPolls is a function that does the same for lists.

type PollMatrix

type PollMatrix struct {
	Head []string
	Body [][]string
}

PollMatrix describes the contents of data as from a VotesCSVReader.

The implementation might be more specific to fit my needs, but I think it could be re-used by other projects and in anyway demonstrates how the library is constructed and all methods can be combined.

In general the csv file only contains names for voters and polls, these usually should be matched against an existing collection of voters / polls.

This type gives you methods to help to deal with this content: ReadMatrixFromCSV just creates the matrix from a reader.

func ReadMatrixFromCSV

func ReadMatrixFromCSV(r *VotesCSVReader) (*PollMatrix, error)

ReadMatrixFromCSV creates a matrix and reads the content from the csv reader.

func (*PollMatrix) FillPollsWithVotes

func (m *PollMatrix) FillPollsWithVotes(polls PollMap, voters VoterMap,
	parsers map[string]VoteParser, policies PolicyMap,
	allowMissingVoters, allowMissingPolls bool) (actualVoters VoterMap, actualPolls PollMap, err error)

FillPollsWithVotes does the actual parsing of votes, it creates new vote entries in the polls.

It takes the map of existing polls, these polls get filled by calling AddVote on the poll object. Each poll must have a unique parser that is used to parse a vote for the poll, see ParserCustomizer of how this should be done. The parses map must map vote name to the parser instance. Each poll (also by name) must have an EmptyVotePolicy associated with it. If the csv entry is empty (string contains only whitespace) not the parse method of the parser is called but GenerateEmptyVoteForVoter for the policy associated with the poll.

If a poll does not have parser / policy associated with it a PollingSemanticError is returned. Also all errors from AddVote are returned. The matrix is also verified with MatchEntries function and any error from this function is returned. The arguments allowMissingVoters and allowMissingPolls determine what should happen if a voter or poll is missing. As described in MatchEntries it is possible that some voters / polls do not appear in the csv. If allowMissingVoters is set to false a PollingSemanticError is returned if a voter is missing. Also if allowMissingPolls is set to false and poll is missing in the csv a PollingSemanticError is returned.

If everything is okay this method returns nil as error and the actual voters / polls that appeared in the csv. Especially if allowMissingVoters = false and allowMissingPolls = false the result should return maps that are equivalent to the input maps.

Note that if an error is returned it is possible that some of the polls got already filled with votes! In this case not all votes for a poll might be present and the whole operation should be marked as failure and probably none of the votes that already appear in some poll should be used.

func (*PollMatrix) MatchEntries

func (m *PollMatrix) MatchEntries(voters VoterMap, polls PollMap) (matchedVoters VoterMap, matchedPolls PollMap, err error)

MatchEntries tests if the matrix is well-formed.

The maps voters and polls are maps that specify the allowed names / voter names.

By we-formed we mean: The matrix must have at least one column, it is contains the voters in the first row and each row describes a poll, thus each line in the body is of the form [voter, poll1, ..., pollN]. If the matrix does not have this form a PollingSemanticError is returned.

Each voter in any of the rows must be contained in the given voters map, otherwise PollingSemanticError error is returned. Each poll (given in the head columns [1:]) must exist in the polls map, otherwise a PollingSemanticError is returned. Also there are no duplicates allowed in the voter names and column names, if a duplicate is found a DuplicateError is returned.

Note that it is allowed that a voter is missing, i.e. not contained in the CSV. The same is true for polls, not each poll must be contained. That's why this method returns two maps, they contain the voters and polls that were actually found. If you want to make sure that each voter / poll is contained in the csv just compare the lengths of the original map and the matched map.

This function will do no parsing, i.e. creating actual votes from the entries in the csv. You can use FillPollsWithVotes for that.

type PollSkeleton

type PollSkeleton struct {
	Name    string
	Options []string
}

PollSkeleton is an AbstractPollSkeleton for a poll with a list of options (strings).

func NewPollSkeleton

func NewPollSkeleton(name string) *PollSkeleton

NewPollSkeleton returns a new PollSkeleton given the name and an empty list of options.

func (*PollSkeleton) Dump

func (skel *PollSkeleton) Dump(w io.Writer) (int, error)

Dump writes the skeleton to some writer w.

It returns the number of bytes written as well as any error writing to w.

func (*PollSkeleton) GetName

func (skel *PollSkeleton) GetName() string

GetName returns the name of the poll description.

func (*PollSkeleton) SkeletonType

func (skel *PollSkeleton) SkeletonType() string

SkeletonType returns the constant GeneralPollSkeletonType.

type PollSkeletonCollection

type PollSkeletonCollection struct {
	Title  string
	Groups []*PollGroup
}

PollSkeletonCollection describes a collection of polls that are divided into groups.

func NewPollSkeletonCollection

func NewPollSkeletonCollection(title string) *PollSkeletonCollection

NewPollSkeletonCollection returns a new PollSkeletonCollection with an empty list of groups.

func (*PollSkeletonCollection) CollectSkeletons

func (coll *PollSkeletonCollection) CollectSkeletons() []AbstractPollSkeleton

CollectSkeletons returns a list of all skeletons that appear in any of the groups.

func (*PollSkeletonCollection) Dump

func (coll *PollSkeletonCollection) Dump(w io.Writer, currencyFormatter CurrencyFormatter) (int, error)

Dump writes the collection to some writer w, it needs a currencyFormatter to write currency values.

It returns the number of bytes written as well as any error writing to w.

func (*PollSkeletonCollection) HasDuplicateSkeleton

func (coll *PollSkeletonCollection) HasDuplicateSkeleton() (string, bool)

HasDuplicateSkeleton tests if the names in the collection are unique (which they should). It returns an empty string and false if no duplicates where found, otherwise it returns the name of the skeleton and true.

func (*PollSkeletonCollection) HasSkeleton

func (coll *PollSkeletonCollection) HasSkeleton() bool

HasPolls returns true if there is at least one poll skeleton in any of the groups.

func (*PollSkeletonCollection) NumGroups

func (coll *PollSkeletonCollection) NumGroups() int

PollSkeletonCollection

func (*PollSkeletonCollection) NumSkeletons

func (coll *PollSkeletonCollection) NumSkeletons() int

NumSkeletons returns the number of skeletons in all groups.

func (*PollSkeletonCollection) SkeletonsToMap

func (coll *PollSkeletonCollection) SkeletonsToMap() (PollSkeletonMap, error)

SkeletonsToMap returns a map from skeleton name to skeleton. If it finds any duplicate names an error of type DuplicateError is returned together with nil.

Otherwise it returns the map and nil.

type PollSkeletonMap

type PollSkeletonMap map[string]AbstractPollSkeleton

PollSkeletonMap is a map from a poll name to the poll skeleton with that name.

type PollTypeError

type PollTypeError struct {
	PollError
	Msg string
}

PollTypeError is an error returned if a skeleton / poll has an invalid / unsupported type, for example if a skeleton can't be converted to an empty poll.

func NewPollTypeError

func NewPollTypeError(msg string, a ...interface{}) PollTypeError

NewPollTypeError returns a new PollTypeError given a format string and the values for the placeholders (like fmt.Sprintf).

func (PollTypeError) Error

func (err PollTypeError) Error() string

type PollingSemanticError

type PollingSemanticError struct {
	PollError
	Err error
	Msg string
}

PollingSemanticError is an error returned if somewhere an option that is syntactically correct is parsed but is not valid semantically.

it can wrap another error (set to nil of not required).

func NewPollingSemanticError

func NewPollingSemanticError(err error, msg string, a ...interface{}) PollingSemanticError

NewPollingSemanticError returns a new PollingSemanticError.

The message can be formatted with placeholders (like fmt.Sprintf).

func (PollingSemanticError) Error

func (err PollingSemanticError) Error() string

func (PollingSemanticError) Unwrap

func (err PollingSemanticError) Unwrap() error

Unwrap returns the wrapped error.

type PollingSyntaxError

type PollingSyntaxError struct {
	PollError
	Err     error
	Msg     string
	LineNum int
}

PollingSyntaxError is an error returned if a syntax error is encountered.

It can wrap another error (set to nil if not required) and has an optional line number, if this number is < 0 the line number is assumed to be unknown / not existing for this error.

func NewPollingSyntaxError

func NewPollingSyntaxError(err error, msg string, a ...interface{}) PollingSyntaxError

NewPollingSyntaxError returns a new PollingSyntaxError with a line number of -1.

The message can be formatted with placeholders (like fmt.Sprintf).

func (PollingSyntaxError) Error

func (err PollingSyntaxError) Error() string

Error returns the error message, it contains (if given) the line number and error cause (the wrapped error) and the original message.

func (PollingSyntaxError) Unwrap

func (err PollingSyntaxError) Unwrap() error

Unwrap returns the wrapped error.

func (PollingSyntaxError) WithLineNum

func (err PollingSyntaxError) WithLineNum(lineNum int) PollingSyntaxError

WithLineNum returns a copy of the error but with the line number set to a new value.

type RawCentCurrencyHandler

type RawCentCurrencyHandler struct{}

RawCentCurrencyHandler implements CurrencyHandler. In th Parse method it accepts plain integers and reads them as plain integers, no currency symbol is allowed there. So the integer 10 would be translated to a currencly value "0.10" (10 cents). In its Format method it returns DefaultFormatString with . as separator.

func NewRawCentCurrencyParser

func NewRawCentCurrencyParser() RawCentCurrencyHandler

func (RawCentCurrencyHandler) Format

func (RawCentCurrencyHandler) Parse

type SchulzeMatrix

type SchulzeMatrix [][]Weight

SchulzeMatrix is a matrix used to represent the matrices d and p. It is assumed to be of dimension n × n.

func NewSchulzeMatrix

func NewSchulzeMatrix(dimension int) SchulzeMatrix

NewSchulzeMatrix returns a new matrix given the dimension, so the resulting matrix is of size n × n.

func (SchulzeMatrix) Equals

func (m SchulzeMatrix) Equals(other SchulzeMatrix) bool

Equals tests if two matrices are the same. Note that this method (like all others) assume a matrix of size n × n.

type SchulzePoll

type SchulzePoll struct {
	NumOptions int
	Votes      []*SchulzeVote
}

SchulzePoll is a poll that can be evaluated with the Schulze method, see https://en.wikipedia.org/wiki/Schulze_method for details. It implements the interface AbstractPoll.

A poll instance has the number of options in the poll (must be a positive int) and all votes for the poll.

Note that all votes must have a ranking of length NumVotes. If this is not the case the the vote will be silently dropped. You should use TruncateVoters first to identify problematic cases.

The implementation was inspired by the German Wikipedia article (https://de.wikipedia.org/wiki/Schulze-Methode) and https://github.com/mgp/schulze-method.

This type also implements VoteGenerator.

func NewSchulzePoll

func NewSchulzePoll(numOptions int, votes []*SchulzeVote) *SchulzePoll

NewSchulzePoll returns a new SchulzePoll. numOptions must be >= 0, otherwise this function panics. Note that the votes are not validated (have the correct ranking length). Use TruncateVoters to identify invalid votes.

func (*SchulzePoll) AddVote

func (poll *SchulzePoll) AddVote(vote AbstractVote) error

AddVote adds a vote to the poll, the vote must be of type *SchulzeVote.

Note that no length check is happening here! I.e. the vote can have a different number of answers than poll.NumOptions. We do this because in general it is also allowed to append any vote, it is the job of the user of this library to deal with invalid votes.

func (*SchulzePoll) GenerateVoteFromBasicAnswer

func (poll *SchulzePoll) GenerateVoteFromBasicAnswer(voter *Voter, answer BasicPollAnswer) (AbstractVote, error)

GenerateVoteFromBasicAnswer implements VoteGenerator and returns a SchulzeVote.

It will return [0, 0, ..., 1] for Aye, [1, 1, ..., 0] for No and [0, 0, ..., 0] for Abstention.

func (*SchulzePoll) PollType

func (poll *SchulzePoll) PollType() string

PollType returns the constant SchulzePollType.

func (*SchulzePoll) Tally

func (poll *SchulzePoll) Tally() *SchulzeResult

Tally computes the result of a Schulze poll.

Note that all voters with an invalid ranking (length is not poll.NumOptions) are silently discarded. Use TruncateVoters before to find such votes.

func (*SchulzePoll) TruncateVoters

func (poll *SchulzePoll) TruncateVoters() []*SchulzeVote

TruncateVoters removes all voters that have a ranking with length != poll.NumOptions.

If such culprits are found they are removed from poll.Votes. In this case a new slice of votes will be allocated containing the original vote objects. All culprits are returned (for logging or error handling).

type SchulzeRanking

type SchulzeRanking []int

SchulzeRanking is a ranking for a Schulze poll.

The ranking must have one entry for each option of the poll. The entries of the ranking describe the ranked position for the option.

Consider a poll with three alternatives ["A", "B", "C"]. Then the ranking [1, 0, 1] would represent the ranking B > A = C. That is the smaller the value the more "important" or higher ranked the option.

func NewSchulzeAbstention

func NewSchulzeAbstention(numOptions int) SchulzeRanking

NewSchulzeAbstention returns a Schulze ranking that describes an abstention, i.e. a ranking of size numOptions with all values set to 0.

func NewSchulzeAye

func NewSchulzeAye(numOptions int) SchulzeRanking

NewSchulzeAye returns a new Schulze ranking that can be thought of as a vote for "aye" / "yes", meaning for every option with the same weight, except no. Thus the ranking returned is [0, 0, ...,1].

func NewSchulzeNo

func NewSchulzeNo(numOptions int) SchulzeRanking

NewSchulzeNo returns a new Schulze ranking that can be thought of as a vote for "no", meaning against all options. In this case it is assumed that the last option stands for no. Thus the ranking returned is [1, 1, ..., 0].

func NewSchulzeRanking

func NewSchulzeRanking() SchulzeRanking

NewSchulzeRanking returns a new SchulzeRanking with a size of 0.

func (SchulzeRanking) IsAbstention

func (ranking SchulzeRanking) IsAbstention() bool

IsAbstention returns true if all options are ranked with exactly the same number.

type SchulzeResult

type SchulzeResult struct {
	D, P         SchulzeMatrix
	DNonStrict   SchulzeMatrix
	RankedGroups SchulzeWinsList
	WeightSum    Weight
}

SchulzeResult is the result returned by the Schulze method.

It stores (for testing and further investigation) the matrices d and p and of course the sorted winning groups as a SchulzeWinsList. It also contains DNonStrict, which is exactly like the matrix d but instead of counting in d[i][j] how many voters (or weights) strictly preferred i to j it counts how many voters preferred i to j or ranked them equally (ranking[i] < ranking[j] vs ranking[i] <= ranking[j]).

WeightSum is the sum of the weights of all votes in the poll.

func NewSchulzeResult

func NewSchulzeResult(d, dNonStrict, p SchulzeMatrix, rankedGroups SchulzeWinsList, votesSum Weight) *SchulzeResult

NewSchulzeResult returns a new SchulzeResult.

func (*SchulzeResult) BetterOrEqualNo

func (schulzeRes *SchulzeResult) BetterOrEqualNo() []Weight

BetterOrEqualNo returns a list of weights, each weight says how many voters (by weight) considered the option equal or better than no.

That is result[i] says: How many voters (by weight) have voted option higher or equal no. Higher means that the ranking position of i is smaller than the ranking position of no.

It simply returns the last column of the matrix d in non-strict mode, thus assumes that no is always the last option. Note that due to this the last entry in the returned list will always be 0.

func (*SchulzeResult) StrictlyBetterThanNo

func (schulzeRes *SchulzeResult) StrictlyBetterThanNo() []Weight

StrictlyBetterThanNo returns a list of weights, each weight says how many voters (by weight) considered the option strictly better than no.

That is result[i] says: How many voters (by weight) have voted option strictly higher than no. Higher means that the ranking position of i is smaller than the ranking position of no.

It simply returns the last column of the matrix d, thus assumes that no is always the last option. Note that due to this the last entry in the returned list will always be 0.

type SchulzeVote

type SchulzeVote struct {
	Voter   *Voter
	Ranking SchulzeRanking
}

SchulzeVote is a vote for a SchulzePoll. It is described by the voter and the ranking of said voter. It implements the interface AbstractVote.

func NewSchulzeVote

func NewSchulzeVote(voter *Voter, ranking SchulzeRanking) *SchulzeVote

NewSchulzeVote returns a new SchulzeVote.

func (*SchulzeVote) GetVoter

func (vote *SchulzeVote) GetVoter() *Voter

GetVoter returns the voter of the vote.

func (*SchulzeVote) VoteType

func (vote *SchulzeVote) VoteType() string

VoteType returns the constant SchulzeVoteType.

type SchulzeVoteParser

type SchulzeVoteParser struct {
	Length int
}

SchulzeVoteParser implements VoteParser and returns an instance of SchulzeVote in its ParseFromString method.

The ranking is assumed to be a comma separated list of integers, for example "1, 0, 1" (slashes are also okay, so "1/0/1" would be the same). See documentation of SchulzeRanking for more details about the ranking.

It allows to set the length that is expected from the ranking string. If the string describes a ranking not equal to length an error is returned.

It also implements ParserCustomizer.

func NewSchulzeVoteParser

func NewSchulzeVoteParser(length int) *SchulzeVoteParser

NewSchulzeVoteParser returns a new SchulzeVoteParser.

The length argument is allowed to be negative in which case the length check is disabled. Set it to a length >= 0 to enable the check or use WithLength.

func (*SchulzeVoteParser) CustomizeForPoll

func (parser *SchulzeVoteParser) CustomizeForPoll(poll AbstractPoll) (ParserCustomizer, error)

CustomizeForPoll implements ParserCustomizer and returns a new parser with Length set if a *SchulzePoll is given.

func (*SchulzeVoteParser) ParseFromString

func (parser *SchulzeVoteParser) ParseFromString(s string, voter *Voter) (AbstractVote, error)

ParseFromString implements the VoteParser interface, for details see type description.

func (*SchulzeVoteParser) WithLength

func (parser *SchulzeVoteParser) WithLength(length int) *SchulzeVoteParser

WithLength returns a shallow copy of the parser with only length set to the new value.

type SchulzeWinsList

type SchulzeWinsList [][]int

SchulzeWinsList describes the winning groups of a Schulze poll. The first list contains all options that are ranked highest, the next list all entries ranked second best and so on. Each option should appear in at least one of the lists.

type SimpleEuroHandler

type SimpleEuroHandler struct{}

SimpleEuroHandler is an implementation of CurrencyHandler (and thus CurrencyFormatter and CurrencyParser).

It returns always strings of the form "1.23 €" or "1.23" (depending on whether Currency is set to an empty string or not). The parser allows strings of the form "42€", "21.42 €", "-42€", "21,42 €" (both , and . are allowed to be used as decimal separator, no thousands separator is supported).

func (SimpleEuroHandler) Format

func (h SimpleEuroHandler) Format(value CurrencyValue) string

Format implements the CurrencyFormatter interface.

func (SimpleEuroHandler) Parse

Parse implements the CurrencyParser interface.

type SkeletonConverter

type SkeletonConverter func(skel AbstractPollSkeleton) (AbstractPoll, error)

SkeletonConverter is a function that takes a skeleton and returns an empty poll for this skeleton. If an unknown type is encountered or the skeleton is in some way invalid it should return nil an error of type PollTypeError.

An implementation is given in DefaultSkeletonConverter and a generator in NewDefaultSkeletonConverter.

func NewDefaultSkeletonConverter

func NewDefaultSkeletonConverter(convertToBasic bool) SkeletonConverter

NewDefaultSkeletonConverter is a generator function that returns a new SkeletonConverter. It does the following translations: A MoneyPollSkel gets translated to a MedianPol, it checks if the value described is >= (< 0 is not allowed). A PollSkeleton is translated to a BasicPoll or SchulzePoll. A BasicPoll is returned if the PollSkeleton has exactly two options,otherwise a SchulzePoll is created. If the number of options in the PollSkeleton is < 2 an error is returned.

If convertToBasic is false a SchulzePoll will be returned even for two options.

Note: A poll with two options is independent of the actual content of the two options, it is assumed that the first option represents Aye/Yes in some way and the second one No.

type VoteGenerator

type VoteGenerator interface {
	AbstractPoll
	GenerateVoteFromBasicAnswer(voter *Voter, answer BasicPollAnswer) (AbstractVote, error)
}

VoteGenerator is used to describe polls that can produce a poll specific vote type for a basic answer (yes, no or abstention).

It is not allowed to return a nil vote and error = nil, that is if there is no error the returned vote is not allowed to be nil.

It should return a PollTypeError if an answer is not supported (or none at all). All polls implemented at the moment also implement this interface.

type VoteParser

type VoteParser interface {
	ParseFromString(s string, voter *Voter) (AbstractVote, error)
}

VoteParser parses a vote from a string.

Returned errors should be an internal error type like PollingSyntaxError or PollingSemanticError. If the error is not nil the returned vote is not allowed to be nil.

It is recommended to also implement ParserCustomizer.

type Voter

type Voter struct {
	Name   string
	Weight Weight
}

Voter implements everyone who is allowed to participate in polls.

A voter has a name and weight. The weight specifies how much the vote of a certain voter counts (in "normal elections" this is 1).

func NewVoter

func NewVoter(name string, weight Weight) *Voter

NewVoter creates a new Voter given its name and weight.

func (*Voter) Equals

func (voter *Voter) Equals(other *Voter) bool

Equals tests if two voters are equal (have the same name and weight).

func (*Voter) Format

func (voter *Voter) Format(indent string) string

Format returns a formatted string (one that can be parsed back with the voters parsing methods).

type VoterMap

type VoterMap map[string]*Voter

VoterMap is a mapping from user name to a Voter.

func VotersToMap

func VotersToMap(voters []*Voter) (VoterMap, error)

VotersToMap returns a map from voter name to voter object. If it finds a a duplicate in the names of voters it returns nil and a DuplicateError.

type VotersParser

type VotersParser struct {
	MaxNumLines         int
	MaxNumVoters        int
	MaxLineLength       int
	MaxVotersNameLength int
	MaxVotersWeight     Weight
}

VotersParser parses voters from a file / string. See ParseVotersLine and ParseVoters for details.

Furthermore the parser can be configured to read only a certain amount of voters or validate / limit the file. This limit / validation is set via the member variables above. They all default to a value that disables all limits and checks in NewVotersParser. The default value is -1 for all int types and NoWeight for MaxVotersWeight.

This checking / limitation make it easier to already prevent entries with too many values from parsing. It also gives an easy method to disallow files that are too big from being parsed. All these validations are indicated by a returned error of type ParserValidationError.

The meaning is as follows: MaxNumLines is the number of lines that are allowed in a voters file for ParseVoters. MaxNumVoters is the number of voters that are allowed to be parsed in ParseVoters. Note that we allow comments and empty lines in the file, thus we have one variable for lines and one for voters.

MaxLineLength is the maximal number of bytes (not runes) allowed in a single line of the file. MaxVotersNameLength is the maximal number of bytes allowed in a single voters name. MaxVotersWeight is the maximal weight a voter can have, this is useful to for example avoid overflows when you have many voters.

However MaxLineLength is probably one of the most useful limits because it finds very long lines early and avoids the parsing of such lines.

Of course some combinations don't make sense, for example setting MaxLineLength=21 and MaxVotersNameLength=42 will never result in a voter name length > 21.

ComputeDefaultMaxLineLength is a small helper that may be called and sets MaxLineLength depending on MaxVotersNameLength and MaxVotersWeight.

func NewVotersParser

func NewVotersParser() *VotersParser

NewVotersParser returns a new parser with all limitations disabled.

func (*VotersParser) ComputeDefaultMaxLineLength

func (parser *VotersParser) ComputeDefaultMaxLineLength()

ComputeDefaultMaxLineLength sets MaxLineLength depending on the values of MaxVotersNameLength (if set) and MaxVotersWeight. It allows the whitespaces that are required in the description and adds a small constant to allow additional whitespaces, but not too many.

func (*VotersParser) ParseVoters

func (parser *VotersParser) ParseVoters(r io.Reader) ([]*Voter, error)

ParseVoters parses a list of voters from a reader.

Each line must contain one voter entry. Each line must be of the form as described in ParseVotersLine, in short

"* <VOTER-NAME>: <WEIGHT>".

or

"* <VOTER-NAME>"

in which case weight defaults to 1.

Empty lines and lines starting with "#" are ignored.

This method will return an internal error whenever for syntax errors / validation errors, all errors from reader are returned directly however.

The returned internals errors are either PollingSyntaxError or ParserValidationError.

func (*VotersParser) ParseVotersFromString

func (parser *VotersParser) ParseVotersFromString(s string) ([]*Voter, error)

ParseVotersFromString works like ParseVoters but reads from a string.

func (*VotersParser) ParseVotersLine

func (parser *VotersParser) ParseVotersLine(s string) (*Voter, error)

ParseVotersLine parses a voter line.

Line must be of the form "* <VOTER-NAME>: <WEIGHT>". The name can consist of arbitrary letters, weight must be a positive integer. The weight can also be omitted and defaults to 1. The returned error will be of type ParserValidationError or PollingSyntaxError.

type VotesCSVReader

type VotesCSVReader struct {
	Sep rune

	MaxNumLines         int
	MaxVotersNameLength int
	MaxPollNameLength   int
	MaxRecordLength     int
	// contains filtered or unexported fields
}

VotesCSVReader can be used to parse a CSV file of votes (see wiki for details about CSV files). It can only be used to parse the "matrix", that is the strings from the CSV file. No conversion to a vote object is done, it reads the pure strings which then need to be processed further. For an example see ReadMatrixFromCSV, but you probably want your own method for dealing with a parsed CSV file.

Furthermore the parser can be configured to read only a certain amount of lines / put restrictions on the records read. This is the same idea as in VotersParser, see there for details of when you would want to use restrictions.

The reader returned by NewVotesCSVReader sets all these validation fields to -1, meaning no restrictions.

The following restrictions can be configured: MaxNumLines is the number of lines that are allowed in a polls file (including head). Therefor it must be a number >= 1. MaxRecordLength is th maximal length in bytes (not runes) a record in a row is allowed to have. MaxVotersNameLength is the maximal length a voter name is allowed to have. MaxPollNameLengthis the maximal length a poll name is allowed to have.

func NewVotesCSVReader

func NewVotesCSVReader(r io.Reader) *VotesCSVReader

NewVotesCSVReader returns a VotesCSVReader reading from r.

func (*VotesCSVReader) ReadRecords

func (r *VotesCSVReader) ReadRecords() (head []string, lines [][]string, err error)

ReadRecords reads the records from the CSV file.

The head should always be of the form ["Voter", <poll_name1>, <poll_name2>, ...., <poll_nameN>].

The body (the lines part) should always be of the form [<voter_name>, <vote_for_poll1>, <vote_for_poll2>, ..., <vote_for_pollN>].

It returns any error reading from the source. It might also return a PollingSyntaxError if the file is not correctly formed.

type VotesCSVWriter

type VotesCSVWriter struct {
	Sep rune
	// contains filtered or unexported fields
}

VotesCSVWriter can be used to create a CSV file template for inserting polls in it. Refer to the wiki for details about CSV files.

func NewVotesCSVWriter

func NewVotesCSVWriter(w io.Writer) *VotesCSVWriter

NewVotesCSVWriter returns a new VotesCSVWriter writing to w.

func (*VotesCSVWriter) GenerateEmptyTemplate

func (w *VotesCSVWriter) GenerateEmptyTemplate(voters []*Voter, skels []AbstractPollSkeleton) error

GenerateEmptyTemplate generates an empty CSV template (contains all polls and voters, but no votes).

It returns any errors from writing to w.

type Weight

type Weight uint32

Weight is the type used to reference voter weights.

const NoWeight Weight = math.MaxUint32

NoWeight is a value used to signal that a value is not a valid Weight, for example as default argument.

func ComputeMajority

func ComputeMajority(majority *big.Rat, votesSum Weight) Weight

ComputeMajority computes the required majority given the majority as a rational. The rational majority must be a value <= 1, for example 1/2 for 50 percent or 2/3 for two thirds majority, see also the constants FiftyPercentMajority and TwoThirdsMajority.

For example consider that there are 10 votes (or sum of weights). Then ComputeMajority(1/2, 10) returns 5, meaning that > 5 (strictly greater!) votes are required. ComputeMajority(2/3, 10) would return 6, meaning that > 6 votes are required.

func ParseWeight

func ParseWeight(s string) (Weight, error)

ParseWeight parses a Weight from a string.

A PollingSyntaxError is returned if s is no valid int or is NoWeight.

func WeightMax

func WeightMax(a, b Weight) Weight

WeightMax returns the maximum of a and b.

func WeightMin

func WeightMin(a, b Weight) Weight

WeightMin returns the minimum of a and b.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL