программирование

Бу: ООП в bash

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

Долго мучался с названием, решил, что пусть пока называется «Бу» (b-oo.sh), выложил туда же пример на котором вчера тестировал:

#!/bin/bash
# Evgeny.Stepanischev Jan 2013 /

. b-oo.sh

@Class Base
    @Dim cnt

    @Dim
    __construct() {
        This[cnt]=0
    }

    @Dim
    IncCnt() {
        let 'This[cnt]++'
    }

    @Dim
    GetCnt() {
        [ "$This[cnt]" -gt 1 ] && s=s
        echo $Self said $This[cnt] time$s
    }
@End

@Class Dog Base
    @Dim
    say() {
        $This.IncCnt
        echo 'Bow-wow!'
    }
@End

@Class Car Base
    @Dim
    say() {
        $This.IncCnt
        echo 'Beep'
    }
@End

@Class Proxy
    @Dim obj

    @Dim Static
    getInstance() {
        $1.New Self[obj]
        @Ret $2 $Self[obj]
    }
@End


Proxy.getInstance Car car # создание через «фабрику», через статический метод
Proxy.getInstance Dog dog # создание через «фабрику», через статический метод

$car.say
$car.say
$car.GetCnt # 2, вызывается из родительского класса 

$dog.say
$dog.GetCnt # 1, вызывается из родительского класса

Dog.New anothedog # создание через конструктор

$anothedog.say
$anothedog.say

$anothedog.GetCnt # 2, вызывается из родительского класса

Я не стал заморачиваться с модификаторами доступа (хотя может ещё и сделаю), из реализованного: создание объектов, наследование (есть множественное), статические и динамические методы и свойства, конструктор, есть переменная This (текущий объект), Self (текущий класс) и Parent (чтобы вызывать родительские методы).

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

Во-первых, «@Dim», определящий свойства должен находиться на той же строке, что и имя свойства, а в случае метода имя метода должно быть записано на следующей строке (смотрите пример).

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

# !неправильно
obj=$(Dog.New)
obj=`Dog.New`

Чтобы выполнить Dog.New интерпретатор создаёт отдельный процесс, где всё и выполняется, после выхода из этого процесса всё моё колдунство исчезает и от переданного значения толку нет (если у вас не «баш 4», тогда есть способы).

Поэтому конструктор у меня принимает первым параметром имя переменной, куда нужно положить экземпляр класса, а облегчения жизни программисту сделана специальная команда «@Ret», посмотрите как она используется в методе getInstance.

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

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

Получение случайного числа 0…255 в bash

Понадобилось получить случайное число в шеле из устройства /dev/urandom, написал небольшую функцию, сохраню у себя, чтобы не потерялась.

getrand() {
    head -c1 /dev/urandom | hexdump -d | sed -E '1!d;s/.+ +0+//'
}

Ещё вариант придумался (но необходим openssl):

getrand() {
    openssl rand 1 -hex | xargs -I. printf "%d" 0x.
}

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

Отгадка: CSS не видно, картинка спрятана

«Уровень один» вчерашней головоломки прошли многие, этот ларчик открывается просто — есть RFC 5988, где описан способ подключать некоторые ресурсы через HTTP-заголовок. Этот RFC поддерживают «Опера» и «Файерфокс». На скриншоте, в средстве для разработчиков «Оперы» заголовок хорошо видно.

Где спряталось содержимое (20.32КиБ)

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

Теперь главное. А картинка-то откуда взялась?

Есть такой стааааренький формат графики XMB, тестовый формат и по виду явно происходит от заголовочных файлов Си. Про него и не помнит уже никто, важно же в нём то, что когда-то его поддерживали все браузеры. Я даже когда-то библиотеку Image_XBM писал для работы с ним из ПХП.

Вот начало одного из файлов, содержащих картинку в формате XBM:

#define _width 300
#define _height 225
static char _bits[] = {
0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef,
0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0xde, 0x7b, 0xef, 0xbd, 0xf7, 0x0e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00…
}

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

