@@ -3,13 +3,12 @@ mod structs;
33use tracing:: { debug, info} ;
44
55use self :: structs:: { Candidate , ListCandidateNomination , PreferenceThreshold } ;
6- pub use structs:: CandidateNominationResult ;
7-
86use super :: {
97 ApportionmentError , ApportionmentInput , CandidateVotesTrait , ListVotesTrait ,
108 fraction:: Fraction ,
11- structs:: { CandidateNominationInputType , CandidateNumber , LARGE_COUNCIL_THRESHOLD } ,
9+ structs:: { CandidateNominationInputType , CandidateNumber , LARGE_COUNCIL_THRESHOLD , ListNumber } ,
1210} ;
11+ pub use structs:: CandidateNominationResult ;
1312
1413/// Candidate nomination
1514pub ( crate ) fn candidate_nomination < ' a , T : ApportionmentInput > (
@@ -97,12 +96,16 @@ fn candidate_nomination_per_list<'a, T: ListVotesTrait>(
9796 seats : u32 ,
9897 list_votes : & ' a [ T ] ,
9998 preference_threshold : Fraction ,
100- total_seats : & [ u32 ] ,
99+ total_seats : & [ ( ListNumber , u32 ) ] ,
101100) -> Result < Vec < ListCandidateNomination < ' a , T :: Cv > > , ApportionmentError > {
102101 let mut list_candidate_nomination: Vec < ListCandidateNomination < T :: Cv > > = vec ! [ ] ;
103- for ( index, list) in list_votes. iter ( ) . enumerate ( ) {
104- let list_seats = total_seats[ index] ;
105- let candidate_votes = list. candidate_votes ( ) ;
102+ for list in list_votes {
103+ let ( list_number, list_seats) = total_seats
104+ . iter ( )
105+ . find ( |( number, _) | * number == list. number ( ) )
106+ . expect ( "Total seats exists" )
107+ . to_owned ( ) ;
108+ let candidate_votes = & list. candidate_votes ( ) ;
106109 let candidate_votes_meeting_preference_threshold =
107110 candidate_votes_meeting_preference_threshold ( preference_threshold, candidate_votes) ;
108111 let preferential_candidate_nomination = preferential_candidate_nomination :: < T :: Cv > (
@@ -142,8 +145,8 @@ fn candidate_nomination_per_list<'a, T: ListVotesTrait>(
142145 }
143146 } ;
144147
145- list_candidate_nomination. push ( ListCandidateNomination :: < T :: Cv > {
146- list_number : list . number ( ) ,
148+ list_candidate_nomination. push ( ListCandidateNomination {
149+ list_number,
147150 list_seats,
148151 preferential_candidate_nomination,
149152 other_candidate_nomination,
@@ -267,8 +270,10 @@ mod tests {
267270 structs:: ListNumber ,
268271 test_helpers:: {
269272 ApportionmentInputMock , CandidateVotesMock ,
273+ candidate_nomination_fixture_with_given_list_numbers_and_number_of_seats,
270274 candidate_nomination_fixture_with_given_number_of_seats,
271275 seat_assignment_fixture_with_given_candidate_votes,
276+ seat_assignment_fixture_with_given_list_numbers_candidate_numbers_and_votes,
272277 } ,
273278 } ;
274279
@@ -341,6 +346,124 @@ mod tests {
341346 ( chosen_candidates, not_chosen_candidates)
342347 }
343348
349+ /// Candidate nomination with non-consecutive list and candidate numbers
350+ ///
351+ /// List seats: [(1, 8), (2, 3), (4, 2), (5, 1), (7, 1)]
352+ /// List 1: Preferential candidate nominations of candidates 1, 4, 3, 5 and 12 and other candidate nominations of candidates 7, 8 and 9
353+ /// List 2: Preferential candidate nomination of candidate 2 and 6 and other candidate nomination of candidates 3
354+ /// List 3: Preferential candidate nomination of candidate 1 and 4 and no other candidate nominations
355+ /// List 4: Preferential candidate nomination of candidate 1 and no other candidate nominations
356+ /// List 5: Preferential candidate nomination of candidate 3 and no other candidate nominations
357+ #[ test]
358+ fn test_with_lt_19_seats_and_non_consecutive_list_and_candidate_numbers ( ) {
359+ let quota = Fraction :: new ( 5104 , 15 ) ;
360+ let seat_assignment_input =
361+ seat_assignment_fixture_with_given_list_numbers_candidate_numbers_and_votes (
362+ 15 ,
363+ vec ! [
364+ (
365+ 1 ,
366+ vec![
367+ ( 1 , 1069 ) ,
368+ ( 3 , 303 ) ,
369+ ( 4 , 321 ) ,
370+ ( 5 , 210 ) ,
371+ ( 7 , 36 ) ,
372+ ( 8 , 101 ) ,
373+ ( 9 , 79 ) ,
374+ ( 10 , 121 ) ,
375+ ( 11 , 150 ) ,
376+ ( 12 , 181 ) ,
377+ ] ,
378+ ) ,
379+ ( 2 , vec![ ( 2 , 452 ) , ( 3 , 39 ) , ( 4 , 81 ) , ( 6 , 274 ) , ( 7 , 131 ) ] ) ,
380+ ( 4 , vec![ ( 1 , 229 ) , ( 2 , 147 ) , ( 4 , 191 ) ] ) ,
381+ ( 5 , vec![ ( 1 , 347 ) , ( 3 , 189 ) ] ) ,
382+ ( 7 , vec![ ( 3 , 266 ) , ( 2 , 187 ) ] ) ,
383+ ] ,
384+ ) ;
385+ let input = candidate_nomination_fixture_with_given_list_numbers_and_number_of_seats (
386+ quota,
387+ & seat_assignment_input,
388+ vec ! [ ( 1 , 8 ) , ( 2 , 3 ) , ( 4 , 2 ) , ( 5 , 1 ) , ( 7 , 1 ) ] ,
389+ ) ;
390+ let result = candidate_nomination :: < ApportionmentInputMock > ( & input) . unwrap ( ) ;
391+
392+ assert_eq ! ( result. preference_threshold. percentage, 50 ) ;
393+ assert_eq ! (
394+ result. preference_threshold. number_of_votes,
395+ quota * Fraction :: new( result. preference_threshold. percentage, 100 )
396+ ) ;
397+ check_list_candidate_nomination (
398+ & result. list_candidate_nomination [ 0 ] ,
399+ & [ 1 , 4 , 3 , 5 , 12 ] ,
400+ & [ 7 , 8 , 9 ] ,
401+ & [ 1 , 4 , 3 , 5 , 12 , 7 , 8 , 9 , 10 , 11 ] ,
402+ ) ;
403+ check_list_candidate_nomination (
404+ & result. list_candidate_nomination [ 1 ] ,
405+ & [ 2 , 6 ] ,
406+ & [ 3 ] ,
407+ & [ 2 , 6 , 3 , 4 , 7 ] ,
408+ ) ;
409+ check_list_candidate_nomination (
410+ & result. list_candidate_nomination [ 2 ] ,
411+ & [ 1 , 4 ] ,
412+ & [ ] ,
413+ & [ 1 , 4 , 2 ] ,
414+ ) ;
415+ check_list_candidate_nomination ( & result. list_candidate_nomination [ 3 ] , & [ 1 ] , & [ ] , & [ ] ) ;
416+ check_list_candidate_nomination ( & result. list_candidate_nomination [ 4 ] , & [ 3 ] , & [ ] , & [ ] ) ;
417+
418+ let lists = input. list_votes ;
419+ check_chosen_candidates (
420+ & result. chosen_candidates ,
421+ & lists[ 0 ] . number ,
422+ & [
423+ & lists[ 0 ] . candidate_votes [ ..7 ] ,
424+ & lists[ 0 ] . candidate_votes [ 10 ..] ,
425+ ]
426+ . concat ( ) ,
427+ & lists[ 0 ] . candidate_votes [ 8 ..9 ] ,
428+ ) ;
429+ check_chosen_candidates (
430+ & result. chosen_candidates ,
431+ & lists[ 1 ] . number ,
432+ & [
433+ & lists[ 1 ] . candidate_votes [ ..2 ] ,
434+ & lists[ 1 ] . candidate_votes [ 3 ..4 ] ,
435+ ]
436+ . concat ( ) ,
437+ & [
438+ & lists[ 1 ] . candidate_votes [ 2 ..3 ] ,
439+ & lists[ 1 ] . candidate_votes [ 4 ..] ,
440+ ]
441+ . concat ( ) ,
442+ ) ;
443+ check_chosen_candidates (
444+ & result. chosen_candidates ,
445+ & lists[ 2 ] . number ,
446+ & [
447+ & lists[ 2 ] . candidate_votes [ ..1 ] ,
448+ & lists[ 2 ] . candidate_votes [ 2 ..] ,
449+ ]
450+ . concat ( ) ,
451+ & lists[ 2 ] . candidate_votes [ 1 ..2 ] ,
452+ ) ;
453+ check_chosen_candidates (
454+ & result. chosen_candidates ,
455+ & lists[ 3 ] . number ,
456+ & lists[ 3 ] . candidate_votes [ ..1 ] ,
457+ & lists[ 3 ] . candidate_votes [ 2 ..] ,
458+ ) ;
459+ check_chosen_candidates (
460+ & result. chosen_candidates ,
461+ & lists[ 4 ] . number ,
462+ & lists[ 4 ] . candidate_votes [ ..1 ] ,
463+ & lists[ 4 ] . candidate_votes [ 2 ..] ,
464+ ) ;
465+ }
466+
344467 /// Candidate nomination with ranking change due to preferential candidate nomination
345468 ///
346469 /// Actual case from GR2022
0 commit comments