This shows you the differences between two versions of the page.
Both sides previous revision Previous revision | |||
dydaktyka:cprog:2016:loops-solutions [2016/10/30 09:23] pkleczek [Zadanie BABSQRT] |
— (current) | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Pętla for, pętla "while" - rozwiązania i odpowiedzi ====== | ||
- | |||
- | Laboratorium: [[dydaktyka:cprog:2016:loops]] | ||
- | |||
- | ===== Pętla "for" ===== | ||
- | |||
- | ==== Zadanie FOR-1 ==== | ||
- | |||
- | <code c for-1.c> | ||
- | /* Program oblicza sume liczb naturalnych 1,2,...,10. */ | ||
- | |||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int i = 0; | ||
- | int suma = 0; | ||
- | |||
- | for (i = 1; i <= 10; i = i + 1) { | ||
- | suma = suma + i; | ||
- | } | ||
- | | ||
- | printf("suma = %d\n", suma); | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | Zwróć uwagę, że równie dobrze można zastosować następującą pętlę ''for'': | ||
- | <code c> | ||
- | for (i = 0; i < 11; i = i + 1) { | ||
- | suma = suma + i; | ||
- | } | ||
- | </code> | ||
- | gdyż dodanie $0$ do zmiennej ''suma'' nie zmieni jej wartości, a warunki $i \leq 10$ oraz $i < 11$ są równoważne \\ | ||
- | ALE \\ | ||
- | :!: pierwszy sposób ($i_0 = 0$, $i \leq 10$) lepiej przekazuje **intencję** programisty, przez co kod jest bardziej zrozumiały! | ||
- | |||
- | ==== Zadanie FOR-2 ==== | ||
- | |||
- | <code c for-2.c> | ||
- | /* Program oblicza sume liczb calkowitych z przedzialu <a,b>. */ | ||
- | |||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int a = 0; | ||
- | int b = 0; | ||
- | int suma = 0; | ||
- | |||
- | int i; | ||
- | |||
- | printf("Podaj liczby `a` i `b`: "); | ||
- | scanf("%d %d", &a, &b); | ||
- | |||
- | for (i = a; i <= b; i = i + 1) { | ||
- | suma = suma + i; | ||
- | } | ||
- | |||
- | printf("suma = %d\n", suma); | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | W powyższym przykładzie zmienna ''i'' oznacza aktualnie sumowaną liczbę. | ||
- | |||
- | |||
- | Zwróć uwagę, że równie dobrze można zastosować następującą pętlę ''for'': | ||
- | <code c> | ||
- | for (i = 0; i <= b - a; i = i + 1) { | ||
- | suma = suma + (i + a); | ||
- | } | ||
- | </code> | ||
- | w którym zmienna ''i'' oznacza **indeks** aktualnie sumowanej liczby -- bądź inaczej mówiąc //odległość aktualnie sumowanej liczby od lewego krańca przedziału (czyli od $a$)//. | ||
- | |||
- | Podobnie jak w przypadku zadania FOR-1, pierwszy sposób lepiej przekazuje **intencję** programisty, jest prostszy do zrozumienia i (w moim odczuciu) bardziej intuicyjny. | ||
- | |||
- | ===== Priorytety operatorów ===== | ||
- | |||
- | ==== Zadanie PRIOR-1 ==== | ||
- | |||
- | W wyrażeniu ''3 < x < 6'' mamy dwa operatory o tym samym priorytecie, a kierunek wiązania dla operatora ''<'' to od-lewej-do-prawej (czyli najpierw obliczana jest wartość tego, co stoi po lewej stronie operatora, a następnie tego co po prawej) -- możemy zatem zapisać ''3 < x < 6'' jako ''(3 < x) < 6''. | ||
- | |||
- | W języku C wartość wyrażenia logicznego to albo ''1'' albo ''0'' (typu całkowitego), zatem dla $x = 2$ mamy: \\ | ||
- | ''(3 < 2) < 6'' ⇔ ''0 < 6'' ⇔ ''1'' | ||
- | |||
- | Widać zatem, że w przypadku programowania intuicja matematyczna czasem zawodzi -- o czym zresztą ostrzega nas kompilator: | ||
- | <code> | ||
- | D:\cb_projects\sample\main.c|10|warning: comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]| | ||
- | </code> | ||
- | |||
- | ===== Pętla "while" ===== | ||
- | |||
- | ==== Zadanie WHILE-1 ==== | ||
- | |||
- | |||
- | <code c while-1_1.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int n = 5; | ||
- | int k = 3; | ||
- | int iloczyn = 0; | ||
- | int i = 0; // ilosc dotychczas zsumowanych `n` | ||
- | |||
- | while (i < k) { | ||
- | iloczyn = iloczyn + n; | ||
- | i = i + 1; | ||
- | } | ||
- | |||
- | printf("%d * %d = %d\n", n, k, iloczyn); | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | Inny sposób -- tu zmienna ''k'' określa to, ile razy musimy jeszcze dodać ''n''! | ||
- | <code c while-1_2.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int n = 5; | ||
- | int k = 3; | ||
- | int iloczyn = 0; | ||
- | int k0 = k; // zapamietaj wartosc `k`, bo bedziemy ja modyfikowac... | ||
- | |||
- | // tu `k` okresla ile `n` pozostalo nam jeszcze do zsumowania! | ||
- | |||
- | while (k > 0) { | ||
- | iloczyn = iloczyn + n; | ||
- | k = k - 1; | ||
- | } | ||
- | |||
- | printf("%d * %d = %d\n", n, k0, iloczyn); | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | ===== Pętla "do-while" ===== | ||
- | |||
- | ==== Zadanie DOWHILE-1 ==== | ||
- | |||
- | <code c dowhile-1.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int n; | ||
- | |||
- | do { | ||
- | printf("Podaj liczbe dodatnia: "); | ||
- | scanf("%d", &n); | ||
- | } while (n <= 0); | ||
- | |||
- | printf("OK, wpisano %d.\n", n); | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | ==== Zadanie DOWHILE-2 ==== | ||
- | |||
- | Na początek kilka słów wstępu: | ||
- | |||
- | Pętla ''do-while'' (podobnie jak każda inna pętla -- ''for'', ''while'') wykonuje się dopóki warunek __jest__ spełniony. Nam zależy na wylosowaniu liczby __należącej__ do przedziału $\langle 10, 15 \rangle$, zatem chcemy wykonywać pętlę dopóki wylosowana liczba __nie będzie należeć__ do przedziału $\langle 10, 15 \rangle$. | ||
- | |||
- | Pseudokod (czyli ogólny schemat) naszego programu wygląda zatem następująco: | ||
- | <code> | ||
- | losuj liczbę `n` DOPÓKI `n` spoza przedziału <10,15> | ||
- | wypisz `n` | ||
- | </code> | ||
- | |||
- | Najpierw zastanówmy się, jak wygląda zapis $n \in \langle 10, 15 \rangle$ w języku C. Jak już wiesz, nie możemy napisać ''%%10 <= n <= 15%%'' -- musimy rozbić to na dwie części, ''%%10 <= n && n <= 15%%''. | ||
- | Jak zatem zapisać warunek //spoza przedziału $\langle 10, 15 \rangle$//? Można na dwa sposoby: | ||
- | - Skorzystać z operatora negacji: ''%%!(10 <= n && n <= 15)%%'' | ||
- | - Skorzystać z [[https://pl.wikipedia.org/wiki/Prawa_De_Morgana#Logika|I prawa de Morgana]]: $\neg(10 \leq n \leq 15) \Leftrightarrow \neg(10 \leq n \wedge n \leq 15) \Leftrightarrow \neg(10 \leq n) \vee \neg(n \leq 15) \Leftrightarrow 10 > n \vee n > 15$ otrzymując w języku C ''10 > n || n > 15''. | ||
- | |||
- | Przykład programu z wykorzystującego sposób #1: | ||
- | <code c dowhile-2.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int n; | ||
- | |||
- | do { | ||
- | n = rand() % 101; | ||
- | printf("Wylosowano %d.\n", n); | ||
- | } while (!(10 <= n && n <= 15)); | ||
- | |||
- | printf("OK, wylosowano %d.\n", n); | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | PS. Nie przejmuj się, że powyższy program zawsze losuje te same liczby w tej samej kolejności -- to działanie celowe :) | ||
- | |||
- | ===== Zadania podsumowujące ===== | ||
- | |||
- | ==== Zadanie STAIRS ==== | ||
- | |||
- | <code c stairs.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int n = 3; // ilosc stopni | ||
- | int i_levels, i_dollars; | ||
- | |||
- | for (i_levels = 1; i_levels <= n; i_levels = i_levels + 1) { | ||
- | for (i_dollars = 1; i_dollars <= i_levels; i_dollars = i_dollars + 1) { | ||
- | printf("$"); | ||
- | } | ||
- | printf("\n"); | ||
- | } | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | W powyższym zadaniu zmienne oznaczają: | ||
- | * ''i_levels'' -- który z kolei stopień wyświetlamy w danym obiegu pętli (licząc od góry) | ||
- | * ''i_dollars'' -- który z kolei dolar danego aktualnie "budowanego" stopnia wyświetlamy w danym obiegu pętli (licząc od lewej) | ||
- | |||
- | ==== Zadanie BOX ==== | ||
- | |||
- | Do tego zadania możemy podejść w następujący sposób -- traktujemy pudełko jak kartkę w kratkę o wymiarze $n \times n$ i uzupełniamy kratki wierszami, zaczynając od lewego górnego rogu, wpisując w kratkę ''*'' bądź '' ''. | ||
- | |||
- | Zatem wyświetlamy ''*'', gdy zachodzi __dowolny__ z poniższych warunków: | ||
- | * uzupełniamy pierwszy rząd | ||
- | * uzupełniamy ostatni rząd | ||
- | * uzupełniamy pierwszą kolumnę | ||
- | * uzupełniamy ostatnią kolumnę | ||
- | * uzupełniamy kratkę leżącą na przekątnej naszej kartki | ||
- | |||
- | W języku C taki sposób rozwiązania wygląda następująco: | ||
- | <code c box.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int n = 5; // rozmiar pudelka (dlugosc krawedzi) | ||
- | int i_row; // numer aktualnie uzupelnianego rzedu | ||
- | int i_cell; // numer aktualnie uzupelnianej komorki | ||
- | |||
- | for (i_row = 1; i_row <= n; i_row = i_row + 1) { | ||
- | for (i_cell = 1; i_cell <= n; i_cell = i_cell + 1) { | ||
- | if (i_row == 1 || i_row == n || i_cell == 1 || i_cell == n || i_cell == i_row) { | ||
- | printf("*"); | ||
- | } else { | ||
- | printf(" "); | ||
- | } | ||
- | } | ||
- | printf("\n"); | ||
- | } | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | ==== Zadanie DIGSUM ==== | ||
- | |||
- | Pomysł polega na tym, aby sumować cyfry zaczynając od jedności (z użyciem operatora //modulo//) i następnie "obcinać" po jednej pozycji -- właśnie zaczynając od jedności (z użyciem operatora dzielenia): | ||
- | - $\mod(196, 10) = 6$; w języku C ''196 / 10'' wynosi ''19'' | ||
- | - $\mod(19, 10) = 9$; w języku C ''19 / 10'' wynosi ''9'' | ||
- | - $\mod(1, 10) = 1$; w języku C ''1 / 10'' wynosi ''0'' | ||
- | |||
- | Gdy po (kolejnym) dzieleniu dostaniemy wartość ''0'' -- wiemy, że nie zostało już więcej cyfr do sumowania. | ||
- | |||
- | <code c digsum.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int n = 196; | ||
- | int n0 = n; | ||
- | |||
- | // POMYSŁ: "Odcinaj" po jednej cyfrze od końca i zobacz, co | ||
- | // wlasciwie odcinasz ;) | ||
- | |||
- | int sum = 0; | ||
- | while (n > 0) { | ||
- | sum = sum + n % 10; // dodaj wartość ostatniej cyfry "głowy" | ||
- | n = n / 10; // obetnij ostatnią cyfrę "głowy" | ||
- | } | ||
- | |||
- | printf("Suma cyfr liczby %d to %d.\n", n0, sum); | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | ==== Zadanie PRIME ==== | ||
- | |||
- | Krótkie przypomnienie z gimnazjum: [[http://www.math.edu.pl/czy-pierwsza|Jak rozpoznać czy liczba naturalna jest pierwsza?]] \\ | ||
- | (W poniższym przykładzie nie bawimy się w warunek $i < \sqrt{n}$, czyli równoważnie $i^2 < n$, tylko po prostu sprawdzamy wszystkie liczby naturalne z przedziału $[2,n-1]$ -- to nie wypływa na poprawność rozwiązania, po prostu poniższy warunek jest mniej wydajny, gdyż należy sprawdzić większą liczbę liczb.) | ||
- | |||
- | <code c prime.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int n; | ||
- | int i; | ||
- | |||
- | printf("Podaj liczbe calkowita: "); | ||
- | scanf("%d", &n); | ||
- | |||
- | for (i = 2; (i < n) && (n % i != 0); i = i + 1) | ||
- | ; /* po prostu sprawdz kolejna liczbe... */ | ||
- | |||
- | if (i == n) { | ||
- | // Cala petla zostala wykonana, wiec liczba N nie byla podzielna | ||
- | // przez zadna z liczb 2, 3, ..., N-1. | ||
- | printf("Liczba %d jest pierwsza.\n", n); | ||
- | } else { | ||
- | printf("Liczba %d NIE jest pierwsza.\n", n); | ||
- | } | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | |||
- | ==== Zadanie BABSQRT ==== | ||
- | |||
- | <code c babsqrt.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | int main () | ||
- | { | ||
- | double S = 5.0; | ||
- | double x = S; // krok (1) | ||
- | double x_prev; // wartość `x` w poprzedniej iteracji | ||
- | double accuracy = -10e-3; // pozadana dokladnosc obliczen | ||
- | |||
- | do { | ||
- | x_prev = x; | ||
- | x = (x_prev + S / x_prev) / 2; // krok (2) | ||
- | printf("%.6f\n", x); | ||
- | } while (-accuracy < (x - x_prev) && (x - x_prev) < accuracy); // krok (3) | ||
- | |||
- | printf("sqrt(%.2f) ~= %.6f\n", S, x); | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | |||
- | ==== Zadanie SQUARE ==== | ||
- | |||
- | <code c square.c> | ||
- | #include <stdio.h> | ||
- | #include <stdlib.h> | ||
- | |||
- | int main() | ||
- | { | ||
- | int row, col; | ||
- | |||
- | for (row = 0; row < 3; row = row + 1) { | ||
- | for (col = 0; col < 3; col = col + 1) { | ||
- | printf("%d", (row + col) % 3 + 1); | ||
- | } | ||
- | printf("\n"); | ||
- | } | ||
- | |||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | |||
- | |||
- | |||
- | |||