Java:Logiczne wnioski

> O ja pierdzielę, jaka ta Java paskudna...
>
>

W tej części trzeba będzie w końcu zrezygnować z kucia i zacząć myśleć! Oczywiście będziesz musiał ruszyć łbem i upośledzić swój tok rozumowania. Pomogę ci się uspecjalnić, abyś wkroczył w kolejne stopnie wtajemniczenia.

Ta część zakłada, że opanowałeś już poprzednie części. Nie ma litości dla tych, którzy tego nie zrobili.

## Algorytmy

To ekstrawaganckie słowo znaczy tyle, co sposób zrobienia czegoś. Algorytm jest po prostu kodem, który jeszcze nie jest kodem - tłumaczy go i na podstawie jego można napisać kod. W pewnym sensie przydatna rzecz.

Zobaczmy jednak, jak to wygląda w praktyce. Algorytm sprawdzania jedzenia w lodówce - jak myślisz że będzie wyglądał?

1. Rusz dupę z kanapy
2. Otwórz lodówkę
3. Sprawdź lodówkę
4. Zamknij lodówkę

**Jesteś w błędzie.** To jeszcze nie algorytm - to tylko konspekt. Ten algorytm nic nie tłumaczy. Gdzie jest lodówka? Jak do niej dotrzeć? Czego szukasz w lodówce? Jak szukasz?

Chodzi o to, że upraszczasz swoje myślenie do takich czynności, jak poruszanie się (ale nie automatyczne - tłumaczysz każdy ruch), patrzenie, sprawdzanie (sprawdzanie czego?) i liczenie. Dlatego poprawny algorytm powinien wyglądać tak:

Zakładając, że lodówka stoi naprzeciwko ciebie w tym samym pokoju.

1. Wstań.
2. Zacznij iść do przodu aż natrafisz na przeszkodę.
3. Ta przeszkoda to prawdopodobnie lodówka; spróbuj ją otworzyć.
4. Spojrzyj na półkę na górze w poszukiwaniu owoców, warzyw, jogurtu, szynki, chleba, kota, sera, pasztetu lub rosołu.
5. Jeżeli nie znalazłeś, powtórz punkt 5 dla półki niżej.
6. Zamknij lodówkę.
7. Zastanów się nad wynikiem poszukiwań (opcjonalne).

I właśnie tak wygląda programowanie! Każdy głupi mógłby pomyśleć "Moja gra powinna mieć takie fajne efekty!1!!!1", ale jak je zrobić? To jest bardzo skomplikowany proces ładowania tekstur do pamięci, nakładania ich za pomocą wektorów, potem mieszania z innymi teksturami, a w końcu wyświetlania. A mówię dopiero o grafice 2D. To wygląda prosto dopóki nie musisz tego powtórzyć dla swoich 500 obrazków w grze uwzględniając ich miejsce w danej chwili i to, że nie wszystkie mogą być wyświetlane jednocześnie.

### Interpretacja funkcji i algorytmów matematycznych

#### Najmniejsza

Wyobraź sobie znajdowanie najmniejszej liczby. Jak ty to robisz? Wypiszmy przykładowe liczby:

```
34
42
908
1
-14
28
-2
18

```

Która z nich jest najmniejsza? I skąd to wiesz? Jak to sprawdziłeś?

Ok, po kolei. Nie możesz patrzeć na wszystkie liczby jednocześnie. Najpierw pierwsza, 34. Gdy na nią spojrzysz, to najpierw ona jest najmniejsza. A teraz porównaj z drugą, 42. 34 nadal jest najmniejsza. No to z trzecią - 34 najmniejsza. A czy 1 jest mniejsze od 34? Oczywiście że tak. Teraz 1 jest najmniejsze, porównaj tę najmniejszą liczbę z kolejną, -14. -14 jest najmniejsza. 28 jest większe od naszej obecnej najmniejszej, tak jak i -2 i 18. A więc zostajemy przy tym, że -14 jest najmniejsze.

Jedyną operacją, jaką tu wykonujemy jest zmiana naszej najmniejszej liczby i porównywanie. Obydwie operacje mogą być wykonane w Javie - a więc uprościliśmy tak tok myślenia, że taka pierdoła jak Java ogarnie o co nam chodzi.

W kodzie będzie wyglądało to tak:

```
najmniejsza = pierwsza;
if(druga < najmniejsza) {
  najmniejsza = druga;
}
if(trzecia < najmniejsza) {
  najmniejsza = trzecia;
}

// ...

if(ostatnia < najmniejsza) {
  najmniejsza = ostatnia;
}

```

W powyższym kodzie najpierw zakładamy, że pierwsza jest najmniejsza, a potem porównujemy kolejne liczby do niej. Jeżeli znajdziemy mniejszą, to zamieniamy wartość najmniejszej.

