Twitter: @grzegg
Kategoria: java, Tagi: - - - .

Java [13] – Pętle cz. II

W tej lekcji przedstawię pętlę for a także przedstawię dwa sposoby zatrzymania na określony czas działania programu oraz metodę wyjścia z pętli za pomocą komendy break.

Ta strona jest częścią materiałów do kursu “Programowanie w Javie z elementami bioinformatyki dla poczatkujących”. Pozostałe materiały znajdziesz tutaj

Pętla for

Trzecim rodzajem pętli jest for. Jej struktura jest nieco bardziej skomplikowana niż poprzednio poznanych i wygląda tak:


for (inicjalizacja zmiennej ; warunek ; zmiana zmiennej) {
// Kod objęty pętlą
}

Omówię ją na przykładzie. W NetBeans możesz automatycznie wygenerować szkielet struktury pętli for pisząc właśnie for i naciskając Enter. Wtedy powinien zostać wygenerowany taki kod:

W zasadzie kod jest samowyjaśniający. W nawiasach mamy trzy elementy: w pierwszym dokonujemy inicjalizacji zmiennej, w drugim znajduje się wyrażenie warunkowe – dopóki zwraca wartość true pętla się wykonuje, w trzecim znajduje się wyrażenie zmieniające wartość zadeklarowanej zmiennej.
Wyrażenie warunkowe zazwyczaj jest związany ze zmienną, którą deklarujemy i zmieniamy ale nie jest to konieczne. Ta zmienna funkcjonuje w pętli for jako swego rodzaju „licznik”.

Taka konstrukcja pętli od razu sugeruje jej zastosowanie. Zazwyczaj używamy jej gdy chcemy jakąś czynność wykonać określoną liczbę razy. Zmienna deklarowana i zmieniana w nawiasie może być (i często jest) wykorzystana w bloku kodu objętym pętlą. Może być też w nim modyfikowana.

W najprostszym przypadku możemy po prostu wypisać wartość i:

Otrzymamy:

i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

Pętla wykonała się 10 razy zaczynając od 0 i kończąc na 9.

Zastanów się jak zmodyfikować kod, żeby otrzymać liczby od 1 do 10?
Można do problemu podejść na co najmniej dwa sposoby: albo zmieniamy linię drukującą liczby na:

System.out.println(" i = " + (i + 1));

albo zmieniamy wyrażenia w nawiasie na przykład tak (zastanów się nad innymi możliwościami):


