Logika życia

„Nikt by nie pamiętał o dobrym Samarytaninie, gdyby miał tylko dobre intencje. By przejść do historii, musiał mieć też pieniądze." – Margaret Thatcher

matematyka wyborczaprogramowanie

Pisania makr w VBA na przykładzie programu wdrażającego metodę d’Honda

Podziel się

O ile samo programowanie nie każdemu jest konieczne do życia, to może być przydatne, szczególnie wtedy, jeżeli ktoś wykonuje dużo żmudnych i powtarzalnych czynności w Excelu.

Metoda D’Hondta

Metoda D’Hondta to jedna z najpopularniejszych metod przeliczania poparcia udzielonego komitetom wyborczym (zazwyczaj są to komitety partyjne) na liczbę mandatów (posłów, radnych, europosłów), które przysługują tym komitetom. Wynik wyborczy, czyli liczbę głosów1 wszystkich komitetów dzielimy przez kolejne liczby naturalne, zaczynając od 1, dla każdego komitetu tworzy się ciąg ilorazów: poparcie/1; poparcie/2; poparcie/3, itd . Z tak powstałych ilorazów wybieramy tyle największych ile mandatów mamy do podziału, a mandat o określonym numerze przysługuje temu komitetowi, którego iloraz miał ten sam numer na liście największych ilorazów (licząc od najwyższych). Dla lepszego zrozumienia popatrzmy na przykład. Dla przykładu wybrałem wynik wyborów do sejmu z 2015 roku w okręgu 31.2 Poniżej w tabelce te wyniki, przy czym ograniczyłem się do tych komitetów, które w skali kraju przekroczyły próg wyborczy. W przedostatniej kolumnie, mamy liczbę mandatów w tym okręgu (12), zaś w ostatniej liczbę komitetów, które przekroczyły próg wyborczy (5 komitetów, miałoby to większe znaczenie, gdybyśmy mieli dane wszystkich komitetów w tym okręgu, a nie tylko te przekraczające próg). Jeżeli przekopiujemy to do Excela, tak, aby napis ’lp’ znajdował się w komórce A1, to mamy przygotowane dane, na których możemy napisać program, który rozdzieli nam mandaty na poszczególne komitety.

lp komitet l. głosów procent Mandaty do podziału Komitety
1 Komitet Wyborczy Prawo i Sprawiedliwość 135367 32,92% 12 5
2 Komitet Wyborczy Platforma Obywatelska RP 116658 28,37%
3 Komitet Wyborczy Wyborców „Kukiz’15” 41344 10,05%
4 Komitet Wyborczy Nowoczesna Ryszarda Petru 35591 8,66%
9 Komitet Wyborczy Polskie Stronnictwo Ludowe 4064 0,99%

Zanim jednak zaczniemy, to zróbmy sobie tabelkę z kolejnymi ilorazami, aby lepiej zrozumieć metodę d’Hondta. W kolejnych kolumnach mamy ilorazy poparcia przez kolejne liczby naturalne. Posiadaczom największych ilorazów przypadnie tyle mandatów ile największych ilorazów posiadają (łącznie mamy 12 mandatów, bo tyle mandatów przypada na okręg 31). Widzimy, że komitet z numerem 1 i 2 mają po 5 mandatów, zaś komitet 3 i 4 mają po jednym mandacie.

lp komitet l. głosów/1 l. głosów/2 l. głosów/3 l. głosów/4 l. głosów/5 l. głosów/6
1 Komitet Wyborczy Prawo i Sprawiedliwość 135367 67683,5 45122,3 33841,8 27073,4 22561,2
2 Komitet Wyborczy Platforma Obywatelska RP 116658 58329,0 38886,0 29164,5 23331,6 19443,0
3 Komitet Wyborczy Wyborców „Kukiz’15” 41344 20672,0 13781,3 10336,0 8268,8 6890,7
4 Komitet Wyborczy Nowoczesna Ryszarda Petru 35591 17795,5 11863,7 8897,8 7118,2 5931,8
9 Komitet Wyborczy Polskie Stronnictwo Ludowe 4064 2032,0 1354,7 1016,0 812,8 677,3

Programowanie w Excelu

