Tag: python

Знакъ

Об одной задачке-2 или О молодой шпане

Оказывается, у того сайта, где я нашёл нерешаемую задачку, есть чат поддержки. Я зашёл туда и спросил, как же так получилось, что для программистов на C и на Python установленные одинаковые ресурсные ограничения.

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

Я, конечно, отнёсся скептически, но через некоторое время товарищ предъявил результаты прохождения всех тестов и исходный код.

На моём любимом примере эта программа работала чуть-чуть быстрее, чем моя, но когда я убрал оттуда родной ввод-вывод и чуть-чуть причесал код, он стал работать ВДВОЕ быстрее моего. Ну и потом я ещё потратил некоторое время, чтобы разобраться с применёнными там алгоритмами. ОЧЕНЬ остроумно.

Вот она, молодая шпана, которая сметёт нас с лица земли! Есть, на кого оставить этот гадюшник питонятник!

А вот и текст программы:

Read more...Collapse )

Знакъ

Об одной задачке

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

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

Даны числа M и N, причём гарантируется, что M<N. Требуется вывести частное M/N в виде десятичной дроби. Если дробь периодическая, то период заключить в скобки. Например: 1/2 → 0,5; 4/700 → 0,00(571428)

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

#!/usr/bin/env python

(M,N) = map(int,input().split())
rem = {}
(ps, pe) = (0,0)
M *= 10
rc = "0,"
while True:
    pe += 1
    (q,r) = divmod(M,N)
    M = r*10
    k = str(q)+"."+str(r)
    if k in rem: # такой остаток уже был, значит, это период
        ps = rem[k]
        break
    rem[k] = pe
    rc += str(q)
    if r==0: break  # дробь конечная
if ps!=0: rc = rc[:ps+1]+"("+rc[ps+1:]+")"
print(rc)

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

Тут надо сказать пару слов про систему проверки. Программе выдаётся некое количество ресурсов — 64 мегабайта памяти и 2 секунды времени. Результатом выполнения может быть одна из четырёх строк:

  • OK — если программа корректно отработала и выдала верный результат;
  • Неверный результат — если программа коректно отработала, но результат неверный;
  • Ошибка времени выполнения — если код завершения отличен от 0;
  • Превышено время выполнения — ну, понятно.

Никаких «отладочных print’ов» не предусмотрено. В FAQ русским по белому написано «тестовые примеры не покажем, учитесь отлаживать методом пристального взгляда». И что-то мне стало так обидно...

Методом дихотомического обкладывания назных кусков кода конструкциями try .. except я выяснил, что программе не хватает памяти. Что ж, это вызов...

Первое, что я сделал, это вывел отдельно непериодическую часть. Количество цифр в непериодической части равно тому, сколько раз знаменатель делится на 2 и 5 (точнее, максимуму из этих чисел; например, если знаменатель делится на 1600=52×26, то длина непериодической части будет max(2,6)=6). После того, как это сделано, достаточно сохранять не пару частное+остаток, а только остаток. Мало того, не нужно сохранять его позицию, достаточно просто флага, что такой остаток был.

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

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

Опытным путём я выяснил, что сайт подаёт на вход восьмизначный знаменатель. Интересный момент заключается в том, что время расчёта периода для двух чисел с разностью 2 может отличаться на порядок, потому что длина периода разная. Например, моя конечная программа прямо сейчас на незагруженной машине пример 4/99999991 обрабатывает за 0.07 секунды, 4/99999997 — за 1.2 секунды, а 4/99999993 — за 22 секунды. Исходная программа на последнем примере уходит в себя на несколько часов и сжирает несколько гигабайт памяти, ответа я от неё так и не дождался. Кстати, быстро определить длину периода нельзя, хотя я пытался сделать и это тоже, включая вариант поиска периода по строке, в которой записаны цифры искомой дроби.

В общем, вот окончательный вариант, который проходит 31 тест из 32. Кто может, пусть сделает лучше. И да, на код десятиклассника это не очень похоже.

#!/usr/bin/env python

from sys import stdout
IOBUF_SIZE = 4096

(M,N) = map(int,input().split())

# сокращаем дробь
(n1, n2) = (M, N)
while n1!=0 and n2!=0:
    if n1>n2: n1 %= n2
    else: n2 %= n1
M //= n1+n2
N //= n1+n2

stdout.write("0,")

# nn - "приведённый" знаменатель, т. е. без множителей 2 и 5
(nn,divisor) = (N,10)
iobuf = bytearray(IOBUF_SIZE)
iox = 0
while True:
    M *= 10
    q = M//N
    M %= N
    qq = nn//divisor
    rr = nn%divisor
    if rr==0:
        nn = qq
    elif divisor==10:
        if rr==5:
            nn = qq+qq+1
            divisor = 5
            rr = 0
        elif rr in (2,4,6,8):
            nn = qq*5+rr//2
            divisor = 2
            rr = 0
    if rr!=0: break # дробь периодическая
    iobuf[iox] = q+48
    if (iox := iox+1)==IOBUF_SIZE:
        stdout.write(iobuf)
        iox = 0
    if nn==1: 
        stdout.write(iobuf[:iox].decode("ascii"))
        stdout.write("\n")
        exit(0) # дробь конечная

