User Tools

Site Tools


dydaktyka:cprog:2016:loops-solutions

This is an old revision of the document!


Pętla for, pętla "while" - rozwiązania i odpowiedzi

Pętla "for"

Zadanie FOR-1

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;
}

Zwróć uwagę, że równie dobrze można zastosować następującą pętlę for:

    for (i = 0; i < 11; i = i + 1) {
        suma = suma + i;
    }

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

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;
}

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:

    for (i = 0; i <= b - a; i = i + 1) {
        suma = suma + (i + a);
    }

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) < 60 < 61

Widać zatem, że w przypadku programowania intuicja matematyczna czasem zawodzi – o czym zresztą ostrzega nas kompilator:

D:\cb_projects\sample\main.c|10|warning: comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]|

Pętla "while"

Zadanie WHILE-1

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;
}

Inny sposób – tu zmienna k określa to, ile razy musimy jeszcze dodać n!

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;
}

Pętla "do-while"

Zadanie DOWHILE-1

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;
}

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:

losuj liczbę `n` DOPÓKI `n` spoza przedziału <10,15>
wypisz `n`

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:

  1. Skorzystać z operatora negacji: !(10 ⇐ n && n ⇐ 15)
  2. Skorzystać z 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:

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;
}

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

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;
}

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:

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;
}

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):

  1. $\mod(196, 10) = 6$; w języku C 196 / 10 wynosi 19
  2. $\mod(19, 10) = 9$; w języku C 19 / 10 wynosi 9
  3. $\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.

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;
}

Zadanie PRIME

Krótkie przypomnienie z gimnazjum: 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.)

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;
}

Zadanie BABSQRT

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 (x - x_prev < accuracy);	// krok (3)
 
    printf("sqrt(%.2f) ~= %.6f\n", S, x);
 
    return 0;
}

Zadanie SQUARE

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;
}
dydaktyka/cprog/2016/loops-solutions.1477564286.txt.gz · Last modified: 2020/03/25 11:46 (external edit)