Inbox Zero

Zero-inbox (53.29КиБ)

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

Про «inbox zero» я услышал довольно давно, больше года — точно. Основная идея — в папке «inbox» («входящие») лежат только письма, которые требуют моей реакции, чаще всего их немного, они мозолят глаза и от них не спрятаться. Остальных письма «архивируются» (в почтовых клиентах есть кнопка «архивировать»), в клиенте «Thunderbird» такие письма лежат в папке «Архивы».

Недавно меня допекло — решил перейти на zero inbox, сразу скажу — не пожалел. Главное хранить во «входящих» только те письма, к которым планируется вернуться в скором времени, а не «возможно, когда-нибудь».

Это работает. Попробуйте.

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

Попробовал переползти с ноутбука на стационарное рабочее место

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

Научился. Я очень комфортно чувствую себя за ноутом, меня перестал раздражать тачпад, «маленький экран» (который для меня сейчас вполне достаточен), то что его нужно класть на колени и так далее.

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

Как я работал за этой высокой клавиатурой? Тянуть за мышью — это же неудобно! А мониторы! Они у меня вертикальные, приходится задирать голову, у меня уже шея болит!

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

Жена-дао

Жена у меня скрапбукингом занимается, если кто не знает (это открытки ручной работы и всё такое). Так что она одна из немногих людей, которые буду рады если притащить домой пустую почтовую коробку из «такого плотного картона».

Мне-то казалось, что её больше обрадуют красивые марки (посылка была из Штатов).

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

Optkit: можно ли автоматизировать мою графику?

Скриншот PowerShell с выводом таблицы сравнения размеров файлов (0.png, 1.jpg, 2.gif, 3.gif) и заметкой о конвертации GIF в PNG.

Выкладываю optkit — программу (написана на PowerShell v1, это значит — под Windows), которая определяет можно ли оптимизировать графику в форматах PNG, GIF, JPEG в указанной папке. При запуске нужно указать папку.

Примерный вывод есть на скриншоте. Выводится сколько удалось сэкономить, для GIF выводится сколько они будут занимать в PNG (если это даст экономию).

Программа использует ряд других программ, которые должны лежать в папке utils, это:

  • SuperGIF 1.5 (должен быть «купленным»)
  • GIFLite (можно поискать FTP-поиском), утилита не используется для 64-битных ОС
  • jpegtran
  • optipng
  • pngout (нужна command line версия)
    Моя программа не использует никаких продвинутых техник (например, не пытается избавиться от прозрачных, но не одноцветных участков), может ещё руки дойдут и сделаю.

Дополнено: Харисов подсказывается, что на Bash есть инструмент интереснее — imgcomp и писался явно не пару вечеров, как мой. Кстати, сравнивая две программы (на Bash и PowerShell) можно сделать интересные выводы о читаемости этих языков и их мощности.

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

SQL-инъекция в штрих-коде?

*\'; DROP TABLE products; --* (2.32КиБ)

А вот ещё идея возникла. Распечатываете этот штрих-код на стикеры, и незаметно клеите их рядом с ценниками в супермаркетах конкурента.

Когда в конце рабочего дня будут производить учет товаров какой-нибудь доброход-товаровед обязательно посветит на него штрих-код сканером. :)

А там написано *’; DROP TABLE products; —*

P.S. Я и сам принимал участие в разработках программ складского учета с применением сканеров штрихкода. И знаю — никто там, естественно, входные данные не проверяет, чаще всего шпарят прям напрямую запросы с динамическими параметрами в SQL-базу. А теперь вот задумался, прогресс-то не стоит на месте… :)

Забавно, неужели правда сработает?

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

«Андроид» 2.2.1

У меня вчера «Андроид» на телефоне обновился, выкачав новую прошивку через EDGE (бедняга, попросил бы, я б WiFi включил). В интернетах пишут, что залатали пару дыр, а из того, что я заметил — появился твёрдый знак на клавиатуре.

Дополнение: поправили ошибку с именованием дней недели.

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

Собираемся менять фотоаппарат

