Çember şeklindeki listelerde başa dönme sorunu

Çember şeklinde dizilmiş 41 kişi var. İlk kişi solundakini öldürüp elindeki kılıcı soldaki yaşayan kişiye veriyor. En son canlı kalan kim olur.

Bu soruyu programlamaya çalıştım ama hep bi noktada liste index hatası alıyorum. Kod aşağıda. Aslında çok basit bir kod olması lazım ama nedense yapamadım.

kisiler = [i for i in range(1,42)]
kisi = 0
while len(kisiler)>1:
    if kisiler[kisi]<kisiler[-1]:
        kisi = kisi +1
    else:
        kisi = 0
    kisiler.pop(kisi)
    print(kisiler)

Selamlar,

Oncelikle:

Burada yapilmak istenen nedir?

kisi indisiyle ilgili bir operasyon olmasi lazimken indisteki kisileri karsilastiriyoruz. Bir kisinin bir digerinden kucuk olmasi ne demek?

Baska bir deyisle: Kisiler sayi degil de “ahmet”, “mehmet” gibi isimler olsalar dahi istenen kodun calismasi lazim, zira kimin hayatta kalacagi isimlere veya kisilerin sira disindaki bir ozelliklerine bagli degil.

Bu problemi cozmek icin yapilan adimlarla ana problem de cozulecektir kanaatindeyim.

Yine de kodu inceleyelim:

Once kisi indisi degistiriliyor. Fakat problemde istenen ilk kisinin solundakini oldurmesi? Oncelikle oldurme islemini yapmamiz lazim.

pop fonksiyonu listeyi degistirecegi icin mevcut kisi indisini gecersiz kilmasi veya kime isaret ettigini degistirmesi mumkun. Bunu da cozmek lazim.

Cozumu daha sistematik hale getirmek icin iki adet fonksiyon yazabiliriz:

  1. N kisisini oldur
  2. N kisisinin solundaki kisiyi bul

Bu fonksiyonlari ufak listelerde ve tum olasili sikintili durumlarda (N baslarda, N ortada, N sonlarda) test etmemiz lazim. Bu testleri basit unit testler haline getirebiliriz:

assert nnin_solundakini_oldur(["A", "B", "C", "D", "E"], 1) == (["B", "C", "D", "E"], 0)
assert nnin_solundakini_oldur(["A", "B", "C", "D", "E"], 2) == (["A", "C", "D", "E"], 1)

Burada fonksiyonun girdi ve ciktilarina dair de birer ipucu biraktim. Ne parametre aliyor? Ne donduruyor?

0’dan 4’e butun input’lar icin ciktilari neye benzemeli?

Diger fonksiyon nasil olmali? Bu fonksiyon tarafindan cagrilacak mi? Kac kere?

1 Beğeni

Bunun amacı son kişiden küçük mü diye kontrol. Şimdi her kişinin sayısal bir değeri var. Örneğin listedeki ilk kişinin adı 1, son kişinin adı da 41. Eğer kişinin sayısal değeri, son değerden küçükse sonrakini öldür, eğer küçük değilse de kılıcı tekrar baş indise döndür dedim.

Bir de şöyle denedim, hata almıyorum ama sonuç da yanlış çıkıyor.

kisiler = [i for i in range(1,41)]
kisi = 0
while len(kisiler)>1:
    if kisi<len(kisiler)-1:
        kisi = kisi+1
    else:
        kisi = 1
    kisiler.pop(kisi)
    print(kisi,kisiler)

Edit: Fonksiyonlarla belki daha sağlıklı olabilir evet ama tek while içinde de çözebileceğimi de düşünüyorum nedense.

Şu şekilde yaklaştım ama hala bi yerde atlama yapıyor kişilerde başa dönerken.

kisiler = [i for i in range(1,42)]
kisi = 0
while len(kisiler)>1:
    olecek = (kisi+1)%len(kisiler)
    kisiler.pop(olecek)
    kisi = kisi+1
    print(kisiler)

Bu daha dogru, evet.

Bu da daha dogru. Once olduruyor cunku.

Kodun yaptiklarini adim adim takip ederek sorun bulunabilir.

Yaklaşıyor ama bi yerde atlama yapıyor yine.

kisiler = [i for i in range(1,42)]
kisi = 0
while len(kisiler)>1:
    olecek = (kisi+1)%len(kisiler)
    kisiler.pop(olecek)
    if olecek == 0:
        pass
    else:
        kisi = (kisi+1)%len(kisiler)
    print(kisiler)