for (int i = 1; i <= 10; i++) {

Wyrażenie zmieniające zmienną nie musi polegać na jej zmianie o 1. Możemy na przykład zmniejszać o 1 (jeśli np. chcemy odliczać w dół) albo dokonać innej operacji arytmetycznej.

Struktura pętli for jest dość elastyczna. Na przykład można element pierwszy i trzeci przenieść poza nawias:

W tym przypadku działanie pętli for będzie w zasadzie takie same jak pętli while. Jeśli wykonasz ten kod, przy okazji zauważ, że wypisane liczby będą tym razem z zakresu 2 do 11. Dlaczego? Przypomnij sobie początek tej lekcji jak działa operator ++ kiedy znajduje się po nazwie zmiennej. W takim przypadku najpierw jest zwracana wartość zmiennej która „wędruje” do wnętrza pętli a dopiero później jest zmieniana. Dlatego przy pierwszym kodzie dotyczącym pętli for drukowane wartości zmiennej i zaczynały się od zera. Gdyby zamiast i++ napisać ++i wypisane liczby zaczynałyby się od 1. Jeśli przeniesiemy wyrażenie i++ do bloku kodu objętego pętlą, przed komendą odpowiadającą za jej wydrukowanie, jej wartość zwiększa się zanim dojdzie do pobrania tej wartości. Teraz zastanów się, jak można zmienić kod wewnątrz pętli, żeby otrzymać na ekranie wartości od 1 do 10?

Pętla foreach

Kolejnym rodzajem pętli jest foreach. Poznamy ją przy okazji tablic.

Zagnieżdżanie pętli

Pętle można zagnieżdżać. Na przykład dwie zagnieżdżone pętle for mogą wyglądać tak:

Wynik działania powyższego kodu będzie taki:


Pętla zewnętrzna
Pętla wewnętrzna i = 0 j = 0
Pętla wewnętrzna i = 0 j = 1
Pętla zewnętrzna
Pętla wewnętrzna i = 1 j = 0
Pętla wewnętrzna i = 1 j = 1
Pętla zewnętrzna
Pętla wewnętrzna i = 2 j = 0
Pętla wewnętrzna i = 2 j = 1

Przeanalizuj go, ponieważ dobrze pokazuje jak działają zagnieżdżone pętle. Najpierw uruchamia się pętla zewnętrzna i rusza w niej „licznik” za który odpowiedzialna jest zmienna i. Program następnie dochodzi do pętli wewnętrznej, która się uruchamia, startuje także jej „licznik” za który odpowiada zmienna j. Pętla wewnętrzna wykonuje się określoną liczbę razy (w tym przypadku 2), program przechodzi do pętli zewnętrznej, która dokonuje następnego „obrotu” znów uruchamiając pętlę wewnętrzną. Pętla wewnętrzna uruchamiana jest od nowa, jej licznik jest wyzerowany, więc znów wykonuje wewnętrzny kod dwa razy, po czym program przechodzi do pętli zewnętrznej i tak dalej aż do ukończenia ostatniej iteracji pętli zewnętrznej.

Zauważ, że pętla wewnętrzna „widzi” zmienną i ale gdyby spróbować wywołać zmienną j z poziomu pętli zewnętrznej okaże się to niemożliwe. W tym przypadku znów daje o sobie znać ograniczona widoczność zmiennych. Zagnieżdżony kod ma dostęp do zmiennych używanych na wyższych poziomach ale nie działa to w druga stronę.

Przy okazji zwracam uwagę na nazwy zmiennych używanych jako liczniki w pętlach for. Zwyczajowo nazywane są one kolejnymi literami od począwszy i. Przy czym kolejne litery stosujemy jeśli pętle są zagnieżdżone, dlatego w powyższym przykładzie w pętli zewnętrznej używana była zmienna i a w wewnętrznej j. Jeśli pętle są umieszczone na tym samym poziomie a ich zmienne nie są deklarowane poza pętlami to mogą mieć taką samą nazwę zmiennej liczącej. Oczywiście nie musimy się zawsze trzymać tej konwencji, możemy zmienne liczące nazywać inaczej, zwłaszcza jeśli w kodzie mają jeszcze jakieś znaczenie poza liczeniem kolejnych iteracji.

Poczekaj chwilkę…

Zwykle zależy nam na tym, aby program wykonywał swoje zadania jak najszybciej. Ale bywa i tak, że nadmierna szybkość nie jest pożądana. Dzieje się tak na przykład gdy pętla wykonuje jakieś obliczenia i nie tylko interesuje nas końcowy wynik obliczeń, ale chcielibyśmy obserwować zmiany obliczanych wartości na bieżąco. Jeśli obliczenia nie są szczególnie wymagające, to zwykle obserwujemy wydruk pośrednich wartości na tyle szybki, że nie jesteśmy w stanie ich śledzić. W takich wypadkach przydałaby się możliwość wstrzymywania programu na chwilę, na przykład na sekundę, przy każdej iteracji pętli.
W zasadzie można by na przykład w takiej sytuacji umieścić w takiej pętli jeszcze jedną pętle, która wykonywałaby się na tyle dużo razy, że widocznie zastopowałaby program ale takie rozwiązanie po pierwsze nie pozwoliłoby precyzyjnie określić czasu wstrzymania programu, po drugie niepotrzebnie obciążałoby procesor a po trzecie nie byłoby zbyt „hakerskie” ;-)
Na szczęście Java dostarcza w takich przypadkach kilku znacznie bardziej eleganckich rozwiązań. Do najbardziej znanych należy polecenie:


Thread.sleep(czas);

Podawany jako argument czas określa liczbę milisekund na którą „zatrzyma się” program. Ponieważ 1 sekunda = 1000 milisekund, aby wstrzymać program na sekundę, należy napisać: Thread.sleep(1000);. Samo wpisanie powyższej komendy nie wystarczy. Pojawia się informacja:

Z podobnym komunikatem mieliśmy do czynienia w lekcji 08 poświęconej iterakcji z użytkownikiem, tym razem możemy sobie pomóc podobnie dodając przy nazwie metody, po nawiasie throws InterruptedException (co można zrobić automatycznie wciskając Alt+Enter i wybierając pierwszą opcję). Po co dodajemy ten dopisek i jak to działa omówię w jednej z przyszłych lekcji poświęconej obsłudze błędów. Przykładowy program ilustrujący działanie metody sleep() może wyglądać tak:

Innym rozwiązaniem jest użycie klasy TimeUnit, która umożliwia łatwiejsze posługiwanie się innymi okresami czasu niż milisekunda. Na przykład jeśli czas wstrzymania chcemy wyrazić w sekundach, można zrobić to tak:


TimeUnit.SECONDS.sleep(10);

Program zatrzyma się na 10 sekund. Jeśli chcemy użyć innych jednostek, zamiast SECONDS można użyć NANOSECONDS, MICROSECONDS, MILLISECONDS, MINUTES, HOURS a nawet DAYS .

Także w tym przypadku będzie trzeba użyć throws… (lub innych rozwiązań o których jak wspomniałem jeszcze będziemy mówić) a także zaimportować odpowiednią klasę: import java.util.concurrent.TimeUnit;.

Wyjście z pętli

Czasem istnieje potrzeba aby w kodzie znajdującym się w pętli umieścić możliwość wyjścia z niej. Jest to możliwe za pomocą instrukcji break poznanej przy okazji komendy switch. Na przykład:

Program zbiera od użytkownika 100 liczb, i je sumuje. Ale użytkownik może wcześniej przerwać pobieranie liczb wpisując „K”.
Zauważ, że użytkownik wpisuje liczbę, lub literę, nie można więc wykorzystać metody skaner.nextInt(), oddzielony więc został proces pobierania danych od konwersji na liczbę. Jeśli użytkownik poda „k” lub „K” pętla zostaje przerwana, w innym przypadku wykorzystujemy polecenie Integer.parseInt(liczbaStr), które działa analogicznie do polecenia Double.parseDouble(liczba), które poznaliśmy w lekcji poświęconej interakcji z użytkownikiem z tą różnicą, że jak się łatwo zorientować zwraca jest liczba całkowita typu int.

Zadanie - liczby Fibonacciego

Opis problemu:

Na początku XIII wieku włoski matematyk Leonardo z Pizy, znany szerzej jako Fibonacci wydał księgę w której między innymi opisał matematycznie wzrost populacji królików. Był to oczywiście mocno uproszczony opis czy raczej, jak dzisiaj powiedzielibyśmy, model ale ciąg liczb jaki przy tym uzyskał wykraczają daleko poza problem rozrodczości sympatycznych futrzaków o czym za chwilę.
W modelu Fibonacciego, jak moglibyśmy to ująć dzisiaj, rozmnażaniem królików rządzą trzy zasady:

  1. Króliki osiągają dojrzałość rozrodczą po miesiącu.
  2. Każda dojrzała para królików wydaje na świat dwa króliki co miesiąc (zakładamy, że są różnej płci).
  3. Króliki są nieśmiertelne.

Jak widać model jest rzeczywiście mocno uproszczony ale biologiczne niuanse możemy pominąć, skupmy się raczej na liczbach.

Przyjrzyjmy się jak w takim razie wygląda liczebność populacji, liczona w parach królików w kilku pierwszych miesiącach:

miesiąc co się dzieje w populacji liczba par królików
1 pojawia się para młodych królików 1
2 para królików dojrzewa 1
3 para wydaje na świat parę młodych królików 2
4 pierwsza para wydaje na świat kolejny miot, druga para dojrzewa 3
5 pierwsza i druga para wydają mioty, trzecia para dojrzewa 5
itd..

W ten sposób otrzymujemy taki ciąg liczb:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987…

Każdy kolejny element tego ciągu stanowi sumę dwóch poprzednich elementów, co dla miesięcy powyżej drugiego można ogólnie zapisać tak:


Gdzie F - liczby Fibonacciego, n - miesiąc

Dla pierwszego i drugiego miesiąca F wynosi 1.

Z ciągiem Fibonacciego jest także związana tzw. złota liczba oznaczana grecką literą φ (fi), której wartość określona jest wzorem:

Co daje liczbę 1,618033988….

Jaki to ma związek z ciągiem Fibonacciego? Otóż okazuje się, że kolejne, coraz dokładniejsze przybliżenia φ można uzyskać dzieląc liczbę z ciągu przez liczbę poprzedzającą. Im liczby są większe, tym przybliżenie jest dokładniejsze. Na przykład:

3/2 = 1,5; 8/5 = 1,6; 21/13 = 1,6153…; 55/34 = 1,6176…; 144/89 = 1,6179… itd..

A teraz przechodzimy do biologii. Jeśli przyglądałeś się kiedyś ułożeniu nasion w słoneczniku z pewnością zauważyłeś, że ułożone są w dwie serie spiral. Jedna seria zwija się zgodnie z kierunkiem ruchu wskazówek zegara a druga w kierunku przeciwnym. Gdybyś je policzył, okazało by się, że w jednym kierunku byłoby ich na przykład 21 a w drugim 34 czyli uzyskałbyś kolejne liczby z ciągu Fibonacciego. Liczby te znajdziemy także w helisach w które układają się sześciokąty widoczne na ananasie, brodawki na kaktusach czy łuski szyszek. Z liczbami Fibonacciego związane jest również helikalne ułożenie liści na łodydze a także liczba płatków w kwiatach.

Treść zadania:

Napisz program który wyliczy serię liczb Fibonacciego o długości zadanej przez użytkownika a także poda kolejne przybliżenia liczby φ.

Ta strona jest częścią materiałów do kursu “Programowanie w Javie z elementami bioinformatyki dla poczatkujących”. Pozostałe materiały znajdziesz tutaj

3 komentarze Java [13] – Pętle cz. II

  • Karolina

    Nie umiałam wpaść na to, jak napisać polecenie, by brane były poprzednie wartości ciągu. Trochę musiałam się podeprzeć internetem. Finalnie wyszło mi coś takiego:
    package petle;

    import java.util.Scanner;

    public class Fibonacci {

    public static void main(String [] args){

    Scanner skaner = new Scanner(System.in);

    System.out.println(„Podaj ilość wyrażeń ciągu Fibonacciego: „);
    int x = skaner.nextInt();

    System.out.println(1);
    System.out.println(1);

    long a = 1L, b = 1L;

    for(int i = 0; i < (x-2); i++) {

    long suma = a+b;
    double iloraz = (double)suma/b;
    a = b;
    b = suma;

    System.out.println(suma + " " + iloraz);
    }

    }
    }

  • Danee

    Świetny kurs ! Dużo pozwala zrozumieć!

Leave a Reply