Мыльница как-то всё меньше устраивает (у нас Sony Cyber-shot DSC-N1). Чуть облака на небе и всё, одни шумы, ничего не видно, снимаешь в помещении — всё, уводит в зелёный.

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

Пока думаем брать Canon EOS 550D. Подумаем ещё немного и возьмём.

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

PHP4 без XML-парсера, плюс XPath

Я недавно написал простенький парсер XML для PHP 4.3+, для случаев, когда никакого встроенного парсера XML на хостинге не оказалось.

А вчера мне подумалось, что парсить XML в дерево это, конечно, замечательно, но пользоваться этим всё равно неудобно. Было бы неплохо дать возможность получать произвольные узлы так же просто, как это делается при помощи XPath.

Весь XPath я, конечно, реализовывать не буду, да мне столько и не надо. В общем, написал класс, который используется в паре VerySimple_XMLParser. Реализованы получение потомков тега, «*», «node()» и «text()». Это всё, но мне хватит.

// Simple XPath for VerySimple_XMLParser
// /
class VerySimple_XMLParser_Tree
{
    var $path;
    var $counter;
    var $xpath;
    
    function VerySimple_XMLParser_Tree()
    {
        $this->path = array('/');
        $this->counter = 0;
        $this->xpath = array();
    }


    function collect($type, $args)
    {
        $path = implode($this->path);

        switch ($type) {
            case 'TEXT':
                $this->xpath[$path . $this->counter . '/text()/'] = $args;
                $this->counter++;
                break;

            case 'TAGOPEN':
                list($name, $attributes) = $args;

                $tag = $this->counter . '/' . $name . '/';

                $this->xpath[$path . $tag] = $attributes;
                $this->counter = 0;

                $this->path[] = $tag;
                break;

            case 'TAGCLOSE':
                $this->counter = 1 + (int) array_pop($this->path);
                break;
        }
    }

    function halfxpath($path)
    {
        $keys = array_keys($this->xpath);
        $trans = array(
            '\/'       => '\/\d+\/',
            '\*'       => '[^\/()]+',
            'node\(\)' => '[^\/]+',
        );

        $filter   = strtr(preg_quote($path, '/'), $trans);
        $filtered = preg_grep("/^$filter\/$/su", $keys);

        $nodes = array();

        foreach ($filtered as $key) {
            $nodes[$key] = $this->xpath[$key];
        }

        return $nodes;
    }
}

// а вот так это можно использовать:
$xml =<<<XML
<root>
    <a>
        <b><c>Xa!</c></b>
        <b>Hello</b>
    </a>
    <a name="value"/>
</root>
XML;

$tree = &new VerySimple_XMLParser_Tree();

$sax = &new VerySimple_XMLParser(array(&$tree, 'collect'));
$sax->parse($xml);

echo '<plaintext>';
var_dump($tree->halfxpath('/*/a/b/text()'));
var_dump($tree->halfxpath('/*/a/b/node()'));
var_dump($tree->halfxpath('/*/a/b/*'));
var_dump($tree->halfxpath('/root/a'));

Метод для запросов я назвал «halfxpath», намекая, что это не настоящий XPath, а лишь неполноценная эмуляция.

Основная идея, как видно из кода — не парсить XML в дерево, а помещать каждый элемент по специального вида пути, который потом будет проверен на совпадение по регулярному выражению, приготовленному из запроса.

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

Ярославль

Ярославль — город, входящий в Золотое кольцо России, наверняка, один из красивейших городов России, жаль у меня не было времени в этом убедиться. Надо будет приехать туда летом.

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

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

Я помню празднование наглого тысячелетия в Казани (в 2005-м), кругом новый асфальт, урны, покрашенные заборы и прочее,… с Ярославля либо не пытались сдуть пыль, либо было ещё хуже.

