Notka czwarta w której poznajemy zmienną boolowską i podejmujemy pierwszą decyzję

Dzisiaj zmierzymy się z następującym zadaniem.


Problem 3: Niech się stanie prostokąt

Tomek bawi się patyczkami w przedszkolu. Każdy patyczek ma długość wyrażoną w liczbie naturalnej. Każdy patyczek ma też nacięcia co 1 centymetr, które ułatwiają łamanie patyczka na części. Patyczki są niebieskie.

Tomek ma trzy patyczki, a chciałby z nich utworzyć prostokąt. Ponieważ z trzech patyczków nie da się utworzyć prostokąta, Tomek postanowił przełamać jeden patyczek na dwie części, korzystając z jednego z nacięcia. Ponieważ Tomek nie lubi łamać patyczków, postanowił przełamać TYLKO jeden patyczek. Teraz zastanawia się czy w ogóle jest w stanie połamać tak któryś z patyczków aby możliwe było zbudowanie prostokąta.

Wejście

Na standardowym wejściu podane są trzy liczby naturalne a, b, c (1 a b c 100) oznaczające długości trzech patyczków Tomka

Wyjście

Na standardowym wyjściu należy wypisać jedno słowo: “TAK” jeśli można połamać jeden z patyczków tak, aby dało się ułożyć prostokąt i “NIE” w przeciwnym przypadku.

Przykład

Dla danych wejściowych

3 6 9

Poprawną odpowiedzią jest

TAK

Dla danych wejściowych

2 4 7

Poprawną odpowiedzią jest

NIE

Wytłumaczenie do przykładu. W pierwszym przykładzie możemy połamać patyczek 9 na dwa patyczki o długościach 3 i 6, a następnie utworzyć prostokąt o bokach 3 i 6. W drugim przykładzie nie jesteśmy w stanie połamać patyczka w taki sposób, aby utworzyły one prostokąt.


Wcześniej poznaliśmy zmienne typu całkowitoliczbowego (int). Dzisiaj poznamy zmienne typu logicznego (bool). Zmienne logiczne mogą przyjmować tylko dwie wartości: prawdę (true) oraz fałsz (false). Możemy wykonać trzy podstawowe operacje na zmiennych boolowskich: negację, alternatywę oraz koniunkcję. Negacja (not) zmienia wartość na przeciwną (to jest z true robi false, a z false robi true), alternatywa (or) jest prawdziwa, gdy którykolwiek z jej argumentów jest prawdziwy, a koninkcja (and) tylko wtedy gdy oba argumenty są prawdziwe. Operatory odpowiadają polskim słowom: nie, lub oraz i.

#include <iostream>

using namespace std;

int main()
{
    bool a = true;
    bool b = false;

    bool c = not a;   // będzie równe false
    bool d = a or b;  // będzie równe true
    bool e = a and b; // będzie równe false

    return 0;
}

W programie utworzyliśmy najpierw dwie zmienne: a oraz b. Zmienną a zainicjalizowaliśmy na wartość true (prawda), a zmienną b na wartość false (fałsz). Dalej definiujemy kolejne trzy zmienne. Zmiennej c nadajemy wartość “nie a”. Ponieważ a jest true, wartość c będzie równa false. Zmiennej d nadajemy wartość “a lub b” (jest prawdziwe jeśli a lub b jest prawdziwe). Ponieważ jedna z tych wartości (a) jest prawdziwa, to również d jest prawdziwe. W końcu zmiennej e nadajemy wartość “a i b” (jest prawdziwe jeśli a i b jest prawdziwe). Ponieważ b nie jest prawdą, to e również nie jest prawdą. Oprócz słów not, or oraz and w C++ można używać symboli, odpowiednio !, || oraz &&.

Teraz przyjrzymy się relacjom pomiędzy liczbami naturalnymi. W C++ mamy dostępne następujące operatory: operator równości (==), operator różności (!=), jest mniejsze (<), jest większe (>), jest mniejsze bądź równe (<=) oraz jest większe bądź równe (>=). Zwróć szczególną uwagę na to, że operator równości w C++ to ==. Początkujący programiści (a nawet ci bardziej zaawansowani) często mylą go z operatorem przypisania =. Operator różności w C++ to !=, bo jak już wcześniej wspomnieliśmy, ! oznacza w C++ negację.

#include <iostream>

using namespace std;

int main()
{
    bool a = 0 == 2;
    bool b = 0 != 2;
    bool c = 0 < 2;
    bool d = 0 > 2;
    bool e = 0 <= 2;
    bool f = 0 >= 2;

    return 0;
}

