python

Необычная система счисления в bc

В моём временно любимом bc есть интересная, но не сразу понятная особенность работы с числами. Даже в режиме ввода десятичных чисел принимаются буквы в верхнем регистре, то есть число может состоять из цифр в диапазонах 0…9 и A…Z. Буквы при этом обозначают цифры от десяти до тридцати пяти.

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

Расшифровка слов LEET и CODE в числовые значения (21,14,29 и 12,0,13,14) и их суммирование: 22569 и 12144.

Мне стало интересно решить задачу конвертации десятичных чисел в такую необычную систему. Из самого принципа следует, что десятичное число может быть представлено несколькими способами, так что придумать можно куда больше одного алгоритма.

Сегодня на работе, пока обедали с братишкой, пытались в режиме обсуждения решить задачу в уме. Потратили на это почти весь обед, в конце я перенёс то, что придумалось, в код программы:

def convert(num):
    if num == 0: return 0
    out, orig_num = "", num

    while num:
        num, r = divmod(num, 10)
        v = min(2 if r > 5 else 3, num)
        if (digit := v * 10 + r) > 9:
            out = chr(digit - 10 + ord('A')) + out
        else:
            out = str(digit) + out

        num -= v

    return out if len(out) < len(str(orig_num)) else orig_num

А потом мы решили развлечься ещё одним способом — аккуратно сформулировать эту задачу в тексте и решить её искусственным интеллектом. Взяли топовые сети — ЧатГПТ, ДипСик и так далее.

Я намеренно не показываю получившийся промпт, вдруг вы захотите тоже попробовать, но скажу сразу — с первого или даже пятого раза сформулировать задачу так, чтобы хоть какая-нибудь нейросеть решила её правильно у нас не получилось. Я, в итоге, сдался, а братишка добился-таки код, который её решает, от ДипСика. Листинг, правда, получился вдвое длиннее, чем у меня.

Из этого можно сделать важный и почему-то не всем очевидный вывод: иногда задачу проще решить самому, потому что сформулировать её нейросети — очень сложное занятие.

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

Оставить комментарий

Возвращение плагина Pogoda​Status​Bar

У меня есть традиция — время от времени чинить мой старенький и порядком уже поднадоевший мне плагин для редактора «Саблайм 3». Плагин называется Pogoda​Status​Bar, потому что он когда-то показывал пробки и погоду с сервиса «Яндекс.Погоды».

Эпоха бесплатных сервисов кончилась и «Яндекс» постепенно стал закрывал все те ходы, которыми я пользовался, чтобы получать эти данные. Я искал новые, но в какой-то момент дрогнул и перешёл на «Гисметео».

Всё нормально работало, но в какой-то момент плагин снова сломался. Если ничего не путаю, я предпринял попытку его починить, но у меня с наскоку не вышло и пришлось отложить эту проблему в долгий ящик — были другие дела.

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

Надо сказать, что я не знаю как правильно отлаживать плагины в «Саблайме». В документации ничего не нашёл, поэтому написал небольшую обвязку и запускаю свой плагин просто из командной строки. Но в этот раз этот способ не сработал — в обвязке плагин работал, а внутри «Сайблайма» — нет.

Я уже с таким сталкивался. У «Саблайма» внутри две версии «Пайтона» — 3.3 и 3.8. Обе, в общем-то, уже старые. Мой плагин запускается с версией 3.3 и у меня никак не дойдут руки залезть в общий репозиторий плагинов и переписать для моего плагина версию «Пайтона».

Таким образом между «Пайтоном» у меня в командной строке и внутри «Сайблайма» — десять версий. Иногда я использую в коде что-то, чего «Сайблайм» не понимает.

И вот непонятно как об этом узнавать. Можно, думаю, раздобыть старенький «Пайтон» в контейнере, но лень.

Обычно я просто пристально смотрю на код, пока не понимаю как его исправить. А в этот раз так не вышло. Мне несколько раз казалось, что я понимаю в чём дело, я коммитил новый код, собирал релиз, дожидался, пока тулчейн репозитория плагинов «Саблайма» увидит новую версию, обновлялся и не получал положительного результата.

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

С этим я опять поленился разбираться, вместо этого попытался отредактировать код прямо в архиве через консольный файловый менеджер mc. Неожиданно это сработало. Так я узнал, что ошибка была в том, что я использую вызов html.unescape, который появился только в «Пайтоне» 3.4. Пришлось заменить его на вполне устроивший меня в этом месте replace('&quot;', '"') и всё заработало.

Скриншот редактора Sublime Text с открытым файлом PogodaStatusBar.py, содержащим Python-код для получения данных о регионе через API Яндекс.Погоды и настройки таймера обновления.
Код плагина, открытый через редактор консольного файлового менеджера

