Float Sayılar üzerinde döngü kuramıyorum

Merhaba, döngülerle ilgili bir sorun yaşıyorum. range() fonksiyonu sadece tam sayı alıyor.

for i in range(0,1):
    print(i)

gibi bir kullanım hata veriyor. 0.1, 0.2, 0.3, 0.4… şeklinde tüm sayılar üzerinde gezinmem gerekiyor. Yardımcı olursanız sevinirim.

Merhaba, bu istediğinizi yapabilmek için:

  1. Ya numpy’nin arange fonksiyonunu kullanmalısınız.
  2. Ya da kendiniz range'e benzer ancak ondalık sayıların da kullanılabildiği bir fonksiyon oluşturmalısınız.
def range_(start : float, stop: float, step: float):
    result = []    
    decimal = len(str(step).split(".")[-1])
    while round(start, decimal) < round(stop, decimal):
        result.append(round(start, decimal))
        start += step
    return result

print(range_(0, 1, 0.1))

Sonuç:

[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
4 Likes

Çok teşekkür ederim :slight_smile: , bu kadar zahmetli bir iş olacağını düşünmemiştim. Bu kodları parçalara ayırarak okumak kolay fakat bir araya geldiklerinde zorlanabiliyor insan. Kodları kabaca bi’ açıklayabilirseniz minnettar kalırım.

Tabi açıklayayım:

def range_(min_ : float, max_: float, step_: float)

Burada range_ isimli bir fonksiyon tanımlandı. Bu fonksiyon 3 tane parametre alıyor. Bu parametrelerin veri tiplerinin float olması gerekiyor. Daha doğrusu sadece step_'in veri tipi float olsa, diğerlerinin veri tipi int olsa da olur. Ama belki 0.5 ile 9.3 arasındaki sayıları 0.35 artış miktarı ile aramak istersiniz diye bu parametrelerin de tipi float olarak belirlendi.

   result = []    

min_ ile max_ arasındaki sayıları result isimli bir listede tutacağız.

    decimal = len(str(step_).split(".")[-1])

Burada virgülden sonra kaç basamak var onu hesaplıyoruz. Mesela step_ sayısı 0.1 ise, str(0.1), '0.1' yapar. Bu string dosyasını split(".") fonksiyonu ile . işaretinden ayırıyoruz. Ayırdığımız veri iki elemanlı bir liste haline geliyor. Bu listenin son elemanı "1". Bu stringin uzunluğunu ölçüyoruz, bu örnek için o da bir 1 çıkıyor. Yani noktadan sonra kaç basamak var onu hesaplıyoruz ve decimal isimli bir değişkene kaydediyoruz.

    while round(min_, decimal) < round(max_, decimal):

Burada yuvarlanmış min_ değeri, yuvarlanmış max_ değerinden küçük olduğu sürece çalışan bir döngü var. Yuvarlama step_ değerinin virgülden sonra kaç basamaklı olduğuna göre yapılır.

        result.append(round(min_, decimal))

result listesine yuvarlanmış min_ sayısını ekleriz.

        min_ += step_

min_ sayısını, arttırma miktarı olan step_ kadar arttırız.

    return result

Ve son olarak result listesini geri döndeririz.

2 Likes

Ben de böyle bir şeyler yazdım. :grinning:

def ondalik_dizi(bas, son, aralik):
    bas *= 10
    son *= 10
    
    gecici = list(range(bas, son, aralik))
    
    kalici = []
    for i in gecici:
        kalici.append(i/10)
        
    print(kalici)

ondalik_dizi(0,1,1)
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]

1 Like

Yalnız ondalik_dizi fonksiyon sadece integer değerler ile çalışıyor. Ve ondalıkta sadece tek basamak gidebiliyor.

Evet haklısınız. Maksat yatmadan önce küçük bir şeyler yazayım dedim. :slight_smile:

2 Likes

Elinize sağlık. :blush:

1 Like
def frange(start, stop, step):
    while start < stop:
        yield start
        start += step
2 Likes

@aib, Biraz araştırma yaptım, isim alanında yield keyword u kullanılan fonksiyonlar generator olarak geçiyormuş ve generator fonksiyonlarda return kullanılamazmış. Yalnız yield keyword unun tam olarak ne işe yaradığını anlayamadım. Basitçe açıklar mısınız?

Siz yazınca ben de merak ettim. yukarıdaki adreste bir tür nesne olduğu yazıyor. Tahminimce range() fonksiyonunun verdiği gibi bir aralık nesnesi geri döndürüyor. Aşağıdaki bağlantıda range() fonksiyonunu bir aralık nesnesi olduğu yazıyor.

https://belgeler.yazbel.com/python-istihza/gomulu_fonksiyonlar.html#range

