- Büyük O gösterimini okumak ve liste, sözlük, küme işlemlerinin maliyetini bilmek
timeitvecProfileile kodun hızını ölçmekCounter,defaultdict,deque,namedtupleveitertools'u yerinde kullanmak
Bir program 100 000 siparişi, engellenmiş 100 000 müşterinin listesiyle karşılaştırıyor. Listeyle bu onlarca saniye sürer; tek bir satırı değiştirip küme kullanırsan iş saniyenin küçük bir kesrinde biter. Python'da hız nadiren zekice hilelerden gelir; doğru veri yapısını ve algoritmayı seçmekten ve tahmin etmek yerine ölçmekten gelir. Bu ders ikisi için de araçlar verir.
Büyük O: süre nasıl artar
Büyük O gösterimi, bir işlemin çalışma süresinin veri boyutu n ile nasıl büyüdüğünü, sabit çarpanları göz ardı ederek tanımlar. O(1) — sabit: n'ye bağlı değildir. O(log n) — çok yavaş büyür (ikili arama). O(n) — n ile orantılıdır (listeyi bir kez dolaşmak). O(n log n) — iyi bir sıralama. O(n²) — aynı veri üzerinde iç içe döngüler: 10 kat fazla veri, 100 kat fazla iş demektir.
| İşlem | list | dict / set |
|---|---|---|
x in c | O(n) | O(1) |
| indeks / anahtarla erişim | O(1) | O(1) |
append, add, d[k] = v | O(1)* | O(1) |
insert(0, x), pop(0) | O(n) | — |
sondan pop() | O(1) | — |
sorted(c) | O(n log n) | O(n log n) |
Sözlük ve küme neden bu kadar hızlı? Bunlar hash tablolarıdır: hash(key), elemanın nerede saklandığını neredeyse doğrudan gösterir; bu yüzden in veriyi taramaz. Listenin böyle bir göstergesi yoktur ve elemanları tek tek karşılaştırmak zorundadır. Listenin başına eleman eklemek ise diğer tüm elemanları kaydırır; bu nedenle insert(0, x) ve pop(0) O(n)'dir.
Tahmin etme, ölç: timeit ve cProfile
timeit, küçük bir kod parçasını birçok kez çalıştırır ve toplam süreyi döndürür; böylece rastgele dalgalanmalar yumuşar. 100 000 sayılık bir listede ve kümede aramayı karşılaştıralım:
import timeit
setup = 'data_list = list(range(100_000)); data_set = set(data_list)'
t_list = timeit.timeit('99_999 in data_list', setup=setup, number=200)
t_set = timeit.timeit('99_999 in data_set', setup=setup, number=200_000) / 1000
print(f'list: {t_list * 1000:.2f} ms for 200 lookups')
print(f'set: {t_set * 1000:.4f} ms for 200 lookups')
print(f'the set is about {t_list / t_set:,.0f} times faster')Bütün bir program yavaş olduğunda, bir şeyi değiştirmeden önce sürenin nereye gittiğini bul. cProfile her fonksiyon çağrısını ve içinde geçen süreyi sayar. Onu bir betik için komut satırından çalıştır ve raporu toplam süreye göre sırala:
$ python -m cProfile -s cumulative slow.py
28656
92741 function calls (51 primitive calls) in 0.019 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.019 0.019 {built-in method builtins.exec}
1 0.000 0.000 0.019 0.019 slow.py:1(<module>)
1 0.000 0.000 0.019 0.019 slow.py:4(main)
1 0.000 0.000 0.019 0.019 {built-in method builtins.sum}
23 0.000 0.000 0.019 0.001 slow.py:5(<genexpr>)
92712/22 0.019 0.000 0.019 0.001 slow.py:1(fib)slow.py, 0'dan 21'e kadar Fibonacci sayılarının toplamını saf özyinelemeyle hesaplar. Süreler bilgisayara bağlıdır, çağrı sayıları değildir.92712/22, toplam 92 712 çağrı demektir; bunların yalnızca 22'si dışarıdan yapılmıştır (geri kalanı özyinelemelidir). Profil aracı koddan da kullanılabilir; burada yalnızca çağrı sayılarını okuyoruz ve dekoratörler dersindeki lru_cache'in on binlerce çağrıyı nasıl 21'e indirdiğini görüyoruz:
import cProfile
import pstats
from functools import lru_cache
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
@lru_cache(maxsize=None)
def fib_cached(n):
return n if n < 2 else fib_cached(n - 1) + fib_cached(n - 2)
for func in (fib, fib_cached):
profiler = cProfile.Profile()
profiler.runcall(func, 20)
stats = pstats.Stats(profiler).stats
calls = sum(v[1] for (_, _, name), v in stats.items() if name == func.__name__)
print(f'{func.__name__}(20): {calls} calls')▸ Beklenen çıktı
fib(20): 21891 calls fib_cached(20): 21 calls
collections modülü
**Counter**, saymak için bir sözlüktür: ona herhangi bir yinelenebilir nesne ver, elemanları sayar. most_common(n) en sık geçen n elemanı döndürür; olmayan bir anahtar KeyError yerine 0 verir; sayaçlar toplanıp çıkarılabilir bile:
from collections import Counter
words = Counter('the cat and the hat and the bat'.split())
print(words.most_common(2))
print(words['the'], words['dog'])
votes = Counter(['plov', 'dolma', 'plov', 'kebab', 'plov', 'dolma'])
print(votes)
print(votes + Counter(['kebab', 'kebab']))▸ Beklenen çıktı
[('the', 3), ('and', 2)]
3 0
Counter({'plov': 3, 'dolma': 2, 'kebab': 1})
Counter({'plov': 3, 'kebab': 3, 'dolma': 2})**defaultdict**, eksik bir değeri ilk erişimde, verdiğin fabrikayla otomatik olarak oluşturur: list, int, set. Gruplama için idealdir. Dikkat: eksik bir anahtarı okumak bile onu sözlüğe ekler:
from collections import defaultdict
students = [('Aysel', '9A'), ('Murad', '9B'), ('Leyla', '9A'), ('Elvin', '9B'), ('Nigar', '9A')]
by_class = defaultdict(list)
for name, group in students:
by_class[group].append(name)
print(dict(by_class))
print(by_class['10C'], len(by_class))▸ Beklenen çıktı
{'9A': ['Aysel', 'Leyla', 'Nigar'], '9B': ['Murad', 'Elvin']}
[] 3by_class['10C'] boş bir grup oluşturdu; sözlüğün uzunluğu 3 oldu.**deque** (çift uçlu kuyruk) her iki uçtan O(1) sürede eleman ekler ve siler; listede ise baştan silmek O(n)'dir. maxlen ile yalnızca son n elemanı tutar; “son görüntülenenler” listeleri ve kayan pencereler için idealdir. **namedtuple**, alanlarının adları da olan hafif ve değiştirilemez bir demet oluşturur:
from collections import deque, namedtuple
queue = deque(['Aysel', 'Murad'])
queue.append('Leyla')
queue.appendleft('Elvin')
print(queue.popleft(), queue)
recent = deque(maxlen=3)
for page in ['home', 'lessons', 'python', 'quiz', 'profile']:
recent.append(page)
print(list(recent))
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p, p.x, p[1], p._replace(x=10))▸ Beklenen çıktı
Elvin deque(['Aysel', 'Murad', 'Leyla']) ['python', 'quiz', 'profile'] Point(x=3, y=4) 3 4 Point(x=10, y=4)
itertools ve Python tarzı hız
**itertools**, döngüler için C ile yazılmış hızlı ve tembel yapı taşlarından oluşan bir settir: dizileri birleştirme (chain), birikimli toplamlar (accumulate), komşu çiftler (pairwise), kombinasyonlar ve Kartezyen çarpım, gruplama:
from itertools import accumulate, chain, combinations, groupby, pairwise, product
print(list(chain([1, 2], (3, 4), 'ab')))
print(list(accumulate([5, 3, 8, 2])))
print(list(pairwise([10, 12, 15, 11])))
print(list(combinations('ABC', 2)))
print(len(list(product(range(10), repeat=4))))
fruits = sorted(['cherry', 'apple', 'blueberry', 'avocado', 'banana'])
print({k: list(g) for k, g in groupby(fruits, key=lambda w: w[0])})▸ Beklenen çıktı
[1, 2, 3, 4, 'a', 'b']
[5, 8, 16, 18]
[(10, 12), (12, 15), (15, 11)]
[('A', 'B'), ('A', 'C'), ('B', 'C')]
10000
{'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}numbers = [1, 2, 2, 3, 4, 4, 5]
for n in numbers:
if n % 2 == 0:
numbers.remove(n)
print(numbers)
numbers = [1, 2, 2, 3, 4, 4, 5]
numbers = [n for n in numbers if n % 2 != 0]
print(numbers)▸ Beklenen çıktı
[1, 2, 3, 4, 5] [1, 3, 5]
Metinde en sık geçen 3 sözcüğü sözcük: sayı biçiminde yazdır. Büyük-küçük harf ve noktalama işaretleri yok sayılmalıdır. Counter kullan.
from collections import Counter
import re
text = '''Python is fast to write. Python is fun.
Fast code is good, but correct code is better.'''
# 1) turn the text into a list of lower-case words
# 2) print the 3 most common words as 'word: count'▸ Beklenen çıktı
is: 4 python: 2 fast: 2
Birden fazla kez geçen kimlikleri artan sırayla, ardından benzersiz kimliklerin sayısını yazdır. İç içe döngüler (O(n²)) değil, tek geçiş ve kümeler (O(n)) kullan.
ids = [105, 230, 105, 999, 412, 230, 7, 105]
seen = set()
duplicates = set()
# one pass: if an id is already in seen, it is a duplicate
print(sorted(duplicates))
print(len(seen), 'unique ids')▸ Beklenen çıktı
[105, 230] 5 unique ids
Önemli noktalar
- Büyük O, sürenin n ile nasıl büyüdüğünü gösterir; bir döngü içinde liste üzerinde
ingibi gizli O(n²)'lerden kaçın. - Küme ve sözlükte
inortalama O(1), listede O(n)'dir; listedeinsert(0, x)vepop(0)O(n)'dir;dequekullan. - İyileştirmeden önce
timeitvecProfileile ölç. Countersayar,defaultdictgruplar,dequehızlı bir kuyruktur,namedtupledemet alanlarına ad verir.itertoolstembel araçlar sunar;groupbysıralı veri ister; bir listeyi dolaşırken asla değiştirme.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.
x in s denetiminin ortalama maliyeti nedir?