A D’Hondt-módszer

Használható segédanyagok: Haskell könyvtárainak dokumentációja, (lokális!) Hoogle, a tárgy honlapja és a BE-AD rendszerbe feltöltött beadandók. Ha bármilyen kérdés, észrevétel felmerül, azt a felügyelőknek kell jelezni, nem a diáktársaknak!

A feladatok egymásra épülnek ezért érdemes ezeket a megadásuk sorrendjében megoldani, de legalább megérteni az aktuális feladatot megelőző feladatokat! A függvények definíciójában lehet, sőt javasolt is alkalmazni a korábban definiált függvényeket (függetlenül attól, hogy sikerült-e azokat megadni).

Tekintve, hogy a tesztesetek, bár odafigyelés mellett íródnak, nem fedik le minden esetben a függvény teljes működését, határozottan javasolt még külön próbálgatni a megoldásokat beadás előtt, vagy megkérdezni a felügyelőket!

Részpontszámokat csak az elégséges szint elérése után lehet kapni!

A feladat összefoglaló leírása

A D’Hondt-módszer a választási rendszerekben a mandátumok kiosztásának egyik lehetséges módja, nevét kidolgozójáról, Victor d’Hondt belga matematikusról kapta. Ezt a módszert használja a magyar választási rendszer az országgyűlési választásokon az országos (kompenzációs) listás mandátumok, illetve az európai parlamenti képviselők mandátumának kiosztásakor.

A módszer rendkívül egyszerű: feljegyzik az egyes pártok által kapott szavazatok számát, majd ezek alá külön sorokban a szavazatszámok felét, harmadát, negyedét, stb. egészen a megszerezhető mandátumok számáig osztva. (Bár, mint később látni fogjuk, ennek a szabálynak több variációja is létezik.) Az így kapott, ún. D’Hondt-mátrixban megkeresik a legnagyobb számot, és amelyik pártnak az oszlopában az található, kap egy mandátumot. Ezután megkeresik a következő legnagyobbat, és ez addig ismétlődik, amíg az összes mandátumot ki nem osztották.

A feladatban a következő konstansokat fogjuk használni. Ezek egy listában tárolják, hogy milyen pártok és mennyi szavazatot kaptak egyenként.

votes1 = [("Pirate Party", 100), ("Party Hard", 80), ("Tow Party", 30)
         ,("Lets Party", 20)]

votes2 = [("Sárgák", 47000), ("Fehérek", 16000), ("Pirosak", 15900)
         ,("Zöldek", 12000), ("Kékek", 6000), ("Barnák", 3100)]

A hányadosok kiszámítása (2 pont)

Írjunk egy olyan függvényt, amely segítségével ki lehet számítani egy egész értékhez tartozó hányadosokat! Ezek fogják a későbbiekben a megszerkesztendő táblazatunk sorait adni. A hányadosok számolását a végtelenségig végezzük, tetszőleges képlet alapján, amelyet a függvénynek paraméterként adhatunk át.

A példákban a következő képleteket fogjuk alkalmazni. Az egyszerűbb leírásukhoz először definiálunk még egy segédfüggvényt:

infixl 7 /.
(/.) :: (Integral a, RealFrac b) => a -> a -> b
(/.) = (/) `on` fromIntegral

Az első képletben a hányadosok képzése egyszerűen a nekik megfelelő sorszám alapján történik, ezt jelöli az s:

DHondt643cc89e8018ba0c0ada35f4cb82c94e.png

f1 :: Integer -> Integer -> Double
f1 v s = v /. (s + 1)

Ekkor a hányadosok a következő módon képződnek, adott V értékkel:

DHondt115ff1620af7d6810ffc710e1dca7520.png

Egy másik esetben az s indexet kettő megfelelő hatványának kiszámítására alkalmazzuk:

DHondt7e2f1722bb7f089279c6a85dbf28d14c.png

f2 :: Integer -> Integer -> Double
f2 v s = v /. (2 ^ s)

Ebben az esetben így számolódnak ki a hányadosok adott V értékre:

DHondtdec71632fb94be7c4a224387fc2b180e.png

Az elkészítendő függvényünk ennek megfelelően a következő lesz:

quotients :: (Integral a, RealFrac b) => (a -> a -> b) -> a -> [b]

Test>
[100.0, 50.0, 33.333333333333336, 25.0, 20.0, 16.666666666666668, 14.285714285714286, 12.5, 11.11111111111111, 10.0, 9.090909090909092, 8.333333333333334, 7.6923076923076925, 7.142857142857143, 6.666666666666667, 6.25, 5.882352941176471, 5.555555555555555, 5.2631578947368425, 5.0, 4.761904761904762, 4.545454545454546, 4.3478260869565215, 4.166666666666667, 4.0, 3.8461538461538463, 3.7037037037037037, 3.5714285714285716, 3.4482758620689653, 3.3333333333333335, 3.225806451612903, 3.125, 3.0303030303030303, 2.9411764705882355, 2.857142857142857, 2.7777777777777777, 2.7027027027027026, 2.6315789473684212, 2.5641025641025643, 2.5, 2.4390243902439024, 2.38…381, …, ……] :: [Double]
Test>
[100.0, 50.0, 25.0, 12.5, 6.25, 3.125, 1.5625, 0.78125, 0.390625, 0.1953125, 9.765625e-2, 4.8828125e-2, 2.44140625e-2, 1.220703125e-2, 6.103515625e-3, 3.0517578125e-3, 1.52587890625e-3, 7.62939453125e-4, 3.814697265625e-4, 1.9073486328125e-4, 9.5367431640625e-5, 4.76837158203125e-5, 2.384185791015625e-5, 1.1920928955078125e-5, 5.9604644775390625e-6, 2.9802322387695313e-6, 1.4901161193847656e-6, 7.450580596923828e-7, 3.725290298461914e-7, 1.862645149230957e-7, 9.313225746154785e-8, 4.6566128730773926e-8, 2.3283064365386963e-8, 1.1641532182693481e-8, 5.820766091346741e-9, 2.9103830456733704e-9, 1.4551915228366852e-9, 7.275957614183426e-10, 3.6379…e-10, 1.81…-10, ……] :: [Double]
Test>
[500.0, 250.0, 166.66666666666666, 125.0, 100.0, 83.33333333333333, 71.42857142857143, 62.5, 55.55555555555556, 50.0, 45.45454545454545, 41.666666666666664, 38.46153846153846, 35.714285714285715, 33.333333333333336, 31.25, 29.41176470588235, 27.77777777777778, 26.31578947368421, 25.0, 23.80952380952381, 22.727272727272727, 21.73913043478261, 20.833333333333332, 20.0, 19.23076923076923, 18.51851851851852, 17.857142857142858, 17.24137931034483, 16.666666666666668, 16.129032258064516, 15.625, 15.151515151515152, 14.705882352941176, 14.285714285714286, 13.88888888888889, 13.513513513513514, 13.157894736842104, 12.820512820512821, 12.5, 12.19512…219512, 11.9…905, ……] :: [Double]
Test>
[500.0, 250.0, 125.0, 62.5, 31.25, 15.625, 7.8125, 3.90625, 1.953125, 0.9765625, 0.48828125, 0.244140625, 0.1220703125, 6.103515625e-2, 3.0517578125e-2, 1.52587890625e-2, 7.62939453125e-3, 3.814697265625e-3, 1.9073486328125e-3, 9.5367431640625e-4, 4.76837158203125e-4, 2.384185791015625e-4, 1.1920928955078125e-4, 5.9604644775390625e-5, 2.9802322387695313e-5, 1.4901161193847656e-5, 7.450580596923828e-6, 3.725290298461914e-6, 1.862645149230957e-6, 9.313225746154785e-7, 4.6566128730773926e-7, 2.3283064365386963e-7, 1.1641532182693481e-7, 5.820766091346741e-8, 2.9103830456733704e-8, 1.4551915228366852e-8, 7.275957614183426e-9, 3.637978807091713e-9, 1.81…e-9, …, ……] :: [Double]

Függvények párhuzamos alkalmazása egy párra (1 pont)

Adjunk meg egy olyan függvényt, amely két függvény és egy tetszőleges értékeket tartalmazó pár esetén az első függvényt alkalmazza a pár első, a második függvényt pedig a pár második elemére!

infixr 3 &&&
(&&&) :: (a -> c) -> (b -> d) -> (a,b) -> (c,d)

Test>
(4, 8) :: (Integer, Integer)
Test>
(1, [2, 3, 4, 5, 6, 7, 8, 9, 10]) :: (Integer, [Integer])
Test>
([0, 0, 0, 0], "foobar") :: ([Integer], [Char])

A D’Hondt-mátrix elkészítése (2 pont)

Definiáljuk azt a függvényt, amely adott képlet és szétosztandó mandátumok alapján a pártokhoz megadja azok sorait a mátrixban! Vegyük észre, hogy ekkor lényegében listák listáit kapjunk, ahol a sorokat mindig felcímkézzük a pártok neveivel. Ezt nevezzük D’Hondt-mátrixnak.

Megjegyzés: A törtek és egész számok közti átalakításra használjunk kerekítést!

dHondt :: (Integral a, RealFrac b)
  => (a -> a -> b) -> Int -> [(String,a)] -> [(String,[a])]

Test>
[("Pirate Party", [100, 50, 33, 25]), ("Party Hard", [80, 40, 27, 20]), ("Tow Party", [30, 15, 10, 8]), ("Lets Party", [20, 10, 7, 5])] :: [(String, [Integer])]
Test>
[("Pirate Party", [100, 50, 33, 25, 20, 17, 14, 12, 11, 10]), ("Party Hard", [80, 40, 27, 20, 16, 13, 11, 10, 9, 8]), ("Tow Party", [30, 15, 10, 8, 6, 5, 4, 4, 3, 3]), ("Lets Party", [20, 10, 7, 5, 4, 3, 3, 2, 2, 2])] :: [(String, [Integer])]
Test>
[("Pirate Party", [100, 50, 25, 12]), ("Party Hard", [80, 40, 20, 10]), ("Tow Party", [30, 15, 8, 4]), ("Lets Party", [20, 10, 5, 2])] :: [(String, [Integer])]
Test>
[("Pirate Party", [100, 50, 25, 12, 6, 3, 2, 1, 0, 0]), ("Party Hard", [80, 40, 20, 10, 5, 2, 1, 1, 0, 0]), ("Tow Party", [30, 15, 8, 4, 2, 1, 0, 0, 0, 0]), ("Lets Party", [20, 10, 5, 2, 1, 1, 0, 0, 0, 0])] :: [(String, [Integer])]
Test>
[("Sárgák", [47000, 23500, 15667, 11750, 9400, 7833, 6714, 5875]), ("Fehérek", [16000, 8000, 5333, 4000, 3200, 2667, 2286, 2000]), ("Pirosak", [15900, 7950, 5300, 3975, 3180, 2650, 2271, 1988]), ("Zöldek", [12000, 6000, 4000, 3000, 2400, 2000, 1714, 1500]), ("Kékek", [6000, 3000, 2000, 1500, 1200, 1000, 857, 750]), ("Barnák", [3100, 1550, 1033, 775, 620, 517, 443, 388])] :: [(String, [Integer])]
Test>
[("Sárgák", [47000, 23500, 11750, 5875, 2938, 1469, 734, 367]), ("Fehérek", [16000, 8000, 4000, 2000, 1000, 500, 250, 125]), ("Pirosak", [15900, 7950, 3975, 1988, 994, 497, 248, 124]), ("Zöldek", [12000, 6000, 3000, 1500, 750, 375, 188, 94]), ("Kékek", [6000, 3000, 1500, 750, 375, 188, 94, 47]), ("Barnák", [3100, 1550, 775, 388, 194, 97, 48, 24])] :: [(String, [Integer])]

Egy elem hozzákapcsolása egy lista elemeihez (1 pont)

Definiáljunk egy olyan függvényt, amely egy tetszőleges típusú értéket, valamint szintén tetszőleges típusú értékek listáját tartalmazó párt úgy alakít át, hogy a pár első elemét a második elemként szereplő lista összes elemével párba állítja!

spread :: (a,[b]) -> [(a,b)]

Test>
[(1, 't'), (1, 'e'), (1, 's'), (1, 't')] :: [(Integer, Char)]
Test>
[("something", 's'), ("something", 'o'), ("something", 'm'), ("something", 'e'), ("something", 't'), ("something", 'h'), ("something", 'i'), ("something", 'n'), ("something", 'g')] :: [([Char], Char)]
Test>
[(6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7), (6, 8), (6, 9), (6, 10)] :: [(Integer, Integer)]

A mandátumok elosztása (4 pont)

Adjuk meg azt a függvényt, amely segítségével el tudjuk osztani a pártok között a szavazatok alapján a mandátumokat! Ehhez bemenetként megkapjuk a hányadosok kiszámításához használható függvény (ld. a quotient függvény leírását), az elosztandó mandátumok számát, valamint a pártok neveit és a kapott szavataikat. Ezekből kell a függvénynek kiszámolnia a mandátumokat a pártok neveihez társítva. Ügyeljünk arra, hogy az eredménynek a mandátumok szerint csökkenő sorrendben kell létrejönnie!

A módszer röviden a következő:

Például a votes1 esetén 8 mandátumot szeretnénk szétosztani az f1 képlettel 4 párt között, amelyek rendre 100, 80, 30 és 20 szavazatot kaptak. Ekkor a D’Hondt-mátrix a következőképpen épül fel:

Párt /1 /2 /3 /4 /5 /6 /7 /8 Mandátum
Pirate Party 100 50 33 25 20 17 14 12 4
Party Hard 80 40 27 20 16 13 11 10 3
Tow Party 30 15 10 8 6 5 4 4 1
Lets Party 20 10 7 5 4 3 3 2 0

Itt a mátrixban az első 8 legnagyobb (kiemelt) érték a 100, 80, 50, 40, 33, 30, 27 és 25. Ebből 4 a Pirate Party, 3 a Party Hard, 1 pedig a Tow Party értéke, amelyek egyben a nekik járó mandátumok számát is jelentik.

dispose :: (Integer -> Integer -> Double) -> Int
  -> [(String, Integer)] -> [(String, Int)]

Test>
[("Pirate Party", 4), ("Party Hard", 3), ("Tow Party", 1)] :: [(String, Int)]
Test>
[("Party Hard", 3), ("Pirate Party", 3), ("Lets Party", 1), ("Tow Party", 1)] :: [(String, Int)]
Test>
[("Sárgák", 5), ("Fehérek", 2), ("Pirosak", 2), ("Zöldek", 1)] :: [(String, Int)]
Test>
[("Sárgák", 3), ("Fehérek", 2), ("Pirosak", 2), ("Zöldek", 2), ("Kékek", 1)] :: [(String, Int)]

A szavazatok tényleges eloszlása (2 pont)

Készítsünk egy olyan függvényt, amely segítségével megállapíthatjuk kizárólag a szavazatok aránya alapján, hogy melyik pártnak mennyi mandátum jutna! Ebben az esetben értelemszerűen a mandátumokat törtszámok jelzik és az összegük továbbra is a kiosztandó mandátumok számával egyenlő.

A számítás alapja a következő képlet:

DHondt6afb3e20453337ce5e1b1af16c77d1ed.png

ahol Vx az x párt kapott szavazatainak száma, n a pártok száma, és S az elosztandó mandátumok száma.

trueProportions :: (Integral a, RealFrac b) => Int -> [(String,a)] -> [(String,b)]

Test>
[("Pirate Party", 3.4782608695652173), ("Party Hard", 2.782608695652174), ("Tow Party", 1.0434782608695652), ("Lets Party", 0.6956521739130435)] :: [(String, Double)]
Test>
[("Pirate Party", 4.3478260869565215), ("Party Hard", 3.4782608695652173), ("Tow Party", 1.3043478260869565), ("Lets Party", 0.8695652173913043)] :: [(String, Double)]
Test>
[("Sárgák", 4.699999999999999), ("Fehérek", 1.6), ("Pirosak", 1.59), ("Zöldek", 1.2), ("Kékek", 0.6), ("Barnák", 0.31)] :: [(String, Double)]
Test>
[("Sárgák", 1.88), ("Fehérek", 0.64), ("Pirosak", 0.636), ("Zöldek", 0.48), ("Kékek", 0.24), ("Barnák", 0.124)] :: [(String, Double)]

Pontozás (elmélet + gyakorlat)