diff --git a/backend/apportionment/src/candidate_nomination/mod.rs b/backend/apportionment/src/candidate_nomination/mod.rs index f85f62990..13e1192c8 100644 --- a/backend/apportionment/src/candidate_nomination/mod.rs +++ b/backend/apportionment/src/candidate_nomination/mod.rs @@ -3,13 +3,13 @@ mod structs; use tracing::{debug, info}; use self::structs::{Candidate, ListCandidateNomination, PreferenceThreshold}; -pub use structs::CandidateNominationResult; - use super::{ ApportionmentError, ApportionmentInput, CandidateVotesTrait, ListVotesTrait, fraction::Fraction, structs::{CandidateNominationInputType, CandidateNumber, LARGE_COUNCIL_THRESHOLD}, }; +use crate::structs::ListNumber; +pub use structs::CandidateNominationResult; /// Candidate nomination pub(crate) fn candidate_nomination<'a, T: ApportionmentInput>( @@ -97,12 +97,16 @@ fn candidate_nomination_per_list<'a, T: ListVotesTrait>( seats: u32, list_votes: &'a [T], preference_threshold: Fraction, - total_seats: &[u32], + total_seats: &[(ListNumber, u32)], ) -> Result>, ApportionmentError> { let mut list_candidate_nomination: Vec> = vec![]; - for (index, list) in list_votes.iter().enumerate() { - let list_seats = total_seats[index]; - let candidate_votes = list.candidate_votes(); + for list in list_votes { + let (list_number, list_seats) = total_seats + .iter() + .find(|(number, _)| *number == list.number()) + .expect("Total seats exists") + .to_owned(); + let candidate_votes = &list.candidate_votes(); let candidate_votes_meeting_preference_threshold = candidate_votes_meeting_preference_threshold(preference_threshold, candidate_votes); let preferential_candidate_nomination = preferential_candidate_nomination::( @@ -142,8 +146,8 @@ fn candidate_nomination_per_list<'a, T: ListVotesTrait>( } }; - list_candidate_nomination.push(ListCandidateNomination:: { - list_number: list.number(), + list_candidate_nomination.push(ListCandidateNomination { + list_number, list_seats, preferential_candidate_nomination, other_candidate_nomination, @@ -267,8 +271,11 @@ mod tests { structs::ListNumber, test_helpers::{ ApportionmentInputMock, CandidateVotesMock, + candidate_nomination_fixture_with_given_list_numbers_and_number_of_seats, candidate_nomination_fixture_with_given_number_of_seats, + seat_assignment_fixture_with_given_candidate_numbers_and_votes, seat_assignment_fixture_with_given_candidate_votes, + seat_assignment_fixture_with_given_list_numbers_and_candidate_votes, }, }; @@ -353,22 +360,29 @@ mod tests { #[test] fn test_with_lt_19_seats_and_preferential_candidate_nomination_and_updated_candidate_ranking() { let quota = Fraction::new(5104, 15); - let seat_assignment_input = seat_assignment_fixture_with_given_candidate_votes( - 15, - vec![ - vec![1069, 303, 321, 210, 36, 101, 79, 121, 150, 149, 15, 17], + let seat_assignment_input = + seat_assignment_fixture_with_given_list_numbers_and_candidate_votes( + 15, vec![ - 452, 39, 81, 76, 35, 109, 29, 25, 17, 6, 18, 9, 25, 30, 5, 18, 3, + ( + 1, + vec![1069, 303, 321, 210, 36, 101, 79, 121, 150, 149, 15, 17], + ), + ( + 2, + vec![ + 452, 39, 81, 76, 35, 109, 29, 25, 17, 6, 18, 9, 25, 30, 5, 18, 3, + ], + ), + (4, vec![229, 63, 65, 9, 10, 58, 29, 50, 6, 11, 37]), + (5, vec![347, 33, 14, 82, 30, 30]), + (7, vec![266, 36, 39, 36, 38, 38]), ], - vec![229, 63, 65, 9, 10, 58, 29, 50, 6, 11, 37], - vec![347, 33, 14, 82, 30, 30], - vec![266, 36, 39, 36, 38, 38], - ], - ); - let input = candidate_nomination_fixture_with_given_number_of_seats( + ); + let input = candidate_nomination_fixture_with_given_list_numbers_and_number_of_seats( quota, &seat_assignment_input, - vec![8, 3, 2, 1, 1], + vec![(1, 8), (2, 3), (4, 2), (5, 1), (7, 1)], ); let result = candidate_nomination::(&input).unwrap(); @@ -432,14 +446,14 @@ mod tests { #[test] fn test_with_lt_19_seats_and_no_preferential_candidate_nomination() { let quota = Fraction::new(105, 5); - let seat_assignment_input = seat_assignment_fixture_with_given_candidate_votes( + let seat_assignment_input = seat_assignment_fixture_with_given_candidate_numbers_and_votes( 5, vec![ - vec![5, 4, 4, 4, 4], - vec![4, 5, 4, 4, 4], - vec![4, 4, 5, 4, 4], - vec![4, 4, 4, 5, 4], - vec![4, 4, 4, 4, 5], + vec![(1, 5), (3, 4), (4, 4), (6, 4), (7, 4)], + vec![(2, 4), (3, 5), (5, 4), (6, 4), (8, 4)], + vec![(1, 4), (2, 4), (3, 5), (4, 4), (6, 4)], + vec![(2, 4), (3, 4), (4, 4), (5, 5), (7, 4)], + vec![(1, 4), (2, 4), (3, 4), (4, 4), (5, 5)], ], ); let input = candidate_nomination_fixture_with_given_number_of_seats( @@ -455,9 +469,9 @@ mod tests { quota * Fraction::new(result.preference_threshold.percentage, 100) ); check_list_candidate_nomination(&result.list_candidate_nomination[0], &[], &[1], &[]); - check_list_candidate_nomination(&result.list_candidate_nomination[1], &[], &[1], &[]); + check_list_candidate_nomination(&result.list_candidate_nomination[1], &[], &[2], &[]); check_list_candidate_nomination(&result.list_candidate_nomination[2], &[], &[1], &[]); - check_list_candidate_nomination(&result.list_candidate_nomination[3], &[], &[1], &[]); + check_list_candidate_nomination(&result.list_candidate_nomination[3], &[], &[2], &[]); check_list_candidate_nomination(&result.list_candidate_nomination[4], &[], &[1], &[]); let lists = input.list_votes; diff --git a/backend/apportionment/src/seat_assignment/mod.rs b/backend/apportionment/src/seat_assignment/mod.rs index c10a54def..394983bf4 100644 --- a/backend/apportionment/src/seat_assignment/mod.rs +++ b/backend/apportionment/src/seat_assignment/mod.rs @@ -111,11 +111,13 @@ pub(crate) fn seat_assignment( }) } -pub fn get_total_seats_from_apportionment_result(result: &SeatAssignmentResult) -> Vec { +pub fn get_total_seats_per_list_number_from_apportionment_result( + result: &SeatAssignmentResult, +) -> Vec<(ListNumber, u32)> { result .final_standing .iter() - .map(|p| p.total_seats) + .map(|p| (p.list_number, p.total_seats)) .collect::>() } @@ -279,9 +281,14 @@ fn reassign_residual_seats_for_exhausted_lists( #[cfg(test)] pub(crate) mod tests { use crate::{ + SeatAssignmentResult, fraction::Fraction, - seat_assignment::{ListStanding, SeatChange, list_numbers}, + seat_assignment::{ + ListStanding, SeatChange, get_total_seats_per_list_number_from_apportionment_result, + list_numbers, + }, structs::ListNumber, + test_helpers::convert_total_seats_per_u32_list_number_to_total_seats_per_list_number, }; use test_log::test; @@ -292,6 +299,22 @@ pub(crate) mod tests { } } + fn check_total_seats_per_list( + result: &SeatAssignmentResult, + expected_total_seats_per_list: Vec<(u32, u32)>, + ) { + let total_seats_per_list_number = + get_total_seats_per_list_number_from_apportionment_result(result); + let expected_total_seats_per_list_number = + convert_total_seats_per_u32_list_number_to_total_seats_per_list_number( + expected_total_seats_per_list, + ); + assert_eq!( + expected_total_seats_per_list_number, + total_seats_per_list_number + ); + } + #[test] fn test_list_numbers() { let standing = [ @@ -348,10 +371,9 @@ pub(crate) mod tests { mod lt_19_seats { use test_log::test; + use super::check_total_seats_per_list; use crate::{ - ApportionmentError, - seat_assignment::{get_total_seats_from_apportionment_result, seat_assignment}, - structs::ListNumber, + ApportionmentError, seat_assignment::seat_assignment, structs::ListNumber, test_helpers::seat_assignment_fixture_with_default_50_candidates, }; @@ -368,8 +390,10 @@ pub(crate) mod tests { assert_eq!(result.full_seats, 15); assert_eq!(result.residual_seats, 0); assert_eq!(result.steps.len(), 0); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![6, 2, 2, 2, 1, 1, 1]); + check_total_seats_per_list( + &result, + vec![(1, 6), (2, 2), (3, 2), (4, 2), (5, 1), (6, 1), (7, 1)], + ); } /// Apportionment with residual seats assigned with largest remainders method @@ -396,8 +420,19 @@ pub(crate) mod tests { result.steps[1].change.list_number_assigned(), ListNumber::from(7) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![7, 2, 2, 1, 1, 1, 1, 0]); + check_total_seats_per_list( + &result, + vec![ + (1, 7), + (2, 2), + (3, 2), + (4, 1), + (5, 1), + (6, 1), + (7, 1), + (8, 0), + ], + ); } /// Apportionment with residual seats assigned with largest remainders and highest averages methods @@ -440,8 +475,19 @@ pub(crate) mod tests { result.steps[4].change.list_number_assigned(), ListNumber::from(4) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![12, 1, 1, 1, 0, 0, 0, 0]); + check_total_seats_per_list( + &result, + vec![ + (1, 12), + (2, 1), + (3, 1), + (4, 1), + (5, 0), + (6, 0), + (7, 0), + (8, 0), + ], + ); } /// Apportionment with residual seats assigned with largest remainders method @@ -473,8 +519,19 @@ pub(crate) mod tests { result.steps[2].change.list_number_assigned(), ListNumber::from(3) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![7, 4, 4, 0, 0, 0, 0, 0]); + check_total_seats_per_list( + &result, + vec![ + (1, 7), + (2, 4), + (3, 4), + (4, 0), + (5, 0), + (6, 0), + (7, 0), + (8, 0), + ], + ); } /// Apportionment with residual seats assigned with highest averages method @@ -507,8 +564,21 @@ pub(crate) mod tests { result.steps[2].change.list_number_assigned(), ListNumber::from(3) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![1, 1, 1, 0, 0, 0, 0, 0, 0, 0]); + check_total_seats_per_list( + &result, + vec![ + (1, 1), + (2, 1), + (3, 1), + (4, 0), + (5, 0), + (6, 0), + (7, 0), + (8, 0), + (9, 0), + (10, 0), + ], + ); } /// Apportionment with residual seats assigned with largest remainders and highest averages methods @@ -539,8 +609,10 @@ pub(crate) mod tests { result.steps[2].change.list_number_assigned(), ListNumber::from(5) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, [0, 0, 0, 0, 1, 9]); + check_total_seats_per_list( + &result, + vec![(1, 0), (2, 0), (3, 0), (4, 0), (5, 1), (6, 9)], + ); } /// Apportionment with 0 votes on candidates @@ -597,8 +669,7 @@ pub(crate) mod tests { result.steps[3].change.list_number_assigned(), ListNumber::from(1) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![8, 3, 2, 1, 1]); + check_total_seats_per_list(&result, vec![(1, 8), (2, 3), (3, 2), (4, 1), (5, 1)]); } mod drawing_of_lots { @@ -665,9 +736,12 @@ pub(crate) mod tests { use crate::{ ApportionmentError, - seat_assignment::{get_total_seats_from_apportionment_result, seat_assignment}, + seat_assignment::{seat_assignment, tests::check_total_seats_per_list}, structs::ListNumber, - test_helpers::seat_assignment_fixture_with_given_candidate_votes, + test_helpers::{ + seat_assignment_fixture_with_given_candidate_votes, + seat_assignment_fixture_with_given_list_numbers_and_candidate_votes, + }, }; /// Apportionment with no residual seats @@ -680,14 +754,14 @@ pub(crate) mod tests { /// 2 - largest remainder: seat assigned to list 5 #[test] fn test_with_list_exhaustion_during_full_seats_assignment() { - let input = seat_assignment_fixture_with_given_candidate_votes( + let input = seat_assignment_fixture_with_given_list_numbers_and_candidate_votes( 15, vec![ - vec![500, 500, 500, 500], - vec![400, 400, 400, 400], - vec![400, 400, 400], - vec![400, 400], - vec![200, 200], + (1, vec![500, 500, 500, 500]), + (2, vec![400, 400, 400, 400]), + (4, vec![400, 400, 400]), + (5, vec![400, 400]), + (7, vec![200, 200]), ], ); let result = seat_assignment(&input).unwrap(); @@ -705,10 +779,9 @@ pub(crate) mod tests { ); assert_eq!( result.steps[1].change.list_number_assigned(), - ListNumber::from(5) + ListNumber::from(7) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![4, 4, 3, 2, 2]); + check_total_seats_per_list(&result, vec![(1, 4), (2, 4), (4, 3), (5, 2), (7, 2)]); } /// Apportionment with residual seats assigned with largest remainders method @@ -785,8 +858,22 @@ pub(crate) mod tests { result.steps[7].change.list_number_assigned(), ListNumber::from(6) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![3, 1, 2, 2, 1, 2, 1, 0, 3, 1, 1]); + check_total_seats_per_list( + &result, + vec![ + (1, 3), + (2, 1), + (3, 2), + (4, 2), + (5, 1), + (6, 2), + (7, 1), + (8, 0), + (9, 3), + (10, 1), + (11, 1), + ], + ); } /// Apportionment with residual seats assigned with largest remainders and highest averages methods @@ -843,8 +930,10 @@ pub(crate) mod tests { result.steps[4].change.list_number_assigned(), ListNumber::from(4) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, [0, 0, 0, 1, 1, 8]); + check_total_seats_per_list( + &result, + vec![(1, 0), (2, 0), (3, 0), (4, 1), (5, 1), (6, 8)], + ); } /// Apportionment with residual seats assigned with largest remainders and highest averages methods @@ -933,8 +1022,7 @@ pub(crate) mod tests { result.steps[8].change.list_number_assigned(), ListNumber::from(2) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, [2, 2, 2]); + check_total_seats_per_list(&result, vec![(1, 2), (2, 2), (3, 2)]); } /// Apportionment with residual seats assigned with largest remainders and highest averages methods @@ -1023,8 +1111,7 @@ pub(crate) mod tests { result.steps[8].change.list_number_assigned(), ListNumber::from(2) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, [2, 2, 2]); + check_total_seats_per_list(&result, vec![(1, 2), (2, 2), (3, 2)]); } /// Apportionment with residual seats assigned with largest remainders method @@ -1093,8 +1180,7 @@ pub(crate) mod tests { result.steps[5].change.list_number_assigned(), ListNumber::from(4) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![7, 3, 2, 2, 1]); + check_total_seats_per_list(&result, vec![(1, 7), (2, 3), (3, 2), (4, 2), (5, 1)]); } /// Apportionment with residual seats assigned with largest remainders and highest averages methods @@ -1165,8 +1251,7 @@ pub(crate) mod tests { result.steps[5].change.list_number_assigned(), ListNumber::from(3) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, [4, 3, 1]); + check_total_seats_per_list(&result, vec![(1, 4), (2, 3), (3, 1)]); } /// Apportionment with residual seats assigned with largest remainders and highest averages methods @@ -1253,8 +1338,7 @@ pub(crate) mod tests { result.steps[7].change.list_number_assigned(), ListNumber::from(2) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, [2, 1, 5]); + check_total_seats_per_list(&result, vec![(1, 2), (2, 1), (3, 5)]); } /// Apportionment with no residual seats @@ -1314,10 +1398,9 @@ pub(crate) mod tests { mod gte_19_seats { use test_log::test; + use super::check_total_seats_per_list; use crate::{ - ApportionmentError, - seat_assignment::{get_total_seats_from_apportionment_result, seat_assignment}, - structs::ListNumber, + ApportionmentError, seat_assignment::seat_assignment, structs::ListNumber, test_helpers::seat_assignment_fixture_with_default_50_candidates, }; @@ -1334,8 +1417,10 @@ pub(crate) mod tests { assert_eq!(result.full_seats, 25); assert_eq!(result.residual_seats, 0); assert_eq!(result.steps.len(), 0); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![12, 6, 2, 2, 2, 1]); + check_total_seats_per_list( + &result, + vec![(1, 12), (2, 6), (3, 2), (4, 2), (5, 2), (6, 1)], + ); } /// Apportionment with residual seats assigned with highest averages method @@ -1369,8 +1454,7 @@ pub(crate) mod tests { result.steps[3].change.list_number_assigned(), ListNumber::from(4) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![12, 6, 1, 2, 2]); + check_total_seats_per_list(&result, vec![(1, 12), (2, 6), (3, 1), (4, 2), (5, 2)]); } /// Apportionment with residual seats assigned with highest averages method @@ -1421,8 +1505,20 @@ pub(crate) mod tests { result.steps[6].change.list_number_assigned(), ListNumber::from(1) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![15, 1, 1, 1, 1, 0, 0, 0, 0]); + check_total_seats_per_list( + &result, + vec![ + (1, 15), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 0), + (7, 0), + (8, 0), + (9, 0), + ], + ); } /// Apportionment with 0 votes on candidates @@ -1493,8 +1589,19 @@ pub(crate) mod tests { result.steps[6].change.list_number_assigned(), ListNumber::from(1) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![13, 2, 2, 2, 2, 2, 1, 0]); + check_total_seats_per_list( + &result, + vec![ + (1, 13), + (2, 2), + (3, 2), + (4, 2), + (5, 2), + (6, 2), + (7, 1), + (8, 0), + ], + ); } mod drawing_of_lots { @@ -1547,7 +1654,7 @@ pub(crate) mod tests { use crate::{ ApportionmentError, - seat_assignment::{get_total_seats_from_apportionment_result, seat_assignment}, + seat_assignment::{seat_assignment, tests::check_total_seats_per_list}, structs::ListNumber, test_helpers::seat_assignment_fixture_with_given_candidate_votes, }; @@ -1588,8 +1695,7 @@ pub(crate) mod tests { result.steps[1].change.list_number_assigned(), ListNumber::from(5) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![4, 5, 4, 4, 3]); + check_total_seats_per_list(&result, vec![(1, 4), (2, 5), (3, 4), (4, 4), (5, 3)]); } /// Apportionment with residual seats assigned with highest averages method @@ -1633,8 +1739,7 @@ pub(crate) mod tests { result.steps[2].change.list_number_assigned(), ListNumber::from(1) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![5, 4, 4, 4, 2]); + check_total_seats_per_list(&result, vec![(1, 5), (2, 4), (3, 4), (4, 4), (5, 2)]); } /// Apportionment with residual seats assigned with highest averages method @@ -1720,8 +1825,19 @@ pub(crate) mod tests { result.steps[8].change.list_number_assigned(), ListNumber::from(7) ); - let total_seats = get_total_seats_from_apportionment_result(&result); - assert_eq!(total_seats, vec![12, 2, 2, 2, 2, 2, 2, 0]); + check_total_seats_per_list( + &result, + vec![ + (1, 12), + (2, 2), + (3, 2), + (4, 2), + (5, 2), + (6, 2), + (7, 2), + (8, 0), + ], + ); } /// Apportionment with no residual seats diff --git a/backend/apportionment/src/seat_assignment/structs.rs b/backend/apportionment/src/seat_assignment/structs.rs index ced0cc46f..024ee9fd9 100644 --- a/backend/apportionment/src/seat_assignment/structs.rs +++ b/backend/apportionment/src/seat_assignment/structs.rs @@ -20,7 +20,7 @@ pub struct SeatAssignmentResult { #[derive(Debug, PartialEq)] pub struct ListSeatAssignment { /// List number for which this assignment applies - list_number: ListNumber, + pub list_number: ListNumber, /// The number of votes cast for this group votes_cast: u64, /// The remainder votes that were not used to get full seats assigned to this list diff --git a/backend/apportionment/src/structs.rs b/backend/apportionment/src/structs.rs index 48a8f11e6..2b1982e64 100644 --- a/backend/apportionment/src/structs.rs +++ b/backend/apportionment/src/structs.rs @@ -2,7 +2,9 @@ use super::{ candidate_nomination::CandidateNominationResult, fraction::Fraction, int_newtype_macro::int_newtype, - seat_assignment::{SeatAssignmentResult, get_total_seats_from_apportionment_result}, + seat_assignment::{ + SeatAssignmentResult, get_total_seats_per_list_number_from_apportionment_result, + }, }; use std::fmt::Debug; @@ -51,8 +53,7 @@ pub(crate) struct CandidateNominationInput<'a, L: ListVotesTrait> { pub number_of_seats: u32, pub list_votes: &'a [L], pub quota: Fraction, - // TODO: #2785 Should be mapped by ListNumber, not index - pub total_seats_per_list: Vec, + pub total_seats_per_list: Vec<(ListNumber, u32)>, } pub(crate) type CandidateNominationInputType<'a, T> = @@ -66,6 +67,8 @@ pub(crate) fn as_candidate_nomination_input<'a, T: ApportionmentInput>( number_of_seats: input.number_of_seats(), list_votes: input.list_votes(), quota: seat_assignment.quota, - total_seats_per_list: get_total_seats_from_apportionment_result(seat_assignment), + total_seats_per_list: get_total_seats_per_list_number_from_apportionment_result( + seat_assignment, + ), } } diff --git a/backend/apportionment/src/test_helpers.rs b/backend/apportionment/src/test_helpers.rs index 14fc73b8a..27b975661 100644 --- a/backend/apportionment/src/test_helpers.rs +++ b/backend/apportionment/src/test_helpers.rs @@ -84,6 +84,18 @@ impl ListVotesMock { } } +#[cfg(test)] +pub fn convert_total_seats_per_u32_list_number_to_total_seats_per_list_number( + total_seats_per_list_number: Vec<(u32, u32)>, +) -> Vec<(ListNumber, u32)> { + total_seats_per_list_number + .iter() + .map(|(number, total_seats)| (ListNumber::from(*number), *total_seats)) + .collect() +} + +/// Create a CandidateNominationInput with consecutive list numbers and +/// given quota, seat assignment input and total seats per list. pub fn candidate_nomination_fixture_with_given_number_of_seats( quota: Fraction, seat_assignment_input: &ApportionmentInputMock, @@ -93,11 +105,36 @@ pub fn candidate_nomination_fixture_with_given_number_of_seats( number_of_seats: seat_assignment_input.number_of_seats, list_votes: &seat_assignment_input.list_votes, quota, - total_seats_per_list, + total_seats_per_list: total_seats_per_list + .iter() + .enumerate() + .map(|(list_index, total_seats)| { + (ListNumber::try_from(list_index + 1).unwrap(), *total_seats) + }) + .collect(), + } +} + +/// Create a CandidateNominationInput with given quota, seat assignment input +/// and total seats per list number. +pub fn candidate_nomination_fixture_with_given_list_numbers_and_number_of_seats( + quota: Fraction, + seat_assignment_input: &ApportionmentInputMock, + total_seats_per_list_number: Vec<(u32, u32)>, +) -> CandidateNominationInput<'_, ListVotesMock> { + CandidateNominationInput { + number_of_seats: seat_assignment_input.number_of_seats, + list_votes: &seat_assignment_input.list_votes, + quota, + total_seats_per_list: + convert_total_seats_per_u32_list_number_to_total_seats_per_list_number( + total_seats_per_list_number, + ), } } -/// Create a ApportionmentInputMock with given total votes and list votes. +/// Create a ApportionmentInputMock with consecutive list numbers +/// and given list votes and number of seats. pub fn seat_assignment_fixture_with_default_50_candidates( number_of_seats: u32, list_vote_counts: Vec, @@ -123,9 +160,34 @@ pub fn seat_assignment_fixture_with_default_50_candidates( } } -/// Create a ApportionmentInputMock with given votes per list. -/// The number of lists is the length of the `list_votes` vector. -/// The number of candidates in each list is by default 50. +/// Create a ApportionmentInputMock with given number of seats and list numbers and votes. +pub fn seat_assignment_fixture_with_given_list_numbers_and_candidate_votes( + number_of_seats: u32, + list_candidate_votes: Vec<(u32, Vec)>, +) -> ApportionmentInputMock { + let total_votes = list_candidate_votes + .iter() + .map(|(_, candidate_votes)| candidate_votes.iter().sum::()) + .sum(); + + let mut list_votes: Vec = vec![]; + for (list_number, list_candidate_votes) in list_candidate_votes.iter() { + list_votes.push(ListVotesMock::from_test_data_auto( + ListNumber::from(*list_number), + list_candidate_votes, + )) + } + + ApportionmentInputMock { + number_of_seats, + total_votes, + list_votes, + } +} + +/// Create a ApportionmentInputMock with consecutive list numbers +/// and given candidate votes per list and number of seats. +/// The number of lists is the length of the `candidate_votes` vector. pub fn seat_assignment_fixture_with_given_candidate_votes( number_of_seats: u32, candidate_votes: Vec>, @@ -145,3 +207,38 @@ pub fn seat_assignment_fixture_with_given_candidate_votes( list_votes, } } + +/// Create a ApportionmentInputMock with consecutive list numbers and +/// given candidate numbers and votes per list and number of seats. +/// The number of lists is the length of the `candidate_votes` vector. +pub fn seat_assignment_fixture_with_given_candidate_numbers_and_votes( + number_of_seats: u32, + candidate_votes: Vec>, +) -> ApportionmentInputMock { + let mut total_votes = 0; + let mut list_votes: Vec = vec![]; + for (list_index, list_candidate_votes) in candidate_votes.iter().enumerate() { + let list_total_votes = list_candidate_votes + .iter() + .map(|(_, candidate_votes)| candidate_votes) + .sum(); + total_votes += list_total_votes; + list_votes.push(ListVotesMock { + number: ListNumber::try_from(list_index + 1).unwrap(), + total_votes: list_total_votes, + candidate_votes: list_candidate_votes + .iter() + .map(|(number, candidate_votes)| CandidateVotesMock { + number: CandidateNumber::from(*number), + votes: *candidate_votes, + }) + .collect(), + }) + } + + ApportionmentInputMock { + number_of_seats, + total_votes, + list_votes, + } +}