28 февраля 2006

Быстрая загрузка CSV-файла в базу данных

Как известно, для работы с CSV-Файлами PHP предоставляет функции fgetcsv и fputcsv. Поскольку эти функции обрабатывают файл построчно, то загрузка данных в базу данных происходит очень медленно: я так ни разу и не дождался, когда же обработается тестовый файл 40 метров длиной.

Опрос показал, что есть специальные средства, позволяющие загрузить CSV в базу очень быстро - речь шла о софтине bcp из комплекта MS SQL. Но тулза эта виндовая, а мне требовалось решение на кросс-платформе.

Так я обнаружл в MySQL'е наличие мега-структуры LOAD DATA IN FILE. Работает ну очень быстро: мой преславутый файл в 40 метров загружается за 16 секунд. В этом SQL-запросе есть всё, что надо для работы с CSV: указание символов для экранирования и отквочивания, пропуск первых N строк (заголовок), итд.

Кому лень ковырять мануал, милости прошу поюзать специально мной написанный для этой цели PHP-класс.

27 февраля 2006

Про простоту и незамысловатость

Тут вот в комментах люди отмечают, что пишу я вещи в ПРИНЦипе прозаические (про заек?), словно вынырнул я из каких-то неведомых глубин программирования и узрел свет в оконце - более простые и даже примитивные методы запрогать что-то полезное. Да, я согласен, для многих людей то, что я здесь излагаю - проза, скучная, серая. А для многих других - новинка, даже диковинка.

Но когда я вижу вот такой код, то я готов завести ещё 5 блогов и писать ещё более простые вещи ещё более примитивным языком:

<?php
$idmode = $userdata->user_idmode;
if ($idmode == 'nickname')
  $user_identity = $userdata->user_nickname;
if ($idmode == 'login')
  $user_identity = $userdata->user_login;
if ($idmode == 'firstname')
  $user_identity = $userdata->user_firstname;
if ($idmode == 'lastname')
  $user_identity = $userdata->user_lastname;
if ($idmode == 'namefl')
  $user_identity = $userdata->user_firstname.
  ' '.$userdata->user_lastname;
if ($idmode == 'namelf')
  $user_identity = $userdata->user_lastname.
  ' '.$userdata->user_firstname;
if (!$idmode)
  $user_identity = $userdata->user_nickname;
?>

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

Как бы я написал? Просто: switch() и в дефолте $user_identity = $userdata->user_nickname;

Кстати, знаете, как я понимаю, что написанный код действительно хорош? Когда после N-ной правки я удаляю половину кода, а функциональность не теряется и не ухудшается. Чаще всего так получается сделать, если сделегировать часть работы какой-то другой и более грамотно написанной программе: броузеру, апачу, май-эс-кью-элю, самой операционке... И тогда я слышу чей-то шепот за спиной: keep it simple, stupid :]

Урок чернухи

ON DUPLICATE KEY UPDATE ещё круче

Я уже писал, как можно заменить связку из трёх запросов SELECT-INSERT-UPDATE на один запрос INSERT-SELECT FROM-ON DUPLICATE KEY UPDATE. Так же я тогда отметил о таком не очень удобном моменте, как необходимость нагенеривать данные для запроса: как для INSERT-блока, так и для UPDATE-блока. Например, в данный момент я ковыряю кусок кода, который либо добавляет нового подписчика, либо обновляет данные существующего:
<?
$sql = "INSERT INTO `subscription` (`email`, `id_language`, `id_currency`)
VALUES ('".$email."',".$language.",".$currency.")
ON DUPLICATE KEY UPDATE
`id_language`=".$language.",
`id_currency`=".$currency;
?>

Видите? Бедному РНР нужно подставить значения переменных не только в INSERT, но и в UPDATE. Чем это плохо? Во-первых, если придётся ковырять этот код в следующий раз, например, перед значениями $language и $currency приписать (int), чтобы явно привести их к целому типу, то придётся сделать 4 правки. Во-вторых, работы для PHP-сервера больше: одно дело просто строчку скопировать, другое - проставлять значения переменных. Столбцов может быть не 2-3, а 20, да плюс несколько таких запросов (в цикле) - тогда это может быть значительно.

Чтобы в этой ситуации облегчить работу PHP-серверу и немного больше напрячь ленивый SQL-сервер, есть функция VALUES(). Она позволяет, в данном случае, обратиться в UPDATE-блоке к данным из INSERT-блока. Таким образом, код становится чище:
<?
$sql = "INSERT INTO `subscription` (`email`, `id_language`, `id_currency`)
VALUES ('".$email."',".$language.",".$currency.")
ON DUPLICATE KEY UPDATE
`id_language`=VALUES(`id_language`),
`id_currency`=VALUES(`id_currency`)";
?>

26 февраля 2006

Скорочтение

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

Меня долго смущала практическая реализизация такого требования: где найти много текста разной ширины. Я сразу откинул идею скачивания с lib.ru разных книжек и переформатирования их в броузере или ворде - читать с экрана не люблю, только по необходимости.

И вот сегодня меня осенило! Я читаю блоги в Абилоне, и одна из вертикальных колонок в его окне служит для отображения текста конкретного заголовка: разумеется, ширина этой колонки регулируется, таким образом обеспечивая удобный интерфейс для 1) моего обучения и 2) чтения новостей. Всё равно такую информацию я на бумаге не читаю...

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

24 февраля 2006

