- PVSM.RU - https://www.pvsm.ru -
Однажды меня посетила мысль, а что если попробовать решить первую задачу Проекта Эйлера всевозможными способами, но с условием, что решение должно быть в одну строку. В итоге получилось более пяти однострочных решений с применением Filter, Map, Reduce, Generator Expression и т.д. В этой статье я покажу то, к чему я пришёл.
Это моя первая статья. Стоит отнестись к ней настороженно. Уникальные решения будут оформлены в отдельные пункты. Менее уникальные - в подпункты.
Если выписать все натуральные числа меньше 10, кратные 3 или 5, то получим 3, 5, 6 и 9. Сумма этих чисел равна 23.
Найдите сумму всех чисел меньше 1000, кратных 3 или 5.
Источники: Оригинал [1] или Русскоязычное зеркало [2]
Прежде чем перейти непосредственно к однострочным решениям, разумно было бы упомянуть сначала стандартное, классическое решение:
result = 0
for i in range(1, 1000):
if i%3 == 0 or i%5 == 0:
result += i
print(result) # => 233168
Перебираем последовательность чисел от 1 до 999. Если перебираемое число делится на 3 или на 5 без остатка от деления, то прибавляем каждое такое число в заранее объявленную переменную result
.
print(sum(i for i in range(1, 1000) if i%3 == 0 or i%5 == 0)) # => 233168
Числа из последовательности от 1 до 999, делящиеся на 3 или на 5 без остатка от деления, собираются в генератор. Затем функция sum()
складывает содержимое генератора.
print(sum([i for i in range(1, 1000) if i%3 == 0 or i%5 == 0])) # => 233168
В отличии от предыдущего, здесь выражение дополнительно помещается в список. Стоило упомянуть этот вариант, так как он довольно часто встречается в различных статьях.
print(sum({i for i in range(1, 1000) if i%3 == 0 or i%5 == 0})) # => 233168
Тоже, что и в предыдущем, но вместо списка здесь множество.
print(sum(filter(lambda i: i%3 == 0 or i%5 == 0, range(1, 1000)))) # => 233168
Функция filter
схожа по принципу работы с выражением-генератором. Функция лямбда применяется к каждому элементу последовательности чисел от 1 до 999. Все числа последовательности, делящиеся на 3 или на 5 без остатка от деления, возвращаются, затем суммируются функцией sum()
.
print(sum(map(lambda i: i if i%3 == 0 or i%5 == 0 else 0, range(1, 1000)))) # => 233168
Перебираемые числа последовательности от 1 до 999, делящиеся на 3 или 5 без остатка от деления, остаются без изменений, все остальные числа заменяются на ноль. Полученная последовательность суммируется функцией sum()
.
from functools import reduce
print(reduce(lambda x, y: x+y if y%3 == 0 or y%5 == 0 else x, range(1, 1000), 0)) # => 233168
Из всей подборки, этот вариант "очень не очень". Как по степени реализации, так и по времени выполнения(но об этом попозже).
Если в reduce
указан инициализатор(в нашем случае ноль), то он становится накопителем. К нему по очереди прибавляются только те числа из последовательности от 1 до 999, которые делятся на 3 или на 5 без остатка от деления. Если из функции reduce
убрать инициализатор ноль, то инициализатором станет крайний левый элемент последовательности.
print(sum(set(range(3, 1000, 3)) | set(range(5, 1000, 5)))) # => 233168
Самое элегантное решение, как по красоте написания, так и по времени выполнения.
Последовательность чисел от 1 до 999, кратную трём, помещаем во множество и объединяем со множеством, содержащим в себе последовательность чисел от 1 до 999, кратную пяти. Содержимое, полученного множества суммируем функцией sum()
.
print(sum({*range(3, 1000, 3)} | {*range(5, 1000, 5)})) # => 233168
Похожий вариант на предыдущий, но, если использовать фигурные скобки, то последовательность чисел от 1 до 999, кратную трём и последовательность чисел от 1 до 999, кратную пяти, нужно распаковывать.
print(sum(set(range(3, 1000, 3)).union(range(5, 1000, 5)))) # => 233168
Создаём множество, с последовательностью чисел от 1 до 999, кратную трём и присоединяем к нему последовательность чисел от 1 до 999, кратную пяти. Затем функцией sum()
суммируем.
print(sum(set([*range(3, 1000, 3)] + [*range(5, 1000, 5)]))) # => 233168
По аналогии с предыдущими. Распаковываем последовательности чисел в списки. Складываем списки. Оборачиваем во множество. Затем суммируем функцией sum()
.
Если проверить скорость выполнения каждого однострочного решения в командной строке, при помощи timeit, получим следующие значения в микросекундах:
sum(i for i in range(1, 1000) if i%3 == 0 or i%5 == 0) # 203 usec
sum([i for i in range(1, 1000) if i%3 == 0 or i%5 == 0]) # 181 usec
sum({i for i in range(1, 1000) if i%3 == 0 or i%5 == 0}) # 189 usec
sum(filter(lambda i: i%3 == 0 or i%5 == 0, range(1, 1000))) # 306 usec
sum(map(lambda i: i if i%3 == 0 or i%5 == 0 else 0, range(1, 1000))) # 313 usec
reduce(lambda x, y: x+y if y%3 == 0 or y%5 == 0 else x, range(1, 1000), 0)# 324 usec
sum(set(range(3, 1000, 3)) | set(range(5, 1000, 5))) # 47 usec
sum({*range(3, 1000, 3)} | {*range(5, 1000, 5)}) # 47 usec
sum(set(range(3, 1000, 3)).union(range(5, 1000, 5))) # 41 usec
sum(set([*range(3, 1000, 3)] + [*range(5, 1000, 5)])) # 43 usec
Методика расчёта: python -m timeit "выражение"
Быстрее всего справились с задачей последние четыре варианта.
Всего получилось 5 уникальных + 5 не уникальных решений. Благодаря этой задаче у меня появилось более устойчивое понимание работы функций Filter, Map, Reduce. И если раньше я недоумевал, почему функцию Reduce убрали из основного модуля, то теперь я не сомневаюсь в правильности этого решения.
В статье я старался отойти от популярного шаблона повествования "точность на грани бесполезности". Где предложения набиты под завязку "тяжёлыми" терминами, а из знакомого там только союзы и предлоги. Не уверен, что у меня получилось.
Всем спасибо.
Автор:
axelthepop
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/369071
Ссылки в тексте:
[1] Оригинал: https://projecteuler.net/problem=1
[2] Русскоязычное зеркало: https://euler.jakumo.org/problems/view/1.html
[3] Источник: https://habr.com/ru/post/585176/?utm_source=habrahabr&utm_medium=rss&utm_campaign=585176
Нажмите здесь для печати.