Önceki yazılarda verdiğimiz basit örneklerin ötesine geçip biraz daha elle tutulur fonksiyon örnekleri verelim.
- Asallık testi
- Asal çarpanlar
- Standart sapma
- Collatz dizisi uzunluğu
- Taylor serisiyle üstel fonksiyon
Asallık testi
Döngülerden bahsederken, bir sayının asal olup olmadığını belirleyen bir program yazmıştık. Şimdi bunu bir fonksiyon haline getirelim. Parametre değeri bir asal sayıysa fonksiyonumuz True
verecek, değilse False
verecek.
In [1]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def asal_mı(x):
i = 2
while i*i <= x:
if x % i == 0:
return False
i += 1
else:
return True
In [2]:
1
2
3
asal_mı(8)
Out[2]:
1
False
In [3]:
1
2
3
asal_mı(79)
Out[3]:
1
True
Asal çarpanlar
Önceki bir bölümde bir sayının asal çarpanlarını ekrana basan bir döngü yazmıştık. O döngüyü bir fonksiyon içine koyalım ve verilen sayının asal çarpanları bir liste olarak geri verilsin.
In [4]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def asalçarpanlar(N):
çarpanlar = [] # boş liste
x = 2
while N > 1:
# x asal mı?
asal = True
i = 2
while i*i <= x:
if x % i == 0:
asal = False
break
i += 1
if asal and N % x == 0: # x asalsa ve N'yi bölüyorsa...
çarpanlar.append(x) # x'i listenin sonuna ekle
while N % x == 0 : # N x'e bölünebildiği sürece...
N = N / x # N'yi x'e böl, x çarpanı kalmasın.
x += 1
return çarpanlar
In [5]:
1
2
3
asalçarpanlar(600851475143)
Out[5]:
1
[71, 839, 1471, 6857]
Bu fonksiyonu, yukarıda tanımladığımız asal_mı()
fonksiyonunu kullanarak kısaltabiliriz.
In [6]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def asalçarpanlar(N):
çarpanlar = [] # boş liste
x = 2
while N > 1:
if asal_mı(x) and N % x == 0: # x asalsa ve N'yi bölüyorsa...
çarpanlar.append(x) # x'i listenin sonuna ekle.
while N % x == 0 : # N x'e bölünebildiği sürece...
N = N / x # N'yi x'e böl, x çarpanı kalmasın.
x += 1
return çarpanlar
asalçarpanlar(600851475143)
Out[6]:
1
[71, 839, 1471, 6857]
Standart sapma
Elimizde $N$ tane $x_1, x_2,\ldots,x_N$ sayısı olsun. Sayıların ortalamasına $\bar{x}$ diyelim. Bu sayıların standart sapması şöyle tanımlanır:
\[\sigma = \sqrt{ \frac{1}{N-1} \left[ (x_1-\bar{x})^2 + (x_2-\bar{x})^2 + \ldots + (x_N-\bar{x})^2\right]}\]Belirsiz sayıda parametre alan ve bu parametrelerin standart sapmasını veren stdsap()
isimli bir fonksiyon yazalım. Standart sapmanın tanımlanabilmesi için fonksiyon en az iki parametre almalı. Bunun dışında parametre sayısında bir kısıtlama olmayacak. Bu şartı sağlamak için fonksiyon arayüzünü stdsap(x1, x2, *y)
şeklinde yazmamız gerekir.
In [7]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import math # karekök için
def stsapma(x1, x2, *y):
N = len(y) + 2 # Fonksiyona verilen parametre sayısı
# ortalamayı hesapla
toplam = x1 + x2
for z in y:
toplam += z
ort = toplam / N
karetoplam = (x1-ort)**2 + (x2-ort)**2
for z in y:
karetoplam += (z-ort)**2
stsap = math.sqrt(karetoplam / (N-1))
return stsap
In [8]:
1
2
3
4
5
print(stsapma(1,2))
print(stsapma(1,2,2,3,3,3,3,4,4,5,5))
1
2
3
4
5
0.7071067811865476
1.250454462839956
Tipik bir uygulamada, verileri önceden kullanıcıdan veya bir dosyadan tek tek almış ve bir listeye yazmış olabilirsiniz. O durumda parametre çözme (link) kullanarak listeyi bu fonksiyona parametre olarak verebilirsiniz.
In [9]:
1
2
3
4
5
data = [1,2,2,3,3,3,3,4,4,5,5]
stsapma( *data )
Out[9]:
1
1.250454462839956
Collatz dizisi uzunluğu
Collatz dizisini önceki bölümlerde görmüştük. Bir dizide belli bir $n_0$ sayısından başlayıp $n_1$’i buluruz, $n_1$ ile $n_2$’yi buluruz, ve böyle gider. Collatz dizisinde bir sonraki sayıyı bulma kuralı şöyledir:
- $n_i$ çift sayıysa: $n_{i+1} = n_i/2$
- $n_i$ tek sayıysa: $n_{i+1} = 3n_i+1$
- $n_i = 1$ ise dur.
Söz gelişi, 13 ile başlayan bir dizi aşağıdaki gibi olur.
1
2
3
4
5
13, 40, 20, 10, 5, 16, 8, 4, 2, 1
Collatz dizisi, bildiğimiz kadarıyla, hangi sayıyla başlarsak başlayalım hep 1’e gelir ve durur (ama bunun her zaman böyle olacağı ispat edilememiştir). Öyle bir fonksiyon yazalım ki, verilen bir başlangıç değeriyle Collatz dizisini üretsin ve 1’e kaç adımda ulaşıldığını saysın. Bunu yaparken dizinin en fazla kaça çıktığını da kaydetsin ve bu iki değeri geri versin.
In [10]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def collatz_uzunluk(n):
enbüyük = n
uzunluk = 0
while n > 1:
if n % 2==0:
n = n/2
else:
n = 3*n + 1
uzunluk += 1
if n > enbüyük:
enbüyük = n
return uzunluk, enbüyük
In [11]:
1
2
3
4
5
6
7
for n0 in [11, 79]:
u, m = collatz_uzunluk(n0)
print("Başlangıç = {}, dizi uzunluğu = {}, en büyük değer = {}".format(n0, u, m))
1
2
3
4
5
Başlangıç = 11, dizi uzunluğu = 14, en büyük değer = 52.0
Başlangıç = 79, dizi uzunluğu = 35, en büyük değer = 808.0
11 ile başlayan Collatz dizisi 14 adım sonra 1’e ulaşıyor ve arada aldığı en büyük değer 52. Diziyi 79 ile başlatırsak 1’e ulaşana kadar 35 adım atıyor; ara değerler 808’e kadar çıkıyor.
Taylor serisiyle üstel fonksiyon
Döngü alıştırmaları bölümünde üstel fonksiyon $e^x$ değerini Taylor serisi ile bulmaktan bahsetmiştik.
\[e^x = \sum_{n=0}^{\infty} \frac {x^n}{n!} = 1 + x + \frac{1}{2}x^2 + \frac{1}{6}x^3 + \cdots\]Bu açılımı kullanarak, üstel fonksiyonu bir Python fonksiyonu olarak yazalım.
Bu serinin sonsuz terimi olduğu için bir yerde kesmek zorundayız. Bu kesme bir hataya yol açacak, ve biz ne kadarlık bir hataya göz yumabileceğimizi (toleransımızı) önceden belirleyeceğiz. Her yeni terim bir öncekinden küçük olduğu için, yeni terim toleranstan küçükse hesabı durduracağız.
Ayrıca, kaç tane terim kullanabileceğimizin katı bir sınırı da olacak. Belki öyle bir $x$ sayısı ile başlarız ki, seriye ne kadar terim eklersek ekleyelim bir türlü toleransın altına inemeyiz. Tabii bu serinin her $x$ değeri için yakınsak olduğunu biliyoruz, ama bu ek emniyet tedbiri bu tür iteratif algoritmalarda genelde faydalıdır. Eğer azami terim sayısına ulaşılmasına rağmen terimler toleransın altına inmediyse ekrana bir uyarı mesajı basılsın ve fonksiyon elde ettiği sonucu geri versin.
Tolerans ve azami terim sayısı için makul varsayılan değerler verelim, böylece fonksiyonu kullanırken her seferinde yazmak zorunda kalmayalım. Sadece deney yapmak istediğimizde değiştirelim.
In [12]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def üstel(x, hata = 1e-8, maxterim = 20):
n = 1
sonterim = 1 # serideki ilk terimimiz
toplam = sonterim
while sonterim >= hata:
if n > maxterim :
print("{} terimde yakınsama sağlanamadı".format(maxterim))
break
sonterim = sonterim * x/n # yeni terim bir öncekini x ile çarpıp terim sıra numarasına bölerek bulunuyor.
toplam += sonterim
n += 1
return toplam
Fonksiyonumuzu standart kütüphanedeki üstel fonksiyonla karşılaştıralım:
In [13]:
1
2
3
4
5
6
7
import math
print(math.exp(2))
print(üstel(2))
1
2
3
4
5
7.38905609893065
7.389056098516415
Virgülden sonra dokuz basamağa kadar uyum sağlanıyor. Daha iyi uyum için toleransı küçültebiliriz. Ama çok küçültürsek yirmi terim yeterli olmayacaktır.
In [14]:
1
2
3
üstel(2, hata=1e-15)
1
2
3
20 terimde yakınsama sağlanamadı
Out[14]:
1
7.389056098930604
Bu sınıra takılmadan daha yüksek hassasiyet sağlamak için maxterim
değerini artırabiliriz.
In [15]:
1
2
3
4
5
print(math.exp(2))
print(üstel(2, hata=1e-15, maxterim=30))
1
2
3
4
5
7.38905609893065
7.389056098930649
Büyük $x$ değerleri kullanmak da varsayılan terim sayısının yetmemesine sebep olabilir.
In [16]:
1
2
3
üstel(10)
1
2
3
20 terimde yakınsama sağlanamadı
Out[16]:
1
21991.482025665064
In [17]:
1
2
3
4
5
print(math.exp(10))
print(üstel(10, maxterim=50))
1
2
3
4
5
22026.465794806718
22026.46579480579
Burada bir örnek olsun diye üstel fonksiyon için kod yazdık, ama pratik uygulamalarda bu kodu kullanmamalısınız. Matematiksel işlemler için Python’un matematik kütüphanesini kullanmanız daha iyi olur. Bu kütüphane C dilinde yazılmış ve derlenerek makine diline çevrilmiş kod kullandığı için çok daha hızlıdır. Tek çalıştırmada bu hız farkı çok belli olmasa da çok sayıda işlem yapıldığında ciddi bir fark görülür.
In [18]:
1
2
3
%timeit üstel(2)
1
2
3
3.11 µs ± 584 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [19]:
1
2
3
%timeit math.exp(2)
1
2
3
138 ns ± 6.25 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Görüldüğü gibi math.exp
fonksiyonu işlemi 140 nanosaniyede yaparken, bizim yazdığımız üstel
fonksiyonuyla yapılan işlem yaklaşık 3 mikrosaniye sürüyor, yirmi kat yavaş. Üstelik math.exp
daha yüksek hassasiyette sonuç veriyor ve fazladan kontroller içeriyor.
Meraklısı için: math.exp
içinde üstel fonksiyon Taylor serisiyle hesaplanmaz. Python matematik işlemlerinde C dilinin standart matematik kütüphanesini kullanır. Bu kütüphanede üstel fonksiyon hızlı ve isabetli hesaplanabilecek şekilde özel olarak düzenlenmiştir. C’de üstel fonksiyonun tanımı şurada görülebilir. Özetle, önce öyle bir tamsayı $k$ ve $r<0.5\ln 2$ sağlayan reel sayı $r$ bulunur ki, parametremiz $x = k\ln 2 + r$ biçiminde olsun. O zaman $e^x = 2^k + e^r$ şeklinde yazılabilir. Bunun avantajı şudur: Bilgisayarda 2’nin tamsayı kuvvetini almak kolaydır (sayı bitlerini sola kaydırmak yeter). Kalan $r$ sayısı küçük olduğu için $e^r$ içinse yüksek hassasiyetli özel bir formül kullanılabilir. Ara hesaplar, bayt seviyesi işlemler kullanılarak mümkün olduğunca hızlandırılır.