Миранда-броузер

Идея, описанная ниже, родилась в таких условиях: у меня постоянно невовремя кончалась предоплата за инет в нашей районной локалке, но через бесплатную местную проксю продолжала работать аська (ICQ); тогда я подумал, что чисто технически было бы интересно реализовать такую систему халявного (для меня) интернета:

  1. я пишу адрес (УРЛ) в броузере. В настройках броузера прокси-сервер прописан как локальный адрес, который на самом деле представляет собой плагин к миранде, благо она предоставляет интерфейс для плагинов. Таким образом адрес попадает в аську;
  2. плагин в миранде коннектится к серверу ICQ через бесплатный местный прокси-сервер и после этого остылает принятый УРЛ на определённый ICQ-номер (задаётся в настройках). Таким образом адрес попадает на другой номер;
  3. другой номер - это мой PHP-скрипт на удалённом серваке, который крутится в цикле и принимает входящие ICQ-сообщения (типа демон);
  4. получив адрес, PHP-скрипт делает читает весь контент по этому адресу и возвращает его обратно по цепочке в мой броузер.

Схема работыДумаю, схема будет работать даже для всяких имеющихся на странице картинок, скриптов и цсс-файлов.

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

Тырить инет - очень по-русски, но меня интересует только техническая сторона :]

23 февраля 2006

Punto Switcher для программиста

Оказывается, люди очень похожи - уже в который раз я слышу от разных товарищей то же, что раньше говорил сам: Punto Switcher (далее ПС) неудобен, потому что лезет исправлять то, что не нужно. Ну что ж, так и есть, если он работает в автоматическом режиме. Я подумал, что неплохо будет поделиться своим опытом, потому что ПС - удобный молоток для забивания сразу нескольких гвоздей.

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


С этого момента в ваших руках появляются следующие шоткаты, к которым привыкаешь достаточно быстро:

  • Break - конвертировать раскладку (если вы набрали "cfif", то это тут же по нажатии Break превратиться в "саша"). Отлично работает для только что набранного слова;
  • Shift+Break - конвертировать раскладку для всего выделенного куска текста.
  • Alt+Break - инверсия регистра (было "Саша", выделяем, нажимаем, становится "сАША");
  • Alt+ScrollLock - перевод в транслит: соответственно, только из русского в английский (было "Саша", выделяем, нажимаем, становится "Sasha"). Из-за включённого таким образом ScrollLock может неожиданно странно начать себя вести скроллинг с клавиатуры в Excel, но это поправимо - выключите его и всё (ScrollLock, не Excel :] ).
Это раз.

Есть такая удобная штука, как автозамена. Сие означает, что набор всех часто набираемых комбинаций текста можно упростить. Какой текст программиста чаще всего набирает? Вот мои примеры:
  • for($i=0; $i>sizeof($data); $i++)

  • while(list($key, $val) = each($attributes))

  • not_empty_array()

  • $attributes[""]

  • border: 1px solid blue;

  • document.getElementById('')

  • ну иногда ещё своё имя или фамилия в письмах

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

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

Хинт: не надо набивать в поле "Заменять что" нормальных слов типа "for" или "while" - это чревато тем, что вы будете материться, когда в письме напишете это слово, затем нажмёте естесственный пробел, а этот "глупый ПС" сделает замену! Лучше использовать "fo" и "whil".

Это два.

И теперь три: на закуску супер-мега тул. Называется "история буфера обмена", выглядит обычным поп-ап меню так:

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

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

Как настроить.

  1. Идём в Параметры -> Общий и включаем галку "Разрешить слежение за буфером обмена".
  2. Шагаем в Параметры -> Горячие клавиши, там выбираем пункт "Показать историю буфера обмена", и выставляем шоткат (у меня это англ. буква H + клавиша Win).

В ПС есть ещё несколько функций, но ими не пользуюсь. Если кто научит - спасибо скажу.

Всех с праздником!

22 февраля 2006

Быстрая проверка на ненулёвость

Когда нужно проверить несколько числовых значений, превышают ли все они нуль (важные значения, однако!), то можно их просто перемножить и проверить результат:


if($id_product * $id_boutique * $id_top > 0)


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


if($name_product . $name_boutique . $name_top != "")

21 февраля 2006

Ругаться нет сил

Полчаса дебажил, чтобы обнаружить, что я написал не "enabled", а "ebabled".

20 февраля 2006

JSMin

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

Как пускать. Внимательный читатель, думаю, заметил, что я написал "получает на вход код JavaScript" - сие означает, что надо передать параметром не имя файла, а его текст. В простейшем случае - запустить jsmin.exe и набить всё вручную в консоли. Но мы же украинские девелоперы! Да здравствует автоматизация!

Такой командой мы передаём этому выкусывателю текст входного файла, и указываем, куда сложить вывод:


jsmin.exe < in.js > out.js


В среднем файл сжимается на 40-50%.

Автор проги, похоже, читал увещевания Джоэля Спольски про поддержку юникода, так что доступные кодировки - ASCII и UTF-8.

Кстати, приятный work-around: таким образом можно "жать" все файлы с Си-подобным синтаксисом: не только JavaScript и Си, но и РНР, и даже CSS.

Скачать оригинальный JSMin.exe, JSMin, написанный на С,
на РНР или на Си-решётке

18 февраля 2006

Про вдохновение

Меня поражают люди, которые говорят что-то вроде "я делаю то-то тогда, когда появляется вдохновение", имея ввиду не досуг.

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