Tak jak wspominałem w poprzednim wpisie: gdy mamy do czynienia z operacją przypisania, najpierw wykonywane są obliczenia po prawej stronie znaku równości. Wartość zmiennych to kolejno: fałsz (bo 0 nie jest równe 2), prawda (bo 0 jest różne od 2), prawda (bo 0 jest mniejsze od dwóch), fałsz (bo 0 nie jest większe od 2), prawda (bo 0 jest mniejsze bądź równe 2) oraz fałsz (bo 0 nie jest większe bądź równe 2).

Ostatnią rzeczą jaką dziś poznamy jest instrukcja warunkowa. Do tej pory program zawsze wykonywany był tak samo. Teraz dzięki instrukcji warunkowej będziemy mogli wykonywać inne instrukcje w zależności od warunków. Najprostsza pętla warunkowa wygląda następująco:

if (warunek)
{
    // Blok instrukcji
}

Pętla składa się z następujących części:

  • słowa kluczowego if (od ang. jeśli)
  • warunku logicznego ujętego w okrągłe nawiasy ()
  • blok instrukcji ujęty w nawiasy klamrowe {}

Instrukcje w bloku instrukcji zostaną wykonane tylko wtedy, gdy warunek jest prawdziwy. Dla przykładu, ponieważ 5 nie jest równe 3, poniższy program nic nie wypisze na ekran:

#include <iostream>

using namespace std;

int main()
{
    if (5 == 3)
    {
        cout << "Ten napis nie pojawi się na ekranie" << endl;
    }

    return 0;
}

Zwróć uwagę na to, że instrukcje umieszczone w bloku instrukcji, zostały dodatkowo przesunięte w prawo (tak zwane wcięcia). Nie jest to przypadek. Wcięcia stosuje się, aby zwiększyć czytelność kodu. Podczas zawodów będziesz miał bardzo mało czasu aby napisać program, znaleźć w nim błędy i dokonać zmian w programie. Ucz się dbać o czytelność kodu od samego początku edukacji.

Instrukcja warunkowa może przyjmować bardziej skomplikowane formy. W poniższym przykładzie pojawia się nowe słowo kluczowe: else (od ang. w przeciwnym przypadku). Przy takiej konstrukcji, jeśli warunek zostanie spełniony to uruchomia się instrukcje z bloku pierwszego, a jeśli nie będzie spełniony, to zostaną uruchomione instrukcje z bloku drugiego.

if (warunek)
{
    // Pierwszy blok instrukcji
}
else
{
    // Drugi blok instrukcji
}

Poniżej przedstawiony jest program do sprawdzania kodu pin, korzystający z instrukcji if … else …

#include <iostream>

using namespace std;

int main()
{
    const int poprawny_pin = 4242;
    int pin;

    cout << "Podaj pin: ";
    cin >> pin;

    if (pin == poprawny_pin)
    {
        cout << "Pin podany prawdidłowo" << endl;
    }
    else
    {
        cout << "Pin niepoprawny" << endl;
    }

    return 0;
}

Program prosi o wpisanie numeru pin. Następnie wczytuje pin od użytkownika. Jeśli pin jest poprawny, to program wypisuje na ekranie komunikat “Pin podany prawidłowo”. W przeciwnym razie wypisuje “Pin niepoprawny”. Zwróć uwagę na nowe słówko kluczowe const przed deklaracją zmiennej poprawny_pin. Oznacza on, że zmienna poprawny_pin jest tak na prawdę… stała. Oznacza to, że w trakcie działania programu, nie możemy jej zmienić. Jeśli spróbujemy ją zmienić to program się nie skompiluje:

test.cpp: In function ‘int main()’:
test.cpp:13:18: error: assignment of read-only variable ‘poprawny_pin’
   13 |     poprawny_pin = 15;
      |     ~~~~~~~~~~~~~^~~~

Jest to zabezpieczenie przed tym, gdybyśmy przypadkowo zmodyfikowali zmienną, której modyfikować nie powinniśmy. Dużo lepiej jest znaleźć błąd w programie w momencie kompilacji programu niż dociekać co też poszło nie tak w trakcie działania programu.

Ostatnią wersję instrukcji warunkowej jaką chciałem dzisiaj pokazać wygląda następująco:

if (warunek)
{
    // Pierwszy blok instrukcji
}
else if (warunek2)
{
    // Drugi blok instrukcji
}
else if (warunek3)
{
    // Trzeci blok instrukcji
}
else if (warunek4)
{
    // Czwarty blok instrukcji
}
else
{
    // Piąty blok instrukcji
}

Jak się domyślasz, do konstrukcji może zostać dodana dowolna liczba warunków. Ostatni blok else nie jest wymagany (co być może nie jest już takie łatwe do domyślenia się). Co jest jeszcze trudniejsze do domyślenia się to to, jak ta konstrukcja działa. Na początku sprawdzany jest pierwszy warunek. Jeśli warunek jest prawdziwy, to wykonywany jest pierwszy blok instrukcji i instrukcja warunkowa kończy się (kolejne warunki nie są sprawdzane). Jeśli warunek jest fałszywy to sprawdzany jest warunek2. Jeśli jest on prawdziwy to wykonywany jest blok drugi i kończona jest instrukcja warunkowa (kolejne warunki nie są sprawdzane). Jeśli oba pierwsze warunki są fałszywe, to sprawdzany jest warunek trzeci. Jeśli jest on prawdziwy to wykonywany jest blok trzeci i instrukcja warunkowa kończy się. Jeśli warunek, warunek2 i warunek3 są fałszywe to sprawdzany jest warunek4. Jeśli jest on prawdziwy to wykonywany jest czwarty blok instrukcji, a w przeciwnym przypadku blok piąty.

Mamy już wszystkie narzędzia aby uporać się z postawionym nam zadaniem. Zanim je rozwiążemy, zwróćmy uwagę na sekcję wejście. Jest w niej napisane, że rozmiary patyczków są podane w niemalejącej kolejności. Będziemy z tego korzystać.

Zastanówmy się kiedy Tomek jest wstanie przełamać patyczek, aby utworzyć prostokąt. Ponieważ przełamujemy tylko jeden patyczek, to znaczy że dwa pozostałe zostają niezłamane. Mamy dwie możliwości:

  • Niezłamane patyczki układamy do siebie prostopadle
  • Niezłamane patyczki układamy do siebie równolegle

Z pierwszym przypadkiem mieliśmy do czynienia z w przykładzie pierwszym. Jeśli chcemy w ten sposób utworzyć prostokąt to musimy połamać największy patyczek (c). I możemy to zrobić tylko wtedy gdy c = a + b.

Jeśli niezłamane patyczki chcemy ułożyć równolegle, to oznacza, że muszą one już mieć taką samą długość. Taką własność mogą mieć patyczki a oraz b lub b oraz c (nie ma sensu sprawdzać a oraz c, bo jeśli a = c to wszystkie patyczki mają ten sam rozmiar, a już sprawdziliśmy co gdy pierwsze dwa patyczki mają ten sam rozmiar). Wtedy trzeci patyczek (odpowiednio c lub a) musimy przełamać idealnie na pół. Możemy to zrobić tylko wtedy gdy jego długość jest parzysta.

Jak sprawdzić czy liczba jest parzysta? Możemy sprawdzić czy przy dzieleniu przez 2 daje ona resztę zero. W poprzedniej notce poznaliśmy operację modulo, która liczy nam jaka jest reszta z dzielenia jednej liczby przez drugą.

Aby teraz połączyć wszystko w jedną całość. Mamy trzy przypadki: jeden gdy niezłamane patyczki układamy prostopadle i dwa gdy patyczki układamy do siebie równolegle. Ponieważ interesuje nas czy spełniony jest którykolwiek z tych warunków, użyjemy operatora or. W sytuacji gdy układamy patyczki do siebie równolegle, w każdym z dwóch przypadków musimy sprawdzić dwa warunki. Czy dwa patyczki mają tę samą długość i czy trzeci patyczek ma długość parzystą. Ponieważ oba warunki muszą być spełnione, użyjemy operatora and. Całość może wyglądać na przykład tak:

#include <iostream>

using namespace std;

int main()
{
    int a, b, c;
    
    cin >> a >> b >> c;
    
    if (a + b == c or (a == b and c % 2 == 0) or (b == c and a % 2 == 0))
    {
        cout << "TAK" << endl;
    }
    else
    {
        cout << "NIE" << endl;
    }

    return 0;
}

Po raz kolejny udało nam się pomóc Tomkowi w jego przygodach. Poznaliśmy już pełno konstrukcji języka C++. Do pełnego szczęścia potrzebujemy jeszcze jednej. Ale o niej opowiem dopiero w następnej notce.

Back To Top