Merhaba,

Aşağıdaki koda bakın:

def kim_kalir(liste: list):
    return liste[0] if len(liste) == 1 else kim_kalir([liste[-2], *liste[:-2]])


print(kim_kalir(liste=list(range(41))))

Mekanizma şöyle:
İlk öldürülecek eleman -1. indiste yer alıyor. Sonra -2'nci indisteki kişinin -3. indisteki kişiyi öldürmesi gerekiyor. Peki, biz -2. indisteki kişiyi 0. indise taşısak yani eski 0. indisin yanına getirsek çemberde bir değişiklik yapmamış oluruz değil mi? Dolayısıyla -2. indisteki eleman, yine 0. indisin yanında olmuş olur, silinecek eleman yine -1'de kalır.

1 2 3 4 5 6 listesinden ilk silinecek 6 olur.
geriye 5 1 2 3 4 listesini elde ederiz.
5 de 4’ü siler, 3 başa gelir:
3 5 1 2
3, 2’yi siler, 1 başa gelir.
1 3 5
1, 5’i siler, 3 başa gelir.
3 1
3, 1’i siler, geriye 3 kalır.

1 Beğeni

Fikir güzel, ama ilk tur 2,4 ve 6 sırayla ölüyor. Kılıç 5 de kalıyor ve 1 e veriyor. 1 , 3 öldürüyor kılıcı 5 e veriyor ve 5 de 1 i öldürür.

Bu yazdığın programın simetrisini mi almalıyım.

Tamam, bize göre olan sola göre yapmışım :smiley:. Onun tersi de aşağıdaki gibi olur:

def kim_kalir(liste: list):
    return liste[0] if len(liste) == 1 else kim_kalir([*liste[2:], liste[0]])
1 Beğeni

Nerede?

Adim adim takip etmeniz, her seferinde degiskenlerin (kisiler, kisi) eski ve yeni degerlerine bakmaniz lazim.

Ben log/print ederek yapmayi seviyorum. Debugger tercih eden de var.

@dildeolupbiten in yöntemi ile en son şu şekilde doğru olarak sonuç üreten kodu yazdım.

kisiler = range(1,42)
while len(kisiler)>1:
    kisiler = [*kisiler[2:], kisiler[0]]
print(kisiler)

Doğrudan listeyi istenildiği şekilde değiştirip yaşayanları arkaya gönderme oldukça mantıklı.

@aib ama yine de şu pop yöntemi ile de deneyeceğim. Son koddaki çıktıda şurada kayıyor.

[3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41]
[3, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41]
[3, 7, 9, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41]

Doğrusu kılıç 3 teyken 5 i öldürüyor. Kılıcı 7 ye veriyor ve 7 nin de 9 u öldürmesi lazım. Ama ne hikmetse kılıç 7 ye değil de 9 un eline geçip 11 i öldürüyor. Buraya kadar doğru gidiyor sistem burada bir atlama yapıyor. Haliyle sonuç da yanlış çıkıyor.

Listenin yanina kilic sahibini de yazabilir miyiz?

Veya dur ben yazayim:

print(f"{kisiler}, kisi={kisi} ({kisiler[kisi] if kisi < len(kisiler) else '?'}), olecek={olecek} ({kisiler[olecek]})")

[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41], kisi=20 (41), olecek=0 (1)
[3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41], kisi=20 (?), olecek=1 (5)
[3, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41], kisi=2 (9), olecek=3 (11)
[3, 7, 9, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41], kisi=3 (13), olecek=4 (15)

Simdi daha acik olmasi lazim sorunun.

Evet şurası sorunlu, kılıç 0 a geçmiyor, son kişinin elinde kalıyor. Ama yine de doğru kişi ölüyor. Sonrasında kisi=1 in elinde olması gereken kılıç kişi 2 ye geçmiş.

Ona da şöyle bir çözüm buldum. Her tur birisi öldüğüne göre liste sayısına göre işlem yapmaktansa yasayanların sayısı olan bir değişken üzerinden gittim.

kisiler = range(1,42)
kisi = 0
yasayan = len(kisiler)
while len(kisiler)>1:
    olecek = (kisi+1)%yasayan  
    kisiler.pop(olecek)
    if olecek != 0:
        kisi = (kisi+1)%yasayan        
    yasayan = yasayan - 1
    print(kisiler)

Şimdi doğru sonucu üretiyor.