View
233
Download
1
Category
Preview:
Citation preview
Ten projekt został zrealizowany przy wsparciu finansowym Komisji Europejskiej. Projekt lub publikacja odzwierciedlają jedynie stanowisko ich autora i Komisja Europejska nie ponosi odpowiedzialności za umieszczoną w nich zawartość merytoryczną.
Algorytmika ● Programowanie ● Dydaktyka
Algorytmika i Programowanie Materiały szkoleniowe dla nauczycieli
MARIA CHRISTODOULOU
ELŻBIETA SZCZYGIEŁ
ŁUKASZ KŁAPA
WOJCIECH KOLARZ
Algorytmika i programowanie Materiały szkoleniowe dla nauczycieli
MARIA CHRISTODOULOU
ELŻBIETA SZCZYGIEŁ
ŁUKASZ KŁAPA
WOJCIECH KOLARZ
Krosno, 2018
Autorzy:
Maria Christodoulou, Elżbieta Szczygieł, Łukasz Kłapa, Wojciech Kolarz
Recenzent naukowy: dr Marek Sobolewski, Politechnika Rzeszowska
Wydawnictwo:
P.T.E.A. Wszechnica Sp. z o.o.
ul. Rzeszowska 10,
38-404 Krosno
Tel.: +48 13 436 57 57
https://wszechnica.com/
Krosno, 2018
ISBN 978-83-951529-1-7
Creative Commons Attribution-ShareAlike 4.0 International
Spis treści
Zamiast wprowadzenia ......................................................................................................... 5
1 Wprowadzenie do algorytmiki .................................................................................... 7
1.1 Programy komputerowe ........................................................................................... 8
1.2 Algorytmy i ich znaczenie.......................................................................................... 8
1.3 Projektowanie algorytmiczne ................................................................................... 8
1.4 Algorytmy, programy i języki programowania ....................................................... 9
1.4.1 Składnia i semantyka ........................................................................................ 10
1.4.2 Projektowanie algorytmiczne: stopniowe udoskonalanie algorytmów ...... 11
1.4.3 Projektowanie algorytmiczne: sekwencja ...................................................... 16
1.4.4 Projektowanie algorytmiczne: wybór ............................................................. 16
1.4.5 Projektowanie algorytmiczne: iteracja ........................................................... 18
1.4.6 Podsumowanie definicji najważniejszych konstrukcji algorytmicznych .... 20
1.4.7 Projektowanie algorytmiczne: rekurencja ..................................................... 21
1.4.8 Różnice między algorytmami iteracyjnymi a rekurencyjnymi ..................... 24
1.4.9 Projektowanie algorytmiczne: struktury danych .......................................... 25
1.5 Bibliografia ............................................................................................................... 33
2 Wprowadzenie do programowania .......................................................................... 34
2.1 Definicja programowania ........................................................................................ 34
2.2 Historia programowania ......................................................................................... 36
2.3 Umiejętności programistów i proces ich opracowywania ................................... 37
2.4 Zmienne i stałe ......................................................................................................... 41
2.5 Obiekty ...................................................................................................................... 43
2.6 Operatory .................................................................................................................. 44
2.7 Wyrażenia decyzyjne ............................................................................................... 47
2.8 Pętle ........................................................................................................................... 50
2.9 Funkcje ...................................................................................................................... 55
2.10 Bibliografia ............................................................................................................ 57
3 Dydaktyka przy użyciu algorytmiki i programowania ......................................... 59
3.1 Podstawowe założenia algorytmiczne i programistyczne w nauczaniu szkolnym
59
3.2 Koncepcja myślenia komputacyjnego w nauczaniu myślenia algorytmicznego 61
3.3 Zastosowanie myślenia komputacyjnego w praktyce edukacyjnej ..................... 64
3.4 Praktyczne ćwiczenia z wykorzystaniem algorytmów i programowania ........... 69
3.5 Bibliografia ............................................................................................................... 83
5
Zamiast wprowadzenia
Umiejętność posługiwania się algorytmem i programowaniem jest uznawana przez
władze europejskie za jedną z ważnych, aktualnych umiejętności stanowiących część
tzw. „kompetencji cyfrowych", która stanowią jedną z ośmiu kluczowych kompetencji.
W raporcie EURYDICE (2012) sformułowano następujące oświadczenie: "Konieczność
poprawy jakości i przydatności umiejętności i kompetencji, z jakimi młodzi
Europejczycy opuszczają szkołę, została uznana na szczeblu UE oraz szczeblach
krajowych. Konieczność zajęcia się tą kwestią dodatkowo podkreśla aktualna sytuacja,
w której Europa boryka się z wysokim bezrobociem wśród młodzieży, a w niektórych
przypadkach, z poważnymi niedopasowaniami umiejętności(...). Europejska Sieć Polityki
w zakresie wdrażania Kluczowych Kompetencji (KeyCoNet) analizuje pojawiające się
inicjatywy dotyczące rozwoju kluczowych kompetencji(...). Jedna z nich dotyczy
potrzeby akcentowania bardziej strategicznej postawy celem wspierania podejścia
opartego na kluczowych kompetencjach w szkole. Drugi dotyczy wysiłków
zmierzających do podniesienia statusu kompetencji przekrojowych (cyfrowych,
obywatelskich i przedsiębiorczych) w porównaniu z tradycyjnymi kompetencjami
przedmiotowymi."
Publikacja pt. Algorytmika i Programowanie - Materiały szkoleniowe dla nauczycieli.
Algorytmika. Programowanie. Dydaktyka, spełnia te zalecenia. Głównym celem publikacji
jest przedstawienie nauczycielom idei algorytmiki i programowania wraz z ich
praktycznym zastosowaniem w dydaktyce. Publikacja jest pierwszym rezultatem
intelektualnym projektu pn. „CodeIT: Enhancing Teachers’ professional
development through algorithmic and programming”, realizowanego przez
międzynarodowe konsorcjum składające się z sześciu partnerów z pięciu krajów:
P.T.E.A. Wszechnica Sp. o.o. (Krosno, Polska), M.K. INNOVATIONS LTD (Nikozja, Cypr),
Danmar Computers Sp. o.o. (Rzeszów, Polska), Istituto Superiore E. Mattei (Fiorenzuola
d'Arda, Włochy), Liceul Pedagogic "Mircea Scarlat" Aleksandria (Aleksandria, Rumunia)
i Kekavas vidusskola (Kekava, Łotwa). Projekt realizowany jest w ramach Programu
Erasmus+ Partnerstwa Strategiczne.
Głównym celem projektu jest pomoc nauczycielom w poprawie ich rozwoju
zawodowego poprzez podniesienie kompetencji programowania przy wykorzystaniu
innowacyjnych zasobów. Główną grupą docelową są nauczyciele przedmiotów
nieinformatycznych ze szkół podstawowych (klasy 4 i wyższe) oraz gimnazjów (w tym
młodszych klas szkół ponadgimnazjalnych), ze szczególnym uwzględnieniem nauczycieli
chemii, geografii, matematyki i fizyki. Drugą grupą docelową są uczniowie wyżej
wymienionych szkół.
Publikacja składa się z trzech rozdziałów. Pierwszy rozdział poświęcony jest
algorytmice i prezentuje ideę programów komputerowych i języków programowania,
6
znaczenie algorytmów i ich konstrukcję. W rozdziale drugim przedstawiono definicję
programowania, jego historię oraz umiejętności programistów oraz proces tworzenia
programów. W tym rozdziale znajduje się również informacja o zasadach
programowania. Ostatni rozdział poświęcony jest prezentacji elementów dydaktycznych
z wykorzystaniem algorytmiki i programowania. W rozdziale przedstawiono również
informacje o podstawowych założeniach algorytmiki i programowania w nauczaniu
szkolnym, a także koncepcję myślenia komputacyjnego. Ostatnia część publikacji
poświęcona jest prezentacji jej praktycznego zastosowania.
Autorzy mają nadzieję, że publikacja spotka się z zainteresowaniem ze strony
nauczycieli i przyniesie im przydatną wiedzę w zakresie algorytmiki i programowania.
Publikacja zainicjowała zestaw materiałów edukacyjnych dla nauczycieli i uczniów,
składający się z:
Wirtualnego środowiska nauczania dla nauczycieli zawierającego materiały
szkoleniowe w zakresie algorytmiki i programowania oraz ich zastosowania
w nauczaniu przedmiotów innych niż informatyczne,
Przykładowych planów lekcji obejmujących zastosowanie podstaw
programowania w nauczaniu chemii, geografii, matematyki i fizyki,
Podręcznika metodycznego zatytułowanego „Zwiększ swoje umiejętności
pedagogiczne z wykorzystaniem algorytmiki i programowania".
Autorzy
7
1 Wprowadzenie do algorytmiki (Maria Christodoulou)
Algorytm to opis sposobu rozwiązania określonego problemu.
Jeśli kiedykolwiek piekłeś ciasteczka, wiesz, że najpierw musisz zebrać składniki,
następnie zważyć je, wymieszać, a potem przygotować miskę, podgrzać piekarnik
i upiec.
Rysunek 1 - Opis algorytmiczny jest jak przepis
Źródło: momsrecipesandmore.blogspot.gr
Jeśli zapomnisz cukru, nie smakują dobrze i musisz zacząć od nowa.
Wyznaczanie właściwych kroków, poprawne i kompletne podążanie za nimi oraz
uczenie się na błędach są częścią procesu projektowania algorytmów.
8
1.1 Programy komputerowe
Aby mogły być wykonywane przez komputery, algorytmy muszą mieć postać
„programu”. Program napisany jest w języku programowania, a aktywność wyrażania
algorytmu jako programu nazywa się programowaniem.
W algorytmach kroki są wyrażane w postaci instrukcji lub wyrażeń. W konsekwencji
program komputerowy zawiera szereg wyrażeń, które wskazują komputerowi, którą
operację wykonać.
Zastosowany język programowania będzie określał charakter instrukcji w programie.
1.2 Algorytmy i ich znaczenie
Aby skorzystać z komputera w celu realizacji poszczególnych procesów, konieczne jest:
zaprojektowanie algorytmu opisującego sposób realizacji procesu;
używanie języka programowania do wyrażenia algorytmu w programie;
uruchomienie programu na komputerze.
W tym celu należy zrozumieć, że algorytmy są niezależne od używanego języka
programowania, a każdy algorytm może być wyrażany w różnych językach
programowania i wykonywany na różnych komputerach. Z tego powodu projektowanie
algorytmów jest podstawowym aspektem informatyki. Konstrukcja algorytmu jest
wymagającą aktywnością intelektualną, znacznie trudniejszą niż wyrażenie algorytmu
jako programu.
Wśród umiejętności potrzebnych do projektowania algorytmów są kreatywność
i wnikliwość (Goldschlager i Lister, 1988). Podczas gdy nie istnieje jedna ogólna zasada,
nie oznacza to jednak, że nie ma algorytmu do projektowania algorytmów!
1.3 Projektowanie algorytmiczne
Projektowanie algorytmu:
zawiera zestaw instrukcji do wykonania zadania,
przenosi problem z fazy modelowania na fazę operacyjną,
zawiera zestaw instrukcji, który powinien być sekwencyjny, kompletny, dokładny
i mieć wyraźny punkt końcowy,
9
jeśli algorytm jest przeznaczony dla komputera, musi składać się z szeregu zadań
napisanych w sposób, który sprawi, że komputer będzie mógł je wykonać.
W tym rozdziale przyjrzymy się algorytmom, biorąc pod uwagę ich strukturę, skład
i wyrażenia w formie wykonywalnej.
1.4 Algorytmy, programy i języki programowania
Jak już wspomniano, algorytm jest opisem sposobu wykonywania zadania lub procesu
i istnieją algorytmy do wykonywania prawie wszystkich rodzajów zadań czy też
procesów: od budowy płaszczyzny modelu po obsługę koparki.
Rysunek 2 - Algorytmy, programy i języki programowania
Źródło: shutterstock.com
Najczęściej proces opisany przez algorytm współdziała z jego otoczeniem,
akceptując dane wejściowe i tworząc dane wyjściowe (Goldschlager i Lister, 1988).
Procesy wykonywane na komputerze częściej wymagają danych wejściowych w postaci
danych i bardzo często wytworzone wyniki będą również miały postać danych. Jako
przykład, zastanówmy się nad procesem obliczania wynagrodzeń, które będą wymagały
nakładów, takich jak dzienny koszt, liczba przepracowanych dni, itd. Proces obliczania
przyniesie takie wyniki jak: pensje do zapłacenia, składki pracowników/pracodawców
i podatki do odliczenia.
Wejścia i wyjścia są częścią specyfikacji procesu, ale nadal są niezależne
od procesora, który wykonuje proces.
10
Kolejnym ważnym aspektem procesów jest ich zakończenie. Proces może zakończyć się
lub może nigdy nie zostać zakończony. Jest to powodem, dla którego zakończenie lub
brak zakończenia procesu jest jedną z jego ważnych cech.
1.4.1 Składnia i semantyka
Komputer musi mieć możliwość interpretowania algorytmu w celu wykonania procesu
opisanego przez algorytm. W związku z tym komputer musi być w stanie:
zrozumieć, w jakiej formie wyrażany jest algorytm;
wykonać operacje opisane przez algorytm.
Rysunek 3 - Składnia i semantyka
Źródło: shutterstock.com
W niniejszym rozdziale przyjrzymy się formie, w której wyrażane są algorytmy.
Zestaw reguł gramatycznych, które regulują sposób, w jaki symbole w języku mogą być
używane zgodnie z prawem, nazywa się składnią języka (Goldschlager i Lister, 1988).
Uważa się, że program zgodny ze składnią języka, w którym jest wyrażany, jest
poprawny pod względem składniowym. Odchylenie od prawidłowej składni języka
jest nazywane błędem składni. Poprawność składniowa jest zwykle warunkiem
koniecznym, aby komputer był w stanie wykonać program.
Znaczenie poszczególnych form ekspresji w języku zwane jest semantyką języka.
Wykrywanie niespójności semantycznych opiera się na znajomości przywoływanych
obiektów, w szczególności na znajomości atrybutów tych obiektów i relacji między nimi.
Rozważmy przykład, jak poradzi sobie procesor komputerowy z następującym
poleceniem (Goldschlager i Lister, 1988):
zapisz nazwę 13 miesiąca roku
Jeśli procesor wie, że jest tylko 12 miesięcy w roku, może wykryć semantyczną
niespójność w poleceniu, zanim spróbuje je wykonać. Jeśli tego nie wie, to spróbuje je
wykonać i najprawdopodobniej zostanie zgłoszony wyjątek.
11
Częściej, niespójności semantyczne – bardziej subtelne – mogą być wynikiem wykonania
poprzedniej części algorytmu:
Wymyśl liczbę w zakresie od 1 do 13
Zadzwoń pod numer N
Zapisz nazwę N-tego miesiąca roku
Algorytm ten zawiera potencjalną niekonsekwencję, która pojawia się tylko wtedy, gdy
wynik obliczeń z pierwszej linii da liczbę 13. Gdy niespójność jest wynikiem wykonania
algorytmu, nie ma na ogół szansy na wykrycie go wcześniej.
Oprócz błędów składniowych i semantycznych występują również błędy logiczne.
Możliwe, że program jest poprawny pod względem składni i nie zawiera żadnych
niespójności semantycznych, ale może nadal nie prowadzić do realizacji zamierzonego
procesu.
Jako przykład rozważmy algorytm obliczania obwodu koła (Goldschlager i Lister, 1988):
Oblicz obwód przez pomnożenie promienia przez π
Algorytm jest poprawny pod względem składniowym i semantycznym, ale daje błędny
wynik z powodu błędu logicznego, którym jest pominięcie współczynnika 2.
Niestety, błędy logiczne są trudne do wykrycia przez procesory komputerowe przed
wykonaniem procesu i porównaniem wyniku z pożądanym wynikiem.
1.4.2 Projektowanie algorytmiczne: stopniowe udoskonalanie algorytmów
Projektowanie algorytmów opisujących nietrywialne procesy jest zazwyczaj bardzo
trudne. Bardzo często błędy pojawiające się w algorytmach mają związek z opisanym
procesem, który jest bardzo zbliżony do zamierzonego procesu, ale nie odzwierciedla go
dokładnie.
Innym częstym błędem jest to, że wykonanie powoduje zamierzony proces, ale
w pewnych okolicznościach (nieprzewidzianych lub przeoczonych przez projektanta)
nie daje takiego efektu. Oto przykład (Goldschlager i Lister, 1988), który opisuje sposób
obliczania czasu lotu samolotu z harmonogramu przylotu:
1. Sprawdź czas odlotu
2. Sprawdź czas przylotu
3. Odejmij godzinę odlotu od czasu przylotu
Algorytm ten w większości przypadków wygeneruje poprawny wynik, ale nie zrobi tego,
jeśli punkt początkowy i docelowy znajdują się w różnych strefach czasowych.
12
Wniosek jest taki, że projektant algorytmu powinien upewnić się, że algorytm dokładnie
opisuje proces, który należy przeprowadzić, a wszystkie możliwe okoliczności zostały
uwzględnione. Jeśli proces do wykonania jest zbyt skomplikowany, zadanie projektanta
jest trudne.
W związku z powyższym, potrzebne jest podejście metodyczne. Jednym z takich podejść
jest stopniowe udoskonalanie (lub tzw. projekt odgórny).
Stopniowe udoskonalanie jest odmianą dzielenia i realizacji, podczas którego proces,
który ma być przeprowadzony, dzieli się na kilka etapów, z których każdy można opisać
za pomocą mniejszych i prostszych algorytmów. Ponieważ każdy taki pod-algorytm jest
prostszy niż cały proces, projektant zwykle ma jaśniejszy pomysł, jak go efektywnie
skonstruować i dlatego może „szkicować” go bardziej szczegółowo, niż gdyby próbował
obsłużyć cały algorytm naraz. Pod-algorytmy same mogą zostać podzielone na mniejsze
części, które są jeszcze prostsze i mogą być ponownie wyrażone z jeszcze większą
szczegółowością i precyzją. Udoskonalanie algorytmu trwa w ten sposób, dopóki każdy
z jego kroków nie jest wystarczająco szczegółowy i dokładny, aby umożliwić wykonanie
przez procesor komputera.
PRZYKŁAD
Zaprojektuj algorytm dla robota domowego służącego do przygotowania filiżanki kawy
rozpuszczalnej.
Początkowa wersja algorytmu może wyglądać następująco (Goldschlager and Lister,
1988):
(1) zagotuj wodę
(2) umieść kawę w filiżance
(3) dodaj wodę do filiżanki
Kroki tego algorytmu nie są wystarczająco szczegółowe, aby robot mógł je wykonać.
Każdy krok musi zatem zostać uszczegółowiony w sekwencji prostszych kroków,
z których każdy jest bardziej szczegółowo określony niż oryginał. Zatem krok:
(1) zagotuj wodę
może zostać udoskonalony do postaci:
(1.1) napełnij czajnik wodą
(1.2) włącz czajnik
(1.3) poczekaj, aż woda zacznie wrzeć
(1.4) poczekaj, aż czajnik się wyłączy
Podobnie, polecenie
(2) umieść kawę w filiżance
może zostać udoskonalone do postaci:
13
(2.1) otwórz pojemnik z kawą
(2.2) zanurz łyżkę i napełnij kawą
(2.3) włóż łyżkę do filiżanki
(2.4) zamknij pojemnik z kawą
oraz
(3) dodaj wodę do filiżanki
może zostać udoskonalone do:
(3.1) wlej wodę z czajnika do filiżanki, aż będzie pełna
Ostatnie udoskonalenie faktycznie nie zwiększa liczby kroków w algorytmie, ale
po prostu ponownie wyraża istniejący krok bardziej szczegółowo.
Na tym etapie pierwotny algorytm został dopracowany do trzech pod-algorytmów,
które mają być wykonywane sekwencyjnie. Jeśli robot potrafi zinterpretować wszystkie
kroki w każdym pod-algorytmie, proces uszczegóławiania może się zatrzymać i projekt
algorytmu jest kompletny. Jednak niektóre kroki mogą być nadal zbyt skomplikowane,
aby robot mógł je zinterpretować i te kroki należy dalej udoskonalić. Zatem krok:
(1.1) napełnij czajnik wodą
może wymagać dalszego udoskonalenia do postaci:
(1.1.1) umieść czajnik pod kranem
(1.1.2) odkręć wodę
(1.1.3) poczekaj, aż czajnik się napełni
(1.1.4) zakręć wodę
Inne kroki mogą wymagać podobnego udoskonalenia, chociaż mogą być pewne, takie
jak:
(1.2) włącz czajnik
który może już zostać wykonany przez robota bez potrzeby dalszego udoskonalania.
Na koniec, po wielu udoskonaleniach, każdy krok algorytmu zostanie zrozumiany
i wykonany przez robota. Na tym etapie algorytm jest kompletny. Kolejne udoskonalenia
pokazano na rysunku 4 poniżej.
14
Oryginalny Pierwsze udoskonalenie Drugie udoskonalenie
(1) zagotuj wodę (1.1) napełnij czajnik wodą (1.1.1) umieść czajnik pod kranem
(1.1.2) odkręć wodę
(1.1.3) poczekaj, aż czajnik się napełni
(1.1.4) zakręć wodę
(1.2) włącz czajnik
(1.3) poczekaj, aż woda zacznie wrzeć (1.3.1)
poczekaj, aż woda się zagotuje
(1.4) poczekaj, aż czajnik się wyłączy
(2) umieść kawę w filiżance (2.1) otwórz pojemnik z kawą (2.1.1)
weź pojemnik z kawą z półki
(2.1.2) odkręć zakrętkę
(2.2) zanurz łyżkę i napełnij kawą
(2.3) włóż łyżkę do filiżanki
(2.4) zamknij pojemnik z kawą (2.4.1) zakręć pojemnik
(2.4.2) odłóż pojemnik na półkę
(3) dodaj wodę do filiżanki (3.1)
wlej wodę z czajnika do filiżanki, aż będzie pełna
Rysunek 4 - Udoskonalenie algorytmu parzenia kawy
Źródło: przykład oparty na (Goldschlager i Lister, 1988)
Ostateczną wersję algorytmu uzyskuje się, biorąc ostatnie udoskonalenie każdego
kroku, jak pokazano na rysunku 5.
15
Rysunek 5 - Ostateczna wersja algorytmu przygotowywania kawy
Źródło: przykład oparty na (Goldschlager i Lister, 1988)
Użycie udoskonalenia krokowego oznacza, że projektant algorytmu wie, gdzie ma się
zatrzymać. Projektant musi wiedzieć, kiedy określony krok w algorytmie jest
wystarczająco prymitywny, aby pozostawić go bez dalszego udoskonalania. Wymaga to
od projektanta wiedzy na temat kroków, jakie może wykonać procesor komputerowy.
W naszym przykładzie projektant wiedział, że robot może zinterpretować włącz czajnik
i nie zastosowano dalszego udoskonalenia, ale robot nie może zinterpretować napełnij
czajnik i zastosowano dodatkowe udoskonalenie.
Wniosek jest taki, że stopniowe udoskonalanie algorytmu nie może odbywać się
w próżni. Projektant musi zdawać sobie sprawę z możliwości interpretacyjnych
planowanego procesora, aby mógł popchnąć precyzję w określonych kierunkach
i wiedzieć, kiedy zakończyć udoskonalanie każdej części.
Dobrą wiadomością jest to, że możliwości interpretacyjne komputerów są precyzyjnie
określone: komputer może interpretować wszystko, co jest właściwie wyrażone
w języku programowania. W ten sposób projektant udoskonala algorytm w taki
sposób, że kroki mogą być wyrażone w odpowiednim języku programowania i kończy
udoskonalanie, gdy każdy krok jest wyrażany w wybranym języku.
Każde udoskonalenie implikuje szereg decyzji projektowych opartych na zestawie
kryteriów projektowych. Wśród tych kryteriów są: efektywność, oszczędność
pamięci, przejrzystość i regularność struktury. Projektanci muszą być świadomi
podejmowanych decyzji i być w stanie poddawać je krytycznej analizie. Powinni również
{zagotuj wodę}
(1.1.1) umieść czajnik pod kranem
(1.1.2) odkręć wodę
(1.1.3) poczekaj, aż czajnik się napełni
(1.1.4) zakręć wodę
(1.2) włącz czajnik
(1.3.1) poczekaj, aż woda się zagotuje
(1.4) poczekaj, aż czajnik się wyłączy
{umieść łyżeczkę kawy w filiżance}
(2.1.1) weź pojemnik z kawą z półki
(2.1.2) odkręć zakrętkę
(2.2) zanurz łyżkę i napełnij kawą
(2.3) włóż łyżkę do filiżanki
(2.4.1) zakręć pojemnik
(2.4.2) odłóż pojemnik na półkę
{wlej wodę do filiżanki}
(3.1) wlej wodę z czajnika do filiżanki, aż będzie pełna
16
odrzucać niektóre z nich, nawet wtedy, kiedy otrzymane wyniki są poprawne.
Projektanci muszą zatem, w świetle tych kryteriów, nauczyć się odpowiednio dobierać
alternatywne rozwiązania. W szczególności, muszą być w stanie wycofać wcześniejsze
decyzje, zrobić krok wstecz, lub, jeśli sytuacja tego wymaga, zacząć proces tworzenia
algorytmu od początku. Stosunkowo proste przykłady często wystarczą do
zilustrowania tego ważnego punktu; nie jest konieczne przywoływanie przykładu
tworzenia systemu operacyjnego.
1.4.3 Projektowanie algorytmiczne: sekwencja
Algorytm parzenia kawy z poprzedniej sekcji obejmuje proste kroki, które należy
wykonać sekwencyjnie:
kroki są wykonywane pojedynczo, a nie równolegle,
każdy krok jest wykonywany tylko raz,
kolejność wykonywania to kolejność, w jakiej zapisane są kroki,
wykonanie ostatniego kroku kończy algorytm.
Taki algorytm nie jest elastyczny, ponieważ nie może być dostosowany do reagowania
na różne okoliczności. Na przykład pomyśl o nieuniknionej sytuacji, w której słoik kawy
jest pusty lub o sytuacji, w której robot musi obsłużyć kilka próśb o kawę lub
niestandardowe prośby o mleko lub cukier.
Wniosek jest taki, że algorytm będący jedynie kombinacją kroków nie jest dobrym
rozwiązaniem i że potrzebne są bardziej elastyczne struktury w celu zaprojektowania
algorytmów zdolnych do realistycznego odwzorowania rzeczywistych sytuacji.
1.4.4 Projektowanie algorytmiczne: wybór
Bardziej zaawansowaną strukturą zapewniającą elastyczność jest wybór. Korzystając
z selekcji, możemy zawęzić krok 2.1 z poprzedniego przykładu algorytmu ekspresów do
kawy w następujący sposób (Goldschlager i Lister, 1988):
(2.1.1) weź pojemnik z kawą z półki
(2.1.2) jeśli pojemnik jest pusty
wtedy weź nowy pojemnik z szafki
(2.1.3) odkręć zakrętkę
Widzimy wprowadzenie warunku w kroku 2.1.2. Warunkiem jest "słoik jest pusty". Jeśli
warunek jest spełniony, obowiązuje instrukcja warunkowa, która brzmi "weź nowy słoik
z szafki".
Jest to ogólna forma, w której wyrażana jest struktura wyboru:
Jeśli warunek
Wtedy krok
17
Warunek ten może być użyty do określenia dowolnego rodzaju okoliczności, które, gdy
prawdziwe, wymagają wykonania określonego kroku.
Oczywiście, w prawdziwym życiu istnieją alternatywy na wypadek pojawienia się
określonej sytuacji. Aby poradzić sobie z takimi sytuacjami, struktura wyboru może
zostać rozszerzona, aby zapewnić alternatywne kroki do wykonania.
Załóż prosty algorytm do jazdy z samochodem do pracy, który musi być dostosowany do
sytuacji, w której samochód potrzebuje paliwa:
(1) uruchom silnik
(1.1) jeśli wskaźnik niskiego poziomu paliwa świeci się
wtedy jedź do najbliższej stacji benzynowej
w przeciwnym razie jedź do pracy
W tym przypadku możemy wybrać pomiędzy dwoma alternatywnymi krokami,
w których to przypadku warunek (włączanie lub wyłączanie wskaźnika paliwa) dyktuje,
który krok ma zostać wykonany w zależności od sytuacji, z którą mamy do czynienia.
Oto kolejny przykład tego, w jaki sposób udoskonalenia i wykorzystanie selekcji mogą
dać o wiele bardziej realistyczny algorytm zdolny do obsługi większości okoliczności bez
nieoczekiwanych rezultatów:
(1) umyj auto
(1.1) jeśli nie masz na to ochoty
jedź do myjni
(1.2) umyj go ręcznie
Etap 1.1 można dalej udoskonalić:
(1.1.1) kup żeton
(1.1.2) poczekaj w kolejce
(1.1.3) myj samochód
Krok 1.1.3 może być bardziej szczegółowy:
(1.1.3.1) jedź do myjni
(1.1.3.2) sprawdź czy wszystkie drzwi i okna są zamknięte
(1.1.3.3) wyjdź z auta
(1.1.3.4) włóż żeton do automatu
(1.1.3.5) poczekaj aż mycie się skończy
(1.1.3.6) wejdź do auta
(1.1.3.7) odjedź
Większą elastyczność można wprowadzić w algorytmach, korzystając z wyboru
zagnieżdżonego. Rozważmy następujący algorytm przejścia dla pieszych na ulicy przy
przejściu dla pieszych.
jeśli świeci się zielone światło
przechodź
w przeciwnym razie zatrzymaj się
18
Ten przykład zawiera jeden wybór i można go ulepszyć jako:
jeśli nie świeci się żadne światło lub świeci się migające zielone
wtedy przechodź ostrożnie
w przeciwnym wypadku jeśli świeci się czerwone
wtedy zatrzymaj się
w przeciwnym razie przechodź
Ten późniejszy przykład zawiera dwa wybory. Drugi wybór jest zagnieżdżony wewnątrz
pierwszego i jest wykonywany tylko wtedy, gdy światło jest czerwone.
1.4.5 Projektowanie algorytmiczne: iteracja
Iteracja to powtórzenie procesu w programie komputerowym. Iteracje funkcji są
powszechne w programowaniu komputerowym, ponieważ pozwalają na przetwarzanie
wielu bloków danych w sekwencji. Zazwyczaj robi się to przy użyciu "pętli while" lub
"pętli for". Te pętle powtarzają proces, aż do osiągnięcia określonej liczby lub danego
przypadku.
Mówiąc najprościej, iteracja oznacza powtarzanie tego samego kroku kilka razy.
Rysunek 6 - Iteracja: Powtarzanie tego samego kroku kilka razy
(Źródło: okclipart.com)
Iteracyjne algorytmy powinny spełniać trzy ważne zasady:
1. Iteracyjny proces powtarza (iteruje) pewien podproces.
2. Każda iteracja powinna zmienić co najmniej jedną wartość.
19
3. Powinien istnieć pewien warunek, w którym kończy się iteracja. Iteracja powinna
osiągnąć ten stan.
Bardzo prosty algorytm do spożywania płatków śniadaniowych może składać się
z następujących kroków (BBC Bitesize KS3 Subjects: Iteration):
(1) umieść płatki w misce
(2) dodaj mleko do płatków zbożowych
(3) włóż łyżkę płatków i mleka do ust
(3.1) powtórz krok 3, aż zjesz wszystkie płatki i mleko
(4) spłucz miskę i łyżeczkę
To, co widzimy w kroku 3.1, to wprowadzenie stanu, który jest sytuacją sprawdzaną za
każdym razem, gdy występuje iteracja. Warunek jest wprowadzany za pomocą słów
repeat i until. Bez tego algorytm nie wiedziałby, kiedy przerwać. Warunkiem w tym
przypadku będzie sprawdzenie, czy całe mleko i płatki zostały zjedzone. Jeśli ten
warunek jest fałszywy (w misce nadal jest mleko i płatki), następuje kolejna iteracja.
Jeśli warunek jest prawdziwy (nie ma już mleka i płatków w misce), wówczas nie ma już
powtórzeń.
Ogólna forma to:
repeat
część algorytmu
until warunek
Oznacza to, że część algorytmu między repeat a until jest wykonywana wielokrotnie,
dopóki warunek po until nie zostanie spełniony. Warunek, który pojawia się po until jest
nazywany warunkiem zakończenia.
Występowanie iteracji nazywa się pętlą.
Iteracja pozwala na uproszczenie algorytmów poprzez stwierdzenie, że pewne
kroki będą się powtarzać, dopóki nie zostanie podane inaczej. To sprawia, że
algorytmy projektowania są szybsze i prostsze, ponieważ nie muszą zawierać wielu
niepotrzebnych kroków (BBC Bitesize KS3 Subjects: Iteration).
Bardziej zaawansowanym zastosowaniem iteracji jest użycie zagnieżdżonych pętli.
Zagnieżdżona pętla to pętla w pętli, wewnętrzna pętla wewnątrz ciała zewnętrznego.
Jak to działa: pierwsze przejście zewnętrznej pętli uruchamia wewnętrzną pętlę, która
kończy się. Następnie drugie przejście zewnętrznej pętli uruchamia ponownie
wewnętrzną pętlę. Powtarza się, aż kończy się zewnętrzna pętla.
Kiedy jedna pętla jest zagnieżdżona w innej, wykonuje się kilka iteracji pętli
wewnętrznej dla każdej iteracji zewnętrznej pętli.
Przykład: załóżmy, że mamy dane w poniższym formularzu, zawierające kilka ciągów w postaci liczb przyrostowych (powiedzmy od 200 do 202). Ciągi te odpowiadają dniu odbioru poczty (220 dni w roku). Dla każdego ciągu liczbowego (dzień zbiórki)
20
zarejestrowano zmienną liczbę pakietów (pakiety reprezentują całkowitą liczbę pakietów zebranych w urzędzie pocztowym w różnych odstępach czasu w tym samym dniu); liczba zliczeń paczek dla każdego dnia zbiórki Dzieńzbiórki jest pokazana w kolumnie Ilezliczeń, która zawiera całkowitą liczbę zliczeń paczek na dzień zbiórki:
Dzieńzbiórki Ilezliczeń Paczki
200 3 110 30 10
201 5 2 46 109 57 216
202 2 10 500
Naszym zadaniem jest odczytanie danych i wyświetlenie podsumowującego wykresu
pokazującego średnią liczbę paczek na liczbę dla każdego dnia zbiórki w następujący
sposób:
Dzieńzbiórki ŚREDNIA
200 50
201 86
202 255
Algorytm może wyglądać tak:
read pierwszy Dzieńzbiórki oraz Ilezliczeń
repeat
wyświetl Dzieńzbiórki
repeat
odczytaj i dodaj ilości paczek z danego dnia
oblicz i wyświetl wartość średnią
until end of ile jest Ilezliczeń
read następny Dzieńzbiórki
until end of Dzieńzbiórki
Patrząc na algorytm łatwo zauważamy, że zewnętrzna pętla kontroluje liczbę linii,
a wewnętrzna pętla kontroluje zawartość każdej linii. Tak więc 3 dni zbiórki prowadzą
do 3 wierszy, a każdy wiersz wyświetla tylko jedną średnią obliczoną przez dodanie
wszystkich pakietów, aby uzyskać ich sumę i podzielenie przez liczbę zliczeń, aby
uzyskać średnią.
1.4.6 Podsumowanie definicji najważniejszych konstrukcji algorytmicznych
Jest to podsumowanie definicji najważniejszych konstruktów algorytmicznych
omawianych do tej pory:
Sekwencja: seria kroków wykonywanych sekwencyjnie w kolejności, w jakiej zostały
napisane. Każdy krok przeprowadzany jest tylko raz
Wybór: należy wybrać jedną z dwóch lub więcej opcji.
Iteracja: Część algorytmu powinna być zdolna do powtórzenia, określoną liczbę razy lub
do momentu spełnienia określonego warunku.
21
1.4.7 Projektowanie algorytmiczne: rekurencja
Rekurencja jest potężnym narzędziem, na podstawie którego algorytm może być
wyrażony sam w sobie. Zapewnia prosty, skuteczny sposób podejścia do różnych
problemów. Często trudno jest pomyśleć o rekurencyjnym podejściu do problemu.
Łatwo jest również napisać algorytm rekurencyjny, który albo trwa zbyt długo, albo nie
kończy się prawidłowo.
W niniejszym podrozdziale przyjrzymy się podstawom rekurencji, aby umożliwić
czytelnikowi zrozumienie, jak skomplikowane problemy można łatwo rozwiązać przy
pomocy myślenia rekurencyjnego. Pierwszą rzeczą do zrobienia jest odpowiedź na
pytanie: „czym jest rekurencja?"
Rysunek 7 – Rekurencja
(Źródło: Shutterstock.com)
Rosyjskie lalki stanowią pomocną analogię do zrozumienia rekurencji. Załóżmy, że
masz algorytm do otwierania każdej lalki. Następnie, aby dostać się do ostatniej lalki,
której nie można już otworzyć, aby uzyskać mniejszą wersję, można wykonać ten sam
algorytm za każdym razem, dopóki nie będzie już więcej lalek do otwarcia.
W algorytmicznej konstrukcji mówi się, że algorytm jest rekurencyjny, jeśli sam się
wywołuje. Oczywiście, gdy mamy do czynienia z algorytmem, który sam siebie
wywołuje, istnieje wyzwanie, aby algorytm ostatecznie zakończył się, dając poprawny
wynik. Cykliczność algorytmów rekurencyjnych eliminuje się, zapewniając, że za
każdym razem, gdy dane wejściowe do kolejnych wykonań zostaną zmniejszone
22
o pewną liczbę w taki sposób, że w pewnym momencie ostatecznie dojdzie ona do zera
i algorytm się zakończy.
Aby zaimplementować algorytm rekurencyjny, problem musi spełniać następujące
warunki:
Można go podzielić na prostszą wersję tego samego problemu
Ma jedną lub więcej przypadków bazowych, których wartości są znane
Oto prawdziwy przykład rekurencji: siedzisz w ostatnim rzędzie kina i chcesz policzyć
liczbę rzędów. Pytasz osobę siedzącą przed tobą, aby powiedziała, ile rzędów znajduje
się przed nią. Ta osoba robi to samo i prosi osobę, która siedzi przed nią i tak dalej, aż
osoba siedząca w pierwszym rzędzie zostanie zapytana. Ta osoba może odpowiedzieć
zero, ponieważ widzi, że przed nią nie ma już żadnych rzędów. To jest podstawowy
przypadek. Osoba w drugim rzędzie dodaje 1 do odpowiedzi i przekazuje ją osobie
siedzącej w trzecim rzędzie itd., dopóki odpowiedź nie dotrze do ciebie. Zauważ, że na
każdym etapie problem ogranicza się do prostszej wersji tego samego problemu.
Zobaczmy teraz przykład używając algorytmu. Po pierwsze, rozważmy moduł
(w językach programowania nazywa się to procedurą, funkcją, procesem lub rutyną),
który drukuje wyrażenie "Cześć Entuzjaści CodeIT" łącznie X razy:
module Cześć (X)
if(X<1)
return print("Cześć Entuzjaści CodeIT!")
Cześć (X - 1)
Symulujemy wykonanie, wywołując moduł Cześć z wartością X=10. Ponieważ X ma nie
mniej niż 1, nic nie robimy w pierwszym wierszu. Następnie drukujemy "Cześć
Entuzjaści CodeIT!" jeden raz. W tym momencie musimy wydrukować naszą frazę 9 razy
więcej. Ponieważ mamy teraz moduł Cześć, który może to zrobić, po prostu wywołujemy
Cześć (tym razem z X ustawionym na 9), aby wydrukować pozostałe kopie. Ten moduł
Cześć wypisze frazę raz, a następnie wywoła inną kopię Cześć, aby wydrukować
pozostałe 8. Będzie to trwało, dopóki w końcu nie wywołamy Cześć z X ustawionym na
zero. Cześć(0) nic nie robi; tylko po prostu kończy działanie. Gdy Cześć(0) zostanie
zakończone, Cześć(1) również się zakończy. Trwa to aż do pierwotnego wywołania
Cześć(10), które kończy się po wydrukowaniu łącznie 10 „Cześć Entuzjaści CodeIT!".
Oto kilka kluczowych kwestii podczas projektowania algorytmu rekurencyjnego:
Obsługuje prostą ogólną sytuację bez użycia rekurencji: W powyższym
przykładzie ogólna sytuacja to Cześć(0). Jeśli moduł zostanie poproszony
o wydrukowanie zero razy, to powróci on bez ponownego pojawiania się „Cześć
Entuzjaści CodeIT!".
Pozwala uniknąć cykli. Wyobraź sobie, że Cześć(10) wywołuje Cześć(10),
które wywołuje Cześć(10). Kończy się nieskończony cykl wywołań, co zwykle
skutkuje błędem "przepełnienia stosu" podczas działania na komputerze.
23
W wielu programach rekurencyjnych można uniknąć cykli, ponieważ każde
wywołanie modułu dotyczy problemu, który jest w jakiś sposób mniejszy lub
prostszy niż pierwotny problem. W takim przypadku na przykład X będzie
mniejszy i mniejszy przy każdym wywołaniu. Ponieważ problem staje się
prostszy i prostszy (w tym przypadku uznamy za "prostsze" wydrukowanie
czegoś zero razy zamiast 5-krotnego drukowania), w końcu dojdzie do ogólnej
sytuacji i zatrzyma rekurencję. Istnieje wiele sposobów unikania nieskończonych
cykli, ale upewnienie się, że mamy do czynienia z coraz mniejszymi lub
prostszymi problemami, jest dobrą zasadą.
Każde wywołanie modułu reprezentuje kompletną obsługę danego zadania.
Czasami rekurencja wydaje się przełamać wielkie problemy w magiczny sposób,
ale nie dzieje się tak w rzeczywistości. Kiedy nasz moduł otrzymuje argument 10,
drukujemy "Cześć Entuzjaści CodeIT!!" raz, a potem drukujemy go jeszcze 9 razy.
Część zadania możemy przekazać do wywołania rekurencyjnego, ale pierwotna
funkcja musi w jakiś sposób odpowiadać za wszystkie 10 kopii.
Jednym z powodów, dla których rekurencja jest nieco myląca, jest to, że nie jest ona
bezpośrednim naturalnym sposobem myślenia, jak w przypadku iteracji.
Algorytm rekurencyjny zakłada, że część modułu rekurencyjnego algorytmu musi zostać
wywołana początkowo spoza modułu, ale następnie wywołuje się z wnętrza modułu,
dopóki nie osiągnie stanu zakończenia. Z punktu widzenia komputera, jeśli moduł
wywołuje się lub wywołuje inny moduł, nadal jest to zwykłe wywołanie modułu, o ile
zapewnione są wymagane parametry wejściowe.
Rekurencja postępuje zgodnie z techniką dzielenia i wywołania algorytmu, co oznacza,
że problem jest dzielony na podrzędne problemy tego samego lub pokrewnego typu, aż
problem podrzędny stanie się na tyle prosty, że można go rozwiązać bezpośrednio.
Rozwiązania problemów dodatkowych są następnie łączone w celu rozwiązania
pierwotnego problemu. Jak więc podział i wywołanie odnoszą się do modułu, który sam
siebie wywołuje? W pierwszym wywołaniu modułu rekursywnego dostarczana jest
pełna wartość wejściowa, od momentu, w którym moduł zaczyna się wywoływać,
wprowadzane jest mniejsze wejście za każdym razem, gdy w każdej jednostce
wywołującej dane wejściowe są wystarczająco proste, aby rozwiązanie w jednym
wywołaniu było możliwe.
Jak się okazuje, dla dowolnego algorytmu używającego rekurencji może istnieć
równoważny algorytm, który używa iteracji. Tak więc, rekurencję można również
postrzegać jako inną formę iteracji. Niemniej jednak rekurencyjny algorytm opisujący
dany proces jest często bardziej zwięzły niż iteracyjny.
Być może najbardziej znanym przykładem problemu, który można rozwiązać za pomocą
rekurencji bardziej efektywnie niż można to zrozumieć i rozwiązać za pomocą iteracji,
są Wieże Hanoi [Wieża Hanoi to gra matematyczna lub układanka. Składa się z trzech
prętów i kilku dysków o różnych rozmiarach, które mogą przesuwać się na dowolny
24
pręt. Układanka rozpoczyna się od dysków w zgrabnym stosie w rosnącej kolejności
wielkości na jednym pręcie, najmniejszym u góry, tworząc w ten sposób stożkowy
kształt. Więcej informacji tutaj: https://pl.wikipedia.org/wiki/Wie%C5%BCe_Hanoi].
Wreszcie, jako wiedza ogólna, rekurencja jest bardzo duża w tworzeniu gier. Oto kilka
częstych zastosowań przez twórców gier do tworzenia wspólnych części gier (Źródło:
Quora.com):
Zniszczenie części ciała: algorytm rekurencyjny służy do oceny odcięć wokół
miecza w celu ustalenia, co jest nadal połączone, a co powinno odpaść
Tworzenie korytarza labiryntu: Algorytm rekurencji jest używany do losowego
wędrowania po labiryncie, dodając drzwi w określonych odstępach czasu.
Wyszukiwanie ścieżki: Algorytm rekurencyjny służy do konstruowania słownika
odległości wokół pozycji siatki w celu porównania długości ścieżek między
alternatywnymi ruchami.
1.4.8 Różnice między algorytmami iteracyjnymi a rekurencyjnymi
Zarówno iteracja, jak i rekurencja są oparte na strukturze kontrolnej: iteracja używa
struktury powtarzania; rekurencja używa struktury wyboru. Algorytm iteracyjny użyje
instrukcji pętli, takich jak pętla for, pętla lub pętla do-while, aby powtórzyć te same
kroki, podczas gdy algorytm rekurencyjny, moduł (funkcja) wywołuje się ponownie,
dopóki warunek podstawowy (warunek zatrzymania) nie zostanie spełniony.
Algorytm iteracyjny będzie szybszy niż algorytm rekurencyjny z powodu narzutów,
takich jak funkcje wywołujące i wielokrotne rejestrowanie stosów. Często algorytmy
rekurencyjne nie są wydajne, ponieważ zabierają więcej miejsca i czasu.
Algorytmy rekurencyjne są najczęściej używane do rozwiązywania skomplikowanych
problemów, gdy ich aplikacja jest łatwa i skuteczna. Na przykład algorytm Wieże Hanoi
jest łatwy dzięki rekurencji, podczas gdy iteracje są szeroko stosowane, wydajne
i popularne.
Algorytmy rekurencyjne a iteracyjne:
Podejście: w podejściu rekurencyjnym funkcja wywołuje się, dopóki warunek
nie zostanie spełniony, natomiast w podejściu iteracyjnym funkcja powtarza się,
dopóki warunek nie powiedzie się (tj. nie jest już spełniony).
Programowanie: Algorytm rekurencyjny wykorzystuje strukturę rozgałęzień,
a algorytm iteracyjny wykorzystuje konstrukcję pętli.
Czas i przestrzeń: rozwiązania rekurencyjne są często mniej wydajne pod
względem czasu i przestrzeni w porównaniu z rozwiązaniami iteracyjnymi.
Test zakończenia: Iteracja kończy się, gdy warunek kontynuacji pętli kończy się
niepowodzeniem; rekurencja kończy się, gdy rozpoznany zostanie podstawowy
przypadek. Przypomnijmy, że algorytm jest rekurencyjny, jeśli sam się wywołuje.
25
W algorytmie, który sam siebie wywołuje, warunek zakończenia musi być
poprawny, aby algorytm ostatecznie się zakończył. Aby upewnić się, że algorytm
się zakończy, dane wejściowe do kolejnego wykonania zmniejszają się za każdym
razem o pewną liczbę, tak że w pewnym momencie w końcu dojdzie do zera
i algorytm się zakończy. Wykonanie, które prowadzi do zera, jest przypadkiem
podstawowym.
Nieskończone wywołanie: nieskończona pętla występuje z iteracją, jeśli test
kontynuacji pętli nigdy nie staje się fałszywy; nieskończona rekurencja
występuje, jeśli krok rekurencji nie zmniejsza problemu w sposób zbieżny
w przypadku podstawowym.
Praktyczny problem: solenizant tnie tort urodzinowy i musi upewnić się, że wszyscy
w pokoju dostają kawałek.
Rozwiązanie 1 - iteracyjne: Solenizant z okazji urodzin używa tacy i idzie nią, dając
każdemu kawałek. Przyjmijmy algorytm iteracyjny pod nazwą daj_kawałek, który
będzie wykonywany tyle razy, ile jest osób do obsłużenia lub tyle razy, ile jest kawałków
do podania, niezależnie od tego, który warunek zostanie osiągnięty jako pierwszy.
daj_kawałek (podczas gdy istnieją ludzie do podania lub pozostały kawałki, daj
kawałek)
Rozwiązanie 2 - Rekurencyjne: Weź kawałek ciasta z tacy i podaj tacę kolejnej osobie,
która bierze plasterek z tacy i przekazuje tacę kolejnej osobie, która bierze plasterek
z tacki i przekazuje tacę do następnej osoby...
Zauważ, że za każdym razem wykonywana jest ta sama funkcja.
weźkawałek(taca)
jeśli są osoby do obsłużenia albo zostały plasterki
weź kawałek i wykonaj weźkawałek(taca)
else return
1.4.9 Projektowanie algorytmiczne: struktury danych
Do tej pory koncentrowano się na sposobie kontrolowania algorytmów poprzez
przeglądanie konstrukcji, które są używane do kontrolowania porządku i okoliczności,
w których wykonywane będą poszczególne kroki algorytmu. Powodem jest to, że wybór
najbardziej odpowiednich struktur kontrolnych jest ważny dla zaprojektowania
wydajnego i skutecznego algorytmu. Algorytmy są jednak zaprojektowane do obsługi
danych wejściowych w postaci danych, a te dane wejściowe muszą być brane pod uwagę
przy projektowaniu algorytmu.
Zwykle dane, które będą przetwarzane przez algorytm, zawierają elementy, które są ze
sobą w jakiś sposób powiązane, bardziej niż arbitralne kolekcje niepowiązanych
elementów. Weźmy na przykład dane tworzące profil użytkownika, takie jak imię, wiek,
26
adres, funkcja, numer kontaktowy. Te elementy są logicznie połączone ze sobą i nie
mogą być przetwarzane jako zbiór niepowiązanych elementów przez algorytm, ale
muszą być przetwarzane jako rekord, w którym każdy rekord zawiera informacje
o określonym użytkowniku.
Dane zorganizowane w taki sposób, aby uchwycić relacje logiczne między jego
elementami, nazywane są danymi strukturalnymi, a ich elementy wraz z ich
wzajemnymi powiązaniami tworzą strukturę danych.
Najpopularniejszym rodzajem struktury danych jest prawdopodobnie sekwencja, która
składa się z zestawu elementów posortowanych w taki sposób, że każdy element poza
ostatnim elementem w sekwencji ma następcę, a każdy element oprócz pierwszego
w sekwencji ma poprzednika. Oto kilka typowych przykładów łatwo zrozumiałych
sekwencji:
Liczba przedstawiona jako sekwencja cyfr, np. 1512000 (1 to pierwszy, 0 to
ostatni)
Słowo przedstawione jako ciąg liter, np. sekwencja (s jest pierwsza, a jest
ostatnia)
Łańcuch
Wizytownik (rolodeks)
Oczywiście sekwencja jest idealną strukturą dla elementów, które mają być
przetwarzane sekwencyjnie, jedna po drugiej. Najpopularniejszym sposobem
przechodzenia przez sekwencję jest użycie struktury pętli, takiej jak pętla while lub pętla
repeat, z tą różnicą, że używana jest pętla while, gdy liczba elementów w sekwencji nie
jest znana, podczas gdy pętla repeat jest używana dla skończonej iteracji, gdy znana jest
liczba elementów w sekwencji.
Przykładowa konstrukcja algorytmiczna do poruszania się po sekwencji jest
następująca:
otwórz sekwencję
jeśli nie osiągnięto końca sekwencji
przetwórz kolejny element z sekwencji
Niektóre sekwencje są tak częste i występują tak często, że nadano im konkretne nazwy:
27
Rysunek 8 - Tablica
Źródło: wikipedia.org, Row- and column-major order
Tablica: ciąg ustalonej długości, gdzie każdy element jest identyfikowany przez jego
pozycję w sekwencji (Goldschlager i Lister, 1988). Jest to indeksowana kolekcja
jednorodnych elementów. Sekwencja Tablica ma następujące właściwości:
Wszystkie elementy tablicy są tego samego typu danych (jednorodne)
Wymiar: jedna zwymiarowana macierz ma jeden wymiar, dwie zwymiarowane
macierze mają dwa wymiary itp.
Każdy wymiar ma rozmiar.
Rozmiar określonego wymiaru jest stały. Weź pod uwagę macierz. Ma wiersze
i kolumny. Rozmiar każdego wiersza jest równy liczbie kolumn.
Każdy element tablicy jest identyfikowany przez jego pozycję wzdłuż każdego
wymiaru (indeks). Tak więc, na rysunku, który jest tablicą dwuwymiarową,
num[1] = a11 i num[4] = a21
Rysunek 9 – Wektor
Źródło: wikibooks.org, A-level_Computing
28
Wektor: jest podobny do tablicy, ale z dodatkową funkcjonalnością. Wektory śledzą ich
rozmiar i automatycznie zmieniają rozmiar po dodaniu lub usunięciu elementu. Zaletą
wektorów jest to, że są łatwe w użyciu, śledzą ich rozmiar, są skalowalne i oferują prosty
dostęp, jak normalne tablice. Z drugiej strony, zmienne wektory mogą być powolne
(wiele nowych elementów i usuwanie starych elementów), dlatego ważne jest
zapewnienie wydajnej implementacji.
Rysunek 10 – Stos
Źródło: Creative Commons Image
Stos: ciąg o zmiennej długości, w którym elementy są dodawane i usuwane tylko na
jednym końcu. Pomyśl o tym, jako o pojemniku z elementami, które są wkładane
i usuwane zgodnie z zasadą "pierwsze przyszło pierwsze wyszło" (last-in first-out -
LIFO), ponieważ kolejność, w której elementy są usuwane, jest odwróceniem tego,
w którym zostały dodane:
Dozwolone są dwie operacje, umieszczenie elementu na stosie i usunięcie
przedmiotu ze stosu.
Elementy można dodawać i usuwać ze stosu tylko u góry (należy pomyśleć
o stosie płyt w szafce).
Push dodaje element na wierzch stosu, pop usuwa przedmiot z góry.
Jest to rekurencyjna struktura danych.
Jest albo pusty, albo ma wierzchołek, za którym znajduje się stos (wierzchołek
jest ostatnim elementem, który wszedł do struktury stosu, a więc pierwszym,
który ma zostać przeniesiony, a stos to wszystko, co zostało wstawione przed
ostatnim elementem, a więc może tylko być wydobyte, jeśli górna część zostanie
usunięta jako pierwsza).
29
Rysunek 11 – Kolejka
Źródło: Creative Commons Image
Kolejka: ciąg o zmiennej długości, gdzie elementy są zawsze dodawane na jednym
końcu i usuwane z drugiego końca. W przeciwieństwie do stosów, kolejka jest otwarta
na obu końcach i jest zgodna z zasadą pierwsze weszło-pierwsze wyszło (First-In-First-
Out - FIFO), tzn. pierwszy element danych przechowywany jest najpierw, ponieważ
kolejność usuwania elementów jest taka sama. w którym są one dodawane. Kolejka
zawiera:
• Tył, określany również jako ogon, w którym pierwszy element jest wstawiany
z jednego końca.
• Przód, określany również jako głowa, gdzie pierwszy element jest usuwany
z drugiego końca.
Przykładem kolejki jest jednopasmowa droga jednokierunkowa, gdzie pojazd, który
wjechał na nią pierwszy, zjedzie z niej również jako pierwszy. Innym prostym
przykładem jest dowolny punkt usługowy, taki jak licznik biletów, w którym pierwsza
osoba w kolejce jest pierwszą osobą, która dostała swój bilet z lady.
Rysunek 12 - Lista połączona
(Źródło: Creative Commons Image)
Lista połączona: to struktura danych, która składa się z grupy węzłów w sekwencji
i jest używana jest do przechowywania elementów, gdy nie wiemy, ile elementów
będziemy przechowywać. Lista połączona to w zasadzie ciąg struktur, w których każdy
element w strukturze ma węzeł i łącze. Węzeł zawiera elementy, które mogą być różne,
a link jest wskaźnikiem, który wskazuje na następny węzeł w sekwencji. Lista połączona
jest strukturą danych zdolną do przechowywania arbitralnie dużego uporządkowanego
zbioru elementów z minimalnym narzutem, ponieważ wielką zaletą połączonych list jest
to, że operacje wstawiania i kasowania mogą być łatwo zaimplementowane.
30
Powszechnym zastosowaniem list połączonych jest implementacja stosów i kolejek
z korzyścią pozwalającą na wstawianie elementów na początku i na końcu listy, podczas
gdy rozmiar połączonej listy nie musi być uprzednio określony. Są więc łatwiejsze do
edycji niż tablice i można je łatwo dzielić i manipulować nimi.
Prawdziwą analogią do listy połączonej jest pociąg. Każdy wagon jest połączony
z poprzednim wagonem i następnym wagonem, z wyjątkiem oczywiście z pierwszego
i ostatniego wagonu.
Rysunek 13 – Drzewo
Źródło: Creative Commons Image
Drzewo: to potężna struktura danych do organizowania przedmiotów.
W przeciwieństwie do tablicy i listy połączonej, które są linearnymi strukturami danych,
drzewo jest hierarchiczną (lub nieliniową) strukturą danych. Drzewo jest ułożone
hierarchicznie i służy do wyrażania hierarchicznych relacji, takich jak struktura
organizacyjna. Jego węzły służą do reprezentowania danych lub punktów rozgałęzienia.
Węzeł drzewa ma część danych i odwołania do jej lewego i prawego węzła podrzędnego.
Węzeł na najwyższym poziomie hierarchii jest nazywany korzeniem, podczas gdy
węzły na najniższym poziomie, gdzie kończy się drzewo, nazywane są liśćmi. Gałęzie
drzewa reprezentują relacje logiczne między elementami na jednym poziomie hierarchii
i innymi elementami na następnym poziomie. Drzewo jest skonstruowane w taki
sposób, że każdy węzeł jest jednocześnie korzeniem innego drzewa (poddrzewo). Tak
więc, drzewo jest rekurencyjną strukturą danych, ponieważ każde drzewo można
zdefiniować w kategoriach innych drzew. Pod względem właściwości:
• Drzewo bez węzłów jest nazywane zerowym lub pustym drzewem.
• Drzewo, które ma węzły (nie jest zerowe / puste) będzie miało węzeł główny i jeden
lub więcej poziomów węzłów z zestawami gałęzi tworzących hierarchię.
31
PDF to format oparty na drzewach. Ma węzeł główny, po którym następuje węzeł
katalogu, a następnie węzeł stron z kilkoma węzłami stron podrzędnych.
Rysunek 14 –Tablica mieszająca
Źródło: Creative Commons Image
Tablica mieszająca: są parami klucz-wartość, co oznacza, że dla każdego klucza (klucz
może być nazwą, tytułem pracy, samochodem itd.) istnieje powiązana wartość,
a mechanizm, który przypisuje klucze do wartości, jest nazywany funkcją mieszającą. Są
one nazywane kluczami, ponieważ są unikalnymi identyfikatorami dla wartości, do
której są powiązane. Innymi słowy, za pomocą klucza funkcja mieszająca może
identyfikować (odblokowywać) powiązaną wartość. Tablica mieszająca (nazywana
również mapą skrótu) jest strukturą danych używaną do implementacji tablicy
asocjacyjnej, struktury, która może odwzorowywać klucze na wartości. Organizuje
elementy według pewnej właściwości, która jest łatwa do znalezienia, nawet jeśli nie
jest bezpośrednio związana z przedmiotem, a nie jest pełnym opisem. Tabela skrótu
pomaga zredukować możliwe miejsca, w których można znaleźć przedmiot. Tablica
mieszająca jest wypełniana przez funkcję "mieszającą". Funkcja mieszająca to sposób
mapowania elementów na lokalizacje. Jako przykład rozważ funkcję mieszania, która
pomoże Ci zdecydować (i później wyszukać/znaleźć) półkę, na której zamierzasz
umieścić książkę. Funkcja mieszająca to funkcja matematyczna, która otrzymuje
przedmiot i zapewnia lokalizację do przechowywania przedmiotu, jednocześnie
mapując każdy przedmiot do odpowiedniej lokalizacji. Tabele skrótu umożliwiają
wstawianie/ wyszukiwanie i usuwanie pozycji poprzez dostarczanie powiązanych
kluczy dla każdego elementu. Klucze są przekształcane na liczby całkowite za pomocą
funkcji mieszającej, a elementy są przechowywane w tablicy za pomocą indeksów
odpowiadających tym liczbom całkowitym (klucze mieszane).
skrót klucze funkcja skróty
32
Rysunek 15 - Sterta
Źródło: Creative Commons Image
Sterta: to zbiór, który pozwala na wstawianie przedmiotów i znalezienie najmniejszego
przedmiotu i/lub jego usunięcie. Wyobraź sobie strukturę danych sterty, jako
implementację "kolejki priorytetowej", w której zamiast dołączania do kolejki na jej
końcu, osoba lub obiekt mogą być wstawione dalej w kolejce w zależności od ich
priorytetu. Sterty są szczególnie użyteczne, gdy potrzebna jest wielokrotna kwerenda
o minimalną wartość z dynamicznego zbioru wartości. Algorytmem matematycznym
wykorzystującym strukturę sterty jest algorytm Dijkstry służący do znajdowania
najkrótszych ścieżek między węzłami na grafie, które mogą reprezentować na przykład
sieci drogowe. Algorytm Dijkstry może być użyty do znalezienia najkrótszej ścieżki
między dwoma węzłami. Szczegółowe wyjaśnienie algorytmu Dijkstry można znaleźć na
stronie https://pl.wikipedia.org/wiki/Algorytm_Dijkstry
Rysunek 16 – Grafy
Źródło: Creative Commons Image
33
Grafy: struktury danych grafu zapewniają łatwą reprezentację rzeczywistych relacji
między różnymi typami danych (węzłów) i są w ten sposób wykorzystywane do
reprezentowania sieci. Graf składa się z wierzchołków, węzłów lub punktów
połączonych krawędziami, łukami lub liniami. Graf może być nieukierunkowany
(relacja istnieje w obu kierunkach, tj. Jan jest przyjacielem Pawła oznacza, że Paweł jest
przyjacielem Jana), co oznacza, że nie ma rozróżnienia między dwoma wierzchołkami
związanymi z każdą krawędzią, lub jego krawędzie mogą być skierowane (może to być
relacja w jedną stronę lub dwukierunkowa, ale musi być jednoznacznie określona)
z jednego wierzchołka na drugi. Struktura odnośników strony internetowej może być
reprezentowana przez graf skierowany, w którym wierzchołki reprezentują strony
internetowe, a skierowane krawędzie oznaczają łącza z jednej strony do drugiej. Istnieją
różne sposoby tworzenia grafów w algorytmicznej konstrukcji. Stosowana struktura
danych zależy zarówno od struktury grafu, jak i od metody użytej do manipulowania
nim. Zwykle grafy są konstruowane przy użyciu kombinacji struktur list i macierzy. Na
grafach można wykonywać następujące operacje:
Dodawanie:
o Dodaj wierzchołki do grafu o Utwórz krawędzie między dwoma danymi wierzchołkami grafu
Usuwanie:
o Usuń wierzchołki z grafu o Usuń krawędzie między dwoma danymi wierzchołkami grafu
Szukanie
o Sprawdź, czy graf zawiera określoną wartość o Sprawdź, czy istnieje połączenie między dwoma danymi węzłami grafu
1.5 Bibliografia
BBC Bitesize KS3 Computer Science Algorithms
[https://www.bbc.co.uk/education/topics/z7d634j] (dostęp: 8 lutego 2018)
Goldschlager L, Lister A., (1988), Computer Science: A Modern Introduction, Second
edition, Prentice Hall International Series in Computer Science.
Quora.com, Questions and Answers on “Data Structures”, “Algorithms”, “Programming”
[http://quora.com] (dostęp: 8 lutego 2018)
34
2 Wprowadzenie do programowania (Elżbieta Szczygieł, Łukasz Kłapa)
2.1 Definicja programowania
Intuicyjne zdefiniowanie programowania nie nastręcza większych trudności i przywodzi
na myśl wprowadzanie do komputera poleceń w formie kodów, wyrażonych
w specyficznym języku programowania. Z dokładnym określeniem istoty
programowania jest jednak nieco trudniej. Wynika to z konieczności użycia kilku pojęć,
które powinny zostać uprzednio zdefiniowane (m.in.: algorytmu, programu, języka
programowania) oraz stopnia sformalizowania przekazywanej treści.
Na potrzeby niniejszej publikacji przyjęto następującą definicję programowania:
Zespół czynności związanych z przygotowaniem programu dla maszyny cyfrowej (Encyklopedia, 1988).
Ponieważ najczęściej maszyną tą jest komputer, w dalszej części podręcznika termin ten
będzie używany na oznaczenie wszelkiego rodzaju elektronicznych maszyn cyfrowych.
Pod określeniem program rozumie się zapis algorytmu określonego zadania w postaci
ciągu deklaracji i instrukcji w jednym z języków programowania (Encyklopedia,
1988). Algorytmem określa się opis rozwiązania problemu (zadania) wyrażony za
pomocą takich operacji, które wykonawca algorytmu rozumie i potrafi wykonać
(Encyklopedia, 1988). Zatem, w programowaniu ma się do czynienia z zadaniem, które
należy rozwiązać w określony sposób. Sposób ten jest wyrażony w specyficznej formie
będącej językiem programowania. Język ten jest przeznaczony do zapisu programów
przetwarzania danych przez komputery (Encyklopedia, 1988). Mając na uwadze te
założenia, pojęcia związane z programowaniem można uporządkować w następujący
sposób (Rysunek 1).
35
Rysunek 1. Powiazania między pojęciami programu, programowania, algorytmu i języka programowania
Źródło: opracowanie własne na podstawie (Encyklopedia, 1988)
Programowanie będzie więc zapisywaniem algorytmu przekazującego opis rozwiązania
zadanego problemu za pomocą nośnika treści opisu, czyli języka programowania.
W dalszej części podręcznika zaprezentowane zostaną języki programowania. Warto
w tym miejscu jednak wspomnieć, że jak każdy język, również język programowania ma
swój alfabet, którym najczęściej są litery alfabetu łacińskiego, cyfry arabskie oraz znaki
operacji arytmetycznych i logicznych, a także znaki specjalne. Polecenia zapisane za
pomocą alfabetu języka programowania są z kolei tłumaczone przez komputer na jego
wewnętrzny język, który przybiera postać kodu binarnego (dwójkowego). W zapisie tym
występują tylko dwie liczby „0” oraz „1” i to za ich pomocą przekazywane są informacje
do komputera. Ponieważ za pomocą dwóch symboli - tj. „0” i „1” – czyli tzw. bitu, można
przekazać tylko dwie informacje, system binarny poprzez łączenie bitów w grupy
umożliwia przekazywanie bardziej szczegółowych komunikatów (Wałaszek, 2018).
W ten sposób, za pomocą grupy bitów wyrażonej symbolem złożonym, komputer ma
możliwość wykonania żądanej operacji. Przykładowo, pojedyncze litery alfabetu
zapisane zostaną w następujący sposób (BiMatrix, 2018):
„a” - 01100001
„b” - 01100010
Wyraz „jestem” – oznaczony zostanie poprzez zapis „01101010 01100101 01110011
01110100 01100101 01101101”, a wyrażenie „jestem tutaj” – „01101010 01100101
01110011 01110100 01100101 01101101 00100000 01110100 01110101 01110100
01100001 01101010”.
Posługując się językiem programowania można stworzyć dowolne komendy, dzięki
którym komputer będzie mógł wykonać zadane polecenie. Ważne jest by polecenia były
algorytmami, a więc by spełniały poniższe warunki (Encyklopedia, 1988):
• Programowanie - zestaw działań związanych z przygotowaniem programu na urządzenie cyfrowe.
Program
• Program - zapis algorytmu wykonania określonego zadania w postaci sekwencji deklaracji i instrukcji w danym języku programowania.
Algorytm• Algorithm - opis rozwiązania
problemu (zadania) wyrażony w postaci zrozumiałych i możliwych do wykonania operacji.
Język programowania
• Język programowania - jasno określony zestaw znaków, reguł i konwencji determinujących tworzenie specyficznych wyrażeń i sposobu ich używania podczas tworzenia programu komputerowego.
36
• miały wyraźnie określony początek,
• miały wskazaną operację, czyli czynność, od której rozpoczyna się użycie algorytmu,
• miały precyzyjnie określone wykonanie dalszych czynności podane w dokładnej
kolejności,
• miały wskazany koniec (zakończenie realizacji algorytmu).
Należy jednak zaznaczyć, że wprowadzanie zapisu rozwiązania danego problemu do
komputera jest zaledwie kodowaniem, nie zaś programowaniem (Soukup, 2015).
Programowanie jest pojęciem o wiele szerszym i zawiera w sobie analizę problemu do
rozwiązania oraz pomysł, by w szukaniu tego rozwiązania wykorzystać dostępne
technologie cyfrowe.
2.2 Historia programowania
Historia programowania rozpoczęła się na długo przed zbudowaniem pierwszego
komputera, kluczowy udział we jej tworzeniu miała kobieta wychowana w domu
jednego z największych poetów romantyzmu, zaś pierwszy język programowania został
zapisany w zwykłym pamiętniku. Historia programowania i komputerów związana jest
z historią matematyki, co nie wydaje się już takie odległe. Już w starożytności
konstruowano różnego rodzaju liczydła czy tablice ułatwiające kalkulacje czy pomiary.
W XVII w., J. Neper, twórca logarytmu opracował tzw. pałeczkę Nepera służącą do
obliczenia logarytmów i znacznie skracającą czas dokonywania obliczeń (Encyklopedia,
1988). Kolejne lata przyniosły opracowania pierwszych maszyn liczących,
skonstruowanych oddzielnie przez W. Schickharda (w 1623), B. Pascala (w 1645) czy
G. W. Leibniza (w 1671). Niestety, żadna z tych maszyn nie mogła zostać nazwana
automatyczną. Do pomysłu skonstruowania takiej, na początku XIX w., wrócił
J. M. Jacquard, prezentujący na wystawie w Paryżu karty dziurkowane umożliwiające
sterowanie krosnem tkackim (Heath, 1972). Technikę tę wykorzystał Ch. Babbage,
którego uznaje się za wynalazcę maszyny automatycznej. W roku 1833 opracował on
projekt maszyny analitycznej, oparty o wykorzystanie kart dziurkowanych, jednak
fizycznie maszyna ta nie powstała ze względu na trudności techniczne i finansowe.
Niemniej jednak, jego pomysły skłoniły A. A. Lovelance, córkę poety G. G. Byrona do
opracowania idei tworzenia wymiennych programów w tego typu maszynach. Z tej racji,
na podstawie notatek A. A. Lovelace przypisuje się jej autorstwo pierwszego programu
komputerowego. Sama spekulowała na temat możliwości gry w szachy z maszynami
cyfrowymi, czy też możliwości śpiewania przez nie (The history of computer
programming). Dalsze prace nad rozwojem maszyn cyfrowych doprowadziły do
powstania elektronicznego systemu tabulacyjnego, dzięki któremu maszyny mogły
odczytywać dane. Było to zasługą H. Hollerith’a, który opracował i zastosował ten
system w maszynach liczących. Po sukcesach w zastosowaniu jego maszyn podczas
spisu powszechnego w USA, założył on w 1996 r. Tabulating Company Machine, które
zajęło się produkcją masową maszyn liczących. Wiele lat później, to przedsiębiorstwo
37
wraz z innymi dało początek koncernowi IBM®. Późniejszy rozwój informatyki i samego
programowania przebiega wielowątkowo, choć w dużej mierze historia ta związana jest
z przebiegiem II Wojny Światowej i działaniami podejmowanymi na polu naukowym by
rozwiązywać problemy logistyczne i militarne. Rozwój nurtu badań operacyjnych
i potrzeba prowadzenia coraz bardziej skomplikowanych i obszernych obliczeń
spowodowały, że w 1942 r. opracowano pierwszy komputer cyfrowy (Atanasoff-Berry
Computer - ABC) służący do obliczania równań liniowych oraz – rok później - komputera
Colossus służącego do odszyfrowywania niemieckich wiadomości (The history of
computer programming). Późniejsze prace programistyczne odbywały się na urządzeniu
EDSAC, zaprojektowanym i stworzonym w 1949 r., będącym kontynuacją
i udoskonaleniem projektów komputerów ENIAC i EDVAC, opracowanych tuż po
zakończeniu działań wojennych (Encyklopedia, 1988). Przełomowym wydarzeniem dla
historii programowania okazało się stworzenie pierwszego wysokiej klasy języka
programowania – Fortran, którego twórcą był w roku 1954 J. Backus pracujący dla
koncernu IBM®. Rozwój teorii i praktyki programowania przyniósł wzrastającą liczbę
wykorzystanych języków. Po sukcesie języka Fortran, przyszła kolej na języki
o wyższych stopniach uniwersalności takich jak ALGOL 60, czy o specyficznych celach
przeznaczenia jak COBOL. Przełomowym w tym zakresie okazało się spopularyzowanie
komputerów osobistych w początku lat 80-tych XX w. Wykorzystanie dyskietek do
przenoszenia danych wymusił w tym względzie bardzo duże zmiany, również co do
rozwoju ich ochrony. W roku 1983, F. Cohen stworzył pierwszy wirus komputerowy
przenoszony za pomocą dyskietek, tylko w tym celu, by pokazać taką możliwość. Nie
wszystko w programowaniu było i jest związane z pozytywnymi celami. Jak każdy
rozwój, również i rozwój programowania narażony jest na wpływ niekorzystnych czy
wręcz szkodliwych czynników. Jest to aktualne szczególnie teraz, kiedy komputery
i różnego rodzaju urządzenia cyfrowe weszły masowo do codziennego użycia, nie tylko
jako one same, ale również jako części innych urządzeń. Również stworzenie i rozwój
sieci internetowej wymusza nieustanne zmiany w obszarze programowania, a co za tym
idzie – w sposobie myślenia i patrzenia na pojawiające się problemy cyfrowego świata.
2.3 Umiejętności programistów i proces ich opracowywania
Definiowanie umiejętności wymaga odróżnienia ich od pojęcia kompetencji, z którymi
bywa utożsamiane. To ostatnie definiowane jest jako zdolność do czegoś, zależna od
wiedzy, umiejętności i sprawności oraz od stopnia przekonania o potrzebie
posługiwania się tą zdolnością (Jurgielewicz-Wojtaszek, 2012). Jest to pojęcie szerokie
i zawiera w sobie pojęcie umiejętności. Zdefiniować je można, jako praktyczną
znajomość czegoś i biegłość w czymś (Słownik, 2018) lub też spójny zestaw
zdolności do wykonania czegoś (Routier, Mathieu, Secq, 2001). Umiejętności
stanowią jeden z komponentów kompetencji i wpływają na możliwość zrealizowania
działania na podstawie otrzymanego zadania (Rysunek 2).
38
Rysunek 2. Definicja kompetencji w ujęciu behawioralnym
Źródło: (Adamczyk, 2014)
Jak pisał R. Boyatzis (1982), programista pracujący w zaciszu swojego domu będzie
potrzebował innych umiejętności niż neurochirurg wykonujący wspólnie z innymi
lekarzami skomplikowaną operację. Choć obaj powinni mieć zdolność do diagnozy
problemu, logicznego myślenia czy szukania dodatkowych informacji by rozwiązać
problem, to jednak pierwszy winien mieć umiejętność eksperymentowania
i komunikowania z użytkownikiem programu, zaś drugi musi umieć podejmować
szybkie decyzje i dobrze komunikować się z zespołem. To oznacza, że każdy z nich
będzie mieć oprócz wspólnych umiejętności, również te specyficzne, które winien
rozwijać. Rysunek 3 obrazuje powiązanie między programistą, a wytworem jego pracy,
nośnikiem oraz użytkownikiem.
Rysunek 3. Powiązanie między programistą a użytkownikiem programu
Źródło: opracowanie własne na podstawie (Rogalski, Samurçay, 1990)
Definiując umiejętności, jakie potrzebuje programista, można wskazać obszary
(kompetencje), których umiejętności te dotyczyć będą. W literaturze przedmiotu
powstało szereg klasyfikacji zarówno kompetencji, jak i szczegółowych umiejętności
w ramach każdej z nich. Biorąc za podstawę aktualny stan wiedzy, weryfikuje się często
w badaniach, które z nich są najistotniejsze w pracy programisty i które winny zostać
rozwijane. W Tabeli 1 przedstawiono podsumowanie najpopularniejszych klasyfikacji
kompetencji programistów oraz grup umiejętności, z jakich się składają. Przykładowo,
jedne z ostatnich badań (Manawadu, Johar, Perera, 2015), wskazały, że spośród siedmiu
obszarów kompetencji, programiści będą musieli rozwinąć przede wszystkim:
PROGRAM
UŻYTKOWNIK KOMPUTER
PROGRAM
ZADANIE
AKCJA
REZULTAT
WIEDZA
UMIEJĘTNOŚCI POSTAWA
KOMPETENCJA
39
wymagania użytkownika, proces tworzenia oprogramowania oraz analiza i projektowanie
systemu.
Tabela 1. Zestawienie kompetencji i umiejętności programisty
Adamczyk, 2014 Turley, Bieman, 1994 Manawadu, Johar, Perera, 2015 Zarządzanie kodem źródłowym (Stosowanie prawidłowego nazewnictwa i komentarzy, korzystanie z gotowych bibliotek i dążenie do osiągania możliwie dużej przenośności): - Stosowanie wzorców - Stosowanie bibliotek - Stosowanie algorytmów - Korzystanie z IDE (Integrated Development Environment - zintegrowane środowisko programistyczne) - Stosowanie przenośności Zarządzanie wiedzą (Zarówno efektywne zdobywanie wiedzy samodzielnie, jak i dzielenie się nią z innymi): - Stosowanie reguł pisania kodu - Uczenie się od innych - Efektywne zdobywanie wiedzy - Dzielenie się wiedzą Zarządzanie własną pracą (Samoorganizacja pracy, objawiająca się dotrzymywaniem założonych terminów): - Organizacja pracy własnej - Stosowanie wersjonowania - Korzystanie z testów Zarządzanie wymaganiami (Orientacja na klienta i tworzenie wszystkich rozwiązań kierując się oczekiwaniami użytkownika końcowego): - Orientacja na klienta
Realizacja zadania: - Wykorzystanie/Przywracanie kodu - Metodyczne rozwiązywanie problemów - Umiejętności/Techniki - Zapisywanie/automatyzacja testów za pomocą kodu - Doświadczenie - Otrzymywanie niezbędnych danych - Szkolenie/nauka - Korzysta z czytnika kodu - Wykorzystanie nowych metod lub narzędzi - Harmonogramy i szacunki - Wykorzystanie prototypów - Wiedza, - Komunikacja/Wykorzystanie zorganizowanych technik komunikacji Cechy osobiste: - Chęć wniesienia wkładu w naukę - Duma z jakości i wydajności - Poczucie zabawy - Brak wybujałego ego - Wytrwałość - Pragnienie poprawy rzeczy - Postawa proaktywna/inicjowanie /dążenie - Szerokie ujęcie spraw i posiadanie wpływu - Pragnienie tworzenia - Dokładność - Poczucie misji - Siła przekonań - Łączenie celów osobistych i zawodowych - Proaktywna rola w zarządzaniu Umiejętności sytuacyjne: - Jakość - Skoncentrowanie się na potrzebach użytkownika lub klienta - Myślenie - Wybór eleganckich i prostych rozwiązań - Innowacja - Szczegółowość - Zmysł projektowy - Odpowiedzialność Umiejętności interpersonalne: - Korzystanie z pomocy - Pomaganie innym - Orientacja na zespół - Gotowość do konfrontacji z innymi
Programowanie Umiejętność pisania programów dla wielu platform, urządzeń i kanałów z możliwością dostosowania do dowolnego języka programowania. Informatyka Umiejętność integrowania zasad informatyki w celu uzyskania namacalnych, fizycznych artefaktów. Analiza i projektowanie systemów Umiejętność analizowania skomplikowanych operacji przemysłowych i biznesowych w celu znalezienia sposobów ich ulepszania lub rozwiązywania w sposób systematyczny. Proces tworzenia oprogramowania Umiejętność efektywnego wykorzystania procesu tworzenia oprogramowania lub cyklu życia, który jest strukturą narzuconą w rozwoju oprogramowania. Wymagania użytkownika Umiejętność rozumienia oczekiwań użytkowników oprogramowania i dostarczania ich zgodnie z oczekiwaniami. Wykorzystanie narzędzi programowych Możliwość korzystania z szeregu narzędzi programowych lub tworzenia własnych narzędzi celem zwiększenia produktywności w zadaniach wykonywanych w inżynierii oprogramowania. Dostarczanie wysokiej jakości kodu Zdolność do dostarczania wysokiej jakości kodu zgodnego z najlepszymi praktykami i zasadami uwzględniając możliwość manipulacji kodu w celu uniknięcia błędów.
Źródło: opracowanie własne na podstawie (Adamczyk 2014; Turley, Biema, 1994; Manawadu, Johar, Perera, 2015)
40
S. Goel (2010) wskazuje, że aktualnie pracownicy zatrudnieni w sektorze
informatycznym winni posiadać umiejętności w następujących obszarach:
1) Rozwiązywanie problemów,
2) Analiza/umiejętności metodyczne,
3) Podstawowa znajomość inżynierii oprogramowania,
4) Wiedza programistyczna,
5) Umiejętność pracy w zespole,
6) Znajomość języka angielskiego,
7) Umiejętność prezentacji,
8) Praktyczne doświadczenie inżynierskie,
9) Umiejętności przywódcze,
10) Komunikacja.
Warto zwrócić uwagę na znajomość języka angielskiego, jako na umiejętność
nietechniczną, jednak kluczową w pracy osób związanych ze środowiskiem
informatycznym. Autor ten wskazuje za Curriculum Guidelines for Undergraduate Degree
Programs in Software Engineering (2004), że programista winien:
1) wykazać się znajomością wiedzy i umiejętności inżynierii oprogramowania oraz
profesjonalnych zagadnień niezbędnych do rozpoczęcia praktyki jako inżynier
oprogramowania,
2) pracować indywidualnie i jako część zespołu w celu opracowania i dostarczenia
wysokiej jakości artefaktów oprogramowania,
3) pogodzić sprzeczne cele projektu, znajdując akceptowalne kompromisy
w zakresie ograniczeń kosztów, czasu, wiedzy, istniejących systemów
i organizacji,
4) projektować odpowiednie rozwiązania, w co najmniej jednej domenie aplikacji
przy użyciu metod inżynierii oprogramowania, które integrują kwestie etyczne,
społeczne, prawne i ekonomiczne,
5) rozumieć i stosować aktualne teorie, modele i techniki, które stanowią
podstawę do identyfikacji i analizy problemów, projektowania
oprogramowania, opracowywania, wdrażania, weryfikacji i dokumentacji,
6) rozumieć i doceniać znaczenie negocjacji, skutecznych nawyków pracy,
przywództwa i dobrej komunikacji z interesariuszami w typowym środowisku
programistycznym,
7) uczyć się nowych modeli, technik i poznawać technologie, jakie się pojawiają
oraz doceniać konieczność ciągłego doskonalenia zawodowego.
Wskazane obszary kompetencji programistów oraz konkretne umiejętności, jakie winni
posiadać odnoszą się do różnych aspektów pracy twórców oprogramowania. Wynika
stąd wyraźnie, że praca profesjonalnego programisty daleka jest od stereotypowego
obrazu człowieka pochylonego nad klawiaturą, pracującego w pojedynkę, który
komunikuje się tylko z komputerem. Rozwój umiejętności programistów jest w tym
41
zakresie nieodzowny, tym bardziej, że środowisko ich pracy zmienia się bardzo szybko.
Obecnie nie wystarczy już sama umiejętność pisania kodu (czyli potocznie, tłumaczenie
na język komputera poszczególnych komend związanych z zadaniem), ale wymagane są
silne umiejętności komunikacyjne by zrozumieć potrzebę oraz móc przekazać
konkretną propozycję programu odpowiadającego na nią. Podobnie też ze względu na
turbulencje otoczenia, wielość zadań i potrzeb, programista winien posiadać
umiejętność dobrego zarządzania czasem oraz charakteryzować się elastycznością
w podejściu do realizowanych zadań. Akcentowanie miękkich umiejętności w pracy
programisty nie oznacza zmniejszenia istotności tych w obszarze technicznym, ale
wskazuje na wzrost wymagań dotyczących umiejętności programistów.
Wspomniany już dokument: Curriculum Guidelines for Undergraduate Degree Programs
in Software Engineering (2004) oraz jego późniejsza wersja (2014) a także Curriculum
Guidelines for Graduate Degree Programs in Software Engineering, (2010), są ciekawymi
propozycjami programów kształcenia przyszłych programistów rozwijanymi przez IEEE
Computer Society (https://www.computer.org/) wraz z Association for Computing
Machinery (https://www.acm.org/). Programy te bazują na The Guide to the Software
Engineering Body of Knowledge (SWEBOK Guide) i opisują aktualny stan wiedzy
w 15 obszarach kompetencji związanych z inżynierią oprogramowania. Stanowić one
mogą podstawę do uzupełnienia posiadanych już kwalifikacji przez programistów lub
też – ze względu na swoja budowę – powinny zostać wzięte pod uwagę w procesie
formalnego kształcenia programistów.
2.4 Zmienne i stałe
Programowanie może być postrzegane jako sposób "tłumaczenia" przypadków
z rzeczywistego świata na formę kodu oprogramowania. W przypadku tego procesu
istnieje jednak potrzeba modelowania i naśladowania tego, co jest znane z otaczającego
środowiska. Istnieje potrzeba, aby kod mógł wymieniać określone rzeczy i wykonywać
różne operacje na tych cechach. Załóżmy przez chwilę, że mamy do czynienia z aplikacją,
która ma przechowywać podstawowe informacje o twoich przyjaciołach, w tym ich imię,
nazwisko i datę urodzin. Nie skupiając się zbytnio na szczegółach, potrzebowalibyśmy
odpowiednich miejsc w kodzie, które byłyby w stanie przechowywać te informacje
i mogłyby wykonywać różne operacje na zbiorze danych. Jeśli rozważamy wyżej
wymienione trzy cechy, można bezpiecznie założyć, że będą one różne dla różnych osób.
Innymi słowy, będą miały zmienne znaki i do tego właśnie wykorzystywane są zmienne.
W programowaniu komputerowym zmienną jest lokalizacja zdolna do przechowywania
tymczasowych danych w programie. Dane te można następnie modyfikować,
przechowywać lub wyświetlać w razie potrzeby
(https://www.computerhope.com/jargon/v/variable.htm).
42
Wracając do naszego przykładu, zmienne mogą być używane do przechowywania
imienia, nazwiska i daty urodzenia, jak wyjaśniono powyżej. W rezultacie, gdybyśmy
mieli wprowadzić dane nowej osoby, oprogramowanie musiałoby zapytać nas o te
informacje, przechowując je wewnętrznie jako zmienne. Być może zastanawiasz się,
dlaczego uważamy takie rzeczy, jak data urodzin za coś zmiennego? Nie ma wątpliwości
co do tego, że data urodzenia nie może się zmienić w rzeczywistości. "Zmienność"
odnosi się tu ściśle do sposobu, w jaki kod obsługuje dane.
Zmienne mogą być nazywane na różne sposoby, ale zgodnie z ogólną regułą, ich nazwy
powinny zaczynać się od litery, a nie cyfry (jest kilka wyjątków, ale ponieważ wykracza
to poza zakres tego podręcznika, nie krępuj się zgłębić ten temat we własnym zakresie!).
Oprócz tego, każdy język programowania może mieć różne konwencje dotyczące tego,
jak można nazywać zmienne, które z nich są zarezerwowane (i dlatego nie mogą być
używane), czy zmienne są poprzedzone danym znakiem specjalnym (np. $) oraz inne
użyteczne informacje w tym zakresie.
Na razie, a także w przyszłych rozdziałach tego podręcznika, użyjemy tak zwanego
pseudokodu, czyli kodu, który nie jest rzeczywistym językiem programowania, ale raczej
naśladuje jego semantykę. Wracając do naszego przykładu z osobami i ich datami
urodzenia, możemy wywnioskować, że nasz kod (lub raczej pseudokod), który wykonuje
przypisanie zmiennych, może mieć następującą strukturę:
firstname = “John”
lastname = “Doe”
birthday = “1998-02-20”
Z powyższego przykładu widać, że do wykonania faktycznego przypisania użyto znaku
równości. Zaczynając od lewej, mamy nazwę zmiennej, następnie znak równości,
a następnie wartość. Dzięki temu możemy używać imienia w innych miejscach kodu, na
przykład, aby wyświetlić wiadomość, że <firstname> <lastname> ma datę urodzenia
w <birthday>.
Na tym etapie warto wspomnieć, że zmienne można nazywać na wiele sposobów,
ponieważ język programowania nakłada pewne konwencje, w tym także różne podejścia
do nazywania zmiennych. Innymi przykładami zmiennych imienia mogą być
firstname, firstName, first_name, FirstName i tak dalej. Ważne jest, aby zmienna
informowała o posiadanych informacjach. Zmienne w powyższym przykładzie mogły
być nazwane jako a, b i c, ale byłoby to trudne do zrozumienia w innej części kodu.
Innym ważnym aspektem dotyczącym zmiennych jest to, że w niektórych językach
programowania wymagane jest również określenie ich typu. Typ może być rozumiany
na przykład jako tekst, liczba, data lub wartość logiczna. Zmienne typu logicznego mogą
przechowywać tylko dwie wartości: true lub false. Jest to jeden z najczęściej
używanych typów zmiennych. Na przykład, możemy mieć zmienną o nazwie
birthdaytoday, co byłoby prawdą, jeśli ktoś z naszej listy ma dziś urodziny, lub false
w przeciwnym wypadku.
43
Istnieje również specjalna zmienna, która jest nazywana tablicą. Reprezentuje zbiór
różnych elementów i może mieć prostą postać, np. Owoce = [jabłko, gruszka,
śliwka] lub bardziej złożoną:
żywność = [
owoce => [jabłko, gruszka, śliwka],
warzywa => [szparagi, ziemniaki, pomidory]
]
Za pomocą tablic możemy przechowywać elementy w hierarchiczny sposób, a następnie
wykorzystywać je w naszym kodzie, pisząc żywność[owoce][0], która ma wartość
jabłko. Jest tak, ponieważ odnieśliśmy się do pierwszego elementu z tablicy owoców
zawartych w naszej tablicy żywnościowej. Być może zauważyłeś, że indeks, którego
użyliśmy, jest równy 0. Wynika to z tego, że w programowaniu zazwyczaj zachowujemy
numerację, która jest określana jako indeksowanie oparte na 0. Na tym etapie wystarczy
pamiętać o tablicach i ich podstawowej strukturze. Aby uzyskać więcej informacji,
zapoznaj się z sekcją "Dalsze czytanie".
Teraz, gdy poznaliśmy krótko kwestię zmiennych, nadszedł czas, abyśmy przenieśli się
do stałych. Stałe mogą być postrzegane jako zmienne z jednym ważnym wyjątkiem.
Mimo że mają podobne koncepcje (konwencje nazewnictwa, typy i tym podobne), ich
celem jest utrzymanie tej samej wartości w całym wykonaniu kodu. Innymi słowy, one
się nie zmieniają. Stałe są zwykle pisane wielkimi literami, więc jeśli mamy ograniczyć
maksymalną liczbę wpisów urodzinowych, które może przechowywać nasz kod,
moglibyśmy użyć stałej MAX_RECORDS = 100. Użycie stałych upraszcza proces pisania
kodu. Na przykładzie MAX_RECORDS ta stała będzie używana wielokrotnie w naszym
kodzie. Jeśli w dowolnym momencie w przyszłości zdecydujemy się podnieść limit
rekordów do 200, musimy tylko zmienić wartość tej stałej, a wymagane zmiany będą
natychmiast dostępne we wszystkich innych miejscach kodu.
2.5 Obiekty
Kiedy mówimy o obiektach w programowaniu, odnosimy się do nich w zakresie
tzw. programowania obiektowego. Mówiąc prościej, obiekt programowania przenosi
informacje o obiekcie z rzeczywistości i może to być praktycznie wszystko.
W podrozdziale, w którym omawialiśmy zmienne, mówiliśmy tylko o rzeczach takich jak
imię, nazwisko i data urodzin. To oczywiście opisuje osobę. Czy to możliwe, że osoba
może stać się przedmiotem, gdy mówimy o programowaniu? Zdecydowanie! Osoba
może stać się obiektem, który ma swoje właściwości. Te właściwości obiektu są
rodzajami zmiennych, które są powiązane z tym konkretnym obiektem. Możemy dodać
więcej właściwości, takich jak np. wzrost, waga czy płeć konkretnej osoby. Niezależnie
od tego, jakie cechy danej osoby są potrzebne w naszym oprogramowaniu, można ją
wdrożyć za pomocą właściwości. Prowadzi to nas do pytania - dlaczego nie możemy po
prostu użyć zmiennych? Jaki jest powód tworzenia obiektów?
44
Bez wątpienia programowanie obiektowe zrewolucjonizowało sposób, w jaki dzisiaj
piszemy kod. Ta koncepcja pochodzi z lat sześćdziesiątych i języka programowania
Simula 67 stworzonego przez Ole-Johana Dahla i Kristen Nygaard z Norweskiego
Centrum Obliczeniowego w Oslo. Naukowcy ci potrzebowali sposobu na symulowanie
wielu obiektów z życia codziennego w taki sposób, aby obiekty te mogły być
odpowiedzialne za własne zachowanie. Oczekiwany wynik mógł zostać osiągnięty po
prostu za pomocą zmiennych, które okazały się wysoce nieefektywne. To prowadzi nas
z powrotem do samego rdzenia programowania obiektowego, w którym obiekty mogą
nie tylko mieć swoje własne właściwości, ale w przeciwieństwie do zmiennych, są
w stanie podjąć określone działania.
Wróćmy do naszego przykładu, kiedy dana osoba jest przeniesiona do obiektu w sensie
programowania. Jakie działania możesz podjąć, gdy rozważasz interakcję z osobą
w prawdziwej sytuacji życiowej? A co z czymś tak prostym, jak podanie swojego imienia
innym osobom (innym obiektom)? Najprostszym sposobem wyjaśnienia pojęcia jest
dodanie działania (zwanego również metodą) o nazwie przedstawSie, które
umożliwiłoby obiektowi przywitanie się z Cześć! Nazywam się <imię>. Teraz
prawdopodobnie możesz zobaczyć połączenie między własnością obiektu (imię)
a metodą (przedstawSie). Wewnątrz naszego kodu, jeśli mamy obiekt reprezentujący
osobę, jesteśmy w stanie wchodzić w interakcje z nim za pomocą metod. Więcej
przykładów metod? Pewnie! Co powiesz na kiedyMaszUrodziny, która brzmi: Moje
urodziny są <urodziny>. Zbyt łatwe? Cóż, teraz możemy dodać nową metodę:
czyDzisiajMaszUrodziny? Ta metoda wymaga wykonania dodatkowej operacji.
W pewnym sensie wchodziłoby to w interakcję ze środowiskiem, sprawdzając najpierw,
która data jest dzisiaj. Następnie trzeba porównać tę datę z własnymi urodzinami
obiektu. W zależności od wyniku porównania, obiekt może powiedzieć: Dzisiaj są
moje urodziny!, Dzisiaj nie są moje urodziny, lub, aby skomplikować
przykład, Moje urodziny będą za X dni, gdzie X oznacza obliczoną różnicę między
dzisiejszą datą a datą urodzin obiektu.
Programowanie zorientowane obiektowo zapewnia dodatkową warstwę złożoności
kodu, ale jako nagrodę daje dużą elastyczność w tym, jak możemy odzwierciedlić
rzeczywiste obiekty świata w naszym kodzie. Istnieje również wiele innych koncepcji
związanych z programowaniem obiektowym, których nie omówimy w tym podręczniku,
ale jeśli jesteś zainteresowany, koniecznie zajrzyj do sekcji "Dalsze czytanie", która
zawiera najlepsze propozycje książek na ten temat.
2.6 Operatory
Nadszedł czas, abyśmy przeszli do nieco poważniejszego kodu. W tym podrozdziale
omówimy operatory używane do porównywania dwóch elementów. Czy 4 jest większe
niż 5? Czy 3 jest większe niż lub równe 1? Jaki jest wynik, jeśli pomnożymy 5 na 4, a co
jeśli podzielimy 10 przez 2? Czy dzisiejsza data jest równa dacie moich urodzin? Na
45
wszystkie te pytania (i nieskończoną liczbę innych przykładów) można odpowiedzieć za
pomocą operatorów.
Operatory można ogólnie podzielić na trzy grupy, które pokrótce opiszemy. Posiadamy
operatory arytmetyczne, porównania i logiczne.
Jeszcze jedno ważne wyjaśnienie zanim przejdziemy do przykładów odnosi się do
operandów. Operatory znajdują się pośrodku między dwoma elementami: a operator
b. W tym przypadku a oraz b są tak zwanymi operandami, a operator znajduje się
w środku. Dla jasności, dwa argumenty operandu są nazywane lewym operandem
(w tym przypadku: a) i prawym operandem (w tym przypadku: b). Czym jest ten
operator, który znajduje się pomiędzy dwoma operandami?
Pierwszy typ, operator arytmetyczny, jest prostą operacją matematyczną, którą wszyscy
znamy z naszej wczesnej edukacji, taką jak dodawanie, odejmowanie, mnożenie,
dzielenie i inne. Są to najbardziej popularne operatory arytmetyczne, które są
dodatkowo przedstawione w poniższej tabeli.
Tabela 2. Lista najbardziej popularnych operatorów arytmetycznych – część I
Operator Opis Lewy
operand (a) Prawy
operand (b) Wynik
+ Dodaje dwa operandy 4 5 4 + 5 = 9
- Odejmuje prawy operand od lewego operandu
4 5 4 – 5 = -1
* Mnoży oba operandy 4 5 4 * 5 = 20
/ Dzieli lewy operand przez prawy operand
18 3 18 / 3 = 6
% Oblicza pozostałą część z dzielenia lewego operandu przez prawy operand
20 3 20 % 3 = 2
Źródło: opracowanie własne
Gdzie można wykorzystać te operatory arytmetyczne? W wielu przypadkach! Jeśli
zdecyduję się czytać codziennie 10 stron książki o programowaniu obiektowym, ile
stron będę czytać w ciągu jednego miesiąca? A co powiesz na jeden rok? Jaka jest suma
wszystkich wydatków, które zarobiłem w zeszłym tygodniu? Wszystkie te operatory
mogą być używane razem w jednym równaniu, tak jak zrobiłbyś to na papierze.
Wrócimy do tego później, ale najpierw przejdźmy do pozostałych operatorów.
Następną grupę stanowią operatory relacyjne i, jak sama nazwa wskazuje, są one
używane do porównywania dwóch operandów. Używając operatorów relacyjnych,
możesz powiedzieć jaka jest relacja między dwoma operandami. Można je traktować
jako zadawanie pytań w celu uzyskania odpowiedzi "tak / nie", która w programowaniu
jest wyrażana jako wartość logiczna (przypominając, zwracają one wartość true lub
46
false). Czy osoba a jest starsza niż osoba b? Czy liczba osób z naszego kodu z imieniem
zaczynającym się na literę C jest większa niż ilość osób, których imię zaczyna się na literę
D? Najpierw przyjrzyjmy się poniższej tabeli, która podsumowuje najczęściej używane
operatory relacyjne.
Tabela 3. Lista najbardziej popularnych operatorów relacyjnych – część II
Operator Opis Lewy
operand (a) Prawy
operand (b) Wynik
== Sprawdza, czy oba operandy są równe 4 5 4 == 5
false
!= lub <> Sprawdza, czy oba operandy różnią się od siebie
4 5 4 != 5
true
> Sprawdza, czy lewy operand jest większy niż prawy operand
4 5 4 > 5
false
< Sprawdza, czy lewy operand jest mniejszy niż prawy operand
4 5 4 < 5
true
>= Sprawdza, czy lewy operand jest większy lub równy prawemu operandowi
4 4 4 >= 4
true
<= Sprawdza, czy lewy operand jest mniejszy lub równy prawemu operandowi
3 4 3 <= 4
true
Źródło: opracowanie własne
Ostatni typ operatorów to logiczne. W tej kategorii możemy uwzględnić trzy
najpopularniejsze operatory logiczne, które są obecne w prawie każdym kodzie
programistycznym. Obiecaliśmy także, że wrócimy do bardziej złożonych operacji
z wykorzystaniem operatorów, więc zaczynamy! Najpierw przyjrzyjmy się tabeli 4.
Tabela 4. Lista najbardziej popularnych operatorów logicznych – część III
Operator Opis Lewy
operand (a) Prawy
operand (b) Wynik
&& Sprawdza, czy oba operandy są prawdziwe (logiczne AND)
true false true && false
false
||
Sprawdza, czy co najmniej jeden z operandów jest prawdziwy (logiczne OR)
true false true || false
true
!
Odwraca wartość logiczną operandu, więc true staje się fałszywy, a false staje się prawdą (logiczne NOT)
true false !(true && false)
true
Źródło: opracowanie własne
47
Zwróć szczególną uwagę na ostatni przykład, w którym użyliśmy true && false na
pierwszym miejscu. To oznaczałoby, według pierwszego przykładu, fałsz. Jednakże,
używając operatora logicznego NOT, zmieniliśmy wartość false na true. Wracając do
naszego przykładu, operator ten może się przydać w przypadku, gdy szukamy osób
w wieku powyżej 20 lat, którzy nie mają dzisiaj urodzin. Tacy ludzie będą oznaczani
z wykorzystaniem wartości logicznej. Jak moglibyśmy napisać taki kod za pomocą
operatorów, które znamy do tej pory? Spróbujmy!
!(wiecejlatniz20 && dzisiajurodziny)
Oczywiście będziemy musieli uwzględnić dwie zmienne logiczne, nazwane jako
wiecejlatniz20 i dzisiajurodziny. Teraz widać również, że używamy nawiasów
okrągłych do grupowania operatorów. Pozwala nam to precyzyjnie ocenić jeszcze
bardziej złożone przykłady. Co powiesz na wybór osób starszych niż 20 lat, a ich imię
zaczyna się od A, lub są starsi niż 25 lat, a ich imię zaczyna się na B?
(wiecejlatniz20 && zaczynasieA) || (wiecejlatniz25 && zaczynasieB)
Używanie nawiasów okrągłych daje nam nieograniczone możliwości pracy z różnymi
wartościami logicznymi, aby osiągnąć wymagany warunek. To byłoby mniej więcej
wszystko, jeśli chodzi o bardzo podstawowe wprowadzenie do różnych operatorów.
Należy jednak pamiętać, że wyniki korzystania z operatorów to inny aspekt. Nie
obliczamy ani nie oceniamy wyrażeń bez celu. Używamy ich, aby przekazać naszemu
kodowi, co należy zrobić w określonych okolicznościach. Korzystamy wtedy z wyrażeń
decyzyjnych, które są opisane w następnym podrozdziale.
2.7 Wyrażenia decyzyjne
Regularnie podejmujemy różne decyzje. Kiedy pada deszcz, zabieramy parasol przed
wyjściem. Innymi słowy, w oparciu o różne informacje, które posiadamy, możemy
działać inaczej. Dokładnie w tym celu w naszym kodzie używamy wyrażeń decyzyjnych.
Jeśli osoba A ma dziś urodziny, powinniśmy życzyć jej wszystkiego najlepszego, prawda?
W poprzednim zdaniu używaliśmy już instrukcji warunkowej, która w większości
języków programowania jest wyrażona przez IF. To wyrażenie IF jest oceniane logicznie,
więc w naszym kodzie możemy napisać coś takiego jak:
if (osobaAMaUrodziny)
wszystkiegoNajlepszego()
Powyższy przykład można odczytać wprost, więc jeśli osoba A ma urodziny, "wykonaj"
wszystkiegoNajlepszego. Nie martw się o nawiasy okrągłe po
wszystkiegoNajlepszego. Na razie wystarczy zauważyć, że jest to funkcja, która jest
wywoływana i oczekuje się, że coś zrobi. Wkrótce wrócimy do funkcji. Kod zawiera
także ważne informacje. Warunkowa instrukcja IF zakłada, że
wszystkiegoNajlepszego będzie "wykonane" tylko wtedy, gdy warunek, który jest
48
oceniany, jest prawdziwy. Innymi słowy, jeśli osoba A nie ma dzisiaj swoich urodzin, kod
nie złoży życzeń. To powinno być logiczne. Zwykle wykonywany jest tylko następny
wiersz kodu po instrukcji IF. Co w przypadku, jeśli naprawdę chcemy dać tej osobie
mały prezent? Oznaczałoby to, że musimy nie tylko życzyć im wszystkiego najlepszego,
ale także dać mały prezent. Można to osiągnąć za pomocą nawiasów klamrowych, jak
pokazano na poniższym przykładowym kodzie.
if (osobaAMaUrodziny) {
wszystkiegoNajlepszego()
dajPrezent()
}
Teraz możesz wyraźnie zobaczyć, że warunkowa instrukcja if, a więc i nasz kod,
rzeczywiście wykona te dwa działania. Nie tylko życzenia będą przekazywane, ale także
mały prezent.
Czy nasz kod powinien być nieuprzejmy dla tych wszystkich ludzi, którzy nie mają
dzisiaj szczęścia świętować swoich urodzin? W obecnym kształcie kod dotyczy tylko
solenizantów. Pozostali niczego nie otrzymują. Można to zmienić za pomocą instrukcji
ELSE. Rzućmy okiem na nasz zaktualizowany kod.
if (osobaAMaUrodziny) {
wszystkiegoNajlepszego()
dajPrezent()
} else {
jakSieCzujesz()
}
Teraz wszystko wygląda lepiej! Jeśli osoba, którą my (a raczej nasz kod) rozważamy, nie
ma swoich urodzin, zostaną grzecznie zapytane o samopoczucie. Jest to zdecydowanie
bardziej uprzejme!
Chociaż zwykle słowo kluczowe ELSE nie wymaga w tym przypadku nawiasów
klamrowych (ponieważ ma tylko jedną instrukcję do wykonania), zazwyczaj dobrym
zwyczajem jest zawarcie go dla lepszej przejrzystości.
Rzućmy teraz okiem na bardziej złożoną kwestię. Chcielibyśmy stworzyć grupy osób,
które rozważaliśmy wcześniej na podstawie ich wieku. Będą cztery grupy w zależności
od wieku:
Grupa A - osoby w wieku 21 lat
Grupa B - osoby w wieku 22 lat
Grupa C - osoby w wieku 23 lat
Grupa D - wszystkie inne osoby
Do tej pory dowiedzieliśmy się o operatorach i teraz uczymy się o wyrażeniach
decyzyjnych. Mamy listę osób, a teraz musimy dokonać odpowiednich przypisań do grup
49
na podstawie ich wieku. Aby uzyskać szczegółowe informacje, zapoznaj się z poniższym
przykładowym kodem.
if (wiek == 21) {
przypiszGrupeA()
} else if (wiek == 22) {
przypiszGrupeB()
} else if (wiek == 23) {
przypiszGrupeC()
} else {
przypiszGrupeD()
}
Byłoby dobrze zatrzymać się w tym momencie na chwilę. Powyżej widnieje zestaw 4
instrukcji warunkowych. Kiedy kod jest wykonywany, zaczyna się od góry i ocenia wiek
osoby względem warunków wyrażonych jako operatory relacyjne i logiczne. Jeśli
pierwszy warunek nie zostanie spełniony (innymi słowy, osoba ma na przykład 23 lata),
kod przechodzi do następnej instrukcji. Należy pamiętać, że ponieważ chcieliśmy uznać
wszystkie te instrukcje warunkowe za jedno duże wyrażenie, użyliśmy ELSE IF. Ta
instrukcja, w przeciwieństwie do prostego ELSE, pozwala nam podać więcej stwierdzeń
do oceny. "Pojedynczy" warunek ELSE został przeniesiony na sam koniec kodu, aby
dopasować wszystkie osoby, które nie były w przedziałach wiekowych zdefiniowanych
we wcześniejszych instrukcjach warunkowych.
Musimy się jednak zgodzić, że nawet jeśli istnieją tylko cztery stwierdzenia, kod nie
wygląda zbyt jasno. A gdybyśmy mieli jeszcze bardziej złożoną kwestię do rozwiązania?
Co by było, gdybyśmy mieli 10 lub 20 przypadków? Wspaniale, właśnie wprowadziliśmy
kolejne bardzo przydatne słowo: przypadek! Możemy wykorzystać przypadki do
organizacji kodu, jeśli są one połączone z tak zwaną instrukcją przełączania!
Konstrukcja switch - case jest wykorzystywana specjalnie do tego celu, gdy musimy
ocenić szereg warunków. Odpowiednik kodu do tego, który omówiliśmy powyżej, można
zapisać w sposób przedstawiony poniżej.
switch(wiek) {
case 21:
przypiszGrupeA()
break
case 22:
przypiszGrupeB()
break
case 23:
przypiszGrupeC()
break
default:
przypiszGrupeD()
}
Teraz mówimy naszemu kodowi, aby wykonał wymagane zadania przy użyciu
konstrukcji switch - case. Na samym początku wyjaśniamy, że zamierzamy
50
"przełączyć" kod na podstawie wieku. Następnie poniżej są przypadki. Jeśli wiek pasuje
do podanego przypadku, ten kod przypadku (tutaj - przypisanie do grupy) jest
wykonywany. Prawdopodobnie zauważyłeś instrukcję przerwania. Ta instrukcja mówi
naszemu kodowi, aby się tam zatrzymał i zapomniał o innych przypadkach. Dlaczego to
jest takie? Jak rozumiesz poniższy fragment kodu?
case 21:
case 22:
case 23:
przypiszGrupeX()
break
W przypadkach skonstruowanych w ten sposób nasz kod zostanie przypisany do grupy
X osób, które mają 21, 22 lub 23 lata. Dzieje się tak dlatego, że nie dostarczyliśmy
żadnych innych instrukcji w dwóch pierwszych przypadkach, oraz nie użyliśmy
instrukcji break. Może to być przydatne, w zależności od tego, co próbujemy osiągnąć.
W naszym przypadku musimy jednak użyć instrukcji break, aby móc przypisać ludzi do
wymaganych grup.
Wyrażenia warunkowe są stosunkowo łatwe do zrozumienia, ale tylko dzięki praktycznemu
doświadczeniu uzyskasz większą pewność nawet przy najbardziej złożonym kodzie. Nadszedł
czas, abyśmy poszli dalej, zaczynając od pętli opisanych w następnym podrozdziale.
2.8 Pętle
Pętle w programowaniu mogą znacznie uprościć kod. Te instrukcje służą do
wielokrotnego uruchamiania tego samego bloku kodu, w zależności od potrzeb. Jeśli
zapraszasz wszystkich swoich znajomych do swojego miejsca, musisz indywidualnie
powitać wszystkich przybyłych. Nasz kod może to zrobić bardzo łatwo, wywołując
funkcję (funkcje opisane są w następnym podrozdziale) pokazanym poniżej.
powitajOsobe()
To jest świetne, ale czy możesz sobie wyobrazić kod, jeśli miałbyś powitać 30 osób?
powitajOsobe()
powitajOsobe ()
(27 ukrytych linii)
powitajOsobe ()
Jeśli myślisz, że musi być lepszy sposób na zrobienie tego, masz rację! Jest to doskonały
przykład, w którym możemy używać pętli.
W językach programowania istnieją różne rodzaje pętli. Różnią się one od siebie
nawzajem, jeśli chodzi o konstrukcję, ale w zasadzie są przeznaczone do tego samego -
powtórzenie wykonania tego samego bloku kodu. Każde takie wykonanie jest nazywane
iteracją. Na początek rozważmy prostą pętlę FOR.
51
Pętla FOR służy do wykonywania określonego kodu, o ile warunek, który jest oceniany,
jest równy wartości logicznej true. Aby uzyskać szczegółowe informacje, zapoznaj się
z przykładem pokazanym poniżej.
for (i = 0; i < 30; i++) {
powitajOsobe()
}
Ponieważ są nowe elementy, przejdziemy przez nie jeden po drugim. W pierwszym
wierszu widać, że pętla jest zdefiniowana przez trzy bloki rozdzielone średnikami.
Pierwszy jest oceniany, gdy pętla ma zostać wykonana. W tym przypadku zmienna
i otrzyma wartość równą 0. Zmienna ta będzie używana do oceny, czy pętla powinna
być kontynuowana, czy zatrzymana. Zgodnie z kodem, pętla będzie kontynuowana aż do
osiągnięcia 30. Obliczone wyrażenie, i < 30, będzie ocenione jako prawdziwe dla
i równe 0, 1, ..., 29, ale gdy i osiągnie wartość 30, uzyska ono wartość false,
ponieważ instrukcja 30 < 30 nie jest prawdziwa. Innymi słowy, trzydzieści to nie mniej
niż trzydzieści. W jaki sposób pętla wie o wartości i? To trzeci blok, który mówi i++.
Jest to wyrażenie postinkrementacji, które zwiększa wartość i o 1, a przywołując
wyrażenia arytmetyczne, jest ono równoważne i = i + 1.
Ważną informacją dotyczącą pętli for, a także innych pętli, jest to, że w wielu
przypadkach używane są tak zwane zmienne sterujące, a powszechną praktyką jest
nadawanie im nazwy krótkich, jednoliterowych zmiennych, takich jak i, j oraz k.
Pętla FOR jest przydatna, jeśli dokładnie wiemy, ile potrzebujemy powtórzeń. W naszym
przykładzie wiedzieliśmy, że oczekujemy 30 osób, które powinny zostać powitane. Pętle
można jednak łączyć z innymi instrukcjami. Co by było, gdybyśmy chcieli przejrzeć listę
osób i zatrzymać się w pewnym momencie, który nie jest znany, zanim uruchomimy
kod? Załóżmy, że chcemy ponownie przejrzeć listę 30 osób i zatrzymać się, jeśli
znajdziemy konkretną osobę o imieniu Jane.
for (i = 0; i < 30; i++) {
if (imie == “Jane”) {
break;
}
}
Instrukcja break jest tą samą, którą omawiamy, wyjaśniając instrukcję switch-case.
Kod zatrzyma się na pierwszej osobie, której imię brzmi Jane. To pozwoli nam przestać
niepotrzebnie „przeglądać” inne osoby, jeśli już znaleźliśmy Jane.
W innym przykład może ponownie rozważyć Jane. Wciąż witamy wszystkich ludzi, ale
kiedy witamy Jane, chcemy dać jej mały prezent, ponieważ ma dziś urodziny.
Przykładowy kod może wyglądać tak, jak przedstawiono poniżej.
for (i = 0; i < 30; i++) {
powitajOsobe()
if (imie == “Jane”) {
52
dajPrezent()
}
}
Ten kod wita każdego, ale dla Jane oczekiwane jest inne działanie - wręczenie jej małego
prezentu.
Ostatni przykład ilustruje sytuację, w której wiemy, że Jane niestety nie przyjdzie. W tym
przypadku chcielibyśmy móc pominąć jej powitanie.
for (i = 0; i < 30; i++) {
if (imie == “Jane”) {
continue
}
powitajOsobe()
}
Jak widać wewnątrz fragmentu kodu, który jest wykonywany w każdej iteracji, mamy
instrukcję warunkową. Jeśli osoba, którą teraz rozważamy, jest rzeczywiście Jane,
mówimy, że nasza pętla będzie kontynuowana. To jest sposób, aby powiedzieć pętli, aby
pominąć pozostały fragment kodu i przejść do kolejnej osoby. Jeśli imię nie jest równe
Jane, to wyrażenie warunkowe zostanie ocenione jako false, w wyniku czego zostanie
wykonane powitajOsobe, i właśnie to chcieliśmy osiągnąć.
Możesz się zastanawiać, czy FOR to jedyna pętla, którą możesz uwzględnić w swoim
kodzie. Odpowiedź brzmi: nie! Należy jednak podkreślić, że nie wszystkie typy pętli
występują w każdym języku programowania. W każdym razie spójrzmy na dwie pętle:
WHILE i FOREACH.
Pętla WHILE wykona ten sam blok kodu tak długo, jak sprawdzany warunek ma wartość
true. Innymi słowy, mówimy naszemu kodowi, żeby działał w następujący sposób: IF
<warunek> REPEAT <code> AND wróć do początku
Ponieważ warunek, który ma zostać oceniony, jest typu Boolean, oznacza to, że musimy
uzyskać wartość true lub false, która powie nam, czy blok kodu wewnątrz pętli WHILE
musi zostać wykonany. Na przykład coś takiego:
while (! wszyscyPrzyszli) {
witajOsoby()
}
Załóżmy, że mamy zmienną wszyscyPrzyszli, która jest ustawiona na false. Nowi
ludzie przychodzą do nas, ale ta zmienna będzie fałszywa aż do momentu, w którym
przybędą wszyscy goście. Zasadniczo nasza prosta pętla wykonuje funkcję witajOsoby,
AŻ dotarły wszystkie osoby - i to odzwierciedliłoby się przez wszyscyPrzyszli,
zmieniając wartość z false na true. Musisz również zauważyć znak wykrzyknika przed
nazwą zmiennej. Jest to operator logiczny NOT, który odwraca wartość z true na false
oraz z false na true. Kiedy zaczynamy naszą pętlę, a zmienna wszyscyPrzyszli ma
wartość false, musimy ją negować, aby była prawdziwa, ponieważ tylko ona
53
spowoduje wykonanie bloku kodu. W końcu, gdy wszyscyPrzyszli zmieni swoją
wartość na true, wraz z negacją całe wyrażenie zostanie ocenione jako fałszywe. To
spowoduje, że pętla WHILE nie będzie już wykonywana – robiąc dokładnie to, co było
potrzebne.
Istnieją również inne sposoby zapisu pętli WHILE, ale zależy to od konkretnego języka
programowania. Niektóre z nich podążają za strukturą WHILE <warunek> DO <kod>,
więc jest jedno dodatkowe słowo "do", które wyraźnie mówi, jak kod powinien się
zachowywać. Od tego momentu możemy również bardzo krótko wspomnieć o innej
konstrukcji, która zamiast WHILE <warunek> DO <kod> odwraca swoje zachowanie,
staje się więc DO <code> WHILE <warunek>. Nasz przykład może zatem zostać
nieznacznie zmodyfikowany jako taki:
do {
witajOsoby()
} while (! wszyscyPrzyszli)
Zasadniczo zachowanie jest bardzo podobne do tego, które omówiliśmy powyżej.
Chcielibyśmy witać naszych gości do czasu, kiedy wszyscy przybyli, ponieważ jest to
wskazane przez nasze wyrażenie, które jest oceniane jako true lub false. Jest jeden
ważny wyjątek. Konstrukcja do-while zostanie wykonana co najmniej raz, ponieważ
warunek oceniany jest dopiero po bloku kodu! To może być mylące. Wyobraź sobie, że
przybyli wszyscy nasi goście. Teraz, przypadkowo, wykonywany jest ten sam kod.
Pamiętaj, że zmienna wszyscyPrzyszli ma wartość true, ponieważ są tam wszyscy
nasi goście. Patrząc na pierwszy przykład, co się stanie? Wyrażenie zostanie ocenione na
false (wszyscyPrzyszli jest true, ale razem z operacją logiczną NOT otrzymamy
false). Fałszywa wartość uniemożliwi zatem pętli while wykonywanie bloku kodu,
więc nie będziemy witać ludzi po raz drugi.
Drugi przykład jednak rzeczywiście będzie witał ludzi, ale po pierwszym powitaniu
zauważy, że nie jest to konieczne. Jest to główna różnica między dwoma rodzajami pętli.
Konstrukcja DO-WHILE może być przydatna w innych przypadkach. Załóżmy, że wiesz,
że jest jedna osoba, która ma dziś urodziny i chciałbyś zapytać wszystkich swoich gości,
jeden po drugim, czy to jest ich szczególny dzień, czy nie. Porozmawiamy z każdą osobą
po kolei. Poniższy kod mógłby zilustrować tę sytuację:
do {
zapytaj()
} while (! osobaZnaleziona)
Zanim zaczniemy rozmawiać z ludźmi, nasza zmienna osobaZnaleziona będzie miała
wartość false. Pytamy pierwszą osobę, czy dziś są jej urodziny. Jeśli nie, nie zmieniamy
wartości zmiennej osobaZnaleziona, ale zamiast tego przechodzimy do następnej
osoby. W pewnym momencie otrzymamy pozytywną odpowiedź na nasze pytanie, a my
zmienimy wartość osobaZnaleziona z false na true. W rezultacie, zaraz po
otrzymaniu odpowiedzi (i osoby, której szukaliśmy), stwierdzenie while zostanie
54
ocenione jako false (osobaZnaleziona z wartością true razem z operatorem
logicznym NOT da nam wartość false), a to powstrzyma pętle while od wykonania
następnych iteracji. Możliwe, że pierwszą osobą, którą zadajemy, jest ta, której szukamy.
W takim przypadku pytanie zostanie zadane i natychmiast po pierwszej iteracji
będziemy w stanie przestać zadawać innym osobom to samo pytanie.
Ostatnim typem pętli, który chcielibyśmy przedstawić, jest pętla FOREACH. Ta pętla ma
być wykonana na zbiorze elementów, a kod powinien zrobić coś dla każdego
znalezionego elementu. Wciąż jesteśmy z naszymi gośćmi i chcielibyśmy przepytać
wszystkich, aby sprawdzić, czy numer telefonu, który mamy w naszej książce adresowej,
jest nadal ważny lub czy wymaga aktualizacji. Załóżmy, że nasi goście będą obecni
w jednej zmiennej wszyscyGoscie. Przykładowy kod przedstawiono poniżej.
foreach (wszyscyGoscie as gosc) {
sprawdzNumer()
}
Widać tu, że rzeczywiście przechodzimy przez wszystkich naszych gości, ale w każdej
iteracji patrzymy tylko na jednego konkretnego gościa, który zostanie przypisany
zmiennej gosc. Oznacza to, że z większej kolekcji wszystkich gości możemy skupić się na
jednej konkretnej osobie. Na razie po prostu założymy, że funkcja sprawdzNumer
sprawdza, czy posiadany telefon jest poprawny i aktualizuje go, jeśli to konieczne. Jeśli
ktoś nie ma numeru telefonu, możemy wykluczyć takie osoby przy użyciu instrukcji
continue, o czym już wiesz. Zapoznaj się z przykładem przedstawionym poniżej.
foreach (wszyscyGoscie as gosc) {
if (gosc maNumer) {
sprawdzNumer()
} else {
continue
}
}
W powyższym przykładzie jeśli ktoś ma numer telefonu, zamierzamy go zweryfikować
oraz przejść do następnej w przeciwnym wypadku. Jeśli uważasz, że instrukcja
continue nie jest tu potrzebna, masz rację. Wystarczy zawrzeć funkcję sprawdzNumer
w instrukcji if, a kod będzie działał w ten sam sposób. W niektórych przypadkach jest
jednak konieczne, aby przejść od razu do następnej iteracji pętli foreach i do tego celu
można użyć instrukcji continue.
Piękno programowania polega na tym, że ten sam cel można osiągnąć na wiele
sposobów. Jeśli wrócimy na przykład do kodu, w którym zapytaliśmy ludzi, czy dziś
mają urodziny, używając konstrukcji do-while, moglibyśmy osiągnąć to w podobny
sposób, używając różnych pętli, na przykład foreach.
foreach (wszyscyGoscie as gosc) {
if (gosc maUrodziny) {
osobaZnaleziona = true
55
break
}
}
W tym przypadku dokonujemy iteracji przez wszystkich naszych gości, ale gdy osoba,
którą sprawdzamy w bieżącej iteracji ma dziś urodziny, ustawimy zmienną
osobaZnaleziona na wartość true i opuścimy całą pętlę foreach, używając instrukcji
break.
Jest to coś, o czym powinieneś pamiętać. Programowanie daje możliwość rozwiązania
tego samego problemu za pomocą wielu metod. Niektóre z nich lepiej nadają się do
określonych celów, niektóre z nich można wykonywać szybciej i oczywiście mają swoje
zalety i wady. Tylko poświęcając swój czas na naukę bardzo czystego programowania
i samemu eksperymentując, jesteś w stanie nabrać biegłości w wybranym języku
programowania.
2.9 Funkcje
W tym podrozdziale skupimy się na funkcjach wspomnianych powyżej kilka razy.
Funkcje są wyizolowanymi częściami kodu, które mają zostać wykonane w celu
wykonania określonej akcji. Mogą przyjmować niektóre dane jako parametry wejściowe,
przetwarzać je i ostatecznie zwracać wynik. Innymi słowy, możemy mieć dane
wejściowe i wyjściowe do i z funkcji. Jeśli nie jest to od razu jasne, możemy posłużyć się
przykładem.
Jedną z funkcji, którą stosowaliśmy w poprzednich podrozdziałach, była metoda
powitajOsobe(). Jak moglibyśmy napisać taką funkcję?
function powitajOsobe() {
say Cześć!
say Jak się masz?
say Wejdź do środka, zapraszam!
}
Teraz możemy z łatwością powiedzieć, co jest ukryte w tej funkcji. Za każdym razem,
gdy wywoływana jest ta funkcja, wypowiadane będą te trzy wyrażenia.
Funkcje są używane głównie do organizowania kodu i dzielenia go na elementy logiczne,
które można wywoływać z różnych miejsc naszego kodu. Funkcje mogą również
przyjmować parametry wejściowe, które są nazywane argumentami. Co to znaczy?
Oznacza to, że możemy przekazać zmienną do funkcji i użyć jej wewnętrznie. Co powiesz
na powitanie ludzi po imieniu? Z pewnością jest to normalne, ale w obecnym kształcie
nasza funkcja nie jest świadoma osoby, którą witamy - zmieńmy to!
function powitajOsobe(osoba) {
say Cześć osoba->imie!
say Jak się masz?
56
say Wejdź do środka, zapraszam!
}
W powyższym przykładzie przekazujemy argument jako zmienną osoba. Dzięki temu
w naszej funkcji możemy odnieść się do naszych gości po imieniu. Użyliśmy znaku ->,
który ma uzyskać imię osoby, którą nasza funkcja właśnie dostała jako parametr. W tym
miejscu możemy wspomnieć, że istnieją różne sposoby dostępu do imienia, w zależności
od zmiennej, z którą mamy do czynienia. Zwykle znak -> służy do uzyskiwania dostępu
do właściwości obiektu. W innych przypadkach może się zdarzyć, że użyjemy osoba
[‘imie‘] lub osoba::imie - wszystko zależy od języka programowania, w którym
pracujemy, a także od typu zmiennej.
Funkcje mogą również zwracać wartości, co jest bardzo pomocne dla programistów.
Rzućmy okiem na naszą funkcję sprawdzNumer(), z następującą implementacją:
function sprawdzNumer(gosc) {
if (gosc->maNumer) {
obecnyNumer = gosc->numer
if (obecnyNumer != numerKtoryPosiadam) {
aktualizuj()
return true
}
}
return false
}
Nowością, którą prawdopodobnie zauważyłeś, jest nowe słowo o nazwie return.
Instrukcja return służy do zwracania wartości z funkcji do dowolnego fragmentu kodu
nazwanego tą konkretną funkcją. W tym przypadku zwracamy tylko zmienne typu
Boolean i są one używane do informowania naszego kodu, jeśli zaktualizowaliśmy wpis
(true) lub nie (false). Zwróć jednak uwagę, że aby użyć wartości zwracanej, musisz
przypisać wywoływaną funkcję do zmiennej. Biorąc pod uwagę powyższy przykład,
rozważmy następny przykładowy kod
czyAktualizacjaBylaKonieczna = sprawdzNumer(gosc)
W takim przypadku otrzymujemy zwrotną zmienną z powrotem z funkcji. Gdy funkcja
zostanie wykonana, zmienna czyAktualizacjaBylaKonieczna powie nam, czy
potrzebujemy zaktualizować naszą książkę adresową (zmienna będzie mieć wartość
logiczną true), lub nasz wpis nie wymagał żadnych poprawek (zmienna będzie miała
wartość logiczną false).
Podsumowując, można użyć funkcji, aby zaoszczędzić czas i zwiększyć czytelność kodu.
Jeśli przejrzysz ponownie przedstawione przykłady, prawdopodobnie zauważysz, że
wszystkie te instrukcje zawarte w funkcji sprawdzNumer() mogły zostać
zaimplementowane bezpośrednio w kodzie, gdy przechodziliśmy przez wszyscyGoscie
w naszej pętli foreach. Wtedy jednak kod stałby się zbyt zagmatwany i niełatwy do
odczytania. Podczas programowania należy zawsze koncentrować się na kodzie,
57
ponieważ musi on być jasny również dla innych osób. Ponieważ jest to bardzo obszerny
temat, który niestety wykracza poza zakres tego podręcznika, możesz samodzielnie
zapoznać się z dodatkowymi informacjami. Dobrym punktem wyjścia może być sekcja
"Dalsze czytanie".
2.10 Bibliografia
Adamczyk M., (2014), Rozwój kompetencji zawodowych programistów w gospodarce
opartej na wiedzy, [‘Development of professional competence of programmers in
a knowledge-based economy’], „Zeszyty Studenckiego Towarzystwa Naukowego”
Akademia Górniczo-Hutnicza, w Krakowie, ISSN: 1732-0925
Boyatzis R.E., (1982), The Competent Manager: A Model for Effective Performance, New
Jersey: John Wiley & Sons, Hoboken,.
BiMatrix, (2018), Tłumacz tekstu i kodu binarnego,
[http://dbm.org.pl/strony/kod_binarny.php]
Goel S., (2010), Design of interventions for instructional reform in software development
education for competency enhancement, Jaypee Institute of Information Technology,
Heath F. G., (1972), Origins of the Binary Code, “Scientific American”, Vol. 227, No. 2.
IEEE & ACM, (2004), Curriculum Guidelines for Undergraduate Degree Programs in
Software Engineering.
IEEE & ACM (2014), Curriculum Guidelines for Undergraduate Degree Programs in
Software Engineering.
Jurgielewicz-Wojtaszek M., (2012), Ocena poziomu kompetencji trenerów zarządzania w
zakresie nauczania osób dorosłych, [‘Assessment of the level of competences of
management trainers in the field of adult education’] [in:] Kuźniak A. (ed.), Vedemecum
Trenera II. Tożsamość Zawodu Trenera Zarządzania, Kraków: Księgarnia Akademicka.
Encyklopedia szkolna – Matematyka. [‘School Encyklopedia - Mathematics‘] (1988),
Waliszewski W. (ed.), Warszawa: Wydawnictwa Szkolne i Pedagogiczne, ISBN 83-02-
02551-8.
Manawadu C.D. Johar M.G.M., Perera S.S.N, (2015), Essential Technical Competencies for
Software Engineers: Perspectives from Sri Lankan Undergraduates, “International Journal
of Computer Applications” Vol. 113, No. 17.
Rogalski J., Samurçay R., (1990), Acquisition of Programming Knowledge and Skills, [in:]
Hoc J.M., Green T.R.G., Samurçay R., Gilmore D.J. (ed.), Psychology of Programming, New
York: Academic Press Ltd., doi:10.1016/B978-0-12-350772-3.50015-X
Routier J.C., Mathieu P., Secq Y., (2001), Dynamic Skills Learning: a Support to Agent
Evolution, [w:] Proceedings of the first international joint conference on Autonomous
58
agents and multiagent systems: part 1, New York: ACM (Association for Computing
Machinery).
Słownik Języka Polskiego [‘The Polish language dictionary’]. (2018), Warszawa:
Wydawnictwa Naukowe PWN, (on-line: https://sjp.pwn.pl/)
Soukup B, (2015), Nauka programowania zamiast przedmiotu informatyka w szkołach
Podstawowych [‘Learning of programming instead of IT subject in primary schools’], [in:]
J. Morbitzer, D. Morańska, E. Musiał (ed.) Człowiek –- Media – Edukacja, Dąbrowa
Górnicza: Wyższa Szkoła Biznesu w Dąbrowie Górniczej, ISBN 978-83-64927-39-3.
The history of computer programming, graphic material
[https://visual.ly/community/infographic/technology/history-computer-
programming]
Wałaszek J., (2018), Binarne Kodowanie Liczb [‘Coding of Binary Numbers’], [Online
source: http://eduinf.waw.pl/inf/alg/006_bin/0009.php] (available on: 8th February
2018) GNU Free Documentation License
59
3 Dydaktyka przy użyciu algorytmiki i programowania (Wojciech Kolarz)
3.1 Podstawowe założenia algorytmiczne i programistyczne w nauczaniu
szkolnym
Współczesna szkoła powinna między innymi przygotować młodego człowieka do życia
w zmieniającym się, technologicznym świecie, jednocześnie w świecie opartym
o niezmienne prawa przyrody. Nie bez znaczenia jest również umiejętność
funkcjonowania w społeczeństwie. W związku z powyższym niezbędnym będzie
zarówno umiejętność komunikowania się, współdziałania w zespole, jak również
umiejętność opisywania zjawisk, umiejętność rozwiązywania problemów, w tym
poprzez tworzenie i posługiwanie się modelami.
Dzisiaj nie jest problemem dotarcie do informacji. Wszechobecna technologia daje
możliwości szybkiego i skutecznego poszukiwania informacji. Problemem staje się jej
twórcze wykorzystanie, umiejętność budowania wiedzy, a tym samym umiejętność jej
wykorzystania w rozwiązywaniu problemów w tym również nietypowych,
odbiegających od standardów.
Niezwykle ważnym zadaniem szkoły jest wyrobienie w uczniach umiejętności uczenia
się, umiejętności twórczego myślenia, przełamanie bariery „lenistwa umysłowego”.
Nowe technologie umożliwiają stosowanie nowych środków opisu rzeczywistości,
a informatyka dostarcza narzędzi do komputerowego modelowania rzeczywistości oraz
do rozwiązywania problemów. Znajomość algorytmiki, podstaw programowania, języka
programowania stała się niemal tak ważna jak znajomość języka obcego.
W obecnym modelu nauczania ścierają się dwa główne nurty: behawioryzm
i konstruktywizm.
Twórcą behawioryzmu był John B. Watson. Nurt ten znacząco zdominował nauki
pedagogiczne. Wiele poglądów behawiorystycznych dalece odbiega od wymogów
dzisiejszej szkoły, dzisiejszej edukacji. Jednak nie można tego nurtu zupełnie pominąć
bowiem pewne jego założenia są nadal skuteczne, oczywiście często w zmodyfikowanej
formie, adekwatnej do dzisiejszej rzeczywistości. Przede wszystkim wątpliwości budzi
fakt, że behawioryzm traktuje ucznia jak czystą kartkę papieru, na której nauczyciel
zapisuje, w kolejności przez siebie zaplanowanej, nauczane treści. Poprzez odpowiedni
dobór konsekwencji zachowań, czyli nagród i kar wzmacnia lub osłabia zachowania
60
ucznia. W efekcie takie podejście prowadzi do nauczania programowanego
(opierającego się o niezmienny, sztywny program nauczania) i dyrektywnego (częste
posługiwanie się metodą podającą, krytykowanie niepowodzeń ucznia, odwoływanie się
do autorytetów, udzielanie wskazówek).
Podstawowym założeniem konstruktywizmu jest potraktowanie ucznia jako osoby
aktywnej będącej twórcą swojej własnej wiedzy. Wiedzy nie można przekazać uczniowi
(można przekazać informację), uczeń sam musi swoją wiedzę wybudować. Ważna jest
aktywność ucznia w procesie budowania własnej wiedzy. Konstruktywizm wskazuje, że
uczeń to nie puste naczynie, które wypełniane jest przez nauczyciela, ale osoba aktywnie
konstruująca swoją wiedzę.
Jednym z nurtów konstruktywizmu jest konstrukcjonizm (jest to strategia uczenia się
jak również strategia edukacyjna). Konstrukcjonizm zakłada, że w procesie uczenia się
uczniowie aktywnie angażują się w tworzenie własnych przedmiotów, zdarzeń, idei,
pomysłów, którymi w ramach nauczanych treści mogą podzielić się z innymi w celu
wspólnej analizy i refleksji. Dla konstrukcjonizmu nie bez znaczenia jest aspekt
społeczny – uczenie się poprzez współpracę w zespole, dyskusję, wymianę poglądów.
Ponieważ konstrukcjonizm przewiduje konstruowanie materialnych, zewnętrznych
(istniejących poza rozumem) reprezentacji abstrakcji, szczególne znaczenie ma fakt, że
zewnętrzną formą reprezentacji wiedzy, dowodem na rozumienie zagadnień i zjawisk,
jest program komputerowy, a w zasadzie algorytm. Ponadto, ujmując powyższe
w odwrotnej hierarchii przyczynowo-skutkowej, sam proces tworzenia algorytmu
(programu komputerowego) wymaga zgłębienia i zrozumienia problemu (zjawiska).
Proces tworzenia algorytmu (programu) jest procesem, w którym osoba tworząca
algorytm kreuje również swoją wiedzę dotyczącą analizowanego problemu (zjawiska),
dlatego możemy mówić o uczeniu się poprzez tworzenie algorytmów, uczeniu się
poprzez programowanie.
Osiem wielkich idei konstrukcjonizmu wg Saymoura Paperta (Saymour Papert –
południowoafrykański matematyk i informatyk, m. innymi autor języka programowania
LOGO).
„Pierwszą wielką ideą jest uczenie się przez tworzenie. Uczymy się lepiej, gdy uczenie
się jest elementem uprawiania czegoś, co nas prawdziwie interesuje. Uczymy się
najskuteczniej, gdy możemy wykorzystać to, czego się nauczyliśmy do zaspokojenia
jakichś naszych potrzeb lub pragnień.
Druga wielka idea to technologia jako tworzywo. Dysponując technologią możesz
tworzyć znacznie więcej interesujących nas rzeczy i tworząc je możesz się znacznie
więcej nauczyć. Dotyczy to szczególnie technologii cyfrowej.
Trzecia idea – to idea ostrej zabawy. Uczymy się i pracujemy najlepiej, gdy to nas cieszy.
Ale „cieszy nas”, to nie znaczy „jest łatwe”. Najwięcej satysfakcji daje ostra zabawa (hard
fun). Nasi sportowi bohaterowie pracują bardzo ciężko, by być najlepszymi w swej
61
dyscyplinie. Najlepszy stolarz znajduje radość w stolarstwie. Najskuteczniejszego
biznesmena cieszy trudne ubijanie interesów.
Czwarta wielka idea to idea uczenia się jak się uczyć. Wielu uczniów wynosi ze szkoły
przekonanie, że jedyny sposób uczenia się polega na tym, że ktoś cię musi nauczyć. To
jest przyczyna niepowodzeń w szkole i w życiu. Nikt nie jest w stanie nauczyć cię tego
wszystkiego, co musisz umieć. Musisz sam wziąć odpowiedzialność za swoje uczenie się.
Piąta wielka idea – to daj sobie czas odpowiedni do zadania. Wielu uczniów wynosi ze
szkoły przyzwyczajenie, że ktoś mówi co pięć minut albo co godzinę: zrób to, zrób tamto,
a teraz to. Jeśli ktoś nie dyktuje im co mają robić zaczynają się nudzić. W życiu jest
zupełnie inaczej, by stworzyć coś naprawdę ważnego, musisz się nauczyć sam
gospodarowania własnym czasem. To jest najtrudniejsza lekcja dla wielu uczniów.
Szósta idea najważniejsza ze wszystkich: nie ma sukcesu bez niepowodzeń. Nic
naprawdę ważnego nie działa od razu dobrze. Jedyna drogą do sukcesu jest staranne
analizowanie, co i dlaczego nie funkcjonuje prawidłowo. By odnieść sukces, musisz
uwolnić się od strachu przed błędami.
Siódma wielka idea – to praktykuj sam, co zalecasz uczniom. Uczymy się przez całe
życie. Choć mamy bogate doświadczenie pracy nad projektami, każdy jest inny i zwykle
realizując kolejny nie potrafimy ze wszystkimi szczegółami z góry przewidzieć, jak to
będzie działać. Bawi nas to, co robimy, ale wiemy, że czeka nas ciężka praca. Każda
trudność jest okazją do nauki. Najlepsza lekcja, jakiej możemy udzielić naszym uczniom,
to przekazanie im, jak sami uczymy się.
Ósma wielka idea: wkraczamy w cyfrowy świat, w którym znajomość technologii
cyfrowej jest równie ważna jak czytanie i pisanie. Tak więc uczenie się o komputerach
jest kluczowe dla przyszłości naszych uczniów. Ale najważniejszym celem jest
używanie ich TERAZ do uczenia się innych rzeczy.” (Walat, 2007b)
Wykorzystanie algorytmów w nauczaniu bezsprzecznie wpisuje się
w konstrukcjonistyczną strategię edukacyjną. Algorytmika sama w sobie jest
nierozerwalnie związana z technologią, jak również stymuluje twórcze myślenie
i konieczność poszukiwania optymalnych rozwiązań.
3.2 Koncepcja myślenia komputacyjnego w nauczaniu myślenia
algorytmicznego
Algorytmy i programowanie w nauczaniu to również realizacja koncepcji nauczania
myślenia algorytmicznego, myślenia komputacyjnego.
W roku 2006 Jeannette Wing (dyrektor Avanessians w Data Sciences Institute na
Columbia University, profesor informatyki) zaczęła propagować swoją ideę myślenia
komputacyjnego (Sysło, 2012).
62
„Myślenie komputacyjne, towarzyszące procesom rozwiązywania problemów za pomocą
komputerów, można scharakteryzować następującymi cechami:
problem jest formułowany w postaci, która dopuszcza i umożliwia posłużenie się
do jego rozwiązania metodami informatycznymi i komputerem lub innymi
urządzeniami służącymi do zautomatyzowanego przetwarzania informacji;
problem polega na logicznej organizacji danych i wyciagnięciu z nich wniosków;
pozwala wyabstrahować reprezentację danych, na przykład w postaci modelu lub
symulacji;
rozwiązanie problemu ma postać ciągu kroków, może więc być otrzymane
w wyniku zastosowania podejścia algorytmicznego;
projektowanie, analiza i komputerowa realizacja (implementacja) rozwiązania
problemu prowadzą do otrzymania jak najbardziej efektywnego rozwiązania
oraz jak najlepszego wykorzystania możliwości i zasobów komputera;
doświadczenia nabyte podczas rozwiązywania jednego problemu można
wykorzystać do rozwiązywania innych problemów, pokrewnych jak i z innych
dziedzin.
Na myślenie komputacyjne, w rozumieniu wykorzystywanym przez Centrum Edukacji
Obywatelskiej, składają się następujące umiejętności i postawy:
Umiejętności
1. Formułowanie problemów. Rozpoznawanie, nazywanie problemów, zadawanie
odpowiednich pytań.
2. Zbieranie danych. Określanie rzetelności danych i wiarygodności źródeł
informacji.
3. Rozkładanie na części. Porządkowanie danych, dzielenie zadań na mniejsze
4. Rozpoznawanie schematów. Klasyfikowanie (tworzenie zbiorów),
rozpoznawanie podobieństw, znajdywanie istotnych i nieistotnych różnic,
uogólnianie.
5. Abstrahowanie i tworzenie modeli. Usuwanie zbędnych informacji, upraszczanie,
tworzenie modeli.
6. Tworzenie algorytmów. Ustalanie kolejnych kroków i tworzenie zasad,
sekwencja, rekurencja (powtarzalność procedur i czynności).
7. Wykrywanie i diagnozowanie błędów. Wyszukiwanie, znajdowanie
i analizowanie błędów.
8. Zrozumiałe i skuteczne komunikowanie się. Formułowanie zrozumiałych
komunikatów, dostosowanych do odbiorcy (komputera lub innych ludzi),
kodowanie, przedstawianie (symbole i znaki).
Ocenianie
Rozpoznawanie kryteriów wartościowania, określanie priorytetów, ocenianie
prototypów i rozwiązań
63
Logiczne myślenie
Wyciąganie wniosków, rozpoznawanie błędów logicznych, argumentowanie
Postawy i nawyki
1. Poszukiwanie. Eksperymentowanie, swobodne i otwarte poszukiwanie
rozwiązań, zabawa z rozwiązaniami.
2. Kreatywność i pomysłowość. Rozwijanie i wykorzystywanie wyobraźni,
wymyślanie nowych rozwiązań.
3. Udoskonalanie. Krytyczne podejście do efektów swojej pracy i nastawienie na ich
ciągłe udoskonalanie i poprawianie.
4. Wytrwałość i cierpliwość. Trwanie w dążeniu do celu, opanowanie
w oczekiwaniu na efekty, świadomość konieczności poniesienia wysiłku
5. Współpraca. Praca w grupie i parach.
6. Zdrowy dystans do technologii. Zastanawianie się nad ograniczeniami technologii
i krytyczny stosunek do niej”.
Niepowodzenia i problemy dzisiejszej szkoły powoduje wiele czynników, zarówno
wynikających z negatywnego bagażu behawioryzmu, jakim obciążony jest nauczyciel,
jak i będących skutkiem zmian w sposobie myślenia nowych pokoleń, spowodowanych
często negatywnym wpływem technologii. Negatywny wpływ technologii ma zazwyczaj
źródło w samym procesie nauczania i wychowania.
Jednym z typowych problemów jaki napotykamy w nauczaniu, szczególnie matematyki
i innych przedmiotów ścisłych to nadmierne oczekiwanie ucznia na wskazanie przez
nauczyciela konkretnych przepisów na rozwiązanie problemu – oczekiwanie na
poprowadzenie krok po kroku. Dzisiejsi uczniowie, osoby należące do pokolenia Z,
oczekują natychmiastowo podanej informacji wskazującej sposób rozwiązania
problemu, oczekują szybkich rezultatów, nie są skłonni do wysiłku umysłowego, mają
problem z odniesieniem do otaczającej rzeczywistości zadań przedmiotowych. Z drugiej
strony nauczyciel, który w większości swojej pracy stosuje behawiorystyczny model
nauczania, sprowadzający się do podania informacji i przećwiczeniu omawianych treści
na typowych zadaniach. Często skutkuje to tym, że w przypadku trudności ze
zrozumieniem materiału, nauczyciel zbyt szybko podejmuje działania wskazujące
uczniowi dalsze etapy, sposoby rozwiązania problemu, „podpowiadając” uczniom co
mają robić, jak mają działać. Ponadto nauczyciele preferują ćwiczenie i opanowanie
drobnych, izolowanych umiejętności i faktów, używają zadań, w których należy
zastosować jedną lub dwie proste umiejętności. Natura pokolenia Z i tradycyjny model
nauczania umacniają przyzwyczajenie ucznia do takiego stanu rzeczy, że gdy znajdzie
się on w sytuacji trudnej – wtedy nauczyciel pokaże dalszą drogę do rozwiązania.
Uczniowie zaczynają coraz częściej wykazywać niechęć do wysiłku umysłowego, niechęć
do poszukiwania rozwiązań, analizowania, podejmowania prób zgłębienia tematu
i coraz bardziej skłonni są, do często bezmyślnego, szukania gotowych wzorców
rozwiązania. Dzieje się tak dlatego, że, jak już wspomniano, ćwiczenie ich umiejętności
64
polegało na rozwiązaniu bardzo typowych, jednolitych problemów (zadań), więc gdy nie
znajdują odpowiednich, gotowych wzorców dochodzą do przekonaniu, że zadania nie da
się rozwiązać.
Jeśli natomiast na uczniach zostanie w sposób dyrektywny wymuszona konieczność
samodzielnego dojścia do rozwiązania, często popadają w skrajność bezmyślnego
stosowania jakichkolwiek wcześniej poznanych modeli czy wzorów.
Technologia daje możliwość działania metodą prób i błędów. Co prawda metoda taka
jest jedną z podstawowych metod kreatywnego myślenia, lecz należy pamiętać, że
w zdrowym stosowaniu tej metody – po wybraniu wariantu rozwiązania następuje
dogłębne analizowanie, sprawdzanie poprawności metody, dochodzenie do
konstruktywnych wniosków. Dzisiejszy uczeń natomiast oczekuje i poprzez nową
technologię przyzwyczajony jest do natychmiastowej odpowiedzi na jego działanie
(próbę). Komputerowe gry edukacyjne dają odpowiedź natychmiast, stosowanie metody
prób i błędów jest dla ucznia bezbolesne, proste, bowiem jest w stanie w stosunkowo
krótkim czasie sprawdzić setki wariantów rozwiązań. Działania uczniów pozbawione są
podstawowych i ważnych w procesie budowania wiedzy elementów takich jak:
postawienie hipotezy, sprawdzenie hipotezy, wyciągnięcie wniosków (w tym
zdobywanie doświadczenia) i postawienie nowej hipotezy uwzględniającej wcześniejsze
wnioski. Dzieje się tak dlatego, że nowe technologie dostarczają możliwości wręcz
błyskawicznego potwierdzenia lub zaprzeczenia postawionej hipotezie, uczeń przestaje
zastanawiać się nad przesłankami poprawności hipotezy, nie analizuje niepowodzeń,
bezmyślnie testuje kolejny pomysł na rozwiązanie problemu.
3.3 Zastosowanie myślenia komputacyjnego w praktyce edukacyjnej
W szkołach natomiast, tak naprawdę nie uczy się wykorzystania nowych technologii do
rozwiazywania problemów – nowe technologie wykorzystuje się zazwyczaj do
szybkiego wyszukiwania informacji, wspomagania procesu dydaktycznego – jako
nowoczesne medium przekazu informacji, jako narzędzie wspomagające typowe prace
biurowe. W większości przypadków stosowanie nowych technologii sprowadza się do
biernego wykorzystywania ich funkcjonalności.
Tak więc w dzisiejszej edukacji ścierają się sposoby działania nauczycieli wynikające
z tradycyjnego behawiorystycznego podejścia z oczekiwaniami i możliwościami
pokolenia Z. Nie bez przyczyny o pokoleniu młodych mówi się „cyfrowi tubylcy”,
a tymczasem w wśród nauczycieli spotykamy osoby nalężące do pokolenia tzw.
„cyfrowych imigrantów”. A jeśli nawet nauczyciel jest osobą młodą, to często nauczono
go metod działania „cyfrowych imigrantów”.
Istotnym wydaje się również sąd Andrzeja Walata. „Bardzo wielu uczniów, nie tylko
w Polsce, jest przekonanych, że jeśli nie potrafi przypomnieć sobie algorytmu – wzoru na
rozwiązanie zadania, to sami go nie wymyślą i dalsze zajmowanie się problemem jest tylko
65
stratą czasu. Wielu myśli też, że zadania szkolne zwykle „nie mają sensu”, więc nawet nie
próbuje używać rozumu. trzeba stosować wyuczone metody, myślenie może tylko
zaszkodzić. Trudno nie zadać pytania: Dlaczego tak jest? oraz Jak to zmienić? Niewątpliwie
na listę celów nauczania należy wpisać kształtowanie i ugruntowanie przekonań,
w szczególności przekonania że:
można samemu wymyślić algorytmiczne rozwiązanie problemu,
wiele zadań ma wiele różnych poprawnych rozwiązań i zwykle istnieje wiele
zasadniczo różnych sposobów podejścia do problemu,
warto najpierw szukać rozwiązania problemu we własnej głowie, a dopiero
potem w książce,
nie należy rezygnować po pierwszych niepowodzeniach, zwykle znalezienie
rozwiązania interesującego zadania wymaga wielu prób.”
Przy odpowiedniej strategii działania nauczyciela, uczenie poprzez tworzenie
algorytmów (poprzez myślenie algorytmiczne) może niwelować powyższe wady,
bowiem nawet przyjęcie metody prób i błędów może być ukierunkowane tak, aby już na
etapie stawiania hipotezy wprowadzić analizę problemu opartą na wiedzy już zdobytej.
Ponadto samo sprawdzenie hipotezy (analiza gotowego algorytmu, sprawdzenie jego
poprawności logicznej i merytorycznej) niesie za sobą konieczność ponownego
sięgnięcia po informację i wiedzę dotyczącą tematu problemu. Kolejna analiza tematu,
występująca w procesie sprawdzania (testowania) rozwiązania jest niezwykle istotna,
bowiem stanowi element ugruntowujący już zdobytą wiedzę, jak również przyczynia się
do zdobywania kolejnych elementów doświadczenia związanego z tematem problemu.
Jest to też moment, w którym można, a czasami nawet trzeba, poszerzyć dotychczas
przyswojone informacje, bowiem testowanie rozwiązania może wskazać na braki
wiedzy lub niewłaściwe, niepełne zrozumienie tematu. Tabela 5 prezentuje połączenie
pomiędzy działaniami, myśleniem komputacyjnym a elementami pomysłów
konstrukcjonistycznych.
Tabela 5. Powiązanie między działaniami, myśleniem komputacyjnym i elementami pomysłów
konstrukcjonistycznych
Działania nauczyciela/uczniów Elementy myślenia
komputacyjnego Elementy wielkich idei
konstrukcjonistycznych
Wstęp 1) Jasno i klarownie
przedstawiony problem do rozwiązania, ze zwróceniem szczególnej uwagi na: a) dane wejściowe – czyli
jasne określenie sytuacji początkowej, zbioru danych początkowych,
b) dane wyjściowe – zdefiniowanie oczekiwanej sytuacji końcowej, zbioru danych
1. Sformułowanie problemu. 2. Określenie danych
wejściowych – stanu początkowego.
3. Określenie danych wyjściowych – stanu końcowego, efektu.
4. Ewentualne określenie źródeł informacji.
Realizacja rozwiązania problemu wymaga stworzenia algorytmu, programu. Problem sformułowany w sposób zaspokajający potrzeby ucznia – zabawy, rywalizacji, treść problemu zawiera się w zainteresowaniach uczniów, jest dla nich wyzwaniem. Do rozwiązania problemu konieczne jest użycie technologii, tak aby myślenie przestało być udręką, stało się łatwiejsze,
66
końcowych, określenie rozwiązania.
Należy również określić ograniczenia oraz przypomnieć elementy wiedzy podstawowej związanej z tematem.
skuteczniejsze. Technologią posługujemy się do uczenia się – uczenie się poprzez tworzenie algorytmu (programu)
Faza główna 1. Kierowanie akcjami uczniów
zmierzającymi do rozwiązania zadania - budowanie algorytmu, sekwencji działań rozwiązujących problem (zadanie)1.
2. Wyszukiwanie / badanie rozwiązania.
3. Tworzenie rozwiązania.
1. Podział problemu na mniejsze części, aby ułatwić i rozwiązać problem. Wyszukaj podobne istniejące wzorce. Jeśli zadanie (problem) zostanie rozwiązane metodą projektu lub w grupach studenckich, możliwe jest również zaproponowanie podziału obowiązków (zakresu pracy) pomiędzy poszczególne osoby.
2. Kreatywne dążenie do rozwiązania2.
3. Tworzenie modeli i symulacji, upraszczanie.
1. Przedstawiamy pomysł ciężkiej zabawy3.
2. Dajemy uczniom czas4.
Faza końcowa 1. Prezentacja rozwiązań,
dyskusja na temat ich poprawności.
2. Testowanie, sprawdzanie różnych zestawów danych w różnych sytuacjach.
3. Możliwe poprawki algorytmu (programu).
Poszukiwanie optymalnego rozwiązania, testowanie, wyszukiwanie, znajdowanie i analizowanie błędów.
Bez porażek nie ma sukcesu. Niezależne poszukiwania, kreatywność w rozwiązywaniu zadań mających wiele różnych wariantów rozwiązań i błędów pomaga lepiej zrozumieć rdzeń zjawiska, problem lub zadania.
Źródło: opracowanie własne
1 Należy podkreślić, że praca nauczyciela powinna skupiać się wyłącznie na kierowaniu działaniami uczniów
w sytuacjach, w których ich myślenie jest dalekie od oczekiwanego. Należy pamiętać, aby nie tworzyć
sztucznych ograniczeń w procesie myślenia uczniów. Nauczyciel powinien ingerować w działania uczniów, jeśli
ich myślenie zaczyna być niewłaściwe, odbiegając od zdobytej do tej pory wiedzy. Jednak nie zawsze
interwencja nauczyciela jest konieczna, gdy rozumowanie uczniów jest nieprawidłowe z powodu braku wiedzy
(proces tworzenia rozwiązania nadal dotyczy uczniów). W takich przypadkach informacje mogą być uzupełniane
gdy uczniowie sami odkryją, że ich strategia rozwiązania problemu utknęła z powodu braku wiedzy. Być może
konieczny będzie powrót do stanu początkowego i wprowadzenie elementów pracy twórczej nad
rozwiązywaniem problemu, szczególnie w przypadkach, gdy niezbędna ingerencja nauczyciela dotyczyłaby
informacji (wiedzy) wykraczającej poza zakres przewidziany dla danego etapu edukacyjnego. Nie jest możliwe
postawienie sztywnej granicy. Nauczyciel, który zna swoich uczniów, ich umiejętności i możliwości, decyduje,
jak podzielić problem na mniejsze części, które są łatwe i możliwe do rozwiązania. Pomocne będzie
poszukiwanie podobieństw do istniejących wzorców. 2 Jest to najważniejszy moment przede wszystkim dlatego, że podczas procesu tworzenia rozwiązania
(algorytmu) uczniowie skutecznie zdobywają wiedzę (łączą informacje z kontekstem i doświadczeniem). 3 Jeśli problem zostanie sformułowany w celu zaspokojenia potrzeb ucznia, nie będzie to nudny problem.
Zazwyczaj uczniowie nie radzą sobie w szkole, ponieważ coś jest zbyt trudne, lub ponieważ sposób w jaki
przekazywane są treści jest nudny. Jeśli jesteśmy zdeterminowani, mamy do czynienia z fascynującym
problemem wzbudzającym zainteresowanie, to praca nad jego rozwiązaniem nie sprawia nam żadnych
problemów. Jesteśmy gotowi dać więcej. Wysiłek podczas ekscytującej, uzależniającej zabawy nie jest
wyłącznie dobry, sprawia również radość. Sama droga prowadząca do celu staje się bardziej ekscytująca niż sam
fakt osiągnięcia go. 4 Pozwól im znaleźć rozwiązanie we własnym tempie. Sukces w rozwiązywaniu zadania wymaga czasu, często
wielu prób, odkrywania problemu, poszukiwań, podejścia i spojrzenia z różnych perspektyw.
67
Przedstawiony powyżej sposób interpretacji i działania daje możliwość formułowania
problemów, zadań, których rozwiązanie wymaga zastosowania wielu różnych
umiejętności, różnych idei. Sprzyja konstruowaniu rozumowania oraz budowaniu
znaczeń i powiązań.
Wykorzystanie algorytmów i programowania (myślenia komputacyjnego) może
stanowić istotny i fascynujący dodatek do zajęć przedmiotowych.
Podejście polegające na dekompozycji problemu (tworzeniu algorytmów, procedur dla
podproblemów) sprzyja szukaniu rozwiązań bazując na wiedzy, którą do tej pory
posiadamy. Umożliwia rozpoznanie wzorców, znalezienie podobieństw i różnic, wyrabia
umiejętność przewidywania rozwiązania. Natomiast uogólnienia jakie stosujemy
tworząc algorytmy (programy) pozwalają na poznanie i przyswojenie ogólnych zasad,
twierdzeń.
Myślenie algorytmiczne, rozumiane nie jako umiejętność wykonywania algorytmu, tylko
jako umiejętność analizy problemu, analizy zadania w celu opracowania rozwiązania
opisywanego jako zestaw kroków, sprzyja dogłębnemu poznaniu problemu. Tworzenie
algorytmu jest sposobem uczenia się, pomaga poznać i zrozumieć bardzo wiele
obszarów z różnych dziedzin wiedzy szczególnie matematycznej i z zakresu pozostałych
przedmiotów ścisłych.
Wiedza zdobyta „przy okazji” tworzenia rozwiązania algorytmicznego (pisania
programu) jest zwykle głębsza i trwalsza. Uczenie się przy zaangażowaniu w realizację
innych celów – np. tworzenia programu, jest bardziej skuteczne.
Ponadto w publikacji Kazimierza Mikulskiego (2017) czytamy: „Dzisiejszym językiem
kreatywności jest programowanie dające dzieciom i młodzieży możliwość twórczego
podejścia do dóbr informatycznych i rozwoju pozytywnych cech działania, pomagając im
w przyszłej karierze zawodowe. Programowanie uczy młodego człowieka logicznego
myślenia, rozwiązywania problemów i przede wszystkim pracy w grupie.”
Natomiast, Tomasz Kopczyński (2016) napisał: „Rolą współczesnego nauczyciela jest
przygotowanie uczniów do nabycia kompetencji kluczowych i opanowania ich w takim
stopniu, aby w przyszłości mogli poradzić sobie w realnym świecie społecznym. To
właśnie programowanie pomaga poznać i zrozumieć wiele interesujących i ważnych
obszarów z różnych dziedzin wiedzy, które uczeń mógł poznać w procesie uczenia się
w szkole.
Wykorzystywanie nowych technologii w szkole nie jest celem samym w sobie.
Cyfryzacja ma na celu wsparcie procesu uczenia się i nauczania, w którym uczeń jest nie
tylko uczestnikiem, ale również twórcą i ważnym ogniwem. Umożliwia to
indywidualizacje procesu kształcenia i przygotowania do samodzielnego korzystania
z zasobów edukacyjnych, a przede wszystkim w perspektywie dłuższej do
przygotowania do dorosłego życia, w którym również uczniowie będą musieli stale
68
rozwijać się, nabywać nowe kompetencje i umiejętności, kształcić się ustawicznie aby
wykonywać profesjonalnie wybrany zawód lub nawet zdobyć nowy. Dlatego tym, czego
naprawdę chcemy uczyć dzieci to nie jest samo programowanie, ale umiejętności jakich
ono wymaga m. in. logiczne myślenie, rozwiązywanie zadań. Nie ma potrzeby kształcić
wszystkich na przyszłych informatyków czy programistów, ale rozwijać nawyki
myślowe ułatwiające funkcjonowanie we współczesnym świecie”
Można wyróżnić kilka sfer wykorzystania algorytmiki i programowania:
jako jednego z elementów z założenia wspomagającego zdobywanie nowej wiedzy,
w przypadkach gdy nowa wiedza i umiejętności są a priori algorytmiczne,
jako działanie wspomagające wprowadzenie nowych treści, nowych pojęć, gdzie
myślenie komputacyjne i algorytmika są jednymi z narzędzi,
jako działanie, podczas którego uczniowie „przy okazji” ćwiczą zastosowanie
zdobytej wiedzy w różnych problemach, w tym nietypowych oraz zdobywają nową
lub poszerzają już zdobytą wiedzę i umiejętności.
Warto również zwrócić uwagę na nieco inny podział zastosowania programowania:
narzędzie do rozwiązywania zadań obliczeniowych
narzędzie do rozwiązywania zadań sterowania (symulacje, tworzenie modelu
rzeczywistości)
Praktyczne wprowadzanie myślenia komputacyjnego, algorytmiki i programowania
często budzi wiele obaw i wątpliwości, głównie dlatego, że nauczyciele przedmiotów
nieinformatycznych nie zawsze potrafią zobaczyć w aktualnie przerabianym materiale
taką możliwość. Często trzeba samemu przebrnąć przez wiele problemów, aby nauczyć
się rozpoznawać obszary własnego przedmiotu, w których można stosować algorytmikę
i programowanie.
Należy mieć również na uwadze to, że powinna istnieć ścisła współpraca nauczyciela
przedmiotu nieinformatycznego z nauczycielem informatyki, bowiem wiele problemów
będzie również zawierało w sobie podproblemy czysto informatyczne. Z drugiej strony
nauczyciel informatyki też powinien mieć świadomość, że wykorzystanie algorytmiki
i programowania na innych przedmiotach, pokazuje uczniom, że świat cyfrowy nie jest
oderwany od innych dyscyplin, że tak naprawdę informatyka otacza nas zewsząd.
Pierwsze problemy algorytmiczne z jakimi uczniowie spotkają się na przedmiotach
nieinformatycznych będą dla niech na początku trudne. Realizowanie założeń
konstrukcjonizmu nie wystąpi automatycznie, w pierwszej fazie nauczyciel powinien
wybierać problemy bardzo łatwe, więcej ukierunkowywać uczniów, aby nabrali
pewnego doświadczenia, nauczyli się pewnych wzorców „podejścia do tematu”.
69
3.4 Praktyczne ćwiczenia z wykorzystaniem algorytmów i programowania
Na kilku przykładach prześledźmy w jaki sposób można wykorzystywać algorytmikę
i programowanie w nauczaniu przedmiotów nieinformatycznych.
PRZYKŁAD 1.5
Napisz program spełniający następujące założenie:
w środku ekranu znajduje się punkt. W prawej górnej części ekranu program losowo
rysuje figurę geometryczną (trójkąt, czworobok). Zadaniem gracza jest narysowanie
w dolnej lewej części ekranu takiej figury, aby po obrocie o 180 stopni pokryła się jak
najdokładniej z figurą narysowaną przez komputer.
Po dekompozycji problemu otrzymamy zestaw podproblemów związanych z częścią
graficzną. Podproblemy te nie będą stanowiły większej trudności (np. w Scratchu są do
rozwiązania w dość prosty sposób).
Z punktu widzenia nauczania pozainformatycznego interesować nas będzie podproblem
matematyczny - znalezienie współrzędnych wierzchołków figury po obrocie o kąt
półpełny. To zagadnienie może posłużyć matematykowi do wprowadzenia pojęcia
symetrii osiowej i symetrii środkowej (obrót o kąt półpełny to symetria środkowa).
Podproblem matematyczny możemy poddać dalszej dekompozycji, do momentu gdy
zestaw czynności będzie zrozumiały i w prosty sposób możliwy do wykonania przez
ucznia. Zakładamy, że uczeń nie zna jeszcze pojęć symetrii.
Rozważmy więc następujący problem – jak utworzyć obraz będący odbiciem lustrzanym
obrazu pierwotnego. Ponadto zagadnienie ułatwimy „powiększając” płaszczyznę, tak
aby operować na poszczególnych pikselach, analizując układ czterech pikseli. Jest to
również istotny moment naszego działania z uczniami bowiem nasze rozważania
przeprowadzimy modelując rzeczywistość w sposób uproszczony. Wyobraźmy sobie, że
na płaszczyźnie mamy cztery elementy oraz widoczne krawędzie luster ustawionych do
siebie pod katem prostym.
Płaszczyzna składa się z pojedynczych pikseli, których położenie możemy określać za
pomocą współrzędnych (tradycyjnie oś pozioma X, pionowa Y). Grubość lustra pokrywa
się z wymiarem piksela. Na rysunku grube czarne linie to krawędzie luster. Czerwona
figura, na której będziemy eksperymentować składa się z czterech pikseli. 5 Przykłady opracowane w oparciu o: Kolarz & Tluczykont, 2018.
70
W pierwszej kolejności zajmijmy się odbiciem lustrzanym względem poziomej linii.
9
8
7
6
5
4
3
2
1
1 2 3 4 5 6 7 8 9
Uczniowie z łatwością ułożą obraz, jaki powstanie po odbiciu lustrzanym figury
czerwonej.
9
8
7
6
5
4
3
2
1
1 2 3 4 5 6 7 8 9
Spróbujmy teraz znaleźć prawidłowości i zapisać w sposób algorytmiczny sposób
przekształcenia figury czerwonej w jej odbicie lustrzane (figura niebieska). Do analizy
przydatna nam będzie tabelka (w pierwszych dwóch wierszach zapisane są
współrzędne pikseli tworzących czerwoną figurę):
X Y X Y X Y 2 2 3 2 3 3 3 4
Po ułożeniu odbicia lustrzanego czerwonej figury, uczniowie uzupełniają w tabelce
współrzędne elementów niebieskich. Następnie próbują dokonać analizy i znaleźć
prawidłowości, w celu algorytmicznego zapisania sposobu przekształcenia (odbicia
lustrzanego).
71
X Y X Y X Y 2 2 2 8 3 2 3 8 3 3 3 7 3 4 3 6
1) Wybierz element
2) Współrzędne X wybranego element pozostaw bez zmian
3) Współrzędną Y oblicz w następujący sposób:
Yniebieskie = (Ykrawędzi – Yczerwony) + Ykrawędzi = 2 × Ykrawędzi - Yczerwony
4)Jeśli pozostał jakiś nieodbity element, przejdź do pkt 1)
Spróbujmy poeksperymentować z „lustrem”, którego krawędź przebiega w pionie,
szczególnie interesować nas będzie obraz obrazu powstałego w poprzednim odbiciu.
Mamy już gotowy algorytm, wystarczy zamienić miejscami X z Y.
Teraz uczniowie powinni zrealizować powyższy algorytm najpierw uzupełniając
współrzędne w tabelce, a następnie zgodnie z współrzędnymi „zielonymi” ułożyć
elementy na płaszczyźnie.
Uczniowie powinni rozpoznać, że będą postępować analogicznie jak przy współrzędnej
Y, a więc już mają gotowy algorytm i wystarczy zmienić miejsce Y na X.
1) Wybierz element
2) Współrzędne Y wybranego element pozostaw bez zmian
3) Współrzędną X oblicz w następujący sposób:
Xzielone = (Xkrawędzi – Xniebieski) + Xkrawędzi = 2 × Xkrawędzi - Xniebieski
4) Jeśli pozostał jakiś nieodbity element, przejdź do pkt 1)
Teraz uczniowie powinni zrealizować powyższy algorytm najpierw uzupełniając
współrzędne w tabelce, a następnie zgodnie z współrzędnymi „zielonymi” ułożyć
elementy na płaszczyźnie.
X Y X Y X Y 2 2 2 8 8 8 3 2 3 8 7 8 3 3 3 7 7 7 3 4 3 6 7 6
72
9
8
7
6
5
4
3
2
1
1 2 3 4 5 6 7 8 9
Czas na analizę i testowane rozwiązania.
Czym jest, dla wybranego czerwonego elementu, wartość powstała z obliczenie
wyrażenia (Ykrawędzi – Yczerwony)? Oczywiście odległością tego elementu od krawędzi
lustra wyrażoną w pikselach. Zatem zwiększając rozdzielczość wykorzystane zależności
też będą prawdziwe. Czy zwiększenie rozdzielczości do nieskończoności, a więc tak
naprawdę przejście na układ kartezjański, w którym, można powiedzieć, rozmiar piksela
jest nieskończenie mały, będzie również możliwe?
Odbicie lustrzane to przecież symetria osiowa, której właściwości odkryli uczniowie.
A złożenie dwóch symetrii osiowych o osiach prostopadłych do siebie to nic innego jak
symetria środkowa – obrót o kąt półpełny.
Ideą tego ćwiczenia jest to, że uczniowie sami doświadczają pewnych prawidłowości
i zależności, a dopiero potem dowiadują się, że ich „odkrycia” są już zdefiniowane.
PRZYKŁAD 2.
Dane jest równanie:
a = b/c
należy wyliczyć c.
Można uczniom przedstawić metodę, często zwaną magicznym trójkątem, zapisując
argumenty równania w następujący sposób:
c
a b
73
Zakrywając szukaną otrzymamy przekształcony wzór. Uczeń bardzo łatwo przyswoi
sobie tę metodę, nawet bez pomocy nauczyciela znajdzie ją w Internecie. Metodę da się
zapisać w postaci algorytmu. Lecz jaki będzie efekt? Uczeń zdobędzie informację
(przepis) na przekształcenie wzoru, lecz nie ma to nic wspólnego ze zdobyciem wiedzy.
Informacja jak to zrobić (przepis) będzie stosowana tylko i wyłącznie dla wzorów
w takiej a nie innej postaci. Uczeń wykona przekształcenie wzoru bezmyślnie. Nie
poradzi sobie w sytuacjach nietypowych (bardziej skomplikowany wzór).
Przeanalizujmy przekształcanie wzoru w postaci:
a = bc + d
Przed przystąpieniem do rozwiazywanie problemu należy przypomnieć uczniom
zasady:
1) do obu stron równania można dodać (odjąć) dowolne wyrażenie,
2) obie strony równania można pomnożyć (podzielić ) przez dowolne wyrażenie
(w przypadku dzielenia oczywiście należy wykluczyć dzielenie przez zero).
Krok A.1.: Dokonajmy dekompozycji problemu, problemem łatwiejszym będzie
równanie:
a = x + d
Nasze dane wejściowe to powyższa postać wzoru. Dane wyjściowe to wzór w postaci:
x =?
Krok A.2.: Co można zrobić korzystając z zasad 1) lub 2) aby x pojawił się po lewej
stronie i „zniknął” po prawej? Należy x odjąć obu stron równania:
a – x = x + d - x
co da nam:
a – x = d
Krok A.3.: Po lewej stronie równania zbędne jest a. Co można zrobić, aby pozbyć się
a z lewej strony równania? Trzeba zastosować metodę z Kroku A.1. (jest to przykład na
poszukiwanie wzorców, podobieństw) i odjąć a od obu stron równania:
a – x – a = d - a
co da nam:
(– x) = d - a
Krok A.4.: Jak zlikwidować minus przed x?
Trzeba –x pomnożyć przez -1, pamiętając że pomnożyć przez -1 trzeba obie strony
równania:
74
(-x) × (- 1) = (d - a) × (- 1)
co da nam:
x = (-d) + a [np.: x = d – a]
Krok A.5.: Przeanalizujmy tok postepowania (Kroki 2 do 4) pod kątem optymalizacji
działań. Zestawmy ze sobą pierwotna postać wzory (Krok A.1.) z postacią uzyskaną
w Kroku A.2.:
a = x + d
a - x = d
Krok A.6.: Wniosek - przenosząc wyrażenie z jednej strony na drugą zmieniamy znak na
przeciwny.
Tworzymy algorytm po optymalizacji działań.
Krok B.1.: Dane wejściowe – równanie w postaci:
a = x + d
Dane wyjściowe w postaci:
x = ?
Krok B.2.: Przenieś x na lewą stronę, zmieniając znak na przeciwny:
a - x = d
Krok B.3.: Przenieś a na prawą stronę zmieniając znak na przeciwny:
(-x) = d - a
Krok B.4.: Pomnóż obie strony równania przez -1:
(-x) × (- 1) = (d - a) × (- 1)
co da nam:
x = a – d
Kroki od B.1. do B.4. (algorytm) możemy nazwać: PRZEKSZTAŁĆ(x,a,d). W tym kroku
zdefiniowaliśmy procedurę, którą można od tego momentu wykorzystywać.
Wróćmy do problemu właściwego. Tworząc algorytm dla przekształcania wzoru:
a = b/c + d
postępujemy analogicznie ja w przypadku przedstawionego wyżej podproblemu,
omawiając z uczniami każdy jego krok, każdy przypadek dochodząc do ostatecznej
postaci algorytmu:
75
Rysunek 20 – Algorytm do przykładu 2
Źródło: opracowanie własne
Zwróćmy uwagę, że powyższe rozwiązane jest oczywiście prawidłowe, lecz można
dyskutować dalszą optymalizację, dalsze zgłębianie tematu. Uważny czytelnik zwróci
zapewne uwagę na fakt, że podczas przekształcania równania:
a = x + d
można najpierw przenieść d na lewą stronę uzyskując:
a – d = x
a następnie zapisać od razu równanie w postaci:
x = a - d
76
Zwrócenie uwagi na ten fakt jest okazją do dyskusji, że skoro a – d = x to x = a – d,
lecz również można powyższe rozbić na:
Krok C.1.:
(-x) = -(a - d)
Krok C.2.: Mnożymy równie obustronnie przez -1 otrzymując:
x = a - d
Przedstawiony przykład pokazuje, jak z pozoru trywialny dla nauczyciela problem
można przenalizować bardzo dogłębnie. Działania zmierzające do algorytmicznego
ujęcia rozwiązania dostarczają możliwości wnikliwej dyskusji, a tym samym
odpowiedniego zrozumienia tematu. Testowanie rozwiązania, próby optymalizacji, stają
się istotnym elementem, który sprzyja tworzeniu właściwych powiazań przyczynowo-
skutkowych podczas budowania uogólnień i uproszczeń. W prawidłowy sposób
budowane są wzorce i metody, bowiem przyswojenie ich wiąże się z procesem
zrozumienia.
PRZYKŁAD 3.
Zadanie dla uczniów
Utwórz grę, która polegać będzie na tym, ze gracz regulując prędkość początkową piłki
(prędkość w kierunku poziomym) spowoduje, że piłka wpadnie do kosza. Ruch piłki
powinien odzwierciedlać rzeczywistość.
Zadanie sformułowane jest bardzo ogólnie, jest typowym problemem dywergencyjnym.
Zadanie może być realizowane w grupach. Daj uczniom czas na zastanowienie się
i wypracowanie ogólnej strategii. Zaproponuj aby zapisali różne pomysły gry, najlepiej
w grupach z wykorzystaniem burzy mózgów. Uczniowie mogą zanotować pomysły
stosując mapę myśli.
77
Nauczyciel może naprowadzać uczniów wskazując jakie prawa fizyki powinny być
stosowane.
Załóżmy, że w efekcie analizy pomysłów uczniowie wybrali następujący wariant
rozwiązania:
Z lewej strony ekranu na, losowo wybranej wysokości (współrzędnej y) znajduje się
piłka. Po drugiej stronie ekranu, również na losowo wybranej wysokości znajduje się
kosz. Gracz mając do wyboru różne prędkości piłki (w poziomie) stara się trafić do
kosza. Na piłkę działają siły grawitacji – należy je przewidzieć, tak aby ruch piłki był
odzwierciedleniem ruchu piłki w rzeczywistości. Zaniedbane zostaną wszelkie opory
tarcia.
Należy zwrócić uwagę co będzie stanowić dane wejściowe, a co wyjściowe:
• danymi wejściowymi będą: pozycja (współrzędna y) piłki, oraz kosza,
• danymi wyjściowymi będą: pozycja końcowa piłki, przy założeniu, że program
zakończy swoje działanie, gdy:
o piłka wypadnie poza ekran;
o piłka wpadnie do kosza (zgodnie z projektem, dotknie prawej krawędzi
kosza);
78
o piłka uderzy w kosz, lecz do niego nie wpadnie (zgodnie z projektem
dotknie lewej krawędzi kosza).
Dekompozycja problemu
Uczniowie powinni zastanowić się i zdefiniować podproblemy. Poza trywialnymi i być
może mało istotnymi, z punktu widzenia nauczanego przedmiotu, podproblemami,
takimi jak opracowanie graficzne piłki, kosza, strzałki reprezentującej wektor prędkości,
sposobu sterowania, należy zwrócić szczególną uwagę na ruch piłki (trajektorię lotu).
Kluczowym będzie samodzielne, ewentualnie z pomocą nauczyciela, pełniącego jedynie
rolę pomocniczą, dojście do wniosku, że ruch piłki będzie składał się z ruchu poziomego
i pionowego. Właściwy efekt nauczyciel może osiągnąć poprzez zadawanie uczniom
odpowiednich pytań.
Pierwsze pytanie od nauczyciela
Spróbujmy uprościć problem, gdyby rozpatrywać tylko ruch piłki po płaskiej
powierzchni. Zakładając, że piłka na początku ma jakąś, zadaną przez gracza prędkość,
zaniedbując opory tarcia, jaki byłby to ruch?
Spodziewana odpowiedź uczniów: Ruch jednostajny prostoliniowy.
Drugie pytanie od nauczyciela
Gdyby taki ruch trzeba było przedstawić w postaci animacji, na czym by to polegało?
Spodziewana odpowiedź uczniów: Na przedstawieniu w krótkich odstępach czasu
kolejnych faz ruchu.
Trzecie pytanie od nauczyciela
Czy umiemy obliczyć pozycję piłki po upływie zadanego czasu? Jak na kartce papieru
narysować kolejne fazy ruchu? Co trzeba obliczyć? Jak mógłby wyglądać algorytm
umieszczający co jakiś czas na ekranie obiekt symbolizujący piłkę, poruszającą się
ruchem jednostajnie prostoliniowym? Jak wyglądałby prosty program realizujący taki
algorytm? Jakie równanie do tego wykorzystać?
Spodziewana odpowiedź uczniów: Równanie drogi w ruchu jednostajnym
prostoliniowym opisanym równaniami: s = V × t, lub na osi X: x = V × t.
W związku z powyższym algorytm i przykładowy program w Scratch’u przedstawiałyby
się następująco:
1) Podaj V
2) t = 0
3) x = V × t
4) Ustaw obiekt na pozycji x
5) Zwiększ t o 1
6) Powtórz od pkt 3)
79
Czwarte pytanie od nauczyciela
Co zrobić aby piłka zawsze przesuwała się od lewej części ekranu? Co zrobić aby piłka
zatrzymała się na prawej stronie ekranu?
Spodziewana odpowiedź uczniów: algorytm i program będą następujące:
1) Ustaw obiekt na pozycji x = -200, y = 0
2) Podaj V
3) t = 0
4) x = -200 + V × t
5) Ustaw obiekt na pozycji x
6) Zwiększ t o 1
7) Powtórz od pkt 4 tak długo dopóki x nie będzie większe od 200
Piąte pytanie od nauczyciela
Odłóżmy na bok problem ruchu poziomego. Co powoduje ruch piłki w dół? Jaki to jest
rodzaj ruchu? Czy znamy równania opisujące ten ruch? Czy doświadczenie zdobyte
w rozpatrywaniu ruchu poziomego będzie przydatne? Czy animacje ruchu pionowego
(spadania w dół) można zrealizować w podobny sposób, jakie ewentualnie będą
różnice? Jak mógłby wyglądać algorytm i program?
80
Spodziewana odpowiedź uczniów: algorytm i program będą następujące:
1) Ustaw obiekt na pozycji x = 0, y = 150
2) t = 0
3) y = 150 – 0,5 × 9.81 × t2
4) Ustaw obiekt na pozycji y
5) Zwiększ t o 1
6) Powtarzaj od kroku 3) dopóki y jest mniejsze niż 150
Pozostaje tylko poeksperymentować z powyższymi algorytmami, aby stworzyć wersję
końcową, choć być może nie ostateczną:
1) Ustaw obiekt na pozycji x = -200, y = 150
2) Podaj V
3) t = 0
4) x = -200 + V × t
5) y = 150 – 0.5 × 9.81 × t2
6) Ustaw obiekt na pozycji x, y
7) Zwiększ t o 1
8) Potwórz od pkt 4 tak długo dopóki y nie będzie mniejsze od 150 lub x nie
będzie większe niż 200
W powyższym przykładzie można odnaleźć istotne elementy myślenia komutacyjnego
i idei akonstrukcjonizmu. Problem został sformułowany w sposób bardzo ogólny, co
sprzyja rozwojowi umiejętności kreatywnego myślenia w celu poszukiwania rozwiązań
problemów dyferencyjnych. Rozwiązując problem staramy się określić czym są dane
wejściowe (stan na początku) oraz dane wyjściowe (stan na końcu, cel do osiągnięcia).
Określamy przy tym ewentualne ograniczenia. Mówiąc językiem informatyki
definiujemy specyfikację wejścia, specyfikację wyjścia oraz warunki brzegowe
(ograniczenia). Problem dzielony jest na podproblemy. Poszukując rozwiązania
stosujemy wzorce, odkrywamy prawidłowości (prawidłowością, wzorcem dla realizacji
pkt 5 będą punkty 3 i 4). Rozwiązanie ma postać listy kroków (algorytm, program).
Rozwiązanie (w tym również rozwiązania podproblemów) są testowane i poprawiane.
Uczymy poprzez tworzenie używając technologii jako tworzywa. Stosujemy ideę ostrej
zabawy, mając oczywiście nadzieję, że problem związany z zaprogramowaniem gry
bardziej wciągnie uczniów niż rozpatrywanie często oderwanych od rzeczywistości,
81
często nudnych dla uczniów typowych zadań. Tak naprawdę tworzymy symulację
zjawiska fizycznego, a przy okazji uczymy jak się uczyć – realizacja nawet prostych
zagadnień wymaga wgłębienia się w temat, przeanalizowania i efektywnie pomaga
zrozumieć zjawisko. Na pierwszy rzut oka przedstawione algorytmy mogą nie nasuwać
wielu aspektów, które wystąpią przy ich tworzeniu i testowaniu programu. Można
odnieść wrażenie, że zagadnienie sprowadza się tylko do zastosowania odpowiednich
równań fizycznych. Należy zwrócić uwagę na kilka innych aspektów, które ujawnią się
podczas testowania przedstawionych programów. Na tym etapie tworzenia algorytmu
nic nie mówiliśmy o jednostkach, jednak uczniowie w procesie testowania sami
zauważą, że „coś jest nie tak”. Przyjęcie kroku zmiany czasu o 1 daje dobry efekt
wizualny w przypadku ruchu poziomego, natomiast w przypadku ruchu pionowego
okaże się że efekt trwa zbyt krótko – krok czasowy jest zbyt duży. Jest to doskonała
okazja do dyskusji na temat jednostek, skali, doboru kroku czasowego – co właściwie
oznacza t=1. Jest to również doskonała okazja do zaznajomienia uczniów z tematem
symulacji komputerowej, zwrócenie uwagi, że w symulacjach w zdecydowanej
większości rozpatrujemy zmieniające się wielkości w czasie, że czas „nie przebiega”
w sposób ciągły, lecz podlega dyskretyzacji. Ponadto uczniowie zdobywają cenne
doświadczenie związane z umiejętnościami typowo programistycznymi. Warto również
zwrócić uwagę na to, że budujemy pewien obszar wiedzy, który do momentu realizacji
programu nie występował. W tym przykładzie uczniowie przed realizacją programu nie
musieli nic wiedzieć o tym, ze ruch, rozpatrywany w układzie współrzędnych można
rozłożyć na składowe. Znając zależności związane z ruchem poziomym i pionowym,
uczniowie mogą eksperymentować łącząc obydwa algorytmy, w ten sposób
uświadamiając sobie, że ruch po dowolnej trajektorii można przedstawić jako złożenie
ruchów wzdłuż osi układu odniesienia.
Dochodzenie do prawidłowego rozwiązania nie obędzie się bez wielu niepowodzeń. Jeśli
z pozoru poprawny algorytm, poprawny program, nie daje spodziewanego efektu,
skłania to do dalszej analizy problemu.
Należy pamiętać o tym, aby dać uczniom czas, pozwolić eksperymentować, umiejętnie
stosować wskazówki i pytania naprowadzające, wtedy gdy naprawdę utknęli
w martwym punkcie (Walat, 2007b).
„Uczeń pisząc programy symulujące, niewątpliwie pogłębia swoją wiedzę o naturze
spraw sterujących złożonymi procesami. Ta aktywność ma wielkie znaczenie dla
personalizacji wiedzy.” Ponadto „przy okazji poszukiwania rozwiązań interesujących
zadań matematycznych, a także problemów z zakresu innych przedmiotów można się
uczyć programowania znaczne skuteczniej niż na lekcjach, których jedynym celem jest
nauka programowania”.
Wykorzystanie myślenia algorytmicznego, przemiennego i programowania w nauczaniu
matematyki i innych przedmiotów przyrodniczych daje możliwość skutecznego
zdobywania wiedzy i uczenia się. Ważny jest fakt, że taki sposób pracy z uczniem daje
82
możliwość wykorzystania odpowiedniego, z punktu widzenia edukacji, sposobu
radzenia sobie z problemem (Kopczyński, 2016):
1. Podejście eksperymentalne (metoda prób i błędów, zawierające hipotezy, analiza
rozwiązań) - uczy konsekwencji w postępowaniu, buduje doświadczenie
"przyczyn i skutków".
2. Projektowanie i formułowanie - obejmuje elementy wyboru formy, oceny rzeczy
i metod niezbędnych do rozwiązania, jest procesem aktywnej realizacji
wyznaczonych celów.
3. Korygowanie (uczenie się na błędach) - często zajmuje więcej czasu niż
stworzenie algorytmu lub programu, ale jest ważnym elementem pogłębiania
wiedzy. Wymaga to określenia stanu początkowego, diagnozowania błędów
(nieporozumień, luk wiedzy), lokalizowania przyczyny do bieżącej sytuacji, próby
naprawienia lub wyeliminowania błędów, sprawdzenia poprawności po
usunięciu błędów.
4. Konsekwencja - niesie istotny element wytrwałości, niezbędny przy
wyszukiwaniu i naprawianiu błędów algorytmu (programu). Przyczynia się do
usystematyzowania wiedzy, tworzy dobre nawyki.
5. Współpraca - rozwija umiejętność pracy w grupie, promuje jeden z elementów
uczenia się - uczniowie uczą się od siebie, umożliwiają sprawną realizację
bardziej złożonych zadań, są istotnym elementem pracy.
Edukacja z wykorzystaniem algorytmów i programowania jest skutecznym sposobem
nauczania i uczenia się, jest odpowiedzią na wymagania edukacyjne dzisiejszej szkoły,
wpisując się w nowoczesne paradygmaty i strategie edukacyjne, a także jest
nieodzownym elementem interdyscyplinarnego przygotowania młodego człowieka do
funkcjonowania w cyfrowym świecie.
83
3.5 Bibliografia
Jelińska A., (2017). Kompetencje przyszłości. Po co nauczycielom przedmiotów
nieinformatycznych programowanie?
Kwiatkowska A. B., Sysło M. M. (ed.) Technologia informacyjna w edukacji. Toruń:
Wydawnictwo Naukowe Uniwersytet im. Mikołaja Kopernika.
Kolarz W., Tluczykont K., Kodowanie z matą. Stowarzyszenie Komputer i Sprawy Szkoły
KISS, Katowice, w przygotowaniu.
Kopczyński T., (2016), Myślenie komputacyjne jako imperatyw XXI wieku w kontekście
nadmiaru łatwej do pozyskania informacji.
Mitasa A. W. System komplementarnego nauczania algorytmiki w aspekcie myślenia
komputacyjnego , Goleszów: “Galeria na Gojach” A.B.K. Heczko.
Mikulski K., (2017), Programowanie elementem kreatywnej pedagogiki.
Myślenie Komputacyjne wg Centrum Edukacji Obywatelskiej. [Online source:
http://www.ceo.org.pl/sites/default/files/news-
files/elementy_myslenia_komputacyjnego_wedlug_ceo.pdf] (dostępne: 08.02.2018)
Sysło M. M., (2014). Myślenie komputacyjne. Nowe spojrzenie na kompetencje
informatyczne.
Walat A., (2007a). O konstrukcjonizmie i ośmiu zasadach skutecznego uczenia się według
Seymoura Paperta “Meritum” Cz. 4(7).
Walat A., (2007b). Zarys dydaktyki informatyki. Ośrodek Edukacji Informatycznej
i Zastosowań Komputerów, Warszawa.
84
Umiejętność wykorzystania algorytmiki i programowania jest uznawana przez
władze europejskie za jedną z ważnych, współczesnych umiejętności stanowiących
część „kompetencji cyfrowych”, która jest jedną z ośmiu kluczowych kompetencji.
Publikacja pt. „Algorytmy i programowanie - materiały szkoleniowe dla
nauczycieli. Algorytmiczny. Programowanie. Dydaktyka.” spełnia zalecenia
EURYDICE w tym zakresie. Głównym celem publikacji jest przedstawienie
nauczycielom idei algorytmiki i programowania wraz z ich praktycznym
zastosowaniem w dydaktyce.
[zamiast Wstępu]
ISBN 978-83-951529-1-7
Recommended