Listeler ve Demetler

Birden çok değeri saklamak, dolaşmak ve doğru koleksiyonu seçmek

Dr. Öğr. Üyesi Oğuz Emre Kural

Birden Çok Notu Nasıl Tutarız?

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.

Tek Tek Değişkenler

İ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:

Aynı İşi Listeyle Yaparsak

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.

Bu Bölümde

  • Listeleri oluşturacak, değiştirecek ve onlardan yeni listeler üreteceğiz.
  • Atama, eşitlik ve kopyalamanın farklı sonuçlarını örneklerle inceleyeceğiz.
  • for, enumerate(), zip() ve list comprehension ile liste verilerini işleyeceğiz.
  • Liste ve demet arasında kullanım amacına göre seçim yapacağız.

Listeler

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.

Listedeki Konumlar

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.

Oluşturma ve İndeksleme

Ş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.

Dilimleme ile Aralık Seçmek

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.

Dilimleme

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.

Liste Değiştirilebilir

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.

Listeyle Günlük İşlemler

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.

Sona Ekleme: append ve extend

append(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.

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.

Yeni Listeyle Birleştirme: +

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.

+ ile Birleştirme

birlesik yeni listedir; eski ise raporda kullanılabilecek ilk hâlini korur:

Araya Ekleme: insert

Gö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 ile Ekleme

Yeni görevi listenin başına yerleştirelim:

Silme: remove ve pop

Silinecek 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 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.

Bulma ve Sayma

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.

Yerinde Sıralama ve Ters Çevirme

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.

Kaynağı Korumak: sorted ve Dilim

Notları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.

Aynı Listeyi Paylaşmak

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.

Atama Kopya Değildir

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.

Eşit Değer, Ayrı Nesne: == 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.

Bağımsız Liste Oluşturma

İ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.

İki Adın Gösterdiği Nesneler

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.

Solda ilk ve ikinci adları aynı listeye yöneliyor. Sağda ikinci, ilk listesinin ayrı bir kopyasına yöneliyor.

Atama ve kopyalama arasındaki fark

İç İçe Listelerde Kopya

Öğ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.

Fonksiyona Liste Vermek

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.

Listelerde Dolaşma

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.

Doğrudan Değerler

Görevleri yalnızca yazdıracaksak indeks tutmamıza gerek yoktur:

Hem İndeks Hem Değer: enumerate

Numaralı 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.

İki Koleksiyonu Birlikte: 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şmeleri

Bu 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.

adlarAdaDenizEce
puanlar8590puan yok
zip sonucu("Ada", 85)("Deniz", 90)çift oluşmaz

Eksik Kaydı Yakalamak

İ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.

List Comprehension

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.

Önce Açık Döngü

Her sayının karesini hesaplayıp ayrı bir listeye ekleyelim:

Aynı İşin Kısa Yazımı

[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.

Koşula Göre Seçme

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.

Demetler

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.

Sabit Bir Kayıt

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.

Paketleme ve Açma

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.

Bir Elemanlı Demet: Virgül Önemli

(42), parantez içine alınmış bir tam sayıdır; demet değildir. Tek elemanlı demeti belirleyen işaret virgüldür: (42,).

Demet Değiştirilemez

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.

Demette Bulma ve Sayma

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.

İçindeki Nesne Değişebilir

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.

Liste mi, Demet mi?

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.

Alıştırmalar

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.

Alıştırma 1: Yedeği Koru

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.

Alıştırma 2: Geçen Notlar

Öğ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.
  • Comprehension yeni bir liste üretir, puanlar üzerinde dolaşır ama onu değiştirmez.

Alıştırma 3: Adlar ve Puanlar

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.
  • Bu çözüm iki listenin aynı uzunlukta olduğunu varsayar. Uzunluklar farklıysa zip() kısa olan bitince sessizce durur; eksik kayıt kaçmasın diye önce len(adlar) == len(puanlar) kontrolü yapılmalıdır.

Alıştırma 4: Liste mi, Demet mi?

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

  • Dışta liste — yeni haftalar geldikçe kayıt eklenecek; ekleyip çıkarabileceğimiz yapı listedir.
  • İçte demet — (hafta, puan) çiftinin alanları sabittir ve sonradan değişmez; konumun anlamı belli olan bir kayıt için demet uygundur.

Kendini Sına

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.

Soru 1

sayilar = [1, 2]
sayilar.append([3, 4])
print(sayilar)
  • A. [1, 2, 3, 4]
  • B. [1, 2, [3, 4]]
  • C. [3, 4]
  • D. None
  • E. Hata verir

Soru 2

ilk = ["a", "b"]
ikinci = ilk
ikinci[0] = "x"
print(ilk)
  • A. ['a', 'b']
  • B. ['x', 'b']
  • C. ['x']
  • D. None
  • E. Hata verir

Soru 3

puanlar = [80, 60, 90]
sonuc = puanlar.sort()
print(sonuc, puanlar)
  • A. [60, 80, 90] [80, 60, 90]
  • B. None [60, 80, 90]
  • C. [60, 80, 90] [60, 80, 90]
  • D. None [80, 60, 90]
  • E. Hata verir

Soru 4

a = [70, 85]
b = [70, 85]
print(a == b, a is b)
  • A. True True
  • B. True False
  • C. False True
  • D. False False
  • E. Hata verir

Soru 5

tek = (42)
demet = (42,)
print(type(tek).__name__, type(demet).__name__)
  • A. tuple tuple
  • B. int int
  • C. int tuple
  • D. tuple int
  • E. Hata verir

Soru 6

sayilar = [1, 2, 3, 4]
print([n * n for n in sayilar if n % 2 == 1])
  • A. [1, 4, 9, 16]
  • B. [1, 3]
  • C. [1, 9]
  • D. [4, 16]
  • E. []

Soru 7

kayit = ("Ada", [70, 80])
kayit[1].append(90)
print(kayit)
  • A. ('Ada', [70, 80])
  • B. ('Ada', [70, 80, 90])
  • C. ('Ada', [70, 80], 90)
  • D. ['Ada', [70, 80, 90]]
  • E. TypeError oluşur

Kod Yazma: Not Raporu

Öğ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ı.

Özet ve Kazanımlar

Tek Sayfa Özet

  • Liste: Elemanların sırasını korur ve değiştirilebilir. İndeksleme ve dilimleme kuralları stringlerle aynıdır.
  • Ekleme ve silme: append bir değeri, extend bir koleksiyonun elemanlarını ekler. remove değerle siler; pop konumla silip elemanı döndürür.
  • Yerinde değişiklik ve yeni sonuç: sort listeyi değiştirip None döndürür. sorted, + ve dilimleme yeni liste oluşturur.
  • Referans ve kopya: b = a iki adı aynı listeye bağlar. copy() dış listeyi ayırır; iç nesneler ortak kalabilir.
  • Eşitlik ve kimlik: == içerikleri karşılaştırır; is aynı nesneye bakılıp bakılmadığını gösterir.
  • Dolaşma: Yalnız değer için for, sıra numarasıyla değer için enumerate, karşılıklı listeler için zip kullanılır.
  • Yeni liste üretme: [ifade for x in veri if koşul], seçilen değerlerden yeni bir liste oluşturur.
  • Demet: Sıralıdır; elemanlarına atama yapılamaz. Tek elemanlı demet (x,) biçiminde yazılır; count ve index arama için kullanılabilir.