Birden çok değeri saklamak, dolaşmak ve doğru koleksiyonu seçmek
Bir derste her hafta kısa sınav yapılıyor. Bir öğrencinin ilk üç notu 70, 85 ve 90. Sınavlar eşit ağırlıklı olduğu için ortalamayı, notların toplamını sınav sayısına bölerek bulabiliyoruz. Dönem boyunca yeni sınavlar da yapılacak. Notları nasıl saklamalıyız?
Koleksiyon, birden çok değeri bir arada tutan yapılara verilen genel addır. Önceki derslerde split() sonucunda bir liste elde etmiş, *args ile gelen değerlerin bir demette toplandığını görmüştük. Şimdi not ortalamasını önce ayrı değişkenlerle, sonra bir koleksiyonla hesaplayalım.
İlk üç notu ayrı değişkenlerde tutarsak ortalamayı şöyle hesaplarız. Dördüncü sınav yapıldığında ise hem yeni bir değişken tanımlamamız hem de hesabı değiştirmemiz gerekir:
Notları bir listede topladığımızda aynı hesabı sınav sayısını ayrıca yazmadan yapabiliriz. sum() notları toplar, len() listedeki notları sayar. Yeni bir not eklendiğinde ortalama formülü değişmez.
Henüz hiç not girilmemişse liste boştur. Bu durumda sıfıra bölme hatası oluşacağından ortalamayı hesaplamadan önce listenin boş olup olmadığını kontrol etmeliyiz.
for, enumerate(), zip() ve list comprehension ile liste verilerini işleyeceğiz.Liste, elemanların sırasını koruyan ve sonradan değiştirilebilen bir koleksiyondur. Örneğimizdeki üç notu tek bir listede, verildikleri sırayla tutabiliriz:
Her kutu bir elemanı gösterir; hepsi notlar adı altında tutulur. Listeyi köşeli parantezle ([]) yazarız. Python farklı türden değerleri aynı listeye koymamıza izin verse de elemanlar aynı amaca hizmet ettiğinde listeyi anlamak kolaylaşır.
Bir elemanın yerini indeks ile belirtiriz. Soldan sayarken ilk indeks 0’dır. Sağdan saydığımızda ise son elemanın indeksi -1 olur.
Bu listede 0 ilk notu, -1 son notu gösterir. Üç eleman bulunduğu için 3 geçerli bir indeks değildir.
Şimdi aynı listeyi Python’da oluşturalım. Stringlerdeki gibi köşeli parantez içinde indeks yazarak bir elemana erişebiliriz. Boş listeyi de [] ile oluştururuz.
notlar[3] okumaya çalışmak IndexError üretir; son geçerli pozitif indeks 2’dir.
Bir listenin yalnızca bir bölümüne ihtiyacımız varsa dilimleme kullanırız. notlar[1:4], indeks 1’den başlar ve 4’e gelmeden durur.
Dilimlemenin genel yazımı liste[başlangıç:bitiş:adım] biçimindedir. Adım 2 olursa her iki elemandan biri seçilir; -1 ise listeyi tersten okur. Her dilim kaynak listeyi değiştirmeden yeni bir liste oluşturur.
Bir stringin belirli bir karakterine doğrudan atama yapamıyorduk. Listede ise bir konuma yeni değer atayabiliriz:
Bu işlem yeni bir liste oluşturmaz; meyveler listesinin içeriğini değiştirir. Birazdan aynı listeye iki farklı değişken adıyla eriştiğimizde bu ayrımın neden önemli olduğunu göreceğiz.
Liste işlemlerini seçerken önce şunu sorarız: Elimizdeki liste değişebilir mi, yoksa ilk hâlini korumalı mıyız? Yöntemlerin önemli farkı budur.
| İşlem | Kaynak liste | Dönen değer |
|---|---|---|
append, extend, insert, remove, sort, reverse |
Değişir | None |
pop |
Değişir | Çıkarılan eleman |
+, sorted(), dilimleme |
Değişmez | Yeni liste |
İlk iki satırdaki işlemler mevcut liste üzerinde çalışır. Kaynağı korumamız gereken durumlarda son satırdaki işlemleri kullanırız.
append ve extendappend(x) verilen değeri listenin sonuna tek eleman olarak ekler. extend(koleksiyon) ise koleksiyondaki elemanları sırayla ekler. İki işlem de mevcut listeyi değiştirir.
appendVerilen listeyi tek eleman olarak ekler.extendVerilen listenin elemanlarını ayrı ayrı ekler.append ve extend ile EklemeŞemadaki iki sonucu kodla karşılaştıralım:
İlk örnekte ["armut", "muz"] listenin içinde tek bir eleman hâline geldi. Meyvelerin ayrı elemanlar olması isteniyorsa ikinci örnekteki gibi extend kullanılmalıdır.
+Eski katılımcı listesi bir raporda kullanılacaksa onu değiştirmek istemeyebiliriz. + iki listenin elemanlarından yeni bir liste oluşturur; extend ise üzerinde çağrıldığı listeyi değiştirir.
eski hâlâ ["Ada", "Deniz"].+ ile Birleştirmebirlesik yeni listedir; eski ise raporda kullanılabilecek ilk hâlini korur:
insertGörev listesine diğerlerinden önce yapılması gereken yeni bir iş ekleyelim. insert(indeks, değer) elemanı seçilen konuma yerleştirir ve sonraki elemanları sağa kaydırır. Sona ekleme yapacaksak append daha açık bir tercihtir.
insert(0, "Verileri kontrol et") ↓insert ile EklemeYeni görevi listenin başına yerleştirelim:
remove ve popSilinecek elemanın değerini biliyorsak remove(değer) kullanabiliriz; aynı değer birden çok kez geçiyorsa yalnızca ilk eşleşme silinir. Konumunu biliyorsak pop(indeks) kullanırız. pop silinen elemanı döndürür; indeks verilmezse son elemanı çıkarır.
remove("kalem")↓ ilk eşleşme silinirpop(1)↓ 1. indeksteki eleman çıkarılır"kalem"remove ve pop ile Silmeİki işlem de mevcut listeyi değiştirir; fakat yalnızca pop çıkarılan elemanı bize geri verir:
Aradığımız değer listede yoksa remove, ValueError üretir. pop ise boş listede veya geçersiz bir indekste IndexError üretir. Bu yüzden silme işlemini seçerken hem değerin varlığını hem de konumu düşünmeliyiz.
Bir notun listede bulunup bulunmadığını in ile sorgulayabiliriz. count() o değerin kaç kez geçtiğini, index() ise ilk görüldüğü konumu verir. index() bulunmayan değerde ValueError ürettiği için, belirsiz durumlarda önce in ile kontrol etmek gerekir.
sort() listeyi artan düzende sıralar. reverse() ise elemanları mevcut sıralarının tersine dizer; değerleri küçükten büyüğe sıralamaz. İki metot da asıl listeyi değiştirir ve None döndürür.
Sıralı sonucu ayrı bir değişkende saklamak için sirali = puanlar.sort() yazamayız. Bu atamadan sonra sirali değişkeni None olur; sıralanan liste puanlar’dır.
sorted ve DilimNotların sisteme giriliş sırası önemliyse rapor için onları yerinde sıralamak doğru olmaz. sorted(koleksiyon) sıralı yeni bir liste, [::-1] ise mevcut sıranın tersini içeren yeni bir liste üretir. Her iki durumda da kaynak liste olduğu gibi kalır.
Bir listenin birden fazla adı olabilir. ikinci = ilk ataması listeyi çoğaltmaz; iki adı aynı nesneye bağlar. Önceki derste gördüğümüz değiştirilebilir varsayılan parametre tuzağı da aynı paylaşma davranışından kaynaklanıyordu.
ikinci üzerinden yapılan ekleme ilk üzerinden de görülecek mi? Kodu çalıştırmadan önce tahmin edin.
İki değişken de aynı listeyi gösterdiğinden eklenen eleman her iki ad üzerinden görünür. is işleci de bu iki adın aynı nesneye bağlı olduğunu doğrular.
== ve isİki listenin aynı elemanları taşıması, aynı liste oldukları anlamına gelmez. == içerikleri karşılaştırır; is ise iki ifadenin aynı nesneyi gösterip göstermediğini sorar.
Değer eşitliğini sınarken == kullanırız. is, örneğin bir listenin yanlışlıkla iki değişken arasında paylaşılıp paylaşılmadığını anlamak için yararlıdır.
İki bağımsız listeye ihtiyacımız varsa copy() kullanabiliriz. [:] dilimi de aynı işi yapar. Yeni listenin dış yapısına ekleme veya ondan silme, kaynak listeye yansımaz.
Atamada ikinci bir ad oluştururuz; kopyalamada ise aynı elemanları taşıyan ikinci bir liste oluştururuz. Aşağıdaki şemada okların nereye yöneldiğine bakın.
Atama ve kopyalama arasındaki fark
Öğrenci kayıtlarını iç içe listelerde tuttuğumuzu düşünelim. yeni için bir kopya aldıktan sonra notu değiştiriyoruz. Eski kayıt da değişir mi? Kodu çalıştırmadan önce tahmin edin.
copy() yalnızca dış listeyi kopyalar; yeni[0] ve kayitlar[0] aynı öğrenci kaydını gösterir. Bu nedenle notu yeni üzerinden değiştirmek kayitlar içindeki notu da değiştirir. Kopyanın tam bir yedek olması gerekiyorsa iç listelerin de ayrıca kopyalanması gerekir.
Bir listeyi fonksiyona verdiğimizde parametre de o listeyi gösterebilir. Aşağıdaki fonksiyon append kullandığı için çağıranın listesi değişir. Bu tür bir yan etki, fonksiyonun adından ve açıklamasından anlaşılmalıdır.
Bir listedeki bütün elemanlara aynı işlemi uygulayacaksak for döngüsü kullanırız. Çoğu durumda elemanın kendisi yeterlidir; konumu da gerekiyorsa enumerate() bize ikisini birlikte verir.
Görevleri yalnızca yazdıracaksak indeks tutmamıza gerek yoktur:
enumerateNumaralı bir görev listesi yazdırırken sıra numarası da gerekir. enumerate() her turda bir numara ve ilgili elemanı verir. start=1 yazdırdığımız numaraları birden başlatır; listenin kendi indeksleri yine sıfırdan başlar.
zipÖğrenci adları ve puanları iki ayrı listede aynı sırayla tutuluyorsa zip() karşılıklı elemanları birlikte okumamızı sağlar. Bu yaklaşımda listelerin aynı uzunlukta olduğunu varsayıyoruz.
zip() kısa olan liste bittiğinde durur. Bir öğrencinin puanının sessizce atlanması kabul edilemezse döngüden önce liste uzunluklarının eşitliğini kontrol etmeliyiz.
zip() EşleşmeleriBu kez Ece’nin puanı eksik. Sunumda her tıklamada bir konumun sonucu açılır. Son konuma gelindiğinde zip()’in neden durduğuna bakın.
İlk döngü, kontrolsüz zip() çağrısının Ece’yi sessizce atladığını gösterir. İkinci bölümde uzunlukları veriyi işlemeden önce kontrol ediyoruz.
Bir sayı listesinin karelerini ayrıca saklamak istediğimizi düşünelim. Önce yeni listeyi açık bir döngüyle oluşturacağız; ardından aynı işlemi list comprehension ile yazacağız.
Her sayının karesini hesaplayıp ayrı bir listeye ekleyelim:
[ifade for eleman in koleksiyon] yazımında soldaki ifade her eleman için hesaplanır ve sonuçlar yeni bir listede toplanır. Önceki döngüyle aynı sonucu verir; sayilar listesi değişmez.
Yeni listeye yalnızca belirli koşulu sağlayan elemanları da alabiliriz. Örneğin geçme sınırını 50 kabul edersek, sondaki if hangi puanların seçileceğini belirler.
Comprehension, kısa ve açık bir dönüşüm için uygundur. Koşullar çoğaldığında veya işlem birkaç adımdan oluştuğunda açık for döngüsü daha okunur olabilir.
Demet (tuple), liste gibi sıralı bir koleksiyondur; ancak elemanlarını sonradan ekleyip çıkaramayız ve konumlarına yeni değer atayamayız. Bu özellik, birbiriyle ilişkili ve konumlarının anlamı sabit olan değerleri taşırken işe yarar.
Bir konumu enlem ve boylamıyla ifade ederiz. İki değerin sırası anlamlıdır: önce enlem, sonra boylam gelir. Parantez, bu ikiliyi demet olarak okumayı kolaylaştırır.
konum[0:1] tek değer içerse de sonucu bir demettir. Çıktıdaki virgülün görevine birazdan döneceğiz.
Bir demetteki değerleri ayrı değişkenlere tek atamada dağıtabiliriz. Buna açma denir. Tersi yönde, virgülle ayrılmış değerler bir demette paketlenir; önceki dersteki *args parametresi de aynı fikri kullanıyordu.
(42), parantez içine alınmış bir tam sayıdır; demet değildir. Tek elemanlı demeti belirleyen işaret virgüldür: (42,).
Bir konumun enlemini konum[0] = 40.0 yazarak değiştirmeye çalışırsak TypeError alırız. Demetin elemanlarına atama yapılamaz; ekleme ve silme metotları da yoktur.
Hata mesajı demetin elemanına atama yapılamadığını söyler. Değiştirilemezlik, demetten değer okuyamayacağımız anlamına gelmez. İndeksleme, dilimleme ve in kontrolü demette de çalışır.
count() bir değerin kaç kez geçtiğini, index() ise ilk geçtiği konumu verir. Bu metotlar demeti değiştirmez.
index() ile aranan değer demette yoksa ValueError oluşur. Demette append() ve sort() gibi değiştirici metotlar bulunmaz.
Demetin ikinci elemanı bir liste. Aşağıdaki append() çağrısı hata verir mi? Kodu çalıştırmadan önce tahmin edin.
kayit[1] hâlâ aynı not listesini gösterir; değişen listenin içeriğidir. Bu nedenle “demet değiştirilemez” ifadesini, “demetin içindeki bütün nesneler değiştirilemez” diye yorumlamamalıyız.
| Soru | Tercih |
|---|---|
| Eleman ekleyip çıkaracak veya sıralayacak mıyız? | Liste |
| Sabit konumları olan bir kayıt mı taşıyoruz? | Demet |
| Fonksiyondan birden fazla değer mi döndürüyoruz? | Çoğunlukla demet |
sorted(demet) kullanırsak sonuç demet değil, yeni bir liste olur. Liste ve demet arasında seçim yaparken parantezleri değil, verinin kullanımını düşünmeliyiz.
Her alıştırmada önce hangi verinin değişebileceğine karar verin. Kodu tamamladıktan sonra yalnızca beklenen sonucu değil, kaynak listenin durumunu da kontrol edin.
Bir öğretmen notları güncellemeden önce eski hâllerini saklamak istiyor. Ancak aşağıdaki programda yedek de değişiyor. Yalnızca bir satırı düzelterek yedek listesinin güncellemeden etkilenmemesini sağlayın.
Çözüm
yedek = notlar.copy() — güncellemeden önce ayrı bir liste oluşturulur; notlar.append(90) artık yalnızca notlar’ı değiştirir.yedek = notlar yazımı kopya almıyordu: iki adı aynı listeye bağlıyordu. print(yedek is notlar) ile farkı görebilirsiniz.Öğrencilerin notları sisteme girildiği sırayla duruyor. 50 ve üzerindeki notlardan yeni bir liste üretin; asıl listeyi değiştirmeyin. Tam 50 alan öğrencinin sonucunu özellikle kontrol edin.
Çözüm
puan >= 50 — sınır değeri dâhil; > 50 yazılsaydı tam sınırdaki 50 elenirdi.puanlar üzerinde dolaşır ama onu değiştirmez.Adlar ve puanlar aynı sırayla kaydedilmiş. zip() ile eşleşen çiftleri dolaşın; notu 50 veya daha yüksek olan öğrencilerin adlarını gecen_adlar listesine ekleyin.
Çözüm
for ad, puan in zip(adlar, puanlar) — her turda karşılıklı bir ad ve bir puan gelir; ikisi aynı satırda açılır.if puan >= 50 — yalnızca geçenlerin adı eklenir; Deniz 49 ile dışarıda kalır.zip() kısa olan bitince sessizce durur; eksik kayıt kaçmasın diye önce len(adlar) == len(puanlar) kontrolü yapılmalıdır.Bir öğrencinin kısa sınav sonuçları dönem boyunca kaydediliyor. Her sonuç bir hafta numarası ve puan çiftinden oluşuyor; yeni haftalar geldikçe kayıt sayısı artıyor. Bütün sonuçları tutan dış yapı ve tek bir (hafta, puan) kaydı için liste mi, demet mi seçersiniz? İlk iki haftayı içeren örnek bir değer yazın ve seçiminizi açıklayın.
Çözüm
(hafta, puan) çiftinin alanları sabittir ve sonradan değişmez; konumun anlamı belli olan bir kayıt için demet uygundur.Kodları çalıştırmadan önce çıktıları tahmin edin. Ardından cevabınızı ve gerekçenizi karşılaştırın; yanlış şıkların hangi kavram karışıklığından kaynaklandığına dikkat edin.
[1, 2, 3, 4][1, 2, [3, 4]][3, 4]None['a', 'b']['x', 'b']['x']None[60, 80, 90] [80, 60, 90]None [60, 80, 90][60, 80, 90] [60, 80, 90]None [80, 60, 90]True TrueTrue FalseFalse TrueFalse Falsetuple tupleint intint tupletuple int[1, 4, 9, 16][1, 3][1, 9][4, 16][]('Ada', [70, 80])('Ada', [70, 80, 90])('Ada', [70, 80], 90)['Ada', [70, 80, 90]]TypeError oluşurÖğrenciler (ad, puan) demetleri olarak saklanıyor. gecen_adlar() fonksiyonunu yazın: geçme eşiğine ulaşan öğrencilerin adlarını alfabetik sıralı yeni bir liste olarak döndürsün. Kaynak kayıtlar değişmemeli; boş liste de geçerli bir girdi olmalı.
append bir değeri, extend bir koleksiyonun elemanlarını ekler. remove değerle siler; pop konumla silip elemanı döndürür.sort listeyi değiştirip None döndürür. sorted, + ve dilimleme yeni liste oluşturur.b = a iki adı aynı listeye bağlar. copy() dış listeyi ayırır; iç nesneler ortak kalabilir.== içerikleri karşılaştırır; is aynı nesneye bakılıp bakılmadığını gösterir.for, sıra numarasıyla değer için enumerate, karşılıklı listeler için zip kullanılır.[ifade for x in veri if koşul], seçilen değerlerden yeni bir liste oluşturur.(x,) biçiminde yazılır; count ve index arama için kullanılabilir.İleri Programlama – OMÜ | 🏠