4 комментария

«Киберслав»

Вчера вышла первая серия отечественного сериала «Киберслав». Я выхода первой серии ждал несколько лет, с тех пор как впервые увидел тизер. Очень боялся, что результат окажется очень слабым, всё-таки какого-то сильного сюжетного материала не обещали — сериал не по книге или готовому миру от какого-то нибудь мэтра, это реализация идеи «давайте объединим славян и киберпанк».

Но обошлось, смотрится отлично.

В заставке серии, кстати, используется идея, в чём-то похожая на «дождь» из «Матрицы» Вачовски — изображение составленное из символов. Только в «Кибреславе» используется другая цветовая гамма и буквы старославянской кириллицы. Мне настолько понравилась эта отсылка, что я сделал свою консольную версию «дождя» из «Матрицы», но в стиле «Киберслава».

Заодно посмотрел как модуль curses в «Пайтоне» позволяет облегчить работу в консоли. Много про него слышал, но не доводилось попробовать.

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

2 комментария

Замена цифр

Придумал тут штуку интересную. Как у я уже писал ранее, полюбилось мне что-то писать время с минутами в верхнем индексе. Для этого мне пришлось решить проблему ввода всего набора таких символов.

Сначала я сделал достаточно просто, — отредактировал раскладку, которой я пользуюсь. Мне не нравилось это решение, так как пришлось выкинуть из неё кое-какие полезные символы, которые мне бы тоже пригодились.

А сегодня мне пришло в голову решение лучше — попробовать обойтись функцией «Замены текста», которая встроена в «МакОСь». Мысль простая — заменять двоеточие и две цифры на то же самое, но без двоеточия и в верхнем индексе. Сначала я пытался набрать всё нужное руками, а потом случайно нашёл функцию импорта.

Настройки ввода текста: источники Russian - Ilya Birman Typography и English - Ily..., кнопки «Изменить...» и «Замены текста...», слева — опции быстрого переключения пользователей.

Формат для импорта оказался довольно простым. После того как я в нём разобрался, накидал небольшую программу на «Пайтоне», которая мне за мгновение сформировала нужный файл.

print("""
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>""")

trans = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹")

for i in range(0, 60):
	print(f"""
	<dict>
		<key>phrase</key>
		<string>{f"{i:02d}".translate(trans)}</string>
		<key>shortcut</key>
		<string>:{i:02d}</string>
	</dict>""")


print("</array></plist>")

Чтобы его импортировать, нужно перейти в «Системные настройки…», оттуда в  «Клавитуру», там нажать на кнопку «Замены текста…» и перетащить на открывшийся список получившийся файл.

После этого можно проверить что получилось, — если написать в любом месте «13:00», оно должно само преобразоваться в «13⁰⁰».

9 комментариев

И опять чинил PogodaStatusBar

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

В общем, прошла неделя и мне снова прикрыли лавочку — урл «Гисметео», который я использовал, чтобы определять айди населённого пункта по названию вдруг меня забанил. На любой запрос выдаётся «доступ запрещён». Да что ж такое!

Полез я снова смотреть погодный плагин, смотреть как сделано там. Он же как-то определяет текущий населённый пункт, значит и я как-то могу получить эти данные. Оказалось, что есть у «Гисметео» ещё один сервис — services.gismeteo.net/inform-service/inf_chrome/cities/, который определяет местоположение по АйПи (ошибается в моём случае) или по координатам.

Координаты у меня уже есть, я их могу забрать у «Яндекса», надо только распарсить:

@functools.cache
def _getRegionData(self):
    try:
        url = "https://yandex.ru/tune/geo/"
        content = urllib.request.urlopen(url).read().decode('utf-8')
        region_data = re.search(r'data-bem="([^"]+coords[^"]+)"', content).group(1)
        parsed_region_data = json.loads(html.unescape(region_data))

        # {'id': xx, 'region': 'XXXXX', 'coords': ['XX.XXXXX', 'XX.XXXXX'], 'accuracy': 'XXXXXX'}
        return parsed_region_data['checkbox']['auto']
    except (IOError, AttributeError):
        return None

В итоге, получилась такая схема: в «Яндексе» получаем координаты и айди населённого пункта у «Яндекса», по этому айди получаем информацию по пробкам. Далее по координатам получаем айди населённого пункта у «Гисметео», по которому получаем погоду.

Ей-богу я в полушаге от того, чтобы прекратить борьбу и забросить проект.

6 комментариев

Опять чинил PogodaStatusBar

