Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Ерунда на itertools и генераторах

Я люблю itertools и генераторы в Пайтоне, но как-то по работе пока не удаётся их применить, поэтому, чтобы не завять, приходится время от времени писать всякую ерунду.

Прочитал тут на «Хабре», что дробь 1/998001 даёт последовательность из чисел от нуля до 997. Вот я и написал код с использованием генераторов и itertools, который берёт эту дробь и выводит из неё все числа последовательно, пока не попадутся числа с разницей не равной единице.

from decimal import Decimal, getcontext
from itertools import groupby, islice, izip, takewhile, chain, imap

getcontext().prec = 3000

n = (
    groupby(
        enumerate(
            islice(str(1 / Decimal(998001)), 2, None)
        ),
        lambda n: n[0] // 3)
    )

n = (int(''.join(n[1] for n in n[1])) for n in n)

n = chain(*takewhile(lambda n: n[1] - n[0] == 1, izip(n, n)))

print ', '.join(imap(str, n))

Кстати, для вычисления дроби с точностью до трёхтысячного знака, используется модуль decimal.

Подумалось, что многим, наверное, было интересно сравнить как выглядит эквивалентный код на Пайтоне без всех этих страшных (на взгляд большинства) ленивых выражений. А то некоторые ребята по моей вине уже думают, что Пайтон какой-то адский язык. Ничего подобного. Вот, посмотрите, более традиционное решение:

from decimal import Decimal, getcontext

getcontext().prec = 3000

number, prev, out = str(1 / Decimal(998001)), -1, []

for i in xrange(2, len(number), 3):
    curr = int(number[i:i+3])

    if curr - prev != 1: break

    prev = curr
    out.append(str(curr))

print ', '.join(out)
17 комментариев
Alexey Burlakov (profiles.google.com/gaius.julius) 2012

Очень, кстати, интересно, что есть 10 типов людей. Те, которые считают, что «there should be one-​-​ and preferably only one -​-​obvious way to do it» говорит о первом подходе (с итертулз, лямбдами и прочими радостями), и те, которые так же думают о «традиционном» решении (-:

А я теперь определиться не могу как мне больше нравится, а это значит что PEP20 всё врёт )-:

Sergey Solyanik (profiles.google.com/jankkhvej/) 2012

А есть какой-то способ посмотреть на машинный код этих программ? Интересно, насколько он будет запутан.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для profiles.google.com/jankkhvej/:

Насколько я знаю, компиляторов Пайтона не существует и вряд ли может существовать. Это слишком динамический язык, чтобы его можно было компилировать.

SowingSadness 2012

После двух листингов появился вопрос:
А зачем лаконичный код 2 листинга превращать в «не такой очевидный» код 1 листинга.
К тому же нет выигрыша ни по кол-ву подключенных модулей и ни по скорости выполнения(наверняка, но могу ошибаться). Разве что по количеству строк. Или это и было целью? -))

Евгений Степанищев (bolknote.ru) 2012

Комментарий для SowingSadness:

Первый абзац отвечает на этот вопрос же.

Есть выигрыш по скорости и простоте модификации, кстати, но для этого нужно уметь это читать, конечно.

Sergey Solyanik (profiles.google.com/jankkhvej/) 2012

Комментарий для Евгения Степанищева:

Ну вот какому-то реальному процессору, типа i386 или ARM, приходится же исполнять это. Вот и хотелось посмотреть на то, что он в итоге исполняет, и сделать вывод — существует ли смысл в таких конструкциях, умеют ли существующие процессоры эффективно работать с такими языками…
Perl-то, кстати, довольно эффективен в плане перевода его конструкций на реальные устройства.
А вот про Python тут кто-то может сказать, кто-то может уже смотрел в исходники?

Sergey Solyanik (profiles.google.com/jankkhvej/) 2012

Комментарий для Евгения Степанищева:

Кстати, а почему у вас в блоге время не в моём часовом поясе, и это никак не обозначено?

Евгений Степанищев (bolknote.ru) 2012

Комментарий для profiles.google.com/jankkhvej/:

умеют ли существующие процессоры эффективно работать с такими языками

Процессоры — нет. Процессоры-то тут причём? Это всё равно раскрывается в обычные циклы с условиями, процессорам всё равно какие там конструкции.