Do rozpoczęcia przygody z programowaniem zazwyczaj potrzebujemy edytora tekstu, w którym zapiszemy kod programu i kompilatora, który zamieni nasz kod na wykonywalny program. W praktyce jednak wykorzystuje się środowisko programistyczne, czyli program, który oprócz edytora i kompilatora ma również inne narzędzia, które usprawniają cały proces zarządzania tworzonym przez nas oprogramowaniem (np. debuger, który bardzo ułatwia odnajdywanie i poprawę błędów albo kolorowanie składni). Okazuje się, że Excel jest poniekąd takim właśnie środowiskiem. Dodatkowo mamy bardzo ułatwione zadanie, jeżeli do programu potrzebujemy wczytać dużą liczbę danych, nie potrzebujemy czytać ich z zewnętrznych plików, ani podpinać programu do bazy danych, ponieważ sam plik Excela może te dane przechowywać (aczkolwiek w niektórych przypadkach łatwiej byłoby stworzyć relacyjną bazę danych). Żeby zacząć przygodę, mając otwarty program Excel naciśnijmy klawisze Alt+F11 (w niektórych laptopach Alt + Fn + F11), na naszych oczach powinien pojawić się widok podobny do tego na rysunku.

wygląd srodowiska do pisania makr w vba, po naciśnięciu alt + F11 (albo alt +Fn +F11)
Edytor makr w Excelu -Visual Basic Editor

Po lewej mamy eksplorator projektów (jeśli go nie widać, to z paska narzędzi wybieramy: View i tam jest Project Explorer albo skrótem Ctrl+R) , tu myszką zaznaczamy, do którego obiektu przypiszemy makro, niech to będzie: Ten_skoroszyt. Po prawej mamy okno, w którym możemy pisać nasz program, tzn. edytor kodu (jeśli go nie widać to z menu wybieramy: View i Code albo F7). Teraz możemy napisać nasz program.

Kod programu w VBA

 
Sub dhont() ' zaczynamy procedurę
 
    Dim TablicaIlorazow() As Double ' tworzymy miejsce na kolejne ilorazy kolejnych komitetów
    Dim Mandaty As Integer, LiczbaKomitetow As Integer, PozycjaMandatu As Integer
    Dim Wynik() As Integer

    Mandaty = Worksheets("dane").Cells(2, 7)   ' zczytujemy z Excela liczbę mandatów do   'przydzielenia
    LiczbaKomitetow = Worksheets("dane").Cells(2, 8) ' zczytujemy z Excela liczbę komitetów, które 'potencjalnie mogą otrzymać mandaty

    ReDim TablicaIlorazow(1 To LiczbaKomitetow, 1 To Mandaty) ' podajemy dokładny rozmiar tablicy 'na ilorazy kolejnych komitetów
    ReDim Wynik(1 To LiczbaKomitetow)

    For i = 1 To LiczbaKomitetow
        TablicaIlorazow(i, 1) = Worksheets("dane").Cells(1 + i, 3) ' wczytujemy z Excela poparcie 'dla kolejnych komitetów
    Next i ' i przypisujemy do ilorazu powstałego z dzielenia poparcia przez 1

    For i = 1 To LiczbaKomitetow ' dla wszystkich komitetów
        For k = 2 To Mandaty ' dla mandatów od drugiego do ostatniego (w ramach komitetu z numerem 'i)
            TablicaIlorazow(i, k) = TablicaIlorazow(i, 1) / k ' dzielimy poparcie przez liczbę 'naturalną odpowiadającą numerowi mandatu
        Next k                                            ' i zapisujemy w odpowiednim miejscu w 'tablicy ilorazów
    Next i
 
    For licznikMandatow = 1 To Mandaty
        maxVal = 0
        KomitetZNajwIlorazem = 0 'nr komitetu z największym ilorazem
        
        ' Znajdujemy największy iloraz przeszukując wszystkie wartości TablicaIlorazow(i, k); 'jeżeli mamy wartość większą niż dotychczasowa największa (maxVal), to teraz ona staje się 'największą (maxVal = TablicaIlorazow(i, k)) , w tym miejscu zmienna i wskazuje nr komitetu z 'największym ilorazem
        For i = 1 To LiczbaKomitetow
            For k = 1 To Mandaty
                If TablicaIlorazow(i, k) > maxVal Then
                    maxVal = TablicaIlorazow(i, k)
                    KomitetZNajwIlorazem = i
                End If
            Next k
        Next i
        
        ' dodajemy jeden mandat komitetowi, który ma największy iloraz
        Wynik(KomitetZNajwIlorazem) = Wynik(KomitetZNajwIlorazem) + 1
        
        ' w tablicy ilorazów zerujemy największy wynik, aby nie pojawił się, gdy będziemy 'przydzielać kolejny mandat tzn. gdy licznikMandatow zwiększymy o 1, korzystamy tu z tego, że 'wynik(KomitetZNajwIlorazem) jest równy numerowi ostatniego zdobytego mandatu dla komitetu z 'największym ilorazem
        TablicaIlorazow(KomitetZNajwIlorazem, Wynik(KomitetZNajwIlorazem)) = 0
        
    Next licznikMandatow

 Worksheets("dane").Cells(1, 5) = "l. mandatów"
 For i = 1 To LiczbaKomitetow
    Worksheets("dane").Cells(1 + i, 5) = Wynik(i)  ' wyświetlamy wyniki w kolejnych wierszach w 5 kolumnie
 Next i


