20 нояб. 2007 г.

Спасибо тебе, Мишка!


В книге "Mastering Perl" есть глава "Философия борьбы с неразрешимыми проблемами от Брайена ди Фоя". Один из рецептов звучит так: "А пытались ли вы поговорить об этом с Мишкой?"

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



Я вспомнил этот совет когда ломал голову над тестовым заданием следующего содержания:
Есть две таблицы:
table1 (table1_id integer, name varchar(255)),
table2 (table2_id integer, table1_id integer, alias varchar(255))

Выбрать записи из table1, для которых количество подчинённых записей в table2 максимально.
У меня сложные отношения с SQL. Периодически в него погружаюсь и решаю довольно сложные задачи. Но стоит полгода не позаниматься -- все забывается чуть ли не начисто. (Нечто подобное, но еще в большей степени происходит с XSLT). Ясно было, что надо использовать GROUP и MAX, но как объединить все это в один запрос?...

Я прокрался в спальню, к детской кроватке. У пятилетнего Степы есть несколько медведей, в том числе два белых брата Кнут и Олаф Хаммундсены. Интуиция подсказывала, что для моих целей подойдет Винни-Пух. Через пять минут я четко и с расстановкой объяснял ему, в чем проблема. А через 10 минут решение было готово. Вот оно:

SELECT table1_id FROM table2
GROUP BY table1_id
HAVING COUNT(*) =
(
SELECT MAX(table2_count)
FROM (
SELECT COUNT(*) AS table2_count
FROM table2
GROUP BY table1_id
) AS inner_table
);

Ответ находится в 3 этапа:
  1. Узнать, сколько дочерних записей приходится на каждую родительскую запись.
  2. Найти максимум.
  3. Выбрать строки, где количество дочерних записей равно найденному максимуму.
Моя ошибка заключалась в том, что я пытался отыскать какой-то волшебный метод, который позволил бы добиться результата сразу.

CGI::Application и AJAX

В выходные решил попрактиковаться с CGI::Application, библиотекой JQuery и AJAX, потому что за год погружения в краулеры/парсеры подзабыл, как пишутся CGI-приложения. Получился вот такой словарик онлайн.

Больше всего времени угрохал на то, чтобы разобраться, как передавать клиенту в ответ на асинхронный запрос данные в JSON-формате -- то есть ни HTML ни XML, а JavaScript. Соответственно, content-type в заголовке HTTP-ответа должен быть "text/javascript". Между тем, фреймворк CGI::Application предполагает, что каждый "run-mode" возвращает HTML.
Вот как выглядит решение:
$self->header_type('none');
print "Content-Type: text/javascript; charset=utf-8\n\n";
return objToJson($self->_get_dictionaries() );
  • $self->header_type(none) "сообщает" фреймворку: не заботься о HTTP-заголовках.
  • затем мы выводим свой собственный, нестандартный заголовок
  • метод $self->_get_dictionaries() возвращает структуру данных, которая преобразуется в JSON-формат функцией objToJson из CPAN-библиотеки JSON
На стороне клиента:
function resetDictionaries() {
$.getJSON('/cgi-bin/words.cgi',
{rm: 'update_dicts',
dicttype: document.SearchForm.DictType.value
},
function(data) {
populateCombo('#Dict', data);
});
}
Эта функция вызывается когда пользователь меняет категорию словаря в верхнем выпадающем списке.
  • методом JQuery getJSON мы отправляем на сервер асинхронный запрос
  • ответные данные -- те самые, которые были упакованы методом objToJson -- приходят в анонимную callback-функцию. Ее аргумент data преставляет собой готовый к использованию JavaScript-объект. Функция populateCombo (которая здесь не приводится) динамически меняет содержание выпадающего списка словарей.
Разобраться с JQuery мне, помимо официальной документации, помогла статья на RSDN: JQuery -- Javascript нового поколения.

14 нояб. 2007 г.

List Comprehension средствами Perl

Решая один из вопросов теста, понял, как средствами Perl сделать подобие питоновского 'list comprehension'.

Задача: имеется строка запроса вида: 'param1=foo&param2=bar...'
Необходимо получить хэш: (param1 => 'foo', param2=>'bar'...)

Прямолинейное решение:
sub query2hash {
my $query = shift;
my @arr;
foreach (split /&/, $query) {
push @arr, split /=/, $_;
}
@arr;
}
Каждому, кто знаком с "list comprehension" в языке Python, столь многословная инициализация массива @arr покажется каменным веком. Выполнив на Python-е:
query = 'param1=foo&param2=bar&param3=zoo'
arr = [ pair.split('=') for pair in query.split('&') ]
мы получим список списков:
[['param1', 'foo'], ['param2', 'bar'], ['param3', 'zoo']]
Можно ли написать на Perl столь же лаконичную конструкцию?...
Проще простого:
sub query2hash {
map { split /=/ } split /&/, shift;
}
Всего строчка! Более того, в Python-е предстоит еще повозиться, чтобы преобразовать список списков в словарь. А тут мы получаем хэш даром. Достаточно присвоить список, возвращаемый функцией, хэш-переменной:
my %params
= query2hash( 'param1=foo&param2=bar&param3=zoo' );

См также:

Уверенные знания и большие плюсы

Компании Инфо-скан требовался ведущий web-разработчик, обладающий списком добродетелей, который я не цитирую, дабы не выглядеть занудой. Все эти "уверенные знания", "большие плюсы"...

Тестовое задание состояло из вопросов по Perl, SQL и Unix. Я отдавал себе отчет, что 24 вопроса -- многовато для первого знакомства. Но работа обещала быть интересной (сбор информации, анализ текста -- близко к поисковым задачам, которыми я занимался почти год). К тому же, хотелось освежить память по SQL. Вот почему я посвятил этому тесту целый выходной.

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

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

Позапрошлым летом я пытался устроиться в Гугл. После, наверное, месячного молчания оттуда ответили, что у них, к сожалению, нет подходящей для меня вакансии в Москве. И ни слова о 8-клеточных пятнашках, которые я вполне корректно решил методом "A-Star" -- таково было тестовое задание.

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