Distribuzione dei seggi parlamentari


13

introduzione

In un'elezione generale, si vorrebbe calcolare un prezzo costante per sede parlamentare. Ciò significa che per la N >= 0distribuzione dei seggi e un elenco nsdi voti per partito, vorremmo trovare un numero dtale

sum(floor(n/d) for n in ns) == N 

Per rendere le cose interessanti (e più simili al mondo reale), aggiungiamo altri due fatti:

  1. Due partiti possono riunirsi in una "coalizione", in modo che i posti siano assegnati alla "coalizione" dalla somma dei voti per tutte le parti in essa contenute. Quindi i posti ottenuti dalla "coalizione" sono divisi tra le parti in modo simile (trova divisore, ecc.)

  2. Un partito che non ha superato una determinata percentuale di voti (ad es. 3,25%) ottiene automaticamente 0 seggi e i suoi voti non contano per una "coalizione".

Sfida

Ti viene dato:

  1. Un elenco di elenchi, ciascuno degli elenchi nidificati contiene numeri interi (numero di voti) ed è della lunghezza 1 per un singolo partito o della lunghezza 2 per una "coalizione".
  2. Percentuale minima di voti (alias "bar" per "barrage") per ottenere seggi, in percentuale (quindi il 3,25% è dato come 0,0325)
  3. Numero totale di seggi da distribuire tra tutte le parti (numero intero)

Devi stampare la stessa struttura di elenco nidificata, con il numero di voti sostituito con seggi parlamentari.

Il vincitore è il codice con il minor numero di byte.

Valigie angolari:

  • Potrebbero esserci (e di solito ci saranno) più di un possibile divisore. Dal momento che non è nell'output, non importa davvero.
  • Immagina N=10e ns = [[1]], quindi, il divisore può essere 0.1 (non un numero intero)
  • Alcuni casi non possono essere risolti, ad esempio ns=[[30],[30],[100]], bar=0, N=20. C'è un limite in d=7.5cui la somma dei valori minimi salta da 19 a 21. Non ci si aspetta che tu risolva questi casi. (grazie al membro della comunità Arnauld per aver segnalato questo caso)

Esempio di input e output

Un esempio di Python3 molto non ottimizzato:

from math import floor

def main(_l, bar, N):
    # sum all votes to calculate bar in votes
    votes = sum(sum(_) for _ in _l)

    # nullify all parties that didn't pass the bar
    _l = [[__ if __ >= bar * votes else 0 for __ in _] for _ in _l]

    # find divisor for all parliament seats
    divisor = find_divisor([sum(_) for _ in _l], N)

    # find divisor for each 'coalition'
    divisors = [find_divisor(_, floor(sum(_)/divisor)) for _ in _l]

    # return final results
    return [[floor(___/_) for ___ in __] for _, __ in zip(divisors, _l)]

def find_divisor(_l, N, _min=0, _max=1):
    s = sum(floor(_ / _max) for _ in _l)
    if s == N:
            return _max
    elif s < N:
            return find_divisor(_l, N, _min, (_max + _min) / 2)
    else:
            return find_divisor(_l, N, _max, _max * 2)

print(main(l, bar, N))

Esempio di input:

l = [[190970, 156473], 
    [138598, 173004], 
    [143666, 193442], 
    [1140370, 159468], 
    [258275, 249049], 
    [624, 819], 
    [1125881], 
    [152756], 
    [118031], 
    [74701]]
bar = 0.0325
N = 120

E il suo output:

[[6, 4], [0, 5], [4, 6], [35, 5], [8, 8], [0, 0], [35], [4], [0], [0]]

Alcuni altri esempi di output:

Se bar=0.1otteniamo un interessante scontro tra due parti in quanto nessuna delle parti minori viene conteggiata in:

[[0, 0], [0, 0], [0, 0], [60, 0], [0, 0], [0, 0], [60], [0], [0], [0]]

E se N=0(caso d'angolo), ovviamente, nessuno ottiene nulla:

[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0], [0], [0], [0]]

5
Benvenuti in PPCG!
Arnauld,

Benvenuti in CGCC (precedentemente noto come PPCG)! Mi sono preso la libertà di aggiungere l'evidenziazione di Python in modo che il tuo codice diventi più leggibile e ho inserito l'input sotto il codice in modo che l'input-output sia più ravvicinato. Ho anche aggiunto due tag rilevanti. Bella prima sfida però, quindi +1 da parte mia! PS: potresti usare il Sandbox delle sfide proposte per ottenere un feedback sulle sfide prima di pubblicarle in main, anche se in questo caso penso che la sfida sia chiara. Forse aggiungere alcuni casi di test aggiuntivi? Buon soggiorno :)
Kevin Cruijssen,

Certo, @KevinCruijssen, ho aggiunto altri due casi. Per quanto riguarda l'output esistente, confido che sia vero in quanto sono i risultati esatti di una recente elezione :)
scf