Синий флаг с гербом, изображающим чёрного медведя на белом фоне, развевается на столбе на фоне серого неба и голых ветвей деревьев.
Баннер с гербом Ярославля (медведь с копьем) и надписью «Ярославль» на фасаде здания, перед ним припаркованы автомобили.
Граффити с гитарой и надписью «1000 мелодий» на стене здания, вид из-за припаркованного автомобиля в дождливый день.
Вход в ателье с вывеской «РЕМОНТ ПОШИВ» и списком услуг: одежда, изделия из кожи, меха, ткани. Слева — табличка с режимом работы.
Флаг с гербом и надписью «Архангельск» на фоне серого неба, прикрепленный к столбу с проводами.
Белая входная дверь с синим логотипом «Аромакафе» в виде трёх кофейных зёрен и надписью, установленная в розовой стене.

Я люблю думать, гуляя по улицам какого-либо города — что если мне завязать глаза и повозить 5-6 часов, а потом выпустить, отличил бы я — вывезли меня из Москвы или я ещё в её пределах? Мне показалось, что, выпусти меня в Ярославле, я бы понял, что я не в Москве. Но я так и не понял, на чём это ощущение основано.

Кажется, в Ярославле всё те же не прополотые корни прошлого, что и в Москве…

Фасад здания с надписью «ЮНЫЙ ТЕХНИК» и абстрактной мозаикой из геометрических фигур и слов «САНТЕХНИКА», «МАТРИЦА», «ОБЩЕСТВЕННОЕ
На фасаде здания вывеска «IDEA ГИПЕРМАРКЕТ МЕБЕЛИ» с надписью «СКОРО ОТКРЫТИЕ», адрес: Полушкина роща, д. 9, телефон: 67-17-67, логотип «ТАНДЕМ».
Белая статуя мальчика на колонне в саду среди хвойных деревьев с желтой листвой и цветущих кустарников.
Бронзовый памятник сидящему мужчине на высоком каменном постаменте с надписью «В.И. Ленин» на фоне здания с арочными окнами и деревьев.

То же народное творчество на стенах…

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

Те же однообразные и одинаковые по всей стране пятиэтажки и старые дворики…

Детская площадка с качелями и качалкой-балансином, огороженная рядом разноцветных покрышек, на фоне жилого дома и деревьев.
Мокрая городская улица с движущимися автомобилями, пешеходным переходом и жилыми домами с магазинами на первых этажах под дождём.
Два человека идут по осеннему парку мимо жёлтого многоэтажного дома с магазинами на первом этаже; на переднем плане — деревья и упавшие листья, на дороге — чёрный внедорожник.
Черный автомобиль припаркован на асфальтированной дороге между двумя желтыми жилыми домами, окружёнными высокими деревьями с желто-зелёной листвой в солнечный день.

Те же памятники старины…

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

То же самое разгильдяйство везде — даже на кладбище…

Надгробие с портретом и цветами на фоне других памятников и ржавой решётки среди осенних листьев.
Погребальный памятник с табличкой «Ефимов Владимир Прокофьевич 28.04.1943–2017», окружённый зелёной растительностью и чёрной металлической оградой.

Конечно, может быть я узнал о том, что не в Москве по многочисленным упоминаниям названия города…

Фасад здания Дома культуры железнодорожников узла Ярославль-Главный с красной лентой «Россия живет дорогами» и логотипом РЖД
Жёлтое здание вокзала с часами на башне и надписью «Казанский вокзал», перед ним — автомобили, автобусы и пешеходы на улице под облачным небом.

Или по реке, разве река Москва течёт так же несуетно?

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

Может отличаются люди? Или они везде одинаковые?

Прогулка по осенней аллее у белого здания с колоннами: люди идут по мокрому тротуару, деревья в жёлто-зелёной листве, на заднем плане — припаркованные машины.
Большой рекламный щит с надписью «МАРИЯ» и логотипом ASUS на фоне осенних деревьев и мокрой дороги с автомобилями и светофором.
Жёлтое здание с вывеской «РОСТЭК» и информационным стендом, рядом — серебристый автомобиль, пешеход и деревья на фоне другого здания под облачным небом.
Четверо людей идут по тротуару вдоль здания с розово-белым фасадом и вывеской «Книжный магазин», рядом — ряд деревьев и припаркованные автомобили.
Осенняя улица с деревьями, покрытыми жёлтыми листьями, и металлической рамой на тротуаре; справа — здание с вывеской «Интер Кафе», слева — парковка с автомобилями.

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

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

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

