Что является наиболее читаемым регулярным выражением для извлечения второго слова без пробелов из строки через запятую? - PullRequest
4 голосов
/ 26 марта 2012

У меня есть массив строк вида:

@source = (
     "something,something2,third"
    ,"something,something3   ,third"
    ,"something,something4"
    ,"something,something 5" # Note the space in the middle of the word
);

Мне нужно регулярное выражение, которое будет извлекать второе из слов, разделенных запятыми, НО без завершающих пробелов, помещая эти вторые слова в массив.

@expected_result = ("something2","something3","something4","something 5");

Какой самый читаемый способ достижения этого?

У меня есть 3 возможности, ни одна из которых не кажется оптимальной для удобочитаемости:

  1. Чистое регулярное выражение, а затем захватить $ 1

    @result = map { (/[^,]+,([^,]*[^, ]) *(,|$)/ )[0] } @source;
    
  2. Разделить запятыми (это не CSV, поэтому анализ не требуется), затем обрезать:

    @result = map { my @s = split(","), $s[1] =~ s/ *$//; $s[1] } @source;
    
  3. Поместите разбиение и обрезку во вложенные map s

    @result = map { s/ *$//; $_ } map { (split(","))[1] } @source;
    

Какой из них лучше? Любая другая, более читаемая альтернатива, о которой я не думаю?

Ответы [ 7 ]

6 голосов
/ 26 марта 2012

Используйте именованные группы захвата и присвойте имена подшаблонам с (DEFINE), чтобы значительно улучшить читаемость.

#! /usr/bin/env perl

use strict;
use warnings;

use 5.10.0;  # for named capture buffer and (?&...)

my $second_trimmed_field_pattern = qr/
  (?&FIRST_FIELD) (?&SEP) (?<f2> (?&SECOND_FIELD))

  (?(DEFINE)
    # The separator is a comma preceded by optional whitespace.
    # NOTE: the format simple comma separators, NOT full CSV, so
    # we don't have to worry about processing escapes or quoted
    # fields.
    (?<SEP>  \s* ,)

    # A field stops matching as soon as it sees a separator
    # or end-of-string, so it matches in similar fashion to
    # a pattern with a non-greedy quantifier.
    (?<FIELD> (?: (?! (?&SEP) | $) .)+ )

    # The first field is anchored at start-of-string.
    (?<FIRST_FIELD>  ^  (?&FIELD))

    # The second field looks like any other field. The name
    # captures our intent for its use in the main pattern.
    (?<SECOND_FIELD> (?&FIELD))
  )
/x;

В действии:

my @source = (
     "something,something2,third"
    ,"something,something3   ,third"
    ,"something,something4"
    ,"something,something 5" # Note the space in the middle of the word
);

for (@source) {
  if (/$second_trimmed_field_pattern/) {
    print "[$+{f2}]\n";

    #print "[$1]\n";  # or do it the old-fashioned way
  }
  else {
    chomp;
    print "no match for [$_]\n";
  }
}

Вывод:

[something2]
[something3]
[something4]
[something 5]

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

sub make_second_trimmed_field_pattern {
  my $sep = qr/
    # The separator is a comma preceded by optional whitespace.
    # NOTE: the format simple comma separators, NOT full CSV, so
    # we don't have to worry about processing escapes or quoted
    # fields.

    \s* ,
  /x;

  my $field = qr/
    # A field stops matching as soon as it sees a separator
    # or end-of-string, so it matches in similar fashion to
    # a pattern with a non-greedy quantifier.
    (?:
        # the next character to be matched is not the
        # beginning of a separator sequence or
        # end-of-string
        (?! $sep | $ )

        # ... so consume it
        .
    )+  # ... as many times as possible
  /x;

  qr/ ^ $field $sep ($field) /x;
}

Используйте его как в

my @source = ...;  # same as above

my $second_trimmed_field_pattern = make_second_trimmed_field_pattern;
for (@source) {
  if (/$second_trimmed_field_pattern/) {
    print "[$1]\n";
  }
  else {
    chomp;
    print "no match for [$_]\n";
  }
}

Вывод:

$ perl5.8.8 prog
[something2]
[something3]
[something4]
[something 5]
6 голосов
/ 26 марта 2012

Из этих возможностей, я думаю, # 2 - самый ясный, хотя я думаю, что я бы немного скорректировал его, чтобы включить пробелы в split:

@result = map { my @s = split(/ *(?:,|$)/); $s[1] } @source;

(В этом отношении я мог бы написать /[ ]*(?:,|$)/ с классом неактивных символов, просто чтобы было немного более очевидно, что количественно определяет *.)

Отредактировано, чтобы добавить: Ой, у меня раньше была глупая ошибка, когда это не убирало бы конечный пробел из чего-то вроде "foo, bar ". Теперь, когда я исправил эту ошибку, результат не так хорош и прост, и я больше не уверен, рекомендую ли я выше!

1 голос
/ 27 марта 2012

Я бы сделал:

my @result = map /,(.*?[^\s,])\s*(?:,|\z)/, @source;
0 голосов
/ 26 марта 2012

Мне нравится ваш вариант 3 лучше всего.Он четко определяет различные шаги, которые вы предпринимаете для «выбора» правильных данных, и какие дополнительные манипуляции вы затем выполняете с ними.

Так что, если критерием является «удобочитаемость»: вариант 3 - явный победитель.

0 голосов
/ 26 марта 2012

Лучше всего обрабатывать с помощью split, который удалит все пробелы, предшествующие запятым для вас. Просто разделите на /\s*(?:,|$)/, возьмите второй элемент списка, и вся тяжелая работа сделана. Полный код выглядит так

use strict;
use warnings;
use feature 'say';

my @source = (
  "something,something2,third",
  "something,something3   ,third",
  "something,something4",
  "something,something 5  ",
);

my @result = map { (split /\s*(?:,|$)/)[1] } @source;

say "|$_|" for @result;

OUTPUT

|something2|
|something3|
|something4|
|something 5|
0 голосов
/ 26 марта 2012
@result = map { /,([^,]*?)\s*(?:,|$)/ } @source;
0 голосов
/ 26 марта 2012

Регулярные выражения, как правило, не читаются в общепринятом смысле.Они более аналогичны сложной математической формуле.Если удобочитаемость, подумайте об использовании комментариев (регулярное выражение поддерживает встроенные комментарии).

На этой странице представлен хороший обзор: http://www.perl.com/pub/2004/01/16/regexps.html

Пример с этой страницы:

$_ =~ m/^                         # anchor at beginning of line
          The\ quick\ (\w+)\ fox    # fox adjective
          \ (\w+)\ over             # fox action verb
          \ the\ (\w+) dog          # dog adjective
          (?:                       # whitespace-trimmed comment:
            \s* \# \s*              #   whitespace and comment token
            (.*?)                   #   captured comment text; non-greedy!
            \s*                     #   any trailing whitespace
          )?                        # this is all optional
          $                         # end of line anchor
         /x;                        # allow whitespace

Если подумать, если читаемость является проблемой, почему, черт возьми, вы используете Perl?;)

...