for, range() ve döngü problem kalıpları
Geçen hafta while ile koşul doğru olduğu sürece tekrar yaptık. Bazen de kaç tekrar yapacağımız baştan bellidir: 1’den 10’a kadar sayıları işlemek, 7 günlük veri girmek ya da 6 taksitlik bir ödeme planı çıkarmak gibi.
Bu tür durumları Python’da for döngüsü ve range() fonksiyonuyla daha kısa yazabiliriz. Bu hafta for söz dizimini öğrendikten sonra sayma, biriktirme, arama ve özetleme gibi birçok problemde karşımıza çıkan döngü kalıplarını tanıyacağız.
Bu sayfadaki kod hücreleri kendiliğinden çalışmaz. Her hücrede önce çıktıyı tahmin edin, sonra Run Code düğmesine basın. Kodu değiştirip yeniden çalıştırabilir, Start Over ile ilk hâline döndürebilirsiniz.
Bu bölümün kapsamı
range()nesnesinin ürettiği değerler ve listeden farkırange()biçimleri ve bitiş değerinin dışarıda kalması- Sayma ve biriktirme kalıpları
- Arama kalıbında
breakile erken bitirme continueile yalnızca o yinelemeyi atlamawhile Truevebreakile geçerli girdi kalıbı ve bu kalıbın özel bitirme değerinden (sentinel) farkı- Probleme göre
forilewhilearasında seçim
enumerate()vezip()kullanımı- İkiden çok katmanlı iç içe döngüler
Bu başlıklar konunun devamıdır. İleride karşınıza çıkar, ama bu derste ezberlemeniz beklenmiyor.
for ile belirli sayıda tekrar
for döngüsü bir dizi değeri sırayla tek tek alır ve her değer için girintili gövdeyi bir kez çalıştırır. for sayi in range(5): satırı şunu söyler: range(5)’in verdiği her değeri sırayla sayi adına bağla ve gövdeyi çalıştır. Bu hücre hangi sayıları yazar? 5 de yazılır mı?
range(5) 0’dan başlar ve 5’e gelmeden durur. Bitiş değeri olan 5 dâhil değildir.
Bu davranış başta alışılmadık gelebilir, ama kaç değer üretileceğini hesaplamayı kolaylaştırır: range(5) beş değer, range(2, 6) ise 6 - 2 = 4 değer üretir.
range() değerleri sırası gelince verir
range(5) ile döngüde 0–4 değerlerini dolaşabiliriz, ama range() çağrısı bu değerleri bir liste olarak tutmaz. Köşeli parantez içinde virgülle sıralanan değerlere liste denir: [0, 1, 2] gibi. Listeleri 9. bölümde ayrıntılı göreceğiz.
range() bunun yerine ayrı bir range nesnesi oluşturur. Nesne (object), Python’ın bellekte tuttuğu, türü belli bir değerdir. range nesnesi değerleri döngüye sırası gelince verir. Değerleri bir kerede görmek isterseniz list() ile listeye çevirebilirsiniz. Bu hücre iki satır yazar. Satırlar aynı mı?
İlk satır range(0, 5) yazar. print() nesnenin kendisini gösterir, içindeki değerleri tek tek dökmez. İkinci satır [0, 1, 2, 3, 4] yazar, çünkü list() değerleri alıp bir liste oluşturur.
for sayi in range(5): yazarken list(range(5)) yapmanız gerekmez. Döngü değerleri range nesnesinden doğrudan sırayla alır. Burada list() yalnızca aralığın içeriğini göstermek için kullanıldı.
Tahmin et: range() hangi değerleri üretir?
range()’e üç sayı verildiğinde ilki başlangıç, ikincisi bitiş, üçüncüsü adımdır: değer her turda adım kadar artar. Aşağıdaki kod hangi sayıları yazar? Çalıştırmadan önce tahmin edin.
range(2, 9, 2) 2 değerinden başlar, her adımda 2 artar ve 9 değerine gelmeden durur. Bu yüzden çıktı 2, 4, 6, 8 olur. Sıradaki değer 10 olurdu, ama 10 bitiş sınırını geçer.
range() biçimleri
Üç temel kullanım biçimi vardır:
| Yazım | Ürettiği değerler |
|---|---|
range(5) |
0’dan başla, 5 hariç ilerle |
range(2, 6) |
2’den başla, 6 hariç ilerle |
range(10, 0, -2) |
10’dan başla, 0’a gelmeden ikişer azal |
Adım negatif olduğunda kural değişmez, 0 için de ayrı bir kural yoktur. Bitiş (stop) değeri hiçbir durumda dâhil edilmez. Değişen yalnızca ilerleme yönüdür.
Aşağıdaki hücre 5’ten geriye doğru sayıyor. Liste 0 ile biter mi?
Çıktı [5, 4, 3, 2, 1] olur. Listede 0 yok, çünkü bu çağrıda bitiş değeri 0’dır.
Başlangıcın dâhil, bitişin hariç olduğu bu tür aralığa yarı açık aralık denir. Adım pozitif olduğunda da aynı kural geçerlidir.
Alıştırma: son değeri dâhil edin
1’den 10’a kadar on sayının hepsini yazdırmak istiyoruz. Aşağıdaki kod 10 değerini atlıyor.
range() çağrısındaki bitiş değerini düzeltin.
range(1, 11) ifadesini deneyin.
for sayi in range(1, 11):
print(sayi)Sayaç ve biriktirici
4. bölümde döngülerde iki değişken rolü görmüştük:
- Sayaç: kaç kez işlem yapıldığını ya da kaçıncı adımda olduğumuzu tutar.
- Biriktirici: toplam, çarpım gibi her turda güncellenen sonucu tutar.
İkisi for ile de aynı biçimde çalışır. Aşağıdaki program 1’den 5’e kadar sayıları topluyor. sayi, range() çağrısından sırayla gelen değerdir, toplam ise biriktiricidir. Son satır ne yazar?
Çıktı 15 olur. İzleme tablosu her turu gösterir:
sayi |
Yeni toplam |
|---|---|
| 1 | 1 |
| 2 | 3 |
| 3 | 6 |
| 4 | 10 |
| 5 | 15 |
Alıştırma: haftalık çalışma süresini toplayın
Bir öğrencinin her gün sabit 45 dakika çalıştığını ve bunu 7 gün sürdürdüğünü varsayalım. Toplam çalışma süresini biriktiriciyle hesaplayın.
toplam_dakika = toplam_dakika + 45 satırı her yinelemede çalışmalıdır.
toplam_dakika = 0
for gun in range(1, 8):
toplam_dakika = toplam_dakika + 45
print("Toplam dakika:", toplam_dakika)Sayma kalıbı
Bazen değerlerin toplamını değil, bir koşulu kaç değerin sağladığını bulmak isteriz. Aşağıdaki kod 1–20 arasında 3 ile tam bölünen sayıları sayıyor. Kaç sayı bulur?
Buradaki sayaç her yinelemede değil, yalnızca koşul doğru olduğunda artar.
Tahmin et ve çalıştır
Aşağıdaki kod 1–15 arasında kaç çift sayı olduğunu sayıyor. Çalıştırmadan önce sonucu tahmin edin:
Sonuç 7 olur: 2, 4, 6, 8, 10, 12, 14. range(1, 16) 15’te biter ve 15 tek sayıdır.
Arama kalıbı ve break
Bir aralıkta aradığımız ilk değeri bulduktan sonra döngünün devam etmesine gerek kalmayabilir. break, içinde bulunduğu döngüyü hemen bitirir.
Aşağıdaki kod 50’den büyük ve 7 ile tam bölünen ilk sayıyı arıyor. Kaç satır yazar?
Tek satır yazılır: Bulundu: 56. 56 koşulu sağlayan ilk sayıdır ve break döngüyü orada bitirir. 63, 70 gibi öteki katlara hiç bakılmaz.
break çalıştığında döngü, koşulu yanlış olduğu için değil, programcı o noktada bitirmek istediği için biter. Gereksiz yere kullanılan break kodun akışını izlemeyi zorlaştırabilir. Aranan sonuç bulunduktan sonra kalan turlar bir işe yaramıyorsa break kullanmak uygundur.
Alıştırma: ilk uygun katı bulun
30’dan büyük, 8 ile tam bölünen ilk sayıyı bulun. Aşağıdaki program uygun değerlerin hepsini yazdırıyor. Yalnızca ilkini yazdıracak biçimde değiştirin.
print() satırından sonra aynı girinti düzeyinde break yazın.
for sayi in range(31, 80):
if sayi % 8 == 0:
print("Bulundu:", sayi)
breakGeçerli girdi gelene kadar: while True + break
Geçen hafta input() satırını döngüden önce ve döngü içinde iki kez yazdığımız örneği gördük. Artık break’i bildiğimize göre aynı akışı tek bir input() satırıyla yazabiliriz:
while True:
puan = int(input("1-5 arasında puan: "))
if 1 <= puan <= 5:
break
print("Geçersiz değer")
print("Kabul edilen puan:", puan)while True koşulu hiçbir zaman yanlış olmaz. Bu yüzden bu kalıpta çıkış koşulunu ve break satırını sizin yazmanız gerekir. Geçerli değer gelince break döngüyü bitirir. Değer geçersizse yeni bir yineleme başlar ve puan yeniden sorulur.
Bu örnek de kullanıcının int() ile sayıya çevrilebilecek bir değer yazdığını varsayar. Sayıya çevrilemeyen metinle ne yapılacağını 13. haftada, istisnalar konusunda öğreneceğiz.
continue: bu yinelemenin kalanını atla
continue döngüyü bitirmez. O anki yinelemenin geri kalanını atlar ve bir sonraki yinelemeye geçer.
Tek ve basit bir koşul için çoğu zaman continue gerekmez. Örneğin yalnızca saat == 3 durumunu atlamak istiyorsanız if saat != 3: yazmanız yeterlidir. continue en çok, veriyi işlemeden önce birden fazla geçersiz durumu ayıklamak gerektiğinde işe yarar. Asıl işi yapan satırlar böylece daha sade kalır.
for bir listenin elemanlarını da sırayla alabilir. Aşağıdaki kodda for sicaklik in olcumler: her turda listeden bir ölçüm alır. -50 derecenin altındaki ve 60 derecenin üstündeki değerleri geçersiz sayıyoruz. Program iki satır yazar. Hangi sayılar?
Bu örnekte iki eleme koşulu, toplama ve sayma satırlarından önce gelir. Geçersiz bir değerde continue çalışınca toplam ve sayaç güncellenmez, döngü sonraki ölçümle devam eder.
-80 ilk koşula, 75 ikinci koşula takılır ve ikisi de atlanır. Geriye 22, 24 ve 19 kalır. Çıktı Geçerli ölçüm: 3 ve Toplam: 65 olur.
Aynı işi continue olmadan daha kısa ve açık bir koşulla yazabiliyorsanız onu seçin. Ama bir veriyi atlamak için birden fazla kontrol yapıyorsanız continue, asıl işi yapan satırları daha az girintili ve daha okunur tutabilir.
Özel bitirme değeri (sentinel)
Kaç veri geleceği baştan belli değilse for range(...) işe yaramayabilir. Kullanıcı veri girişini özel bir değer yazarak bitiriyorsa geçen haftaki while yapısını kullanırız.
Örneğin harcama tutarları girilsin, -1 girildiğinde giriş bitsin:
toplam = 0
tutar = float(input("Tutar (-1 bitirir): "))
while tutar != -1:
toplam = toplam + tutar
tutar = float(input("Tutar (-1 bitirir): "))
print("Toplam:", toplam)Burada -1 bir harcama değildir, yalnızca girişin bittiğini bildirir. Bu tür değere özel bitirme değeri (sentinel) denir.
Gerçek bir programda girilen tutarın geçerli olup olmadığı ayrıca kontrol edilmelidir. Bu örnekte -1 dışında negatif değer girilmediğini ve kullanıcının boş giriş yapmadığını varsayıyoruz. Kullanıcı hiçbir şey yazmadan Enter’a basarsa float("") dönüşümü ValueError hatası verir. Bu tür dönüşüm hatalarını yakalamayı 13. haftada try/except ile göreceğiz.
İç içe döngü ne zaman gerekir?
Bir tekrarın içinde başka bir tekrar gerekiyorsa döngüleri iç içe yazarız. Bu derste iç içe döngüyü yalnızca problemde gerçekten iki ayrı tekrar olduğunda kullanacağız: katlar ve her kattaki odalar gibi.
Aşağıdaki kod iki katlı ve her katta üç odası olan bir binanın oda etiketlerini yazdırıyor. Dış döngünün her yinelemesinde iç döngü baştan sona çalışır. Kaç satır yazılır?
Dış döngü kat için 1 ve 2 değerlerini alır. Her katta iç döngü oda için 1, 2, 3 değerlerini dolaşır. Ekrana toplam 2 × 3 = 6 satır yazılır.
Bu hafta yıldızlarla üçgen çizmek gibi karmaşık desenlere girmeyeceğiz. Aklınızda şu kalsın: problemde birbirinden bağımsız iki tekrar varsa iç içe döngü gerekebilir.
Hangi döngüyü seçmeliyim?
Basit bir başlangıç kuralı:
| Durum | Genellikle uygun yapı |
|---|---|
| Tekrar sayısı baştan belli | for + range() |
| Bir koşul sağlandığı sürece devam | while |
| Özel bitirme değeri gelene kadar devam | while |
| Belirli sayı aralığındaki değerleri işle | for + range() |
Tabloyu kesin bir kural gibi okumayın: aynı problem çoğu zaman iki yapıyla da çözülebilir. Seçerken kodun niyetini en açık anlatan yapıyı tercih edin.
Soruya dönelim: kâğıt okuma adımları Python’da
Bölümün başında sınav kâğıtlarını okuma adımlarını yazıp dört durumla denemiştiniz. Python’daki karşılıkları şöyle:
| Durum | Sözde kodda | Python’da | Dikkat |
|---|---|---|---|
30 numaralı kâğıt okunuyor mu? |
“1’den 30’a kadar her kâğıt için” | for no in range(1, 31): |
range(1, 30) yazılırsa 30 numaralı kâğıt okunmaz: bitiş değeri dışarıda kalır |
| Geçenler ve ortalama | “Geçtiyse sayacı bir artır, puanı toplama ekle” | if puan >= 50: altında gecen = gecen + 1, her kâğıtta toplam = toplam + puan |
Sayaç if dışında artarsa geçenleri değil kâğıtları sayar |
| Boş kâğıt | “Kâğıt boşsa sonrakine geç” | if puan == "boş": altında continue |
Boş kâğıt 0 puan sayılırsa ortalama düşer. Ortalama, okunan kâğıt sayısına bölünür, 30’a değil |
100 alan var mı? |
“100’ü bulunca dur” | if puan == 100: altında break |
break olmazsa 3 numaralı kâğıttan sonraki 27 kâğıt da boşuna okunur |
| Mazeret kâğıtları | “‘Bitti’ sayfası gelene kadar oku” | while ve özel bitirme değeri |
Kaç kâğıt geleceği belli olmadığı için range() ile sınır yazılamaz |
Listenizi tabloyla karşılaştırın. “1’den 30’a kadar” yazdıysanız Türkçede doğru, ama Python’da bitiş sayısı bir fazla yazılır. Boş kâğıdı hiç düşünmediyseniz program da düşünmez: o kâğıtta ya hata verip durur ya da kâğıdı ortalamaya yanlış bir değerle katar. Mazeret kâğıtları için “30 kere oku” gibi bir sayı yazdıysanız, o sayının nereden geldiğini kendinize sorun.
Değiştir: taksit planını inceleyin
12000 TL’lik bir ürün 6 eşit taksitle ödenecek olsun:
Şunları deneyin:
taksit_sayisideğerini4yapın.- İlk ve son ayın
aydeğerini tahmin edin. range(1, taksit_sayisi)yazarsanız son taksitte ne olur?- Her ayın başındaki ve sonundaki
kalandeğerini küçük bir izleme tablosuna yazın.
Tek sayfa özet
Bu hafta belirli sayıda tekrarı for ve range() ile yazdık ve sık kullanılan döngü kalıplarını gördük.
for, birrange()nesnesinin ya da bir listenin değerlerini sırayla alır ve her değer için gövdeyi bir kez çalıştırır.range()bir liste değil, aralıktaki değerleri sırayla veren ayrı birrangenesnesidir; değerleri görmek içinlist(range(...))kullanılabilir.range()bitiş değerini, adım pozitif de olsa negatif de olsa dâhil etmez.range(baslangic, son, adim)ile başlangıç ve ilerleme miktarı belirlenebilir.- Sayaç bir şeyin kaç kez olduğunu, biriktirici ise toplam gibi biriken sonucu tutar.
- Koşulu sağlayan değerleri saymak için sayaç yalnızca
ifdoğru olduğunda artırılabilir. breakaranan sonuç bulunduğunda ya da geçerli girdi alındığında döngüyü tamamen bitirebilir.while True+breakile geçerli girdi gelene kadar sorma kalıbındainput()satırını iki kez yazmak gerekmez.continueyalnızca o anki yinelemenin kalanını atlar; özellikle geçersiz veriyi birden fazla kontrolle ayıklarken işe yarar.- Tekrar sayısı baştan biliniyorsa
for, koşula bağlı belirsiz tekrar içinwhileçoğu zaman daha açıklayıcıdır. - Özel bitirme değeri (
sentinel) veri girişinin sona erdiğini bildirmek için kullanılabilir; boş ya da sayıya çevrilemeyen girdi ise ayrıca kontrol edilmelidir. - İç içe döngü, problemde gerçekten iki ayrı tekrar olduğunda kullanılmalıdır.
Bir sonraki bölümde tekrar eden kod parçalarını fonksiyonlara ayıracak; parametreler ve dönüş değerleriyle problemi daha küçük parçalara böleceğiz.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
range()nesnesinin ürettiği değerleri belirler ve listeden farkını açıklar.range()çağrısının farklı biçimlerini kullanır ve bitiş değerinin dışarıda kaldığını hesaba katar.- Sayma ve biriktirme kalıplarını
forile kurar. - Arama kalıbında
breakile döngüyü erken bitirir. continueile yalnızca o yinelemenin kalanını atlar.while Truevebreakile geçerli girdi gelene kadar soran döngüyü yazar ve bu kalıbı sentinel kullanımından ayırt eder.- Problemin yapısına göre
forilewhilearasında seçim yapar.