{
    "version": "https:\/\/jsonfeed.org\/version\/1",
    "title": "Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: solver",
    "home_page_url": "http:\/\/test.leftjoin.ru\/tags\/solver\/",
    "feed_url": "http:\/\/test.leftjoin.ru\/tags\/solver\/json\/",
    "icon": "http:\/\/test.leftjoin.ru\/user\/userpic@2x.jpg",
    "author": {
        "name": "Николай Валиотти",
        "url": "http:\/\/test.leftjoin.ru\/",
        "avatar": "http:\/\/test.leftjoin.ru\/user\/userpic@2x.jpg"
    },
    "items": [
        {
            "id": "129",
            "url": "http:\/\/test.leftjoin.ru\/all\/how-to-solve-wordle\/",
            "title": "Решение головоломок Wordle с помощью Basic Python",
            "content_html": "<p class=\"note\">Перевод статьи <a href=\"https:\/\/www.inspiredpython.com\/article\/solving-wordle-puzzles-with-basic-python\">“Solving Wordle Puzzles with Basic Python”<\/a> автора <a href=\"https:\/\/www.inspiredpython.com\/author\/mickey-petersen\">Mickey Petersen<\/a><\/p>\n<p><img src=\"http:\/\/test.leftjoin.ru\/pictures\/2022-01-24-18.26.50.jpg\" border=\"0\" width=\"100%\" height=\"100%\"><\/a><\/p>\n<p>Вы наверняка слышали о Wordle? Это словесная головоломка не так проста, как кажется на первый взгляд. Вас просят угадать английское «слово дня», которое состоит из пяти букв. Если вы ошибетесь, вам дадут несколько подсказок: буква в слове будет <i>зеленой<\/i>, если вы правильно угадали нужную букву в нужном месте; <i>желтой<\/i>, если эта буква присутствует в слове, но не на этом месте; и серой, если буквы вообще нет в слове.<\/p>\n<p>На самом деле, решать эту головоломку довольно сложно! Вот как вы можете написать Wordle Solver на Python, с использованием множеств, представления списков (list comprehension) и капелькой удачи!<\/p>\n<h2>Суть головоломки<\/h2>\n<p>Каждый день Wordle генерирует новое слово, которое нужно угадать. Поскольку у нас есть только шесть попыток — сайт использует файлы cookie для отслеживания вашего прогресса — попытки нужно использовать аккуратно!<\/p>\n<p>На первый взгляд, есть несколько подсказок, которые упрощают решение:<\/p>\n<ol start=\"1\">\n<li>Слово состоит ровно из пяти букв.<\/li>\n<\/ol>\n<ol start=\"2\">\n<li>Слово из английского языка и использовать можно только алфавит — никаких знаков препинания, цифр или других символов.<\/li>\n<\/ol>\n<ol start=\"3\">\n<li>Любая попытка дает подсказки:<\/li>\n<\/ol>\n<ul>\n<li>Зеленая буква, если буква и её место в слове правильные.<\/li>\n<li>Желтая буква, если буква присутствует в слове, но было выбрано не то место.<\/li>\n<li>Серая буква, если буквы вообще нет в слове.<\/li>\n<\/ul>\n<ol start=\"4\">\n<li>Существует конечное число слов, и их количество дополнительно ограничено словарем, используемым Wordle.<\/li>\n<\/ol>\n<p>Поскольку я не хочу пытаться извлечь тот же словарь, что использует Wordle (это слишком просто), вместо этого я буду использовать свободно доступный словарь, который устанавливается через Linux в директорию <b>\/usr\/share\/dict\/american-english<\/b>. Словарь — это текстовый файл с одним словом в каждой строке.<\/p>\n<h2>Загрузка и генерация слов<\/h2>\n<p>Для начала нам понадобится словарь — вы можете установить любой удобный вам или использовать уже установленный, если такой есть.<\/p>\n<p>Далее нам нужно закодировать правила игры:<\/p>\n<pre class=\"e2-text-code\"><code>import string\r\n\r\nDICT = &quot;\/usr\/share\/dict\/american-english&quot;\r\n\r\nALLOWABLE_CHARACTERS = set(string.ascii_letters)\r\nALLOWED_ATTEMPTS = 6\r\nWORD_LENGTH = 5<\/code><\/pre><p>У нас есть всего шесть попыток; длина слова равна пяти, и мы можем использовать все доступные буквы английского алфавита.<\/p>\n<p>Я преобразовываю допустимые символы в Python set(), чтобы использовать функции, которые доступны для работы с множествами, для проверки наличия букв в слове — но об этом чуть позже.<\/p>\n<p>Теперь я могу сгенерировать множество тех слов, которые соответствуют правилам:<\/p>\n<pre class=\"e2-text-code\"><code>from pathlib import Path\r\n\r\nWORDS = {\r\n  word.lower()\r\n  for word in Path(DICT).read_text().splitlines()\r\n  if len(word) == WORD_LENGTH and set(word) &lt; ALLOWABLE_CHARACTERS\r\n}<\/code><\/pre><p>Здесь я использую представление списков (list comprehension) для создания множества допустимых слов. Я использую отличный класс Path для чтения непосредственно из файла. Если вы еще не знакомы с Path, <a href=\"https:\/\/www.inspiredpython.com\/article\/common-path-patterns\">я рекомендую вам узнать о нем<\/a>, поскольку это очень удобный инструмент.<\/p>\n<p>Так, я фильтрую слова из словаря, которые имеют правильную длину и в которых набор символов в слове является подмножеством ALLOWABLE_CHARACTERS. Другими словами, выбираются только слова, которые составлены из набора допустимых символов.<\/p>\n<h2>Алфавитно-частотный анализ на английского языка<\/h2>\n<p>Особенность английского языка заключается в неравномерном распределении букв, используемых в словах. Например, буква <b>E<\/b> используется гораздо чаще, чем <b>X<\/b>. Поэтому, если мы сможем генерировать слова с наиболее распространенными буквами, у нас больше шансов угадать некоторые или даже все буквы в слове. Таким образом, выигрышная стратегия состоит в том, чтобы придумать систему, которая вычислит наиболее популярные буквы английского языка.<\/p>\n<p>К счастью, у нас есть словарь английских слов!<\/p>\n<pre class=\"e2-text-code\"><code>from collections import Counter\r\nfrom itertools import chain\r\n\r\nLETTER_COUNTER = Counter(chain.from_iterable(WORDS))<\/code><\/pre><p>Класс <i>Counter<\/i> — полезное изобретение! Это модифицированный словарь, который считает количество повторений каждого элемента. Когда вы передаете ему список элементов, они становятся ключами, а затем он сохраняет количество появлений каждого ключа в его значение. Это как раз то, что нам нужно, чтобы посчитать популярность каждой буквы среди всех английских слов из 5 букв.<\/p>\n<p>Для этого я использую малоизвестную функцию chain из модуля <i>itertools<\/i>. Эта функция имеет один скрытый метод <i>from_iterable<\/i>, который берет один элемент и итерирует его:<\/p>\n<p>Я думаю, что лучше всего объяснить на конкретном примере:<\/p>\n<pre class=\"e2-text-code\"><code>&gt;&gt;&gt; list(chain.from_iterable([&quot;inspired&quot;, &quot;python&quot;]))\r\n['i', 'n', 's', 'p', 'i', 'r', 'e', 'd', 'p', 'y', 't', 'h', 'o', 'n']<\/code><\/pre><p>Поскольку строки также можно итерировать, а <i>WORDS<\/i> — это множество строк, то мы можем разбить это множество (или список и т. д.) на составные элементы. Это очень полезное свойство строк; вы можете прогнать строку через <i>set()<\/i> и получить все уникальные символы строки:<\/p>\n<pre class=\"e2-text-code\"><code>&gt;&gt;&gt; set(&quot;hello&quot;)\r\n{'e', 'h', 'l', 'o'}<\/code><\/pre><p><i>Множества созданы по образцу своих математических тезок.<\/i><\/p>\n<p>Это означает, что множества могут содержать только уникальные значения — без дубликатов — и они неупорядочены. Вот почему порядок в множестве и в строке  получился разным.<\/p>\n<p>Множества обладают многими полезными функциями, такими как проверка, содержится ли одно множество полностью в другом множестве (подмножестве); получение элементов, содержащихся в обоих множествах (пересечение); совмещение элементов двух множеств (объединение) и так далее.<\/p>\n<p>Итак, мы посчитали количество букв во всем словаре, и вот что получилось:<\/p>\n<pre class=\"e2-text-code\"><code>&gt;&gt;&gt; LETTER_COUNTER\r\nCounter({'h': 828,\r\n         'o': 1888,\r\n         'n': 1484,\r\n         'e': 3106,\r\n         's': 2954,\r\n         'v': 338,\r\n         # ...etc...\r\n        })<\/code><\/pre><p>Так, мы получили только абсолютное количество букв в словаре. Теперь нужно разделить его на общее количество букв, чтобы перейти к относительным величинам. К счастью, в классе Counter есть удобный метод total, который может посчитать общее количество букв всех слов словаря.<\/p>\n<p>Затем составляем таблицу частот:<\/p>\n<pre class=\"e2-text-code\"><code>LETTER_FREQUENCY = {\r\n    character: value \/ LETTER_COUNTER.total()\r\n    for character, value in LETTER_COUNTER.items()\r\n}<\/code><\/pre><p class=\"note\">Метод Counter.total() был добавлен в Python 3.10, поэтому, если вы используете более старую версию Python, вы можете заменить его на sum(LETTER_COUNTER.values()), который делает то же самое.<\/p>\n<p>Здесь я использую представление словарей (<i>dictionary comprehension<\/i>) для обработки каждого ключа и значения LETTER_COUNTER (это модифицированный словарь) и деления каждого значения на общее количество символов:<\/p>\n<pre class=\"e2-text-code\"><code>&gt;&gt;&gt; LETTER_FREQUENCY\r\n{ 'h': 0,02804403048264183,\r\n  'o': 0,06394580863674852,\r\n  'n': 0,050262489415749366,\r\n  'e': 0,10519898391193903,\r\n  's': 0.10005080440304827,\r\n  # ...etc...\r\n  }<\/code><\/pre><p>И теперь у нас есть идеальный счетчик букв в подмножестве словаря, которые мы считаем существующими словами Wordle. Обратите внимание, что я не делал эти операции для всего словаря — я обработал только те части, которые нам интересны. Маловероятно, что это сильно повлияло бы на ранжирование популярности букв, но в конечном итоге мы руководствуемся именно этим набором слов.<\/p>\n<p>Теперь нам нужен способ оценки каждого слова, чтобы мы могли предположить, какие слова встретятся с наибольшей вероятностью. Итак, нам нужно взять нашу таблицу частоты и создать функцию подсчета популярности слов, которая оценивает, насколько «популярны» буквы в этом слове:<\/p>\n<pre class=\"e2-text-code\"><code>def calculate_word_commonality(word):\r\n    score = 0.0\r\n    for char in word:\r\n        score += LETTER_FREQUENCY[char]\r\n    return score \/ (WORD_LENGTH - len(set(word)) + 1)<\/code><\/pre><p>Снова вспоминаем, что строка является итерируемой, и перебираем каждую букву в слове. Затем получаем популярность каждой буквы и суммируем их. Потом общее количество делится на длину слова минус количество уникальных символов (плюс один, чтобы предотвратить деление на ноль).<\/p>\n<p>Это не то что бы удивительная функция оценки слов, но она проста и взвешивает слова таким образом, что более уникальным символам придается больший вес. В идеале нам нужно как можно больше уникальных, часто встречающихся символов, чтобы максимизировать вероятность получения зеленых или желтых совпадений в Wordle.<\/p>\n<p>Быстрый тест подтверждает, что слова с редкими и повторяющимися символами имеют более низкий вес, чем слова с частыми и более уникальными символами.<\/p>\n<pre class=\"e2-text-code\"><code>&gt;&gt;&gt; calculate_word_commonality(&quot;fuzzy&quot;)\r\n0.04604572396274344<\/code><\/pre><pre class=\"e2-text-code\"><code>&gt;&gt;&gt; calculate_word_commonality(&quot;arose&quot;)\r\n0.42692633361558<\/code><\/pre><p>Все, что нам сейчас нужно — придумать способ сортировки и отображения этих слов, чтобы игрок мог выбирать из них:<\/p>\n<pre class=\"e2-text-code\"><code>import operator\r\n\r\ndef sort_by_word_commonality(words):\r\n    sort_by = operator.itemgetter(1)\r\n    return sorted(\r\n        [(word, calculate_word_commonality(word)) for word in words],\r\n        key=sort_by,\r\n        reverse=True,\r\n    )\r\n\r\ndef display_word_table(word_commonalities):\r\n    for (word, freq) in word_commonalities:\r\n        print(f&quot;{word:&lt;10} | {freq:&lt;5.2}&quot;)<\/code><\/pre><p>С помощью sort_by_word_commonality я создаю отсортированный (от большего к меньшему) список кортежей, где каждый кортеж содержит слово и рассчитанный балл для этого слова. Ключ, по которому я сортирую, — это относительная популярность слова в словаре.<\/p>\n<p>Я не использую лямбду для получения первого элемента; для таких простых вещей я предпочитаю operator.itemgetter, который делает то же самое.<\/p>\n<p>Также, я добавил функцию быстрого отображения для форматирования слов и их оценки в простую таблицу.<\/p>\n<p>Теперь поговорим о самом решении головоломки.<\/p>\n<h2>Решение головоломки Wordle<\/h2>\n<p>Поскольку я создаю его как простое консольное приложение, я собираюсь использовать input() и print().<\/p>\n<pre class=\"e2-text-code\"><code>def input_word():\r\n    while True:\r\n        word = input(&quot;Input the word you entered&gt; &quot;)\r\n        if len(word) == WORD_LENGTH and word.lower() in WORDS:\r\n            break\r\n    return word.lower()\r\n\r\n\r\ndef input_response():\r\n    print(&quot;Type the color-coded reply from Wordle:&quot;)\r\n    print(&quot;  G for Green&quot;)\r\n    print(&quot;  Y for Yellow&quot;)\r\n    print(&quot;  ? for Gray&quot;)\r\n    while True:\r\n        response = input(&quot;Response from Wordle&gt; &quot;)\r\n        if len(response) == WORD_LENGTH and set(response) &lt;= {&quot;G&quot;, &quot;Y&quot;, &quot;?&quot;}:\r\n            break\r\n        else:\r\n            print(f&quot;Error - invalid answer {response}&quot;)\r\n    return response<\/code><\/pre><p>Функционал приложения очень прост. Нужно узнать у пользователя слово WORD_LENGTH, которое он ввел в игре Wordle, и записать ответ от Wordle. Поскольку есть только три возможных цвета для буквы (зеленый, желтый и серый), ответ закодирован в простую строку из трех символов: G, Y и ?.<\/p>\n<p>Я также добавил обработку ошибок на тот случай, если пользователь ошибается при вводе данных, повторяя цикл до тех пор, пока не будет задана правильная последовательность. Я делаю это, снова преобразовывая полученную информацию в множество, а затем проверяя, является ли это множество подмножеством допустимых ответов.<\/p>\n<h2>Фильтрация зеленых, желтых и серых букв с помощью вектора слова<\/h2>\n<p>Зеленая буква указывает на правильность буквы и ее места в слове. Желтый означает, что место неправильное, но буква в слове присутствует; а серый что буквы нигде нет.<\/p>\n<p>Другой способ интерпретации этой информации заключается в том, что пока Wordle не сообщит нам, какие буквы зеленые, желтые или серые, существуют все варианты.<\/p>\n<pre class=\"e2-text-code\"><code>word_vector = [set(string.ascii_lowercase) for _ in range(WORD_LENGTH)]<\/code><\/pre><p>Я создаю список из пяти множеств, так как нам нужно определить 5 букв слова. Каждый элемент списка — это множество всех строчных английских букв. Проходясь по каждому множеству, я могу удалять буквы, в соответствии с тем, как окрашены буквы после попытки:<\/p>\n<ul>\n<li>Зеленая буква дает нам информацию только по текущему множеству<br \/>\nЭто означает, что если я встречу зеленую букву на втором месте, то я могу изменить второе множество и оставить только эту букву.<\/li>\n<\/ul>\n<ul>\n<li>Желтые буквы исключают возможность использовать эту букву на этом месте<br \/>\nТаким образом, все буквы, кроме этой, технически могут оказаться на этом месте. Удаление буквы из набора в этой позиции гарантирует, что мы не сможем выбрать слова, в которых эта буква стоит на этом месте.<\/li>\n<\/ul>\n<ul>\n<li>Серые буквы подразумевают исключение буквы из всего вектора.<br \/>\nСледовательно, эта буква должна быть удалена из всех множеств в векторе слова.<\/li>\n<\/ul>\n<p>Теперь нам нужна функция, которая выбирает слова, которые соответствуют текущему вектору слова. Есть несколько способов сделать это, но я предлагаю вот такой красивый и простой вариант:<\/p>\n<pre class=\"e2-text-code\"><code>def match_word_vector(word, word_vector):\r\n    assert len(word) == len(word_vector)\r\n    for letter, v_letter in zip(word, word_vector):\r\n        if letter not in v_letter:\r\n            return False\r\n    return True<\/code><\/pre><p>Этот подход использует метод <b>zip<\/b> для попарного сопоставления каждого символа в слове и каждого символа в векторе слова.<\/p>\n<p>Проходимся по каждому множеству, и, если буквы на определенном месте нет в соответствующем множестве, то цикл прерывается и слово исключается. Если все в порядке и цикл отрабатывает до последней буквы, то мы получаем ответ True и выходим из цикла, отмечая совпадение слова с вектором.<\/p>\n<h2>Проверяем слова на соответствие<\/h2>\n<p>Принимая во внимание все правила игры, теперь можем написать функцию поиска, которая фильтрует список слов с учетом ответов, которые мы получили от Wordle.<\/p>\n<pre class=\"e2-text-code\"><code>def match(word_vector, possible_words):\r\n    return [word for word in possible_words if match_word_vector(word, word_vector)]<\/code><\/pre><p>Эта функция объединяет в себе все правила, которые мы обсудили выше, и ищет с помощью представления списков (list comprehension). Каждое слово проверяется на соответствие с word_vector с помощью match_word_vector.<\/p>\n<h2>Сортировка результатов<\/h2>\n<p>Наконец, нам нужно создать небольшой UI, который может многократно запрашивать нужный нам ответ.<\/p>\n<pre class=\"e2-text-code\"><code>def solve():\r\n    possible_words = WORDS.copy()\r\n    word_vector = [set(string.ascii_lowercase) for _ in range(WORD_LENGTH)]\r\n    for attempt in range(1, ALLOWED_ATTEMPTS + 1):\r\n        print(f&quot;Attempt {attempt} with {len(possible_words)} possible words&quot;)\r\n        display_word_table(sort_by_word_commonality(possible_words)[:15])\r\n        word = input_word()\r\n        response = input_response()\r\n        for idx, letter in enumerate(response):\r\n            if letter == &quot;G&quot;:\r\n                word_vector[idx] = {word[idx]}\r\n            elif letter == &quot;Y&quot;:\r\n                try:\r\n                    word_vector[idx].remove(word[idx])\r\n                except KeyError:\r\n                    pass\r\n            elif letter == &quot;?&quot;:\r\n                for vector in word_vector:\r\n                    try:\r\n                        vector.remove(word[idx])\r\n                    except KeyError:\r\n                        pass\r\n        possible_words = match(word_vector, possible_words)<\/code><\/pre><p>Функция solve() включает в себя некоторые элементы, которые мы уже обсудили. Затем попадаем в цикл от 1 до ALLOWED_ATTEMPTS + 1, и после каждой попытки мы отображаем номер текущей попытки и количество оставшихся возможных слов. Затем мы вызываем функцию display_word_table, которая выводит красивую таблицу из 15 совпадений с наибольшим рейтингом популярности слова. Затем функции нужно узнать слово, которое пользователь в итоге ввел и ответ Wordle.<\/p>\n<p>После этого мы проходимся по ответу Wordle, отмечая какой букве соответствует ответ (последовательность цветов). Код прост: мы сопоставляем каждый из трех возможных цветов с соответствующим контейнером (зеленый — с word_vector и т. д.) и применяем правила, которые мы обсудили выше.<\/p>\n<p>Наконец, мы заново берем possible_words и проверяем совпадение с новым вектором слова с помощью match, сокращая множество потенциальных вариантов.<\/p>\n<h2>Давайте проверим, как это работает<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"http:\/\/test.leftjoin.ru\/pictures\/wordle.png\" width=\"518\" height=\"476\" alt=\"\" \/>\n<\/div>\n<p>Все начинается с запуска функции solve() (для краткости часть вывода опущена):<\/p>\n<pre class=\"e2-text-code\"><code>&gt;&gt;&gt; Attempt 1 with 5905 possible words\r\narose      | 0.43\r\nraise      | 0.42\r\n\r\n   ... etc ...\r\n\r\nInput the word you entered&gt; arose\r\nType the color-coded reply from Wordle:\r\n  G for Green\r\n  Y for Yellow\r\n  ? for Gray\r\nResponse from Wordle&gt; ?Y??Y\r\nAttempt 2 with 829 possible words\r\nliter      | 0.34\r\nliner      | 0.34\r\n\r\n   ... etc ...\r\n\r\nInput the word you entered&gt; liter\r\nResponse from Wordle&gt; ???YY\r\nAttempt 3 with 108 possible words\r\nnerdy      | 0.29\r\nnehru      | 0.28\r\n\r\n   ... etc ...\r\n\r\nInput the word you entered&gt; nerdy\r\nResponse from Wordle&gt; ?YY?G\r\nAttempt 4 with 25 possible words\r\nquery      | 0.24\r\nchewy      | 0.21\r\n\r\n   ... etc ...\r\n\r\nInput the word you entered&gt; query\r\nResponse from Wordle&gt; GGGGG\r\nAttempt 5 with 1 possible words\r\nquery      | 0.24<\/code><\/pre><h2>Резюме<\/h2>\n<ul>\n<li>Представления (Comprehensions) — мощный инструмент Python<br \/>\nОни могут совмещать итерацию с фильтрацией, но если вы злоупотребите этой функцией, добавляя слишком много циклов for или слишком много условных операторов, код может стать нечитаемым. Избегайте сильной вложенности этих операторов, если это возможно.<\/li>\n<\/ul>\n<ul>\n<li>Множества — полезный тип объекта в Python<br \/>\nМножества их их верное использование делают код более стабильным, более математически правильным и более кратким. Главное — знать, когда и как их использовать. В нашем решении множества сыграли существенную роль — не пренебрегайте ими!<\/li>\n<\/ul>\n<ul>\n<li>Регулярные выражения могут помочь описать все требования к поиску<br \/>\nХотя это не было использовано в коде, совпадение (или несовпадение) шаблона и слова — это то, что регулярные выражения делают круче всего. Подумайте, как можно переписать мэтчинг и векторизацию слов с помощью регулярных выражений.<\/li>\n<\/ul>\n<ul>\n<li>Модули itertools и collections содержат очень полезные инструменты<br \/>\nВы можете многого добиться с базовым знанием Python, если знаете, как именно можно использовать встроенные модули. Модуль itertools особенно полезен, если вы вам нужно провернуть итеративные вычисления.<\/li>\n<\/ul>\n",
            "date_published": "2022-01-26T15:18:49+03:00",
            "date_modified": "2022-01-25T13:35:25+03:00",
            "image": "http:\/\/test.leftjoin.ru\/pictures\/2022-01-24-18.26.50.jpg",
            "_date_published_rfc2822": "Wed, 26 Jan 2022 15:18:49 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "129",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "http:\/\/test.leftjoin.ru\/pictures\/2022-01-24-18.26.50.jpg",
                    "http:\/\/test.leftjoin.ru\/pictures\/wordle.png"
                ]
            }
        }
    ],
    "_e2_version": 3365,
    "_e2_ua_string": "E2 (v3365; Aegea)"
}