Получается, что ожидатели вдохновения просто никуда не торопятся, и их устраивает текущее состояние вещей, т.е. они топчутся на месте.

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

Конференция «Бизнес-Клуба»

Только что вернулся с однодневной мини-конференции «Бизнес-Клуб», проходившей на территории Севастопольского филиала МГУ. В целом, впечатления очень бурные и положительные.

На конференции слушателями были студенты и молодые специалисты, а докладчиками – «представители бизнес-элиты города», среди которых управляющие и руководители, профессоры СевНТУ, бизнес-консультатны, итп.

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

Плюсы:

  • Очень чёткая организация мероприятия. На КПП было двое молодых людей, проверивших нашу регистрацию в списках. Везде было много указателей, куда идти (и даже где туалет). За столиком регистрации мы получили раздаточный материал, включающий персональный график конфы с указанием времени и аудиторий. Организаторы не стеснялись прерывать докладчиков, если они не укладывались в отведённый им интервал времени. Всё было по расписанию, мы практически не выбивались из графика. На кофе-брейке и после подходили специальные люди и спрашивали, всё ли в порядке и есть ли у нас проблемы или чего-то не хватило. В конце было проведено анкетирование – впечатления, пожелания и прочее. Всё очень чётко и профессионально.
  • На кофе-брейк все, кто хотел, смогли возместить потраченные деньги едой :] Набор стандартный: кофе, чай, печенье, вафли, но в целом всё было мило.
  • Изначально у участников был выбор, какие выступления посетить: одна из двух линии по три лекции в каждой. Тут приходилось ориентироваться по названиям и информации о докладчиках.
  • Темы были в целом интересные, есть что послушать.
  • Девочек было больше, и процент симпатичных – не маленький.
  • Всё это стоило 7 (семь) гривен.

Минусы (их немного):

  • В расписании не было учтено время на смену аудитории, однако спешки и паники не было.
  • На кофе-брейке не с кем было поговорить или завести профессиональный контакт: всё студенты, молодёжж. И улыбчивые девочки :]
  • Домой пришлось идти в дождь :]

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


Затем были корки. Мельник В. М. пытался поведать о «вариантах построения бизнеса сегодня для молодежи». Докладчик он классный, умело работает с аудиторией, просто он сетевик :] Его план был быстро раскрыт: показать минусы обычного бизнеса, а затем на контрасте сыграть плюсы сетевого маркетинга. Как часто говорит Джоэль Спольски, «притянутый за уши пример доказывает всё что угодно», в итоге слушать было местами смешно. Я зачем-то начал было пытаться спорить, но мы не нашли общий язык, и я, похоже, нажил ещё одного недоброжелателя. Спор начался из-за тезиса «Нашему государству выгодно держать своих граждан бедными», с чем я, мягко говоря, не согласен. Ну да ладно, зачем мне было спорить по этому поводу, если докладчик, по его словам, добился огромного успеха в своём бизнесе. Это хорошо, пусть так и будет.


Потом был кофе-брейк. Без общения с новыми людьми он превратился просто в дозаправку. И ладно.


Зато третий докладчик нас просто окрылил. Эдгар Филиппович Костоломов умело, захватывающе и аргументировано распространялся на тему «Первые шаги в бизнесе и самоменеджмент». С первой минуты он завоевал моё уважение рассказами про Америку без тупого гона на неё, как это принято у нас; нет, всё профессионально, с плюсами и минусами. Кучи примеров из реальной жизни (он, по его словам, разрабатывал при СССР систему управления для «Апполона» и ещё много чем занимался в жизни), тонкий юмор, ощущение внутренней молодости, уверенность в силах нынешней молодёжи изменить мир и прочий позитив. Просто вау. Я захотел пойти к нему учиться.

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

P.S. Чуть не забыл. В конце представители AIESEC, которые были устроителями конфы, продемонстрировали свою корпоративную культуру: танцы под музыку барабанов, энергетичный речитатив и «кричалки». Очень впечатлило, ребята просто оторванные - с трудом верилось, что такое можно вытворять трезвым :] Молодцы, вот что такое «ощущение внутренней молодости»...

17 февраля 2006

Сон грядущий дай нам днесь

В продолжение начатой темы ранних утренних бдений. С самого первого дня я стал вести табличку времени отхода ко сну и подъёма. Затем я построил по этим данным график в Excel. Получилась такая картина:

График отходов ко сну и подъёмовВыводы:

  • судя по амплитуде графиков, пока у меня получается всё наоборот - ложусь я чётко в час-два ночи, а встаю как высплюсь;
  • большие горбы на графике подъёмов свидельствуют о силе привычки к аристократическому подъёму заполдень;
  • большие провалы на графике подъёмов свидельствуют о силе необходимых время от времени телодвижений вроде субботнего баскетбола в 9-10 часов утра или запланированных встреч с другими "ранними пташками".

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

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

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

16 февраля 2006

Типа AJAX

Сегодня с моим напарником Никитой разбирались, как динамически подгружать инфу на страницу без использования ифрейма. "Конечно, AJAX!" - привычно скажут продвинутые девелоперы и стройными рядами уйдут курить кеды. Фикус в том, что если в динамически подгруженный и отображённый на странице хтмл случайно затешется скрипт на яваскрипте, то он не выполнится. Просто отобразится хтмл и всё. Пример для неверующих:

<div id="output"></div>
<script>
  var name = 'output';
  var the_div = document.getElementById(name);
  if(null!=the_div)
    the_div.innerHTML = 'some HTML with JavaScripts here
<script>alert(666)<\/script> another one <script src=
"http://ya.ru"><\/script>
';
  else
    alert('cannot find the <div>');
</script>


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

Что делать? Стали копать исходники, как-то ж люди это делают. Пошли на уважаемый мной сайт dklab.ru и стали рыть пример имеющегося там движка типа AJAX'a. Что выяснили:

  1. создаётся контейнер (див или спан);
  2. в него помещается хтмл с одним яваскриптом, причём перед тегом <script> должен быть какой-то текст, а то у ИЕ с этим проблемы;
  3. далее берётся первый элемент массива скриптов этого контейнера (об этом ниже);
  4. обновляется его сурс (свойство "src"), что является отмашкой для запуска этого кода.


Общий вывод: хочешь запустить такой яваскрипт - обнови его сурс.

Посему поэтому приведённый выше код должен быть исправлен так:

<div id="output"></div>
<script>
  var name = 'output';
  var the_div = document.getElementById(name);
  if(null!=the_div)
  {
    the_div.innerHTML = 'some HTML with JavaScripts here
<script>alert(666)<\/script> another one <script src=
"http://ya.ru"><\/script>
';
    run_js(name);
  }
  else
    alert('cannot find the <div>');
</script>

Oбращаем внимание на выделенный кусок "run_js(name)". Это специальная функция, которая пробегает по массиву всех имеющихся в указанном контейнере скриптов, благо такая функциональность предоставляется броузером, и обновляет его сурс. Но тут одна загвоздка: если указать тегу script сурс, то автоматически перестаёт работать содержимое тега, просто не запускается. Например:

<script src="http://someserver.ua/1.js">
alert(4444);
</script>

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

Теперь вернёмся к проблеме - как обновить сурс так, чтобы:
случай А) если его нет, то он и не появился,
случай Б) если он есть, то изменился так, чтобы указывал на тот же самый файл.

Ответ:
случай А) обновить пустой строкой,
случай Б) приписать ничего не значащий УРЛ-параметр, например, "?1=2" или "&1=2" в зависимости от наличия других параметров.

Так или иначе, вот код многострадальной функции для запуска *статичного* JavaScript:

<script>
function run_js(name)
{
  var the_div = document.getElementById(name);
  if(null!=the_div)
  {
    with(the_div)
    {
      var arr_scripts =
the_div.getElementsByTagName("script");
      for(i in arr_scripts)
      {
        var src = arr_scripts[i].src;
        if(null!=src)
        {
          var has_question =
(src.indexOf('?', 1)>0 ? true : false );
          if(0==src.length)
            arr_scripts[i].setAttribute('src', '');
          else
            arr_scripts[i].setAttribute('src',
arr_scripts[i].src +
(has_question==true ? '&' : '?') + '1=1');
        }
      }
    }
  }
}
</script>

15 февраля 2006

Параметры-атрибуты в PHP

В XML-основанных языках (HTML, ColdFusion) есть замечательная вещь: передача параметров через атрибуты. Например:

<input type="checkbox" value="check" onclick="SelectAll"/>



Что мы тут видим? Какому-то объекту "input" на обработку передаются три параметра:

  • type
  • value
  • onclick

причём каждый из них имеет своё значение.

Что измениться, если мы эти атрибуты поменяем местами? Ничего. Это раз - передача параметров не привязана к их месту, в отличие от позиционных функций в РНР, где, например, в вызове функции explode() первым параметром идёт именно разделитель, вторым - именно входная строка.

Далее, у объекта "input" могут быть несколько дополнительных необязательных атрибутов, которых нет в вызове, но которые, будучи вызванными, заставят объёкт вести себя совершенно по-другому. Что c этим делать в РНР? Можно попробовать перегрузить методы, если есть класс, а можно всем входным параметрам функции задать дефолты, чтобы можно было вызывать функцию с любыми комбинациями параметров.

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

Допустим, по аналогии с вышеприведённым примером, в РНР у нас есть функция "input". Формируем параметр и передаём его функции:

<?
$arr = array();
$arr["type"] = "checkbox";
$arr["value"] = "check";
$arr["onclick"] = "SelectAll";

input($arr);
?>

?????????

Что делать, если РНР выводит русский текст из БД в виде знаков вопроса? Такой эффект чаще всего получается, если данные попали из одного приложения (phpMyAdmin), а выводятся в другом (ваша страница), и кодировки этих двух аппликух не совпали.

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

Для начала неплохо выяснить, в какой кодировке работает ваше приложение:


<?
$sql = "SHOW SESSION VARIABLES
LIKE 'character_set_connection'";
$res = mysql_query($sql);
print_r(mysql_result($res, 0, 1));
echo "<hr/>";


$sql = "SHOW SESSION VARIABLES
LIKE 'collation_connection'";
$res = mysql_query($sql);
print_r(mysql_result($res, 0, 1));
echo "<hr/>";
?>


Эти два нехитрых запроса выведут кодировку (character set) и сравнение (collation)
вашего соединения. У меня они чаще всего равняются "latin1" и "latin1_swedish_ci"
соответственно.