Сегодня участвовал в довольно скучной видеоконференции. Моё участие там было хоть и необходимое, но небольшое. А чтобы не одуреть от безделья, пока я слушал всю эту скуку, решил чем-то занять руки. Давно хотел починить свой плагин для «Саблайма» «PogodaStatusBar», этим и занялся.

Строка состояния редактора кода: город Казань, температура 29°C, иконка солнца с облаком, число 3, зелёный индикатор, строка 127, столбец 1.
Внешний вид плагина на настоящий момент

Плагин в строке статуса показывает текущую погоду и пробки в вашем городе. Много лет данные для этого я брал с неофициального урла «Яндекса», но компания решила коммерциализировать погоду и, сколько я не искал новый способ получить её бесплатно, так и не нашёл.

Пробки, кстати, тоже перестали нормально работать. Когда-то регион определялся автоматически, но это сломали. Я чинил это через парсинг страницы погоды, но теперь и это не работает — «Яндекс» борется с автоматическими роботами. Поэтому пришлось искать другой способ.

Я перебрал несколько малоизвестных сервисов, пока, наконец, не увидел, что страница yandex.ru/tune/geo/ содержит в себе необходимый номер. Этого хватило, чтобы починить показ пробок.

С «Гисметео» пришлось повозиться. Не нашёл официальную информацию о каких-либо АПИ, которые можно вызвать без токена, но внутри погодного плагина для браузера нашёлся адрес services.gismeteo.ru/inform-service/inf_chrome/forecast. В нём есть всё что нужно — текущая температура и закодированная иконка погоды.

Определить номер города в формате «Гисметео» помогает сервис www.gismeteo.ru/rmq/search/{название}/1/, куда я подставляю название, полученное от «Яндекс.Пробок» — там он возвращается вместе с остальными данными.

Оставалось только расшифровать иконку погоды. В документации есть часть описания (остаток можно понять, скачав с «Гисметео» значки погоды), выглядит всё несложно. Например, d.r3 означает «день» (day) и «дождь максимальной интенсивности» (rain 3).

Поскольку у меня Юникод, в нём есть не все необходимые значки, я сделал вот такой объект, содержащий признаки, которые позволяют мне выбрать какую иконку показать:

_icons = {
    "🌞": {"d"}, # day
    "🌙": {"n"}, # nite
    "☁️": {"n", "c"}, # night, cloud
    "🌧": {"rs", "c", "r"}, # rain+snow, cloud, rain
    "🌤": {"d", "c"}, # day, cloud
    "🌦": {"d", "r"}, # day, rain
    "🌩️": {"c", "st"}, # #day, storm
    "⛈️": {"c", "st", "r"}, # cloud, storm, rain
    "🌨": {"c", "s"}, # cloud, storm
    "💨": {"mist"}, # mist
    "⚡️": {"st"}, # storm
}

И вот такой простой алгоритм для выбора иконки:

def _getStatus(self, coded_weather):
    codes = {x.strip("0123456789") for x in coded_weather.split(".")}

    max_icon = None
    max_score = 0

    for icon, code in self._icons.items():
        result = codes & code
        score = sum(len(x) for x in result)
        if score > max_score:
            max_icon, max_score = icon, score

    return max_icon

Идея простая — чем больше значков совпало, тем лучше; если совпал значок с более длинным названием (st, mist), считаем его более конкретным и важным.

4 комментария

Графики в стиле xkcd

xkcd — культовая серия веб-комиксов. Когда-то я их обожал, но потом их перестали переводить, а мой английский в то время было недостаточно хорош, чтобы понимать их без посредников. Сейчас, наверное, можно было бы попытаться их читать, но уже нет желания.

У xkcd очень узнаваемый стиль рисовки и, когда я их вдруг встречаю, они по-прежнему вызывают у меня улыбку. Сегодня я узнал, что в «Пайтоне» есть возможность рисовать графики в этом стиле. Надо добавить всего одну строку — вызов метода plt.xkcd().

Вот код:

import matplotlib.pyplot as plt
import numpy as np

t = np.linspace(0, 2*np.pi, 1000)
x = 16 * np.sin(t)**3
y = 13 * np.cos(t) - 5 * np.cos(2*t) - 2 * np.cos(3*t) - np.cos(4*t)

plt.xkcd()

plt.figure(figsize=(8, 6))
plt.plot(x, y, color='b')
plt.title('cos(…) = ?')
plt.show()

А вот результат:

График синей кривой в форме сердца на координатной плоскости с осями от -15 до 15 и подписью «cos(...) = ?» над графиком.

Оставить комментарий

Шутка, которая не работает

