@DennyD314

Почему в решениях с одинаковой сложностью существенная разница во времени расчета?

Всем привет!
Недавно встретилась довольно простая задача: дан массив целых чисел и число "a", нужно найти первое ближайшее к "a" число в массиве.
Сразу пришел на ум не самый оптимальный вариант - посчитать для всех чисел в массиве модуль разности с a, найти минимальный и за третий проход остановиться при совпадении.
Потом понял, что решить задачу можно еще как минимум 3 способами (через key в min, через reduce, через простой самописный алгоритм) и решил измерить время расчета для каждого способа.
def time_deco(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = datetime.now()
        res = func(*args, **kwargs)
        return (datetime.now() - start).total_seconds()
    return wrapper

 

@time_deco
def with_min(a, l):
    return min(l, key=lambda x: abs(a-x))

 

@time_deco
def with_reduce(a, l):
    return reduce(lambda x, y: x if abs(a-y) >= abs(a - x) else y, l)

 

@time_deco
def with_map(a, l):
    z = min(map(lambda x: abs(a-x), l))
    for i in l:
        if abs(a-i) == z:
            return i

 

@time_deco
def traditional_way(a, l):
    value = l[0]
    i = abs(a - value)
    for item in l:
        if abs(a - item) < i:
            i = abs(a - item)
            value = item
    return value
 

my_funcs = [with_min, with_reduce, with_map, traditional_way]

 

a = random.randint(0, 500000000)

 
random_list_100 = [random.randint(0, 100) for _ in xrange(100)]
random_list_100k = [random.randint(0, 100000) for _ in xrange(100000)]
random_list_1kk = [random.randint(0, 1000000) for _ in xrange(1000000)]
random_list_100kk = [random.randint(0, 100000000) for _ in xrange(100000000)]
random_list_200kk = [random.randint(0, 200000000) for _ in xrange(200000000)]
random_list_300kk = [random.randint(0, 300000000) for _ in xrange(300000000)]

 
for_df = dict()


for func in my_funcs:
    for_df[func.__name__] = [func(a, random_list_100),
                             func(a, random_list_100k),
                             func(a, random_list_1kk),
                             func(a, random_list_100kk),
                             func(a, random_list_200kk),
                             func(a, random_list_300kk),
                            ]

 
df = pd.DataFrame(data=for_df)


Результаты оказались довольно странными для меня:
5c6bbc14029e3051209645.jpeg

Я не могу понять, почему min и reduce считаются в разы дольше чем самописный вариант, ведь нахождение нужного элемента во всех случаях производится за 1 проход, а min, на сколько я понимаю, работает так же как я расписывал в traditional_way.
  • Вопрос задан
  • 273 просмотра
Решения вопроса 1
@res2001
Developer, ex-admin
Виноваты накладные расходы.
В traditional_way они минимальны.
Замените лямбды вычислением промежуточного массива содержащего abs(a-x) в остальных случаях и получите дополнительный прирост производительности.
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
jcmvbkbc
@jcmvbkbc
"I'm here to consult you" © Dogbert
Почему в решениях с одинаковой сложностью существенная разница во времени расчета?

Я отвечу на вопрос из заголовка: потому что сложность алгоритма говорит о том, как он будет вести себя при неограниченном увеличении размерностей входных данных. И больше ни о чём. Т.е. нельзя имея два линейных алгоритма сказать, что они будут работать одинаковое время. Но можно имея линейный и квадратичный алгоритм сказать, что начиная с какого-то момента линейный всегда будет работать быстрее.
Ответ написан
@dmshar
"Я вам не скажу за всю Одессу" :-), но давайте сравним только with_map(a, l) и traditional_way(a, l). В обоих функциях присутствует цикл (for item in l:) который выполняется примерно за одинаковое время. Но with_map содержит еще и строку
z = min(map(lambda x: abs(a-x), l)) , которая сначала выполняет map(lambda x: abs(a-x), l) -
еще один проход по массиву, а в полученном результате ищет min - т.е. пробегает по списку еще раз. Итого - три прохода по массиву, вместо одного в traditional_way. С остальными функциями - примерно та-же история. (Например min(l, key=lambda x: abs(a-x)) - это как минимум два прохода по вашему списку).
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы