Разделение на уровни вложенности в PHP

alexsun7

Новичок
Подскажите, пожалуйста, можно ли разделить на уровни вложенности строку и каждому уровню вложенности задать процент (где между фигурной скобкой и прямой чертой идет значение).

Например, такая строка: { 1 уровень - 1 | 1 уровень - 2 { 2 уровень - 1 | 2 уровень - 2 } }

Чтобы в результате получилась строка:

{ 50%: 1 уровень - 1 | 50%: 1 уровень - 2 { 25%: 2 уровень - 1 | 25%: 2 уровень - 2 } }

Примерно по такому принципу. Не только на 2 уровень, но и на более глубокие уровни вложенности.
 

alexsun7

Новичок
Проще говоря, как бы разделить эту строку по "|", но чтобы оно было вне фигурных скобок.
 

AmdY

Пью пиво
Команда форума
разделить это explode. вопро зачем вам такие извращения и такой странный формат для парсинга. неужели нельзя данные изначально получать в удобной форме?
 

alexsun7

Новичок
разделить это explode. вопро зачем вам такие извращения и такой странный формат для парсинга. неужели нельзя данные изначально получать в удобной форме?
В таком случае разделение будет происходить и внутри фигурных скобок. Это исходная форма данных.
 

ksnk

прохожий
Вариантов много. Например, можно в цикле регуляркой выковыривать все фигурные скобки, внутри которых нет открывающихся-закрывающихся фигурных скобок. Например, заменяя это на пустую строку и запоминая место... Так мы можем, при достаточной ловкости рук, пронумеровать все вложенные скобочные конструкты.
А можно написать относительно честный парсер языка. Будут токены - фигурные скобки, разделитель - палки и все остальное. Например вот
PHP:
function calc($x)
{
    // указатель в строке. В комбинации с preg_match мы тут имеем бинарное смещение в строке - 8-bit
    $start = 0;
    /**
     * Выдаем ошибку. Эхом. Если не нравится - можно раскоментировать Exception
     * @param $msg
     */
    $error  =function($msg) use ($x,&$start){
        // вот так находим место ошибки. Примерно... substr работает как он хочет, в зависимости от версии php, так что mb_...
        $msg.="\n".mb_substr($x,0,$start, '8bit').'^^^'.mb_substr($x,$start,NULL , '8bit');
        echo $msg; /* or
        throw new Exception($msg);
        */
        return null;
    };

    /**
     * тип последнего отданного нам токена
     */
    $lastTokenIsString=0;

    /**
     * Токенизация, функция, выдающая следующий токен из строки. Почти yeld, только не yeld...
     */
    $getNextToken = function() use($x,&$error,&$start, &$lastTokenIsString){
        if ($start < strlen($x)) {
            if (!preg_match('/\s*(?:([^\}\{\|]+)|\}|\{|\|)/u', $x, $m, 0, $start))
                return null;
            if ('' == $m[0]) {
                return $error('WTF? ошибка в регулярке?'); // системная ошибка, исправлять регулярку...
            }
            $start += mb_strlen($m[0], '8bit'); // в 7.3 регулярки работают с utf как с бинарными строками.
            $lastTokenIsString = !empty($m[1]);
            return trim($m[0]);
        }
        return null;
    };

    /**
     * свертка токенов в результат
     * сюда попадаем, когда слопали токен `{`
     * @return array|mixed
     */
    $getBracers = function () use (&$getBracers, &$getNextToken, &$error, &$lastTokenIsString) {
        $result = [];
        do {
            $value = $getNextToken();
            if($lastTokenIsString) {
                $next= $getNextToken();
            } else { // вместо текстовой строки указана операция
                $next = $value;
                $value = null;
            }
            if(is_null($next)) {
                return $error('Что-то пошло не так. Скобки не закрыты ?');
            }
            if(!empty($value))
                $result[]=['value'=>$value];
            if ($next == '}') {
                return $result;
            } else if ($next == '|') {
            } else if ($next == '{') {
                $last=count($result) - 1;
                if(!isset($result[$last]['child']))
                    $result[$last]['child']=[];
                $result[$last]['child'] = array_merge(
                    $result[$last]['child'],$getBracers()
                );
            }
        } while(true);
        return $result;
    };
    /**
     * расставляем проценты
     */
    $percents=function(&$array,$pers=100) use(&$percents){
        if(!empty($array))
            foreach($array as &$a){
                $a['percent']=$pers/count($array);
                if(isset($a['child'])){
                    $percents($a['child'],$a['percent']);
                }
            }
    };

    // если в начале строки есть какой-то мусор - вот тут можно его поскипать...
    if('{'!=$getNextToken()){
        return $error('строка начинается не с символа {');
    }

    $result=$getBracers();
    $percents($result);
    return $result;
}