Теперь необходимо выяснить, в какой кодировке работает выше "входное" приложение. Это можно сделать либо выполнив приведённые запросы прямо в нём (если позволяет), либо посмотрев логи MySQL на предмет выполняемых запросов в момент инициализации "входного" приложения. Что касается phpMyAdmin, то он выполняет два приведенных ниже запроса, чтобы работать в кодировке utf8, которая рекомендуется для работы с национальными кодировками типа русской:


SET NAMES utf8

SET collation_connection = 'utf8_general_ci'


Таким образом, ваша задача состоит в запуске этих же запросов
в начале вашего приложения.

Если теперь знаки вопроса сменились абракадаброй, то это уже прогресс! Тогда нужно выставить кодировку страницы в броузере:


<head>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8"/>
</head>

Занимательное кэширование

Говорят, творчество применимо даже на кухне. Вот мой пример: всем известно, что при готовке толчонки (картофельного пюре) нужно слить воду, в которой варился картофель, перед этим отлив немного этого "бульона" в стакан для последующего добавления в пюре (для разжижения). Раньше я решал эту задачу так (пюре у нас готовлю я):
  1. Отлить запас бульона в стакан на столе (запас)
  2. Остальное вылить в раковину
Потом я подумал, что даже эту нехитрую задачу можно спрямить:
  1. Вылить всё в стакан, стоящий в раковине.

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

12 февраля 2006

Расслабление для мужчин

Rambo First Blood
Сегодня в первый раз стрелял из автомата. Ощущения - мощные! Грохот, отдача, запах оружейного масла, вылетающие гильзы... А попадания, попадания!!

Где-то читал, что стрельба в цель из огнестрельного оружия является прекрасным анти-депрессантом. Это правда: временная глухота притупляет раздражители внешнего мира :]

11 февраля 2006

Множества в PHP

В Паскале есть замечательная штука - множество (set). Благодаря ему этот вариант:
IF ((type=1) OR (type=3) OR (type=5) OR (type=11))
THEN ...

может быть успешно заменён на:
IF type IN [1,3,5,11]
THEN ...

До недавнего времени я считал, что в РНР такое невозможно в принципе. Однако по небольшому раздумию я пришёл к такому коду, который не уступает паскалевскому:
if(in_array($type, array(1,3,5,11))) ...

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

10 февраля 2006

Гы…

Сегодня была моя третья самостоятельная тренировка игры на барабане, т.е. я пришёл в незнакомую студию, оплатил время аренды барабанной установки и давай шпилить. (Надоело барабанить пальцами по клаве, так что я решил поставить это на профессиональные рельсы) Сегодняшняя тренировка знаменательна тем, что плавно превратилась в репитицию: «заряжале» Юре стало холодно и скучно сидеть без дела, и он стал ненавязчиво подыгрывать мне на синтезаторе. Позже подключился его товарищ с бас-гитарой. Это было что-то :]

Мои ощущения коллективного музицирования:
  • первые мгновения – захватывающий дыхание восторг «У меня получается!»
  • на второй минуте наступает паника «Щас собъюсь, щас собъюсь!»
  • если сосредоточиться и совладать с паникой, то появляется лёгкий автоматизм, и начинаешь наблюдать за собой как бы со стороны.
  • через минут 15 начинаешь понимать, какие проф. болезни убивают барабанщиков: деревенеющие запястья при неправильной технике удара в холодном помещении и боль в позвоночнике при непрямой посадке.

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

Один из планов на этот год у меня значится поиграть в группе. Скоро-скоро…

Новая игра

Я придумал новую игру, которой можно весело коротать время, скажем, на привале в походе. Должна происходить весело, потому что вовсю напрягает память игроков.
В общем, слушал я песню Black Eyed Peas "Bebot". А там один из певцов постоянно толдычит "Фили-липино", и эта фраза надолго стала моей песней-заразой. Тогда я начал её склонять на разные лады, что, в принципе, могут делать несколько участников:

  • няня Арина
  • мой муж скотина
  • пришла Марина
  • красна калина
  • вот бригантина
  • висит картина
  • и Буратино
  • дала Мальвина
  • и ещё вариантов 20.


Фразы произносятся в тон "Фили-липино" из песни, как будто поёшь рэп.

Проигравших нет, кроме отказавшихся дятлов :]

09 февраля 2006

Математика

Простой пример. Есть табличка с данными. Пользователь клацает по ссылке в заголовке столбца, и данные сортируются по этому столбцу. Щёлкает ещё раз по тому же столбцу, и режим сортировки меняется с убывания на возрастание.
Я завёл отдельную переменную под режим сортировки, пусть будет $order.
Задачи:
1. Преобразовать пользовательский ввод (wild input) так, чтобы переменная $order принимала только значения 0 или 1.
2. При выводе заголовка столбца значение $order инвертировать для смены режима сортировки.
От использования if я отказался, потому что, допустим, появится ещё один режим, и придётся в первой задаче дописывать целую ветку.
Поэтому я использовал такие тривиальные математические выкладки:
1. Какая функция даёт в результате только 0 или 1? Правильно, нахождение остаток от деления на 2, а чтобы убрать отрицательные значения, возьмём ещё и модуль:
$order = abs($order % 2);

Т.е. в общем случае надо брать остаток от max+1 (соответстенно, в случае расчёта рейтингов в диапазоне 0-10 я находил остаток от 11).
2. Где-то я слышал про такой метод, работает отлично для инверсии: складываем оба варианта и каждый раз вычитаем входной вариант из общей суммы:
echo (1-$order);
Единица здесь получилась из мега-рассчёта 1 = (0+1), т.е. оба варианта в сумме.