У формата тяжёлая судьба, сначала его похоронил «Эксплорер», убрав в версии 6.0SP1 его поддержку из-за ошибки в кодеке, потом «Файрфокс» выбросил его за ненабодностью, а в «Хроме» его не было с рождения. Остался он только в «Сафари» и в «Опере».

Мне давно хотелось стереть с него пыль и куда-нибудь его пристроить повеселее, вот я и решил вчера к ночи попробовать вставить его в CSS. Удалось сделать это быстро, почти без труда, но стало интересно — сколько можно выкинуть из формата, чтобы браузер его по-прежнему понимал.

Вот часть CSS из вчерашнего примера:

  background: 20px 20px url(bolk.css#0x5F0xBF0xFF0xDC0x530xF60x870…) no-repeat;
  width: 80px; /*_width 80px*/
  height: 80px; /*_height 80px*/
  border: 7px dashed #259;
  outline: #259 dashed 6px;
  margin: 10px; padding: 3px;
}

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

В гонке выкидывания лишнего из картинки в формате XBM победила «Опера», ей нужно было только оставить первую строку «#define», которую она использует в качестве «магического» значения для определения формата, где-то раскидать «_width» и «_height» (я их засунул в коментарии CSS) и оставить строку шестнадцатеричных чисел, причём, как оказалось, «Опере» и запятые не нужны!

Итак, откуда взялась картинка?

«Опера» смотрит в свойство «background» и видит там урл с картинкой, часть с хешем просто отбрасывается, в данном случае она не используется и браузер грузит картинку с адреса «bolk.css». «Опера» пытается определить в каком формате пришла картинка, тут нам помогает «магическая» строка «#define», раз она есть, стало быть это XBM.

Дальше браузер ищет в полученном файле «_width» и «_height» с цифрами и находит, считая цифры размерами картинки, после чего ищет первое вхождение «0x…» и двигается по ним, пока не находит их все, это и есть тело картинки, которую надо отобразить.

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

Головоломка для верстальщика: CSS не видно, картинка спрятана

Хотите голову поломать? Вот вам тест на знание всякого странного, откройте следующий урл в «Опере»: /files/opera-mystery/ (именно в «Опере») и попробуйте догадаться как это сделано.

Ответ завтра. Кажется, во всём интернете так ещё никто не делал.

Загадка (25.18КиБ)

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

Вот что видно в консоли, если попытаться скачать этот файл:

bolk@Bolk ~$ wget -O- --user-agent="Opera/9.80 (Macintosh; Intel Mac OS X 10.8.2) Presto/2.12.388 Version/12.12" /files/opera-mystery/
--2013-01-22 00:24:09--  /files/opera-mystery/
Resolving bolknote.ru... 91.230.61.15
Connecting to bolknote.ru|91.230.61.15|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 0 [text/html]
Saving to: ‘STDOUT’

    [ <=>                                                                                                                                                   ] 0           --.-K/s   in 0s      

2013-01-22 00:24:09 (0.00 B/s) - written to stdout [0/0]

Когда догадаетесь где тело страницы, переходите на уровень два — догадайтесь откуда берётся картинка.

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

Поиск пропусков в таблице

У нас тут сегодня на работе возникла гипотетическая потребность найти пропуски в конкретной таблице базы данных. Так как я кроме как с MySQL в последнее время ни с чем не имел, прикинул как бы я решил эту задачу там. Получилось что-то такое:

SELECT id FROM (
    SELECT id, id-@prev AS diff, @prev:=id
    FROM
        (SELECT @prev:=NULL) ``,
        sourceofdata
   ORDER BY id
) `` WHERE diff>1

Внутри есть напрасный запрос «SELECT @prev:=NULL», который там только затем, чтобы решение получилось в одну строку.

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

Может кто-то придумать проще? Не важно для какой СУБД.

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

ASCII art vs. Unicode art