@Arnauld Per curiosità, quale dovrebbe essere l'output previsto per quel caso di test?
Kevin Cruijssen

1
Ho già aggiunto un proiettile nel caso d'angolo, penso che questo sia un caso irrisolvibile poiché nel limite d=7.5si ottiene un salto da 19 a 21 posti.
scf

Risposte:


2

05AB1E , 42 39 byte

ÐOOI*@*DO¸I¸¸2Fнζε`sDO/*Щ±/D{®1%Oòè‹+ï

Provalo online!

05AB1E non ha una buona ricorsione, quindi implementare una ricerca binaria come nel codice di riferimento sarebbe doloroso. Per fortuna, non abbiamo affatto bisogno di trovare il divisore!

Facciamo un semplice esempio: [600, 379, 12, 9] voti, 100 seggi, nessuna coalizione, nessun bar. Innanzitutto, calcoliamo quanti posti frazionari ottengono ciascuna parte, definendo i posti frazionari comeparty_votes * seats / sum_of_votes . Nel nostro esempio, ciò produce [60, 37,9, 1,2, 0,9].

La cosa interessante è che se una festa ottiene fposti frazionari, otterrà anche unoint(f) oint(f) + 1 dei posti reali. Questo significa che sappiamo già come verranno assegnati 60 + 37 + 1 = 98 dei posti, e ci rimangono 2 "posti bonus" da distribuire tra le 4 parti (nessuna parte può ottenere più di 1 posto bonus). A chi vanno questi posti bonus? Le parti con il rapporto più alto di f / (int(f) + 1)(prova lasciata come esercizio al lettore). Nei nostri esempi, i rapporti sono [0.98, 0.997, 0.6, 0.9], quindi le prime due parti ottengono un posto bonus ciascuno.


Diamo un'occhiata al codice. Innanzitutto, sostituiamo il conteggio dei voti di tutti i partiti che non hanno soddisfatto la barra con 0:

Ð          # triplicate the first input (list of votes)
 OO        # flattened sum
   I*      # multiply by the second input (bar)
     @     # greater than? (returns 0 or 1)
      *    # multiply

Ora, per ovviare alla mancanza di ricorsione, usiamo un imbarazzo 2F per ripetere due volte il codice principale. Al primo passaggio distribuirà i seggi totali tra la coalizione e al secondo passaggio distribuirà i seggi di ciascuna coalizione tra i suoi partiti. Dal momento che il primo passaggio viene eseguito una sola volta, ma il secondo passaggio viene eseguito per ogni coalizione, ciò comporta un lavoro piuttosto intenso.

DO¸I¸¸2Fнζε`s    # i don’t want to detail this tbh

Va bene, dopo questa parte oscura, la parte superiore della pila è ora un elenco di voti (voti della coalizione al primo passaggio, voti del partito al secondo) e sotto quello è il numero di seggi da assegnare. Usiamo questo per calcolare l'elenco dei posti frazionari:

D        # duplicate
 O       # sum  
  /      # divide each vote count by the sum
   *     # multiply by the number of seats
    ©    # save the fractional seats in variable r

Ora, calcoliamo i rapporti:

Ð            # triplicate
 ±           # bitwise not
  /          # divide

Bitwise non funziona magnificamente, qui. Si tronca in numero intero, aggiunge 1 e nega, tutto in un singolo byte. Perché negare? In 05AB1E, la divisione per 0 restituisce 0 e abbiamo bisogno di questi per ordinare per ultimi.

