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

Podobne dokumenty