Przejdź do głównej treści
INF.04-01-25.01-SGStyczeń 2025 · 30 kroków · ok. 89 min · 35 kryteriów z klucza

Rozwiązanie krok po kroku: Aplikacja konsolowa i galeria zdjęć (Python + Angular)

Rozwiązanie dotyczy arkusza z galerią zdjęć w wersji Python + Angular: w konsoli klasa z prywatną listą liczb losowych i metodami wypisującymi, wyszukującymi, wybierającymi liczby nieparzyste i liczącymi średnią, a w przeglądarce jednokomponentowa aplikacja Angular, w której przełączniki Bootstrapa filtrują zdjęcia według kategorii, a przycisk „Pobierz” zwiększa licznik pobrań. To technologia, w której CKE pokazało obrazy 2-4 arkusza, a klucz podaje przykłady właśnie z Angulara (*ngFor, *ngIf, (click)). Do tego dokumentacja metody według wzoru z arkusza, zrzuty i dokument egzamin.

Kroki idą zdanie po zdaniu za treścią arkusza - każdy zaczyna się poleceniem z arkusza, a kod pokazuje dokładnie te linie, które je spełniają. Kod od początku jest pisany tak, jak w prawdziwych projektach; na końcu dwa kroki porównują ten wariant z rozwiązaniem w C# i React (druga zakładka).

Co widać w podglądzie. Wyniki programu konsolowego pochodzą z prawdziwego uruchomienia kodu z danego kroku (Python 3.12) - tablica jest za każdym razem inna, a liczbę do wyszukania wpisuje się dopiero po zobaczeniu tablicy. Podgląd aplikacji webowej wykonuje kod komponentu z kroku (klasę z app.ts i szablon app.html z blokami @for i @if) ze zdjęciami z archiwum pliki3.zip: możesz przełączać kategorie i klikać „Pobierz”. Scenariusz z obrazów 2-4 przeszedłem w podglądzie i w prawdziwej aplikacji zbudowanej poleceniem ng build (Angular 22) - z tym samym wynikiem.