08 февраля 2006

Хитрый SQL

Я заметил, что частенько работа с БД состоит в такой последовательности шагов:
1. Проверить наличие определённых данных в PRIMARY KEY-поле таблицы (или просто уникальном) через SELECT.
2. Если есть такая запись, то UPDATE этой строки.
3. Если нет такой записи, то INSERT.

Как ни крути, получается как минимум 2 запроса и условие на стороне сервера приложений. А мне бы хотелось перенести часть работы на сервер БД.

Сначала я начал копать оператор IF, но он, насколько я разобрался, работает внутри SELECT, и так не получится, разве что писать хранимую процедуру, что но до недавнего времени это было невозможно в MySQL.

Поэтому усиленное копание мануала привело к такой замечательной структуре, о которой, вероятно, мало кто слышал. Она построена на базе связки INSERT INTO - SELECT FROM, только оказалось, что в неё можно встроить и UPDATE. Выглядит так:

INSERT INTO table1 (name, role)
SELECT FROM table2 name, role
ON DUPLICATE KEY UPDATE name='Vasya', role=DEFAULT


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

07 февраля 2006

База знаний

Помнится, ещё в универе на лабах по Лиспу старший преподаватель Крицкий обратил моё внимание, что Лисп, как интерпретируемый язык, может генерить не только данные, но и код: можно написать Лисп-программу, которая выведет в файл другую Лисп-программу, которую в свою очередь тоже можно запустить, поскольку это всё есть простые текстовые файлы. То же касается и других интерпретируемых языков: РНР, JavaScript и иже с ними. Никак не могу понять, к какому замочку этот ключ.

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

Пока придумал только что-то вроде плагинов в случае РНР или макросов в случае JS (наподобие МС Офис, только для веба).

Трудно придумать что-то реально новое, но так увлекательно :]

06 февраля 2006

MySQL поля типа ENUM

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

ЗАЧЕМ: бессмысленно делать таблицу-справочник и бесчётное число раз джойнить её с другими таблицами, если данные практически не меняются. Также енум-поле - хорошая замена хранению где-то всего списка вариантов в виде строки с последующим её распарсиванием при каждом запуске программы. БД всё это сделает за вас. Так или иначе, БД хранит где-то в своих недрах список вариантов, а в каждой вашей записи добавляется не сам элемент списка, а его порядковый номер (начиная с единицы), что есть экономия памяти, потому как элементы списка могут быть весьма длинны. Получить значение такого поля можно как в виде элемента списка, так и в виде его порядкового номера.

КАК: если вы не являетесь счастливым обладателем СУБД вроде PHPMyAdmin, то вручную добавить енум-поле можно таким запросом:

ALTER TABLE `users` ADD `role`
ENUM('guest', 'boutique', 'admin')
DEFAULT 'guest' NOT NULL;


Если вам надо получить значение этого поля, то делается это просто:


SELECT `role` FROM `users`;


Этот запрос вернёт роли пользователей в вашей
таблице в текстовом виде. Если вам надо в цифровом (порядковые номера), то можно использовать неявное приведение типа:


SELECT `role`+0 FROM `users`;


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

<?
function get_enum_field_values($table, $field)
{
$values = array();
$sql="SHOW COLUMNS FROM `".$table.
"` LIKE '".trim($field)."'";
$res = mysql_query($sql);
if (mysql_num_rows($res))
{
$values_t = mysql_result($res, 0, 1);

//удалить "enum(" в начале
$values_t = explode("(", $values_t);

//удалить ")" в конце
$values_t = explode(")", $values_t[1]);

//список в массив
$values = explode(",",
strtolower(trim($values_t[0])));

//удалить ковычки во всех элементах массива
array_walk($values, "del_quotes");
}
return $values;
}

//удаляет все ковычки в строке
function del_quotes(&$item1)
{
$item1=str_replace("'", "", $item1);
}

//пример использования
function get_users_roles()
{
return get_enum_field_values("users", "role");
}
?>

РНР: способы получить содержимое файла

Допустим, к нам в скрипт GET-запросом пришла переменная URL, содержащая имя файла, который нужно прочитать.

1. Заметил, что многие делают так:
<?
ob_start();
$arrContent = file($_GET['url']);
foreach ($arrContent as $val)
echo $val;
$fullContent = ob_get_contents();
ob_end_clean();
?>
То есть по сути, file() во второй строке возвращает содержимое файла в виде массива его строк, а всё остальное требуется для перегона этого массива в одну длинную строку. Способ, на мой взгляд, не самый элегантный (хотя что-то подобное в комментах на php.net рекомендовалось использовать для получения объёма памяти в байтах, требуемого для хранения массива, т.е. буферизовать вывод print_r($massiv) и посчитать длину получившейся строки).

2. Можно продолжать брать содержимое массива через file(), т.к. это довольно быстрый и удобный способ, но перегонять его в строку через implode():
<?
$fullContent = implode("", file($_GET['url']));
?>
3. Самое простое - использовать специальную функцию file_get_contents():
<?
$fullContent = file_get_contents($_GET['url']);
?>
Правда, в этом случае РНР, кажется, должен открывать ссылки как файлы (см. php.ini). Я это проверяю так:
<?
$can = (function_exists("ini_get") &&
ini_get("allow_url_fopen") ? true : false);
?>