print_r(calc('{ 1 уровень - 1 | 1 уровень - 2 { 2 уровень - 1 | 2 уровень - 2 }}'));
// чилды в одной ячейке собираются в кучку
print_r(calc('{ 1 уровень - 1 | 1 уровень - 2 { 2 уровень - 1 | 2 уровень - 2 {aaaaaaaa}{aaaaaaaa}{aaaaaaaa} }}'));
Код:
Array
(
    [0] => Array
        (
            [value] => 1 уровень - 1
            [percent] => 50
        )

    [1] => Array
        (
            [value] => 1 уровень - 2
            [child] => Array
                (
                    [0] => Array
                        (
                            [value] => 2 уровень - 1
                            [percent] => 25
                        )

                    [1] => Array
                        (
                            [value] => 2 уровень - 2
                            [percent] => 25
                        )

                )

            [percent] => 50
        )

)
Собирать обратно в строку с процентами - это останется на факультатив...
 

alexsun7

Новичок
Вариантов много. Например, можно в цикле регуляркой выковыривать все фигурные скобки, внутри которых нет открывающихся-закрывающихся фигурных скобок. Например, заменяя это на пустую строку и запоминая место... Так мы можем, при достаточной ловкости рук, пронумеровать все вложенные скобочные конструкты.
А можно написать относительно честный парсер языка. Будут токены - фигурные скобки, разделитель - палки и все остальное. Например вот
PHP:
function calc($x)
{
    // указатель в строке. В комбинации с preg_match мы тут имеем бинарное смещение в строке - 8-bit
    $start = 0;
    /**
     * Выдаем ошибку. Эхом. Если не нравится - можно раскоментировать Exception
     * @param $msg
     */
    $error  =function($msg) use ($x,&$start){
        // вот так находим место ошибки. Примерно... substr работает как он хочет, в зависимости от версии php, так что mb_...
        $msg.="\n".mb_substr($x,0,$start, '8bit').'^^^'.mb_substr($x,$start,NULL , '8bit');
        echo $msg; /* or
        throw new Exception($msg);
        */
        return null;
    };

    /**
     * тип последнего отданного нам токена
     */
    $lastTokenIsString=0;

    /**
     * Токенизация, функция, выдающая следующий токен из строки. Почти yeld, только не yeld...
     */
    $getNextToken = function() use($x,&$error,&$start, &$lastTokenIsString){
        if ($start < strlen($x)) {
            if (!preg_match('/\s*(?:([^\}\{\|]+)|\}|\{|\|)/u', $x, $m, 0, $start))
                return null;
            if ('' == $m[0]) {
                return $error('WTF? ошибка в регулярке?'); // системная ошибка, исправлять регулярку...
            }
            $start += mb_strlen($m[0], '8bit'); // в 7.3 регулярки работают с utf как с бинарными строками.
            $lastTokenIsString = !empty($m[1]);
            return trim($m[0]);
        }
        return null;
    };

    /**
     * свертка токенов в результат
     * сюда попадаем, когда слопали токен `{`
     * @return array|mixed
     */
    $getBracers = function () use (&$getBracers, &$getNextToken, &$error, &$lastTokenIsString) {
        $result = [];
        do {
            $value = $getNextToken();
            if($lastTokenIsString) {
                $next= $getNextToken();
            } else { // вместо текстовой строки указана операция
                $next = $value;
                $value = null;
            }
            if(is_null($next)) {
                return $error('Что-то пошло не так. Скобки не закрыты ?');
            }
            if(!empty($value))
                $result[]=['value'=>$value];
            if ($next == '}') {
                return $result;
            } else if ($next == '|') {
            } else if ($next == '{') {
                $last=count($result) - 1;
                if(!isset($result[$last]['child']))
                    $result[$last]['child']=[];
                $result[$last]['child'] = array_merge(
                    $result[$last]['child'],$getBracers()
                );
            }
        } while(true);
        return $result;
    };
    /**
     * расставляем проценты
     */
    $percents=function(&$array,$pers=100) use(&$percents){
        if(!empty($array))
            foreach($array as &$a){
                $a['percent']=$pers/count($array);
                if(isset($a['child'])){
                    $percents($a['child'],$a['percent']);
                }
            }
    };

    // если в начале строки есть какой-то мусор - вот тут можно его поскипать...
    if('{'!=$getNextToken()){
        return $error('строка начинается не с символа {');
    }

    $result=$getBracers();
    $percents($result);
    return $result;
}