#### Silnia

Silnia to ciekawy przypadek. Jest to iloczyn wszystkich liczb naturalnych od 1 do n, dla n z którego liczymy tę silnię. Dla przykładu: 5! = 1 \* 2 \* 3 \* 4 \* 5.

Wygląda na to, że trzeba tu tylko mnożyć, co Java potrafi. A więc pomnóżmy pięć liczb dla 5!. A co, jeśli zmienimy zdanie i będziemy chcieli policzyć dla 8? A 10? 120?

Każdy głupi potrafi pisać mnożenie w nieskończoność, aby tylko był spokój z algorytmem i dobry wynik. Jednak szuka polega na zautomatyzowaniu tego procesu!

W tym przypadku mamy n liczb. Zakładamy, że przed policzeniem silni nasz wynik wynosi 1. Teraz mnożymy wynik przez kolejne liczby od 1 do n i zapisujemy to właśnie w naszym wyniku. Będzie to wyglądało mniej więcej tak, że wynik = 1, a potem wynik = wynik \* 1 \* 2 \* ... \* n. Na razie wydaje się, że mnożenie wyniku przez 1 jest trochę głupie, ale tylko na początku!

Problem polega na tym, że nie wiemy ile wynosi n przed podaniem go. Dlatego nie wiemy ile liczb mnożyć, ani jak duża jest ostatnia liczba. Dlatego z pomocą przyjdzie nam pętla! Pamiętasz może co robiła pętla? Powtarzała kod n ilość razy!

Dlatego robimy to mniej więcej tak:

Załóżmy, że n = 10, czyli liczymy 10!.

Wynik na razie to 1. Pętla for zaczyna na i = 1, kończy na i = n, co oznacza, że powtórzy się n razy. Za każdym razem mnoży wynik \* i i zapisuje go w wyniku. Na końcu wyświetlamy wynik.

Czyli: Rozpoczynamy program, wynik = 1. Pętla for, gdzie i = 1 -> wynik = wynik \* 1. Pętla for, gdzie i = 2 -> wynik = wynik \* 2. Pętla for, gdzie i = 3 -> wynik = wynik \* 3. ... Pętla for, gdzie i = n -> wynik = wynik \* n. Wyświetlamy wynik.

Wygląda to tak, jakbyś wpadł w trans i próbował wciąż przemnażać liczby aż dojdziesz do n. Jednak nie mnożysz ich od razu, domnażasz kolejną i do wyniku domnażasz kolejną itd...

Program działa tak, jakbyśmy oddzielnie domnażali do wyniku kolejne liczby. To nic, ponieważ jest to jedyny sposób automatyzacji - dzięki temu mnożysz n liczb, ponieważ pętla powtarza się n razy.

W kodzie wygląda to mniej więcej tak:

```
wynik = 1;
for(i = 1; i <= n; i++) {
  wynik = wynik * i;
}

```

## Prawdziwe wyzwanie

To jest rzecz dla prawdziwych wytrwalców! Spróbujemy zrobić dosyć trudne programy, gdzie musisz upośledzić swoje myślenie i myśleć automatycznie!

### Zapisywanie zmiennych w tablicy

Czasami zdarza się, że będziesz miał humory. Jednego dnia wymyślisz sobie, że zapiszesz 4 zmienne, a drugiego 987, a potem jednak 5. Zdarza się niestety, a taka rzecz jak wczytywanie i zapisywanie zmiennych jest na tyle monotonna, że grzech jej nie uprościć.

Do pobierania zmiennych używamy obiektu Scanner. Tworzymy tablicę o rozmiarze "rozmiar", akurat tutaj jest to 20. W pętli pobierzemy wszystkie zmienne.

```
int rozmiar = 20;
int[] tablica = new int[rozmiar];
Scanner scanner = new Scanner(System.in);
for(int i = 0; i < rozmiar; i++) {
 tablica[i] = scanner.nextInt();
}

```

Skoro pobieranie za każdym razem wygląda tak samo (scanner.nextInt()), skoro tablica ma ileśtam elementów, z czego do każdego można się dostać kolejnymi liczbami i skoro pętla for daje takie liczby, to po co mielibyśmy pisać w oddzielnych linijkach kolejne nextInty? Po prostu zapisujemy rozmiar tablicy, tworzymy tablicę o tym rozmiarze, tworzymy nowy Scanner i używając go zapisujemy kolejne liczby. Uwierz mi, niczym to się nie różni w działaniu od pisanie nextInt w oddzielnych linijkach. Poza tym jest to szybsze i wygodniejsze.

Używając tego sposobu (a raczej tej pętli) możesz zamienić wartości tablicy, wyświetlić je, zaktualizować, porównać (szukając np. najmniejszej) lub zjeść.

### Szukanie najmniejszego elementu z tablicy

Używamy sztuczek wyżej. Najmniejsza to pierwszy element z tablicy (nr 0), a potem porównujemy każdy element do najmniejszej. Jak znajdziemy mniejszą, to mianujemy ją nową najmniejszą. Pętla ta co wyżej z dwoma zmianami - zaczynamy od nr 1 i sugerujemy się rozmiarem tablicy używając length. Każda tablica ma swoje length. Ale to już powinieneś wiedzieć.

```
int[] liczby = {  34, 42, 908, 1, -14, 28, -2, 18 };
int najmniejsza = liczby[0];

for(int i = 1; i < liczby.length; i++) {
  if(liczby[i] < najmniejsza) {
    najmniejsza = liczby[i];
  }
}

```

### Nieszczęsna silnia

Ten przykład co wyżej, jednak w działającym kodzie.

Zakładamy, że wynik jest na początku równy 1. Nie 0, bo wtedy mnożymy przez 0. Nie bez wartości, bo nie możemy mnożyć liczbę przez brak wartości. To musi być wartość i to musi być 1 (liczba neutralna mnożenia).

Potem w pętli mnożymy n liczb o wartościach 1 do n przez wynik. W rzeczywistości to, co program będzie robił to:

wynik = 1

wynik = wynik \* 1

wynik = wynik \* 2

wynik = wynik \* 3

itd.

Czyli po podstawieniu pod wynik poprzedniego mnożenia:

wynik = 1

wynik = 1 \* 1

wynik = 1 \* 1 \* 2

wynik = 1 \* 1 \* 2 \* 3

itd.

Zauważ, że w miejsce mnożenia wstawiłem to, co było linijkę wyżej. To właśnie będzie się działo, gdy będziemy mnożyć wynik przez kolejne liczby i zapisywać to w wyniku.

```
int n = 5;
int wynik = 1;

for(int i = 1; i <= n; i++) {
  wynik = wynik * i;
}

```

## Zabawa z kodem (inne algorytmy)

### Połowa choinki

Spróbujmy narysować choinkę. To nie będzie trudne. Będziemy potrzebowali jedynie rozmiarów choinki.

Najpierw jednak zastanówmy się, jak choinka będzie rysowana. Trzeba będzie w każdej linijce dokładać gwiazdkę. Na razie rysujemy połowę choinki, więc to nie będzie trudne. Jeżeli będziemy chcieli narysować 3-linijkową choinkę, to będzie ona wyglądała tak:

```
**

```

Zobaczmy pewną ciekawą zależność. W pierwszej linijce (linijka nr 1) mamy jedną (1) gwiazdkę. W drugiej (nr 2), mamy 2 gwiazdki, a w trzeciej trzy.

Oznacza to, że każda linijka będzie musiała mieć swój numer i na podstawie tego numeru będzie rysowała gwiazdki.

Da się to zautomatyzować? Da się! Pętle!

Zauważ, że linijki mają kolejne numery. Napisanie pętli for z kolejnymi liczbami jest proste jak for(int i = 1; i < 20; i++). Tu nie trzeba będzie za dużo wysiłku wkładać.

Problem pojawia się przy rysowaniu gwiazdek. One też muszą być zautomatyzowane. Jeżeli pętla wyżej będzie odpowiadać za linijki, nie może jednocześnie odpowiadać za gwiazdki. Jeżeli ona tworzy i nadaje numery nowym liniom, może maksymalnie wypisać tyle gwiazdek, ile jest linii! A my potrzebujemy o wiele więcej gwiazdek.

Odpowiedzią na nasz problem jest pętla w pętli. Druga pętla będzie rysowała tyle gwiazdek, jaki jest numer linii. Skąd będzie znała numer linii? Z pętli, w której jest oczywiście!

```
for(int i = 1; i <= 20; i++) {

  for(int j = 1; j <= i; j++) {
    System.out.println("*");
  }

  System.out.println("");
}

```

Zauważ, że w drugiej pętli for mamy zmienną j! Okazuje się, że Java jednak jest sprytna i nie pozwoli nam użyć "i" po raz drugi. Poza tym w tym przypadku popsulibyśmy choinkę, bo musimy narysować i gwiazdek (czyli jak i = 3 to 3 gwiazdki)!

Na końcu linijki, czyli już po gwiazdkach, tworzymy nową linię. Zauważ, że tworzymy już po drugiej pętli, ale jeszcze w pierwszej. Zastanów się dlaczego. Wyobraź sobie rysowanie każdej gwiazdki w nowej linii. Albo rysowanie wszystkich gwiazdek w jednej linii. Przeanalizuj działanie programu (lub sprawdź), kiedy by tak się działo.