Algoritmada off-by-one hatası, döngünün başlangıç veya bitiş sınırı bir konum yanlış seçildiğinde ortaya çıkar. Bu durumda ilk ya da son eleman atlanabilir, fazladan bir eleman işlenebilir veya geçersiz bir indekse erişilmeye çalışılabilir.
Sınırı doğru kurmak için önce hangi indekslerin kapsama girdiğini yazmak, ardından ilk ve son elemanı ayrı ayrı kontrol etmek gerekir. Boş veri durumu da ayrıca test edilmelidir; çünkü uzunluğu sıfır olan bir liste, sınır varsayımlarını hızlıca görünür hâle getirir.
Off-by-One Hatası Bir Elemanlık Sınır Yanılgısıdır
Off-by-one, Türkçede “bir eksik veya bir fazla” sınır hatası olarak düşünülebilir. Hatanın kaynağı çoğu zaman işlemin kaç kez çalışacağı ile hangi elemanların işleneceğinin birbirine karıştırılmasıdır. Örneğin beş elemanlı bir listenin tümünü gezmek isteyen bir algoritma, yalnızca dört eleman üzerinde çalışırsa son eleman atlanır. Altıncı konuma erişmeye çalışırsa bu kez liste sınırı aşılır.
Bu hata yalnızca döngülerde görülmez. Dizi veya liste erişiminde, bir arama algoritmasının başlangıç ve bitiş değerlerinde, metin dilimlemede ya da iki parça arasında sınır belirlerken de oluşabilir. Ortak soru şudur: “Bu işlem hangi konumları kapsıyor?” Bu soruya sayı doğrusu veya indeks aralığıyla cevap vermeden yazılan koşullar hata üretmeye daha açıktır.
Örneğin aşağıdaki kod, dört elemanlı bir listenin geçerli indekslerini yazdırır:
notlar = [70, 85, 90, 100]
for i in range(len(notlar)):
print(i, notlar[i])
Beklenen çıktı şöyledir:
0 70
1 85
2 90
3 100
Burada son yazdırılan indeks 3’tür, çünkü listenin uzunluğu 4’tür. Döngüde sınırı belirlerken yalnızca uzunluk değerine bakmak yerine, erişilecek son konumu da kontrol etmek gerekir. Özellikle sınav sorularında ve proje kodlarında şu üç kontrol yararlıdır:
- İlk eleman işleme dâhil mi?
- Son eleman işleme dâhil mi?
- Veri boşsa algoritma geçerli bir erişim yapmadan çalışabiliyor mu?
0’dan Başlayan İndekslerde Son Geçerli Konum Neden n-1’dir?
Uzunluğu n olan bir liste 0’dan indeksleniyorsa ilk elemanın indeksi 0’dır. Toplam n konum bulunduğuna göre indeksler sırasıyla 0, 1, 2 ve devamında n-1 olur. n değeri bir elemanın indeksi değil, eleman sayısıdır. Bu nedenle liste[n] ifadesi, n elemanlı bir listede sınırın dışına çıkar.
Döngü koşulunu kurarken aralığın türü belirleyicidir. Kapalı aralıkta [başlangıç, bitiş] hem başlangıç hem bitiş dâhildir. Kapalı-açık aralıkta [başlangıç, bitiş) başlangıç dâhil, bitiş hariçtir. Bu ayrım küçük görünse de koşulu doğrudan değiştirir: Kapalı aralıkta bitişe kadar ilerlenir, kapalı-açık aralıkta ise bitişe ulaşınca durulur.
| İndeks aralığı | Geçerli indeksler | Döngü koşulu | Tipik hata |
|---|---|---|---|
| Boş liste, n = 0 | Yok | Başlangıçta hiçbir indeks geçerli olmamalıdır | 0. elemana erişmeye çalışma |
| [0, n) | 0, 1, ..., n-1 | i < n | i <= n yazarak n. indekse erişme |
| [0, n-1] | 0, 1, ..., n-1 | i <= n-1 | Son indeksi dışarıda bırakacak koşul kurma |
| [k, n) | k, k+1, ..., n-1 | i < n | Başlangıçtaki k elemanı yanlışlıkla işleme alma |
İki koşul aynı aralığı temsil edebilir: i < n ve i <= n-1. Ancak kapalı-açık aralık mantığında i < n daha doğrudan okunur; üst sınır eleman sayısını, erişilen son indeks ise n-1 değerini ifade eder.
Hatalı ve Doğru Döngü Koşulunu Kod Üzerinde Karşılaştırma
Başlangıç veya bitiş sınırı yanlış seçildiğinde bir eleman hiç işlenmeyebilir ya da fazladan işlenebilir. Aşağıdaki örnekte amaç, listedeki tüm sayıları toplamaktır. Hatalı döngüde range(len(sayilar)-1) kullanıldığı için son geçerli indeksin bulunduğu tur çalışmaz.
def hatali_toplam(sayilar):
toplam = 0
for i in range(len(sayilar) - 1):
toplam += sayilar[i]
return toplam
def dogru_toplam(sayilar):
toplam = 0
for i in range(len(sayilar)):
toplam += sayilar[i]
return toplam
testler = [[7], [4, 6, 9], []]
for sayilar in testler:
print("Liste:", sayilar)
print("Hatalı toplam:", hatali_toplam(sayilar))
print("Doğru toplam:", dogru_toplam(sayilar))
print()
Beklenen çıktı şöyledir:
Liste: [7]
Hatalı toplam: 0
Doğru toplam: 7
Liste: [4, 6, 9]
Hatalı toplam: 10
Doğru toplam: 19
Liste: []
Hatalı toplam: 0
Doğru toplam: 0
Tek elemanlı listede hatalı döngü hiç başlamaz, çünkü range(0) boş bir aralıktır. Birden çok elemanlı listede ise son değer, örnekte 9, toplama eklenmez. Boş listede her iki döngünün de güvenli biçimde çalışması, sınır kontrolünün yalnız dolu veri üzerinde değil boş veri üzerinde de değerlendirilmesi gerektiğini gösterir.
İndeks gerektiğinde doğru sınır range(len(sayilar)) olur. İndekse ihtiyaç yoksa for sayi in sayilar biçiminde doğrudan eleman üzerinde dolaşmak, sınır hesaplamasını azaltabilir. Buradaki asıl ilke Python sözdizimi değil, işlenecek aralığı açıkça tanımlamaktır.
Döngü Sınırını Belirlerken Önce Aralığı Yazın
Sınav sorusunda veya proje kodunda döngüyü yazmadan önce hangi konumların işleneceğini kısa bir cümleyle belirtin. Örneğin, “ilk elemandan son elemana kadar tüm konumlar” ifadesi, son elemanın da kapsama dahil olduğunu açıkça ortaya koyar. Ardından döngü koşulunun bu aralığı gerçekten üretip üretmediğini kontrol edin.
- İşlenecek ilk konumu belirleyin.
- Son konumun dahil olup olmadığına karar verin.
- Kullandığınız döngü yapısının bitiş değerini içerip içermediğini denetleyin.
- İlk eleman, son eleman ve boş veri durumlarını ayrı ayrı izleyin.
| Deneme durumu | Kontrol edilecek nokta | Beklenen sonuç |
|---|---|---|
| İlk eleman | Döngü başlangıcı | İlk konum işlenir. |
| Son eleman | Bitiş sınırı | Son konum atlanmaz. |
| Boş veri | Döngünün başlangıç koşulu | Güvenli biçimde sıfır tur çalışır. |
Bu küçük iz sürme, özellikle < ile <= arasındaki seçimin sonucu değiştirdiği algoritmalarda yararlıdır. Dil bağımsız biçimde aralık kurma ve sınır durumlarını değerlendirme pratiği için ücretsiz Algoritmik Düşünme Testi de kullanılabilir.
Sınır Hatalarını Önlemek İçin Son Kontrol Listesi
Başlangıç veya bitiş sınırı yanlış seçildiğinde bir eleman hiç işlenmeyebilir ya da döngü geçersiz bir konuma ulaşmaya çalışabilir. Bu nedenle döngüyü yazmadan önce hangi elemanların kapsanacağını, döngüden sonra da hangi sınır durumlarının denendiğini açıkça kontrol etmek gerekir.
- Geçerli indeks aralığını yaz: Liste uzunluğu
nise sıfırdan başlayan indekslerde geçerli konumlar0ilen - 1arasındadır. Döngünün hangi indekste başlayıp biteceğini bu aralığa göre belirle. - Bitiş değerinin dahil olup olmadığını doğrula: Kullandığın koşulun veya aralık yapısının üst sınırı kapsayıp kapsamadığını kontrol et. Özellikle “son eleman da işlendi mi?” sorusunu koddan değil, yazdığın aralıktan yanıtla.
- Boş, tek elemanlı ve çok elemanlı veriyle test et: Bu üç durum, başlangıç ve bitiş koşullarındaki hataları görünür kılar. Kod yalnızca normal uzunluktaki listede doğru çalışıyorsa sınır kontrolü tamamlanmış sayılmaz.
Aşağıdaki kısa testte range(len(liste)), her liste için yalnızca geçerli indeksleri üretir:
for liste in ([], [10], [10, 20, 30]):
sonuc = [liste[i] for i in range(len(liste))]
print(sonuc)
Beklenen çıktı şöyledir:
[]
[10]
[10, 20, 30]
Boş listede döngü hiç çalışmaz, tek elemanlı listede yalnızca 0. indeks kullanılır, üç elemanlı listede ise son geçerli indeks olan 2 de kapsanır. Döngü sınırları ve algoritma pratiği için kişiye özel bir çalışma düzenini değerlendirmek isteyenler, online yazılım eğitimi programları içindeki birebir seçenekleri inceleyebilir.
Sık Sorulan Sorular
Off-by-one hatası neden özellikle son elemanda görülür?
Son elemanın indeksi, listenin uzunluğundan bir eksiktir. Döngü üst sınırı uzunluğa eşit olduğunda son geçerli indeksten sonra bir konuma geçilebilir. Üst sınır erken biterse bu kez son eleman atlanır.
range(len(liste)) neden son geçerli indeksi aşmaz?
range(len(liste)), sıfırdan başlayıp liste uzunluğundan küçük değerlere kadar ilerler. Liste uzunluğu n olduğunda üretilen son değer n - 1 olur. Bu da son geçerli indekstir.
Boş liste döngü sınırı testinde neden ayrı ele alınmalıdır?
Boş listede geçerli hiçbir indeks yoktur. Kodun ilk elemana, son elemana veya sabit bir indekse doğrudan erişmeye çalışıp çalışmadığı bu durumda anlaşılır. Böylece döngünün hiç çalışmaması gereken senaryolar da kontrol edilmiş olur.
Sınırı önce aralık olarak yazmak, ardından küçük veri kümeleriyle denemek, hem sınav sorularında hem projelerde bir elemanlık hataları daha erken fark etmeyi kolaylaştırır.