Тонкости heredoc в PHP

В PHP, как и в некоторых других языках (Bash, Perl) есть конструкция heredoc — это способ задать многострочную строку. Посмотрите как удобно, не нужно экранировать кавычки:

// это PHP
$str = <<<STR
Привет, я строка "в кавычках",
'в апострофах'.
STR;

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

Сегодня, в очередной раз раздражаясь по этому поводу, я решил попробовать использовать неразрывные пробелы (Alt Right+Space в раскладке Бирмана), получилось, работает даже в PHP 4.4.4. Подходит, конечно, для исходников в кодировке UTF-8. Надеюсь, вы в другой кодировке их уже и не пишете.

Правда, строки внутри, конечно, менять нельзя, но эта конструкция часто используется вместе с HTML, а ему отступы неинтересны.

$str = <<<    HTML
    <table>
        <tr class="row"><td>'Привет'</td></tr>
    </table>
    HTML;

Только не забудьте, что отступы надо делать и в начальной конструкции, и в конечной.

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

House M.D.

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

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

3G в Ярославле

Интернет в гостинице (42.48КиБ)

Ловил два дня 3G в Ярославле. Кстати, да. В номере «Люкс» есть радиоточка!

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

Bitrix WTF

Я сейчас в Ярославле слушаю доклад по клиентским сертификатам.

Рядом со мной сидит Коля Мациевский, он один из первых в России начал комплексно исследовать вопрос оптимизации загрузки сайтов и владеет сейчас первой в России компанией «WeBo», которая этим занимается. Коле скоро выступать, а пока он ковыряет исходники своего продукта, а точнее плагина, который встраивает упомянутый продукт в CMS «Bitrix».

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

$ll = strlen(bin2hex($this->body))/2;

if($ll > 500)
{
    $val = 9;
    $r += $val;
    $this->resultrules['rulescriptlenghts_sl'] = $val;
    return $r;//только одно правило из блока
}

Вот эта странная во всех отношениях (почему не просто strlen?) проверка считает вирусным любой JS-код, строка которого занимает больше, чем 500 байт.

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

Пасхальное яйцо в Lixil CMS

Скриншот веб-страницы teletrade.ru с адресной строкой http://old.t... и чёрно-белым фото мужчины в спортивной форме, держащего ракетку.
Пасхальное яйцо

Когда-то в фирме «Lixil», совладельцем которой я когда-то был (сейчас не существует), мы делали «Lixil CMS». Достаточно неплохая штука получалась, кстати. Но речь не об этом. В одну из версий этой CMS я встроил «пасхальное яйцо» — свою фотографию. Причём, фотография собиралась достаточно запутанным куском кода на PHP (если найду, покажу), а бинарная информация была раскидана по всем библиотеками в виде комментариев.

Сегодня я случайно обнаружил один из сайтов на той CMS и оказалось, что пасхальное яйцо до сих пор работает, его можно увидеть, если послать RPC-запрос (эти запросы использовались в AJAX-интерфейсе) специального вида: http://old.teletrade.ru/?LRPC=90&args={}

Картинку можно увидеть только в IE, так как не выдаётся HTTP-заголовок картинки, ну или в других браузерах можно использовать dataURI:

data:text/html,<img src="//old.teletrade.ru/?LRPC=90&args={}">

Вставлял я этот кусок кода ради теста — найдут ли программисты компании его в коде и смогут ли понять что он делает.

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

IE9 и его скорость, по сравнению с другими браузерами

Сижу сейчас на семинаре Михаила Черномордикова из Microsoft «HTML5, CSS3 и новый Internet Explorer 9». На экране — известный тест Flying Images, который Михаил демонстрирует в IE9 бета1 и FF 4 (неизвестной) бете.

У IE9, судя по всему сильное преимущество. Я как-то засомневался, помню на «Хабре» был материал, из которого следовало прямо обратное — как только в FF4 появилось аппаратное ускорение, IE9 остался далеко позади.