04 февраля 2006

Классные статьи про программинг

Одни отличные русские ребята перевели много статей другого классного английского парня Joel Spolsky, который работал программистом и управляющим в Microsoft и др., на великий-могучий. Мне очень нравится его/их доходчивый стиль, образные примеры и реальность приводимых фактов.

Как я становлюсь ранней пташкой

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

Одна из таких задач у меня: научиться вставать в 8 утра в целях развития cамодисциплины.

В своих экспериментах я использовал статьи С.Павлины (почти Павленко) «How to become an early riser» иВ.Колесника «Зимний распорядок дня по Аюрведе». Должен признать, сегодня я пришёл к выводу, что оба авторы по-своему правы.

Итак, я решил ложиться спать, когда глаза начнут слипаться, а вставать в 8 утра. В первый раз (2 ночи назад) я, хотя так и сделал, но провалялся без сна с 23:30 до около 2:00. Тут я вспомнил, что «В половину двенадцатого начинается активная фаза следующего периода Питты, поэтому до половины второго заснуть уже вряд ли получится» (В.Колесник). Блин. Сделал вывод, что глаза должны начать слипаться до 23:30.

Так и получилось вчера вечером: я забил на компьютер около 11 вечера, почувствовав лёгкую сонливость. Лёг спать, быстро уснул. Но проснулся не в 8 (био-будильник у меня не идеален, но обычно не врёт), а в 6:58. Причём проснулся лёгким и свежим. «Фигня», подумал я, перевернулся на другой бок и… пролежал так до 7:30, тщетно пытаясь заснуть,чтобы не потерять драгоценные минуты бодрящего сна, то есть выспался я уже. И всё-таки я уснул. Разбудил меня подстраховочный будильник в мобилке, заведённый с форой в полчаса — на 8:30. Я был тяжёл и вял, вставать не хотелось совсем, и единственная мысль в моём потревоженном и словно воспалённом мозгу — «Какого хера?!». Аюрведа снова права — вставать надо было раньше, поезд ушёл.

Сегодня лягу, когда устану, а вставать буду в 7.

P.S. И всё же раньше 8 вставать не получается.

Выжить без JavaScript: ссылка vs кнопка

Недавно увидел на одном сайте такой код:

<input type="button" name="name"
value="Go somewhere"
onclick="javascript:
document.location.href=
'www.somewhere.org.ia'"/>

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

Вопрос: зачем писать отдельный код, чтобы сделать то, для чего есть простое, готовое и подходящее решение?!

Минусы этого подхода очевидны:

  • выключенный в целях безопасности JavaScript не даст пользователю перейти по ссылке. Конечно, нашего человека это не остановит, и он легко выкусит УРЛ из исходника, но кому это надо?
  • как-то я решил потестить известные сайты в броузере на мобиле... Оказалось, что там (пока) вообще нет такого понятия, как JavaScript.
  • чисто технически писать onclick="javascript:..." бессмысленно, потому что внутри обработчика интерпретатор УЖЕ ждёт JavaScript-код.

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

Далее. Бывает, что формы субмитят так:

<form>
<input />
<input />
<a href="javascript: document.forms[0].submit();
return false;">Submit data</a>
</form>


Минусы те же, за исключение того факта, что в данном случае
href="javascript:..." оправдано, т.к. броузер ждёт здесь ссылку, а не код.


Вывод: и тут можно обойтись без программирования, просто вспомнив о ВСТРОЕННОМ типе инпутов - <input type="submit"/>.

03 февраля 2006

Булева логика на службе программиста 2

Теперь пример поинтереснее. Буду объяснять подробно.

Допустим, имеем такой код (РНР):

<?
if($enabled)
{
if($has_property)
do_it();
}
else
do_it();
?>

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

Что можно сделать? Допустим, переменная $enabled есть предикат А, а переменная $has_property есть предикат В (Что такое предикат? Это утверждение, которое либо ложно, либо истинно, например, "жена беременна"). Теперь нарисуем карту Карно, помним такую?

A
¦-----¦-----¦
¦ ¦ ¦
¦-----¦-----¦
¦ ¦ ¦B
¦-----¦-----¦

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

Итак, ячейка 1 (первый столбец, первая строка). В ней А истинно (А стоит прямо над ней), а В - нет (В стоит во второй строке). Что же даёт наша задача, если А=$enabled=да и И=$has_property=нет? Ничего: программа успешно пройдет первую строку [if($enabled)], и пропустит вторую [if($has_property)]. Значит, ставим в первую ячейку нашей таблицы 0.

Ячейка 2 (второй столбец первой строки). Здесь А=нет и В=нет. Как видно по программе, если А=нет, то запускается else, и do_it() выполнится, значит, ставим единичку.

Ячейка 3 (первый столбец, вторая строка). А=да, В=да. В итоге будет единица, так как программа пройдёт оба if и запустит do_it().

Ячейка 4 (второй стоблец, вторая строка). Здесь опять А=нет, а В=да.Как снова видно по программе, если А=нет, то запускается else, значит,ещё одна единичка.

В итоге получаем такую картину:
   A
¦-----¦-----¦
¦ 0 ¦ 1 ¦
¦-----¦-----¦
¦ 1 ¦ 1 ¦B
¦-----¦-----¦

