17 Nisan 2022

Döngüler: while ve for

Çoğu zaman aynı bir sıra ile tekrar yapma ihtiyacı duyulabilirsiniz.

Örneğin bir listede bulunan ürünlerin sıra ile çıktısını almak. Veya aynı kodu 1-10’a kadar olan sayılar için çalıştırmak.

Döngü aynı kod parçacığının birden fazla defa çalıştırılmasına denir.

“while” döngüsü

while döngüsü aşağıdaki gibi bir yazıma sahiptir:

while (koşul) {
  // kod
  // veya döngünün gövdesi
}

koşul doğru iken(while), döngü gövdesinde bulunan kod çalıştırılır.

Örneğin, aşağıdaki kod i < 3 iken çalışır.

let i = 0;
while (i < 3) { // önce 0, sonra 1, sonra 2
  alert( i );
  i++;
}

Döngünün gövdesinde bulunan kodun her çalışmasına tekerrür(iteration) denir. Yukarıdaki örnekte gövde 3 defa tekerrür etmiştir.

Eğer i++ gibi bir kod olmasaydı, teoride kod sonsuza kadar devam ederdi. Pratikte ise tarayıcınız bu kodun uzun süre çalışmasını engeller, sunucu tabanlı JavaScript yazdığınızda ise bu işlem durdurulur.

Sadece karşılaştırma değil, bir ifade veya değişken koşul olabilir. While döngüsü tarafından alınan tüm ifadeler boolean’a dönüştürülür.

Örneğin, while(i != 0 ) while(i)'de olabilir.

let i = 3;
while (i) {  // i 0 olduğunda koşul `yanlış` olur ve döngü biter.
  alert( i );
  i--;
}
Tek satır gövde varsa süslü paranteze gerek kalmaz.

Eğer döngü gövdesi tek satır ise süslü parantezi yazmayabilirsiniz. {..}:

let i = 3;
while (i) alert(i--);

“do…while” döngüsü

do..while döngüsü kullanarak koşul kontrolünü sonda yapmak mümkündür.

do {
  // döngü gövdesi
} while (condition);

Döngü önce gövdeyi çalıştırır, sonra koşul kontrolü yapar ve eğer doğruysa tekrar döngü gövdesini çalıştırır.

Örneğin:

let i = 0;
do {
  alert( i );
  i++;
} while (i < 3);

Bu şekilde döngü yazımı çok nadir olarak kullanılır. Kullanılmasının en önemli amacı en azından bir defa ne olursa olsun gövdenin çalıştırılma istenmesidir. Genelde while(..){} şekli tercih edilir.

“for” döngüsü

for döngüsü en fazla kullanılan döngüdür.

Aşağıdaki şekilde kullanıma sahiptir:

for (başla; koşul; adım) {
  // ... döngü gövdesi ...
}

Örnekler üzerinden incenecek olursa. Aşağıdaki döngü alert(i) yi i 0 dan 3 olana kadar çalıştırır.(3 dahil değil)

for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2
  alert(i);
}

Bölüm bölüm inceleyecek olursak

bölüm
başla i = 0 Döngüye girildiğinde çalışır.
koşul i < 3 Her tekerrürden önce çalışır, eğer yanlış ise döngü durur.
adım i++ Gövdenin tekerrür etmesinden sonra fakat koşuldan önce çalışır
gövde alert(i) koşul doğru olduğu sürece durmadan çalışır

Genel döngü algoritması aşağıdaki gibidir:

Çalışmaya başlar
→ (if koşul → gövdeyi çalıştır ve adımı çalıştır.)
→ (if koşul → gövdeyi çalıştır ve adımı çalıştır.)
→ (if koşul → gövdeyi çalıştır ve adımı çalıştır.)
→ ...

Eğer döngüleri yeni görüyorsanız, belki geri dönüp bu olanları sırasıyla kağıda yazarak takip ederseniz sizin için daha iyi olacaktır.

Yukarıdaki kodda tam olarak ne oluyor peki:

// for (let i = 0; i < 3; i++) alert(i)

// Çalışmaya başla
let i = 0
// if koşul → gövdeyi çalıştır ve adımı çalıştır
if (i < 3) { alert(i); i++ }
// if koşul →  gövdeyi çalıştır ve adımı çalıştır
if (i < 3) { alert(i); i++ }
// if koşul →  gövdeyi çalıştır ve adımı çalıştır
if (i < 3) { alert(i); i++ }
// ...bitir, çünkü şimdi i=3
Satır arasında değişken tanımlama