print_r(calc('{ 1 уровень - 1 | 1 уровень - 2 { 2 уровень - 1 | 2 уровень - 2 }}'));
// чилды в одной ячейке собираются в кучку
print_r(calc('{ 1 уровень - 1 | 1 уровень - 2 { 2 уровень - 1 | 2 уровень - 2 {aaaaaaaa}{aaaaaaaa}{aaaaaaaa} }}'));
Код:
Array
(
    [0] => Array
        (
            [value] => 1 уровень - 1
            [percent] => 50
        )

    [1] => Array
        (
            [value] => 1 уровень - 2
            [child] => Array
                (
                    [0] => Array
                        (
                            [value] => 2 уровень - 1
                            [percent] => 25
                        )

                    [1] => Array
                        (
                            [value] => 2 уровень - 2
                            [percent] => 25
                        )

                )

            [percent] => 50
        )

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

В втором варианте строки:
print_r(calc('{ 1 уровень - 1 | 1 уровень - 2 { 2 уровень - 1 | 2 уровень - 2 {aaaaaaaa}{aaaaaaaa}{aaaaaaaa} }}'));

Где идет: "{aaaaaaaa}{aaaaaaaa}{aaaaaaaa}" делится 25% на 3 и в итоге получаем результат.

Можно ли сделать так, чтобы каждому из этих блоков присваивалось значение в 25% (значение с "2 уровень - 2")? Таким образом:
"{25%: aaaaaaaa}{25%: aaaaaaaa}{25%: aaaaaaaa}"

Если бы эти блоки шли таким образом:
"{aaaaaaaa|aaaaaaaa}{aaaaaaaa|aaaaaaaa}{aaaaaaaa|aaaaaaaa}"

Тогда результат был бы такой:
"{12,5%: aaaaaaaa|12,5%: aaaaaaaa}{12,5%: aaaaaaaa|12,5%: aaaaaaaa}{12,5%: aaaaaaaa|12,5%: aaaaaaaa}"
 

AnrDaemon

Продвинутый новичок
Откуда такой больной исходный формат? Не верю, что нет альтернативы.
Если хотите нормального результата - пишите конечный автомат. Тем более, что структура регулярная.
 

ksnk

прохожий
У меня массив собирается в кучу вот такой конструкцией
PHP:
 $result[$last]['child'] = array_merge(
     $result[$last]['child'],$getBracers()
);
Таким образом, с точки зрения моего кода, конструкция {aaaaaaaa}{aaaaaaaa}{aaaaaaaa} эквивалентна {aaaaaaaa|aaaaaaaa|aaaaaaaa}.
Если это не так, можно заменить array_merge на добавление нового элемента в массив чилдов и разбираться с ними всеми уже отдельно. Но для начала неплохо бы понять что за язык, откуда он взялся и какие еще конструкции в нем могут присутствовать. Неплохо бы еще понимать - зачем все это.
 

weregod

unserializer
Откуда такой больной исходный формат? Не верю, что нет альтернативы.
Если хотите нормального результата - пишите конечный автомат. Тем более, что структура регулярная.
Подозреваю, что задача преподом поставлена.
 

ksnk

прохожий
Подозреваю, что задача преподом поставлена.
Очень вероятно... Входящий формат еще можно попытаться повоображать - накой нужен. Кто-то таким образом вручную что-то описывает, обязательно вручную, оттого и формат плавающий. А вот исходящий, примерно в том же виде, уже некуда воткнуть.
 
Сверху