С приходом цветного Юникода некоторые символы могут присутствовать в системе в так называемом «стиле эмодзи», проще говоря в цвете.

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

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

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

Что получилось (14.70КиБ)

Для начала я выяснил, что единственный браузер на «Маке», который показывает эти символы в цвете, это «Сафари», остальные показывают в лучшем случае в ч/б («Файрфокс», «Хром»), в худшем показывают какие-то обрывки («Опера»).

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

Карта цветов (11.91КиБ)

На «баше» (ну да, я извращенец, но так просто быстрее было) накидал небольшую программу, который из этой картинки генерирует текстовый файл, состоящий из номера символа по порядку и значения цвета в формате #rrggbb:

#!/bin/bash
FILE='color-map.png'

wh=(`file $FILE | grep -oE '\S+ x [0-9]+'`)

function ColorAt {
    convert $1[1x1+$2+$3] txt: | grep -oE '#[0-9A-F]{6}'
}

for (( y=0; y<${wh[2]}; y+=5 )); do
    for (( x=0; x<${wh[0]}; x+=5 )); do
        pos=$(( $x/5 + $y*${wh[0]}/25 ))

        echo "$pos $(ColorAt $FILE $x $y)"
    done
done

Утилита convert, которая используется в скрипте, это часть пакета ImageMagick.

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

$cached = [];

for ($y = 0; $y<$h; $y++) {
    for ($x = 0; $x<$w; $x++) {
        $cnum = imagecolorat($im, $x, $y);

        if (isset($cached[$cnum])) {
            $outchar = $cached[$cnum];
        } else {
            $mindiff = $outchar = INF;

            $c = imagecolorsforindex($im, $cnum);
            foreach ($colors as $colorandch) {
                list($ch, $r, $g, $b, $ch) = $colorandch;

                $diff = abs($r - $c['red']) + abs($g - $c['green']) + abs($b - $c['blue']);

                if ($diff < $mindiff) {
                    $mindiff = $diff;
                    $outchar = $ch;
                }
            }
            $cached[$cnum] = $outchar;
        }
        echo '<b>', $outchar, '</b>';
    }
}

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

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

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

Сокращение ссылок через bit.ly из bash

На «Макрадаре» появилась статья про сокращение ссылок через bit.ly через «Автоматор», хорошая штука, иногда бывает нужно, когда кривой парсер какого-нибудь сайта не принимает какую-то ссылку, но сам скрипт (в примере «Автоматор» запускает его через «баш») мне не понравился.

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

Вот мой вариант:

TOKEN=<ваш токен, полученный по инструкции выше>
URL="$1"