Тут в чате бывших коллег, где мы постим мемы, пробежала картинка, которая больше повеселила меня не смыслом, который в неё вкладывал автор, а тем, что на системе, под которую написан код на ней (Виндоуз, очевидно), она не выполнится.

Проблема тут в том, что вызов os.remove умеет удалять только файл, тогда как System32 — папка, знакомая многим пользователям Виндоуз. Я не уверен точно, но думаю, что при её удалении система будет сопротивляться. Она, вроде, как-то защищена от такого.

Возможно, кстати, код и не предполагалось делать рабочим, а шутка немного тоньше — ведь вызов os.remove можно прочитать как «удалить операционную систему», не знаю, я в любом случае хотел рассказать не об этом.

Дело в том, что шутка написана под ОС, под которой она работать не будет, но смешно то, что под ОС, на которую она рассчитана не была, она вполне может заработать.

В Линуксе или МакОСи надо в любом месте, где будет запускаться эта программа, создать папку C:, в ней — папку Windows, а в ней — файл System32, который и удалится, если выпадет искомое число на рандомайзере.

Скриншот кода russian_roulette.py: импорты random, os. Проверка случайного числа 0-6. Если 1 — удаление C:\Windows\System32.

4 комментария

Анекдот на Пайтоне

У меня есть друг, которому я регулярно шлю шутки про «Вархаммер». Сам в этой теме не разбираюсь, но хочется думать, что его они веселят. А я — тот самый друг, которому шлют то же, но на тему программирования. Пару раз даже присылали мои собственные шутеечки.

Вчера прислали известный неприличный анекдот про Василия Ивановича и Петьку, но написанный на «Пайтоне». И хотя у меня довольно пуританский блог, анекдот так вырвиглазно написан, что пришлось его переписать.

Вот что мне прислали:

class Chapaev:
    nyuans = True
    def fuck(Class):
        Class.nyuans = True
        Class.ass.append('Dick')

class Petyka:
    nyuans = False
    ass = []

if Chapaev.nyuans == True and Petyka.nyuans == False:
    Chapaev.fuck(Petyka)

Как по мне, плохо написано примерно всё, мне очень не понравилось, так что держите, вот вам моя версия, которая, кстати, как мне кажется, более точно отражает суть анекдота:

Chapaev = []
Petka   = []
dick    = [20, Chapaev]

Petka.insert(*dick)

isNuance = Chapaev in Petka and Petka not in Chapaev
print(isNuance) // True

Ну и пока у меня ещё был запал, переписал на ПХП известнейший мем из фильма «ДМБ»:

class Gopher implements Countable
{
    public function count(): int
    {
        return 0;
    }
}

$gopher = new Gopher;
var_dump(count($gopher)); // 0
var_dump(empty($gopher)); // false

2 комментария

Починил PogodaStatusBar

Обратил внимание, что мой погодно-пробочный плагин для «Саблайма» сломался — ничего не показывает. Я заметил не сразу, сейчас редко запускаю этот редактор. Ребята, которые пользуются регулярно, говорят, что сломался довольно давно.

Строка статуса с иконкой терминала, текстом «Казань | +20°C ☀️⛅ | 3 🍏, Line 1, Column 1» на тёмном фоне.
Строка статуса: в Казани +20°C, пробки три балла

Пришлось исправить две вещи — во-первых, импорт одного из модулей перестал работать, это произошло, как я думаю, с обновлением интерпретатора, во-вторых, АПИ «Яндекса», которое я использую, перестало корректно определять регион, в котором надо показать погоду. Пришлось искать обходные манёвры.

Остался ещё один баг — если «Яндекс» не отдаёт информацию о пробках (в некоторых небольших городах такой информации нет), то плагин работать не будет. Не знаю, стоит ли это править, всё-таки популярностью у него невелика́ и делал я его больше для себя.

2 комментария

InlineC

Продолжая тему интерфейсов к языку Си, кстати будет упомянуть появившийся недавно модуль inlinec для Пайтона. Он позволяет вставлять в программу на Пайтоне Си-код довольно естественным способом. Вот пример из документации:

# coding: inlinec
from inlinec import inlinec

@inlinec
def Q_rsqrt(number):
    float Q_rsqrt( float number )
    {
        long i;
        float x2, y;
        const float threehalfs = 1.5F;

        x2 = number * 0.5F;
        y  = number;
        i  = * ( long * ) &y;
        i  = 0x5f3759df - ( i >> 1 );
        y  = * ( float * ) &i;
        y  = y * ( threehalfs - ( x2 * y * y ) );

        return y;
    }

print(Q_rsqrt(1.234))

Я проверил, это действительно работает. Тут вычисляется так называемый быстрый обратный квадратный корень от числа 1,234.

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