Sayaç değişkeni olan i döngüye girdiğinde oluşturulur. Buna “satır arası” değişken tanımlama denir. Bu değişken sadece döngü içerisinde kullanılabilir.

for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // hata! böyle bir değişken bulunamadı.

Değişken tanımlamak yerine var olan da kullanılabilir:

let i = 0;

for (i = 0; i < 3; i++) { // var olan değişkeni kullan
  alert(i); // 0, 1, 2
}

alert(i); // 3, görünür halde çünkü değişken döngünün dışında tanımlandı.

Bazı bölümlerin pas geçilmesi

for döngüsünün her bölümü pas geçilebilir.

Örneğin başlangıç bölümüne ihtiyaç yoksa pas geçilebilir.

Örneğin:

let i = 0; // i'yi tanımlanıp 0 değeri atandı

for (; i < 3; i++) { // "başlangıç"'a ihtiyaç yok
  alert( i ); // 0, 1, 2
}

basamak bilgisini silmek de mümkün:

let i = 0;

for (; i < 3;) {
  alert( i++ );
}

Döngü while(i<3) ile aynı oldu.

Aslında her şeyi silebiliriz:

for (;;) {
  // sonsuz döngü
}

Dikkat ederseniz for döngüsü yazarken noktalı virgüller ; yazılmalıdır, aksi halde yazım hatası verir.

Döngüyü kırma

Normalde döngüler koşul yanlış olduğunda biter.

Fakat bazı durumlarda bu döngü kırılabilir ( break ).

Örneğin, kullanıcıdan bir dizi sayı girmesini istediniz eğer boş bir değer girerse döngüyü kırabilirsiniz.

let toplam = 0;

while (true) {

  let deger = +prompt("Bir sayı giriniz", '');

  if (!deger) break; // (*)

  toplam += deger;

}
alert( 'Toplam: ' + toplam );

break talimatı (*) satırında görüldüğü üzere. Eğer kullanıcı boş değer girerse doğrudan döngü durur ve döngüden sonraki ilk satıra atlar. Yani alert çalışır.

“Sonsuz döngü” + break birlikte kullanıldığında başlangıçta koşul kontrol edilmese de olur ama döngü gövdesinde veya sonunda kontrol edilmesi gerekir denen döngüler için güzel bir birliktelik oluşturur. Bu döngü içerisinde birçok defa koşul kullanılarak döngü kırılabilir.

Bir sonraki tekerrüre geçme

continue, break in daha hafif versiyonudur. Döngüyü tamamen kırmaz da zorla bir sonraki tekerrüre geçer(tabi koşul sağlanıyorsa)

O anda tekrar eden değer ile işimiz bitti ve bir sonraki tekrar geçmek istendiğinde continue kullanılır.

for (let i = 0; i < 10; i++) {

  // Eğer 2'ye bölünebiliyorsa bir sonraki adıma atlar.
  if (i % 2 == 0) continue;

  alert(i); // ekranda 1, 3, 5, 7, 9 değerleri gösterilir.
}

i nin çift değerleri için döngü gövdesi durdurulur, sonraki adıma geçilir. Bundan dolayı alert sadece tek değerler için çalışır.

continue talimatı döngü sayısının azalmasına yardımcı olur.

Tek değerler gösteren döngü aşağıdaki gibi de yazılabilir:

for (let i = 0; i < 10; i++) {

  if (i % 2) {
    alert( i );
  }

}

Teknik açısından birbiri ile aynıdırlar. Her zaman continue bloğunun yerine if kullanabiliriz.

Tabi bunun yan etkisi döngü gövdesi içinde bir tane daha if kullanarak okunabilirliği düşürmektir.

‘if’ yerine ‘?’ kullanılıyorsa sağ tarafa ‘continue/break’ yazılmaz.

break/continute talimatları '?' ile kullanılamazlar

Örneğin:

if (i > 5) {
  alert(i);
} else {
  continue;
}

Yukarıdaki döngü ? ile yazılacak olursa:

(i > 5) ? alert(i) : continue; // `continue` burada kullanılamaz!!!

… sonrasında çalışmayı durdurur. Böyle kodlar yazım hatası verir.

Bu da '?' işaretini if yerine kullanmamak için ayrı bir neden.

break/continue için etiket tanımlanması ( Label )

Bazen birden fazla döngü içinden tek bir break ile çıkılma ihtiyacı duyulabilir. Örneğin aşağıdaki kodda döngü i ve j kordinatlarını ekranda gösterir:

for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let deger = prompt(`Kordinattaki değer (${i},${j})`, '');

    // Burada döngüden çıkmak istersem ne yapmalıyım?

  }
}

