Regex для проверки поля фиксированной длины с упакованным пространством - PullRequest
3 голосов
/ 11 августа 2009

Скажем, у меня есть текстовый файл для анализа, который содержит контент фиксированной длины:

123jackysee        45678887
456charliewong     32145644
<3><------16------><--8---> # Not part of the data.

Первые три символа - это идентификатор, затем имя пользователя из 16 символов, а затем 8-значный номер телефона.

Я хотел бы написать регулярное выражение для сопоставления и проверки ввода для каждой строки, которую я придумаю:

(\d{3})([A-Za-z ]{16})(\d{8})

Имя пользователя должно содержать 8-16 символов. Но ([A-Za-z ]{16}) также будет соответствовать нулевому значению или пробелу. Я думаю о ([A-Za-z]{8,16} {0,8}), но он обнаружит более 16 символов. Есть предложения?

Ответы [ 7 ]

8 голосов
/ 11 августа 2009

Нет, нет, нет, нет! : -)

Почему люди настаивают на том, чтобы пытаться упаковать столько функций в один оператор RE или SQL?

Мое предложение, сделайте что-то вроде:

  • Убедитесь, что длина составляет 27.
  • Извлеките три компонента в отдельные строки (0-2, 3-18, 19-26).
  • Убедитесь, что первое совпадение "\d{3}".
  • Убедитесь, что второе соответствует "[A-Za-z]{8,} *".
  • Убедитесь, что третий соответствует "\d{8}".

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

Даже что-то вроде этого поможет:

def isValidLine(s):
    if s.len() != 27 return false
    return s.match("^\d{3}[A-za-z]{8,} *\d{8}$"):

Не обманывайте себя, думая, что это чистый код Python, на самом деле это PaxLang, мой собственный проприетарный псевдокод. Надеюсь, это достаточно ясно, первая строка проверяет, что длина равна 27, а вторая соответствует данному RE.

Среднее поле автоматически составляет всего 16 символов из-за первой строки и того факта, что два других поля имеют постоянную длину в RE. RE также гарантирует, что за восемью или более альфами следует правильное количество пробелов.

Делать подобные вещи с одним RE было бы чудовищно, как:

^\d{3}(([A-za-z]{8} {8})
      |([A-za-z]{9} {7})
      |([A-za-z]{10} {6})
      |([A-za-z]{11} {5})
      |([A-za-z]{12}    )
      |([A-za-z]{13}   )
      |([A-za-z]{14}  )
      |([A-za-z]{15} )
      |([A-za-z]{16}))
      \d{8}$

Вы можете сделать это, проверив два отдельных RE:

^\d{3}[A-za-z]{8,} *\d{8}$
^.{27}$

но, поскольку последний является просто проверкой длины, он ничем не отличается от isValidLine() выше.

0 голосов
/ 09 сентября 2010

Вы можете использовать lookahead: ^(\d{3})((?=[a-zA-Z]{8,})([a-zA-Z ]{16}))(\d{8})$

Тестирование:

    123jackysee        45678887      Match
    456charliewong     32145644      Match
    789jop             12345678      No Match - username too short
    999abcdefghijabcde12345678       No Match - username 'column' is less that 16 characters
    999abcdefghijabcdef12345678      Match
    999abcdefghijabcdefg12345678     No Match - username column more that 16 characters
0 голосов
/ 11 августа 2009

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

#!/usr/bin/perl

use strict;
use warnings;

while ( <DATA> ) {
    chomp;
    last unless /\S/;
    my @fields = split;
    if (
        ( my ($id, $name) = $fields[0] =~ /^([0-9]{3})([A-Za-z]{8,16})$/ )
            and ( my ($phone) = $fields[1] =~ /^([0-9]{8})$/ )
    ) {
        print "ID=$id\nNAME=$name\nPHONE=$phone\n";
    }
    else {
        warn "Invalid line: $_\n";
    }
}

__DATA__
123jackysee       45678887
456charliewong    32145644
678sdjkfhsdjhksadkjfhsdjjh 12345678

А вот и другой способ:

#!/usr/bin/perl

use strict;
use warnings;

while ( <DATA> ) {
    chomp;
    last unless /\S/;
    my ($id, $name, $phone) = unpack 'A3A16A8';
    if ( is_valid_id($id)
            and is_valid_name($name)
            and is_valid_phone($phone)
    ) {
        print "ID=$id\nNAME=$name\nPHONE=$phone\n";
    }
    else {
        warn "Invalid line: $_\n";
    }
}

sub is_valid_id    { ($_[0]) = ($_[0] =~ /^([0-9]{3})$/) }

sub is_valid_name  { ($_[0]) = ($_[0] =~ /^([A-Za-z]{8,16})\s*$/) }

sub is_valid_phone { ($_[0]) = ($_[0] =~ /^([0-9]{8})$/) }

