Ключевые слова:perl, hash, (найти похожие документы)
From: jkeks <jkeks@mail.ru.>
Newsgroups: email
Date: Mon, 20 Sep 2004 18:21:07 +0000 (UTC)
Subject: Перебор сложной структуры в Perl
Сравнение сложных структур данных для ret WebOS
Теперь когда написан уже непростой алгоритм сравнения сложных структур,
потребовался снова алгоритм, однако упрощщенный до нельзя.
Задача стоит следующая:
Необходимо взять из базы записи и отобразить их в WEB, для этого нам
необходимо грубо перебрать полностью всю структуру, любой сложности.
Как же сделать это ?
Мы понимаем, что у нас может быть безкончно сложная структура.
Что такое сложная структура ?
это любой элемент простой структуры может собержать ссылку на другую
структуру, которая в свою очередь может содержать аналогичные элементы.
Например массив хэшей - это сложная структура, двухмерный массив - это сложная
структура (с точки зрения Perl)
Существует 2 принципиальных способа перебора всей структуры:
1. С накоплением ссылок
2. Без накопления ссылок
Рассмотрим оба варианта и оценим их возможности:
Накопление ссылок
Любой элемент находится на определенном уровне.
Например $a[0][1][2][3]
У нас имеется 4 уровня, пятым является конечная запись (не ссылка)
Итак мы сначала перебираем только первый уровень и накапливаем ссылки в
массиве, полностью перебрав первый уровень переходим к массиву ссылок
иперебираем его, ..
причем технология такая, что у нас циклический массив, мы читаем сверху
массива, а пишем вниз, при этом как бы все уровни немного теряются и у нас нет
вообще необходимости следить за ними, однако можно манипулировать двумя
массивами для сохранения информации о уровне, что нам впринципе необходимо!
Есть вариант манипулирования всего одним массивом с сохранением информации об
уровне, в этом случае нам необходимо сохранять не только адрес ссылки, но и
номер уровня.
Без накопления ссылок
Нас теперь не интересуют уровни, мы начинаем перебор, ищя первый конечный
элемент, грубо говоря первым будет элемент $a[0][0]
он всегда присутствует, после него мы автоматически ищем в ветке
$a[0][1]..
если там ссылка на другой массив, то мы перебираем его
$a[0][1][0]
Наконец мы добрались до второго конечного элемента
$a[0][1][0][0]
после него может следовать
$a[0][1][0][1]
$a[0][1][0][2]
$a[0][1][0][3]
$a[0][1][1]
$a[0][1][2]
$a[0][2][0][0]
Это напоминает последовательность при инкременте в бинарной системе счисления
00
01
10
11
При этом нам надо запоминать на каком месте мы столкнулись с ссылкой.
Это будет похоже на дополнительный массив примерно следующего содержания:
@a=(0,2,0,0) для последнего варианта в примере выше.
По нему мы сможем всегда быть в курсе куда нам двигаться дальше.
Какой вариант будет являться более нужным для нас ?
Вариант с сохранением ссылок реализован в СУБД blogs.pm по той причине, что он
охватывает изначально всю ширину массива, тем самым больше вероятность того
что если в существующих структурах есть различия, то они найдутся быстрее.
Хотя однозначно это сложно определить для любых ситуаций.
Вариант без сохранения ссылок дает нам более четкую информацию о текущем
элементе, т.е. тот самый массив, где хранится текущий проверяемый элемент.
Эта информация для нас важна тем что мы должны ее преобразовать , а после
суметь преобразовать обратно.
т.е. для нас важен процесс определения нужной ветки структуры.
На скорость тут ориентироваться сложно, потому как в любом случае будет
перебираться вся структура.
Отсюда вывод, что метод полного охвата структуры - еще эффективнее засчет того
что мы манипулируем всего лишь одним циклическим массивом, и изменения в нем
происходят лишь при нахождении или извлечении ссылок.
В случае же без накопления ссылок мы при любом продвижении по структуре меняем
элементы массива.
Чем еще выгоден для нас второй вариант без накопления ссылок, тем что мы едем
от начала структуры и до конца, т.е. начиная от нулевых элементов, до эНных в
каждой структуре - это сохраняет порядок слежования.
Если же нам обхватывать всю ширину структуры за один раз, то мы имеем порядок
лишь на определенном уровне, и то разделение уровней будет представлять из
себя анализ массивов ссылок, что сводит плюсы метода на нет.
Рассмотрим подробнее метод без использования накопления ссылок
Прмер структуры:
0-0
1
1-0
1-0
1
Пускай эта структура будет состоять только из массивов
Тогда процесс сканирования всей структуры будет происходить так:
$a[0] - сслыка ?
да
$a[0][0] - ссылка ?
нет
Что-то делаем
$a[0][1] - ссылка ?
нет
что-то делаем
$a[0][] - больше элементов в этой ветке нет
$a[1] - ссылка ?
да
$a[1][0] - ссылка ?
нет
что-то делаем
$a[1][1] - ссылка ?
да
$a[1][1][0] - ссылка ?
нет
что-то делаем
$a[1][1][1] - ссылка ?
нет
что-то делаем
$a[1][1][] - больше элементов в этой ветке нет
$a[1][] - больше элементов в этой ветке нет
$a[] - больше элементов в этой ветке нет
Это все ясно, однако возникает вопрос: Ведь кроме массивов есть хэши и скаляры
? как их сохранять в массиве и отличать ?
Есть два свпособа:
1.Хранить тип данных нечетными элементами
2.Хранить тип в отдельном массиве в аналогичной позиции
Как правильнее поступить ?
Дело в том, что мы будем образаться к тем и другим данным одновременно, и
разделение на двамассива вызовет излишний процесс инициализации массива, так
же возможно что данные будут распологать не в непосредственной близости, что
увеличит нагрузку на кэш оперативной памяти и вызовет дополнительные нагрузки.
Поэтому целесообразнее хранить данные в одном массиве.
Хотя это не так и критично все.
И если уж говорить о скорости обработки, то нам не на Perl надо писать.
Однако все же мы учтем эту небольшую особенность.
Вопрос о том как восстановить структуру ?
Немного вдумавшись в задачу мы приходим к мысли о том, что мы должны суметь
отправить данные, и после их корректирования получить их обратно в ту же самую
позицию.
$a[1][0]
или
$a{test}[6]{test2}
или какую угодно другую позицию.
Выбранный нами способ сканирования дерева позволяет нам легко отследить
позицию, однако на этом проблеммы не кончаются.
У нас возникает вопрос, о типе данных, ведь у нас их три: массив хэш и скаляр.
А так же сам алгоритм восстановления структуры.
т.е.
мы отправили клиенту масив @a
он изменил элемент $a[0]{2}[3]
Теперь нам надо изменить в массиве этот же элемент и сохранить уже измененный.
Для нас проблемматично динамически дополнять сложную структуру. Но давайте не
будем торопиться с идентификацией элементов пришедших от клиента. Для начала
мы должны усвоить каким образом мы все же будем сканировать структуру, принцип
мы поняли а теперь ближе к реализации.
У нас будет массив примерно с таким содержнием:
@a=(1,a,3,h,,s);
четные элементы - это значения, нечетные - тип (определяющий что это хэш (h),
массив(a) или скаляр(s)), тогда все встает на свои места и становиться понятно
что 1 - это элемент массива под номером 1.
3 - это элемент хэша, который идентифицируется именем '3'
undef - (неопределенность) ничего не обозначает, т.к тип у нас у этого
элемента - скаляр, то соответственно это значение существует ради принципа
сохранения структуры массива, определяющего текущщее положение сканирования.
Ну вот мы заварили эту кашу, дальше у нас есть два способа составления полного
пути к любому элементу - это посредством манипулирования ссылками и
посредством составления кода, который мы затем поместим в eval.
Из примера @a=(1,a,3,h,,s);
у нас должна получиться цепочка:
$a[1]{3}
(скалярная ссылка - по сути пустой элемент, с ним ничего не остается делать
как только переходить по его ссылке)
Именно строка $a[1]{3} для нас нужна для получения содержимого.
Одним из самых простых и понятных вариантов является следующий код, который
преобразует массив в строку, в конечном итоге показывается значение элемента сложной
структуры:
$m[1]{3}='assle';
@b=(1,'a','3','h',,'s');
$s='$m';
for ($i=0;$i<$#b;$i+=2)
{
if ($b[$i+1] eq 'a') {$s.='['.$b[$i].']'}
if ($b[$i+1] eq 'h') {$s.='{'.$b[$i].'}'}
}
print "s=$s\n";
$s='$a=\\'.$s;
eval $s;
print $$a;
Расставляем все по полочкам
Программа должна делать следующее:
сканировать всю сложную структуру и выводить в качестве результата все
значения элементов с их координатами в структуре (по принятым нами правилами)
Этот вариант правильно сканирует любую сложную структуру , состоящую из одним
лишь массивов.
$a[0]=1;
$a[1][0]='as1s';
$a[1][1]='as2s';
$a[1][2]='as3s';
$a[2]=2;
$a[3][0]='Zs1s';
$a[3][1]='Zs2s';
$a[3][2]='Zs3s';
# @a - сложная структура
# @b - массив с текущей позицией в дереве
# $z - значение текущего элемента
@b=(0,'a');
$f=0;
while ($f==0)
{
$s='$a';
for ($i=0;$i<$#b;$i+=2)
{
if ($b[$i+1] eq 'a') {$s.='['.$b[$i].']'}
}
$s='$z=\\'.$s;
eval $s;
$z=$$z;
if (!defined $z)
{
pop@b;pop@b;
if ($#b>0) {$b[$#b-1]++;}
else {$f=1}
}
else
{
if (ref($z) eq 'ARRAY')
{
push @b, 0; push @b, 'a';
}
if (!ref($z))
{
$b[$#b-1]++;
print "#######data: $z\n";
}
}
}
print "\nEND!\n";
С массивами код приведен лишь для того чтобы показать насколько это несложно,
добавление хэшей и скаляров, сделает код несколько сложнее, как нам бы
правильнее перемещаться по хэшу ?
Тут пожалуй можно предложить следующую технику, по идее хэш будет аналогичен
массиву и в массиве текущего положения будут указываться не имена ключей а номер
ключа в отсортированном хэше. Это позволит нам так же просто
инкрементировать/декрементировать знаение, тип же останется 'h' (хэш)
А вот код который теперь уже перебирает любую структуру состоящую из массивов и
хэшей:
$a[0]=1;
$a[1]{'A'}='as1s';
$a[1]{'C'}='as2s';
$a[1]{'B'}='as3s';
$a[2]=2;
$a[3][0]='Zs1s';
$a[3][1]='Zs2s';
$a[3][2]='Zs3s';
# @a - сложная структура
# @b - массив с текущей позицией в дереве
# $z - значение текущего элемента
@b=(0,'a');
$f=0;
while ($f==0)
{
$s='$a';
for ($i=0;$i<$#b;$i+=2)
{
if ($b[$i+1] eq 'a') {$s.='['.$b[$i].']'}
if ($b[$i+1] eq 'h')
{
$ss='$zz=\\'.$s;
eval $ss;
$zz=$$zz;
@keys=sort keys %$zz;
$s.='{'.$keys[$b[$i]].'}'
}
}
$s='$z=\\'.$s;
eval $s;
$z=$$z;
if (!defined $z)
{
pop@b;pop@b;
if ($#b>0) {$b[$#b-1]++;}
else {$f=1}
}
else
{
if (ref($z) eq 'ARRAY')
{
push @b, 0; push @b, 'a';
}
if (ref($z) eq 'HASH')
{
push @b, 0; push @b, 'h';
print "z-HASH ref!\n";
}
if (!ref($z))
{
$b[$#b-1]++;
print "#######data: $z\n";
}
}
}
print "\nEND!\n";
И последнее, мы должны учесть так же и скаляры, хотя их использование вряд ли
несет хоть какую-то полезную нагрузку, у нас дело принципа, и вообще заранее мы
не можем предположить что и как мы захотим использовать в наших базах, поэтому
никаких ограничений не будет!
Perl имеет интересный алгоритм разименовывания, который еще надо понять, на самм
деле мне пришлось достаточно долго доходить до сути дела, но как потомоказалось
- ничего тут страшного нету.
Разименование происходит восновном за счет указания типа разименовываемых данных
перед непосредственной ссылкой.
Если $a='test' и $b=\$a , тогда мы можем обратиться к данным через $b вот так:
print $$b
Теперь надо учесть что у нас структура сложная и подобных ссылок может быть
безконечное множество
$a[3]='test';
$b=\$a[3];
$c=\$b;
$d=\$c;
$e[4]=\$d;
print $$$${$e[4]};
Но Кристинсен утверждает, что многие программисты используют боле правильную
форму, которая исключает недоразумения:
$a[3]='test';
$b=\$a[3];
$c=\$b;
$d=\$c;
$e[4]=\$d;
print ${${${${$e[4]}}}};
Впринципе можно и согласиться.. тогда мы делаем вывод, что каждая ссылка
добавляет слева ${ и справа }
Вот и все правило что было нужно для нас.
Есть еще одно, тип ссылки теперь для нас может быть двух видов: 'SCALAR' и 'REF'
Почему REF ?
Излазив все что можно было излазить я пришел к выводу, что этот тип
присваивается в том случае, когда элемент, на который ссылаются - является
ссылкой.
Однако специфику этого типа я так и не узнал.
По мере написания кода, выяснилось, что превращение массива в строку - можно
вынести в подпрограмму, которая вдобавок ко всему должна возвращать размер
массива и хэша, если задан не скаляр. Ох и непросто это.
Дело в том, что функция по определению должна возвращать содержимое текущего
элемента, а чтобы возвратить ширину текщего массива или хэша необходимо во
первых проверить,, не скаляр ли у нас, а если не так, то выбрать метод
определения ширины текущего типа данных.
Впрочем мы поступим немного хитро, в Perl нет нормального метода узнать ширину
хэша, для массива это мы можем сделать даже двумя спосабами.
print scalar @a;
print $#a+1;
Для хэша же нам придется немного постараться чтобы получить результат.
Существует достаточно много методов получить ширину хэша хитрыми способами.
1. Перебрать хэш в цикле
2. Интерполировать хэш как скаляр и результат пропарсить регекспом
3. Интерполировать хэш как массив
Насчет первого вы поняли, что цикл - это не для нас, когда можно сделать все
прощще.
Хэш в скалярном контексте возвращает примерно следующее:
$a{asd}='dsa';
print scalar %a;
1/8
Теперь регекспом вытаскиваем число элементов в хэше:
$a=~s/^(.+?)\/.+/$1/;
Диковато смотрится, да и впринципе такой метод сложно назвать правильным.
Но еще сложнее сказать что Perl правильно поступает, когда возвращает в
хэш скалярном контексте в виде строки, которая содержит число элементов и
количество зарезервированных блоков памяти для хэша, разделенных слэшем.
Но спорить тут не будешь, мы поступим самым последним способом интерпретируя хэш
как массив, на самом деле получится что мы получили чило элементов массива.
$a{asd}='dsa';
print $#{[%a]};
с этим тоже связано несколько замечаний.. 8)
На самом деле мы хотим получить номер последнего элемента (при рассмотрении
массивов это происходит именно так, а хэш мы рассматриваем как массив и поэтому
принцип мы не меняем.
Однако $#{[%a]} возвращает немного не то, во первых возвращается удвоенный
результат. Мы бы могли все разделить на 2 и все, однако первый элемент массива
всегда имеет нулевой номер (если это не измерено программой)
, поэтому деление приведет к дроби, если округлять до наибольшего, то код
получиться некрасивым, и можно даже сказать неточным, поэтому провернем
небольшую махинацию:
К результату $#{[%a]} прибавим единичку, разелим на два, и уже после вычтем
единичку обратно, и это будет правильно!
Тогда то что нам надо мы получим таким образом:
$a=($#{[%a]}+1)/2-1;
Теперь рассмотрим, как должен меняться синтаксис когда мы достаем ширину
массива/хэша:
Массив:
${$a[3][2]{ass}}[7]='MOTHER';
print $#{$a[3][2]{ass}}+1;
$ заменяется на $#
и последний элемент ([7]) в скобках удаляется.
С хэшем дела обстоят немного по другому:
${$a[3][2]{ass}}{zxc}='MOTHER';
${$a[3][2]{ass}}{xcv}='FU*KER';
$a=(($#{[%{$a[3][2]{ass}}]})+1);
print $a/2-1;
начальная $ заменяется на $#{[%
последние фигурные скобки заменяются на ]}
середина заменяется на $^%#*#^@(&#^%@$^&{{@#${{# что приводит к выигрышу в любой
лотарее. (шутка 8)
Вот и весь принцип того как достать ширину любого хэша или массива внутри
сложной структуры используя eval.
Код, выполняющий эту работу примерно следующий:
if ($b[$#b-1] eq 'a')
{
$ss=~s/^\$/\$#/;
$ss=~s/\[\d+\]$//;
$ss='$ss='.$ss;
eval $ss;
}
elsif ($b[$#b-1] eq 'h')
{
$ss=~s/^\$/\$#\{\[%/;
$ss=~s/\{[a-zA-Z0-9_]+\}$/]}/;
$ss='$ss='.$ss;
eval $ss;
$ss=($ss+1)/2-1;
}
else {undef $ss;}
Вернемся к теории.
Что должен делать наш код, если перебирая структуру мы найдем НЕ ССЫЛКУ ?
Вообще правильно будет рассказать вам о двух методах поиска, первый из них это
метод, когда мы анализируем структуры с учетом наталкивания на несуществующие
элементы. т.е. у нас есть массив из трех элементов, проверяя четвертый мы
приходим к выводу, что это конец массива. Этот метод я изначально реализовывал,
однако потом пришел к выводу, что это неправильно.
Есть более строгий путь, по которому сейчас я и иду, метод заключается в сканировании
ТОЛЬКО существующих элементов, благодаря чему я избавился от некоторой путаницы,
однако код от этого как мне кажется быстрее не стал. Да , за счет зканирования
несуществующих элементов можно выиграть в быстродействии, однако тут возникает
проблемма со ссылками на скаляры.
Ну и вообще это принципиально неправильный метод.
Итак , что же мы должны делать если встречаем в структуре конечным элементом -
НЕ ССЫЛКУ ?
Если это массив, необходимо узнать, а не последний ли элементы мы нашли ?
Не последний: Инкрементируем позицию в структуре.
Последний: Включается цикл от текущей позиции до начала структуры (т.е. начинаем
упрощать структуру как я писал по принципу декремента в двоичной системе), т.е.
каждая ротация цикла отсекает последний элемент, теперь последним становиться
предпоследний (если он существует) Если последний элемент - скаляр, то переходим
на следующую ротацию, если элемент массив, то сравниваем ширину массива с
текущей позицией, если есть куда инкрементировать, то инкрементироуем, если нет,
то следующая ротация.
Если нам попался хэш, опреация аналогична массиву, только по текущему значению
хэша вычисляется его позиция в отсортированном порядке, после чего уже позиция
играет роль текущщей позиции в массиве.
Хэши - самое слабое звено!
Но как нам написать такую программу ?
Чтобы нам не запутаться необходимо конретезировать задачу так чтобы было ясно
где какие блоки чем занимаются. А точнее нам необходимо написать ЗНР (Задание на
разработку)
Итак у нас изначальные входящие данные:
массив @b хранящий текущую позицию в структуре.
Структуру массива мы уже знаем: чередование пар позиция/тип
Типы хранятся в виде (всего 3 типа): скаляр 's', массив 'a' и хэш 'h'
Позиция:
Скаляр - всегда 0
Массив - индекс (цифра)
хэш - ключь, определяемый цифрой (при условии что ключи отсортированы по
алфавиту)
В результате работы блока долно происходить очередное изменение текущей позиции
в структуре. Т.е. должен измениться массив @b так чтобы он указывал на следующий
элемент.
Алгоритм перебора должен соблдать следующие условия:
массивы, как и хэши перебираются от 0 до последнего элемента (с возрастанием)
если какой либо элемент массива, скаляра или хэша является ссылкой, тогда
перебор переходит на тот элемент, куда эта ссылка указывает.
Соответственно перебор будет являться законченным, когда каждый четный элемент
массива @b будет максимальным.
Для облегчения задачи необходимо реализовать функцию возвращающую ширину
любого вложенного массива или хэша (такая функция уже описана выше)
Кроме того необходима дополнительная функция релизовывающая передвижение по
структуре с шагом 1. Функция должна выполняться независимо от любых условий.
Входящие данные для функции: @b
Выходящие данные: @b
С учетом всего вышесказанного пишем программу, мне пришлось значительно
переработать описанные выше функции, так что теперь код выглядит достаточно
массивным, но зато универсальным, И это окончательный вариант кода для перебора
любой сложной структуры:
-------------Код Программы
#perl/bin/perl -w
#${$a[3][2]{ass}}[7]='MOTHER';
#print $#{$a[3][2]{ass}}+1;
#${$a[3][2]{ass}}{zxc}='MOTHER';
#${$a[3][2]{ass}}{xcv}='FUCKER';
#$a=(($#{[%{$a[3][2]{ass}}]})+1);
#print $a/2-1;
$aaaaa='DOT';
$aaaa=\$aaaaa;
$aaa=\$aaaa;
$a[0]=1;
$a[1]{'A'}='as1s';
$a[1]{'E'}='as4s';
$a[2]=2;
$a[3][0]='Zs1s';
$a[3][1]='Zs2s';
$a[3][2]='Zs3s';
$a[3][3]=\$aaa;
$a[4]='Ludka';
#$a[3][4][2][5]='Zs3s';
#print $#{[@{$a[3]}]};
#$z=$#{$a[3][4][2]};
#print $z;
#__END__
# @a - сложная структура
# @b - массив с текущей позицией в дереве
# $z - значение текущего элемента
@b=(0,'a');
$f=0;
while ($f==0)
{
print "\n";
($z,$ss)=_Array2String();
# print "($z,$ss)=Array2Srting()\n";
# $z - содержимое текущего элемента
# $ss- количество элементов
# print "Begin _StepForward \@b=";
# print @b;
# print "\n";
# print "5.1 ref(z)=".ref($z)." z=$z\n";
if (!ref($z))
{
$f=0;
@c=@b;
$r=_StepForward();
if ($r eq 'end') {print "END of structure\n";$f=1;}
print "Element=$z\n" if $f!=1;
print "\@c=";
print @c;
print "\n";
}
# print "5.2 ref(z)=".ref($z)." z=$z\n";
if (ref($z) eq 'ARRAY')
{
#print "z-ARRAY ref!\n";
push @b, 0; push @b, 'a';
}
elsif (ref($z) eq 'HASH')
{
push @b, 0; push @b, 'h';
#print "z-HASH ref!\n";
}
elsif (ref($z) eq 'REF' || ref($z) eq 'SCALAR')
{
push @b, 0; push @b, 's';
#print "z-SCALAR ref!\n";
}
#if (ref($z) eq 'SCALAR') {push @b, 0; push @b, 'a';}
}
print "END!\n";
# передвигается по структуре на 1 шаг (найти следующий элемент - НЕ ССЫЛКУ
sub _StepForward
{
#print "Begin _StepForward \@b=";
#print @b;
#print "\n";
if ($f==1) {return}
if ($#b<=0) {return 'end';}
# print '1.$b[$#b]='.$b[$#b]."\n";
# print '2.$b[$#b-1]='.$b[$#b-1]."\n";
if ($b[$#b] eq 'a' || $b[$#b] eq 'h')
{
($sz,$ss)=_Array2String();
# если массив не перебран весь, то инкремент позиции
# print "3. ss=".$ss."\n";
# print '3.1 $b[$#b-1]='.$b[$#b-1].'<ss='.$ss."\n";
# print "3.2 In _StepForward \@b=";
# print @b;
# print "\n";
if ($b[$#b-1]<$ss) {$b[$#b-1]++;}#
else
{
# print "4.\@b=pop\n";
pop (@b);
pop (@b);
_StepForward();
#
}
}
elsif ($b[$#b] eq 's')
{
pop@b;pop@b;
_StepForward();
#
}
# print "3.3 In _StepForward \@b=";
# print @b;
# print "\n";
return;
}
# Парсит массив @b в строку, которую возвращает
sub _Array2String
{
$s='$a';
for (my $i=0;$i<$#b;$i+=2)
{
if ($b[$i+1] eq 'a') {$s.='['.$b[$i].']'}
elsif ($b[$i+1] eq 'h')
{
$ss='$zz=\\'.$s;
eval $ss;
$zz=$$zz;
@keys=sort keys %$zz;
$s.='{'.$keys[$b[$i]].'}'
}
elsif ($b[$i+1] eq 's') {$s='${'.$s.'}';}
}
$ss=$s;
# print "2.1 ss=$ss\n";
#print "b=".$b[$#b]."\n";
# подсчет количества элементов в $ss
if ($b[$#b] eq 'a')
{
# print "2.2 Array ss=$ss\n"; # $z= $#{$a[3]};
if ($#b>1)
{
$ss=~s/^\$/\$#\{\$/;
$ss=~s/\[\d+\]$/}/;
}
else
{
$ss=~s/^\$/\$#/;
$ss=~s/\[\d+\]$//;
}
$ss='$ss='.$ss;
#print "2.3 Array ss=$ss\n";
eval $ss;
#print "2.4 Array ss=$ss\n";
}
elsif ($b[$#b] eq 'h')
{
$ss=~s/^\$/\$#\{\[%\{\$/;
$ss=~s/\{[a-zA-Z0-9_]+\}$/}]}/;
$ss='$sz='.$ss;
eval $ss;
# print "2.3 hash sz=$sz\n";
$sz++;
$sz=$sz/2;
$sz--;
$ss=$sz;
}
else {undef $ss;}
$s='$sz=\\'.$s;
eval $s;
# print "2.9 ss=$ss\n";
$sz=$$sz;
return ($sz,$ss);
}
-------------конец программы
Ошибки с которыми мне пришлось столкнуться при написании кода:
Восновном это ошибки 0,1, однако еще я долго искал ошибку, которая оказалась
допущена из-за того что я использовал одинаковые имена в двух процедурах, и
последняя ошибка была число алгоритмической, Изначально мы ведь перебираем
массив, определение количества элементов для массива имеют разный синтаксис для
сложной и простой структуры, аналогично и для хэша, однако простого хэша мы не
используем впринципе,а простой массив используем, в программе я учитывал только
простой, пришлось добавить и сложный, а вот хэш я изначально учел сразу сложный.
Теперь наконец мы приступим к рассмотрению схемы, какнам нужно организовать Web
интерфейс для наших структур.Недаром в вышеприведенной прграмме выводяться не
только значения всех элементов сложной структуры, но и координата элемента в
структуре, по которой мы легко можем найти нужный элемент и заменить его.
Результат программки выглядит так:
Element=1
@c=0a
Element=as1s
@c=1a0h
Element=as4s
@c=1a1h
Element=2
@c=2a
Element=Zs1s
@c=3a0a
Element=Zs2s
@c=3a1a
Element=Zs3s
@c=3a2a
Element=DOT
@c=3a3a0s0s0s
Element=Ludka
@c=4a
END of structure
@c=
END!
Мы смело можем сказать, что У элемента 3a1a значение Zs2s
А у элемента 4a значение Ludka
Таким образом мы можем имя элемента назначить имени тэга XHTML.
<input type="text" name="4a" value="Ludka">
Хотя правильнее будет сделать так:
<textarea name="4a">Ludka</textarea>
Мы воспользуемся следующими модулями:
Storable (входит в стандартную поставку с Perl)
ret WebOS blogs.pm (Данный модуль работает исключительно на базе Storable, и
оганизует микро СУБД с использованием любых сложных структур)
ret WebOS можно скачать тут: http://ret.revda.biz (официальная страница проекта,
так же можно чего-нибудь поискать тут: http://jkeks.far.ru/ret )
Модули из этого проекта не обязательно устанавливать, они не имеют Си кода,
поэтому с ними легко работать, корректировать и использовать на различных
платформах.
В программе нам достаточно написать:
use blogs;
Чтобы создать нам свой раздел, вам необходимо создать его (например локально в
папке с инетрнет сайтом)
/projects/test
тогда нам нужно усвоить что теперь у нас есть раздел, который называется test
Давайте для примера запишем в нашь новый раздел сложную структуру и поскорее:
Структуру возьмем из нашего примера:
use blogs;
$aaaaa='DOT';
$aaaa=\$aaaaa;
$aaa=\$aaaa;
$a[0]=1;
$a[1]{'A'}='as1s';
$a[1]{'E'}='as4s';
$a[2]=2;
$a[3][0]='Zs1s';
$a[3][1]='Zs2s';
$a[3][2]='Zs3s';
$a[3][3]=\$aaa;
$a[4]='Ludka';
$b[1]=\@a;
($r)=blogs::BlogsAdd('test',\@b); # Все функции документированы в формате POD самом модуле
Теперь у нас данные храняться на диске, и начинается наша работа.
Нам надо прочитать данные, оформить их и отдаль пользователю на редактирование,
после чего записать данные которые пришли от пользваотеля обратно в базу.
Для этого нам было бы удобнее если сравнение сложных структур выглядело в виде
модуля с функцией, которой передаются имя раздела, координаты данных в базе, а
возвращается нам массив где четные элементы - это имя элемента (координата),
нечетные - значение.
Почему бы нам не воспользоваться хэшем ?
Потому что там теряется порядок, хотя это для нас не так и важно, однако
пользователю лучше видеть тот порядок, который он ожидает.
Немного доработав код я оформил его в виде функции модуля blogs.pm проекта ret
WebOS.
Синтаксис вызова функции следующий:
use blogs;
($r,$a) = blogs::BlogsGetVsPosition ('2kill',undef,undef,-1,-1);
print"Ready\n";
print join ("\n\n",@$a);
Да, теперь мы можем любую сложную структуру получить в виде простого массива с
координатами каждого элемента в сложной структуре. Кроме того теперь мы можем
делать запрос сразу по целой базе данных, используя регулярные выражения и
указывая позицию в базе, например нам надо получить данные и их координаты чисто
в последних 5-ти корневых записях.
Где каждая КОРНЕВАЯ запись представляет из себя сложную структуру.
Да что и говорить, я не хочу вам навязывать свой проект, и хочу добавить, что
вовсе не обязательно пользоваться ret WebOS чтобы делать сложные переборы
структур и менять конкретные элементы базы. Ведь мы работаем с обычной сложной
структурой, Любой массив! Вы можете просто воспользоваться модулем Storable для
сохранения ваших данных, а затем через WEB их менять при помощи описанного
алгоритма. Но полную красоту конечно можно ощутить используя модуль blogs.pm
Ну что же, нам-то надо двигаться дальше и теперь наша задача прикрутить Web
интерфейс и посмотреть что получиться.(хотя скажу вам по секрету вы сделали лишь
четверть работы)
Интерфейс я покажу вам как сделать используя ret WebOS.
Задача тут получается довольно тривиальная, я особо не напрягался и быстро
набросал следующий код:
use blogs;
use CGI::WebIn;
if ($IN{action} eq 'show')
{
if (!$IN{col}) { $IN{col}=-1; }
if (!$IN{padding}) { $IN{padding}=-1; }
if (!$IN{path}) { $IN{path}='2kill'; }
print 'ass'.$IN{col}. $IN{padding}.$IN{path};
# Show database
($r,$a)=blogs::BlogsGetVsPosition($IN{path},undef,undef,$IN{padding},$IN{col});
print '<h1>Interface to ret WebOS database</h1><br><br>';
print 'Result='.$r;
#$b=freeze($a);
#print $b;
$f='1';
print '<form>';
for (my $i=0;$i<$#$a;$i+=2)
{
($ff)=split/_/,$a->[$i];
if ($f ne $ff)
{
print '<br><hr><h3>record number '.$ff.'</h3>';
$f=$ff;
print '<br><input type=submit value="Изменить"></form><form
action=../cgi-bin/index.pl><input type=hidden name=action value=add>';
}
print 'Record position:<br>'.$a->[$i].'<br><textarea name='.$a->[$i].'
rows=5 cols=80>'.$a->[$i+1].'</textarea><br>';
}
print '<br><input type=submit value="Изменить"></form>';
print <<BE1;
<form action="../cgi-bin/blogsEdit.pl" method="POST">
<input type="hidden" name="action" value="show">
<h1>Interface to ret WebOS database</h1><br><br>
database path:<br>
<input name="path" type="text"><br>
padding:<br>
<input name="padding" type="text"><br>
how much records to show:<br>
<input name="col" type="text"><br>
<input value="Send Request" type="submit">
BE1
}
Суть кода в том, что он кладет на страницу данные из базы по умолчанию из
раздела 2kill берет одну, самую последнюю запись padding=-1 col=-1
Внизу есть формочка для того чтобы вести свои любые координаты в базе (тут не
реализован интерфейс для поиска с использованием регекспов или по определенных
ключам), после чего нажав на кнопочку вы увидите собственно те данные что
находили на указанном вами расположении.
Все данные находятся в полях тэга textarea.
А так же есть кнопочки для того чтобы отправить обратно измененные данные.
НУ вот собственно и все.
Итог.
На деле были реализованы 2 функции BlogsGetVsPosition и BlogsAddVsPosition,
которые занимаются вышеописанным,
Функции находятся в модуле blogs.pm, который является частью проекта ret WebOS
(ядро для интернет сайтов)
КОторое вы можете скачать отсюда: http://ret.revda.biz
Кроме того там же лежит скрипт blogsEdit.pl, который предоставляет Web интерфейс
к любым базам основанных на ret принципе.(да практически основанный на Storable)
Удачи и заходите к нам на сайты:
http://revda.bizhttp://jkeks.far.ru
jkeks.