End Sub ' kończymy procedurę

Objaśnienia

Zapis:


Dim TablicaIlorazow() As Double

oznacza tyle, że deklarujemy zmienną o nazwie TablicaIlorazow, która jest tablicą, która będzie przechowywać liczby z podwójną precyzją (As Double) . Tablice to struktury, który przechowują wartości (liczby, teksty, inne tablice) w sposób uporządkowany tzn. każda wartość ma swój numer w tablicy, dzięki czemu łatwo ją pobrać i można to robić w sposób zorganizowany. Wiemy, że zmienna TablicaIlorazow jest tablicą, bo decydują o tym nawiasy, bez nawiasów tzn. zmienna tak zadeklarowana Dim TablicaIlorazow As Double byłaby zwykłą zmienną (miejscem w pamięci przeznaczonym do przechowywania pojedynczej wartości). Podobnie zapis


Dim Wynik() As Integer

oznacza tyle, że deklarujemy tablicę o nazwie Wynik, która będzie przechowywać liczby całkowite (As Integer), tzn. liczby które nie mają części dziesiętnych (przecinków). Z kolei:


Dim Mandaty As Integer, LiczbaKomitetow As Integer, PozycjaMandatu As Integer

to deklaracja trzech zmiennych do przechowywania liczb całkowitych. Zapis


Mandaty = Worksheets("dane").Cells(2, 7)

oznacza tyle, że do zmiennej Manadaty przypisujemy liczbę, która jest w Arkuszu dane, w komórce, która jest w drugim wierszu i siódmej kolumnie. Podobnie zapis


LiczbaKomitetow = Worksheets("dane").Cells(2, 8)

oznacza tyle, że do zmiennej LiczbaKomitetow przypiszemy liczbę, która jest w Arkuszu dane, w komórce, która jest w drugim wierszu i ósmej kolumnie. Zapis:


ReDim TablicaIlorazow(1 To LiczbaKomitetow, 1 To Mandaty)

oznacza tyle, że nasza tablica będzie od teraz dwuwymiarowa, tzn. każda wartość w tablicy będzie dostępne poprzez podanie dwóch liczb, pierwsza z zakresu od 1 do LiczbaKomitetow i druga liczba w zakresie od 1 do Mandaty . Zapis:


ReDim Wynik(1 To LiczbaKomitetow)

to deklaracja, tego że elementy w tablicy Wynik będą dostępne przez podanie jednej liczby z zakresu od 1 do LiczbaKomitetow. Zapis:


For i = 1 To LiczbaKomitetow
TablicaIlorazow(i, 1) = Worksheets("dane").Cells(1 + i, 3)
Next i

to pętla przebiegająca wszystkie wartości i od i=1 do i=LiczbaKomitetow. Dla każdego i z tego zakresu TablicaIlorazow w miejscu o współrzędnych (i,1) zapisze wartość z Excela z arkusza dane, z komórki, która jest w wierszu z numerem i+1 i kolumnie z numer 3. Podobnie zapis:


For i = 1 To LiczbaKomitetow
For k = 2 To Mandaty
TablicaIlorazow(i, k) = TablicaIlorazow(i, 1) / k
Next k
Next i

to dwie pętle, pierwsza (zewnętrzna – gdzie zmienną, która się zmienia jest i) przebiega wszystkie komitety, druga (wewnętrzna) przebiega mandaty (a raczej liczby naturalne odpowiadające mandatom) od drugiego do ostatniego (w ramach ustalonego komitetu), dla komitetu z numerem i oraz mandatu z numerem k dzielimy poparcie (bo wartość TablicaIlorazow(i, 1) jest równa poparciu komitetu z numerem i) przez liczbę naturalną k i zapisujemy w odpowiednim miejscu (tzn. w miejscu o współrzędnych (i,k)) w tablicy ilorazów. Mamy taki zapis, złożony z 3 pętli:


For licznikMandatow = 1 To Mandaty
maxVal = 0
KomitetZNajwIlorazem = 0
For i = 1 To LiczbaKomitetow
For k = 1 To Mandaty
If TablicaIlorazow(i, k) > maxVal Then
maxVal = TablicaIlorazow(i, k)
KomitetZNajwIlorazem = i
End If
Next k
Next i
Wynik(KomitetZNajwIlorazem) = Wynik(KomitetZNajwIlorazem) + 1 TablicaIlorazow(KomitetZNajwIlorazem, Wynik(KomitetZNajwIlorazem)) = 0
Next licznikMandatow

