Рефакторинг: разбиваем смешанные типы проверок

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

Как расшифровать текст с кракозябрами вместо русских букв

Время от времени приходится сталкиваться с текстом в непонятной кодировке, типа такого:

Ëþäè â Ãðîóâëåíäå, ìàëåíüêîì (ïî ìåðêàì Êàëèôîðíèè) ãîðîäêå â øåñòüñîò æèòåëåé, âûõîäèëè íà óëèöû, ñòîÿëè ïåðåä ñâîèìè äîìàìè ñ öâåòàìè íà ïîäîêîííèêàõ è ñìîòðåëè, êàê ýòîò ïèðîêóìóëþñ âûðàñòàåò âûøå Ñüåððû-Íåâàäû. ß è ñàìà ñòîÿëà òàì â áëàãîãîâåíèè è óæàñå è ïîíèìàëà áåç âñÿêèõ ñëîâ, ÷òî åñëè íå ïîéäåò äîæäü, òî ñëåäóþùèå ïîæàðû áóäóò åù¸ óæàñíåå, à åñëè äîæäè âñ¸ æå ïîéäóò è îêàæóòñÿ ñëèøêîì îáèëüíûìè, òî ýòî ñîææ¸ííûå ãîðíûå ñêëîíû ñìîåò íàâîäíåíèÿìè. Âñ¸ áûëî áóêâàëüíî íà ãðàíè êàòàñòðîôû. Íî ðÿäîì áûëè öâåòû â ãîðøêàõ, è íåîáîææ¸ííûå ñîñíû, è òðóùèåñÿ î íîãè ñîáàêè, è ðåñòîðàí, îòêðûòûé äëÿ óæèíà; è ÷óâñòâîâàëîñü, ÷òî âñå íà óëèöå âçäûõàþò ñ áëàãîäàðíîñòüþ çà òî, ÷òî âñ¸ ýòî ó íèõ åù¸ åñòü. Õîòÿ áû íåíàäîëãî (Äèàíà Ìàðêóì, Äåñÿòûé îñòðîâ).

Если текст маленький, то поможет онлайн-декодер, а если текст большой — поможет текстовый редактор. Я использую Sublime Text, подозреваю что этот трюк возможен и в других текстовых редакторах, которые умеют работать с разными кодировками.

В любом случае, начните с онлайн-декодера, чтобы понять с какими кодировками нужно работать. Раскодируйте фрагмент текста и запомните направление декодирования (в нашем случае: ISO-8859-1 → Windows-1251):

Исправление кодировки (шаг 0)

Вся дальнейшая работа будет проходить в текстовом редакторе.

Шаг 1. Создайте обычный пустой текстовый файл, сохраните его с кодировкой Western (ISO 8859-1):

Исправление кодировки (шаг 1)

Шаг 2. Скопируйте текст с кракозябрами и вставьте его в созданный файл:

Исправление кодировки (шаг 2)

Шаг 3. Сохраните файл (Ctrl + S).

Шаг 4. Откройте файл с кодировкой Cyrillic (Windows 1251):

Исправление кодировки (шаг 4)

Шаг 5. Наслаждайтесь результатом:

Исправление кодировки (шаг 5)

Тот же текст в читаемом виде.

MongoDB: The ‘cursor’ option is required

При использовании агрегации через устаревший драйвер MongoDB без указания опций возникает ошибка

Throwable; type: [MongoResultException]; code: [9]; message: [localhost:27017: The ‘cursor’ option is required, except for aggregate with the explain argument]; err level: [1 (E_ERROR)]

Внятного описания в документации, что именно должно быть передано в параметре cursor, нет. Так как драйвер ждёт объект, избавиться от ошибки можно, передав в параметрах метода aggregate() в опции cursor пустой объект:

$pipeline = array(array(
    '$group' => array(
        '_id' => null,
        'min' => array('$min' => '$update_time'),
        'max' => array('$max' => '$update_time'),
    ),
));

$options = array('cursor' => new stdClass());

$res = $MongoCollection->aggregate($pipeline, $options);

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

MongoDB: как разделить поле _id после группировки

Группировку данных в MongoDB можно делать как по одному полю, так и по нескольким, превращая их во вложенный документ:

db.my_collection.aggregate([
    {$group: {
        _id: {surname: "$surname", name: "$name"}
    }},
]);

Этот запрос вернёт такой результат:

{_id: {name: "Аттикус", surname: "Финч"}}
{_id: {name: "Индиана", surname: "Джонс"}}
{_id: {name: "Джеймс", surname: "Бонд"}}
{_id: {name: "Рик", surname: "Блейн"}}
{_id: {name: "Уилл", surname: "Кейн"}}