Куда интереснее разобраться как это всё работает. Декоратор @inlinec тут ничего не делает. Судя по коду модуля, он возвращает функцию как есть и служит маркером, что ниже пойдёт сишная функция. Всё работу выполняет кодек, который подключается первой строкой.

Приём не новый, несколько лет назад я про него уже рассказывал — в Пайтоне в специальном магическом комментарии можно указать кодек, который пропускает через себя текст программы. Обычно он используется для программирования в кодировках, отличных от ЮТФ-8, но ничего не мешает использовать его иначе.

Итак, код перед выполнением скармливается специальному кодеку inlinec, который разбирает программу, находит в ней метки-декораторы, компилирует вставки на Си через gcc и заменяет тело на вызовы скомпилированного через библиотеку cffi.

Оставить комментарий

PythonFuck: разбор

Как я писал в конце прошлого года, есть такое развлечение — не используя алфовитно-цифровые символы, писать осмысленные программы на разных языках программирования. Для конкретного языка это называется «имя языка»+«Fuck». В прошлый раз я попробовал провернуть это на «Пайтоне». Я не в курсе, пытался ли кто-то такое сделать раньше, даже не гуглил, хотелось избежать влияния чужих наработок.

Получилась такая программа, работающая только из консоли интерпретатора и только под вторым «Пайтоном»:

_=(''<())+(''<());__=_**_**_
___='%'+('%'+`'±'`[_])%(__-_-_)

___*(_*_*_-_)%((
(__+_)*_*_,
`{_}`[_/_],
)+(`(''<'')`[_],)*_+\
(
__*_*_*_-__-_/_,
__*_+_/_,
))

Пришло время разобрать как она работает. Я собирался сделать написать разбор через неделю после её написания, но заботы конца года не позволили.

С самого начала было очевидно, что в указанных ограничениях Пайтон не позволит вызвать какой-либо метод по имени, которое записано в переменной. Если попробовать, получится что-то похожее вот на это:

module, method = "__builtin__", "print"
vars(vars()[module])[method]("Hello")

Есть варианты, но без использования букв — никак.

Так как в силу вышеизложенного функцию печати не вызовешь, пришлось довольствоваться выражением в консоли интерпретатора — там результат выводится на экран автоматически. В остальном, как будет видно ниже, удалось обойтись теми конструкциями, которые есть в языке.

Посмотрим в код. Первая строка:

_=(''<())+(''<());__=_**_**_

Тут два разных выражения, разделённых точкой с запятой. В первом в скобках я дважды сравниваю строку и кортеж, получая два True. Второй Пайтон позволяет сравнивать разные встроенные типы, возвращая результат сравнения их названий, записанных как строки. Так как строка str меньше tuple, то результат — истина.

Два True в арифметической сумме дают двойку, неявно преобразовываясь к числу в этой операции. В принципе тут можно использовать унарный минус и тильду, но тогда мне это в голову не пришло:

_=-~(''<()) # число «2» можно получить и так тоже

Результат мы кладём в переменную с именем «подчёркивание». В той же строке мы получаем «16» возводя два в степень два в степени два, результат помещается в переменную «два подчёркивания».

Вторая строка:

___='%'+('%'+`'±'`[_])%(__-_-_)

Тут используется символ «±», он не алфавитно-цифровой, хоть и за пределами однобайтной таблицы, так что правила не нарушаются.

Этот символ вставлен внутрь конструкции «обратный апостроф», которая является во втором Пайтоне эквивалентом repr — функции, возвращающей строковое представление объекта, в данном случае это будет «'\\xc2\\xb1'». Нам отсюда нужен второй символ (нумерация с нуля) — «x». Цифра два для индекса у нас уже есть — в переменной _.

Дальше используется оператор «процент» — форматированный вывод. Полученное «x» используется как шаблон для «процента», чтобы преобразовать число в шестнадцатеричный вид. Из выражения __-_-_ (16−2−2) получаем «12» или, после преобразования, «c».

Вообще можно было обойтись без этого шага, получив эту же букву из строкового представления «±» («'\\xc2\\xb1'»), но в процессе продумывания я себе выписал ряд трюков и мне хотелось использовать их все.

В итоге, в переменную «три подчёркивания» попадает строка «%с», её мы будем использовать для оператора форматированного вывода («%»), чтобы получать символ по его коду.

Дальше символы получаются следующим образом:

(__+_)*_*_ # (16+2)*2*2 = 72, код символа «H», через шаблон «%c» получим отсюда букву
`{_}`[_/_] # первая часть даёт строку «set([2])», отсюда берём символ «e»
`(''<'')`[_]*_ # получаем строку «False», оттуда символ «l», удваиваем его умножением на два
__*_*_*_-__-_/_ # 16*2*2*2-16-2/2 = 111, код символа «о»
__*_+_/_ # 16*2+2/2 = 33, код символа «!»

Не требующее преобразования оставляем как есть, а из кодов символы получаем шаблоном «%с». В результате складывается строка «Hello!».

Второй, а не третий Пайтон нужен, в основном, из-за двух вещей — оператора «обратный апостроф» и двух типов строк, в одном из них юникодные символы отображаются в кодированном представлении, в третьей версии языка ото всего этого избавились.

Оставить комментарий

PythonFuck ★

Давайте вспомним, что у меня блог ещё и про программирование.

Скриншот терминала bash с выполнением Python-кода через /dev/stdin, выводящего 'Hello!', и командой exit().

Есть такое развлечение — не используя алфовитно-цифровые символы, писать осмысленные программы на разных языках программирования. Для конкретного языка это называется «имя языка»+«Fuck». Меня это занятие не то чтобы захватило, но иногда интересно поразмяться и попробовать себя в этой забаве.

Для некоторых языков такого ещё никто не делал, приходится изобретать собственные приёмы, в конечном счете это интереснее всего. Я уже делал PHPFuck, BashFuck (и даже не один раз) и совмещал JavaScriptFuck с языком программирования Brainfuck.

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

Долго мне казалось, что с Пайтоном ничего такого не сделать и я даже пытался, но не получилось. А вчера, пока делал для студентов задачу-завлекалку на день карьеры, решил сделать очередной «подход к снаряду».

_=(''<())+(''<());__=_**_**_
___='%'+('%'+`'±'`[_])%(__-_-_)

___*(_*_*_-_)%((
(__+_)*_*_,
`{_}`[_/_],
)+(`(''<'')`[_],)*_+\
(
__*_*_*_-__-_/_,
__*_+_/_,
))

Это запускается и работает (выводит строку «Hello!»), но только из командной строки и только во второй версии Пайтона. Когда напишу небольшой разбор кода, станет понятным почему.

Оставить комментарий

Reduce

Мы с коллегами сейчас проходим курс по машинному обучению, организованный для нас университетом Иннополиса. Курс рассчитан на два месяца, к настоящему моменту мы бегло пробежались по высшей математике и начали изучать Пайтон — один из языков, популярных в машинном обучении.

По Пайтону соскучился — он мне нравится, а я всерьёз не программировал на нём несколько лет. С удовольствием смотрю что наворотили в третьей версии, которую я почти не трогал. Бо́льшая часть языка легко и почти сразу вспоминается, но долгое отсутствие практики всё же сказывается.

Решали сегодня задачки из «Проекта Эйлер», в частности 67-ю задачу. Суть её такова, вкратце. Дан треугольник чисел, нужно найти в нём максимальную сумму, из всех, что получаются, если двигаться сверху по соседям ниже. Посмотрите задачу, там картинка есть, всё понятно.

from functools import reduce

def read():
    with open('p067_triangle.txt') as f:
        for line in f.readlines()[::-1]:
            yield tuple(int(x) for x in line.strip().split(' '))

reduce(lambda cur, prv: [max(cur[pos:pos+2])+v for pos, v in enumerate(prv)], read())

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

Результат мозговых усилий выше. Код сократился до одной строки (функция над ней — просто чтение данных).

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

На закуску вот вам фолдинг на «Баше», взятый из комментария со «Стека Оверфлоу»:

foldl() {
    echo $(($(</dev/stdin)$2))
} < <(tr '\n' "$1" <$3)

# Sum 20 random ints from 0-999
foldl + 0 <(while ((n=RANDOM%999,x++<20)); do echo $n; done)

Тут суммируются двадцать псевдослучайных чисел из диапазона 0—999.

5 комментариев

Как сконвертировать bytea

Пока сегодня ехал домой с военного поиска (я в таком уже участвовал в прошлом году), написала в «телеграм» дипломница — она не смогла разобраться как сконвертировать в буквы формат постгреса bytea, в котором мы ей выгрузили кое-какие данные по её теме.

Люди в камуфляже раскапывают лопатами и кистями останки человека в земле, проводя археологические или следственные работы на открытом грунте.
Ограничусь фотографией на которой почти ничего не видно, дабы не эпатировать публику

Отдельно про поездку рассказывать не буду, я вообще подумал, что пора завязывать с публикациями на такие темы, а пока ехал, накидал на коленке функцию на Пайтоне, положу сюда, чтобы не потерялась:

def decodeBytea(bytes: bytearray, charset: str) -> str:
	if bytes[:2] == b'\\x':
		return bytearray.fromhex(bytes[2:].decode()).decode(charset)
	else:
		raise ValueError('Invalid format')

Формат немудрёный — впереди слеш-экс, дальше идут шестнадцатеричные коды байт, которые надо превратить в символы.

4 комментария

Погодный плагин для «Sublime Text» ★

Скриншот редактора кода с файлом PogodaStatusBar.py, содержащим класс для отображения погоды в статус-баре Sublime Text с иконками и настройками.
Окно редактора «Саблайм Текст» с погодой и пробками в строке состояния

Написал свой первый плагин для «Саблайма» — для отображения в строке состояния погоды и пробок «Яндекса». Заодно немного повспоминал «Пайтон», давно на нём ничего не писал.

Удивительно, но факт — другого работоспособного плагина на эту тему не обнаружилось. Единственный конкурент использует старое АПИ «Яху», которое уже не работает, потому не работает и плагин.

Отдельное спасибо «Яндексу» за то, что их АПИ умеет определять текущее местоположение — ничего задавать в конфиге не надо, удобно.

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

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

В настройках есть шаблон вывода, можно убрать пробки или погоду, если они не нужны.

Подал пул реквест на включение в саблаймовский пакетный менеджер, но особо упорствовать не буду, если откажут.

14 комментариев

«Кремниевая долина»

Пока отхожу от операции, смотрю сериал «Кремниевая долина» и дабы не заржавел мозг, сделал расшифровку надписи на футболке Эрлиха — одного из героев сериала, на трёх языках: Перле, Пайтоне и Тикле. Листинг один для трёх языков:

#\
"@{[$\=qq{\n},\
print map {chr $_} (0b1000010, 0b1101001, 0b1110100, 0b1100011, 0b1101111, 0b1101001, 0b1101110)]}";#\
__END__ = 1;\
print(''.join(chr(b) for b in (0b1000010, 0b1101001, 0b1110100, 0b1100011, 0b1101111, 0b1101001, 0b1101110))); #\
"""
foreach name {01000010 01101001 01110100 01100011 01101111 01101001 01101110} {
    puts -nonewline [binary format B* $name]
}
puts {} ;#"""

Дольше всего с Перлом возился, пока не вспомнил, что у него есть вычисление выражений внутри строк через конструкцию @{[…]}. Нужно рассказать как всё работает?

Оставить комментарий

Массив с конца: ~n

Чудесное нашлось в комментариях на «Хабре»: логичный, на мой взгляд, способ получать значения с конца массива. Способ, вообще говоря, для «Пайтона», но и для «ПХП» можно применить.

В «Пайтоне» есть такая проблема, даже не то чтобы проблема, скорее мелкое неудобство, что списки, если их считать с начала, нумеруются с нуля, а если с конца — с минус единицы:

>>> print([1,2,3][0], [3,2,1][-1])
(1, 1)

Лично я к этому так привык, что в нелогичность такого поведения сразу и не вник. Неудобство становится более очевидным, если от фиксированных значений перейти к переменным:

>>> i=2
>>> print([1,2,3,4][i], [4,3,2,1][-i-1])
(3, 3)

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

>>> i=2
>>> print([1,2,3,4][i], [4,3,2,1][~i])
(3, 3)
>>> print([1,2,3][0], [3,2,1][~0])
(1, 1)

Для тех, кто не понимает смысла операции «тильда» можно объяснить, что без тильды значения берутся с начала, с тильдой — с конца. Здо́рово!

Обычно в таких случаях возражают, что новички будут путаться. Это какой-то странный аргумент, честное слово, в «Пайтоне» для новичков очень много новых «значков» — те же декораторы, а «тильда» есть в большинстве распространённых языков, пусть новички учатся, к тому же забыть вычесть единицу — одна из частых ошибок, встречается не только у новичков, но и у матёрых программистов.

18 комментариев

«Словохват»

Вчера подсел на игру «Словохват», вроде игра незамысловатая, но очень интересная.

Словохват (85.68КиБ)

Смысл — занять одну из клеток противника, помеченного «звёздочкой», в терминах игры это называется «столица». Чтобы занять клетку, нужно придумать слово с буквой, которая написана на этой клетке, причём нужно уложиться в отведённое время — сверху тикает таймер.

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

На медиане поля действуют ещё какие-то законы, пока не разбирался, правила есть на сайте. Длина слов важна — упрощённо, чем длинее слово, тем больше вам даётся очков, за очки можно «покупать» различные штуки — дополнительное время, дополнительную букву, «молнию» (вычищает близкорасположенную клетку от цвета противника).

