Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions Урок 4. Практическое задание/task_1.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# encoding: utf-8
from __future__ import print_function
"""
Задание 1.

Expand All @@ -13,6 +15,7 @@
"""

from timeit import timeit
from random import randint, seed


def func_1(nums):
Expand All @@ -21,3 +24,71 @@ def func_1(nums):
if nums[i] % 2 == 0:
new_arr.append(i)
return new_arr


def func_2(nums):
return [
i
for i, n in enumerate(nums)
if n % 2 == 0
]


# обеспечим повторяемость при генерации массива
seed(1)
arr100 = [randint(1, 100) for i in range(100)]
arr1000000 = [randint(1, 100) for i in range(1000000)]

arr = arr100
N = 100000

print("func_1(<100>), 100 000")
print(timeit("func_1(arr)", "from __main__ import func_1, arr", number=N))

print("func_2(<100>), 100 000")
print(timeit("func_2(arr)", "from __main__ import func_2, arr", number=N))

arr = arr1000000
N = 10

print("func_1(<1 000 000>), 100")
print(timeit("func_1(arr)", "from __main__ import func_1, arr", number=N))

print("func_2(<1 000 000>), 100")
print(timeit("func_2(arr)", "from __main__ import func_2, arr", number=N))


# python --version
# Python 3.9.2
# результаты для python3
# ---
# func_1(<100>), 100 000
# 0.8526363730197772
# func_2(<100>), 100 000
# 0.7195342039922252
# func_1(<1 000 000>), 100
# 0.9767448229831643
# func_2(<1 000 000>), 100
# 0.8591583400266245
# ---

# python2 --version
# Python 2.7.18

# результаты для python2
# ---
# func_1(<100>), 100 000
# 0.980357170105
# func_2(<100>), 100 000
# 0.845843076706
# func_1(<1 000 000>), 100
# 2.45475101471
# func_2(<1 000 000>), 100
# 1.35260820389
# ---
# Остается поблагодарить разработчиков python за отличную
# оптимизацию виртуальной машины, python3 стал вообще
# быстрее, чем python2, при этом циклы for для больших
# массивов стали работать практически
# со скоростью list comprehension, в то время как в python2
# разница для больших списков была почти двукратной
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

выполнено

117 changes: 86 additions & 31 deletions Урок 4. Практическое задание/task_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from timeit import timeit
from random import randint
from textwrap import dedent


def recursive_reverse(number):
Expand All @@ -21,28 +22,6 @@ def recursive_reverse(number):
return f'{str(number % 10)}{recursive_reverse(number // 10)}'


num_100 = randint(10000, 1000000)
num_1000 = randint(1000000, 10000000)
num_10000 = randint(100000000, 10000000000000)

print('Не оптимизированная функция recursive_reverse')
print(
timeit(
"recursive_reverse(num_100)",
setup='from __main__ import recursive_reverse, num_100',
number=10000))
print(
timeit(
"recursive_reverse(num_1000)",
setup='from __main__ import recursive_reverse, num_1000',
number=10000))
print(
timeit(
"recursive_reverse(num_10000)",
setup='from __main__ import recursive_reverse, num_10000',
number=10000))


def memoize(f):
cache = {}

Expand All @@ -63,19 +42,95 @@ def recursive_reverse_mem(number):
return f'{str(number % 10)}{recursive_reverse_mem(number // 10)}'


# Сначала сделаем числа разной длины, чтобы они порождали
# разное число рекурсивных вызовов
num_1 = 1
num_5 = 12345
num_10 = 1234567890


N = 1
print('Не оптимизированная функция recursive_reverse')
print(
timeit(
"recursive_reverse(num_1)",
setup='from __main__ import recursive_reverse, num_1',
number=N))
print(
timeit(
"recursive_reverse(num_5)",
setup='from __main__ import recursive_reverse, num_5',
number=N))
print(
timeit(
"recursive_reverse(num_10)",
setup='from __main__ import recursive_reverse, num_10',
number=N))
# ---
# Не оптимизированная функция recursive_reverse
# 0.5288310369942337
# 1.658883001015056
# 3.1454150729696266
# ---
# Видно, что время зависит от длины числа, то есть рекурсия происходит


