Розподіл місць у парламенті


14

Вступ

На загальних виборах хочеться обчислити постійну ціну за місце в парламенті. Це означає, що для N >= 0розподілу місць та списку nsголосів на партію ми хотіли б знайти таку кількість d, яка така

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

Щоб зробити речі цікавими (і більше схожими на реальний світ), ми додамо ще два факти:

  1. Дві партії можуть зібратися в "коаліцію", щоб місця були надані "коаліції" за сумою голосів за всі партії в ній. Тоді місця, отримані «коаліцією», поділяються між партіями аналогічним чином (знайти дільника тощо)

  2. Партія, яка не пройшла певний відсоток голосів (наприклад, 3,25%), автоматично отримує 0 місць, і її голоси не враховуються за "коаліцію".

Виклик

Вам дано:

  1. Список списків, кожен із вкладених списків містить цілі числа (кількість голосів) і має довжину 1 для однієї партії або довжину 2 для "коаліції".
  2. Мінімальний відсоток голосів (він же "бар" за "загородження"), щоб отримати місця, як фракція (тому 3,25% дано як 0,0325)
  3. Загальна кількість місць для розподілу між усіма сторонами (ціле число)

Ви повинні надрукувати ту саму вкладену структуру списку, кількість голосів яких буде замінена місцями парламенту.

Winner - код з найменшою кількістю байтів.

Кутові корпуси:

  • Можливо (і зазвичай буде) більше одного можливого дільника. Оскільки це не у виході, це насправді не має значення.
  • Уявіть , N=10і ns = [[1]], таким чином , дільник може бути 0,1 (не є цілим числом)
  • У деяких випадках не може бути вирішена, наприклад ns=[[30],[30],[100]], bar=0, N=20. Існує межа, d=7.5де сума похилих значень стрибає з 19 до 21. Ви не плануєте вирішувати ці випадки. (дякую члену громади Арнольду, що вказав на цю справу)

Приклад введення та виводу

Дуже неоптимізований приклад Python3:

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))

Приклад введення:

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

І його вихід:

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

Ще кілька прикладів результатів:

Якщо bar=0.1ми отримаємо цікаве протистояння між двома сторонами, оскільки жодна з менших партій не враховується:

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

І якщо N=0(кутовий випадок), то, звичайно, ніхто нічого не отримує:

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

5
Ласкаво просимо до PPCG!
Арнольд

Ласкаво просимо до CGCC (раніше відомий як PPCG)! Я взяв на себе сміливість додати підсвічування Python, щоб ваш код став більш читабельним, і я поставив вкладку нижче коду, щоб введення-вихід ближче один до одного. Я також додав два відповідні теги. Хороший перший виклик, хоча +1 від мене! PS: Ви можете скористатися пісочницею запропонованих викликів, щоб отримати зворотній зв'язок щодо викликів, перш ніж надсилати їх до основних, хоча в цьому випадку я думаю, що це завдання зрозуміло. Можливо, додамо кілька додаткових тестових випадків? Насолоджуйтесь своїм перебуванням :)
Кевін Круїйсен

Звичайно, @KevinCruijssen, я додав ще два випадки. Щодо існуючого результату, я вважаю, що це правда, оскільки це точні результати останніх виборів :)
scf

@Arnauld З цікавості, яким повинен бути очікуваний результат для цього тестового випадку?
Кевін Круїйсен

1
Я вже додав кулю в кутовий корпус, я думаю, що це нерозв’язний випадок, оскільки в межі d=7.5ви отримуєте стрибок з 19 місць на 21 місце.
scf

Відповіді:


2

05AB1E , 42 39 байт

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

Спробуйте в Інтернеті!

05AB1E не вистачає хорошої рекурсії, тому здійснення двійкового пошуку, як у довідковому коді, було б болючим. На щастя, нам взагалі не потрібно знаходити дільник!

Скористаємося простим прикладом: [600, 379, 12, 9] голосів, 100 місць, ні коаліції, ні бар. По-перше, ми обчислюємо, скільки дробних місць отримує кожна сторона, визначаючи як дробові місця party_votes * seats / sum_of_votes. У нашому прикладі це дає [60, 37,9, 1,2, 0,9].