У автора получилась неплохая такая тактическая игра. Мне играть было скучновато — очень лень выдумывать слова, поэтому я написал небольшую программу на Пайтоне, которая работает со словарём под редакцией профессора Лопатина, ей нужно задать файл словаря и буквы, которые у вас есть, она придумает слово:

Словохватохват (18.24КиБ)

Работает неспешно, я оптимизировать не стал, времени, даваемого игрой, на поиск вполне хватает (по крайней мере на моём ноуте). Если кому-то захочется оптимизации, то просто посмотрите как преобразуется словарь и закешируйте преобразованный вариант.

У меня на каждом запуске словарь преобразуется заново. Естественно, в словаре, помимо разрешённых частей речи, есть прилагательные, глаголы и так далее — их я не отфильтровываю, в словаре нет для этого информации.

# coding: utf-8
import itertools
import sys
import codecs

# дружим с консолью
sys.stdin = codecs.getreader('utf-8')(sys.stdin)
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

# строки с пропусками любых букв в любом количестве
def combinations(str):
    for l in xrange(2, len(str)+1):
        yield itertools.combinations(str, l)

# собираем каждую в строку
def iterstr(str):
    for s in itertools.chain(*combinations(str)):
        yield u''.join(s)

# все наши комбинации
def uniqcombinations(str):
    return set(iterstr(sorted(str)))

# проходим по словарю
def iterdict(name, min, max):
    with open(name, 'r') as f:
        for line in f:
            line = line.strip().decode('cp1251')

            # текущее слово должно содержать только буквы и быть не больше искомого слова
            if max >= len(line) >= min and line.isalpha():
                yield u''.join(sorted(line)), line

# ищем наше слово
def find(word, dictname):
    words = uniqcombinations(word)

    for line, original in iterdict(dictname, 3, len(word)):
        if line in words: print(original)

# запуск программы и небольшая помощь
try:
    find(sys.argv[2].decode('utf-8'), sys.argv[1].decode('utf-8'))
except IndexError:
    print(u'Запуск: {0} <словарь> <буквы в наличии>'.format(sys.argv[0]))

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

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

23 комментария

30 банковских дней

Делал жене программу, чтобы отсчитывать от введёной даты 30 банковских дней в текущем году, может пригодится кому (в запускаемый файл преобразовал при помощи PyInstaller, получается один файл, очень удобно). При первом запуске программа скачивает календарь с сайта Calend.Ru и сохраняет рядом, потом берёт из него выходные и праздничные дни.

# coding: utf-8
import re
import httplib
import pickle
import ctypes
from datetime import date, datetime, timedelta

MB_OKCANCEL = 1
MB_SYSTEMMODAL = 4096
IDOK = 1
IDCANCEL = 2

def getCalend():
    h = httplib.HTTPConnection('www.calend.ru')
    h.request('GET', '/work/')
    return h.getresponse().read()

def getHolidays():
    table = re.findall(r'<table\s.+?time_of_death(.+)</table>', getCalend(), re.DOTALL)[0]
    return re.findall(r'<td\s+class="\S+\s+col5"\s+day="(\d+)"\s+month="(\d+)"', table, re.DOTALL)

def readHolidays():
    year = str(date.today().year)
    try:
        with open(year + '.cache', 'r') as f:
            holidays = pickle.load(f)
    except IOError:
        with open(year + '.cache', 'w') as f:
            holidays = tuple(getHolidays())
            pickle.dump(holidays, f)

    return [datetime(day=int(d[0]), month=int(d[1]), year=int(year)) for d in holidays]

def getStartDate():
    while 1:
        input = re.split(r'\D', raw_input("Enter start date (dd.mm): "))
        try:
            input = [int(x) for x in input]
            if len(input) == 2:
                break
        except ValueError:
            pass

    return (date.today().year, input[1], input[0])

delta = timedelta(days = 1)

holidays = readHolidays()
msgbox = ctypes.windll.user32.MessageBoxW

while 1:
    try:
        date = datetime(*getStartDate())

        for i in xrange(0, 30):
            while 1:
                date += delta

                if date not in holidays: break

    if msgbox(
            0,
            u'Получилась следующая дата: %02d.%02d.%04d' % (date.day, date.month, date.year),
            u'Ответ',
            MB_OKCANCEL | MB_SYSTEMMODAL
        ) == IDCANCEL:
        raise StopIteration
    except (KeyboardInterrupt, StopIteration):
        break

Программу написал быстро, а сложнее всего было её отправить — ГМэйл упорно не принимает запускаемые файлы, даже в архивах.

15 комментариев

← 1 2 3 4 →