__DATA__
123jackysee        45678887
456charliewong     32145644
678sdjkfhsdjhksadkjfhsdjjh 12345678

Обобщая:

#!/usr/bin/perl

use strict;
use warnings;

my %validators = (
    id    => make_validator( qr/^([0-9]{3})$/ ),
    name  => make_validator( qr/^([A-Za-z]{8,16})\s*$/ ),
    phone => make_validator( qr/^([0-9]{8})$/ ),
);

INPUT:
while ( <DATA> ) {
    chomp;
    last unless /\S/;
    my %fields;
    @fields{qw(id name phone)} = unpack 'A3A16A8';

    for my $field ( keys %fields ) {
        unless ( $validators{$field}->($fields{$field}) ) {
            warn "Invalid line: $_\n";
            next INPUT;
        }
    }

    print "$_ : $fields{$_}\n" for qw(id name phone);
}

sub make_validator {
    my ($re) = @_;
    return sub { ($_[0]) = ($_[0] =~ $re) };
}

__DATA__
123jackysee        45678887
456charliewong     32145644
678sdjkfhsdjhksadkjfhsdjjh 12345678
0 голосов
/ 11 августа 2009

@ OP, не каждая проблема нуждается в регулярном выражении. вашу проблему довольно просто проверить. в зависимости от того, какой язык вы используете, они будут иметь какие-то встроенные строковые функции. используй их. следующий минимальный пример сделан в Python.

import sys
for line in open("file"):
    line=line.strip()
    # check first 3 char for digit
    if not line[0:3].isdigit(): sys.exit()
    # check length of username.
    if len(line[3:18]) <8 or len(line[3:18]) > 16: sys.exit()
    # check phone number length and whether they are digits.
    if len(line[19:26]) == 8 and not line[19:26].isdigit(): sys.exit()
    print line
0 голосов
/ 11 августа 2009

Я бы использовал предложенное вами регулярное выражение с небольшим дополнением:

(\d{3})([A-Za-z]{3,16} {0,13})(\d{8})

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

0 голосов
/ 11 августа 2009

Хмм ... В зависимости от конкретной версии Regex, которую вы используете, подумайте:

(?P<id>\d{3})(?=[A-Za-z\s]{16}\d)(?P<username>[A-Za-z]{8,16})\s*(?P<phone>\d{8})

Заметьте, что на 100% это сработает, и я использовал экранирующий символ пробела вместо реального пробела - я сам нервничаю только из-за символа пробела, но вы, возможно, захотите быть более ограничительным.

Посмотрите, работает ли это. Я сам являюсь лишь посредником в RegEx, поэтому могу ошибаться.

Проверьте синтаксис именованных групп для вашей версии RegEx a) существует и b) соответствует стандарту, который я использовал выше.

EDIT:

Просто чтобы расширить то, что я пытаюсь сделать (извините, что у вас кровоточат глаза, Пакс!) Для тех, у кого нет большого опыта в RegEx:

(?P<id>\d{3})

Эта попытка будет соответствовать именованной группе захвата - 'id' - длиной в три цифры. Большинство версий RegEx позволяют использовать именованные группы захвата для извлечения значений, с которыми вы сопоставляете. Это позволяет выполнять проверку и захват данных одновременно . Разные версии RegEx имеют немного разные синтаксисы для этого - проверьте http://www.regular -expressions.info / named.html для более подробной информации о вашей конкретной реализации.

(?=[A-Za-z\s]{16}\d)

Оператор? = Является прогнозным. Это будет смотреть вперед для следующих шестнадцати символов и вернет true, если все они являются буквами или пробельными символами И, за которыми следует цифра. Оператор lookahead имеет нулевую длину, поэтому он ничего не возвращает. Ваша строка RegEx продолжает идти с того места, где стартовал Lookahead. Проверьте http://www.regular -expressions.info / lookaround.html для более подробной информации о перспективах.

(?P<username>[A-Za-z]{8,16})\s*

Если оглядка проходит, то мы продолжаем считать с четвертого символа. Мы хотим найти от восьми до шестнадцати символов, за которыми следуют ноль или более пробелов. «Или больше» на самом деле безопасно, поскольку мы уже убедились в том, что до следующей цифры не может быть более шестнадцати символов.

Наконец,

(?P<phone>\d{8})

Это должно проверить восьмизначный номер телефона.

Я немного нервничаю, что это точно не сработает - ваша версия RegEx может не поддерживать синтаксис именованной группы или синтаксис упреждающего просмотра, к которому я привык.

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

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

0 голосов
/ 11 августа 2009

Предполагается, что вы имеете в виду регулярное выражение perl и если вы разрешаете '_' в имени пользователя:

perl -ne 'exit 1 unless /(\d{3})(\w{8,16})\s+(\d{8})/ && length == 28'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...