# сюда будем складывать остатки
rem = bytearray(1+(N>>3))
rem[M>>3] |= (1<<(M&7))
stdout.write(iobuf[:iox].decode("ascii"))
stdout.write("("+str(q))
iox = 0
while True:
    M *= 10
    q = M//N
    M %= N
    (r1,r2) = (M>>3,1<<(M&7))
    if rem[r1] & r2!=0:
        stdout.write(iobuf[:iox].decode("ascii"))
        stdout.write(")\n")
        break
    rem[r1] |= r2
    iobuf[iox] = q+48
    if (iox := iox+1)==IOBUF_SIZE:
        stdout.write(iobuf.decode("ascii"))
        iox = 0

P. S. А потом выяснилось, что на этом сайте ограничения по памяти и быстродействию одинаковые для всех языков программирования — и для Python, и для C. Вот тут-то мне, как говорится, фишка и попёрла. Неберущийся пример был решён за 0.05 секунды...

Знакъ

История одного скрипта

Издательская программа Scribus в дополнение ко всем своим достоинствам поддерживает скрипты на языке Python.

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

Как происходит вёрстка альбома?

Для начала надо отобрать фотографии, и тут никакой автоматизации быть не может. Затем надо создать альбом и загрузить фотографии внутрь. Не знаю, какие абстракции применяет Adobe, а тут последовательность действий довольно-таки длинная: создать объект-изображение, а затем выбрать для него файл. Поскольку файлов много, кликать их быстро надоедает, и здесь мы видим первую очевидную точку автоматизации:

#!/usr/bin/env python

from scribus import *
import os

page = 5
path = "/home/hardsign/images"

