Главная страница
Навигация по странице:

  • Вычисление булеана с помощью функционального программирования

  • Арифметические операции над списками

  • Реализация шифра Цезаря с помощью расширенного доступа по индексу и спискового включения

  • Поиск простых чисел с помощью решета Эратосфена

  • однострочники пайтон. Однострочники Python лаконичный и содержательный код by Кристи. Однострочники


    Скачать 4.44 Mb.
    НазваниеОднострочники
    Анкороднострочники пайтон
    Дата22.04.2022
    Размер4.44 Mb.
    Формат файлаpdf
    Имя файлаОднострочники Python лаконичный и содержательный код by Кристи.pdf
    ТипКнига
    #490720
    страница19 из 21
    1   ...   13   14   15   16   17   18   19   20   21
    Рис. 6.3. Рекурсивное вычисление расстояния Левенштейна для слов 'cat' и 'chello' путем вычисления сначала меньших шагов задачи
    Чтобы рекурсивно вычислить расстояние Левенштейна для слов 'cat'
    и 'chel lo'
    , сначала мы решаем более простые задачи вычисления (рекур- сивно).
    1. Вычисляем расстояние между суффиксами at и hello
    , поскольку если известно, как преобразовать at в hello
    , то можно легко преобразовать

    224
    Глава 6. Алгоритмы cat в chello
    , модифицируя сначала первый символ (или сохраняя его неизменным, если оба строковых значения начинаются с одного симво- ла). Если это расстояние равно 5, то можно сделать вывод, что расстоя- ние между cat и chello также не превышает 5, ведь можно получить одно из другого с помощью той же самой последовательности правок (оба слова начинаются с символа c
    , поэтому его редактировать не нужно).
    2. Вычисляем расстояние между суффиксами at и chello
    . Если оно равно 6, то можно сделать вывод, что расстояние между cat и chello не превышает 6 + 1 = 7, поскольку можно просто убрать символ c
    в на- чале первого слова (одна дополнительная операция). А затем повторно использовать то же самое решение для получения chello из at
    3. Вычисляем расстояние между cat и hello
    . Если оно равно 5, то мож- но сделать вывод, что расстояние между cat и chello не превышает
    5 + 1 = 6, поскольку можно просто вставить символ c
    в начале второго слова (одна дополнительная операция).
    А поскольку это все возможные операции над первым символом (замена, удаление, вставка), то расстояние Левенштейна между cat и chello равно минимальному из трех случаев 1, 2 и 3. Теперь подробнее рассмотрим три случая в листинге 6.4.
    Во-первых, мы рекурсивно вычисляем расстояние редактирования между a[1:]
    и b[1:]
    . Если ведущие символы a[0]
    и b[0]
    различаются, то необ- ходимо исправить ситуацию, заменив a[0]
    на b[0]
    , так что мы увеличиваем расстояние редактирования на единицу. При совпадении ведущих символов решение более простой задачи ls(a[1:],
    b[1:])
    совпадает с решением более сложной задачи ls(a,
    b)
    , как вы видели на рис. 6.3.
    Во-вторых, мы рекурсивно вычисляем расстояние между a[1:]
    и b
    . Допу- стим, нам известен результат вычисления (преобразования a[1:]
    в b
    ) — как теперь вычислить расстояние на один шаг дальше, от a
    до b
    ? Ответ: просто
    удалить первый символ a[0]
    из начала a
    , то есть произвести ровно на одну операцию больше. Благодаря этому мы свели более сложную задачу к более простой.
    В-третьих, мы рекурсивно вычисляем расстояние между a
    и b[1:]
    . До- пустим, нам известен результат вычисления (преобразования a
    в b[1:]
    ).
    Как теперь вычислить расстояние от a
    до b
    ? В данном случае мы просто проходим на шаг больше (от a
    до b[1:]
    до b
    ), вставляя символ b[0]
    в начале слова b[1:]
    , что увеличивает расстояние на единицу.

    Вычисление булеана с помощью функционального программирования
    225
    Наконец, мы просто берем минимальное расстояние редактирования из трех (замены первого символа, удаления первого символа и вставки перво- го символа).
    Данное однострочное решение опять демонстрирует важность навыков работы с рекурсией. Возможно, понимание рекурсии не дастся вам очень легко, но несомненно придет к вам по мере изучения множества задач на рекурсию, подобных этой.
    Вычисление булеана с помощью
    функционального программирования
    В этом разделе мы расскажем вам о важном математическом понятии бу- леана: множества всех подмножеств. Булеаны используются в статистике, теории множеств, функциональном программировании, теории вероятно- стей и алгоритмическом анализе.
    Общее описание
    Булеан (powerset) — это множество всех подмножеств заданного множе- ства s
    , включающее пустое множество
    {}
    , исходное множество s
    и все про- чие возможные подмножества исходного множества. Ниже даны несколько примеров.
    Пример 1:
    множество: s
    =
    {1}
    ;
    его булеан:
    P
    =
    {{},{1}}
    Пример 2:
    множество: s
    =
    {1,
    2}
    ;
    его булеан:
    P
    =
    {{},{1},{2},{1,2}}
    Пример 3:
    множество: s
    =
    {1,
    2,
    3}
    ;
    его булеан:
    P
    =
    {{},{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}}

    226
    Глава 6. Алгоритмы
    Для вычисления булеана P
    n
    множества s, состоящего из n элементов, можно использовать булеан P
    n –1
    подмножества s, состоящего из (n – 1) элемента.
    Пусть нам требуется вычислить булеан множества s
    =
    {1,
    2,
    3}
    1. Зададим начальное значение булеана P
    0
    с нулем элементов как P
    0
    = {{}}.
    Другими словами, это булеан пустого множества. Он содержит только само пустое множество.
    2. Для создания на основе булеана P
    n –1
    множества, состоящего из
    (n – 1) элемента булеана P
    n
    , состоящего из n элементов, возьмем один (произвольный) элемент x из множества s и включим все полу- чающиеся подмножества в больший булеан P
    n
    с помощью следующей процедуры.
    3. Проходим по всем множествам p в P
    n –1
    и создаем новые подмноже- ства, состоящие из объединения x и p. В результате получаем новое временное множество множеств T. Например, если P
    2
    = {{}, {1}, {2},
    {1,2}}, то мы создадим временное множество множеств T = {{3}, {1,3},
    {2,3}, {1,2,3}} путем добавления элемента x ко всем множествам из P
    2 4. Объединяем это новое множество множеств T с булеаном P
    n –1
    и полу- чаем булеан P
    n
    . Например, булеан P
    3
    получается путем объединения временного множества T с булеаном P
    2
    следующим образом: P
    3
    = TP
    2 5. Переходим на шаг 2 до тех пор, пока исходное множество s не окажется пустым.
    Ниже вас ждут более подробные пояснения этой стратегии.
    Функция reduce()
    Но сначала необходимо как следует разобраться с важной функцией Python, которую мы применим в нашем однострочнике: reduce()
    . Она встроена в Python 2, но создатели Python решили, что использовали ее настолько мало, что в Python 3 она включена не была, поэтому придется сначала им- портировать ее из библиотеки functools.
    Функция reduce()
    принимает три аргумента: reduce(функция,
    итериру-
    емый_объект,
    начальное_значение)
    . Аргумент
    функция
    определяет способ свертки двух значений x
    и y
    в одно (например, lambda x,
    y:
    x
    +
    y
    ). Таким об- разом можно в цикле свертывать два значения
    итерируемый_объект
    (второй аргумент) в одно до тех пор, пока в
    итерируемый_объект
    не останется только одно значение. Аргумент
    начальное_значение
    — необязательный, если его

    Вычисление булеана с помощью функционального программирования
    227
    не указать, Python будет использовать по умолчанию первое значение
    ите-
    рируемый_объект
    Например, при вызове reduce(lambda x,
    y:
    x
    +
    y,
    [0,
    1,
    2,
    3])
    производятся следующие вычисления:
    (((0
    +
    1)+
    2)+
    3)
    =
    6
    . Другими словами, сначала два значения x=0
    и y=1
    свертываются в их сумму x
    +
    y
    =
    0
    +
    1
    =
    1
    . Этот результат первого вызова лямбда-функции служит входными данными для второго ее вызова: x=1
    и y=2
    . В результате получается сумма x
    +
    y
    =
    1
    +
    2
    =
    3
    . На- конец, результат этого второго вызова лямбда-функции служит входными данными для третьего ее вызова: x=3
    и y=3
    . В результате получается сумма x
    +
    y
    =
    3
    +
    3
    =
    6
    В этом последнем примере, как вы могли заметить, значение x
    всегда было равно результату предыдущего вызова лямбда-функции. Аргумент x
    играет роль значения-накопителя, а аргумент y
    обновляемого значения из
    итери-
    руемый_объект
    . Такое поведение нацелено на «свертку» в цикле всех значений из
    итерируемый_объект
    в одно значение. Необязательный третий аргумент задает начальное значение для x
    . Все это позволяет описать агрегатор для
    последовательностей, как показано ниже в листинге 6.5.
    Арифметические операции над списками
    Прежде чем заняться непосредственно однострочником, вам нужно понимать, как работают еще две операции над списками. Первая из них — оператор кон- катенации списков
    +
    , склеивающий два списка. Например, результат операции
    [1,
    2]
    +
    [3,
    4]
    — новый список
    [1,
    2,
    3,
    4]
    . Вторая — оператор объединения
    |
    , производящий простую операцию объединения двух множеств. Например, результат выражения
    {1,
    2}
    |
    {3,
    4}
    — новое множество
    {1,
    2,
    3,
    4}
    Код
    В листинге 6.5 приведено однострочное решение для вычисления булеана заданного множества s.
    Листинг 6.5. Однострочное решение для вычисления булеана заданного множества
    ## Зависимости from functools import reduce
    ## Данные s = {1, 2, 3}
    ## Однострочник

    228
    Глава 6. Алгоритмы ps = lambda s: reduce(lambda P, x: P + [subset | {x} for subset in P], s, [set()])
    ## Результат print(ps(s))
    Угадайте, что вернет этот фрагмент кода!
    Принцип работы
    Идея данного однострочника заключается в том, чтобы начать формиро- вание булеана с пустого множества  и последовательно добавлять в него подмножества , пока их больше не останется.
    Изначально булеан содержит только пустое множество. На каждом шаге мы берем один элемент x
    из набора данных s
    и создаем новые подмноже- ства, получающиеся естественным образом путем добавления x
    в каждое из подмножеств булеана . Как вы уже видели чуть выше, размер булеана удваивается каждый раз, когда берется дополнительный элемент x
    из на- бора данных s
    . Таким образом можно наращивать булеан из n подмножеств по одному элементу набора данных за раз (но по n подмножеств за раз).
    Обратите внимание, что размер булеана растет экспоненциально: каждый новый элемент набора данных x
    приводит к удвоению размера булеана.
    Это неотъемлемое свойство булеанов: они стремительно заполняют любое хранилище — даже в случае относительно небольших наборов данных всего из нескольких десятков элементов.
    С помощью функции reduce()
    мы производим хранение текущего булеана в переменной
    P
    (изначально содержащей только пустое множество). С по- мощью спискового включения функция reduce()
    создает новые подмноже- ства — по одному для каждого существующего подмножества — и добавляет их в булеан
    P
    . В частности, она добавляет значение x
    из набора данных в каждое из подмножеств и тем самым удваивает размер булеана (который теперь содержит подмножества с элементом x
    набора данных и без него).
    Благодаря этому функция reduce()
    последовательно «сливает воедино» два элемента: булеан
    P
    и элемент x
    из набора данных.
    Поэтому результат нашего однострочника выглядит следующим образом:
    # Результат print(ps(s))
    # [set(), {1}, {2}, {1, 2}, {3}, {1, 3}, {2, 3}, {1, 2, 3}]

    Реализация шифра Цезаря с помощью расширенного доступа по индексу
    229
    Данный однострочник наглядно демонстрирует важность понимания лямб- да-функций, списковых включений и операций над множествами.
    Реализация шифра Цезаря с помощью
    расширенного доступа по индексу
    и спискового включения
    В этом разделе мы расскажем вам о старинной методике шифрования —
    шифре Цезаря, — с помощью которого сам Гай Юлий Цезарь делал личные сообщения непонятными для врагов. К сожалению, шифр Цезаря слишком легко взламывается и никакой настоящей защиты не дает, но часто служит для развлечения и скрытия содержимого форумов
    (тех, которые можно найти в интернете, а не в Древнем Риме), которое необходимо защитить от глаз наивных пользователей.
    Общее описание
    В основе шифра Цезаря лежит идея сдвига шифруемых символов на фик- сированное количество позиций в алфавите. Далее мы рассмотрим один из частных случаев шифра Цезаря — алгоритм ROT13.
    ROT13 — простой алгоритм шифрования, используемый на многих форумах
    (например, Reddit) для защиты от спойлеров или скрытия смысла дискус- сии от новичков. Дешифровка алгоритма ROT13 проста — злоумышленник может взломать ваш код с помощью вероятностного анализа распределения букв в зашифрованном тексте, даже не зная, на сколько позиций сдвинуты символы. Никогда не следует использовать этот алгоритм для настоящего шифрования сообщений! Тем не менее существует немало приложений алгоритма ROT13:
    скрытие ответов на головоломки на интернет-форумах;
    скрытие возможных спойлеров на фильмы или книги;
    высмеивание других слабых алгоритмов шифрования: «56-битный алгоритм DES по крайней мере лучше, чем ROT13»;
    скрытие адресов электронной почты на сайтах от 99,999 % спам-ботов.
    Таким образом, ROT13 — скорее популярная дежурная шутка в интернет- культуре и образовательный инструмент, а не серьезный шифр.

    230
    Глава 6. Алгоритмы
    Этот алгоритм можно объяснить одной фразой: ROT13 = сдвиг шифруемой
    строки символов на 13 позиций (по модулю 26) в алфавите из 26 символов
    (рис. 6.4).
    Рис. 6.4. Таблица демонстрирует, как зашифровывается и расшифровывается каждый из символов алфавита при использовании алгоритма ROT13
    Другими словами, каждый символ сдвигается на 13 позиций по алфавиту.
    При выходе за пределы последнего символа, z, происходит переход к перво- му символу алфавита, a.
    Код
    В листинге 6.6 мы создаем однострочник для шифрования строки s
    с по- мощью алгоритма ROT13!
    Листинг 6.6. Однострочное решение для шифрования строки с помощью алгоритма ROT13
    ## Данные abc = "abcdefghijklmnopqrstuvwxyz"
    s = "xthexrussiansxarexcoming"
    ## Однострочник rt13 = lambda x: "".join([abc[(abc.find(c) + 13) % 26] for c in x])
    ## Результат print(rt13(s))
    print(rt13(rt13(s)))
    Воспользуйтесь рис. 6.4, чтобы взломать этот шифр: каковы результаты вы- полнения данного фрагмента кода?

    Реализация шифра Цезаря с помощью расширенного доступа по индексу
    231
    Принцип работы
    Наше однострочное решение шифрует каждую из букв по отдельности, сдви- гая ее на 13 позиций вправо по алфавиту, хранящемуся в переменной abc
    , после чего создает список этих зашифрованных букв и объединяет элементы этого списка в целую зашифрованную фразу x
    Рассмотрим подробнее способ шифрования каждой из букв. Для создания списка зашифрованных букв используется списковое включение (см. гла- ву 2), каждая буква c
    заменяется буквой, расположенной на 13 позиций даль- ше в алфавите. При этом важно предотвратить выход за пределы алфавита для букв, где индекс >= 13. Например, при сдвиге буквы z с индексом 25 на
    13 позиций получается недопустимый для алфавита индекс 25 + 13 = 38.
    Для решения этой проблемы мы используем оператор сложения по модулю, чтобы даже при выходе за максимальный индекс 25 для буквы z шифрование продолжалось, если индекс = 0 (буква a
    ). И продолжаем сдвигать вправо на оставшиеся из 13 позиций, которые еще не были учтены до перехода в начало алфавита (рис. 6.5). Например, буква z сдвигается на 13 позиций до индекса 38 по модулю 26 (в виде кода Python:
    38%26
    ), то есть до индекса 12 (буквы m
    ).
    Рис. 6.5. Предотвращаем выход за пределы алфавита, начиная отсчет индекса заново с 0, в результате чего получается такая последовательность сдвига:
    25 > 0 > 1 > … > 12
    Вот главная часть кода, описывающая сдвиг каждого символа c
    на 13 по- зиций:
    abc[(abc.find(c) + 13) % 26]
    Во-первых, мы находим индекс символа c
    в алфавите abc
    . Во-вторых, сдви- гаем этот индекс, прибавляя целое число 13 к индексу символа c
    в алфавите abc
    , с учетом нашего приема с модулем 26 (как пояснялось в предыдущих абзацах).

    232
    Глава 6. Алгоритмы
    Результат выполнения кода однострочника выглядит следующим образом:
    ## Результат print(rt13(s))
    # kgurkehffvnafknerkpbzvat print(rt13(rt13(s)))
    # xthexrussiansxarexcoming
    Резюмируя: вы изучили частный случай шифра Цезаря, алгоритм ROT13, при котором каждая буква в строке сдвигается на 13 позиций в алфавите.
    При повторном сдвиге еще на 13 позиций индекса (13+13=26) получается исходная буква, так что для шифрования и дешифрования применяется один алгоритм.
    Поиск простых чисел с помощью
    решета Эратосфена
    Поиск простых чисел играет важнейшую роль на практике, в частности в криптографии. Многие методы с открытым ключом безопасны (с точки зрения криптографии) лишь потому, что вычисление простых множителей больших чисел — трудоемкий и медленный процесс. Мы создадим одно- строчник, отыскивающий все простые числа в заданном диапазоне с помо- щью одного древнего алгоритма.
    Общее описание
    Простое число n — целое число, которое не делится без остатка ни на какое целое число, кроме самого себя и 1. Другими словами, для простого числа n не существует двух целых чисел a > 1 и b > 1, чье произведение равнялось бы ему: ab = n.
    Допустим, мы хотим проверить, является ли заданное число n простым.
    Начнем с «наивного» алгоритма поиска простых чисел (листинг 6.7).
    Листинг 6.7.
    «
    Наивная
    »
    реализация проверки заданного числа n на простоту def prime(n):

    for i in range(2,n):
     if n % i == 0:
    return False

    Поиск простых чисел с помощью решета Эратосфена
    233
    return True print(prime(10))
    # False print(prime(11))
    # True print(prime(7919))
    # True
    Данный алгоритм проверяет, делится ли n
    на каждое из чисел от 2 до n–1
     без остатка . Например, при проверке на простоту числа n
    =
    10
    алгоритм быстро обнаруживает, что выражение n
    %
    i
    ==
    0
    равно
    True при i
    =
    2
    . Алгоритм нашел число i
    , являющееся делителем n
    , поэтому n
    не может быть простым числом. В данном случае алгоритм прерывает все дальнейшие вычисления и возвращает
    False
    Временная сложность проверки отдельного числа совпадает (точнее, линей- но зависит) с n
    : при наихудшем сценарии алгоритму понадобится n
    итераций цикла, чтобы проверить, простое ли n
    Пусть нам нужно вычислить все простые числа от 2 до определенного максимального числа m
    . Для этого можно просто повторить проверку из листинга 6.7 m–1
    раз (листинг 6.8). Однако вычислительные затраты при этом колоссальны.
    1   ...   13   14   15   16   17   18   19   20   21


    написать администратору сайта