alert('Bitti!');

Eğer kullanıcı iptale basarsa döngü iptal edilmelidir.

Normalde içerideki döngü için deger’e değer atadıktan sonra duruma göre içteki döngü kırılabilir. Fakat bu yeterli değildir. Bu gibi durumlarda Labels veya etiket ile sorun çözülebilir.

etiket döngüden önce bir kolon ile döngüyü tanımlamak için kullanılır.

etiketAdi: for (...) {
  ...
}

break <etiketAdi> cümlesi bu etiketin olduğu yeri kırar.

Aşağıdaki gibi:

ust_dongu: for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let giris = prompt(`Kordinattaki değer (${i},${j})`, '');

    // Eğer iptal edildi veya boş bir değer girildiyse dışarıdaki döngüyü de kır.
    if (!giris) break ust_dongu; // (*)

    // değer ile bir şeyler yap.
  }
}
alert('Bitti!');

Yukarıdaki kodda break ust_dongu adımına gelirse üste doğru ust_dongu aranır ve bulunduğu yerde kırılır.

Böylece kontrol doğrudan (*), alert('Bitti!')ye geçer.

Etiketi başka bir satıra geçirmekte mümkündür.

ust_dongu:
for (let i = 0; i < 3; i++) { ... }

continue talimatı da etiket ile kullanılabilir. Bu durumda etiketin yazılı olduğu yere atlar.

Etiketler “goto” değildir.

Etiketler ile kodun herhangi bir yerine atlamak mümkün değildir.

Örneğin aşağıdaki kod çalışmaz.

break etiket;  // etikete atlar değil mi?.

etiket: for (...)

break/continue sadece döngünün içerisinde çalışabilir, ve doğal olarak etiketler de üst tarafa yazılmalıdırlar.

Özet

Bu konuda 3 farklı döngü işlendi:

  • while – Her tekerrürden önce koşul kontrol edilir
  • do..while – Koşul tekerrürden sonra kontrol edilir.
  • for (;;) – Her tekerrürden önce koşul kontrol edilir. Farklı seçenekler mevcuttur.

Sonsuz döngü yapmak için genelde while(true) kullanılır. Böyle döngüler de diğerleri gibi break talimatıyla kırılabilir.

Eğer o anki tekerrür ile işimiz bitti ve bir sonrakine geçmek istiyorsanız continue kullanmanız lazım.

break/continue ile döngüden önce yazılan etikete atlamak veya üst döngüyü kırmak mümkündür.

Görevler

önem: 3

Aşağıdaki kodda en son uyarıda ekrana ne yazar? Neden?

What is the last value alerted by this code? Why?

let i = 3;

while (i) {
  alert( i-- );
}

Cevap: 1.

let i = 3;

while (i) {
  alert( i-- );
}

Her defasında döngü i dolayısıyla 1 azalır. while(i) koşulu i=0 olduğunda sonlanır.

Döngü aşağıdaki gibi işler:

let i = 3;

alert(i--); //  3 gösterir, i 2'ye iner.

alert(i--) // 2 gösterir, i 1'e iner.

alert(i--) // 1 gösterir, i 0'a iner.

// bitti `while(i)` koşulu tekrar kontrol edildi ve i = 0 olduğundan döngüden çıkıldı.
önem: 4

Her döngü için ekranda gösterilecek değerler nelerdir? Bu değerleri yazın ve sonra cevap ile karşılaştırın.

Her iki döngüde de alert aynı değerleri mi gösterir?

Both loops alert same values or not?

  1. Önden eklemeli ++i:

    let i = 0;
    while (++i < 5) alert( i );
  2. Sonradan ekelemeli form i++

    let i = 0;
    while (i++ < 5) alert( i );

The task demonstrates how postfix/prefix forms can lead to different results when used in comparisons.

  1. From 1 to 4

    let i = 0;
    while (++i < 5) alert( i );

    The first value is i = 1, because ++i first increments i and then returns the new value. So the first comparison is 1 < 5 and the alert shows 1.

    Then follow 2, 3, 4… – the values show up one after another. The comparison always uses the incremented value, because ++ is before the variable.

    Finally, i = 4 is incremented to 5, the comparison while(5 < 5) fails, and the loop stops. So 5 is not shown.

  2. From 1 to 5

    let i = 0;
    while (i++ < 5) alert( i );

    The first value is again i = 1. The postfix form of i++ increments i and then returns the old value, so the comparison i++ < 5 will use i = 0 (contrary to ++i < 5).

    But the alert call is separate. It’s another statement which executes after the increment and the comparison. So it gets the current i = 1.

    Then follow 2, 3, 4…

    Let’s stop on i = 4. The prefix form ++i would increment it and use 5 in the comparison. But here we have the postfix form i++. So it increments i to 5, but returns the old value. Hence the comparison is actually while(4 < 5) – true, and the control goes on to alert.

    The value i = 5 is the last one, because on the next step while(5 < 5) is false.