Kısaca anladığım yield kullanıldığında bir aralık nesnesi dönüyor. Nasıl range() fonksiyonunu bir listeye atayıp değerlerini yazdırabiliyorsak yield ifadesinin kullanıldığı kullanıcı tanımlı fonksiyonunu da liste gibi bir veri türüne atayıp içeriğini yazdırabiliriz. Tabi print(*dongu_kurulabilir_veri) şeklinde de olabilir.

Belki de gömülü bir fonksiyon olan range() fonksiyonunun da içerisinde yield yazıyordur :smiley:

Teşekkürler, bu akşam da bir şeyler kattık kendimize.

def parcala(bas, son, adim, duyarlilik=1):
    liste = []
    if bas < son:
        while bas < son:
            liste.append(round(bas, duyarlilik))
            bas += adim
    elif bas > son:
        while bas > son:
            liste.append(round(bas, duyarlilik))
            bas -= adim
    else: print("Aralığın ilk ve son değerleri farklı olmalıdır..!")
    return liste
In [45]: parcala(1, 3, 0.1, 2)
Out[45]: 
[1,
 1.1,
 1.2,
 1.3,
 1.4,
 1.5,
 1.6,
 1.7,
 1.8,
 1.9,
 2.0,
 2.1,
 2.2,
 2.3,
 2.4,
 2.5,
 2.6,
 2.7,
 2.8,
 2.9]

Burada round fonksiyonu olmadan yazdığımda çıktıda virgülden sonra çok fazla hane görünüyor. Round ile virgülden sonraki haneyi duyarlılık değişkeni ile sınırlamaya çalıştığımda tam olarak istediğim çıktıyı alamadım fakat böyle daha iyi sonuç aldım.

round() fonksiyonu ve duyarlilik değişkeni olmadığında çıktı şöyle oluyor;

 In [46]: parcala(1, 3, 0.1)
 Out[46]: 
 [1,
 1.1,
 1.2000000000000002,
 1.3000000000000003,
 1.4000000000000004,
 1.5000000000000004,
 1.6000000000000005,
 1.7000000000000006,
 1.8000000000000007,
 1.9000000000000008,
 2.000000000000001,
 2.100000000000001,
 2.200000000000001,
 2.300000000000001,
 2.4000000000000012,
 2.5000000000000013,
 2.6000000000000014,
 2.7000000000000015,
 2.8000000000000016,
 2.9000000000000017]
1 Like

Iterable dedigimiz, birden fazla deger iceren (for...in loop’una girebilen) degerleri donduren fonksiyonlari coroutine olarak yazmamizi saglayan bir keyword. Bu coroutine’ler dogalari geregi calismaya ara verip devam edebiliyorlar, bu sayede sonsuz veya pahali listeleri parca parca olusturmak mumkun oluyor:

def foo():
    i = 0
    while True:
        yield i
        i += 1

Ayni zamanda cagri hic bir zaman sonlanmamis gibi oldugu icin yerel degiskenler (i) stack frame gibi bir yerde otomatik olarak saklaniyorlar; state bilgisi kaydetmek/yuklemek icin ekstra kod yazmaya gerek kalmiyor.

Generator’larda return kullaniliyor; (return ile veya kodu bitmek suretiyle) return eden generator’lar sonlanmis sayiliyor.

2 Likes

O zaman ciktiyi duzeltmek lazim. Kayan noktali sayilarin dogasi bu. Duz print de gostermek icin kotu bir yontem :slight_smile:

2 Likes

Peki hocam coroutine ifadeler sonlanmamış gibi olduğu için içerisindeki yerel değişkenler saklandığından dolayı bellek kullanımı hususunda sorun yaşatabilme potansiyeli oluyor mu? (farklı büyük projeler için belleğin aşırı kullanımı gibi)

Siz ne gibi durumlarda return yerine yield’i tercih ediyorsunuz. İç değişkenin saklanıyor olması avantajından dolayı mı? yield ifadesini tercih etmem gereken durumları kavramaya çalışıyorum :slight_smile:

Soruyu @aib’e sormuşsunuz ama izninizle sorunuza cevap vereyim. Yazdıklarımda bir eksiklik veya bir yanlışlık varsa düzeltilir.

yield ile return arasındaki farkı şöyle anlatmaya çalışayım:

a = [i for i in range(1000000)]

Bu yukarıdaki ifade enter tuşuna basılır basılmaz hafızada yer kaplamaya başlayacak. İsterseniz, sistem tepsisini açın ve bu ifadeyi yazıp enter tuşuna basmadan önceki cpu ve hafıza kullanım oranıyla, enter tuşuna bastıktan sonraki cpu ve hafıza kullanım oranını karşılaştırın.