D {# copia ordinata dei rapporti ®1% # voti frazionari mod 1 (ovvero le parti decimali) O # somma di quanto sopra (questo è il numero di posti bonus) ò # arrotondare al più vicino (richiesto a causa di virgola mobile bs) è # indice nei rapporti ordinati

Questo ci dà il (n + 1) miglior rapporto, dove n è il numero di posti bonus (+1 perché l'indicizzazione è basata su 0). Pertanto, le parti che ottengono un posto bonus sono quelle che hanno un rapporto strettamente inferiore a questo.

‹      # less than
 +     # add to the fractional seats
  ï    # truncate to integer

Molto bella. Ottimo modo per usare la matematica per ottimizzare il tuo codice :)
scf

3

Python 2 , 220 byte

def d(l,n,a=0,b=1.):s=sum(x//b for x in l);return s-n and d(l,n,*[a,b,(a+b)/2,b*2][s>n::2])or b
def f(l,b,n):l=[[x*(x>=b*sum(sum(l,[])))for x in r]for r in l];return[[v//d(x,sum(x)//d(map(sum,l),n))for v in x]for x in l]

Provalo online!

Fondamentalmente solo un golf dell'implementazione di riferimento ...


1

Gelatina , 63 36 byte

F×S<ḷ×ḷµ§⁵:,1_×¥:@"§IṠʋ÷9ɗ¥ƬṪṪƲ¥¥@⁺"

Provalo online!

Un programma completo che prende tre argomenti: il numero di voti nel formato descritto dalla domanda, barra e N in quell'ordine. Restituisce un elenco di elenchi di conteggi dei posti. Il piè di pagina su TIO è solo per evidenziare la struttura dell'elenco dell'output. (Altrimenti si nasconde Jelly[] per gli elenchi di singoli elementi.)

Spiegazione

F×S<ḷ×ḷµ§⁵:,1_×¥:@"§IṠʋ÷9ɗ¥ƬṪṪƲ¥¥@⁺"

F                                   | Flatten vote counts
 ×                                  | Multiply by bar
  S                                 | Sum
   <ḷ                               | Less than original vote counts (vectorises and respects input list structure)
     ×ḷ                             | Multiply by original vote counts
       µ                            | Start a new monadic link with processed vote counts as input
        §                           | Vectorised sum

         ⁵                      ¥@  | Apply the following as a dyad with the number of seats as the right argument and the vectorised sum of votes as left

           ,                  Ʋ¥    |(*)- Pair vote counts with seat sum and find divisor using the following as a monad:
            1             ¥Ƭ        |     - Starting with 1 as a guess for divisor, and using the paired vote counts and seat sum as the right argument, apply the following as a dyad, collecting intermediate results, until the results repeat
                         ɗ          |       - Following as a dyad:
                      ʋ             |         - Following as a dyad:
                :@"                 |           - Integer divide with arguments zipped and reversed, i.e. divide cote counts by current divisor guess and leave total seats alone
                   §                |           -  Vectorised sum (will sum vote counts but leave seat number alone)
                    I               |           - Find differences i.e. desired total seats minus current calculation based on current divisor guess. Will return a list.
                     Ṡ              |           - Sign of this (-1, 0 or 1)
                       ÷9           |         - Divide by 9 (-0.111, 0 or 0.111)
             _×¥                    |     - Now multiply the current divisor guess by this and subtract it from that guess to generate the next guess. If the current guess is correct, the guess will be unchanged and so the Ƭ loop will terminate
                            ṪṪ      |     - Take the last item twice (first time to get the final
                               output of the Ƭ loop and second to remove the list introduced by I
         :                          | - Integer divide the vote counts by the output of the above

                                  ⁺"| Apply the above dyad from the step labelled (*) again, this time with the output of the previous step (total votes per coalition) as right argument and the vote counts as left argument, zipping the two together and running the link once for each pair

Presentazione originale (più grande ma più efficiente)

Gelatina , 63 byte

:S_3ƭƒṠ©ḢḤ;$;ṪƲṖÆm;ḊƲ®‘¤?ߥ/}ṛ¹?,
1,0;çḢḢ
FS×Ċ’<ḷ×ḷµ:"§:⁵ç$$ç"Ɗ

Provalo online!


Bella presentazione. L'ho provato con l'ingresso [[1]] 0.0 10, che mi aspetto di restituire [[10]] (vedi punto 2 del proiettile in casi d'angolo) e sono scaduto. Puoi confermare che è solo un tempo di esecuzione estremamente lungo e non un bug?
scf

L'invio originale funziona con quell'input BTW.
scf

@scf Supponevo erroneamente che i voti fossero sempre molto più alti dei seggi. La versione rivista dovrebbe funzionare correttamente (ed è molto più efficiente).
Nick Kennedy,

1
Bello, sembra buono! Sarebbe bello se potessi spiegare un po 'il codice.
scf

Domanda Naiive: perché il soffitto è importante? Se ho capito bene esegui il massimale sul numero minimo di voti, tuttavia non è necessario per il confronto.
scf

1

Wolfram - niente golf

Era solo curioso di risolverlo usando LinearProgramming , non un candidato al golf, ma forse un approccio interessante a un problema:

findDivisor[l_, n_] := Quiet@Module[{s, c, r, m, b, cons, sol},
   s = Length[l];
   c = Append[ConstantArray[0, s], 1];
   r = Thread[Append[IdentityMatrix[s], -l]];
   m = Append[Join[r, r], Append[ConstantArray[1, s], 0]];
   b = Append[Join[ConstantArray[{0, -1}, s], ConstantArray[{-1, 1}, s]], {n, 0}];
   cons = Append[ConstantArray[Integers, s], Reals];
   sol = LinearProgramming[c, m, b, 0, cons];
   {1/sol[[-1]], Most@sol}
   ]
solve[l_, bar_, n_] := 
 With[{t = l /. x_ /; x <= bar Total[l, 2] -> 0},
  With[{sol = findDivisor[Total /@ t, n]}, 
   {First@sol, MapThread[findDivisor, {t, Last@sol}]}]
  ]

Leggi alcune spiegazioni e provalo!


Anche se non è un concorrente, avere alcune spiegazioni sul metodo e sul codice sarebbe ottimo per scopi educativi.
scf

@scf Ho aggiunto un link al mio tentativo di spiegarlo
swish il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.