Кстати, а почему у вас в блоге время не в моём часовом поясе, и это никак не обозначено?

Я даже как-то теряюсь перед вашим вопросом. Наверное потому что это мой блог?

Fulcrum (fulc.ru) 2012

Комментарий для Евгения Степанищева:

Мне сложно читать первый пример из-за названий переменных, например:

n = (int(’’.join(n[1] for n in n[1])) for n in n)

Почему тут все n? :) В остальных строках кода, кстати, тоже. Во втором примере они хоть и сокращенные, но можно предположить по названиям, что они означают.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для fulc.ru:

Почему тут все n? :)

Ну, сначала там не «n» были, это уже для запутывания :)

Sergey Solyanik (profiles.google.com/jankkhvej/) 2012

Комментарий для Евгения Степанищева:

Я как то ожидал, что если я его читаю :), то время должно отображается в они часовом поясе. Тем более, что везде практически так и происходит.

Насчёт того, что неважно, какой процессор — ну как же ж?! А кто будет исполнять код-то?! Мерлин? Пушкин? ;)

Евгений Степанищев (bolknote.ru) 2012

Комментарий для profiles.google.com/jankkhvej/:

Тем более, что везде практически так и происходит.

Где, например?

Насчёт того, что неважно, какой процессор — ну как же ж?! А кто будет исполнять код-то?! Мерлин? Пушкин? ;)

Процессор. Я же написал — эти конструкции всё одно будут развёрнуты в циклы и условия. Всё равно процессору что там за конструкции. Вопрос, наверное, в том насколько эффективно будут развёрнуты. Но тут вопрос реализации, сами по себе конструкции каких-то ограничений в этом смысле не несут.

Fulcrum (fulc.ru) 2012

Комментарий для Евгения Степанищева:

Кстати, использование ленивых выражений вовсе не обязывает к написанию диких однострочников. Вполне можно писать что-то наподобие:

string = str(1 / Decimal(99800))
digits = islice(str(string, 2, None)
grouped_digits = groupby(enumerate(digits), lambda index, digit: index // 3)
grouped_digits = (data for grouper, data in grouped_digits)
numbers = (’’.join(digit for index, digit in group) for group in grouped_digits)

Ну и так далее. В сочетании с несокращенными названиями переменных получается, на мой взгляд, гораздо понятнее.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для fulc.ru:

Как будто цель в понятности. Первый абзац же раскрывает тему :)

Ну и использование промежуточных переменных всё ломает, так как, попытайся я встроить что-то в цепочку, мне придётся переименовывать переменные и ещё убедиться, что ниже они нигде не используются.

Fulcrum (fulc.ru) 2012

Комментарий для Евгения Степанищева:

Ну и использование промежуточных переменных всё ломает, так как, попытайся я встроить что-то в цепочку, мне придётся переименовывать переменные

Не очень понял. Если вставить что-то в цепочку в твоем примере, то придется (в общем случае) все равно переписывать то, что идет выше уровнем, потому что данные просто другие будут. В моем случае — в тех строчках, где данные другие, ты все равно будешь менять правую часть, ну и заодно переменные переименуешь. А с того момента, когда данные становятся такими же, все останется прежним.

и ещё убедиться, что ниже они нигде не используются.

Это уже другая задача, я опять не вижу связи с ленивыми выражениями. Вот ты не боишься, что у тебя во втором коде «prev» где-то ниже будет использоваться? Вообще, если название переменной описывает ее содержание (у меня не совсем так, «grouped_digits» используется два раза, но мне было лень думать, как назвать дополнительную переменную), то вероятность двойного использования переменной снизится. Также снизится и от разделения кода на независимые функции.

Sergey Solyanik (profiles.google.com/jankkhvej/) 2012

Комментарий для Евгения Степанищева:

Всё равно процессору что там за конструкции. Вопрос, наверное, в том насколько эффективно будут развёрнуты.

Я про это и спрашивал. Наверное, как-то не очевидно. Ну да ладно, погуглю ещё сам, спасибо за интересную тему.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для fulc.ru:

Ты, Володь, рушишь всю идею, вот я и сопротивляюсь :) Написать какую-то понятную ерунду — скучно, написать что-то непонятное — намного веселее, это же не продакшн, тут мне весело должно быть :)