İfadeyi şöyle değiştirelim:

a = (i for i in range(1000000))

Bir de bu ifadeyi yazıp enter tuşuna basın ve hafıza kullanım oranına bir daha bakın.

Yukarıdaki kodlardan ilki, ikincisinden daha fazla kaynak tüketecektir. Aynı durum return ve yield deyimleri için de geçerli. Mesela:

def f():
    return [i for i in range(1000000)]

yukarıdaki paylaştığım ilk kodlara benzer.

def g():
    yield from range(1000000)

Bu ifade ise yukarıda paylaşılan ikinci kodlara benzer.

return ifadesini şu şekilde kullansaydık, yield deyimi içeren fonksiyon ile aynı oranda kaynak kullanacaktı.

def f():
    return (i for i in range(1000000))

Bu fonksiyon ile yukarıda yield deyim kullanılan fonksiyon arasında hiç bir fark yok.

# fonksiyonu çağıralım.
call = f()
# Burada bir generator `call` isimli bir değişkene kaydedildi.
print(next(call))
# Bu yukarıdaki kod bize üreteçin ilk değerini verecektir.
print(next(call))
# Bu yukarıdaki kod ise üreteçin ikinici değerini verecektir.
# next() fonksiyonunu üretecin içindeki eleman sayısı kadar kullanabiliriz.

Aşağıdaki örneği bir çalıştırın isterseniz:

def f():
    yield from range(5)
    yield from "hello"
    yield 2
    
a = f()
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))

Bu f() fonksiyonundaki yield deyimi, range(5) deki her bir elemanı, "hello" stringindeki her bir elemanı ve 2 sayısını bir üreteç içine dahil eder. Dolayısıyla yield yinelenebilir bir veri setindeki i verisini sırası geldiğinde kullanır. return ifadesinde ise veri setinin tamamı kullanılır ve bu da hafızanın daha çok kullanılması anlamına geliyor.

Yinelenebilir verinin büyük olduğu durumlarda yield deyimini kullanmak return deyimini kullanmaya göre daha avantajlıdır. Ancak yukarıdaki örneklerden birinde şöyle bir ifade vardı:

def f():
    return (i for i in range(1000000))

return deyimini yukarıdaki gibi kullanırsanız, return ifadesini yield gibi kullanmış olursunuz. Yani return edilen şey bir üreteçtir. Ve bu üreteç ne kadar büyük bir sayı dizisini içeriyor olursa olsun, sayı dizisi hafızada yer tutmaz.

2 Likes

Izninle ben de cevapliyorum.

E zaten saklaman gerekmiyor muydu? Coroutine kullanmasan saklanmayacak miydi o data? O zaman niye coroutine kullandin? Yerel degiskenler her zaman saklaniyor.

Bellek kullanimi ne kadar kisitli ki bir coroutine’in yerel degiskenlerini dusunuyorsun? Coroutine olmayan fonksiyonlarin yerel degiskenleri icin benzer bir dusunceye gittin mi?

Ne kadar buyuklukte? O buyuklukte proje yazana kadar mevcut hardware’in bellegi artmayacak mi? RAM fiyatlari yariya dusmeyecek mi?

Buyuk bir projede manasiz asiri bir bellek kullanimi varsa o projedeki daha deneyimli biri uyarir zaten, duruma gore dogru tavsiyeyi de verir. Ben simdi RAM butcesini bilmeden, programcilarin yogunlugunu bilmeden bir sey diyemiyorum.


Hafiza kullanimi veya calisma hizi gibi gereksiz parametreleri optimize etmeye calismayin.


Hangisi daha kolay, anlasilir olacaksa.

yield'i kullanmayi ogren. Sonra kullanabilecegin yerleri gormeyi ogren. Oralarda kullan. Bazen kullanma. Tercih etmen gereken durumlari zaten anlarsin.

2 Likes

Teşekkürler, bu vesile ile yield’i de aklımızın bir köşesine attık. Bilgi eksikliğimiz yüzünden sorular bazen saçma oluyorsa kusura bakmayın.

1 Like

Hiç saçma olmuyor, lütfen sormaktan çekinmeyin. Hepimiz öğrenciyiz, sorular çok öğretici olabiliyor.

1 Like

Aynen.

Teknik dogruluga, dogru dusunce sekillerine yoneltici cumleler kurmaya odaklanmaktan uslubu kaciriyorum cogu zaman. Her soruya tek tek -alinti yaparak- cevap vermemin yeterli bir saygi gostergesi oldugu yanilgisina dusuyorum. Yukaridaki gibi hiyar, ama her soruyu mumkun oldugunca dogru yoneltmeyle cevaplamaya calisan yazilar cikiyor ortaya. Beni de boyle sevin :woozy_face:

1 Like