Jak liczą się punkty. Klucz ma 35 kryteriów (R.1.1 - R.4.8), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 27 punktów - można stracić 8 kryteriów i nadal zdać. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  1. Etap 1: Przygotowanie

    Krok 1 · po tym etapie masz 0 z 35 kryteriów (pkt)

    Krok 1: Folder zdającego i trzy podfoldery

    ok. 3 min
    Polecenie z arkusza

    Utwórz folder i nazwij go numerem zdającego. W folderze utwórz podfoldery: konsolowa, webowa, dokumentacja.

    Na pulpicie zakładasz folder o nazwie z numerem PESEL, a w nim konsolowa, webowa i dokumentacja. Archiwum pliki3.zip z pulpitu (hasło z arkusza: K@tegorie)) rozpakujesz przy aplikacji webowej - są w nim zdjęcia obraz1.jpg - obraz12.jpg i plik dane.txt z listą obiektów galerii.

  2. Etap 2: Aplikacja konsolowa

    Kroki 2-10 · po tym etapie masz 15 z 35 kryteriów (pkt)

    Krok 2: Projekt konsola w PyCharm i pusta klasa

    ok. 3 min
    Polecenie z arkusza

    Zastosowany obiektowy język programowania zgodny z zainstalowanym na stanowisku egzaminacyjnym: C++ lub C#, lub Java, lub Python. Tablica oraz operacje na niej wykonywane są implementowane z wykorzystaniem klasy.

    Wybierasz Pythona. W PyCharm: New Project, nazwa konsola (stąd archiwum konsola.zip), plik main.py. Zakładasz dwie rzeczy: klasę Table na tablicę i operacje na niej (arkusz dopuszcza dla klasy nazwę ogólną typu „tablica”) oraz funkcję main() - program główny, wywoływany pod warunkiem if __name__ == "__main__":. pass to pusta instrukcja: klasa i funkcja nie mogą mieć pustego ciała, za chwilę je wypełnisz.

    konsola/main.py Python nowy plik
    class Table:
        pass
    
    
    def main():
        pass
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
  3. Krok 3: Dwa pola prywatne w konstruktorze

    Kryteria: ,

    R.2.1 W programie zdefiniowana klasa z dwoma polami private, jedno jest typu tablicowego, drugie typu całkowitego, w programie głównym jest powołany obiekt klasy z liczbą > 20 (W języku Python, dla pola typu private, dwa podkreślniki przed nazwą )

    R.1.5 Zastosowane typy pól pasujące do problemu, np. tablica przechowuje liczby całkowite, rozmiar i wartość szukana są liczbami całkowitymi, średnia jest liczbą. W przypadku Python, typ wynika z przypisanych danych

    ok. 3 min
    Polecenie z arkusza

    Pola klasy: Tablica liczb całkowitych (ma być tradycyjną tablicą, a w Python listą). Liczba elementów tablicy zapisana jako liczba całkowita. Oba pola są dostępne tylko w tej klasie oraz niedostępne dla klas potomnych.

    W Pythonie pola obiektu tworzy się w konstruktorze __init__ przez przypisanie do self - self to tworzony obiekt:

    • self.__numbers = [] - lista na liczby (arkusz mówi wprost: w Pythonie listą),
    • self.__count = size - liczba elementów, liczba całkowita (typ wynika z przypisanej wartości, R.1.5),
    • dwa podkreślniki na początku nazwy to w Pythonie pole „prywatne”: Python zmienia jego nazwę (na _Table__numbers), więc spoza klasy i z klas potomnych nie da się do niego sięgnąć zwykłą nazwą. Klucz wymienia ten zapis wprost (R.2.1). Jeden podkreślnik to tylko umowa „nie ruszaj”, odpowiednik protected.
    Typowe błędy

    self._numbers z jednym podkreślnikiem nie spełnia wymagania „niedostępne dla klas potomnych” - klasa potomna widzi takie pole bez przeszkód. Klucz (R.2.1) wymaga dwóch.

    konsola/main.py Python zmienione: 3 linie
    class Table:
        def __init__(self, size):
            self.__count = size
            self.__numbers = []
    
    
    def main():
        pass
    
    
    if __name__ == "__main__":
        main()

    Wynik programu bez zmian - taki jak po kroku 2.

  4. Krok 4: Konstruktor z losowaniem liczb 1-1000

    Kryteria: , ,

    R.2.2 Konstruktor klasy ma jeden parametr i wypełnia tablicę wartościami losowymi od 1 do 1000 (tolerancja +/-1 ) oraz ustawia pole liczby elementów tablicy wartością parametru konstruktora

    R.2.4 Przynajmniej jedna pętla działająca na tablicy jest ograniczona do liczby wypełnionych elementów w tablicy

    R.1.2 Kod zapisany z wcięciami dla zagnieżdżeń bloków

    ok. 3 min
    Polecenie z arkusza

    Konstruktor klasy: Przyjmuje jako argument rozmiar tablicy. Ustawia wartość pola liczby elementów tablicy na wartość argumentu. Wypełnia tablicę, będącą polem klasy, pseudolosowymi liczbami całkowitymi z zakresu od 1 do 1000.

    • konstruktor przyjmuje jeden argument size i zapisuje go w polu liczby elementów,
    • random.randint(1, 1000) - liczba pseudolosowa od 1 do 1000; w Pythonie obie granice wchodzą do zakresu (moduł random importujesz na górze pliku),
    • append dokleja liczbę na koniec listy, a pętla wykonuje się self.__count razy - wszystkie operacje mają być ograniczone tym polem (R.2.4).

    Wcięcie o 4 spacje wyznacza, co należy do pętli, a co do konstruktora (R.1.2).

    Typowe błędy

    random.randrange(1, 1000) nigdy nie wylosuje 1000 - w randrange górna granica nie wchodzi. Klucz toleruje różnicę o 1 (R.2.2), ale warto znać różnicę.

    konsola/main.py Python zmienione: 5 linii
    import random
    
    
    class Table:
        def __init__(self, size):
            self.__count = size
            self.__numbers = []
            for _ in range(self.__count):
                self.__numbers.append(random.randint(1, 1000))
    
    
    def main():
        pass
    
    
    if __name__ == "__main__":
        main()

    Wynik programu bez zmian - taki jak po kroku 2.

  5. Krok 5: Metoda display i pierwszy test w main

    Kryteria: , , ,

    R.2.3 Metoda szukająca pobiera argument typu liczbowego. Metody są typu: wyświetlająca - bez typu (np. void) wyszukująca - typ całkowity wyświetlająca nieparzyste - typ całkowity licząca średnią - typ liczbowy (wystarczy, że zastosowane dla trzech metod)

    R.2.7 Program wyświetla wszystkie elementy tablicy wg. wzoru "<index_tablicy>: <wartość>"

    R.1.7 Program podejmuje komunikację z użytkownikiem, wyświetlane komunikaty są znaczące

    R.1.3 Użyte znaczące polskie lub angielskie nazewnictwo metod

    ok. 3 min
    Polecenie z arkusza

    Metoda wyświetlająca wszystkie elementy tablicy w postaci „<index_tablicy>: <wartość>”. Nie zwraca wartości. Wszystkie metody są dostępne poza klasą. Program główny tworzy obiekt klasy z rozmiarem tablicy większym od 20.

    • metoda bez podkreślnika na początku nazwy jest publiczna - dostępna poza klasą,
    • -> None to adnotacja typu: metoda nic nie zwraca. Python adnotacji nie wymaga, ale dopisujesz je w każdej metodzie - wtedy typ metody, o który pyta klucz (R.2.3), widać w kodzie, a PyCharm podkreśla niezgodności,
    • f-napis f"{index}: {self.__numbers[index]}" - indeks i wartość jak we wzorze z arkusza.

    W main tworzysz obiekt z tablicą 50 elementów (więcej niż 20) i wywołujesz metodę. Podgląd to wynik prawdziwego uruchomienia - każde losuje inne liczby.

    konsola/main.py Python zmienione: 7 linii
    import random
    
    
    class Table:
        def __init__(self, size):
            self.__count = size
            self.__numbers = []
            for _ in range(self.__count):
                self.__numbers.append(random.randint(1, 1000))
    
        def display(self) -> None:
            for index in range(self.__count):
                print(f"{index}: {self.__numbers[index]}")
    
    
    def main():
        table = Table(50)
        print("Elementy tablicy:")
        table.display()
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
    Elementy tablicy:
    0: 611
    1: 142
    2: 717
    3: 534
    4: 557
    5: 825
    6: 776
    7: 231
    8: 264
    9: 902
    10: 10
    11: 838
    12: 217
    13: 796
    14: 497
    15: 790
    16: 523
    17: 180
    18: 325
    19: 126
    20: 192
    21: 480
    22: 697
    23: 669
    24: 764
    25: 77
    26: 114
    27: 544
    28: 803
    29: 388
    30: 177
    31: 855
    32: 271
    33: 914
    34: 756
    35: 828
    36: 948
    37: 943
    38: 447
    39: 768
    40: 683
    41: 661
    42: 421
    43: 573
    44: 141
    45: 630
    46: 468
    47: 644
    48: 999
    49: 161
  6. Krok 6: Metoda find: pierwsze wystąpienie

    Kryteria: ,

    R.2.3 Metoda szukająca pobiera argument typu liczbowego. Metody są typu: wyświetlająca - bez typu (np. void) wyszukująca - typ całkowity wyświetlająca nieparzyste - typ całkowity licząca średnią - typ liczbowy (wystarczy, że zastosowane dla trzech metod)

    R.2.5 W metodzie wyszukującej, gdy odnaleziono pierwsze wystąpienie wartości szukanej w tablicy, działanie pętli jest zakończone lub została zastosowana wbudowana metoda lub funkcja

    ok. 3 min
    Polecenie z arkusza

    Metoda wyszukująca pierwsze wystąpienie wartości, przekazanej jako argument. Metoda zwraca indeks szukanego elementu lub liczbę -1, gdy elementu nie znaleziono.

    Pętla porównuje kolejne elementy z szukaną wartością. Przy pierwszym trafieniu return index od razu kończy metodę - pętla nie szuka dalej, więc zwracasz indeks pierwszego wystąpienia (R.2.5). Jeśli pętla dobiegnie końca, zwracasz -1. Adnotacje mówią, że argument i wynik to liczby całkowite (value: int, -> int, R.2.3).

    Typowe błędy

    self.__numbers.index(value) też znajduje pierwsze wystąpienie, ale gdy wartości nie ma, zgłasza błąd ValueError zamiast zwrócić -1 - trzeba by go obsłużyć osobno.

    konsola/main.py Python zmienione: 6 linii
    import random
    
    
    class Table:
        def __init__(self, size):
            self.__count = size
            self.__numbers = []
            for _ in range(self.__count):
                self.__numbers.append(random.randint(1, 1000))
    
        def display(self) -> None:
            for index in range(self.__count):
                print(f"{index}: {self.__numbers[index]}")
    
        def find(self, value: int) -> int:
            for index in range(self.__count):
                if self.__numbers[index] == value:
                    return index
            return -1
    
    
    def main():
        table = Table(50)
        print("Elementy tablicy:")
        table.display()
    
    
    if __name__ == "__main__":
        main()

    Wynik programu bez zmian - taki jak po kroku 5.

  7. Krok 7: Wyszukiwanie w programie głównym

    Kryteria: , ,

    R.2.8 Program wyszukuje liczbę w tablicy i wyświetla jej indeks, gdy nie odnaleziono liczby nic nie wyświetla

    R.1.7 Program podejmuje komunikację z użytkownikiem, wyświetlane komunikaty są znaczące

    R.1.4 Użyte polskie lub angielskie nazewnictwo zmiennych i obiektów. Nazewnictwo jest znaczące. Wyjątkami od reguły są zmienne: bufor, tmp, iteratory pętli. Kryterium nie jest spełnione tylko wtedy, gdy nazwy zmiennych nic nie znaczą, np.: x, foo (Uwaga! klasa i pole tablicowe klasy może mieć nazwę ogólną np tab, tablica itp )

    ok. 3 min
    Polecenie z arkusza

    Program główny sprawdza działanie metody wyszukującej: jeżeli wartość została wyszukana, wyświetlany jest w programie głównym komunikat z wartością indeksu wyszukanej. W przeciwnym wypadku nic nie jest wyświetlane (taka sytuacja ma miejsce na obrazie 1).

    Liczbę podaje użytkownik: input(...) zwraca tekst, a int(...) zamienia go na liczbę całkowitą. Komunikat wypisujesz tylko wtedy, gdy metoda nie zwróciła -1 - przy braku liczby program nic nie mówi, jak na obrazie 1 (R.2.8).

    Podgląd to dwa prawdziwe uruchomienia: najpierw wpisana liczba, która jest w tablicy (spod indeksu 7 - wynik pokazuje indeks jej pierwszego wystąpienia), potem 1001 - spoza zakresu, więc bez komunikatu.

    konsola/main.py Python zmienione: 5 linii
    import random
    
    
    class Table:
        def __init__(self, size):
            self.__count = size
            self.__numbers = []
            for _ in range(self.__count):
                self.__numbers.append(random.randint(1, 1000))
    
        def display(self) -> None:
            for index in range(self.__count):
                print(f"{index}: {self.__numbers[index]}")
    
        def find(self, value: int) -> int:
            for index in range(self.__count):
                if self.__numbers[index] == value:
                    return index
            return -1
    
    
    def main():
        table = Table(50)
        print("Elementy tablicy:")
        table.display()
    
        searched = int(input("Podaj liczbę do wyszukania: "))
        index = table.find(searched)
        if index != -1:
            print(f"Liczba {searched} jest pod indeksem {index}")
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
    Elementy tablicy:
    0: 716
    1: 53
    2: 50
    3: 239
    4: 652
    5: 350
    6: 187
    7: 994
    8: 567
    9: 33
    10: 49
    11: 459
    12: 759
    13: 832
    14: 515
    15: 817
    16: 168
    17: 880
    18: 778
    19: 528
    20: 499
    21: 39
    22: 99
    23: 787
    24: 896
    25: 860
    26: 251
    27: 288
    28: 472
    29: 793
    30: 579
    31: 701
    32: 908
    33: 827
    34: 781
    35: 144
    36: 483
    37: 847
    38: 380
    39: 860
    40: 8
    41: 745
    42: 751
    43: 627
    44: 967
    45: 282
    46: 899
    47: 947
    48: 120
    49: 161
    Podaj liczbę do wyszukania: 994
    Liczba 994 jest pod indeksem 7
    
    > python main.py
    Elementy tablicy:
    0: 147
    1: 251
    2: 834
    3: 670
    4: 837
    5: 138
    6: 503
    7: 735
    8: 541
    9: 824
    10: 362
    11: 152
    12: 720
    13: 838
    14: 230
    15: 72
    16: 276
    17: 611
    18: 713
    19: 241
    20: 852
    21: 137
    22: 693
    23: 973
    24: 932
    25: 815
    26: 322
    27: 562
    28: 63
    29: 715
    30: 436
    31: 536
    32: 489
    33: 362
    34: 699
    35: 960
    36: 63
    37: 27
    38: 88
    39: 589
    40: 6
    41: 598
    42: 44
    43: 514
    44: 765
    45: 242
    46: 710
    47: 798
    48: 679
    49: 86
    Podaj liczbę do wyszukania: 1001
  8. Krok 8: Liczby nieparzyste i ich liczba

    Kryteria: ,

    R.2.3 Metoda szukająca pobiera argument typu liczbowego. Metody są typu: wyświetlająca - bez typu (np. void) wyszukująca - typ całkowity wyświetlająca nieparzyste - typ całkowity licząca średnią - typ liczbowy (wystarczy, że zastosowane dla trzech metod)

    R.2.9 Program wyświetla na ekranie liczby nieparzyste oraz liczbę ich wystąpień

    ok. 3 min
    Polecenie z arkusza

    Metoda wyświetlająca wszystkie wartości nieparzyste z tablicy i zwracająca ich liczbę. Program główny wyświetla liczby nieparzyste oraz ich ilość z odpowiednim komentarzem.

    Liczba jest nieparzysta, gdy reszta z dzielenia przez 2 (%) jest różna od zera. Metoda wypisuje każdą taką liczbę i zwiększa licznik odd_count, a na końcu go zwraca (-> int). Program główny poprzedza listę nagłówkiem i wypisuje zwróconą liczbę z komentarzem (R.2.9).

    konsola/main.py Python zmienione: 12 linii
    import random
    
    
    class Table:
        def __init__(self, size):
            self.__count = size
            self.__numbers = []
            for _ in range(self.__count):
                self.__numbers.append(random.randint(1, 1000))
    
        def display(self) -> None:
            for index in range(self.__count):
                print(f"{index}: {self.__numbers[index]}")
    
        def find(self, value: int) -> int:
            for index in range(self.__count):
                if self.__numbers[index] == value:
                    return index
            return -1
    
        def display_odd(self) -> int:
            odd_count = 0
            for index in range(self.__count):
                if self.__numbers[index] % 2 != 0:
                    print(self.__numbers[index])
                    odd_count += 1
            return odd_count
    
    
    def main():
        table = Table(50)
        print("Elementy tablicy:")
        table.display()
    
        searched = int(input("Podaj liczbę do wyszukania: "))
        index = table.find(searched)
        if index != -1:
            print(f"Liczba {searched} jest pod indeksem {index}")
    
        print("Liczby nieparzyste:")
        odd_count = table.display_odd()
        print(f"Razem nieparzystych: {odd_count}")
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
    Elementy tablicy:
    0: 223
    1: 767
    2: 341
    3: 29
    4: 43
    5: 866
    6: 2
    7: 111
    8: 522
    9: 761
    10: 168
    11: 761
    12: 357
    13: 382
    14: 325
    15: 240
    16: 380
    17: 677
    18: 406
    19: 650
    20: 214
    21: 27
    22: 91
    23: 626
    24: 511
    25: 847
    26: 396
    27: 586
    28: 207
    29: 589
    30: 129
    31: 964
    32: 618
    33: 583
    34: 8
    35: 266
    36: 789
    37: 872
    38: 931
    39: 261
    40: 239
    41: 139
    42: 688
    43: 493
    44: 665
    45: 788
    46: 201
    47: 212
    48: 124
    49: 54
    Podaj liczbę do wyszukania: 1001
    Liczby nieparzyste:
    223
    767
    341
    29
    43
    111
    761
    761
    357
    325
    677
    27
    91
    511
    847
    207
    589
    129
    583
    789
    931
    261
    239
    139
    493
    665
    201
    Razem nieparzystych: 27
  9. Krok 9: Średnia arytmetyczna

    Kryteria: , , ,

    R.2.3 Metoda szukająca pobiera argument typu liczbowego. Metody są typu: wyświetlająca - bez typu (np. void) wyszukująca - typ całkowity wyświetlająca nieparzyste - typ całkowity licząca średnią - typ liczbowy (wystarczy, że zastosowane dla trzech metod)

    R.2.10 Program liczy średnią arytmetyczną (suma / rozmiar) oraz wyświetla ją na ekranie

    R.1.5 Zastosowane typy pól pasujące do problemu, np. tablica przechowuje liczby całkowite, rozmiar i wartość szukana są liczbami całkowitymi, średnia jest liczbą. W przypadku Python, typ wynika z przypisanych danych

    R.1.1 Kod źródłowy zapisany w sposób czytelny: instrukcje w osobnych liniach, stosowane spacje pomiędzy operatorami, konsekwentnie stosowana wybrana konwencja dla nawiasów klamrowych instrukcji blokowej

    ok. 3 min
    Polecenie z arkusza

    Metoda licząca średnią arytmetyczną wartości w tablicy i zwracająca tą wartość. Program główny wyświetla wartość średniej.

    Sumujesz elementy w zmiennej total i dzielisz przez liczbę elementów. W Pythonie operator / daje zawsze wynik rzeczywisty (float) - także dla dwóch liczb całkowitych - więc nie trzeba rzutowania jak w C#. Metoda zwraca float (R.2.10). W main format :.2f pokazuje dwa miejsca po kropce dziesiętnej.

    Typowe błędy

    total // self.__count (podwójny ukośnik) to dzielenie całkowite - średnia 525,73 wyjdzie jako 525. W Pythonie zwykłe / liczy dokładnie.

    konsola/main.py Python zmienione: 9 linii
    import random
    
    
    class Table:
        def __init__(self, size):
            self.__count = size
            self.__numbers = []
            for _ in range(self.__count):
                self.__numbers.append(random.randint(1, 1000))
    
        def display(self) -> None:
            for index in range(self.__count):
                print(f"{index}: {self.__numbers[index]}")
    
        def find(self, value: int) -> int:
            for index in range(self.__count):
                if self.__numbers[index] == value:
                    return index
            return -1
    
        def display_odd(self) -> int:
            odd_count = 0
            for index in range(self.__count):
                if self.__numbers[index] % 2 != 0:
                    print(self.__numbers[index])
                    odd_count += 1
            return odd_count
    
        def average(self) -> float:
            total = 0
            for index in range(self.__count):
                total += self.__numbers[index]
            return total / self.__count
    
    
    def main():
        table = Table(50)
        print("Elementy tablicy:")
        table.display()
    
        searched = int(input("Podaj liczbę do wyszukania: "))
        index = table.find(searched)
        if index != -1:
            print(f"Liczba {searched} jest pod indeksem {index}")
    
        print("Liczby nieparzyste:")
        odd_count = table.display_odd()
        print(f"Razem nieparzystych: {odd_count}")
    
        average = table.average()
        print(f"Średnia wszystkich elementów: {average:.2f}")
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
    Elementy tablicy:
    0: 70
    1: 673
    2: 969
    3: 609
    4: 441
    5: 743
    6: 518
    7: 680
    8: 883
    9: 559
    10: 811
    11: 142
    12: 237
    13: 927
    14: 6
    15: 208
    16: 454
    17: 972
    18: 220
    19: 455
    20: 74
    21: 68
    22: 321
    23: 216
    24: 297
    25: 467
    26: 779
    27: 138
    28: 60
    29: 463
    30: 712
    31: 124
    32: 729
    33: 544
    34: 688
    35: 961
    36: 497
    37: 438
    38: 900
    39: 242
    40: 425
    41: 519
    42: 41
    43: 969
    44: 328
    45: 161
    46: 243
    47: 93
    48: 144
    49: 898
    Podaj liczbę do wyszukania: 680
    Liczba 680 jest pod indeksem 7
    Liczby nieparzyste:
    673
    969
    609
    441
    743
    883
    559
    811
    237
    927
    455
    321
    297
    467
    779
    463
    729
    961
    497
    425
    519
    41
    969
    161
    243
    93
    Razem nieparzystych: 26
    Średnia wszystkich elementów: 462.32
  10. Krok 10: Folder konsolowa: archiwum i źródło

    ok. 2 min
    Polecenie z arkusza

    Kod aplikacji przygotuj do nagrania na płytę. W folderze konsolowa powinno znaleźć się archiwum całego projektu o nazwie konsola.zip, plik z kodem źródłowym programu oraz plik wykonywalny, jeżeli istnieje.

    Folder projektu pakujesz do konsola.zip, obok kopiujesz main.py. Python nie tworzy pliku wykonywalnego. Archiwum robisz na samym końcu pracy, po dopisaniu dokumentacji z części III.

    Wynik programu bez zmian - taki jak po kroku 9.

  11. Etap 3: Aplikacja webowa

    Kroki 11-23 · po tym etapie masz 24 z 35 kryteriów (pkt)

    Krok 11: Nowy projekt Angular

    ok. 5 min
    Polecenie z arkusza

    Z zastosowaniem dostępnego na stanowisku egzaminacyjnym frameworka Angular lub biblioteki React.js wykonaj aplikację internetową typu front-end realizującą funkcję kategoryzacji zdjęć w galerii.

    Wybierasz Angular. Projekt zakładasz w terminalu narzędziem Angular CLI:

    npx @angular/cli@latest new web
    cd web
    npx ng serve
    

    (z globalnie zainstalowanym CLI wystarczy ng new web). Kreator pyta o format stylów - CSS - i o renderowanie po stronie serwera (SSR) - nie. ng serve uruchamia serwer pod http://localhost:4200.

    Komponent główny App to trzy pliki w src/app: app.ts (klasa z danymi i metodami), app.html (szablon) i app.css (style tylko tego komponentu). Dekorator @Component łączy je w całość. (W Angularze 19 i starszych pliki nazywają się app.component.*.)

    web/src/app/app.ts TypeScript nowy plik
    import { Component, signal } from '@angular/core';
    import { RouterOutlet } from '@angular/router';
    
    @Component({
      imports: [RouterOutlet],
      selector: 'app-root',
      styleUrl: './app.css',
      templateUrl: './app.html',
    })
    export class App {
      protected readonly title = signal('web');
    }

    Wynik programu bez zmian - taki jak po kroku 9.

  12. Krok 12: Biblioteka Bootstrap w projekcie

    Kryteria:

    R.3.2 Do projektu dołączona biblioteka Bootstrap oraz sformatowany obraz: margin: 5px; oraz zaokrąglone rogi (border-radius lub klasa rounded )

    ok. 2 min
    Polecenie z arkusza

    Przyciski oraz pola switch są stylowane zgodnie z przykładami w tabeli 1. Angular: To use Bootstrap add to styles.css: @import "~bootstrap/dist/css/bootstrap.css";

    npm install bootstrap w folderze web, a potem import w src/styles.css - globalnym arkuszu stylów całej aplikacji (R.3.2). Tabela 1 podaje ścieżkę z tyldą (~bootstrap/...) - tak działał starszy Angular. Obecny Angular buduje projekt narzędziem esbuild i tyldy nie rozumie: ng build kończy się błędem „Could not resolve "~bootstrap/..."” (sprawdziłem w Angular 22). Piszesz więc ścieżkę bez tyldy - zadziała też w starszym Angularze.

    Typowe błędy

    Import z tyldą skopiowany z tabeli 1 w nowym Angularze: aplikacja się nie zbuduje, a bez działającej aplikacji przepadają kryteria R.3.8 - R.3.10.

    web/src/styles.css CSS nowy plik
    @import "bootstrap/dist/css/bootstrap.css";

    Wynik programu bez zmian - taki jak po kroku 9.

  13. Krok 13: Zdjęcia w folderze public/assets

    ok. 2 min
    Polecenie z arkusza

    Do utworzenia aplikacji należy wykorzystać zdjęcia oraz plik dane.txt wypakowane z archiwum. Obrazy należy umieścić w folderze assets (egzamin/src/assets lub egzamin/public/assets).

    Rozpakowujesz pliki3.zip (hasło K@tegorie)) i kopiujesz dwanaście zdjęć do web/public/assets. Folder public obecny Angular kopiuje bez zmian do gotowej aplikacji, więc zdjęcie jest dostępne pod adresem assets/obraz1.jpg. (Starszy Angular używał w tym celu folderu src/assets - arkusz wymienia obie możliwości.)

    Wynik programu bez zmian - taki jak po kroku 9.

  14. Krok 14: Komponent App z nagłówkiem

    Kryteria:

    R.3.1 Zapisany w aplikacji jeden komponent, który zawiera nagłówek h1, trzy pola switch lub checkbox zaznaczone, przynajmniej jeden obraz, nagłówek h4 i przycisk "Pobierz" oraz tablicę obiektów z pliku dane.txt

    ok. 2 min
    Polecenie z arkusza

    Aplikacja składa się z jednego komponentu. Komponent składa się z: Nagłówka pierwszego stopnia o treści: „Kategorie zdjęć”.

    Zastępujesz całą zawartość app.html (szablon z kreatora to strona powitalna Angulara) nagłówkiem <h1> (R.3.1). W app.ts usuwasz RouterOutlet i przykładowe pole title - zostaje pusta klasa komponentu, którą za chwilę wypełnisz.

    web/src/app/app.html HTML nowy plik
    <h1>Kategorie zdjęć</h1>
    web/src/app/app.ts TypeScript zmienione: 3 linie
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      imports: [],
      templateUrl: './app.html',
      styleUrl: './app.css'
    })
    export class App {
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  15. Krok 15: Tablica zdjęć z pliku dane.txt w polu klasy

    Kryteria: ,

    R.3.1 Zapisany w aplikacji jeden komponent, który zawiera nagłówek h1, trzy pola switch lub checkbox zaznaczone, przynajmniej jeden obraz, nagłówek h4 i przycisk "Pobierz" oraz tablicę obiektów z pliku dane.txt

    R.1.5 Zastosowane typy pól pasujące do problemu, np. tablica przechowuje liczby całkowite, rozmiar i wartość szukana są liczbami całkowitymi, średnia jest liczbą. W przypadku Python, typ wynika z przypisanych danych

    ok. 3 min
    Polecenie z arkusza

    Dokument dane.txt zawiera listę obiektów zdjęć, którą należy skopiować jako elementy tablicy. Każdy obiekt zdjęcia zawiera pola: id, alt (tekst alternatywny dla zdjęcia), filename (nazwa pliku ze zdjęciem), category (1 dla kategorii kwiaty, 2 zwierzęta, 3 samochody), downloads (liczba pobrań zdjęcia).

    Kopiujesz obiekty z dane.txt do tablicy photos - pola klasy komponentu (R.3.1). Szablon widzi wszystkie pola klasy, więc tablicę wyświetlisz bezpośrednio z niego.

    TypeScript pozwala opisać kształt obiektu interfejsem: interface Photo wymienia pola zdjęcia z ich typami (number, string), a zapis photos: Photo[] mówi, że to tablica takich obiektów. Interfejs nie jest konieczny, ale literówka w nazwie pola (np. downlods) od razu kończy się błędem kompilacji zamiast pustym miejscem na stronie (R.1.5). Dane przepisujesz tak, jak są w pliku (także „Rotwailer” - to dane z arkusza). Na stronie nic jeszcze nie widać.

    web/src/app/app.ts TypeScript zmienione: 22 linie
    import { Component } from '@angular/core';
    
    interface Photo {
      id: number;
      alt: string;
      filename: string;
      category: number;
      downloads: number;
    }
    
    @Component({
      selector: 'app-root',
      imports: [],
      templateUrl: './app.html',
      styleUrl: './app.css'
    })
    export class App {
      photos: Photo[] = [
        { id: 0, alt: 'Mak', filename: 'obraz1.jpg', category: 1, downloads: 35 },
        { id: 1, alt: 'Bukiet', filename: 'obraz2.jpg', category: 1, downloads: 43 },
        { id: 2, alt: 'Dalmatyńczyk', filename: 'obraz3.jpg', category: 2, downloads: 2 },
        { id: 3, alt: 'Świnka morska', filename: 'obraz4.jpg', category: 2, downloads: 53 },
        { id: 4, alt: 'Rotwailer', filename: 'obraz5.jpg', category: 2, downloads: 43 },
        { id: 5, alt: 'Audi', filename: 'obraz6.jpg', category: 3, downloads: 11 },
        { id: 6, alt: 'kotki', filename: 'obraz7.jpg', category: 2, downloads: 22 },
        { id: 7, alt: 'Róża', filename: 'obraz8.jpg', category: 1, downloads: 33 },
        { id: 8, alt: 'Świnka morska', filename: 'obraz9.jpg', category: 2, downloads: 123 },
        { id: 9, alt: 'Foksterier', filename: 'obraz10.jpg', category: 2, downloads: 22 },
        { id: 10, alt: 'Szczeniak', filename: 'obraz11.jpg', category: 2, downloads: 12 },
        { id: 11, alt: 'Garbus', filename: 'obraz12.jpg', category: 3, downloads: 321 },
      ];
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  16. Krok 16: Pierwszy przełącznik: Kwiaty, domyślnie włączony

    Kryteria:

    R.3.3 Przycisk ma przypisaną klasę btn btn-success oraz przynajmniej jedno pole switch zdefiniowane za pomocą biblioteki Bootstrap jako blok formatowany stylami form-check form-switch (form-check-inline opcjonalnie ) i złożony z kontrolki input i przypisanej do niej etykiety label, np.: <div class="form-check form-switch"> <input class="form-check-input" type="checkbox" id="chk3" ...> <label class="form-check-label" for="chk3">Samochody</label> </div> W React.js zastosowane className zamiast class oraz domknięte wszystkie znaczniki

    ok. 3 min
    Polecenie z arkusza

    Komponent składa się z trzech pól switch (checkbox) domyślnie włączonych, o etykietach: Kwiaty, Zwierzęta, Samochody.

    Stan przełączników to pole visible: obiekt, w którym kluczem jest numer kategorii, a wartością true/false - na start wszystkie włączone. Typ Record<number, boolean> to w TypeScripcie właśnie „słownik: liczba → wartość logiczna”.

    Przełącznik przenosisz z tabeli 1: div z klasami form-check form-switch (plus form-check-inline, żeby przełączniki stały w jednym wierszu, jak na obrazie 2), w nim input typu checkbox i etykieta powiązana atrybutem for (R.3.3). W szablonie Angulara HTML zostaje HTML-em - class i for bez zmian. [checked]="visible[1]" wiąże właściwość checked z wartością z klasy: nawiasy kwadratowe oznaczają, że to wyrażenie, a nie tekst.

    web/src/app/app.html HTML zmienione: 4 linie
    <h1>Kategorie zdjęć</h1>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="flowers" [checked]="visible[1]">
      <label class="form-check-label" for="flowers">Kwiaty</label>
    </div>
    web/src/app/app.ts TypeScript zmienione: 1 linia
    import { Component } from '@angular/core';
    
    interface Photo {
      id: number;
      alt: string;
      filename: string;
      category: number;
      downloads: number;
    }
    
    @Component({
      selector: 'app-root',
      imports: [],
      templateUrl: './app.html',
      styleUrl: './app.css'
    })
    export class App {
      photos: Photo[] = [
        { id: 0, alt: 'Mak', filename: 'obraz1.jpg', category: 1, downloads: 35 },
        { id: 1, alt: 'Bukiet', filename: 'obraz2.jpg', category: 1, downloads: 43 },
        { id: 2, alt: 'Dalmatyńczyk', filename: 'obraz3.jpg', category: 2, downloads: 2 },
        { id: 3, alt: 'Świnka morska', filename: 'obraz4.jpg', category: 2, downloads: 53 },
        { id: 4, alt: 'Rotwailer', filename: 'obraz5.jpg', category: 2, downloads: 43 },
        { id: 5, alt: 'Audi', filename: 'obraz6.jpg', category: 3, downloads: 11 },
        { id: 6, alt: 'kotki', filename: 'obraz7.jpg', category: 2, downloads: 22 },
        { id: 7, alt: 'Róża', filename: 'obraz8.jpg', category: 1, downloads: 33 },
        { id: 8, alt: 'Świnka morska', filename: 'obraz9.jpg', category: 2, downloads: 123 },
        { id: 9, alt: 'Foksterier', filename: 'obraz10.jpg', category: 2, downloads: 22 },
        { id: 10, alt: 'Szczeniak', filename: 'obraz11.jpg', category: 2, downloads: 12 },
        { id: 11, alt: 'Garbus', filename: 'obraz12.jpg', category: 3, downloads: 321 },
      ];
      visible: Record<number, boolean> = { 1: true, 2: true, 3: true };
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  17. Krok 17: Przełączniki Zwierzęta i Samochody

    Kryteria:

    R.3.1 Zapisany w aplikacji jeden komponent, który zawiera nagłówek h1, trzy pola switch lub checkbox zaznaczone, przynajmniej jeden obraz, nagłówek h4 i przycisk "Pobierz" oraz tablicę obiektów z pliku dane.txt

    ok. 2 min
    Polecenie z arkusza

    Trzech pól switch (checkbox) domyślnie włączonych, o etykietach: Kwiaty, Zwierzęta, Samochody.

    Dwa kolejne przełączniki według tego samego wzoru, z innymi identyfikatorami (animals, cars) i kategoriami 2 i 3 (R.3.1). Identyfikatory muszą być różne - inaczej etykiety wskazywałyby pierwszy przełącznik.

    web/src/app/app.html HTML zmienione: 8 linii
    <h1>Kategorie zdjęć</h1>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="flowers" [checked]="visible[1]">
      <label class="form-check-label" for="flowers">Kwiaty</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="animals" [checked]="visible[2]">
      <label class="form-check-label" for="animals">Zwierzęta</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="cars" [checked]="visible[3]">
      <label class="form-check-label" for="cars">Samochody</label>
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  18. Krok 18: Zmiana stanu przełącznika: toggleCategory

    Kryteria:

    R.3.7 W kodzie zdefiniowana przynajmniej jedna funkcja (lub operacja), która jest uruchamiana po zmianie stanu switch np. (change)="flowersChanged() lub onChange={this.flowersChanged()}

    ok. 3 min
    Polecenie z arkusza

    Wyświetlane są tylko zdjęcia z kategorii dla której jest włączone pole switch.

    Metoda toggleCategory(category) odwraca stan jednej kategorii (! to zaprzeczenie). Każdy przełącznik wywołuje ją zdarzeniem (change)="toggleCategory(1)" - nawiasy okrągłe to powiązanie zdarzenia; klucz podaje dokładnie taki zapis jako przykład (R.3.7). Angular po obsłużeniu zdarzenia sam odświeża widok.

    W Angularze można zmienić pole obiektu wprost (this.visible[category] = ...) - wykrywanie zmian to zauważy. W React trzeba było budować nowy obiekt stanu.

    Typowe błędy

    onchange="toggleCategory(1)" w stylu HTML - klucz wymienia przypisywanie metod do zdarzeń w ten sposób jako powód niezaliczenia CAŁEGO rezultatu 3. W Angularze: (change)="...".

    web/src/app/app.html HTML zmienione: 6 linii
    <h1>Kategorie zdjęć</h1>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="flowers" [checked]="visible[1]"
             (change)="toggleCategory(1)">
      <label class="form-check-label" for="flowers">Kwiaty</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="animals" [checked]="visible[2]"
             (change)="toggleCategory(2)">
      <label class="form-check-label" for="animals">Zwierzęta</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="cars" [checked]="visible[3]"
             (change)="toggleCategory(3)">
      <label class="form-check-label" for="cars">Samochody</label>
    </div>
    web/src/app/app.ts TypeScript zmienione: 4 linie
    import { Component } from '@angular/core';
    
    interface Photo {
      id: number;
      alt: string;
      filename: string;
      category: number;
      downloads: number;
    }
    
    @Component({
      selector: 'app-root',
      imports: [],
      templateUrl: './app.html',
      styleUrl: './app.css'
    })
    export class App {
      photos: Photo[] = [
        { id: 0, alt: 'Mak', filename: 'obraz1.jpg', category: 1, downloads: 35 },
        { id: 1, alt: 'Bukiet', filename: 'obraz2.jpg', category: 1, downloads: 43 },
        { id: 2, alt: 'Dalmatyńczyk', filename: 'obraz3.jpg', category: 2, downloads: 2 },
        { id: 3, alt: 'Świnka morska', filename: 'obraz4.jpg', category: 2, downloads: 53 },
        { id: 4, alt: 'Rotwailer', filename: 'obraz5.jpg', category: 2, downloads: 43 },
        { id: 5, alt: 'Audi', filename: 'obraz6.jpg', category: 3, downloads: 11 },
        { id: 6, alt: 'kotki', filename: 'obraz7.jpg', category: 2, downloads: 22 },
        { id: 7, alt: 'Róża', filename: 'obraz8.jpg', category: 1, downloads: 33 },
        { id: 8, alt: 'Świnka morska', filename: 'obraz9.jpg', category: 2, downloads: 123 },
        { id: 9, alt: 'Foksterier', filename: 'obraz10.jpg', category: 2, downloads: 22 },
        { id: 10, alt: 'Szczeniak', filename: 'obraz11.jpg', category: 2, downloads: 12 },
        { id: 11, alt: 'Garbus', filename: 'obraz12.jpg', category: 3, downloads: 321 },
      ];
      visible: Record<number, boolean> = { 1: true, 2: true, 3: true };
    
      toggleCategory(category: number): void {
        this.visible[category] = !this.visible[category];
      }
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  19. Krok 19: Bloki zdjęć w pętli @for

    Kryteria: , ,

    R.3.1 Zapisany w aplikacji jeden komponent, który zawiera nagłówek h1, trzy pola switch lub checkbox zaznaczone, przynajmniej jeden obraz, nagłówek h4 i przycisk "Pobierz" oraz tablicę obiektów z pliku dane.txt

    R.3.3 Przycisk ma przypisaną klasę btn btn-success oraz przynajmniej jedno pole switch zdefiniowane za pomocą biblioteki Bootstrap jako blok formatowany stylami form-check form-switch (form-check-inline opcjonalnie ) i złożony z kontrolki input i przypisanej do niej etykiety label, np.: <div class="form-check form-switch"> <input class="form-check-input" type="checkbox" id="chk3" ...> <label class="form-check-label" for="chk3">Samochody</label> </div> W React.js zastosowane className zamiast class oraz domknięte wszystkie znaczniki

    R.3.4 Do generowania obrazów zastosowana pętla działająca na elementach galerii np. Angular: *ngFor = "let zdjecie of galeria" React.js: {this.state.galeria.map(zdjecie => ... (lub bez this.state ) oraz pętla generuje obraz i nagłówek h4 z odwołaniem do pól obiektu np. <img src="assets/{{ zdjecie.filename }}" alt="{{ zdjecie.alt }}"> <h4>Pobrań: {{ zdjecie.downloads }}</h4> W React.js pojedyncze nawiasy klamrowe (uwaga! może poprawnie działać również bez nazwy katalogu assets )

    ok. 4 min
    Polecenie z arkusza

    Bloków zdjęć. Bloki są wyświetlone jeden obok drugiego, zawierają zdjęcie, nagłówek 4 stopnia z liczbą pobrań oraz przycisk o treści „Pobierz”. W aplikacji zastosowano pętle do wyświetlenia bloków zdjęć. Aplikacja jest napisana uniwersalnie i działa poprawnie też dla innej liczby zdjęć.

    Pętlą jest blok @for (photo of photos; track photo.id) { ... } - wbudowana składnia Angulara (od wersji 17): dla każdego obiektu tablicy powtarza swoją zawartość, a w środku pod nazwą photo jest bieżące zdjęcie. track photo.id podaje, po czym Angular rozpoznaje elementy przy odświeżaniu (jak key w React) - w @for jest obowiązkowe. W starszym zapisie to samo robi dyrektywa *ngFor="let photo of photos" z klucza (R.3.4).

    W środku blok zdjęcia: <img> z adresem i tekstem alternatywnym z danych, wstawionymi interpolacją {{ ... }} - dokładnie jak w przykładzie z klucza, <h4> z liczbą pobrań i zielony przycisk btn btn-success (R.3.3). Klasa d-inline-block stawia bloki obok siebie; zawijają się do kolejnego wiersza, gdy zabraknie miejsca. Pętla działa dla dowolnej liczby zdjęć.

    Typowe błędy

    @for (photo of photos) bez track to błąd kompilacji szablonu - w nowym zapisie Angular go wymaga. Gdy dane nie mają id, wystarczy track $index.

    web/src/app/app.html HTML zmienione: 9 linii
    <h1>Kategorie zdjęć</h1>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="flowers" [checked]="visible[1]"
             (change)="toggleCategory(1)">
      <label class="form-check-label" for="flowers">Kwiaty</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="animals" [checked]="visible[2]"
             (change)="toggleCategory(2)">
      <label class="form-check-label" for="animals">Zwierzęta</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="cars" [checked]="visible[3]"
             (change)="toggleCategory(3)">
      <label class="form-check-label" for="cars">Samochody</label>
    </div>
    <div>
      @for (photo of photos; track photo.id) {
        <div class="d-inline-block">
          <img src="assets/{{ photo.filename }}" alt="{{ photo.alt }}">
          <h4>Pobrań: {{ photo.downloads }}</h4>
          <button type="button" class="btn btn-success">Pobierz</button>
        </div>
      }
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  20. Krok 20: Zdjęcia: margines 5 px i zaokrąglone rogi

    Kryteria:

    R.3.2 Do projektu dołączona biblioteka Bootstrap oraz sformatowany obraz: margin: 5px; oraz zaokrąglone rogi (border-radius lub klasa rounded )

    ok. 2 min
    Polecenie z arkusza

    Zdjęcia są formatowane stylem: marginesy zewnętrzne 5 px, zaokrąglone rogi.

    Styl dopisujesz w app.css - arkuszu stylów komponentu. Angular ogranicza takie style do swojego komponentu: reguła img nie dotknie obrazków w innych komponentach (zwykły CSS na stronie zadziałałby globalnie). margin: 5px i border-radius: 8px to dokładnie to, o co pyta klucz (R.3.2); równie dobra jest klasa Bootstrapa rounded przy <img>.

    web/src/app/app.css CSS nowy plik
    img {
      margin: 5px;
      border-radius: 8px;
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  21. Krok 21: Warunek @if: tylko kategorie włączone przełącznikiem

    Kryteria: ,

    R.3.5 Do sprawdzenia czy switch jest zaznaczony lub czy kategoria obrazu zgadza się z zaznaczeniem zastosowany warunek np.: Angular: *ngIf = "kwiaty == true", *ngIf = "zdjecie.category == 1" React.js: funkcja renderująca, w której znajduje się warunek i i instrukcja return, która odpowiednio zwraca kod wyświetlający obrazek

    R.3.9 Zdjęcia wyświetlają się w zależności od stanu pól switch (w uruchomionej aplikacji lub na zrzucie oraz obowiązkowo w kodzie )

    ok. 2 min
    Polecenie z arkusza

    Bloków zdjęć, które są wyświetlane warunkowo, w zależności od ustawień pól switch. W aplikacji zastosowano pętle oraz warunki do wyświetlenia bloków zdjęć.

    Wewnątrz pętli blok zdjęcia obejmujesz warunkiem @if (visible[photo.category]) { ... }: zdjęcie pojawia się tylko wtedy, gdy przełącznik jego kategorii jest włączony. To odpowiednik *ngIf z przykładu w kluczu (R.3.5) - w starszym zapisie: <div *ngIf="visible[photo.category]">.

    Wyłącz w podglądzie „Zwierzęta” - zostają kwiaty i samochody (obraz 3, R.3.9). Włączone same „Samochody” dają dwa zdjęcia.

    web/src/app/app.html HTML zmienione: 7 linii
    <h1>Kategorie zdjęć</h1>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="flowers" [checked]="visible[1]"
             (change)="toggleCategory(1)">
      <label class="form-check-label" for="flowers">Kwiaty</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="animals" [checked]="visible[2]"
             (change)="toggleCategory(2)">
      <label class="form-check-label" for="animals">Zwierzęta</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="cars" [checked]="visible[3]"
             (change)="toggleCategory(3)">
      <label class="form-check-label" for="cars">Samochody</label>
    </div>
    <div>
      @for (photo of photos; track photo.id) {
        @if (visible[photo.category]) {
          <div class="d-inline-block">
            <img src="assets/{{ photo.filename }}" alt="{{ photo.alt }}">
            <h4>Pobrań: {{ photo.downloads }}</h4>
            <button type="button" class="btn btn-success">Pobierz</button>
          </div>
        }
      }
    </div>
    Podgląd strony po tym kroku (web/src/app/app.html)
  22. Krok 22: Przycisk Pobierz zwiększa liczbę pobrań

    Kryteria: ,

    R.3.6 Zapisane zdarzenie kliknięcia przycisku i obsłużone w kodzie np. Angular: <button ... (click)="onPobierzClick(i)">Pobierz</button> onPobierzClick(i: any):void ... zmienna i z przykładu jest indeksem w pętli (let i=index) React.js: onClick={this.OnPobierzClick(this.state.i)} OnPobierzClick() {... }

    R.3.10 Po wciśnięciu przycisku zwiększany jest o jeden licznik pobrań, zmiany są widoczne na stronie, są modyfikowane w tablicy dla odpowiedniego obiektu (w uruchomionej aplikacji lub na zrzucie oraz obowiązkowo w kodzie )

    ok. 3 min
    Polecenie z arkusza

    Gdy przycisk „Pobierz” zostanie kliknięty, wzrasta o jeden liczba pobrań dla danego zdjęcia. Liczba pobrań jest zapisywana w tablicy z obiektami zdjęć, co na bieżąco powoduje wyświetlenie tej modyfikacji na ekranie.

    Przycisk przekazuje metodzie swoje zdjęcie: (click)="download(photo)" - zmienna pętli photo jest dostępna w całym bloku (R.3.6). Metoda zwiększa photo.downloads o jeden. To ten sam obiekt, który leży w tablicy photos, więc zmiana jest zapisana w tablicy, a Angular po zdarzeniu odświeża widok i liczba rośnie od razu (R.3.10). Kliknij kilka razy „Pobierz” przy drugim zdjęciu (obraz 4).

    web/src/app/app.html HTML zmienione: 1 linia
    <h1>Kategorie zdjęć</h1>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="flowers" [checked]="visible[1]"
             (change)="toggleCategory(1)">
      <label class="form-check-label" for="flowers">Kwiaty</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="animals" [checked]="visible[2]"
             (change)="toggleCategory(2)">
      <label class="form-check-label" for="animals">Zwierzęta</label>
    </div>
    <div class="form-check form-switch form-check-inline">
      <input class="form-check-input" type="checkbox" id="cars" [checked]="visible[3]"
             (change)="toggleCategory(3)">
      <label class="form-check-label" for="cars">Samochody</label>
    </div>
    <div>
      @for (photo of photos; track photo.id) {
        @if (visible[photo.category]) {
          <div class="d-inline-block">
            <img src="assets/{{ photo.filename }}" alt="{{ photo.alt }}">
            <h4>Pobrań: {{ photo.downloads }}</h4>
            <button type="button" class="btn btn-success" (click)="download(photo)">Pobierz</button>
          </div>
        }
      }
    </div>
    web/src/app/app.ts TypeScript zmienione: 4 linie
    import { Component } from '@angular/core';
    
    interface Photo {
      id: number;
      alt: string;
      filename: string;
      category: number;
      downloads: number;
    }
    
    @Component({
      selector: 'app-root',
      imports: [],
      templateUrl: './app.html',
      styleUrl: './app.css'
    })
    export class App {
      photos: Photo[] = [
        { id: 0, alt: 'Mak', filename: 'obraz1.jpg', category: 1, downloads: 35 },
        { id: 1, alt: 'Bukiet', filename: 'obraz2.jpg', category: 1, downloads: 43 },
        { id: 2, alt: 'Dalmatyńczyk', filename: 'obraz3.jpg', category: 2, downloads: 2 },
        { id: 3, alt: 'Świnka morska', filename: 'obraz4.jpg', category: 2, downloads: 53 },
        { id: 4, alt: 'Rotwailer', filename: 'obraz5.jpg', category: 2, downloads: 43 },
        { id: 5, alt: 'Audi', filename: 'obraz6.jpg', category: 3, downloads: 11 },
        { id: 6, alt: 'kotki', filename: 'obraz7.jpg', category: 2, downloads: 22 },
        { id: 7, alt: 'Róża', filename: 'obraz8.jpg', category: 1, downloads: 33 },
        { id: 8, alt: 'Świnka morska', filename: 'obraz9.jpg', category: 2, downloads: 123 },
        { id: 9, alt: 'Foksterier', filename: 'obraz10.jpg', category: 2, downloads: 22 },
        { id: 10, alt: 'Szczeniak', filename: 'obraz11.jpg', category: 2, downloads: 12 },
        { id: 11, alt: 'Garbus', filename: 'obraz12.jpg', category: 3, downloads: 321 },
      ];
      visible: Record<number, boolean> = { 1: true, 2: true, 3: true };
    
      toggleCategory(category: number): void {
        this.visible[category] = !this.visible[category];
      }
    
      download(photo: Photo): void {
        photo.downloads++;
      }
    }
    Podgląd strony po tym kroku (web/src/app/app.html)
  23. Krok 23: Folder webowa: web.zip i zmienione pliki

    ok. 3 min
    Polecenie z arkusza

    Kod aplikacji przygotuj do nagrania na płytę. W folderze webowa powinno znaleźć się archiwum całego folderu projektu o nazwie web.zip oraz pliki z kodem źródłowym, które były modyfikowane przez zdającego.

    Pakujesz folder web do web.zip, ale bez node_modules i bez ukrytego folderu .angular (odtwarza je npm install i budowanie) - z nimi archiwum miałoby setki megabajtów. Obok kopiujesz zmienione pliki: app.ts, app.html, app.css i styles.css.

    Podgląd strony bez zmian - taki jak po kroku 22.

  24. Etap 4: Dokumentacja aplikacji

    Kroki 24-27 · po tym etapie masz 35 z 35 kryteriów (pkt)

    Krok 24: Dokumentacja metody find

    Kryteria: , , , ,

    R.4.1 Dla metody z aplikacji konsolowej zapisany nagłówek w postaci komentarza zgodny z Listingiem 1 z arkusza egzaminacyjnego (nie należy brać pod uwagę liczby gwiazdek), komentarz może być wieloliniowy lub kilka jednoliniowy lub Docstrings (potrójny cudzysłów)

    R.4.2 W komentarzu podana nazwa metody oraz opisane jej działanie

    R.4.3 W komentarzu podane wszystkie argumenty metody: nazwy i krótki opis lub zapisane "brak"

    R.4.4 W komentarzu podana nazwa typu zwracanego i krótki opis co jest zwracane lub zapisane "brak"

    R.4.5 W komentarzu podany numer zdającego

    ok. 4 min
    Polecenie z arkusza

    W kodzie źródłowym aplikacji konsolowej za pomocą komentarza utwórz nagłówek dowolnej metody, według wzoru z listingu 1. Komentarz powinien znaleźć się nad lub pod nazwą metody. W sekcji parametry należy umieścić opis wszystkich argumentów metody lub zapisać „brak” w przypadku metody bezparametrowej.

    W Pythonie opis metody to docstring - napis w potrójnym cudzysłowie zaraz pod linią def (klucz dopuszcza go wprost, R.4.1). Pola z listingu 1: nazwa i działanie (R.4.2), parametr value z opisem (R.4.3) - self się nie wlicza, to obiekt, na którym metodę wywołano, zwracany typ int z opisem (R.4.4) i autor (R.4.5) - na egzaminie numer PESEL, tu Bartosz Bryniarski.

    Typowe błędy

    Wpisanie self jako parametru w dokumentacji nie jest błędem, ale „parametry: brak” przy metodzie find już tak - ma ona jeden argument (R.4.3).

    konsola/main.py Python zmienione: 9 linii
    import random
    
    
    class Table:
        def __init__(self, size):
            self.__count = size
            self.__numbers = []
            for _ in range(self.__count):
                self.__numbers.append(random.randint(1, 1000))
    
        def display(self) -> None:
            for index in range(self.__count):
                print(f"{index}: {self.__numbers[index]}")
    
        def find(self, value: int) -> int:
            """
            **********************************************
            nazwa metody:        find
            opis metody:         wyszukuje w tablicy pierwsze wystąpienie podanej wartości
            parametry:           value - liczba całkowita, której szukamy w tablicy
            zwracany typ i opis: int - indeks pierwszego wystąpienia wartości albo -1, gdy jej nie ma
            autor:               Bartosz Bryniarski
            **********************************************
            """
            for index in range(self.__count):
                if self.__numbers[index] == value:
                    return index
            return -1
    
        def display_odd(self) -> int:
            odd_count = 0
            for index in range(self.__count):
                if self.__numbers[index] % 2 != 0:
                    print(self.__numbers[index])
                    odd_count += 1
            return odd_count
    
        def average(self) -> float:
            total = 0
            for index in range(self.__count):
                total += self.__numbers[index]
            return total / self.__count
    
    
    def main():
        table = Table(50)
        print("Elementy tablicy:")
        table.display()
    
        searched = int(input("Podaj liczbę do wyszukania: "))
        index = table.find(searched)
        if index != -1:
            print(f"Liczba {searched} jest pod indeksem {index}")
    
        print("Liczby nieparzyste:")
        odd_count = table.display_odd()
        print(f"Razem nieparzystych: {odd_count}")
    
        average = table.average()
        print(f"Średnia wszystkich elementów: {average:.2f}")
    
    
    if __name__ == "__main__":
        main()
    Wynik programu po tym kroku
    > python main.py
    Elementy tablicy:
    0: 553
    1: 993
    2: 170
    3: 92
    4: 56
    5: 590
    6: 712
    7: 518
    8: 536
    9: 141
    10: 8
    11: 251
    12: 213
    13: 826
    14: 574
    15: 882
    16: 959
    17: 37
    18: 712
    19: 373
    20: 320
    21: 216
    22: 489
    23: 801
    24: 314
    25: 973
    26: 241
    27: 725
    28: 173
    29: 797
    30: 388
    31: 244
    32: 403
    33: 753
    34: 425
    35: 71
    36: 895
    37: 20
    38: 300
    39: 315
    40: 350
    41: 163
    42: 636
    43: 48
    44: 819
    45: 859
    46: 956
    47: 165
    48: 985
    49: 859
    Podaj liczbę do wyszukania: 518
    Liczba 518 jest pod indeksem 7
    Liczby nieparzyste:
    553
    993
    141
    251
    213
    959
    37
    373
    489
    801
    973
    241
    725
    173
    797
    403
    753
    425
    71
    895
    315
    163
    819
    859
    165
    985
    859
    Razem nieparzystych: 27
    Średnia wszystkich elementów: 477.98
  25. Krok 25: Zrzuty konsola1 i konsola2

    Kryteria: , ,

    R.1.6 Podjęta próba skompilowania kodu, udokumentowana zrzutem przedstawiającym uruchomiony program lub jego kompilację

    R.2.6 Program uruchamia się w konsoli, co jest udokumentowane zrzutem ekranu

    R.4.6 Zapisany przynajmniej jeden zrzut ekranu z uruchomienia lub kompilacji aplikacji konsolowej, na zrzucie widoczne jest środowisko, w którym powstała aplikacja

    ok. 3 min
    Polecenie z arkusza

    Wykonaj zrzuty ekranu dokumentujące uruchomienie aplikacji. Zrzuty powinny obejmować cały obszar ekranu monitora z widocznym paskiem zadań. Na zrzucie należy umieścić okno z wynikiem działania programu oraz otwarte środowisko programistyczne z projektem. Aplikacja konsolowa – dowolna liczba zrzutów nazwanych konsola1, konsola2, ...

    Program pyta o liczbę, więc robisz dwa zrzuty całego ekranu z PyCharm i panelem Run: konsola1.png - wyszukana liczba, która jest w tablicy (z komunikatem o indeksie), konsola2.png - liczba spoza tablicy (bez komunikatu, jak na obrazie 1). Zrzuty zapisujesz w folderze dokumentacja.

    Wynik programu po tym kroku
    > python main.py
    Elementy tablicy:
    0: 553
    1: 993
    2: 170
    3: 92
    4: 56
    5: 590
    6: 712
    7: 518
    8: 536
    9: 141
    10: 8
    11: 251
    12: 213
    13: 826
    14: 574
    15: 882
    16: 959
    17: 37
    18: 712
    19: 373
    20: 320
    21: 216
    22: 489
    23: 801
    24: 314
    25: 973
    26: 241
    27: 725
    28: 173
    29: 797
    30: 388
    31: 244
    32: 403
    33: 753
    34: 425
    35: 71
    36: 895
    37: 20
    38: 300
    39: 315
    40: 350
    41: 163
    42: 636
    43: 48
    44: 819
    45: 859
    46: 956
    47: 165
    48: 985
    49: 859
    Podaj liczbę do wyszukania: 518
    Liczba 518 jest pod indeksem 7
    Liczby nieparzyste:
    553
    993
    141
    251
    213
    959
    37
    373
    489
    801
    973
    241
    725
    173
    797
    403
    753
    425
    71
    895
    315
    163
    819
    859
    165
    985
    859
    Razem nieparzystych: 27
    Średnia wszystkich elementów: 477.98
    
    > python main.py
    Elementy tablicy:
    0: 898
    1: 576
    2: 697
    3: 615
    4: 191
    5: 552
    6: 667
    7: 26
    8: 754
    9: 737
    10: 944
    11: 538
    12: 284
    13: 710
    14: 979
    15: 638
    16: 550
    17: 672
    18: 733
    19: 354
    20: 243
    21: 890
    22: 293
    23: 850
    24: 686
    25: 935
    26: 219
    27: 454
    28: 553
    29: 429
    30: 143
    31: 233
    32: 245
    33: 746
    34: 433
    35: 748
    36: 604
    37: 575
    38: 960
    39: 679
    40: 735
    41: 630
    42: 808
    43: 689
    44: 935
    45: 992
    46: 251
    47: 663
    48: 273
    49: 722
    Podaj liczbę do wyszukania: 1001
    Liczby nieparzyste:
    697
    615
    191
    667
    737
    979
    733
    243
    293
    935
    219
    553
    429
    143
    233
    245
    433
    575
    679
    735
    689
    935
    251
    663
    273
    Razem nieparzystych: 25
    Średnia wszystkich elementów: 594.62
  26. Krok 26: Zrzuty web1 - web4

    Kryteria: ,

    R.3.8 Aplikacja jest interpretowana bez błędów. W stanie początkowym są zaznaczone wszystkie pola switch i wyświetlone wszystkie obrazy co jest udokumentowane zrzutem ekranu

    R.4.7 Zapisany przynajmniej jeden zrzut ekranu z uruchomienia aplikacji webowej lub z błędami interpretacji, na zrzucie widoczne jest środowisko, w którym powstała aplikacja

    ok. 4 min
    Polecenie z arkusza

    Aplikacja webowa – dowolna liczba zrzutów nazwanych web1, web2, ... (stan początkowy, różne kombinacje dla pól switch, działanie przycisku „Pobierz”).

    npx ng serve i http://localhost:4200. Zrzuty całego ekranu z przeglądarką i środowiskiem albo terminalem:

    • web1.png - stan początkowy, wszystkie zdjęcia (obraz 2, R.3.8),
    • web2.png - wyłączone „Zwierzęta” (obraz 3),
    • web3.png - włączone same „Samochody”,
    • web4.png - po kilku kliknięciach „Pobierz” (obraz 4).

    Podgląd pokazuje kompletną aplikację - możesz w niej przejść te stany.

    Typowe błędy

    Zrzut samego okna przeglądarki, bez środowiska albo terminala, nie spełnia R.4.7.

    Podgląd strony po tym kroku (web/src/app/app.html)
  27. Krok 27: Dokument egzamin

    Kryteria:

    R.4.8 Dokumentacja zawiera wykorzystane oprogramowanie: nazwa systemu operacyjnego, nazwy środowisk programistycznych, nazwy języków programowania

    ok. 2 min
    Polecenie z arkusza

    W edytorze tekstu pakietu biurowego utwórz plik z dokumentacją i nazwij go egzamin. Dokument powinien zawierać zapisane informacje o wykorzystanych w czasie egzaminu narzędziach: Nazwę systemu operacyjnego, Nazwy środowisk programistycznych, Nazwy języków programowania. Zrzuty ekranu i dokument umieść w podfolderze dokumentacja.

    W tym arkuszu dokument piszesz w edytorze tekstu pakietu biurowego (np. Word albo Writer) i zapisujesz jako egzamin w folderze dokumentacja. Trzy informacje: system, środowiska i języki (R.4.8). W plikach kroku jest ta sama treść jako plik tekstowy.

    egzamin.txt Tekst nowy plik
    System operacyjny: Windows 11
    Środowiska programistyczne: PyCharm Community (aplikacja konsolowa), Visual Studio Code z Node.js i Angular CLI (aplikacja webowa)
    Języki programowania: Python (aplikacja konsolowa), TypeScript i HTML - framework Angular (aplikacja webowa)

    Podgląd strony bez zmian - taki jak po kroku 26.

  28. Etap 5: Porównanie z wariantem C# + React

    Kroki 28-29 · po tym etapie masz 35 z 35 kryteriów (pkt)

    Krok 28: Klasa w Pythonie i w C#

    ok. 3 min
    Polecenie z arkusza

    Pola klasy: Tablica liczb całkowitych (ma być tradycyjną tablicą, a w Python listą). Oba pola są dostępne tylko w tej klasie oraz niedostępne dla klas potomnych.

    Ta sama klasa, różne środki:

    • prywatność: C# ma słowo private, Python - umowę i dwa podkreślniki (self.__numbers), które zmieniają nazwę pola,
    • pola: w C# deklarowane w ciele klasy z typem (private int[] numbers;), w Pythonie tworzone w __init__ przez self.,
    • tablica: w C# stały rozmiar new int[size], w Pythonie lista rośnie przez append,
    • losowanie 1-1000: random.Next(1, 1001) w C# (górna granica nie wchodzi), random.randint(1, 1000) w Pythonie (wchodzi),
    • średnia: w C# (double)sum / count (bez rzutowania byłoby dzielenie całkowite), w Pythonie / zawsze daje float,
    • typy metod (R.2.3): w C# obowiązkowe (void, int, double), w Pythonie widać je dzięki adnotacjom -> None, -> int, -> float.

    Podgląd strony bez zmian - taki jak po kroku 26.

  29. Krok 29: Galeria: Angular a React - co wybrać

    ok. 3 min
    Polecenie z arkusza

    Z zastosowaniem dostępnego na stanowisku egzaminacyjnym frameworka Angular lub biblioteki React.js wykonaj aplikację internetową typu front-end realizującą funkcję kategoryzacji zdjęć w galerii.

    Ta sama galeria w obu technologiach:

    • pętla: @for (photo of photos; track photo.id) w szablonie Angulara, photos.map(...) z key w JSX,
    • warunek: @if (...) wokół bloku w Angularze; w React funkcja renderująca zwraca null dla ukrytych zdjęć,
    • przełączniki: [checked] i (change) w Angularze, checked i onChange w React,
    • licznik pobrań: w Angularze wystarczy photo.downloads++ - wykrywanie zmian zauważy zmianę obiektu; w React taka zmiana nie odświeży ekranu - trzeba zbudować nową tablicę i przekazać ją do setPhotos,
    • style zdjęć: w Angularze osobny app.css ograniczony do komponentu, w React styl w obiekcie style={{ margin: '5px' }}.

    Klucz tego arkusza podaje przykłady z obu technologii (R.3.4 - R.3.7). Angular wymaga więcej plików i kreatora, za to szablon jest zwykłym HTML z dodatkami; React - jeden plik JSX, ale z regułami niezmienności stanu. Wybierz tę, którą ćwiczyłeś.

    Podgląd strony bez zmian - taki jak po kroku 26.

  30. Etap 6: Oddanie pracy

    Krok 30 · po tym etapie masz 35 z 35 kryteriów (pkt)

    Krok 30: Nagranie płyty

    ok. 5 min
    Polecenie z arkusza

    UWAGA: Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego powinny się znajdować podfoldery dokumentacja, konsolowa, webowa. W folderze dokumentacja: pliki ze zrzutami oraz plik egzamin. W folderze konsolowa: spakowany cały projekt aplikacji konsolowej, źródło, opcjonalnie plik wykonywalny. W folderze webowa: spakowany cały projekt aplikacji web, pliki ze źródłami komponentu, html, css i inne modyfikowane pliki.

    Sprawdzasz zawartość przed nagraniem:

    • dokumentacja: konsola1.png, konsola2.png, web1.png - web4.png, egzamin,
    • konsolowa: konsola.zip (spakowany po dopisaniu dokumentacji), main.py,
    • webowa: web.zip (bez node_modules i .angular), app.ts, app.html, app.css, styles.css.

    Nagrywasz, sprawdzasz odczyt płyty, opisujesz ją numerem zdającego.

    Podgląd strony bez zmian - taki jak po kroku 26.

Podgląd

To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.

Autor rozwiązania: . Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.