Скачал FF 4 beta7pre, включил в конфигурации всё, что относится к аппаратному ускорению, потестировал. В общем, Microsoft… вводит нас в заблуждение (как и статья на «Хабре»). FireFox явно впереди, хоть и незначительно.

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

PHP4 без XML-парсера

Мне тут по работе пришлось решить странную, по нынешним временам задачу — принимать XML в программе на PHP4.3+, где никакого парсера XML может не оказаться. Таковы реалии продукта и среды, где он будет использоваться (это код внешнего проекта). Поэтому мне пришлось написать небольшой парсер, который, правда, расcчитывает, что XML будет well-formed (я его получаю из доверенного источника).

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

<?
// Very Simple XML SAX Parser by Evgeny Stepanischev
// /
class VerySimple_XMLParser
{
    var $saxCallback;

    function VerySimple_XMLParser($saxCallback)
    {
        $this->saxCallback = $saxCallback;
    }

    function parse($xml)
    {
        if (preg_match_all('#<![CDATA[.*?]]>|</?[^>]*>|[^<]+#us', $xml, $matches)) {
            foreach ($matches[0] as $match) {
                $this->parseThis($match);
            }
        }
    }

    function parseThis($match)
    {
        $calls = array(
            '<!--'      => 'COMMENT',
            '<?'        => 'PI',
            '<![CDATA[' => 'CDATA',
            '<!'        => 'DTD',
            '</'        => 'TAGCLOSE',
            '<'         => 'TAGOPEN',
        );

        foreach ($calls as $need => $type) {
            if (strpos($match, $need) === 0) {
                return $this->parseByType($type, $match);
            }
        }

        return $this->parseByType('OTHER', $match);
    }

    function parseByType($type, $text)
    {
        if ($type == 'COMMENT') {
            return $this->addChunk($type, substr($text, 4, -3));
        }

        if ($type == 'CDATA') {
            return $this->addChunk('TEXT', substr($text, 9, -3));
        }

        if ($type == 'OTHER') {
            return $this->addChunk('TEXT', $text);
        }

        if ($type == 'TAGCLOSE') {
            $end = strcspn($text, ' >');

            return $this->addChunk('TAGCLOSE', substr($text, 2, $end - 2));
        }

        if ($type == 'TAGOPEN') {
            $this->addChunk('TAGOPEN', $this->parseTag($text));

            if (substr($text, -2) == '/>') {
                $end = strcspn($text, ' >');

                return $this->addChunk('TAGCLOSE', substr($text, 1, $end - 2));
            }
        }

        return '';
    }

    function parseTag($text)
    {
        $end = strcspn($text, ' />');
        $name = substr($text, 1, $end - 1);

        return array($name, $this->parseTagAttributes(substr($text, $end)));
    }

    function parseTagAttributes($text)
    {
        $nameStart = '(?:[A-Za-z_:]|[^\x00-\x7F])';
        $nameChar  = '(?:[A-Za-z0-9_:\.-]|[^\x00-\x7F])';
        $attrValue = '(?:\'[^\']*\'|"[^"]*")';

        if (preg_match_all("/{$nameStart}{$nameChar}*=$attrValue/us", $text, $matches)) {
            return $this->parseAttributes($matches[0]);
        }

        return array();
    }

    function parseAttributes($rawAttributes)
    {
        $attributes = array();

        foreach ($rawAttributes as $rawAttr) {
            list($name, $value) = explode('=', $rawAttr, 2);
            if ($value[0] == '"' || $value[0] == "'") {
                $value = substr($value, 1, -1);
            }

            $attributes[$name] = $value;
        }

        return $attributes;
    }

    function addChunk($type, $arg)
    {
        call_user_func($this->saxCallback, $type, $arg);
    }
}

// Пример использования

$xml =<<<XML
<!-- комментарий -->
<?xml version="1.0"?>
<root attribute="a.a" v="a">
    <v><![CDATA[data]]></v>
    <v>data</v>
    <v/>
</root>
XML;

// Понятно, что можно написать класс и передать в качестве
// callback в парсер метод объекта, но я для простоты, как пример,
// написал очень простую функцию
function VerySimple_XMLParser_Collector()
{
    static $tree = array();

    if (func_num_args() == 2) {
        $tree[] = func_get_args();
    } else {
        return $tree;
    }
}