gotoPage(page)
ip = 0
for f in sorted(os.listdir(os.fsencode(path))):
    loadImage(path+"/"+os.fsdecode(f),img := createImage(20+(ip%3)*80,10+(ip//3)*80,70,70))
    if (ip := 0 if ip==8 else ip+1)==0: gotoPage(page := page+1)

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

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

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

Третья инкарнация скрипта находит ближайший к углу узел сетки, перемещает угол туда, а затем растягивает объект до следующего узла. Уже намного лучше, но выясняется, что одна сетка на все случаи жизни не годится. Где-то получается квадрат 2×2 из четырёх фотографий, а где-то фотки прямоугольные и совсем мелкие, и сетка должна быть 4×5 или наоборот 5×4.

Можно, конечно, сделать стопиццот скриптов на каждую сетку, но иметь множество разных файлов, отличающихся только значениями переменных, очень неудобно. Вводить эти значения — тоже не лучший вариант, да и нет у Scribus’а средств для этого. И тут на помощь приходит unix way: а что, если записать один и тот же скрипт под кучей разных имён и доставать размер сетки прямо из имени?

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

#!/usr/bin/env python

from scribus import *
import os, sys

# количество фотографий по горизонтали и по вертикали
(gx, gy) = [int(i) for i in os.path.basename(sys.argv[0]).replace("grid","").replace(".py","").split("x")]
# расстояние между краями фотографий в миллиметрах
(hspace, vspace) = (10, 10)
# насколько близко угол должен быть к узловой точке (лучше не менять)
distance = 30
# расстояние от левого края листа
x0 = 10 if currentPage()%2==0 else 20
y0 = 10
# размеры листа
(xsize, ysize) = (275.0, 275.0)

width = (xsize-hspace*(gx-1))/gx
height = (ysize-vspace*(gy-1))/gy
abs = lambda x: x if x>=0 else -x
for i in range(0,selectionCount()):
    cur = getSelectedObject(i)
    (x,y) = getPosition(cur)
    (w,h) = getSize(cur)
    (left, top, right, bottom) = (x0, y0, x0+width, y0+height)
    while abs(x-left)>=distance and left<xsize+100: left += width+hspace
    while abs(y-top)>=distance and top<ysize+100: top += height+vspace
    while abs(x+w-right)>=distance and right<xsize+100: right += width+hspace
    while abs(y+h-bottom)>=distance and bottom<ysize+100: bottom += height+vspace
    if left>=xsize+100 or right>=xsize+100 or top>=ysize+100 or bottom>=ysize+100: continue
    moveObjectAbs(left,top,cur)
    sizeObject(right-left,bottom-top,cur)

P. S. Опытный программист может написать Perl-программу на любом языке!

Знакъ

О компьютерной грамотности

Попросили меня хорошие люди скачать запись вебинара с сайта mts-link.

Естественно, ничего просто так не скачивается: «вебинар» — это некая программа, которая выполняется на сервере МТС, и в изображение она превращается только в момент вывода на экран. Можно, конечно, включить воспроизведение вебинара и запись экрана, но это не наши методы.

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

А вот дальше начинается самое интересное — из этого описания собрать видеоролик, который сможет просмотреть каждый. И тут без программирования не обойтись. Python, bash, ImageMagick, ffmpeg...

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

Знакъ

Заметки продолжающего питониста

Корпоративная дисциплина требует заносить кое-что в трекер задач (Jira ЕВПОЧЯ). Поскольку делать это лень, открыл документацию по API, чтобы за меня всё делал робот. Ну, а сам робот — естественно, на Python.

В общем и целом я почти поменял своё мнение — как язык малой автоматизации Python неплох. Во всяком случае, мощнее, чем Bash, и однозначно удобнее, чем Rexx. Более того, я оценил синтаксис массивов, простых и ассоциативных: он такой же, как в JavaScript, что позволяет копировать примеры JSON прямо в текст скрипта.

Отдельно хочется отметить качество и количество документации, а также то, что все примеры всяких вызовов всяких API в интернетах — практически исключительно на Питоне.

Хорошо ли учить Питону детей? А вот тут однозначного ответа у меня нет.

Для тех, кто не собирается связывать свою жизнь с IT, — однозначно хорошо. Иметь под рукой этакий современный VisualBasic всегда приятно. Но вот для тех, кто собирается расти программистом, начинать с Python намного хуже, чем с BASIC.

Почему?

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

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

Одно утешает — я уже старенький, мне можно всё. И Python, и Perl, и чего покрепче...

Знакъ

Записки продолжающего питониста

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

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

Что она умеет?

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

Для управления используется интерфейс PulseAudio, так что под Windows, скорее всего, работать не будет. А красноглазики-линуксоиды могут насладиться в полной мере:

https://gitverse.ru/hardsign/headset-bt

Теперь осталось выучит Git — и на старости смогу подрабатывать программистом.

На КДПВ — собор в Роскиле, где хоронят датских королей. В том числе там находится могила Харальда Синезубого.

Знакъ

Заметки начинающего питониста

Решил вспомнить давно забытые навыки, а то ситуации разные бывают...

Когда-то я программировал на Delphi, C с плюсами и без таковых, а также на PL/SQL, а теперь вот — Python. Впечатления интересные.

Очень... нет, не так. О-О-ОЧЕНЬ много документации. Любой запрос «как сделать то-то и то-то» даёт вразумительный ответ на первой же странице. Удобно, но непонятно, как в такой ситуации учиться. Я-то знаю, что именно нужно сделать, а если не знать?

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

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

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

Очень радует после Perl’а отсутствие проблем с Unicode и понятная иерархия объектов. Чувствуется, что язык создавался людьми для людей. Иногда, правда, создаётся впечатление, что странными людьми для тупых людей, но судя по результату целевая аудитория угадана верно.

А вообще программирование — это здорово. Даже на питоне.

Знакъ

Дыбра кусок

Вернулся из отпуска.

В отпуске хорошо. Съездил в Нижний Новгород, который, как обычно, прекрасен. Собрал пару шкафчиков, хотя планировал гораздо больше. Написал первую программу на питоне — с многопоточностью, XML, регулярными выражениями, онлайн-гемблингом и весёлыми мефрау. Начал писать на Perl’е, но на работе с куками в http-запросах сломался, и теперь буду есть кактус вместе со всем прогрессивным человечеством.

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

Stay tuned, дорогая редакция™ продолжит знакомить вас со всякой <censored>.

Знакъ

О моде

Язык Python сегодня один из самых популярных, если вообще не самый популярный. На нём написаны добрых две трети половина всяких служебных скриптов в Linux, ему обучают школьников, датасаентистов и прочих бухгалтеров. Создатель Питона Гвидо ван Россум удостоен звания полубога. Персон, равных ему по значимости, можно пересчитать по пальцам: Роб Пайк и Деннис Ритчи (создатели Unix и C), Дональд Кнут (создатель TeX и автор «Искусства программирования»), Линус Торвальдс (создатель Linux), Ларри Уолл (создатель Perl).

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

Боже, какое гнусное, невыразимое уродство!

Популярность вот этого окончательно подорвала мою веру в человечество...

Если вы айтишник, то под катом деталиCollapse )

Знакъ

Наши руки – не для скуки

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

Удалил я Python со своей машины (только не спрашивайте – зачем). А надо вам сказать, что вся установка Linux состоит из пакетов, и один пакет может требовать какие-то другие. Например, если у меня есть приложение, которому нужна библиотека, я не включаю эту библиотеку в дистрибутив, а делаю пометку – если будешь устанавливаться, поставь и библиотеку, которая в другом пакете, заодно. Бывает, что установка какого-нибудь пакета тянет за собой полсотни других пакетов.

Верно и обратное: если удаляешь какой-то пакет, то все, кому он был нужен, тоже автоматически удаляются, ибо теперь работать не смогут. Так вот, после удаления Python’а инсталлятор снёс мне половину системы. Из неожиданного – все рабочие столы, какие были в системе, браузер Firefox, Microsoft’овские шрифты и даже терминал. То есть половина окружения в Ubuntu написано на Python’е!

Систему я восстановил, командная строка и утилиты GNU – это сила. Более того, примерно полторы сотни пакетов оказались лишние. Но зато я убедился: Python повсюду, он окружает нас со всех сторон и действительно востребован. Учите Python!