Pull to refresh

Генератор строк из шаблона

Устраиваясь на должность младшего-программиста, мне дали выполнить несколько тестовых заданий. Часть из них я решил на месте, а часть была в виде домашнего задания. Меня заинтересовало одно из них, вот его текст:

На PHP написать класс StringGenerator — генератор строк из шаблона. В конструкторе задается шаблон. Затем вызывается метод, генерирующий заданное количество строк(строки не должны повторяться). В шаблоне, в фигурных скобках записаны варианты слов/словосочетаний. Разделителем служит "|".

$strPattern="{Данная|Эта}" строка {должна быть|будет} сгенерирована {методом{класса StringGenerator|gerateString} автоматически|заданное число раз}.";
$objStringGenerator = new StringGenerator($strPattern);
$objStringGenerator -> generateString(3);
Данный код должен вывести на экран 3 различных строки.

Следующий день оказался суматошным и продумывать работу пришлось в метро с ручкой и ежедневником. На бумаге всё было хорошо, но перевести всё в работающий код не получилось. Шестое чувство подсказало, что задачка типовая и не зачем придумывать велосипеды, когда уже должны быть такие. И это было моей первой ошибкой. Google в паре с Yandex, находили, не то чего я ожидал. Но спустя некоторое время я попал сюда. Не буду долго описывать мою первую радость, а потом разочарование. Углубившись в более гремучие дебри, велосипедов я не находил. Ушел в «изучение» шаблонизаторов. Один из форумов(или хабр) меня познакомил с рекурсивно-нисходящими парсерами, БНФ. Начал уже искать велосипеды с подобными ключевыми словами. Увидя то количество кода, что предлагали в виде «простого примера», понял, что всё это стрельба из пушки по воробьям. И решил вернуться к своему велосипеду.


Отоспавшись два дня, я создал новый файл, достал ежедневник и ручку. Я работаю медленно, но верно. Мой алгоритм был прост: блок — массивы, несколько блоков, несколько массивов. В каждом своё количество элементов. И подписав каждый блок, решил что генерировать строки будет проще, если будут известны варианты комбинаций заранее. В итоге была написана функция, которая зная максимальное количество элементов в каждом блоке генерировала значения.
Например, если шаблон: "{Слово0|Слово1}{Слово2|Слово3|Слово4|Слово5}{Слово6|Слово7|Слово8}", то массив в итоге хранил:
132
131
130
122
121
...
001
000

Потом была написана рекурсивная функция, которая перебирала строку. «Потом», потому что в первый раз сгенерировать варианты комбинаций я не смог и думал, что снова не получиться. Функция перебирала строку-шаблон с помощью регулярного выражения и находила самый низкий блок(внутри которого нет других), передавала найденный блок в функцию, где он резался, сохранялся его номер и вычислялось количество элементов. Блок в строке удалялся и снова обрабатывался, пока поиски шаблонов были возможны. Этим самым я избавлялся от проблемы вложенных блоков. Написав, ещё парочку вспомогательных функций, всё это было собрано в класс, вот его код:
<?php
class StrinGenerator{

var $str; //исходная строка
var $max_arr; //массив, в котором храним максимальное количество вариантов для блока
var $result=array(); //массив выходных строк
var $variant_arr; //массив в котором храним возможные варианты
var $count_i=0; //глобальный счетчик строк, поможет при переборе вариантов
var $count_str; //количество нужных строк
var $step; //переменная является порядковым номером блока
var $key_stop=false; //глобальный ключ для прекращения работы

function StrinGenerator($str){
$this->str=$str;
$this->pre_pars($str); //рекурсивный перебор строки, формирует массив, в котором храним максимальное значение ключа для каждого блока
$this->func_variant_arr(); //генерируем массив с вариантами комбинаций(строк)
}

function generateString($count){
$this->count_str = $count;

while(true){
$this->step=0;
$this->count_i++; //увеличиваем счетчик => смотрим следующий вариант строки
$res_tmp=$this->pars($this->str); //вызываем рекурсивную функцию для перебора и генерирования строки
if(!in_array($res_tmp, $this->result)) { //если нет повторений
$this->result[] = $res_tmp; //сохраняем новый варант строки
if(count($this->result) == $this->count_str OR $this->key_stop) { //если нужное количество или глобальный ключ=true
$this->echo_result(); //вызов функции вывода
break;
}
}
}
}

function pre_pars($str){
if (preg_match('|{([^{]*?)}|i', $str)){ //если есть блоки
$text=preg_replace('|{([^{]*?)}|sie', "\$this->arr_count('\\0')",$str); //захватили и передали
return $this->pre_pars($text);
}
}

function arr_count($text){
$arr=explode("|",$text);
$this->max_arr[]=count($arr)-1; //сохраняем максимальное значение ключа для данного блока
}

function pars($str){
if (preg_match('|{(?=[^\{]*\}).*?}|i', $str)){ //если есть блоки
$text=preg_replace('|{([^{]*?)}|sie', "\$this->return_res('\\1')",$str); //захватили, передали, заменили на один из варинатов
return $this->pars($text); //снова перебираем, помогает справиться с вложенными блоками
} else {
return $str; //возвратили строку
}
}

function return_res($text){
$res=explode("|",$text);
$variant_str=$this->variant_arr[$this->count_i]; //выбираем комбинацию
$key=substr($variant_str,$this->step++,1); //выбираем значение для обрабатываемого блока, вариант из слов
if($variant_str==0) $this->key_stop=true; //дошли до последней возможной комбинации, прекращаем работу
return $res[$key];
}

function func_variant_arr(){
$max_arr=$this->max_arr;

foreach($max_arr as $value){ //фомируем из массива число, легче для подсчетов комбинаций
$max.= $value;
}

while($max>=0){
$max=str_pad($max,4,0,STR_PAD_LEFT); //дополняем число,чтобы вариант комбинации был "целым"
$this->variant_arr[]=$max--;
for($n=0;$n<count($max_arr);$n++){
if (substr($max,$n,1)>$max_arr[$n]) {$max=substr_replace($max,$max_arr[$n],$n, 1);}; //проверка если разряд комбинации(номер блока) больше максимального значения, то приравниваем этому самому значению
}
}
}

function echo_result(){ //вывод строк
foreach($this->result as $value){
echo $value."<br>";
}
}

}

$obj=new StrinGenerator("{Данная|Эта} строка, {должна быть|будет} сгенерирована {методом {класса StringGenerator|generateString} автоматически|заданное число раз}.");
$obj->generateString(5);

?>


Код прост, не используется никаких сложных структур. В этом его плюс, хотя есть люди «свободно читающие» на PHP, которые пробегают по коду, даже не задумывавшись и всё прекрасно поняв. Код занял меньше 100 строк, более чем уверен, что его ещё можно упростить процентов на 20-30.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.