Pierwsza najbardziej zewnętrzna pętla przebiega licznikMandatow od 1 do ostatniego mandatu (czyli do wartości zapisanej w zmiennej Mandaty). Dla każdego mandatu (dla każdego licznikMandatow), chcemy znaleźć komitet, który zdobędzie ten mandat. W tym celu używamy dwóch pętli, które przebiegną wszystkie wartości w tabeli TablicaIlorazow (pierwsza przebiega od 1 do LiczbaKomitetow, druga od 1 do Mandaty (do ostatniego mandatu)) W ten sposób znajdujemy największy iloraz przeszukując wszystkie wartości TablicaIlorazow(i, k); jeżeli mamy wartość większą niż dotychczasowa największa (maxVal), to teraz ona staje się największą (maxVal = TablicaIlorazow(i, k)) , w tym miejscu zmienna i wskazuje nr komitetu z największym ilorazem. Mając numer komitetu z największym ilorazem tablicę wynik dla tego numeru powiększamy o jeden, tzn.:

Wynik(KomitetZNajwIlorazem) = Wynik(KomitetZNajwIlorazem) + 1

Pozostaje ten zapis:

TablicaIlorazow(KomitetZNajwIlorazem, Wynik(KomitetZNajwIlorazem)) = 0

W tablicy ilorazów zerujemy największy wynik, aby nie pojawił się, gdy będziemy przydzielać kolejny mandat, tzn. gdy licznikMandatow zwiększymy o 1 (odpowiada za to: Next licznikMandatow), korzystamy tu z tego, że Wynik(KomitetZNajwIlorazem) jest równy numerowi ostatniego zdobytego mandatu dla komitetu z największym ilorazem. Poza tymi trzema pętlami wyświetlamy wyniki:


Worksheets("dane").Cells(1, 5) = "l. mandatów"
For i = 1 To LiczbaKomitetow
Worksheets("dane").Cells(1 + i, 5) = Wynik(i)
Next i

W komórce, która jest w pierwszym wierszu i piątej kolumnie kolumnie wstawiamy napis: l. mandatów. Następnie pętla przebiegająca numery wszystkich komitetów w 5 kolumnie i w wierszu o numerze 1+ i (tzn. 1+numer komitetu) wyświetli liczbę mandatów przysługująca temu komitetowi.

Znak apostrofu (pojedynczego cudzysłowu) pozwala zapisać komentarz, który w edytorze VBA podświetli się na zielono. Kod z poprzedniego akapitu jest dość mocno skomentowany, możemy go skopiować i wkleić do edytora kodu. Cały kod możemy zobaczyć w działaniu wykorzystując debugger i okno: locals (View i Locals Window). Naciskając klawisz F8 (albo Fn + F8 albo wybieramy myszką z paska narzędzi: Debug i Step Into) wykonamy program krok po kroku, w tym czasie w oknie locals możemy zobaczyć wartości zmiennych (również najeżdżając myszką na zmienną). Dobrze spróbować to wykonać takie ćwiczenie, bo z jednej strony zapoznamy się z debbugerem, a z drugiej możemy wyrobić sobie intuicję jak działają poszczególne instrukcje.

Co zamiast Excela

Większość makr Excela można napisać i wykonać w pakiecie LibreOffice. W tym celu na początku piszemy:

Option VBASupport 1

Więcej informacji tu: https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03103350.html. Natomiast, jeżeli chcemy mieć sam Excel, to moim zdaniem najlepiej mieć go w wersji z dożywotnią licencją, a nie w wersji abonamentowej. Jeszcze niedawno (nie wiem czy nadal tak jest) można było w niektórych sklepach kupić legalny pakiet Office w bardzo dobrych cenach, od osób (albo firm), które już go używały, zrezygnowały z tego i odsprzedały sklepowi (stąd niska cena).


  1. Możemy zamiast liczby głosów wziąć procentowe poparcie dla tych komitetów, gdyby nie to, że poparcie procentowe czasem chcielibyśmy zaokrąglić wynik byłby taki sam. Przy zaokrąglaniu czasami (nie często) możemy dostać wynik nieco przekłamany, jednak pisząc program, który będzie prognozował rozkład mandatów pomiędzy różne komitety wyborcze (dla wyborów, które mają być w przyszłości) wygodniej (dla użytkownika) posługiwać się poparciem procentowym.↩︎
  2. Gdy tworzymy taki program, który prognozę poparcia w skali kraju przeliczy na liczbę mandatów, to staniemy przed problemem w jaki sposób poparcie w kraju przełoży się na poparcie w okręgach. W takiej sytuacji zazwyczaj wykorzystuje się wzorce z poprzednich wyborów, stąd ten program:https://logikazycia.eu/kalkulator-wyborczy-2023/ pisany przed 15.10.2023 nie daje dokładnie takiego podziału mandatów jakie miało miejsce po 15.10.2023.↩︎

Udostępnij

O autorze