Цікавий біт полягає в тому, що якщо партія отримає fдробові місця, вона отримає те int(f)або int(f) + 1реальне місце. Це означає, що ми вже знаємо, як буде розподілено 60 + 37 + 1 = 98 місць, і нам залишається 2 “бонусні місця” для розподілу серед 4 партій (жодна партія не може отримати більше 1 бонусного місця). До кого ходять ці бонусні місця? Сторони з найвищим співвідношенням f / (int(f) + 1)(доказ залишається читачем як вправа). У наших прикладах співвідношення є [0.98, 0.997, 0.6, 0.9], тому перші дві сторони отримують бонусне місце кожна.


Давайте подивимось на код. По-перше, ми замінюємо підрахунок голосів усіх партій, які не досягли планки 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

Тепер, щоб подолати відсутність рекурсії, ми використовуємо стрибок, 2Fщоб повторити основний код двічі. На першому проході він розподілить загальний обсяг місць між коаліцією, а на другому проходить розподіл місць кожної коаліції між своїми партіями. Оскільки перший пропуск працює лише один раз, але другий пропуск проходить для кожної коаліції, це передбачає досить багато напруженої роботи.

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

Добре, після цього незрозумілого біту вершина стеку - це список голосів (коаліційні голоси за перший прохід, партійні голоси на другому), а нижче - кількість місць, які потрібно виділити. Ми використовуємо це для обчислення списку дробових місць:

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

Тепер ми обчислюємо співвідношення:

Ð            # triplicate
 ±           # bitwise not
  /          # divide

Біт не працює красиво, тут. Він обрізається цілим числом, додає 1 і заперечує все в одному байті. Чому заперечують? У 05AB1E поділ на 0 повертає 0, і нам це потрібно для сортування останнього.

D {# відсортована копія коефіцієнтів ® 1% # дробових голосів mod 1 (він називається десятковими частинами) O # сума вищевказаного (це кількість бонусних місць) ò # раунд до найближчого (необхідний через bs з плаваючою комою) è # індекс у відсортованому співвідношенні

Це дає нам (n + 1) найкраще співвідношення, де n - кількість бонусних місць (+1, оскільки індексація дорівнює 0). Таким чином, сторонами, які отримують преміальне місце, є ті, у кого коефіцієнт суворо менший за це.

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

Дуже хороша. Чудовий спосіб використовувати математику для оптимізації коду :)
scf

3

Python 2 , 220 байт

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]

Спробуйте в Інтернеті!

В основному просто гольф еталонної реалізації ...


1

Желе , 63 36 байт

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

Спробуйте в Інтернеті!

Повна програма, що бере три аргументи: кількість голосів у форматі, описаному питанням, смугою та N у тому порядку. Повертає список списків підрахунків місць. Нижній колонтитул TIO - це лише виділити структуру списку виводу. (Інакше Jelly ховається []для однопозиційних списків.)

Пояснення

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

Оригінал подання (більший, але ефективніший)

Желе , 63 байти

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

Спробуйте в Інтернеті!


Приємне подання. Я спробував це з введенням [[1]] 0,0 10, яке, як очікується, поверне [[10]] (див. Пункт 2 у кутових випадках) і вийшов із часу. Чи можете ви підтвердити, що це просто надзвичайно довгий час роботи, а не помилка?
scf

Оригінальне подання працює з цим входом BTW.
scf

@scf Я неправильно припускав, що голоси завжди були набагато вищими за місця. Переглянута версія повинна працювати нормально (і набагато ефективніше).
Нік Кеннеді

1
Приємно, добре виглядає! Було б добре, якби ви могли трохи пояснити код.
scf

Наївне питання: чому стеля важливий? Якщо я правильно розумію, ви виконуєте максимальну кількість голосів, але для порівняння це зайве.
scf

1

Вольфрам - немає гольфу

Було просто цікаво вирішити це за допомогою LinearProgramming , а не кандидата в гольф, але, можливо, цікавий підхід до проблеми:

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}]}]
  ]

Прочитайте пояснення та спробуйте!


Незважаючи на те, що він не є конкурентом, мати деякі пояснення щодо методу та коду було б чудово для навчальних цілей.
scf

@scf Я додав посилання на мою спробу при поясненні його
помах
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.