print('Оптимизированная функция recursive_reverse_mem')
print(
timeit(
'recursive_reverse_mem(num_100)',
setup='from __main__ import recursive_reverse_mem, num_100',
number=10000))
'recursive_reverse_mem(num_1)',
setup='from __main__ import recursive_reverse_mem, num_1',
number=N))
print(
timeit(
'recursive_reverse_mem(num_1000)',
setup='from __main__ import recursive_reverse_mem, num_1000',
number=10000))
'recursive_reverse_mem(num_5)',
setup='from __main__ import recursive_reverse_mem, num_5',
number=N))
print(
timeit(
'recursive_reverse_mem(num_10)',
setup='from __main__ import recursive_reverse_mem, num_10',
number=N))
# ---
# Оптимизированная функция recursive_reverse_mem
# 0.17349300003843382
# 0.169658282015007
# 0.1851398529834114
# ---
# Странные результаты, совершенно не зависят от длины числа
# Можно предположить, что функция отрабатывает только
# в первый раз, а дальше мы имеем 999999 чтений из словаря.
# Сделаем аргументы случайными:

N = 1000000
print('Не оптимизированная функция recursive_reverse')
print(
timeit(
dedent('''
n = randint(10**8, 10**9)
recursive_reverse(n)'''),
setup=dedent('''
from random import randint
from __main__ import recursive_reverse'''),
number=N))

print('Оптимизированная функция recursive_reverse_mem')
print(
timeit(
'recursive_reverse_mem(num_10000)',
setup='from __main__ import recursive_reverse_mem, num_10000',
number=10000))
dedent('''
n = randint(10**8, 10**9)
recursive_reverse_mem(n)'''),
setup=dedent('''
from random import randint
from __main__ import recursive_reverse_mem'''),
number=N))

# ---
# Не оптимизированная функция recursive_reverse
# 3.585237400024198
# Оптимизированная функция recursive_reverse_mem
# 4.854937668016646
# ---

# Что и требовалось доказать, мемоизация в данном случае
# делает только хуже
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

мемоизация здесь просто не нужна
но нужна. если делать много вызовов

48 changes: 48 additions & 0 deletions Урок 4. Практическое задание/task_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

Сделайте вывод, какая из трех реализаций эффективнее и почему
"""
from cProfile import Profile
from pstats import Stats
from timeit import timeit


def revers(enter_num, revers_num=0):
Expand All @@ -34,3 +37,48 @@ def revers_3(enter_num):
revers_num = enter_num[::-1]
return revers_num


n = 846207850378
N = 100000

profiler = Profile()
profiler.enable()
for i in range(N):
revers(n)
revers_2(n)
revers_3(n)
profiler.disable()
Stats(profiler).strip_dirs().print_stats()
# ---
# 1500001 function calls (300001 primitive calls) in 0.744 seconds
#
# Random listing order was used
#
# ncalls tottime percall cumtime percall filename:lineno(function)
# 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
# 1300000/
# 100000 0.488 0.000 0.488 0.000 task_3.py:17(revers)
# 100000 0.209 0.000 0.209 0.000 task_3.py:27(revers_2)
# 100000 0.047 0.000 0.047 0.000 task_3.py:35(revers_3)
# ---


print("revers\t\t", timeit("revers(n)", globals=globals(), number=N))
print("revers_2\t", timeit("revers_2(n)", globals=globals(), number=N))
print("revers_3\t", timeit("revers_3(n)", globals=globals(), number=N))
# ---
# revers 0.29492326197214425
# revers_2 0.20011793699814007
# revers_3 0.03318392898654565
# ---

# Встроенная функция предсказуемо лидирует в двух испытаниях.
# Остается неясным, почему cProfile и timeit дают разное
# отношение времен для цикла и рекурсии, cProfile дает 2,
# а timeit дает 1.5 Мне кажется, доверять нужно timeit,
# как самому "тупому" и поэтому надежному методу.
#
# cProfile строит свою статистику на основе измерений вызовов
# всех функций. Очевидно, при таком подходе чаще всего
# вызывается revers_2(0). При усреднении это приводит
# к занижению результатов по времени.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Остается неясным, почему cProfile и timeit
cProfile - 1 запуск
timeit - number запусков

82 changes: 75 additions & 7 deletions Урок 4. Практическое задание/task_4.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,32 @@
Сделайте замеры и опишите, получилось ли у вас ускорить задачу.
"""

