Rozdz. 4 - L5.pk.edu.pl
Transkrypt
Rozdz. 4 - L5.pk.edu.pl
201 P˛etla o określonej ilości przebiegów 0 Rozdział 4 4.1 Tablicowanie funkcji {chap:for} {sec:tablica} rsja Załóżmy, że chcemy sobie wydrukować tablic˛e jakiejś funkcji, dla prostoty weźmy funkcj˛e sinus. Obecnie tablice funkcji wychodza˛ z użycia, ale rysowanie funkcji na ekranie polega na połaczeniu ˛ punktów. Do tego potrzebna jest znajomość współrz˛ednych punktów, czyli par (x, f (x)), a wi˛ec jest to zadanie analogiczne do tablicowania. x [o ] 0 15 30 45 60 75 90 sin(x) 0.000000 0.258819 0.500000 0.707107 0.866025 0.965926 1.000000 Tabela 4.1: Tablica wartości funkcji sin(x). We Aby ułatwić sobie zadanie pominiemy różne ozdobniki zwiazane ˛ ze sposobem drukowania tablicy. Nie jest łatwo je uzyskać a tylko zaciemnia˛ nasz program. Wystarczy nam w każdej linii wydruku para liczb: kat˛ i wartość funkcji sinus. Na poczatek ˛ jeszcze bardziej uprościmy zadanie, wystarczy tylko, że ograniczymy si˛e do ciagu ˛ wartości, czyli drugiej kolumny z tabeli 4.1. Ponieważ funkcja sin wymaga argumentu w radianach wi˛ec korzystajac ˛ z proporcji π kat ˛ w radianach = kat˛ w stopniach 180 41 {tab:sin} Rozdz. 4 42 dostajemy kat ˛ w radianach = kat˛ w stopniach · π 180 sin(0) sin(15*pi/180) sin(30*pi/180) sin(45*pi/180) sin(60*pi/180) sin(75*pi/180) sin(90*pi/180) 201 1 2 3 4 5 6 7 0 Program obliczajacy ˛ druga˛ kolumn˛e wartości z tablicy 4.1, moglibyśmy napisać w nast˛epujacy ˛ sposób: We rsja Taki program, w taki sposób, daje si˛e napisać, ale tylko dla niewielu wartości funkcji. Gdybyśmy mieli sporzadzić ˛ tablic˛e co 1o , to wpisywanie 91 niemal identycznych linii byłoby okrucieństwem. A gdybyśmy chcieli sporzadzić ˛ dokładne tablice, co 0, 1o ? Napisanie takiego programu sporzadzajacego ˛ tablic˛e co 0, 01o nadaje si˛e jako propozycja kary dla sprawców szczególnie odrażajacych ˛ przest˛epstw, zamiast dożywocia. Potrzebujemy mechanizmu, dzi˛eki któremu program sam tworzyłby sobie nowe linie. Takim mechanizmem jest instrukcja p˛etli. Każe ona powtarzać programowi identyczne, badź ˛ wystarczajaco ˛ podobne ciagi ˛ poleceń. Instrukcja p˛etli istnieje w dwu odmianach: p˛etla z określona˛ ilościa˛ przebiegów i p˛etla z nieokreślona˛ ilościa˛ przebiegów. Podstawowa różnica mi˛edzy tymi odmianami zawiera si˛e w odpowiedzi na pytanie: czy już przed p˛etla˛ wiadomo, ile razy należy wykonać instrukcje p˛etli? Jeśli nie wiadomo, to musimy użyć p˛etli o nieokreślonej ilości przebiegów. Jeśli natomiast wiadomo, to jest bardzo wskazane, aby użyć p˛etli o określonej ilości przebiegów (choć możliwe jest użycie drugiego rodzaju p˛etli). P˛etla o określonej ilości przebiegów posiada tzw. licznik p˛etli, czyli zmienna,˛ która zawiera liczb˛e i przy każdym przebiegu p˛etli jest ona zmieniana. Składnia p˛etli o nieokreślonej liczbie przebiegów w OCTAVE wyglada ˛ nast˛epujaco: ˛ for licznik=zakres licznika p˛etli ... ... end%for Zmienna zawierajaca ˛ licznik p˛etli może nazywać si˛e jakkolwiek (każda legalna nazwa zmiennej), ale z uwagi na tradycj˛e najcz˛eściej używa si˛e zmiennej o nazwie i (dla kolejnych, zagł˛ebionych liczników: j, k). Wersja: 7 listopada 2010 Rozdz. 4 43 1 2 3 201 0 Zakres licznika p˛etli podajemy1 poprzez par˛e liczb oddzielonych znakiem :, przykładowo może to być i=2:5. Pierwsza liczba oznacza wartość licznika na poczatku, ˛ druga wartość licznika, której nie można przekroczyć. W tej formie zakresu, przy każdym przebiegu, licznik jest powi˛ekszany o 1. Tak wi˛ec dla i=2:5 w pierwszym przebiegu i b˛edzie równe 2, przy drugim b˛edzie równe 3 a przy czwartym równe 5. Piatego ˛ przebiegu nie b˛edzie, gdyż wtedy i równałoby si˛e 6 a to jest wi˛ecej niż górny zakres przebiegu równy 5. Dolna wartość zakresu musi być mniejsza od górnej, inaczej p˛etla b˛edzie nieskończona. W szczególności jest to istotne dla liczb ujemnych i=-5:-2. Zakres p˛etli nie musi być wyrażony liczbami całkowitymi, może być zadany jako i=2.35:5.21, ale wtedy możemy mieć problemy z arytmetyka.˛ Szerzej o tym przy drugim sposobie podawania zakresu. Również zakres p˛etli nie musi być określony stałymi, moga˛ to być zmienne – ale już zdefiniowane przed napotkaniem p˛etli, czyli i=a:b, o ile zmienne a i b istnieja.˛ W jaki sposób zmieniany jest licznik p˛etli, pokazuje nast˛epujacy ˛ program, który jedynie wypisuje wartość licznika p˛etli przy każdym przebiegu: for i=1:5 disp(i); end%for 1 2 3 for i=1:5 disp("Ala"); end%for rsja Efektem wykonania programu b˛edzie wyświetlenie kolejnych wartości: 1, 2, 3, 4, 5. Z licznika p˛etli nie musimy korzystać, przykładowy program: wypisze pi˛eć linii zawierajacych ˛ słowo Ala. Licznika p˛etli można użyć do nadawania wartości dowolnym zmiennym. Przykładem może być program: for i=1:5 y=i*i; disp(y); end%for We 1 2 3 4 Program ten wypisze kwadraty kolejnych liczb naturalnych: 1, 4, 9, 16, 25. Podobnie możemy wygenerować kolejne liczby podzielne przez 7: 1 2 for i=1:5 y=7*i; 1 W rzeczywistości OCTAVE posiada daleko bardziej elastyczne mechanizmy definiowania licznika p˛etli ale tutaj skupimy si˛e na tych najprostszych. Wersja: 7 listopada 2010 Rozdz. 4 3 4 44 disp(i); end%for 0 Program ten wypisze kolejno: 7,14, 21, 28, 35. 1 2 3 4 201 Druga postać zakresu licznika. Cz˛esto wygodne jest, aby licznik p˛etli był powi˛ekszany nie o jeden, ale o inna˛ wartość. W takiej sytuacji możemy użyć drugiej postaci zadawania zakresu p˛etli. Postać ta zapisywana jest za pomoca˛ trzech liczb (lub zmiennych) oddzielonych dwukropkami: start:skok:stop. Wartość start określa poczatkow ˛ a˛ wartość licznika p˛etli, wartość stop końcowa,˛ natomiast skok określa, o ile b˛edzie powi˛ekszana (lub pomniejszana) wartość licznika p˛etli za każdym razem. Program realizujacy ˛ zadanie tablicowania funkcji sinus co 15o mógłby wygla˛ dać nast˛epujaco: ˛ for i=0:15:90 y=sin(pi*i/180); disp([i,y]); end%for for k=0:pi/6:pi/2 y=sin(k); disp([k,y]); end%for ale tu dochodzimy znowu do problemu skończonej reprezentacji liczb rzeczywistych. Teoretycznie, jeśli trzy razy dodamy do zera π6 , to otrzymamy π2 , bo π π 0 + 3 · 6 = 2 . Ale jeśli trzykrotnie dodamy przybliżenie liczby π6 , to w zależności, czy jest ono z nadmiarem czy niedomiarem, albo dostaniemy liczb˛e minimalnie mniejsza˛ od π2 albo minimalnie wi˛eksza.˛ W tym ostatnim przypadku ostatni przebieg p˛etli si˛e nie wykona, gdyż k b˛edzie wi˛eksze od wartości górnego zakresu. W sposób tajemniczy zginie ostatni wiersz z wydruku. Użycie liczb całkowitych do liczenia p˛etli jest najlepsze. Jeśli z jakichś wzgl˛edów jest to niedogodne, to powszechnie stosowanym obejściem problemu jest powi˛ekszanie górnego zakresu np. o połow˛e skoku. To (niemal) gwarantuje unikni˛ecie problemów. We 1 2 3 4 rsja W tym programie użyliśmy specjalnej konstrukcji disp([i,y]);, która pozwala wyświetlić w jednej linii wartość i oraz wartość y2 . Zmiana skoku zakresu p˛etli z 15o na 2o czyli for i=0:2:90 pozwala wypisać wartości funkcji co 2o . Gdybyśmy chcieli sporzadzić ˛ analogiczna˛ tablic˛e, ale dla katów ˛ wyrażonych w radianach, to program mógłby wygladać ˛ nast˛epujaco: ˛ {disp-vec} 2 W rzeczywistości odwołujemy si˛e do wektorów Wersja: 7 listopada 2010 Rozdz. 4 0 for k=0:pi/6:pi/2+(pi/6)/2 y=sin(k); disp([k,y]); end%for Warto zwrócić uwag˛e, że zakres licznika może być podawany jako wyrażenie. 201 4.2 Zadanie Gaussa Anegdota głosi, że kiedy Karl Gauss3 był jeszcze w szkole podstawowej – czyli zanim wymyślił twierdzenie Gaussa, rozkład Gaussa i krzywizn˛e Gaussa 4 , to nauczyciel, przed jedna˛ z lekcji w klasie Gaussa, dostał od dyrektora pilne zlecenie. Aby zajać ˛ czymś dzieci, nauczyciel polecił obliczenie uczniom sumy kolejnych liczb całkowitych od 1 do 100, po czym zajał˛ si˛e zleceniem od dyrektora. Jakaż była irytacja nauczyciela, kiedy po kilku minutach jeden z uczniów podniósł r˛ek˛e, że już policzył (łatwo zgadnać, ˛ kto był tym uczniem). Najpierw napiszemy program, który sumuje liczby po kolei a potem pokażemy, w jaki sposób Gauss tak szybko rozwiazał ˛ to zadanie. rsja Sposób rachunkowy. Gdyby kolega młodego Karla sumował liczby po kolei to wykonywałby rachunki mniej wi˛ecej tak. Do 1 dodałby 2 a w rezultacie otrzymałby 1 + 2 = 3. Do tej wartości dodałby 3, czyli 3 + 3 = 6. Do otrzymanej liczby 6 dodałby nast˛epna˛ w kolei, czyli 4 uzyskujac ˛ 6 + 4 = 10. Przebieg obliczeń wygla˛ dałby nast˛epujaco: ˛ 1+2 = 3 3+3 = 6 6 + 4 = 10 10 + 5 = 15 15 + 6 = 21 itd. Warto zwrócić uwag˛e, że wynik uzyskany w poprzednim dodawaniu staje si˛e pierwszym składnikiem w nast˛epnym. Czyli możnaby to wyrazić przepisem: „Weź to, co dostałeś i dodaj do tego kolejna˛ liczb˛e”. Drugie zdanie przepisu powinno brzmieć „powtarzaj dla każdej z kolejnych liczb”. Ponieważ te kolejne liczby to 1, 2, 3...100 wi˛ec doskonale pasuja˛ do licznika p˛etli. 3 4 We 1 2 3 4 45 Karl Friedrich Gauss, 1777–1855, matematyk niemiecki Zadanie o mostach w Królewcu jest zwiazane ˛ z L. Eulerem Wersja: 7 listopada 2010 {sec:gauss} Rozdz. 4 46 0 Tak wi˛ec struktur˛e algorytmu można by ujać: ˛ for i=1:100 „do tego co dostałeś dodaj aktualna˛ wartość i” end%for 1 2 3 4 5 rsja 201 O ile cz˛eść „dodaj wartość i” jest zupełnie jasna, to do czego należy to dodać i co zrobić z tym wynikiem? Można wyobrazić sobie takie rachunki wykonywane za pomoca˛ kalkulatora. Na poczatku ˛ mamy na wyświetlaczu 0. Do tego dodajemy 1, na wyświetlaczu mamy 1. Dodajemy 2, pokazuje si˛e 3 itd. Z tego wynika, że wyobrażajac ˛ sobie obliczenia na kalkulatorze owo „to co dostałeś” można utożsamiać z wynikiem na wyświetlaczu. Wyświetlacz ten przechowuje wartość mi˛edzy jednym dodawaniem a drugim. W OCTAVE nie mamy niczego w rodzaju wyświetlacza. Możemy coś wypisać, ale do tej wypisanej wartości nie można już nic dodać. Natomiast do przechowywania wartości w OCTAVE służa˛ zmienne. Możemy nazwać ja˛ dowolnie ale nazwiemy ja˛ suma, gdyż przechowuje sum˛e już dodanych wyrazów a po wszystkich dodawaniach b˛edzie tam szukana suma. Tak wi˛ec zdanie: „do tego co dostałeś dodaj aktualna˛ wartość i” należałoby rozumieć jako „do sumy już dodanych wyrazów dodaj aktualna˛ wartość i i wynik wpisz do sumy”. Taka operacja w kategoriach zmiennych przekłada si˛e na zapis w programie: suma=suma+i. Mamy już prawie gotowy program, tylko nie wolno zapomnieć o dodaniu na suma=0, bo na ogół wyświetlacz w kalkulatorze na poczatku ˛ pokazuje poczatku ˛ zero a każdej zmiennej trzeba jawnie nadać wartość poczatkow ˛ a.˛ suma=0; for i=1:100 suma=suma+i; end%for disp(suma); We Inne wyjaśnienie działania programu, nieco matematyczne. Mamy obliczyć sum˛e szeregu skończonego: S = 1 + 2 + 3 + .... + 97 + 98 + 99 + 100 = 100 X i i=1 Jeśli zdefiniujemy ciag ˛ sum cz˛eściowych Si jako sum˛e poczatkowych ˛ i składników. Czyli S1 = 1, S2 = 1 + 2, S3 = 1 + 2 + 3 itd., to wartość sumy cz˛eściowej możemy wyrazić wzorem rekurencyjnym: Si = Si−1 + i Wersja: 7 listopada 2010 Rozdz. 4 47 Aby ten przepis był kompletny (i równoważny sumie) to należy podać poczatkowy ˛ wyraz. Dość naturalne byłoby dopisać: S1 = 1 201 0 jednak my spróbujemy pójść nieco okr˛eżna˛ droga,˛ postaramy si˛e zdefiniować S0 . O ile istnienie wyrazu S0 nie wydaje si˛e być naturalne (co miałoby znaczyć suma zerowej ilości wyrazów?) to jeśli wiemy, że S1 = 1 i obowiazuje ˛ wzór rekurencyjny, to: 1 = S0 + 1 z czego z kolei wynika jasno, że nieco sztucznie wprowadzony wyraz S0 musi wynosić zero. W efekcie nasz algorytm wygladałby nast˛epujaco: ˛ S0 = 0 dla kolejnych i od 1 do 100 Si = Si−1 + i S=0; for i=1:100 S=S+i; end%for disp(S); Jak widać, za pomoca˛ innego rozumowania, uzyskaliśmy identyczna˛ wersj˛e programy, za wyjatkiem ˛ nazwy zmiennej. Przechowywanie wartości. Warto zwrócić uwag˛e, w kontekście notacji matematycznej, kolejnych wartości sum jako ciagu ˛ wartości Si , na dość istotny drobiazg. Majac ˛ do czynienia z ciagiem, ˛ istnieje naturalna ch˛eć, aby wyrazy ciagu ˛ zapisywać w wektorze5 . Notacja matematyczna dla składowych wektora jest łudzaco ˛ podobna do wyrazów ciagu, ˛ gdyż dla (n-wymiarowego) wektora a kolejne składowe oznacza si˛e a1 , a2 ... an . Tak wi˛ec wydaje si˛e, że najłatwiej byłoby obliczyć kolejne wyrazy Si i zapami˛etać je w kolejnych elementach jakiegoś wektora a potem sumować. Formalnie nic nie stoi temu na przeszkodzie, jednak każdy element wektora zajmuje tyle pami˛eci ile jedna zmienna. Gdybyśmy mieli obliczyć sum˛e nie pierwszych stu, ale pierwszego miliona liczb, to wymagałoby to użycia dużych ilości pami˛eci. Zorganizowanie obliczeń w pokazany tu sposób wymaga jedynie użycia pami˛eci dla dwu zmiennych niezależnie czy sumujemy sto czy milion liczb. Jest to możliwe, gdyż w trakcie obliczeń potrzebujemy tylko raz wartości ai . 5 We 1 2 3 4 5 rsja Na str. 21 podkreślaliśmy, że wyrażenie Si = Si−1 + i przekłada si˛e w programie na S=S + i, tak wi˛ec nasz algorytm przekłada si˛e na program: Wektorów jeszcze nie znamy ale sa˛ one dost˛epne w OCTAVE . Wersja: 7 listopada 2010 {sec:mem} Rozdz. 4 48 0 Okazuje si˛e, że zadziwiajaco ˛ dużo zadań6 możemy rozwiazać ˛ bez przechowywania wyników pośrednich (ai ). Istnieja˛ jednak zadania, gdzie takiego sposobu nie da si˛e użyć. Przykładowo, gdybyśmy mieli ciag, ˛ który nie jest monotoniczny oraz mieli za zadanie wypisać wyrazy ciagu ˛ malejaco ˛ (zadanie sortowania). W takim wypadku musimy przechowywać wszystkie już obliczone wyrazy, gdyż nie wiadomo, czy nast˛epny nie zmieni kompletnie już istniejacego ˛ uporzadkowania. ˛ 201 Sposób Gaussa. Czas wrócić do wyjaśnienia, jak młody Gauss ominał˛ żmudne rachunki. Zadanie polegało na obliczeniu sumy: S = 1 + 2 + 3 + .... + 97 + 98 + 99 + 100 rsja Gauss zauważył, że jeśli doda wyraz pierwszy i ostatni 1 + 100 to otrzyma 101. Jeśli doda wyraz drugi i przedostatni 2 + 99 to też otrzyma 101. Każda suma dwu liczb, n-tej w kolejności z n-ta˛ od końca daje 101. Takich sum b˛edzie 50, wi˛ec suma wszystkich wyrazów wyniesie 101 · 50 = 5050. . Co W ogólności suma kolejnych liczb całkowitych od 1 do n wynosi n(n+1) 2 ciekawe, pomimo, że we wzorze jest dzielenie przez 2, to wynik jest zawsze liczba˛ całkowita.˛ Wynika to z tego, że jeśli n jest liczba˛ parzysta,˛ to n2 jest liczba˛ całkowita˛ a wi˛ec n2 · (n + 1) musi być całkowite. Natomiast, jeśli n nie jest liczba˛ parzysta,˛ to n + 1 musi być parzyste. Po takich uproszczeniach, które eliminuja˛ potrzeb˛e użycia p˛etli, funkcja sumujaca ˛ kolejne liczby miałaby postać: 1 function y=suma(n) 2 y=n*(n+1)/2; 3 end%function 4 suma(10) 5 suma(100) 4.3 Finanse We W tej cz˛eści b˛edziemy odwoływali si˛e do procentów. Zestawmy wi˛ec podstawowe informacje dotyczace ˛ odsetek. Wedle obowiazuj ˛ acego ˛ prawa oprocentowanie podaje si˛e w skali roku. Odsetki, przy stopie procentowej p, oblicza si˛e od kwoty bieżacego ˛ zadłużenia czy oszcz˛edności. p odsetki = kwota · 100 6 Za Kernigham Wersja: 7 listopada 2010 Rozdz. 4 49 0 W dalszym ciagu ˛ b˛edziemy używali dość uproszczonego modelu, gdyż b˛edziemy zakładali, że operacje dokonywane sa˛ raz w roku, podczas kiedy w rzeczywistości operacje (np. spłata rat pożyczki) najcz˛eściej dokonywane sa˛ miesi˛ecznie. Uwzgl˛ednienie innego niż roczny cyklu operacji komplikuje rachunki ale paradoksalnie, nie wpływa w sposób dominujacy ˛ na wyniki. Ilościowo wyniki tego uproszczonego modelu b˛eda˛ nieco odbiegały od rzeczywistości, ale jakościowo, b˛eda˛ dobrze ilustrowały prawdziwe zależności7 201 4.3.1 Lokata Zakładamy lokat˛e wpłacajac ˛ 1000 zł na 5 lat a bank oferuje nam oprocentowanie w wysokości 6% (rocznie). Po każdym roku do kwoty zgromadzonej na lokacie bank dopisuje nam odsetki powi˛ekszajac ˛ kwot˛e lokaty. Jeśli oznaczymy kwot˛e znajdujac ˛ a˛ si˛e na lokacie po itym roku przez ki to mamy: ki+1 = ki + ki · p p = ki · 1 + 100 100 p Wyrażenie ki+1 = ki · 1 + 100 przekłada si˛e w programie (por. str. 21) na instrukcj˛e: k=k*(1+p/100) Tak wi˛ec, program symulujacy ˛ lokat˛e wygladałby ˛ nast˛epujaco: ˛ {lokata.m} k=1000; % kwota lokaty t=5; % czas lokaty p=6; % stawka procentowa 4 5 6 7 for i=1:t k=k*(1+p/100); end%for disp(k); rsja 1 2 3 % bank dopisuje odsetki We Program pokazuje, że w końcu odbierzemy kwot˛e 1338,2 zł. W rzeczywistości zadanie z lokata˛ sprowadza si˛e do obliczenia wyrazów zwykłego ciagu ˛ geometrycznego. Po 5 latach kwota zgromadzona na lokacie wynosi: p 5 ) = 1338, 2, łatwo wi˛ec przekonać si˛e, że nasz program daje poprawny k · (1 + 100 wynik. Tym niemniej, to zadanie jest wst˛epem do znacznie trudniejszego. Systematyczne oszcz˛edzanie. Zakładamy konto oszcz˛ednościowe. Przez pi˛eć lat, co roku8 wpłacamy kwot˛e 200 zł, czyli w sumie wpłacimy 1000 zł. Ponieważ 7 Dokładniejszy model można uzyskać wprowadzajac ˛ oprocentowanie w skali miesiaca ˛ pm , które 1 si˛e wyraża jako: pm = (1 + p) 12 − 1. Jednak wyrażenie to jest zależne od okresu kapitalizacji. 8 Jeszcze raz podkreślamy, że to mało realistyczne, zazwyczaj miesi˛ecznie. Wersja: 7 listopada 2010 Rozdz. 4 50 pieniadze ˛ sa˛ oprocentowane, wi˛ec bank dopisuje odsetki od kwoty, która była na koncie przez ostatni rok. Zobaczymy jaka b˛edzie różnica mi˛edzy kwota wpłacona˛ od razu (zadanie lokaty) a ta˛ sama˛ kwota˛ wpłacana˛ partiami. p +s 100 0 ki+1 = ki · 1 + {oszczedzanie.m} 1 2 3 4 s=200; k=s; t=5; p=6; 5 6 7 8 9 for i=1:t k=k*(1+p/100)+s; end%for k=k-s; disp(k); składka kwota na koncie czas stawka procentowa 201 % % % % % bank dopisuje odsetki Na poczatku ˛ mamy wpłacone s złotych. Po każdym roku doliczamy odsetki i wpłacamy kolejna˛ składk˛e. Po ostatnim roku (na zewnatrz ˛ p˛etli) musimy pomniejszyć kwot˛e o składk˛e s bo po ostatnim roku już nie wpłacamy kolejnej składki tylko wybieramy oszcz˛edności. Program pokazuje, że uzyskaliśmy kwot˛e końcowa˛ 1195,1 zł. 4.3.2 Pożyczka rsja {kredyt} Skoro nie wystarczyło nam pieni˛edzy na zakup wymarzonego Focke-Wulfa czy innego Crusadera, wi˛ec trzeba zaciagn ˛ ać ˛ kredyt. Bierzemy kredyt 1 tys. zł, oprocentowany na 8%9 (w skali roku). B˛edziemy raz do roku spłacali rat˛e w kwocie 200 zł10 . Gdyby kredyt nie był oprocentowany, to płacac ˛ pi˛eć rat po 200 zł spłacilibyśmy całe zadłużenie. Obliczymy jaki jest wpływ oprocentowania, czyli dowiemy si˛e, ile zostanie nam do spłacenia po 5 latach. Należałoby to rozumieć w nast˛epujacy ˛ sposób: przez cały rok odsetki narastaja.˛ Na koniec roku bank dolicza odsetki do kwoty zadłużenia a my wpłacajac ˛ rat˛e (r) zmniejszamy dług. We ki+1 = ki p −r 1+ 100 {pozyczka.m} 1 2 k=1000; % kwota pożyczki t=5; % czas w latach 9 Bank żyje z tego, że pieniadze, ˛ które Kowalski złoży na lokacie (na 6%) pożycza Nowakowi (na 8%), wi˛ec oprocentowanie kredytu z natury jest wyższe od oprocentowania lokaty. Wartości 6% i 8% sa˛ optymistyczne, warto użyć aktualnie obowiazuj ˛ acych. ˛ 10 To jest rzadko stosowane, zazwyczaj raty płacimy miesi˛eczne. Wersja: 7 listopada 2010 Rozdz. 4 0 p=8; % oprocentowanie r=200; % rata roczna for i=1:t k=k*(1+p/100)-r; end%for disp(k); 201 Program pokazuje, że po 5 latach zostało nam jeszcze do spłacenia 296,01 zł a wi˛ec prawie 1,5 raty. 4.4 Silnia i pot˛ega Napiszmy funkcj˛e obliczajac ˛ a˛ wartości funkcji silnia. Matematyczna funkcja silnia jest definiowana jako n! = 1 · 2 · 3 · .... · n Wbrew pozorom jest to zadanie niemal identyczne do zadania Gaussa opisanego na str. 45. Tam mieliśmy obliczyć sum˛e: S = 1 + 2 + 3 + .... + n rsja jedyna różnica polega na tym, że w zadaniu sumowania był operator dodawania a teraz mamy mnożenie. Ta analogia jest też widoczna w zapisie, gdyż sum˛e cz˛esto zapisujemy S= n X i i=1 natomiast iloczyn jest zapisywany przy użyciu zbliżonej notacji n! = n Y i i=1 Wzorujac ˛ si˛e na poprzednim zadaniu, oraz oznaczajac ˛ Pn = n!, skoro n! = n · (n − 1)! wi˛ec napiszemy: Pi = Pi−1 · i To z kolei przekłada si˛e na j˛ezyk programu (por. str. 21): p=p*i Do tego musimy dopisać wyraz poczatkowy, ˛ który tutaj akurat wynika z definicji We 3 4 5 6 7 8 51 P0 = 1 ale też jest konsekwencja˛ faktu, że, jak to mówia˛ matematycy, elementem neutralnym dla mnożenia jest 1, podczas kiedy elementem neutralnym dla dodawania jest 0. Wersja: 7 listopada 2010 Rozdz. 4 0 function p=silnia(n) p=1; for i=1:n p=p*i; end%for end%function silnia(1) silnia(3) silnia(15) silnia(-5) 201 1 2 3 4 5 6 7 8 9 10 52 Na końcu umieściliśmy wywołanie funkcji silnia dla wartości -5 aby zilustrować bł˛edne działanie funkcji. Co prawda matematycznie (−5)! jest nieokreślone ale nasza funkcja powinna jakoś reagować na bł˛edne argumenty, szczególnie, że w tym przypadku wywołanie z ujemnym argumentem spowoduje nieskończona˛ p˛etl˛e. Funkcja powinna zawierać tzw. obsług˛e bł˛edów. W tej i w pozostałych funkcjach konsekwentnie to pomijamy. 4.4.1 Pot˛ega {subsec:pow} Napiszmy funkcj˛e, która oblicza wartość xn . Funkcj˛e t˛e definiuje si˛e jako: xn = x · x · x · ... · x rsja zadanie sprowadza si˛e do tego aby przemnożyć x n razy przez siebie. Analizujac ˛ to w kategoriach matematyki możemy napisać: xn = xn−1 · x oraz warunek poczatkowy ˛ x0 = 1 Oznaczajac ˛ Pn = xn mamy równoważny zapis: Pn = Pn−1 · x We co w programie należałoby zapisać (patrz str. 21): p=p*x Funkcja realizujaca ˛ to zadanie musi mieć dwa argumenty: x – liczb˛e, która jest pot˛egowana oraz n – wykładnik pot˛egi. 1 function p=ipow(x,n) 2 p=1; 3 for i=1:n 4 p=p*x; 5 end%for 6 end%function Wersja: 7 listopada 2010 Rozdz. 4 53 7 ipow(2,3) 8 ipow(2,5/4) 9 ipow(2,-5) 0 Nasza funkcja działa poprawnie dla n nieujemnych i całkowitych, co widać po wyniku ipow(2,5/4) i ipow(2,-5). Nie jest to też najbardziej efektywny sposób obliczania xn , wymaga n−1 mnożeń. {sec:2pt} 201 Dwupunktowa rekurencja. Do tej pory wielokrotnie wyst˛epowały wzory rekurencyjne w postaci an = f (an−1 ). Jednocześnie podkreślaliśmy (str. 47), że tego typu wyrażenia można oprogramować nie przechowuja˛ wszystkich wartości an . Cechy instrukcji przypisania (patrz str. 21) pozwalaja˛ używać jednej i tej samej zmiennej zarówno do an jak i an−1 . Ważna˛ klasa˛ zadań jest przypadek tzw. rekurencji dwupunktowej an = f (an−1 , an−2 ) rsja gdzie wartość bieżacego ˛ (n-tego) wyrazu zależy od dwu poprzednich. Okazuje si˛e, że oprogramowanie obliczania nie jest dużo bardziej skomplikowane niż rekurencji jednopunktowej. Ponieważ w programach nie możemy używać nazw zmiennych podobnych do an−1 i an−2 , kluczowa˛ kwestia˛ jest przyj˛ecie jakiejś przejrzystej notacji. W dalszym ciagu ˛ b˛edziemy w zmiennej an przechowywać wartość an , w zmiennej ap (a „poprzednie”) wartość an−1 a w zmiennej app (a „poprzednie-poprzednie”) wartość an−2 . Analiz˛e b˛edziemy prowadzili na przykładzie ciagu ˛ Fibonacciego, o którym troch˛e wi˛ecej w innym miejscu, tu zadowolimy si˛e zdefiniowaniem ciagu, ˛ który wyraża si˛e w wyjatkowo ˛ prosty sposób. Nast˛epny element jest suma˛ dwu poprzednich: F0 = 1 F1 = 1 Fn = Fn−1 + Fn−2 11 We Obliczenie n-tego wyrazu możnaby, korzystajac ˛ z własności instrukcji przypisania przeprowadzić an=an+ap jednak w ten sposób nadpisujemy sobie i bezpowrotnie tracimy wartość an11 wi˛ec zapiszemy jawnie: an=ap+app Można to zrobić w ten sposób ale trzeba to odpowiednio zorganizować. Wersja: 7 listopada 2010 Rozdz. 4 0 1 ... 2 for ... 3 an=ap+app; 4 .... 5 end%for 54 201 Musimy zadbać aby przy kolejnym przebiegu p˛etli aktualizować ap i app. W takim razie na użytek nast˛epnego przebiegu w ap powinno si˛e znaleźć an czyli: ap=an Jednak w ten sposób nadpisujemy i tracimy bezpowrotnie to co było w ap a co powinno si˛e teraz znaleźć w app. Krytycznie ważna jest tutaj kolejność, najpierw to co było w ap wpisujemy do app a potem z an do ap: app=ap ap=an W całości zarys wyglada ˛ nast˛epujaco: ˛ rsja 1 ... 2 for i=..:n 3 an=ap+app; 4 app=ap; 5 ap=an; 6 end%for Pozostaje jeszcze poczatek ˛ programu, czyli przypisanie wartości ap i app. {fib1.m} n=8; ap=1; app=1; for i=3:n an=ap+app; app=ap; ap=an; disp([i,an]); end%for Program wypisze ciag ˛ wartości: 2, 3, 5, 8, 13, 21, 34. Jak widać brakuje elementów F0 i F1 , gdyż te znajduja˛ si˛e na poczatku, ˛ w zmiennych app i ap. Aby zostały wyświetlone trzebaby umieścić osobne instrukcje disp() przed p˛etla.˛ Tym niemniej widać, że w otrzymanym ciagu ˛ suma każdych dwóch kolejnych elementów daje nast˛epny. Konsekwencja˛ „ukrycia” dwu poczatkowych ˛ wyrazów jest p˛etla zaczynajaca ˛ si˛e od 2, gdyż w ten sposób zaczynamy od obliczenia F2 . Inna rzecz, że sposób numeracji licznika p˛etli i jest tu bez znaczenia, gdyż wyraz Fn nie zależy jawnie od n. We 1 2 3 4 5 6 7 8 9 Wersja: 7 listopada 2010 Rozdz. 4 55 Analogicznie można sobie poradzić z rekurencja˛ trzy- czy wi˛ecej punktowa.˛ 0 4.5 Podwójna p˛etla 1 for i=1:4 2 for j=1:4 3 disp([i,j]); 4 end%for 5 end%for rsja 201 Turniej. Mamy zorganizować rozgrywki sportowe. W każdej rozgrywce bierze udział dwu zawodników (lub drużyn). Może to być turniej rycerski, gra w szachy, wyścigi samochodów, gry drużyn w piłk˛e nożna˛ itd. Sposób wyłaniania zwyci˛ezcy może być przeprowadzony na wiele różnych sposobów. Jednym z najprostszych jest rozgrywka w systemie „każdy z każdym”. Naszym zadaniem jest przygotowanie listy spotkań, zadanie nieskomplikowane dla 5 zawodników/drużyn, ale dla setki łatwo si˛e pomylić. Napiszemy wi˛ec program, który przygotuje za nas plan spotkań. Dla uproszczenia, aby program nie musiał pisać: pojedynek Zawiszy Czarnego z Grabowa herbu Sulima starosty kruszwickiego z Ulrikiem von Jungingen Wielkim Mistrzem Zakonu Szpitala Najświ˛etszej Marii Panny Domu Niemieckiego w Jerozolimie12 , nadamy każdemu numer startowy (jak to si˛e czyni obecnie) i b˛edziemy mówili, że zawodnik 1 rywalizuje z zawodnikiem numer 2. B˛edziemy to krótko zapisywali: (1,2) a dla uproszczenia samego programu, b˛edziemy oczekiwali, że program wypisze to w postaci: 1 2. Do samego wypisywania pary liczb użyjemy tego samego sposobu co w programie tablicowania na str. 44. Pierwszy pomysł, który si˛e narzuca jest taki, że skoro „każdy z każdym”, to w parze opisujacej ˛ pojedynek na pierwszej pozycji trzeba aby pojawiały si˛e kolejno wszystkie numery startowe oraz na drugiej również kolejno wszystkie numery. Program realizujacy ˛ takie zadanie – na poczatek ˛ rozważymy przypadek 4 zawodników/drużyn – wygladałby ˛ nast˛epujaco: ˛ We Na poczatek ˛ zmienna i równa jest 1. Potem dochodzimy do zagł˛ebionej p˛etli for. Zmienna j równa jest 1. Wypisywana jest wartość pary i, j. Napotykamy zamkni˛ecie wewn˛etrznej (zagł˛ebionej) p˛etli end%for wi˛ec program wraca do tej p˛etli, która została zamkni˛eta, czyli do wewn˛etrznej. Wartość zmiennej j równa si˛e 2. Cykl ten powtarza si˛e. Zmienna j przebiega wartości 3 i 4. Nast˛epnie, skoro wewn˛etrzna p˛etla (ta z j) wyczerpała swój zakres realizowana jest nast˛epna instrukcja. Nast˛epna instrukcja to zamkni˛ecie end%for dla zewn˛etrznej p˛etli for (tej z i). Tak wi˛ec wartość i zmienia si˛e na 2 i realizowana jest wewn˛etrzna p˛etla (ta z j). 12 Dawniej ludzie używali dłuższych określeń, gdyż mieli wi˛ecej czasu a poza tym nie musieli pisać SMS-ów. Wersja: 7 listopada 2010 Rozdz. 4 56 Jednak po uruchomieniu widzimy, że pojawiaja˛ si˛e rozgrywki typu (1,1) i (2,2), co oznacza, że np. zawodnik 1 rywalizuje sam ze soba.˛ Aby uniknać ˛ tego oczywistego nonsensu wystarczy pomijać przypadki kiedy i jest równe j. Można to zrealizować np. w taki sposób 201 0 1 .... 2 if i ˜= j 3 disp([i,j]); 4 end%if 5 ... W tej wersji nasz program wyglada ˛ o wiele lepiej ale po uważnej analizie widzimy, że pojawiaja˛ si˛e regularnie rozgrywki typu (1,3) i (3,1). Oznacza to, że każdy mecz jest rozgrywany dwukrotnie. Skoro zakładaliśmy, że każdy pojedynek odb˛edzie si˛e jeden raz to jest bład ˛ w koncepcji. Aby uniknać ˛ z kolei tego bł˛edu wystarczy zauważyć, że taka sytuacja zawsze pojawia si˛e dwukrotnie raz jako (i,j) a drugi jako (j,i). Ponieważ i musi być różne od j, wi˛ec któraś z tych liczb b˛edzie wi˛eksza od drugiej. Z tego już prosty wniosek, że należy wypisywać tylko te pary, gdzie albo i jest wi˛eksze od j, albo odwrotnie. Przyjmujac ˛ ten drugi wariant nasz program wygladałby ˛ nast˛epujaco: ˛ rsja 1 for i=1:4 2 for j=1:4 3 if j > i 4 disp([i,j]); 5 end%if 6 end%for 7 end%for Nasz program spełnia wszystkie warunki zadania ale po chwili analizy widzimy, że jest dość rozrzutny. Przykładowo dla i równego 3, wewn˛etrzna p˛etla wykonywana jest cztery razy z czego trzy bezproduktywnie. Bardziej oszcz˛edny wariant programu: We 1 for i=1:4 2 for j=i+1:4 3 disp([i,j]); 4 end%for 5 end%for Tu także można coś poprawić, gdyż łatwo zauważyć, że dla i=4 wewn˛etrzna p˛etla odpowiada zakresowi j=5:4. Wystarczy w takim razie ograniczyć zakres i=1:3. Na koniec ten sam program ale dla dowolnej (n) liczby zawodników/drużyn: 1 n=8; 2 for i=1:n-1 Wersja: 7 listopada 2010 Rozdz. 4 57 0 3 for j=i+1:n 4 disp([i,j]); 5 end%for 6 end%for {sec:mnozenie} 201 Tabliczka mnożenia Naszym zadaniem jest wygenerowanie tabelki pokazujacej ˛ tabliczk˛e mnożenia. Samo wygenerowanie liczb znajdujacych ˛ si˛e w tabliczce mnożenia nie jest trudne: 1 for i=1:9 2 for j=1:9 3 disp(i*j); 4 end%for 5 end%for rsja jednak program wypisze ciag ˛ iloczynów, jeden pod drugim, każdy w osobnej linii. Nie o nam chodziło. Cała trudność programu polega na kontroli sposobu wypisywania wielkości, tak aby graficznie odpowiadało to naszym przyzwyczajeniom. Wykorzystamy zmienna˛ linia, która b˛edzie zawierała (i przechowywała) jeden wiersz tekstu przeznaczony do wypisania. Na poczatku ˛ wiersz ten b˛edzie pusty linia="";. Ponieważ linia zawiera ciagi ˛ znaków, wi˛ec zanim dopiszemy liczb˛e na koniec linii, musimy ja˛ zamienić na ciag ˛ znaków. Funkcja OCTAVE num2str – ˛ znaków i wpisze ten ciag ˛ do number to string – zamieni liczb˛e iloczyn i*j na ciag zmiennej w. Funkcja OCTAVE strcat – string concatenate13 „sklei” wszystkie argumenty w jeden ciag ˛ znaków. Jednak „sklejenie” ciagu ˛ "ala" i "as" daje w wyniku "alaas", a nie jak byśmy chcieli "ala as", a wi˛ec trzeba jawnie dodać znak odst˛epu " " w środku. Tak wi˛ec jeśli, w pewnym momencie, zmienna linia zawiera napis "1 2 3" a zmienna w zawiera "4" to polecenie linia=strcat(linia," ", w); obliczy ˛ "1 wartość prawostronna˛ czyli „doklei” na końcu znak odst˛epu i napis "4" dajac 2 3 4" a nast˛epnie wpisze to do zmiennej linia jako nowa˛ jej wartość. {tab-mn.m} 13 We 1 linia=""; 2 for i=1:9 3 for j=1:9 4 w=num2str(i*j); 5 linia=strcat(linia," ", w); 6 end%for 7 disp(linia); 8 linia=""; concatenate – tu w znaczeniu połacz ˛ Wersja: 7 listopada 2010 Rozdz. 4 58 9 end%for 2 3 4 5 6 7 8 9 4 6 8 10 12 14 16 18 6 9 12 15 18 21 24 27 8 12 16 20 24 28 32 36 10 15 20 25 30 35 40 45 12 18 24 30 36 42 48 54 14 21 28 35 42 49 56 63 16 24 32 40 48 56 64 72 18 27 36 45 54 63 72 81 201 1 2 3 4 5 6 7 8 9 0 Jako wynik dostaniemy na ekranie: Jak widać wynik nie jest idealny. Kolumny sa˛ poprzesuwane, gdyż odst˛ep mi˛edzy kolumnami wynosi zawsze jeden niezależnie czy kolejna liczb jest jedno- czy dwucyfrowa. Poprawienie tego nie jest trudne ale nie b˛edziemy si˛e tym zajmowali. We rsja Prawo Benforda. Prawo Benforda wykorzystywane jest szeroko do wykrywania oszustw finansowych. W typowym dokumencie finansowym znajduje si˛e wiele liczb, które nie sa˛ powiazane ˛ w prosty sposób ze soba,˛ nie tworza˛ żadnego regularnego ciagu. ˛ Gdybyśmy wypisali po kolei cyfry ze wszystkich liczb okazałoby si˛e, że sa˛ one w całkowicie przypadkowe, czyli jak mówia˛ matematycy tworza˛ ciag ˛ losowy. Matematycy dodaliby jeszcze, że jest to ciag ˛ losowy o rozkładzie jednostajnym, czyli każda cyfra wyst˛epuje jednakowo cz˛esto (w przybliżeniu). Każdy oszust wie o tym, wi˛ec stara si˛e, aby cyfry nie powtarzały si˛e w jakiś regularny sposób. Tymczasem Benford zauważył, że pomimo tego, że poszczególne cyfry wyst˛epuja˛ jednakowo cz˛esto, to jeśli weźmiemy pod uwag˛e pierwsza cyfr˛e każdej z liczb (dokładniej – pierwsza˛ cyfr˛e znaczac ˛ a˛14 ), to te cyfry wcale nie wyst˛epuja˛ jednakowo prawdopodobnie. Dlatego jednym z elementów wykrywania fałszerstw jest sprawdzenie cz˛estości pierwszych cyfr znaczacych. ˛ Nie rozwijajac ˛ tego tematu, pokażemy ilustracj˛e prawa Benforda. Wypiszemy pierwsze cyfry znaczace ˛ iloczynów w tabliczce mnożenia. Do tego potrzebny jest algorytm pozwalajacy ˛ znaleźć pierwsza˛ cyfr˛e znaczac ˛ a˛ liczby. W naszym przypadku zadanie nieco si˛e upraszcza, gdyż liczby sa˛ całkowite i z przedziału od 1 do 81. Czyli sa˛ jednocyfrowe (¬ 9) lub dwucyfrowe. Jeśli liczby sa˛ jednocyfrowe to jedyna cyfra jest od razu pierwsza˛ cyfra˛ znaczac ˛ a.˛ Jeśli sa˛ dwucyfrowe to musimy odrzucić druga˛ cyfr˛e. 14 Pierwsza˛ cyfr˛e od lewej nie b˛edac ˛ a˛ zerem. Tak wi˛ec np. w 0, 0031 pomijamy zera i pierwsza˛ cyfra˛ znaczac ˛ a˛ jest 3. Wersja: 7 listopada 2010 Rozdz. 4 59 Do tego wykorzystamy funkcj˛e mod(), która pozwala nam znaleźć reszt˛e z dzielenia jednej liczby przez inna.˛ Reszta z dzielenia liczby przez 10 daje nam ostatnia˛ cyfr˛e znaczac ˛ a,˛ np.: 0 81 = 8 · 10 + 1 1 for i=1:9 2 linia=""; 3 for j=1:9 4 x=i*j; 5 if x > 9 6 x=(x-mod(x,10))/10; 7 end%if 8 w=num2str(x); 9 linia=strcat(linia," ", w); 10 end%for 11 disp(linia); 12 end%for 201 Jeśli od liczby odejmiemy jej ostatnia˛ cyfr˛e znaczac ˛ a,˛ otrzymamy liczb˛e całkowita˛ podzielna˛ przez 10. Jeśli zaś t˛e podzielimy przez dziesi˛eć, to otrzymamy liczb˛e, która ma jedna˛ cyfr˛e znaczac ˛ a˛ mniej niż poczatkowa ˛ liczba. Jeśli wi˛ec nasza liczba miała dwie cyfry znaczace, ˛ to po takiej operacji otrzymamy jedna,˛ ta˛ pierwsza.˛ 1 2 3 4 5 6 7 8 9 2 4 6 8 1 1 1 1 1 3 6 9 1 1 1 2 2 2 4 8 1 1 2 2 2 3 3 5 1 1 2 2 3 3 4 4 6 1 1 2 3 3 4 4 5 7 1 2 2 3 4 4 5 6 8 1 2 3 4 4 5 6 7 rsja Jako wynik dostaniemy: 9 1 2 3 4 5 6 7 8 We Patrzac ˛ na wynik działania programu, łatwo zauważyć, że np. cyfra 9 wyst˛epuje 3 razy na 81 możliwych a cyfra 7 wyst˛epuje 4 razy. Gdyby cyfry miały rozkład jednostajny to każda, w przybliżeniu, powinna wyst˛epować 9 razy. Widać wi˛ec, że statystycznie rzecz biorac ˛ iloczyn dwu losowych cyfr wcale nie daje losowej pierwszej cyfry znaczacej. ˛ 4.6 Wzorki geometryczne Istnieje długa tradycja „grafiki terminalowej”, gdzie ze znaków ASCII tworzy si˛e, czasami bardzo ładne, rysunki. Wersja: 7 listopada 2010 {benford.m} Rozdz. 4 60 rsja 201 0 Spróbujemy wygenerować prosty wzorek w postaci szachownicy. Szachownica b˛edzie rozmiaru n × n, gdzie n jest dowolne ale parzyste. Na poczatek ˛ przyjmiemy n = 8. Nasza „szachownica” b˛edzie miała pola numerowane liczbami czyli możemy rozumieć, że każde pole jest opisane para˛ (i,j). Pierwszy wiersz b˛edzie miał i=1, drugi i=2 itd. Chcemy aby kolejne pola na zmian˛e były czarne (wypisujemy znak "X") lub białe (znak " "). W obr˛ebie jednego wiersza, czyli i jest stałe a zmienia si˛e j, zadanie jest proste. Kolejne wartości j sa˛ na zmian˛e nieparzyste i parzyste. Wystarczy wi˛ec, że jeśli j jest parzyste, to wypiszemy „czarne”, jeśli nieparzyste, to „białe” aby powstał naprzemienny czarno-biały wzorek. Gdybyśmy zastosowali ten schemat post˛epowania (algorytm) do każdego wiersza, to otrzymalibyśmy wszystkie parzyste kolumny jednego koloru a nieparzyste drugiego. Tymczasem my chcemy mieć na zmian˛e, tam gdzie w jednym wierszu zaczynało si˛e od czarnego w nast˛epnym powinno si˛e zaczynać od białego. Wystarczy zauważyć, że numery wierszy (i) sa˛ również na przemian parzyste i nieparzyste. Tak wi˛ec, jeśli pewne pole miało np. parzyste i i nieparzyste j to pole poniżej (w kolumnie) b˛edzie miało takie samo j (ta sama kolumna) ale inne i (numer wiersza i b˛edzie wi˛ekszy o 1). Stad ˛ już prosta droga do spostrzeżenia, że wystarczy sprawdzać, czy suma i+j jest parzysta czy nie i przypisywać kolor w zależności od wyniku. Równie dobrze moglibyśmy myśleć jak o odwzorowaniu (funkcji) dwu zmiennych całkowitych (i,j) na wartość i+j. Wyniki takiego odwzorowania pokazane sa˛ w tabeli 4.2. Każdy wiersz jest numerowany przez wartość i (z lewej strony) a każda kolumna przez j (u góry). 1 2 3 4 5 6 7 8 9 2 3 4 5 6 7 9 9 10 We 1 2 3 4 5 6 7 8 {tab:szach} 3 4 5 6 7 9 9 10 11 4 5 6 7 9 9 10 11 12 5 6 7 8 9 10 11 12 13 6 7 8 9 10 11 12 13 14 7 8 9 10 11 12 13 14 15 8 9 10 11 12 13 14 15 16 Tabela 4.2: Odwzorowanie pary (i,j) na wartość i+j Jeśli teraz dla każdej wartości z tabeli 4.2 obliczymy czy jest ona parzysta czy nieparzysta (czyli obliczymy reszt˛e z dzielenia przez 2) to otrzymamy wartości pokazane w tabeli 4.3. Jak widać wartości układaja˛ si˛e w regularny „szachownicowy” wzorek zer i jedynek. Wystarczy teraz przypisać jedynkom kolor czarny a zerom biały (lub odwrotnie, w zależności czy chcemy aby pierwsze pole było czarne czy białe) i wyWersja: 7 listopada 2010 1 2 3 4 5 6 7 8 1 0 1 0 1 0 1 0 1 2 1 0 1 0 1 0 1 0 3 0 1 0 1 0 1 0 1 4 1 0 1 0 1 0 1 0 5 0 1 0 1 0 1 0 1 61 6 1 0 1 0 1 0 1 0 7 0 1 0 1 0 1 0 1 8 1 0 1 0 1 0 1 0 0 Rozdz. 4 201 Tabela 4.3: Odwzorowanie pary (i,j) na reszt˛e z dzielenia i+j przez 2. {tab:szach-mod} pisać odpowiednie wartości. Program reazlizujacy ˛ takie zadanie mógłby mieć nast˛epujac ˛ a˛ postać: rsja 1 n=8; 2 for i=1:n 3 line=""; 4 for j=1:n 5 if 1==mod(i+j,2) 6 w=" "; 7 else 8 w="X"; 9 end%if 10 line=strcat(line,w); 11 end%for 12 disp(line); 13 end%for Wynikiem działania programu jest nast˛epujacy ˛ obrazek pokazujacy ˛ si˛e na ekranie: We X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 4.7 Ćwiczenia 1. Stablicować funkcj˛e ex sin(x) w przedziale od 0 do 2 co 0, 1 2. Stablicować sumy kolejnych liczb całkowitych od 1 do n dla n od 1 do 100. Wersja: 7 listopada 2010 {szachownica.m} Rozdz. 4 62 3. Stablicować lokaty w kwocie 100 zł na 5 lat oprocentowanej od 1% do 10% co 1%. 0 4. Stablicować kwot˛e pożyczki zaciagni˛ ˛ etej w wysokości 100 zł, pozostajac ˛ a˛ do spłacenia z okresem spłaty od 5 do 25 lat, zakładajac ˛ każdorazowo, że raty sa˛ równe kwocie podzielonej przez liczb˛e lat. 5. Napisać program, który wypisuje kwadraty pierwszych 10 liczb podzielnych przez 5. 201 6. Napisać program, który rysuje choink˛e ze znaków ASCII. Choinka na kształt trójkata. ˛ 7. Napisać program, który wypisuje współrz˛edne kolejnych n wierzchołków wieloboku foremnego. Założyć, że środek wieloboku jest w poczatku ˛ układu współrz˛ednych, promień okr˛egu opisanego na tym wieloboku jest 1 oraz, że pierwszy wierzchołek leży na osi X. Można skorzystać ze wzorów wyrażajacych ˛ współrz˛edne kartezjańskie w zależności od współrz˛ednych biegunowych: x = R · cos(φ), y = R · sin(φ). 8. Poprawić formatowanie tabliczki mnożenia, korzystajac ˛ z faktu, że kolumny byłyby równe, gdyby liczba jednocyfrowa była poprzedzona para˛ znaków odst˛epu, a liczba dwucyfrowa jednym znakiem odst˛epu. 10. Pot˛ega dyskretna jest definiowana jako pn mod m czyli reszta z dzielenia pn przez m. Napisać program, który wyliczy pierwsze 26 wyrazów 2n mod 13 (n = 1..26). Uwaga: Ponieważ liczby całkowite typowo reprezentowane sa˛ przez 32 bity (z czego jeden przypada na znak) wi˛ec powyżej 30 wyrazu (≈ 230 ) moglibyśmy mieć niepoprawne wyniki. 11. Wartość pot˛egi dyskretnej, zdefiniowanej w poprzednim zadaniu, musi być w zakresie od 0 do m − 1 (gdyż jest to reszta z dzielenia przez m). Natomiast sama pot˛ega pn rośnie bardzo szybko. Aby móc obliczać wartości pot˛egi dyskretnej dla wysokich wykładników bez ryzyka przekroczenia zakresu liczb całkowitych można skorzystać z zależności: We {dpow2} rsja {dpow1} 9. Napisać program, który wypisuje tablic˛e różnic kwadratów w postaci analogicznej do tabliczki mnożenia. a · b mod m = (a mod m) · (b mod m) mod m Napisać program, który wypisze wyrazy 2n mod 13 dla n od 169 do 195. 12. Gramy w gr˛e planszowa,˛ gdzie ilość pól o które si˛e poruszamy zależy od wyniku rzutu para˛ kostek sześciennych. Jeśli na kostkach jest różna liczba oczek to posuwamy si˛e o sum˛e oczek z obu kostek. Jeśli wypadła taka sama Wersja: 7 listopada 2010 Rozdz. 4 63 We rsja 201 0 to nie posuwamy si˛e. Napisać program, który obliczy średnie tempo posuwania si˛e do przodu. Wskazówka. Jeśli sporzadzilibyśmy ˛ tabelk˛e (analogiczna˛ do tabelki mnożenia) o ile pól posuniemy si˛e w zależności od wyniku na kostce pierwszej i drugiej to, jako że każda para jest jednakowo prawdopodobna, średnia ilość pól jest równa sumie wartości z tabelki podzielona przez liczb˛e pól tabelki. Wersja: 7 listopada 2010