[[ "$URL" =~ ^https?:// ]] || URL="http://$URL"

path="${URL#*//*/}"
host="${URL:0:${#URL}-${#path}}"
path=$(echo -n "$path" | hexdump -ve '1/1 "x%02X"' | tr x %)

curl 'https://api-ssl.bitly.com/v3/shorten' \
     --data "access_token=$TOKEN" \
     --data 'format=txt' \
     --data "longUrl=$host$path"

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

Неразборчиво в ОБД «Мемориал»

Я думал, что в ОБД «Мемориал» записаны данные только тех карточек, данные которых удалось разобрать достоверно. Случайно выяснил, что очень много данных с пропусками.

Результат запуска (24.33КиБ)

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

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

&^ в Google Go

Отличная иллюстрация того как важны мелочи в документации…

Читал чужой исходный код на «Гоу» и обнаружил необычную конструкцию:

v &^= 1 >> n

Почесал голову, не понял что это и пошёл читать документацию. Выяснилось, что эту конструкцию я когда-то упустил при чтении документации и означает она «bit clear (and not)», то есть является противополжностью „|=“, не ставит бит, а очищает его. Более того, есть ещё унарная операция «^», которая инвертирует все биты в числе (соблюдая размер его типа), вот как они работают:

package main

func main() {
    var test byte = 250

    println(test) // 250 (11111010)
    
    test = ^test
    println(test) // 5 (00000101)

    test &^= 4   //    (00000100)
    println(test) // 1 (00000001)
}

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

Алгоритм линейного счётчика на Google Go (golang)

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

Подсчёт 1 000 000 значений (8.81КиБ)

Подсчёт на моём ноуте одного миллиона уникальных значений занимает примерно 6,5 секунд. Погрешность с вектором длиной 100 тысяч бит (≈3КиБ) — 7,6%.

Чтобы скомпилировать программу новым (версии 1 и выше) компилятором «Гоу», нужно запустить следующую команду:

go build linear-counter.go

1 комментарий

Алгоритм линейного счётчика: десять бит в одном бите

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

Упомянул его в разговоре знакомый математик К.Н., за что ему большое спасибо! По этому алгоритму нашлась замечательная статья «Линейный счётчик», с понятным объяснением, добавить к которому нечего, но я всё-таки приведу у себя его краткое описание.

Сначала для чего алгоритм применяется и чем он замечателен.

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

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

Но есть способ лучше! Алгоритм линейного счётчика.

Оказывается, если взять вектор в 1000 бит и 1000 раз установливать в нём в единицу случайный (с равномерным распределением) бит (некоторые биты будут установлены в единицу несколько раз), то нулей останется примерно около 370. Эта оценка получается из 1/e ≈ 0,36787944117… ≈ 0,37, подробнее есть в другой статье на том же сайте.

Таким образом, если мы встречаем битовый вектор длины n, в котором примерно 63% (100 − 0,37 × 100 = 63) единиц, то над этим вектором было произведено n операций установки бита в единицу, при условии, что биты выбирались случайно и равномерно.

Отсюда, если взять битовый вектор достаточной длины, хешировать входные значения какой-то функцией, которая даёт равномерное распределение, из результата выбирать бит в нашем векторе и устанавливать его в единицу, а в конце подсчитать сколько единиц получилось, можно выяснить с хорошей вероятностью сколько уникальных значений было подано на вход (погрешность порядка 1% получается!).

Демо алгоритма линейного счётчика (10.68КиБ)

Хорошо это работает только на больших числах, я для наглядности сделал небольшое демо работы алгоритма линейного счётчика, там пятьдесят входных значений и вектор размером в 30 бит. Так как чисел немного, то и результаты иногда скачут сильно, но на миллионах значений погрешность выравнивается.

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

Список доменов, запрещённых к регистрации в зоне «.рф»

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

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

curl -so- http://flisti.ru/stop-list.txt |
iconv -f cp1251 | awk -F'[\t\.]' 'BEGIN {srand()} $3 {print rand(), $3}' |
sort -n | awk '{print $2; exit}' | tee >(cut -c1 | tr '[:lower:]' '[:upper:]') |
sed -n '1s/^.//;1h;2p;2g;2p' | tr -d '\n' ; echo '!'

Или можно так:

curl -so- http://flisti.ru/stop-list.txt | iconv -f cp1251 |
awk 'BEGIN {srand()} {print rand()"."$2}' |
sort -nt. | head -1 | cut -f2 -d. |
tee >(cut -c1 | tr '[:lower:]' '[:upper:]') | tail -r | tr -d '\n' | xargs -I? echo ?! | cut -c1,3-

Его можно сильно упростить, если выкинуть часть, которая отвечает за изменение регистра первой буквы (заодно будет работать не только в «баше»):

curl -so- http://flisti.ru/stop-list.txt |
iconv -f cp1251 | awk -F'[\t\.]' 'BEGIN {srand()} $3 {print rand(), $3}' |
sort -n | awk '{print $2"!"; exit}'

Вариант для «баша», но без awk:

curl -so- http://flisti.ru/stop-list.txt |
iconv -f cp1251 | grep -Eo '\s.+рф' | cat -b |
tee >(echo $((`wc -l`*$RANDOM/32768))) | tail -r |
grep -w `head -1` | cut -f3 | cut -d. -f1 | xargs -I? echo ?!

Почему-то мне нравится программировать на «баше», остановиться не могу…

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

Финализированные конструкторы под разными именами

Вчера вечером подумалось, что если в PHP сделать следующее, будет ошибка или нет:

class A {
    final public function a() {
        echo 1;
    }
}

class B extends A {
    public function b() {
        echo 2;
    }
}

$o = new B();

Интересно тут то, что не сразу видно в чём вообще загвоздка. Ну или я бы увидел не сразу, думаю, если бы сам этот пример не придумал. Кажется, все уже отвыкли в PHP от «старых» конструкторов, где конструктором считается метод, имя которого совпадает с именем класса (если нет метода «__construct»).

От осознания этого факта дополнительно отвлекают ничего незначащие имена классов и методов.

Проблема же в том, что в первом классе конструктор финализирован, его дальше переопределять нельзя. Но у второго класса имя конструктора другое. Вот и вопрос — что будет? От PHP можно чего угодно ожидать.

Тем не менее, тут язык выдаст логичную ошибку, которая, правда, способна вызвать лёгкое недоумение своей формулировкой:

Fatal error: Cannot override final A::a() with B::b() in test.php on line 12

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

«Чистый веб» Яндекса

У «Яндекса» как-то незаметно проект «Чистый веб» вышел. Ещё я в этом проекте участвовал. Дописывал плагин для phpBB, правда тот, который там дают скачать (зачем-то вместе с phpBB внутри) другой совсем, переписан под PHP 4.1 и у меня смутные воспоминания, что я его под вторую версию писал, а этот под третью.

Остался ли там какой-то мой код не могу сказать, всё сильно изменилось внутри и полтора года прошло.

1 комментарий

Скрипт для перевода даты из старого стиля в новый

Часто перевожу даты из  календаря в григорианский (со старого стиля на новый), так как занимаюсь исследованием своих предков. Устал использовать для этого всякие веб-сервисы, написал по-быстрому JS, который переводит одну дату в другую, надо будет оформить как виджет или скриптлет и прицепить к своему браузеру.

function Jul2Greg(day, month, year) {
    // «сахар» для создания даты
    var d = function(day, month, year) {
        return new Date(year, month - 1, day, 12, 12, 12);
    }

    var date = d(day, month, year);

    // коррекция дней исходя из года
    var correction = date <= d(28, 2, 1700) ? 10 : 0 | (year / 100 - 17 + 11);

    // корректируем левый конец диапазона
    if (year % 100 == 0 && month < 3) {
        correction--;
    }

    return d(day + correction, month, year);
}

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

Генерируемый задний фон

Я как-то пропустил знаменитую (оказывается) программу для генерации фона, похожего на лабиринт из двух слешей. Несложная программа на Бейсике:

10 PRINT CHR$(205.5+RND(1));: GOTO 10

Выводит вот такой фон (скриншот с «Командора 64», поэтому цвета такие):

Фон из слешей (27.66КиБ)

Для тех, кто не понимает этот архаичный язык, перепишу на Пайтоне:

import random; while 1: print random.choice(u'╱╲'),

и ПХП:

for(;;) flush(print '/\\'[mt_rand(0, 1)+.5]);

На «Хабре» статья пробегала, где автор пытался сделать то же при помощи CSS, не справился и сделал генерацию на серверной стороне — на PHP. Он высказывал мысль использовать для этого JS, но то ли не смог, то ли отбросил её по каким-то иным причинам.

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

Сначала результат (в левом верхнем углу фрагмент нормального размера):

Фон в виде лабиринта (31.46КиБ)

Теперь посмотрим код. Я убрал всё лишнее, подробности можно посмотреть в разделе «Храню»:

!function(c) {
    var ctx = document.getCSSCanvasContext ? 
        document.getCSSCanvasContext("2d", "maze", c.width, c.height):
        c.getContext("2d");

    ctx.font      = "10px monospace";
    ctx.fillStyle = '#36f';
    ctx.fillRect(0, 0, c.width, c.height);

    ctx.fillStyle = '#068';

    for (var y = 7, ly = c.height; y < ly; y += 8) {
        for (var x = -1, lx = c.width; x < lx; x+= 4) {
            ctx.fillText("/\\".charAt(Math.random() + .5), x, y);
        }
    }

    if (!document.mozSetImageElement && !document.getCSSCanvasContext) {
        document.getElementsByTagName('body')[0].style.backgroundImage='url(' + c.toDataURL("image/png") + ')';
    }

}(document.getElementById('maze'));

Внутрь функции у меня передаётся объект «канваса» (размером 99×96, подогнан под шрифт).

В первой строке проверяется наличие метода «getCSSCanvasContext», он работает только в браузерах, основанных на Вебките (Хром, Сафари) и позволяет напрямую использовать «канвас» для задника, нужно только в CSS указать идентификатор:

body { background: -webkit-canvas(maze); }

Если этот метод недоступен, то я рисую в скрытый тег «канваса», который есть у меня на странице. Вывожу те же самые символы, которые использовались в Бейсике.

Связь «канваса» и задника для браузеров на «Геко» (ФайрФокса и менее известных) в Джаваскрипте не видна, она полностью на стороне CSS:

body { background: -moz-element(#maze); }

Эти браузеры позволяют указать в качестве задника что угодно — хоть IFRAME, был бы у него идентификатор. Соответственно, «maze» — идентификатор моего «канваса». Делать больше ничего не нужно, любой элемент с этим «айди» размножится в качестве фона.

В последних строках я проверяют доступен ли метод для указания «-moz-element», нет ли метода для рисования «канвасом» прямо в задник, если нет, то перевожу картинку из «канваса» в dataURI и добавляю её в таком виде на фон тега BODY. Таким образом, этот код работает и для «Оперы» с «Эксплорером» (начиная с девятого).

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

Масштабирование тега CANVAS

Думаю, многие знают, что у тега CANVAS можно задать размер, а потом смасштабировать его до нужных размеров при помощи CSS.

<canvas width="10" height="10" style="width:100px; height:100px"></canvas>

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

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

canvas {
    image-rendering: optimizeSpeed; /* old WebKit, Safari, FireFox 3.6+ */
    image-rendering: -webkit-optimize-contrast; /* newer WebKit (CSS3 proposal) */
    image-rendering: -o-crisp-edges; /* Opera 11.6+ (CSS4 prefixed) */
    image-rendering: -moz-crisp-edges; /* FF 6+ (CSS4 prefixed) */
    image-rendering: crisp-edges; /* CSS4 */
}
Сравнение пиксельной и сглаженной диагональной линии: слева — ступенчатый край, справа — размытый, демонстрирующий эффект антиалиасинга.

Чтобы была понятно о чём вообще речь — слева на скриншоте версия без сглаживания (с применёнными стилями), справа — со сглаживанием, это я прочертил одну линию:

with (canvas.getContext('2d')) {
        moveTo(0, 0);
        lineTo(10, 10);
        stroke();
    }

Я снабдил код комментариями, чтобы было видно где это работает. К сожалению, в Эксплорере ничего не получится, когда-то у него было свойство -ms-interpolation-mode, но с девятой версии (где и появился «канвас») оно уже не работает, придётся решать проблему через JS.

Добавлено позднее: похоже, у Хрома тоже проблемы. На разных ОС может работать или нет.

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

Теннис на CSS-анимациях

CSS-теннис (26.61КиБ)

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

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

Префиксы для всех браузеров в коде ставить не стал, оставил только для «вебкита» (будет работать в «Сафари», «Хроме» и «Яндекс.Браузере»), всё равно все браузеры собираются начать их поддерживать, так зачем же стараться?

Так вот, анимации в ЦСС — интересная штука, позволяющая простыми средствами плавно менять свойства объектов на странице, мне понравилось. Но у неё есть недостаток, на мой взгляд — анимируется ровно один элемент и у неё нет состояний.

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

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

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

Кстати, гениальные ребята, которые для Интернет Эксплорера когда-то придумали большую часть веб-технологий, которые мы под другими именами только недавно получили в виде стандартов W3C, первыми задумались и об анимации в вебе без ДжаваСкрипта. Сначала это был примитивный тег MARQUEE, появившийся аж во второй версии браузера, позже — технология HTML+TIME, позволявшая создавать очень сложную анимацию при помощи специальной разметки, это было позднее — в 5.5, но это было 12 лет назад!

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

Наведение порядка в PHP (версия 5.5.0)

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

Например, теперь уже можно сделать вот так:

[
    POW => function($x) { return $x*$x; },
    SIN   => function($x) { return sin($x); },
][$selector]

Правда, всё ещё нельзя вызвать функцию сразу. В 5.5.0 будет работать вот такая вещь:

if (empty(somefunc())) { /* …  */}

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

Появилась возможность использовать list внутри цикла следующим образом:

foreach ($somehash as list($key, $value)) { /* … */ }

И сделано ещё несколько мелочей, но работы впереди ещё много.

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

Генераторы в PHP 5.5

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

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

<?
function enumerate($arr)
{
    $i = 0;

    foreach ($arr as $value) {
        yield [$i++, $value];
    }
}

function ifilter(callable $predicate = null, $arr)
{
    if ($predicate === null) {
        $predicate = 'boolval';
    }

    foreach ($arr as $value) {
        if ($predicate($value)) {
            yield $value;
        }
    }
}

function islice($arr, $start, $stop)
{
    if (is_array($arr)) {
        reset($arr);

        for ($i = $start; $i > 0 && each($arr); $i--);

        for ($i = $stop - $start + 1; $i > 0 && list(,$value) = each($arr); $i--) {
            yield $value;
        }
    } else {
        for ($i = $start; $i > 0 && $arr->valid(); $i--) {
            $arr->next();
        }

        for ($i = $stop - $start; $i > 0 && $arr->valid(); $i--) {
            yield $arr->current();
            $arr->next();
        }
    }
}

$array = [10, 20, 30, 33, 40, 50, 60];

$gen = islice(
    enumerate(
        ifilter(
            function($x) { return ($x % 10) === 0; },
            $array
        )),
        1, 4
    );

foreach ($gen as $value) {
    print_r($value);
}

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

Концепция генераторов проста, как герань. PHP поддерживает два способа их создания (как и Пайтон), но мне милее оператор yield. Работает он примерно как return, но при повторном входе в функцию её выполнение происходит не сначала, а с того места, откуда управление было возвращено при помощи yield. Можно ещё передавать значения внутрь генератора, но я про это сейчас не буду. Вцелом, получаются настоящие ленивые вычисления — всё это вызывается ровно тогда, когда это реально нужно.

Генераторы, обычно, работают с итерируемыми объектами — возращают их и/или принимают. Итерируемый объект, проще говоря, это то, по чему можно пробежаться циклом. Генераторы, кстати, тоже итерируемые.

И вот тут опять проявляются недостатоки PHP: отсутствие общей логики, непродуманность.

Массив тут до сих пор примитивный тип и он требует иной способ обработки. Посмотрите на генератор islice, тут проблема видна во всей красе. Начинается она уже с хинтинга, я не могу потребовать от $arr быть типа array, потому что на вход может прийти другой генератор, и указать класс генератора тоже не могу — массив ему не принадлежит. Думаю, следует ожидать появления нового типа «iterable», как это уже случилось с callable.

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

Мне кажется, PHP следует развивать класс ArrayObject, который уже присуствует в языке и делать прозрачное преобразование примитивных типов в объекты (как это происходит в JavaScript), это решило бы некоторые проблемы.

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

← 1 32 33 34 35 36 67 →