Настало время крутых проектов!
Представьте, что вы запускаете рассылку в крупной компании, получили уже за это много денег и вам хочется как-то её разнообразить. И вы решаете сделать так, чтобы человеку в ответ приходило письмо-благодарность. Но не одно письмо на всех, а несколько разных, чем больше — тем лучше.
Можно их все написать вручную и потратить на это несколько дней. А можно написать код, который сам напишет все эти письма. Мы всегда выбираем второй путь, даже если это не нужно, поэтому сегодня мы запрограммируем генератор текста на JavaScript.
Сразу оговоримся: вам может показаться, что этот генератор плохо работает и выдаёт дурацкие тексты. Всё дело в том, что в этом алгоритме важно предусмотреть все варианты составления структуры текста и самих предложений, а потом проверить, как это всё согласовывается между собой. Чем длиннее и «живее» нужен текст, тем больше вариантов подстановки слов нужно предусмотреть.
Алгоритм работы генератора текста будет такой:
- Готовим HTML-страницу, где это всё будет работать и запускаться.
- Задаём структуру текста на уровне абзацев: приветствие, само письмо, заключение.
- Пишем шаблоны для каждого из этих элементов.
- Случайным образом выбираем, каким тоном будем общаться — официальным или нет.
- Собираем всё письмо в одно целое.
- Заменяем шаблонные слова на нужные фразы и словосочетания.
- Показываем результат.
Делаем HTML-страницу
Берём за основу стандартный шаблон и добавляем в него:
- jQuery, чтобы обращаться к элементам страницы по имени;
- файл скрипта mail.js, в котором будет происходить вся магия JS-кода;
- стили для элементов страницы, чтобы она выглядела опрятно;
- блок, куда будем выводить готовое письмо;
- кнопку, которая по нажатию будет генерировать новый текст.
Всё это мы уже умеем делать, поэтому просто собираем код:
<!DOCTYPE html>
<html>
<!-- служебная часть -->
<head>
<!-- заголовок страницы -->
<title>Генератор писем</title>
<!-- настраиваем служебную информацию для браузеров -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- подключаем jQuery -->
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js'></script>
<!-- подключаем наш скрипт -->
<script src="mail.js"> </script>
<!-- настраиваем внешний вид блока для готового письма и кнопки -->
<style type="text/css">
.text {
font-size: 20px;
width: 80%;
margin: 30px;
}
.controls button {
font-size: 20px;
margin-left: 30px;
}
</style>
<!-- закрываем служебную часть страницы -->
</head>
<body>
<!-- в этом блоке будем выводить готовое письмо -->
<div class="text" id="text_here"></div>
<!-- блок с кнопкой, по нажатию на которую запускается нужная функция в скрипте -->
<div class="controls">
<button value="Получить новый текст" onclick="get_text();">Получить новый текст</button>
</div>
</body>
<!-- конец всей страницы -->
</html>
Готовим структуру письма и шаблоны для текста
Теперь нам понадобится отдельный файл для скрипта, который и будет отвечать за генерацию писем — mail.js. Создаём его в той же папке, что и HTML-документ, и начинаем программировать.
Смысл нашего алгоритма в том, что мы заранее определяем блоки текста и порядок фраз, но случайным образом выбираем, что же будет подставляться на место этих блоков.
Для этого мы будем использовать шаблонные слова, у которых с обеих сторон стоят знаки доллара, например, $somebody$
. Эти шаблонные слова будут означать для нашего алгоритма, что на их месте появится какое-то конкретное слово или целое предложение. Это мы сами решили, что шаблоны будут работать со знаками доллара — можно было выбрать и другие знаки.
Пример: зададим по шаблону предложение «Я $love$ $you$
». Чтобы получилась осмысленная фраза, мы должны задать значения для обоих шаблонных слов. Сделаем это так:
- пусть
$love$
принимает значения «тебя», «вас», «нас», «их», «всех»; - пусть
$you$
принимает значения «люблю», «ненавижу», «не люблю», «рад видеть».
Если мы попросим алгоритм случайно заменить шаблонные слова на конкретные значения из списка, то можем получить такие предложения:
- Я их люблю.
- Я тебя ненавижу.
- Я вас не люблю.
- Я всех люблю.
- Я нас рад видеть.
- …
Смотрите, большинство предложений звучит нормально, а последнее — не очень. Это не значит, что алгоритм плохой, просто мы, когда подбирали слова, не слишком подумали над их сочетанием. Поэтому основная проблема таких алгоритмов — ручная проверка и согласование всех слов и конструкций, которые используются для подстановки. Чем больше времени этому посвятить, тем более осмысленные тексты получатся у алгоритма.
Чтобы тексты получались разнообразными, можно сделать так, чтобы шаблон выбирался в зависимости от какой-то переменной. Именно так мы сделаем выбор интонации письма, официальную или неформальную.
Начало
Вот код, с которого начинается наш скрипт. В нём мы задаём шаблоны начала, тела и концовки письма, причём каждый шаблон — в двух вариантах. Первый отвечает за формальный стиль ответа, а второй — за неформальный. Всё это оформляется как массивы. Если вы не знаете, как с ними работать — решите задачу про сейф.
Интересный момент — внутри массивов a_text
и b_text
мы использовали шаблонное слово $somebody$
. Алгоритм сначала подставит предложение вместе с этим шаблонным словом, а вторым проходом — заменит его на конкретное значение.
Все наши массивы мы объединили в один объект с помощью фигурных скобок, чтобы дальше с ним работать как с единым целым. Это позволит алгоритму найти всё в одном месте и последовательно заменить все шаблонные слова на нужные значения.
// шаблон начала письма
var intro = ['$a_intro$', '$b_intro$ '];
// шаблон тела письма
var text = ['$a_text$', '$b_text$'];
// шаблон концовки письма
var outro = ['$a_outro$', '$b_outro$'];
// задаём набор фраз и слов для всех шаблонов сразу
var text_obj = {
//структуру письма пока оставляем пустой
structure: [
''
],
// текст для начала
a_intro: ['Здравствуйте.', 'Добрый день!'],
b_intro: ['Привет!', 'Хэллоу!', 'Бонжур!'],
// варианты основного текста
a_text: ['Перед вами — первое письмо в рассылке. Наш $somebody$ рад тому, что вы не прошли мимо подписки, и приглашает вас на нашу выставку, адрес — во вложении.', 'Меня зовут Михаил Максимов, и я — $somebody$ в этой компании. От лица всех сотрудников я рад приветствовать вас в рядах наших единомышленников.'],
b_text: ['Если ты видишь это письмо, знай — наш $somebody$ здорово постарался для этого. Зато ты теперь сможешь прийти к нам на выставку и убедиться своими глазами в том, о чём мы тебе говорили!', 'Ты сделал это, а значит, твой $somebody$ будет в восторге! Если нет — дай нам знать, и мы это исправим.'],
// должность
somebody: ['директор', 'руководитель', 'начальник отдела'],
// текст для концовки
a_outro: ['Спасибо, что подписались на нашу рассылку!', 'Если будут вопросы — пишите.', 'Если письмо попало к вам по ошибке — проигнорируйте его.'],
b_outro: ['Теперь ты один из нас!', 'Здорово, что мы теперь — команда!', 'Рады, что ты с нами! Пиши, если есть что спросить.'],
};
Теперь можно собрать письмо в одно целое. Для этого берём начало, середину и концовку, добавляем HTML-теги и соединяем всё в одну строку. Не стоит опасаться того, что всё будет выглядеть некрасиво — для этого мы как раз и добавляем теги, чтобы они разметили наш текст правильно и красиво.
Мы говорили, что сделаем так, чтобы алгоритм сам выбирал, в каком стиле сделать письмо. За это отвечает переменная mood: если она равна 0, то стиль будет официальным, а если она равна 1 — неформальным. Чтобы каждый раз это число определялось случайным образом, добавим функцию, которая возвращает случайное число в заданном диапазоне.
// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// собираем письмо в одно целое
function generate_structure() {
// случайным образом определяем тон письма — официальный (0) или неформальный (1)
var mood = randz(0, 1);
// результат помещаем в переменную вместе с тегами
result = '<h2>' + intro[mood] + '</h1>\n';
result += '<p>' + text[mood] + '</p>\n';
result += '<p>' + outro[mood] + '</p>\n';
// возвращаем результат — одну строку с HTML-разметкой
return result;
}
Чтобы было понятно, что будет происходить дальше в коде, заглянем немного вперёд. Алгоритм будет действовать так: возьмёт структуру письма и раз за разом будет находить все шаблонные слова со знаком доллара, находить нужный массив, откуда они берутся, и подставлять их. Так алгоритм будет работать до тех пор, пока не закончатся все шаблонные слова. Вот как, например, может выглядеть наше письмо с тегами на каждом проходе алгоритма.
1-й проход:
<h2>$a_intro$</h2>
<p>$a_text$</p>
<p>$a_outro$</p>
2-й проход:
<h2>Здравствуйте.</h2>
<p>Перед вами — первое письмо в рассылке. Наш $somebody$ рад тому, что вы не прошли мимо подписки, и приглашает вас на нашу выставку, адрес — во вложении.</p>
<p>Спасибо, что подписались на нашу рассылку!</p>
3-й проход:
<h2>Здравствуйте.</h2>
<p>Перед вами — первое письмо в рассылке. Наш директор рад тому, что вы не прошли мимо подписки, и приглашает вас на нашу выставку, адрес — во вложении.</p>
<p>Спасибо, что подписались на нашу рассылку!</p>
Как видим, алгоритм сначала заменил все шаблонные слова первого уровня, а затем — второго уровня. Можно делать сколько угодно вложений — алгоритм заменит их все на нормальные слова.
Меняем шаблонные слова на текст
Здесь принцип работы такой:
- находим все слова, где есть значки доллара;
- убираем их и получаем название массива в объекте, где хранятся значения для этого шаблонного слова;
- идём в этот массив и случайно выбираем оттуда значение;
- подставляем это значение вместо шаблонного слова;
- делаем так до тех пор, пока не закончатся все шаблонные слова.
// убираем знаки доллара после замены шаблонного слова на реальный текст
function parse_keywords(string) {
// задаём шаблон поиска таких слов
pattern = /\$\w+\$/g;
// проверяем, есть ли в строке нужные нам слова
keyword = string.match(pattern);
// если есть
if (keyword) {
// пока не закончатся все такие слова в строке…
for (var i = keyword.length - 1; i >= 0; i--) {
// убираем значок доллара
keyword[i] = keyword[i].replace(/\$/g, '');
}
}
// возвращаем слово без знаков доллара
return keyword;
}
// выбираем случайный элемент массива
function randomize(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
// меняем одно слово на другое
function replace_keyword(source, keyword, variant) {
return (source.replace('$' + keyword + '$', variant));
}
//подставляем случайным образом готовые слова вместо шаблонных слов со знаком доллара
function bake(object) {
// переменная, в которой на выходе получится готовый текст
var result = randomize(object['structure']);
// пока есть шаблонные слова со знаком доллара
do {
// выбираем их
keywords = parse_keywords(result);
// если они точно есть, то
if (keywords) {
// пока они не закончатся
for (var i = keywords.length - 1; i >= 0; i--) {
// случайным образом подставляем вместо шаблонных слов с долларом слова из наборов
if (object.hasOwnProperty(keywords[i])) {
result = replace_keyword(result, keywords[i], randomize(object[keywords[i]]));
}
}
}
} while (keywords);
// возвращаем готовый результат
return result;
}
Привязываем скрипт к кнопке и выводим результат
Мы уже прописали в HTML-документе, что по клику будет вызываться функция get_text()
. Давайте это запрограммируем так, чтобы текст письма сразу после сборки появлялся на экране. Чтобы это сделать, используем jQuery, найдём блок для вывода текста text_here
и отправим наш текст с HTML-тегами в этот блок.
Браузер сам распознает все теги и отформатирует письмо так, как нужно нам. Если вы хотите другое форматирование — поменяйте теги или пропишите в них нужные стили.
// подставляем текст с тегами в нужне место на странице
function send(text) {
document.getElementById('text_here').innerHTML = text;
}
// что мы делаем по нажатию на кнопку
function get_text() {
// заводим переменную для структуры текста
var currentObject = text_obj;
// наполняем структуру текстом с шаблонными словами
currentObject.structure[0] = generate_structure();
// меняем шаблонные слова на нормальный текст
result = bake(currentObject);
// выводим результат
send(result);
}
// шаблон начала письма
var intro = ['$a_intro$', '$b_intro$ '];
// шаблон тела письма
var text = ['$a_text$', '$b_text$'];
// шаблон концовки письма
var outro = ['$a_outro$', '$b_outro$'];
// задаём набор фраз и слов для всех шаблонов сразу
var text_obj = {
//структуру письма пока оставляем пустой
structure: [
''
],
// текст для начала
a_intro: ['Здравствуйте.', 'Добрый день!'],
b_intro: ['Привет!', 'Хэллоу!', 'Бонжур!'],
// варианты основного текста
a_text: ['Перед вами — первое письмо в рассылке. Наш $somebody$ рад тому, что вы не прошли мимо подписки, и приглашает вас на нашу выставку, адрес — во вложении.', 'Меня зовут Михаил Максимов, и я — $somebody$ в этой компании. От лица всех сотрудников я рад приветствовать вас в рядах наших единомышленников.'],
b_text: ['Если ты видишь это письмо, знай — наш $somebody$ здорово постарался для этого. Зато ты теперь сможешь прийти к нам на выставку и убедиться своими глазами в том, о чём мы тебе говорили!', 'Ты сделал это, а значит, твой $somebody$ будет в восторге! Если нет — дай нам знать, и мы это исправим.'],
// должность
somebody: ['директор', 'руководитель', 'начальник отдела'],
// текст для концовки
a_outro: ['Спасибо, что подписались на нашу рассылку!', 'Если будут вопросы — пишите.', 'Если письмо попало к вам по ошибке — проигнорируйте его.'],
b_outro: ['Теперь ты один из нас!', 'Здорово, что мы теперь — команда!', 'Рады, что ты с нами! Пиши, если есть что спросить.'],
};
// убираем знаки доллара после замены шаблонного слова на реальный текст
function parse_keywords(string) {
// задаём шаблон поиска таких слов
pattern = /\$\w+\$/g;
// проверяем, есть ли в строке нужные нам слова
keyword = string.match(pattern);
// если есть
if (keyword) {
// пока не закончатся все такие слова в строке…
for (var i = keyword.length - 1; i >= 0; i--) {
// убираем значок доллара
keyword[i] = keyword[i].replace(/\$/g, '');
}
}
// возвращаем слово без знаков доллара
return keyword;
}
// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// выбираем случайный элемент массива
function randomize(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
// меняем одно слово на другое
function replace_keyword(source, keyword, variant) {
return (source.replace('$' + keyword + '$', variant));
}
//подставляем случайным образом готовые слова вместо шаблонных слов со знаком доллара
function bake(object) {
// переменная, в которой на выходе получится готовый текст
var result = randomize(object['structure']);
// пока есть шаблонные слова со знаком доллара
do {
// выбираем их
keywords = parse_keywords(result);
// если они точно есть, то
if (keywords) {
// пока они не закончатся
for (var i = keywords.length - 1; i >= 0; i--) {
// случайным образом подставляем вместо шаблонных слов с долларом слова из наборов
if (object.hasOwnProperty(keywords[i])) {
result = replace_keyword(result, keywords[i], randomize(object[keywords[i]]));
}
}
}
} while (keywords);
// возвращаем готовый результат
return result;
}
// собираем письмо в одно целое
function generate_structure() {
// случайным образом определяем тон письма — официальный (0) или неформальный (1)
var mood = randz(0, 1);
// результат помещаем в переменную вместе с тегами
result = '<h2>' + intro[mood] + '</h1>\n';
result += '<p>' + text[mood] + '</p>\n';
result += '<p>' + outro[mood] + '</p>\n';
// возвращаем результат — одну строку с HTML-разметкой
return result;
}
// подставляем текст с тегами в нужне место на странице
function send(text) {
document.getElementById('text_here').innerHTML = text;
}
// что мы делаем по нажатию на кнопку
function get_text() {
// заводим переменную для структуры текста
var currentObject = text_obj;
// наполняем структуру текстом с шаблонными словами
currentObject.structure[0] = generate_structure();
// меняем шаблонные слова на нормальный текст
result = bake(currentObject);
// выводим результат
send(result);
}
Что дальше
На самом деле возможности этого скрипта почти безграничны: при должной усидчивости и знании русского языка можно заставить этот скрипт выдавать осмысленный текст любой длины.
Например, можно генерировать ответы службы поддержки, сообщения в чат-ботах, подводки для статей или создавать резюме. Этот скрипт — убийца копирайтеров, потому что он делает за них всю основную работу. Вам остаётся только пройти по тексту и убрать шероховатости.
Кстати, в одной редакции (не в нашей) такой алгоритм несколько месяцев генерировал статьи, и никто ничего не заметил. Читателям нравилось. Совпадение?