IrvRound<TCandidate extends Comparable> constructor

IrvRound<TCandidate extends Comparable>(
  1. int roundNumber,
  2. List<RankedBallot<TCandidate>> ballots,
  3. Iterable<TCandidate> eliminatedCandidates
)

Implementation

factory IrvRound(
  int roundNumber,
  List<RankedBallot<TCandidate>> ballots,
  Iterable<TCandidate> eliminatedCandidates,
) {
  final cleanedBallots = ballots.map((b) {
    final pruned = b.rank
        .where((c) => !eliminatedCandidates.contains(c))
        .toList(growable: false);
    final winner = pruned.isEmpty ? null : pruned[0];
    return _CleanedBallot<TCandidate>(b, pruned, winner);
  });

  final candidateAllocations =
      groupBy<_CleanedBallot<TCandidate>, TCandidate>(
    cleanedBallots.where((cb) => cb.winner != null),
    (cb) => cb.winner!,
  );

  final voteGroups = groupBy<TCandidate, int>(
      candidateAllocations.keys, (c) => candidateAllocations[c]!.length);

  final placeVotes = voteGroups.keys.toList(growable: false)
    // reverse sorting -> most votes first
    ..sort((a, b) => b.compareTo(a));

  var placeNumber = 1;
  final places = placeVotes.map((vote) {
    final voteGroup = voteGroups[vote]!..sort();
    final currentPlaceNumber = placeNumber;
    placeNumber += voteGroup.length;
    return PluralityElectionPlace<TCandidate>(
        currentPlaceNumber, voteGroup, vote);
  }).toList(growable: false);

  final newlyEliminatedCandidates =
      _getEliminatedCandidates<TCandidate>(places);

  final eliminations = newlyEliminatedCandidates.map((TCandidate c) {
    final transfers = <TCandidate, List<RankedBallot<TCandidate>>>{};

    final exhausted = <RankedBallot<TCandidate>>[];

    for (var cb in cleanedBallots.where((cb) => cb.winner == c)) {
      final rb = cb.ballot;
      final pruned =
          cb.remaining.where((c) => !newlyEliminatedCandidates.contains(c));
      if (pruned.isEmpty) {
        // we're exhausted
        exhausted.add(rb);
      } else {
        // #2 gets the transfer
        final runnerUp = pruned.first;
        transfers.putIfAbsent(runnerUp, () => []).add(rb);
      }
    }

    return IrvElimination<TCandidate>(c, transfers, exhausted);
  }).toList(growable: false);

  return IrvRound<TCandidate>._internal(
    roundNumber,
    places,
    eliminations,
  );
}