Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Perl: Общие вопросы > Разбиение по символу


Автор: JAPH 20.4.2007, 18:03
Вопрос следующего плана. Имеется строка
some operation; one else operation; operation with ';'; and with ` ;`; with "`;'" also possible (':)');
Необходимо её разбить по точке с запятой, но только если она вне кавычек. Таким образом, нужно получить
Код

my @s = (
    'some operation',
    ' one else operation',
    " operation with ';'",
    ' and with ` ;`',
    q/ with "`;'" also possible (':)')/
);

Автор: KSURi 20.4.2007, 20:26
Код

my $str=q{some operation; one else operation; operation with ';'; and with ` ;`; with "`;'" also possible (':)');};
print $_, "\n" foreach( split(/;[^']/, $str) );


Только почему-то получается, что and with ` ;`; захватывается не правильно =\
Код

-> some operation
-> one else operation
-> operation with ';'
-> and with `
->
-> with `;' also possible (':)');

Автор: JAPH 20.4.2007, 20:41
Потому что строка ;` соответствует шаблону ;[^']. Но даже если дополнить: ;[^'"`], это будет неправильно себя вести на строках вроде something ' containing ; in the center ' of the substring

Автор: nitr 20.4.2007, 22:09
JAPH, думаю все же сложный синтак. анализатор получится, на одних регулярках будет не просто сделать :(
хотя все же задачка решаема...
Код
#!perl
use strict; use warnings;

my $str = q{some operation; one else operation; operation with ';'; and with ` ;`; with "`;'" also possible (':)')};

my $begin = 0;
my $result;
my $tmp = '';

my $inner_token = '';
my $splitter = ';';
for (my $i = 0; $i <= length($str.$splitter); $i++) {
    my $s = substr($str.$splitter, $i, 1);
    if ($s =~ /['"`]/) {
        if ($begin == 1 and $inner_token eq $s) {
                $begin = 0;
                $inner_token = '';
        } else {
            $begin = 1;
            $inner_token = $s;
        }
    }
    if ($s eq $splitter and $begin == 0) {
        push @$result, $tmp;
        $tmp = '';
    } else {
        $tmp .= $s;
    }
}
print join("\n", @$result);

прошу оптимизацией самим заниматься ;) , но анализатор, имхо, "на лицо" smile
Если что, поясню код smile

Добавлено через 10 минут и 38 секунд
хех... вот тож самое smile но ближе к перл ;)
Код

...
foreach (split //, $str.$splitter) {
    if ($_ =~ /['"`]/) {
        if ($begin == 1 and $inner_token eq $_) {
                $begin = 0;
                $inner_token = '';
        } else {
            $begin = 1;
            $inner_token = $_;
        }
    }
    if ($_ eq $splitter and $begin == 0) {
        push @$result, $tmp;
        $tmp = '';
    } else {
        $tmp .= $_;
    }
}
...

Автор: Nab 21.4.2007, 03:29
У меня получилось вот такое:
Код

my $str = q{some operation; one else operation; operation with ';'; and with ` ;`; with "`;'" also possible (':)')};
print "--> $_\n" foreach( split(/([^;'"`]*(?:(?:'.*?'|".*?"|`.*?`)[^;]*)*);/, $str) );

Использовал альтернативы, потому как если заюзаем обратные ссылки, то они тоже попадут в вывод, что нам не нужно...
То есть вот такой код тоже работает, но он отдает и закавыченные места, и просто мусорит
Код

print "--> $_\n" foreach( split(/([^;'"`]*((['"`]).*?\3[^;]*)*);/, $str) );


Мой код тоже не идеален, он отдает пустое соответствие после каждого найденного :(, пока не знаю как избавиться...
То есть можно еще так проверить, чтобы получить только значимые результаты:
Код

my $str = q{some operation; one else operation; operation with ';'; and with ` ;`; with "`;'" also possible (':)')};
$_&&print "--> $_\n" foreach( split(/([^;'"`]*(?:(?:'.*?'|".*?"|`.*?`)[^;]*)*);/, $str) );


Автор: Nab 21.4.2007, 04:59
Это мы делали через split, если делать поудобней, то вот так получаеться самый супер вариант:
Код

my $str = q{some operation; one else operation; operation with ';'; and with ` ;`; with "`;'" also possible (':)')};
print "--> $1\n" while( $str =~ /([^;'"`]*((['"`]).*?\3[^;]*)*);/g );



Автор: nitr 21.4.2007, 19:00
Nab,  JAPH, привел пример:
something ' containing ; in the center ' of the substring, у меня твой не сработал :(

P.S.: но мне понравилось ;)

Автор: Nab 21.4.2007, 19:36
Цитата(nitr @  21.4.2007,  19:00 Найти цитируемый пост)
тем более JAPH, привел пример:something ' containing ; in the center ' of the substring, у меня твой не сработал :(

Конечно не сработает, потому как нет ни одного реального разделителя в виде ';'
нужно мой код исправить так:
Код

print "--> $1\n" while( $str =~ /([^;'"`]*((['"`]).*?\3[^;]*)*)/g );

то есть в нем тоже убрать точку с запятой, так как регексп основан на отрицании символов, то ничего страшного не случиться... она по идее даже лишней была.

Цитата(nitr @  21.4.2007,  19:00 Найти цитируемый пост)
Nab, все же мой код более рабочий, думаю сам понимаешь почему ;) 

Нет, nitr, не понимаю, хотя догадуюсь о чем ты.... 
Учитывая что эт я тебя познакомил с "конечными автоматами" smile (скромно. да?), то могу сказать по себе, что я их пытался применить везде где нужно было, и что печальнее, где не особенно нужно smile Они хороши в языках типа C, Delphi и т.д. где нет такой мощной поддержки регекспов. И хороши именно для синтаксического анализа... В перл же есть регулярки, которые в основе своей имеют теже конечные автоматы, просто они не видны пользователю.... И зачастую такие конструкции лучше реализовать на регекспах, чем строить конечный автомат... 
А вот применение конечных автоматов в более высокоуровневых конструкциях и организации машин состояний, очень даже полезная вещь, и ты это очень хорошо показал в топике по разбору внутренних таблиц в HTML. Так как он достаточно сложен чтоб разобрать его одной регуляркой, то там этот метод очень даже подходящий. 
Очень хорошо такая схема работает при паралельных работах, таких как POE, там даже методы определяющие интерфейс зовуться inline_state, object_state и т.д. хотя это немного не та машина состояний, которая идет отдельным компонентом...

Я эт говорю к тому, что не стоит пытаться применить новую технологию везде smile хорошего программиста отличает богатство опыта и арсенал оптимальных решений smile



Автор: nitr 21.4.2007, 19:46
Цитата(Nab @  21.4.2007,  19:36 Найти цитируемый пост)
Учитывая что эт я тебя познакомил с "конечными автоматами"

только с данным определением smile
на Си была практика в написании синтак. анализатора ;)

По поводу регеспов соглашусь, поэтому и писал
Цитата(nitr @  21.4.2007,  19:00 Найти цитируемый пост)
но мне понравилось ;)

smile

А так в перл, на один вопрос "тыщу" ответов ;) хе хе. Мой без регеспов (то что там есть - не считается smile ), подходит нетолько для перл, согласись, тоже своего рода + ;)

А так твой код мне по душе smile (просто я времени не тратил на него), они мне полностью не дались, я это понял, когда тут появились такие замечательные участники как ты и amg smile офигенная коллекция регеспов ;)

Автор: JAPH 21.4.2007, 20:40
Спасибо, набросали возможных решений. Я думаю в том же направлении, что и Nab. Так как получающийся список будет обрабатываться в foreach, хотелось бы уместить это в одну регулярку. В общем, пока решением считаю доработанный вариант Nab`а с учётом возможности существования строк вида some string ' with escaped \' quotes ; and ' so on...;. Пустые поля (как между ;;) должны сохраняться, новых появляться не должно. Так что эта строка будет разбита на всё, кроме последней точки с запятой, и нечто пустое. Итак, вариант:

Код

use strict; use warnings;
while (<>) {
    print "-->$_\n" foreach
/
(?:(?<=^)|(?<=;))
((?:(?>[^;"'`]*)
    |
    (?>'(?:(?>[^'\\]*)|\\.)*')
    |
    (?>"(?:(?>[^"\\]*)|\\.)*")
    |
    (?>`(?:(?>[^`\\]*)|\\.)*`)
)*)
(?=;|$)
/xg;
    print "\n"
}

Проверяем, улучшаем, придираемся. smile

Ну и в качестве нарушения всех правил форума вопрос по форуму. Как вы делаете надпись "Добавлено через 2 минуты"?

Автор: nitr 21.4.2007, 20:49
JAPH, smile)
Цитата(JAPH @  21.4.2007,  20:40 Найти цитируемый пост)
Ну и в качестве нарушения всех правил форума вопрос по форуму. Как вы делаете надпись "Добавлено через 2 минуты"?

форум думаю хранит сессию..., потом смотрит, если моя мессага последняя, то добавляет тег перед моей новой мессагой
[mergetime]время в секундах с 1900 (вроде)[/mergetime]
вроде так smile


Автор: Nab 21.4.2007, 22:11
Блин, JAPH, без пол-литры не разобраться smile я в справочник полез, а то кое-чего никогда не встречал в реальном коде, и тем более не юзал smile

Поначалу я тоже хотел сделать на конструкциях предварительного и постусловий, но мне показалось удобней указать что должно быть, чем нахождение разделителя...

Я вот поглядел, и вроде как разобрал твой код... и вижу что тебе опятьже приходиться делать альтернативу для каждой кавычки, спецом чтоб не создавать найденных ссылок $1, $2 и т.д.

Я опять пошел путем универсальности и предлагаю такой вариант:
Код

use strict; use warnings;
# использую while 
while (<>) {
# и не $_, а беру необходимое соответствие
print "-->$1\n" while
/
(?:(?<=^)|(?<=;)) # Эта строка то, чего мне в split не хватало, не добавляет пустые строки в вывод ( как один из эффектов )
((?:(?>[^;"'`]*) # это чистый текст без всяких кавычек и точек с запятой.
    |                    # или
    (?>(['"`])([^\2\\]|\\.)*?\2)  # возможные строки в кавычках с игнорированием екранированых
                                               # кстати очень оригинальное решение, я его тоже себе возьму на заметку, 
                                               # как проходить экранированные символы
)*)
(?=;|$)
/xg;

print "\n";


Автор: Nab 22.4.2007, 09:19
Кстати я вот подумал, что эт получаеться весьма мощная универсальная функция, разбиения строк по символу, с учетом закавыченных строк:
Код

sub split_s {
    my $s = shift;
    shift;
    my @result = ();
    push @result, $1 while /
        (?:(?<=^)|(?<=$s))
        (
            (?:
            (?>[^$s'"`]*)
         |
         (?>(['"`])([^\2\\]|\\.)*?\2)
            )*
        )
        (?=$s|$)
    /xg;

    return @result
}

my $str=q{some operation; one else operation; operation with ';'; and with ` ;`; with "`;'" also possible (':)');};
print "-->$_\n" foreach (split_s(';', $str));


Первым параметром принимает символ, а вторым строку, возвращает список полученных подстрок.
Символ должен быть один... в качестве разделителя больше одного не прокатит, ну или надо переписать и выделить разделитель как одну лексему....

Автор: JAPH 22.4.2007, 13:04
Nab, один махонький недочёт в строке 3: 
Код
local $_ = shift;

А так здорово

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)