$sax = &new VerySimple_XMLParser('VerySimple_XMLParser_Collector');
$sax->parse($xml);

echo '<plaintext>';
var_dump(VerySimple_XMLParser_Collector());

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

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

Гостиница «Которосль» в Ярославле

Номер категории «Люкс» в гостинице «Которосль» — это, ребята, сидячая ванна, механические замки, шумный холодильник «Смоленск» (этакий отбойный молоток в ночи), отсутствие халата, зубной щётки, мусорки, сейфа и интернета. Правда мебель чистенькая и новая, хотя и тошнотворная, но я не понимаю почему это называется «Люкс». Чего тут «люксового»?

Добавлено на следующий день: вот так выглядит спальня в номере, обратите внимание на оригинальную конструкцию лампы. Нет, это не реализация идей Тесла по беспроводной передачи электричества, это просто провод обрезан.

Номер «Люкс» (22.98КиБ)

Кстати, на конференции ADD, где я сейчас нахожусь, Windows 7 отказалась подключаться к интернету, пришлось на телефоне («Андроид» подключился без проблема и сразу) посмотреть выданный IP-адрес и прописать в Windows всё статически. Что это было я не знаю.

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

PHP и UTF-8: пятый этап

Итак, пятый этап. Точнее на этап, он, пожалуй, не тянет, но после этапов 4,5 и 4,6 мне хочется целых чисел.

Итак, пятый этап
Я заметил, что у меня, в моих аналогах строковых функций, умеющих работать с UTF-8, очень много последовательных проходов по строке. После Пайтона, с его итераторами и генераторами, у меня руки чесались оптимизировать это место.

В самом деле, если простое взятие символа по индексу в строке с фиксированным размером символа имеет сложность O(1), то в UTF-8 строке эта сложность возрастает до O(n). Соответственно, когда я прохожу циклом по строке с фиксированным размером символа эта сложность — O(n), а текущие циклы в моих функциях проходят строку UTF-8 за O(n²). Особенно это будет заметно в функции rtrim.

Я написал небольшие тесты. Замерял три прохода по тексту объёмом 187,5КБ. Проход по строке с однобайтной кодировкой — 0,2 секунды, по строке UTF-8 — 160 секунд. Забегая вперёд, скажу, что проход той же строки (UTF-8) итератором занимает 4,3 секунды, результат хуже, чем у встроенных функций, но в 37 раз лучше (на этих данных), чем проход прежним алгоритмом.

Вообще-то в PHP5 появилось очень много вещей, которые никто не использует. Свежие книжки ещё не появились, а документацию никто ни читает. Поэтому один из моих любимых вопросов на собеседовании по PHP — «что бы вы сделали, если бы вам в PHP понадобился двусвязанный список» (что-что, использовал бы SplDoublyLinkedList), впрочем если претендент не отвечает, в минус ему это не идёт, зато правильный ответ идёт в плюс.

Так вот, одна из вещей, которой мало кто пользуется — возможность создать в PHP свой итератор. У меня тут не руководство по итераторам, поэтому я ограничусь лишь кодом и примером использования. Вообще-то мне в коде понадобятся два итератора (второй будет итерировать строку с конца), но они очень похожи, поэтому я приведу только один.

Основная идея — если мы двигаемся по строке последовательно, где-то сохранять текущую позицию, тогда к следующему символу нам перейти будет проще (сложность — O(n)).

UTF-8 достаточно хорошо устроен — первый байт символа всегда можно отличить по виду от его хвоста. У «хвоста» старшие биты всегда выглядят как «10», вот и весь критерий. Так что мы всегда можем сказать где начался новый символ — там где старшие биты байта не совпали с «10».