А вложенные документы поднять на уровень выше, нужна проекция:

db.my_collection.aggregate([
    {$group: {
        _id: {surname: "$surname", name: "$name"}
    }},
    {$project: {_id: 0, surname: "$_id.surname", name: "$_id.name"}},
    {$sort: {surname: 1, name: 1}}
]);

Этот запрос вернёт данные в таком виде:

{name: "Рик", surname: "Блейн"}
{name: "Джеймс", surname: "Бонд"}
{name: "Индиана", surname: "Джонс"}
{name: "Уилл", surname: "Кейн"}
{name: "Аттикус", surname: "Финч"}

PHP: Detected an illegal character in input string

При преобразовании строки в требуемую кодировку к кодировке, требуемой на выходе можно добавить параметр TRANSLIT или IGNORE. Первый включает режим транслитерации и заменяет проблемный символ на один или несколько наиболее близких по внешнему виду. Ключевое слово — заменяет. Возможно, наиболее близким символом будет знак вопроса, но тем не менее. Второй параметр удаляет те символы, которые не могут быть представлены в требуемой кодировке.

В комментариях к документации один из пользователей упомянул про символ, который не получается проигнорировать — на нём даже с параметром IGNORE iconv падает с ошибкой «Detected an illegal character in input string». И поэтому появилась рекомендация использовать одновременно два параметра: //TRANSLIT//IGNORE — типа, игнорировать всё, что не удалось транслитерировать. Однако, повторюсь, параметр TRANSLIT заменяет все проблемные символы, после него уже нечего игнорировать. В итоге получаем такую картину:

// работает
iconv('utf-8', 'windows-1251//TRANSLIT', '∙'); 
iconv('utf-8', 'windows-1251//TRANSLIT//IGNORE', '∙');

// падает с ошибкой Detected an illegal character in input string
iconv('utf-8', 'windows-1251//IGNORE', '∙'); 
iconv('utf-8', 'windows-1251//IGNORE//TRANSLIT', '∙');

В итоге имеем бессмысленные комбинации //TRANSLIT//IGNORE и //IGNORE//TRANSLIT, которые встречаются в ответах на SO.

Python: проверка на прерывание цикла

В Python у циклов while и for есть опциональный блок else, который проверяет, выполнился ли цикл полностью. Если ключевое слово break не было вызвано, будет выполнен блок else.

while… else:

numbers = [1, 3, 5]

position = 0

while position < len(numbers):
    number = numbers[position]
    if number % 2 == 0:
        print('Найдено четное число', number)
        break
    position += 1
else:
    print('Четное число не найдено')

for… else:

numbers = [1, 3, 5]

for number in numbers:
    if number % 2 == 0:
        print('Найдено четное число', number)
        break
else:
    print('Четное число не найдено')

Подобная конструкция позволяет выполнить некоторое действие при полном завершении работы с циклом (вывод сообщения здесь — явный флаг штатного завершения обхода цикла):

for name in ['Алиса', 'Боб']:
    print(name)
else:
    print('Обход цикла завершен')

Предел прочности

Прекрасное о Дамбе Баньцяо в Википедии:

Дамба была сконструирована таким образом, чтобы пережить крупнейшие наводнения, которые случаются раз в тысячу лет (306 мм осадков за день). Однако в августе 1975 года произошло крупнейшее за 2000 лет наводнение…

PHP: Проверка строк, содержащих числа

Если на входе есть строка, которая может содержать целое число или число с плавающей точкой и нужно эту строку привести к нужному состоянию, то можно сделать так:

if (is_numeric($string)) {
    if ((int)$string == $string) {
        $string = (int)$string;
    } else {
        $string = (float)$string;
    }
}

Функция is_numeric проверяет, является ли переменная числом или строкой, содержащей число (ни is_int, ни is_float не делают вторую часть проверки и при получении строки на входе возвращают false). Далее приводим строку к целому числу и делаем нестрогое сравнение, чтобы убедиться, что перед нами действительно целое число.

Стили написания составных слов

  • kebab-case (lisp-case) — шашлычный-регистр, позвоночный-регистр
  • snake_case — змеиный_регистр
  • SCREAMING_SNAKE_CASE
  • Train-Case
  • CamelCase (UpperCamelCase, PascalCase) — ВерблюжийРегистр, ГорбатыйРегистр, СтильВерблюда
  • lowerCamelCase