önem: 4

Her bir tekerrür için görünecek sonuçları bir yere yazın. Yazdıklarınızı cevap ile karşılaştırın.

Döngüler ekrana aynı uyarılar mı verir?

  1. Önden Eklemeli

    for (let i = 0; i < 5; i++) alert( i );
  2. Sonradan Eklemeli

    for (let i = 0; i < 5; ++i) alert( i );

Cevap: Her iki durum için de 0 dan 4’e kadardır

for (let i = 0; i < 5; ++i) alert( i );

for (let i = 0; i < 5; i++) alert( i );

Bu sonuç doğrudan for algoritmasına bakarak çıkartılabilir:

  1. Başlangıçta öncelikle i = 0’ı başlat.
  2. Koşulu kontrol et i < 5
  3. Eğer doğru dönüyorsa uyarıyı göster alert(i) ve sonra i++

Artırma i++ koşul kontrolünden tamamen ayrı bir olaydır(2). Sadece koşulacak ayrı bir cümledir.

Artırımdan dönen değer burada kullanılmadı, bundan dolayı i++ ile ++i arasında bir fark yoktur.

önem: 5

for döngüsü kullanarak 2 den 10 a kadar olan çift sayıların çıktısını yazdırın.

Demoyu çalıştır

for (let i = 2; i <= 10; i++) {
  if (i % 2 == 0) {
    alert( i );
  }
}

“modül” operatörü % kullanarak kalan değerin çift mi yoksa tek mi olduğunu kontrol edip uyarı verebilirsiniz.

önem: 5

Aşağıdaki kodun davranışı değişmeyecek şekilde while döngüsüne çevirin.

for (let i = 0; i < 3; i++) {
  alert( `number ${i}!` );
}
let i = 0;
while (i < 3) {
  alert( `number ${i}!` );
  i++;
}
önem: 5

Kullanıcıya her defasında 100’den büyük bir sayı girmesi için prompt ile veri girmesini isteyin. Eğer farklı bir değer girerse tekrardan değer girmesi talebinde bulunun.

Kullanıcı 100 den büyük bir sayı veya boş satıra onay verene kadar döngüye devam edilmelidir.

Burada kullanıcının sadece sayısal değerler gireceğini farzedin. Ayrıca sayısal değerleri kontrol için bir kod yazmak ile uğraşmayın.

Demoyu çalıştır

let sayi;

do {
  sayi = prompt("100'den büyük bir sayı giriniz", 0);
} while (sayi <= 100 && sayi);

do..while ile her iki koşul da doğru olana kadar kontrol edin:

  1. sayi <=100 – girilen değerin hala 100 den büyük olmadığını gösterir.
  2. && sayi sayi null veya boş bir değer olduğunda false dönderir. Tabi while döngüsü de burada sona erer.

NOT: Eğer sayi null ise num<=100 true olur. Yani ikinci kontrol olmadan kullanıcı IPTAL tuşuna bassa bile döngü durmayacaktır. İki koşul da gereklidir.

önem: 3

1 den büyük olup 1 veya kendisi haricinde hiçbir sayıya kalansız bölünemeyen sayılara asal sayı denir.

Örneğin 5 bir asal sayıdır. Çünkü 2,3 ve 4 e kalansız bölünemez.

2 den ne kadar olan asal sayıları bulan kodu yazınız.

Örneğin n = 10 için sonuç 2,3,5,7 olacaktır.

NOT: Kod her türlü n değeri için çalışmalıdır, sabit bir sayı değildir.

There are many algorithms for this task.

Let’s use a nested loop:

For each i in the interval {
  check if i has a divisor from 1..i
  if yes => the value is not a prime
  if no => the value is a prime, show it
}

The code using a label:

let n = 10;

nextPrime:
for (let i = 2; i <= n; i++) { // for each i...

  for (let j = 2; j < i; j++) { // look for a divisor..
    if (i % j == 0) continue nextPrime; // not a prime, go next i
  }

  alert( i ); // a prime
}

There’s a lot of space to opimize it. For instance, we could look for the divisors from 2 to square root of i. But anyway, if we want to be really efficient for large intervals, we need to change the approach and rely on advanced maths and complex algorithms like Quadratic sieve, General number field sieve etc.

Eğitim haritası