class UtfString implements Iterator
    {
        protected $str;   // строка
        protected $idx;   // бинарный индекс строки
        protected $pos;   // UTF-8-индекс
        protected $len;   // бинарная длина
        protected $next;  // бинарный указатель на следующий

        protected $idx_start; // бинарный индекс откуда стартуем
        protected $pos_start; // UTF-8-индекс откуда стартуем

        // проверка — байт не является началом символа
        protected function isNotBegin($ch)
        {
            return (ord($ch) & 192) == 128;
        }

        public function __construct($str, $pos_start = 0)
        {
            $this->str = $str;
            $this->pos = $this->pos_start = $pos_start;
            $this->len = strlen($str);
            $this->next = null;

            if ($pos_start) {
                for ($i = 0; $i<$pos_start; $i++) {
                    $this->idx_start++; 

                    while ($this->isNotBegin($this->str[$this->idx_start])) {
                        $this->idx_start++;
                    }
                }
                $this->idx = $this->idx_start;

            } else {
                $this->idx = $this->idx_start = 0;
            }
        }

        public function current()
        {
            $idx = $this->idx;
            $out = $this->str[$idx++];

            while ($this->isNotBegin($ch = $this->str[$idx])) {
                $out .= $ch; // быстрее, чем массив!
                $idx++;
            }

            $this->next = $idx;

            return $out;
        }

        public function key()
        {
            return $this->pos;
        }

        public function next()
        {
            $this->pos++;

            if ($this->next) {
                $this->idx  = $this->next;
                $this->next = null;
            } else {
                for ($this->idx++; $this->isNotBegin($this->str[$this->idx]); $this->idx++);
            }
        }

        public function rewind()
        {
            $this->idx = $this->idx_start;
            $this->pos = $this->pos_start;
        }

        public function valid()
        {
            return $this->idx < $this->len;
        }

        public function __toString()
        {
            return $this->str;
        }
    }

Использовать итератор достаточно просто:

$str = str_repeat('Привет', 64000);

    foreach (new UtfString($str) as $position => $char) {
        // …
    }

Так как в итерации сначала вызывается «current», а потом «next» мне показалось странным терять информацию о положении следующего символа, которую мы в «current» уже знаем, так что я передаю её в «next» через защищённое свойство.

Список функций (из моего списка используемых функций), которые я перепишу с итератором (или скорость которых увеличится): trim, ltrim, rtrim, wordwrap, strcspn и strspn, а так же некоторые места в коде проекта.

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

НАМИ

Ездил сегодня ремонтировать обогреватель «AirComfort». Ничего обогреватель, простенький, потому и ломаться там особо нечему, спираль перегорела. Так бывает, повёз менять. Пишу себе заметку на будущее, если ещё сгорит. Замену осуществляет ровно одна организация — «Ремсервис», располагающаяся на территории НАМИ (это центральный научно-исследовательский автомобильный и автомоторный институт).

Схема территории «Ремсервис-СЕ»: 3 этаж, т. 1-46, показаны здания, подъездные пути и расположение объектов на плане.

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

Замена спирали происходит всё в той же печальной комнате, где принимают клиентов и занимает полчаса. Институт находится на ул. Автомоторная, д. 2 (проезд автобусом №123 с метро Речной вокзал).

Одноэтажное розовое здание с красными дверями и желтыми предупреждающими знаками, окруженное зеленым забором и деревьями с осенними листьями на асфальте.
Осенний двор с жёлтыми листьями на асфальте, белым автомобилем слева, кирпичным зданием и зелёным забором справа под ясным небом.
Серебристый кроссовер припаркован под навесом у бежевого здания, на асфальте видны опавшие листья, на заднем плане — деревья и другие машины на парковке.
Синяя вывеска с белым текстом «ИНФОРМАЦИЯ» и логотипом РЖД над деревянной скамьей у стены здания с облупившейся краской.
Вид через открытую дверь в тамбур с турникетом, освещённый солнечным светом; на стеклянных дверях видны отражения соседнего здания и деревьев.
Чёрный металлический забор с колючей проволокой и табличкой «А 123 будни 20:29 21:06 22:38 23:08 0:14 вых. дни 21:41 22:41 23:41 0:46» на фоне зелёной травы и деревьев.

Вообще НАМИ прекрасный образец «усадьбы разорившегося помещика» — видно, что денег когда-то было очень и очень много, а сейчас организация дышит на ладан.

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

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