Вот теперь начинается оптимизация. Это называется склеивание: мы в уме группируем в команду по две сначала обе нижние единицы, потом две правые. Таким образом, получаем две группы единиц, между которыми нужно поставить знак плюс (дизъюнкция, она же OR, она же ¦¦ ) - таковы правила (не было бы единицы в позиции {2,2} - нечего бы было группировать). То есть первая группа единиц - это две единички во второй строке: они обе сработают, когда В=да (потому что стоят в строке, где В=да). Вторая группа единиц стоит во втором столбце, там А=нет. Получается, что первая группа = В, вторая = !А (А=нет, "не А"или "А есть ложь").

Таким образом, наше выражение записывается так: (!А OR В).

Делая обратную расшифровку, получаем:

<?
if(!$enabled ¦¦ $has_property)
do_it();
?>

Нравится? Мне очень. Да, сперва не слишком очевидное выражение для последующих правщиков, но зато это можно восполнить комментированием рядом с условием.

Желаю удачи в разборе полётов 3 переменных!

Грамотное резюме

Недавно я подумал, что мою публикацию в журнале Хакер про баги ColdFusion вполне можно вставить в одноимённый раздел в своём резюме для солидности, раз уж рекомендаций нет. А потом я наткнулся на автоматическую генерилку научных текстов, причём со списком литературы, графиками, на английском - всё как полагается. Сначала даже предлагают ввести ваше ФИО, и оно многозначительно стоит в подзаголовке этой статьи. Я даже хотел вставить ссылку сгенерённой страницы в резюме ради прикола, пока жена не заметила наличие на странице ссылки "Generate another one"... Вот бы был прикол :]
А вот хорошая статья про хорошие резюме.

02 февраля 2006

Хитрый механизм создания РНР-переменных

В догонку к "Выделенным чекбоксам": как оказалось, если назвать инпут так:
<input type="text" name="f[gzipped]"/>

то после субмита, разумеется, появляется переменная-массив $f, у которого элемент с индексом "gzipped" равен "что-там-пользователь-ввёл".

Вообще добрые люди подсказали отличный ман по таким трюкам: http://www.php.net/manual/ru/faq.html.php#faq.html.arrays

Булева логика на службе программиста

Я как программист напоминаю сам себе Буратино с Золотым Ключиком, который не знает, что с ним делать: нас всех учили в универе чему-нибудь и чем-нибудь, но что теперь делать со всеми этими"ключами", мало кто понимает, и я к ним чаще всего не отношусь.


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


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


Сначала простой пример: вам нужно пробежаться по массиву в цикле и, помимо прочих действий, узнать, была ли хоть в одном подэлементе единица. Используем правила A OR 0 = A, А OR 1 = 1.



<?

$A = false;

for($i=0; $i<sizeof($data); $i++)

{

...

$A = $A ¦¦ $data[$i]["field"];

/* либо */

$A ¦= $data[$i]["field"];

}

?>

Зарядка для мобилки

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

РНР-конференция в Софии

На php.net пишут, что дескать РНР-конференция пройдёт в Софии (Болгария) этим летом.

Съездить, что ли? Входной билет $40.

Надо узнать про цены на дорогу и визовую ситуацию.

Выжить без JavaScript: выделенные чекбоксы

Была у меня такая стандартная задача.

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

Решение 1: JavaScript.

В обработчик события onclick кнопки "Удалить выделенные" пишем функцию на JavaScript, которая а) обходит все чекбоксы и собирает инфу об их выделенности, б) делает из значений выделенных чекбоксов список (просто строку с их перечислением через запятую), в) сохраняет эту строку в hidden-поле. Получается примерно так:

<input type="submit" name="delete"
value="Удалить выделенные"
onclick="document.getElementById
('selected').value=checkedValues('FormName');"/>

<input type="hidden" name="selected"
id="selected" value="" />

Здесь checkedValues() - эта та самая рукотворная функция на JavaScript.

На серверной стороне (а там стоит РНР) преобразуем список в массив:

<?
$selected = explode(",", $selected);
?>

Что мне не нравится в этом решении - нестандартность. Скажем, нет у броузера включённого JavaScript или библиотека с функциями недозагрузилась/пропала/неподключена - и всё, попа, функциональность не работает. Поэтому появилось

решение 2: РНР + HTML.

Матчасть: в результате этого кода будет а) создан переменная типа массив, б) следующему её элементу (в нашем случае первому, так как массив пустой) будет присвоено значение 4.

<?
$arr_example = array();
$arr_example[] = 4;
?>

То бишь "[]" означает "возьми следующий элемент". Данный простой код часто усложняют таким образом (получается то же, только грязнее):


<?
$arr_example = array();
$arr_example[sizeof($arr_example)] = 4;
?>

Так вот вернёмся к теме. Можно смело удалить обработчик onclick - за него всё сделает РНР и хтмл: будем использовать хитрое наименование переменных. Дело в том, что если назвать все хтмл-чекбоксы так:


<input type="checkbox"
name="selected[]" value="<?=$id?>"/>

то после субмита формы на стороне сервера появится переменная $selected типа "массив", проиндексированная последовательно (т.е. можно по ней пройтись циклом FOR, а не FOREACH, что есть быстрее). Более того, любимая explode() тут уже не понадобится. Ура!

Словесные игры

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

Например:
  • Микеле Плачидо + Плачидо Доминго = Микеле Плачидо Доминго
  • Спящий режим + режим Бога = Спящий режим Бога

Всем привет!

Начинаем нашу прогамму телепередач :]