array = [1, 3, 1, 3, 4, 5, 1]
from timeit import timeit
from operator import itemgetter
from random import randint


def func_1():
array7 = [1, 3, 1, 3, 4, 5, 1]
# Добавим еще один массив побольше
array1000 = [randint(1, 100) for _ in range(1000)]


def func_1(): # O(n^2)
m = 0
num = 0
for i in array:
count = array.count(i)
for i in array: # O(n^2)
count = array.count(i) # O(n)
if count > m:
m = count
num = i
return f'Чаще всего встречается число {num}, ' \
f'оно появилось в массиве {m} раз(а)'


def func_2():
def func_2(): # O(n^2)
new_array = []
for el in array:
count2 = array.count(el)
for el in array: # O(n^2)
count2 = array.count(el) # O(n)
new_array.append(count2)

max_2 = max(new_array)
Expand All @@ -37,5 +44,66 @@ def func_2():
f'оно появилось в массиве {max_2} раз(а)'


def func_3(): # O(n)
# словарь {номер: число его повторений}
reg = {}
for el in array: # O(n)
# увеличиваем счетчик для данного значения
reg[el] = reg.get(el, 0) + 1 # O(1)
# ищем max не по ключу, а по значению
elem, max_2 = max(reg.items(), key=itemgetter(1))
return f'Чаще всего встречается число {elem}, ' \
f'оно появилось в массиве {max_2} раз(а)'


# Сначала пройдем много раз по маленькому массиву
array = array7
N = 10000
print(func_1())
print(timeit("func_1()", number=N, globals=globals()))
print(func_2())
print(timeit("func_2()", number=N, globals=globals()))
print(func_3())
print(timeit("func_3()", number=N, globals=globals()))
# output:
# ---
# Чаще всего встречается число 1, оно появилось в массиве 3 раз(а)
# 0.01287864800542593
# Чаще всего встречается число 1, оно появилось в массиве 3 раз(а)
# 0.01819467602763325
# Чаще всего встречается число 1, оно появилось в массиве 3 раз(а)
# 0.018526640022173524
# ---

# Самым быстрым оказывается алгоритм №1,
# самым медленным алгоритм №3.


# Затем несколько раз пройдем большой массив
array = array1000
N = 100
print(func_1())
print(timeit("func_1()", number=N, globals=globals()))
print(func_2())
print(timeit("func_2()", number=N, globals=globals()))
print(func_3())
print(timeit("func_3()", number=N, globals=globals()))
# output:
# ---
# Чаще всего встречается число 86, оно появилось в массиве 23 раз(а)
# 1.1288615339435637
# Чаще всего встречается число 86, оно появилось в массиве 23 раз(а)
# 1.1044497270486318
# Чаще всего встречается число 86, оно появилось в массиве 23 раз(а)
# 0.00952054699882865
# ---

# Здесь самым быстрым является алгоритм №3,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

здесь решается в одну строку - ф-ция max
посмотрите пример

# из-за того, что его линейная сложность
# проявляется на достаточно на больших n.
# Самым медленным оказывается алгоритм №1.

# Вопрос, почему тогда на маленьких массивах
# все наоборот? Я думаю, из-за того, что не
# успевает "окупиться" создание дополнительных
# объектов, списка для №2 и словаря для №3
Loading