문제

Let's say I have the following string

my $val = "3.4 -22.352 4.0"

The goal is to extract each decimal number by itself. There can be any number of spaces on each side or in between. It is also important to make sure that there is exactly 3 numbers present, and no other junk. I have something like this, but it doesn't work:

my @parts = ($val =~ /((\s*[-+]?\d{1,3}\.\d{1,3}\s*)){3}/)

if (scalar(@parts) == 3) {
    print "Validated!\n";

    for my $i (@parts) {
        print "$i\n";
    }
}

For some reason I get the last one twice.

도움이 되었습니까?

해결책

Each capturing group gets you only one value, even if you apply a quantifier on it. If you want 3 values you have to repeat the capturing group 3 times. For example:

my $num = qr/[-+]?\d{1,3}\.\d{1,3}/;
my @nums = $val =~ /^\s*($num)\s+($num)\s+($num)\s*$/;

if(@nums){
    print "Valid, and no need to check the number of elements.\n";
}

다른 팁

Instead of fighting regular expressions, use split and looks_like_number:

use warnings;
use strict;
use Scalar::Util qw(looks_like_number);

my $val = "3.4 -22.352 4.0";
my @parts = split /\s+/, $val;
if (scalar(@parts) == 3) {
    my $ok = 0;
    for (@parts) {
        $ok++ if looks_like_number($_);
    }
    if ($ok == 3) {
        print "Validated!\n";
        for my $i (@parts) {
            print "$i\n";
        }
    }    
}

There are several issues here:

1) If you want three and only three numbers, you should anchor the start (^) and end ($) of the line in the regex.

2) Why are there two sets of parentheses? As written the second pair are redundant.

3) When you have a regex, the number of values returned are usually counted by the left parentheses (unless you use ?: or some other modifier). In this example, you have two, so it only returns two values. Because of the redundant parentheses, you get the same values twice each.

You have two sets of parens, so two values are returned. Both sets surround the same part of the regex, so both values will be the same.


Validating and extracting at not necessarily possible to do at the same time.

Doing it in two steps, extracting first, is quite simple:

my @nums = split ' ', $val;
die "Invalid\n" if @parts != 3;
for (@nums) {
   die "Invalid\n" if !/^[-+]?[0-9]{1,3}\.[0-9]{1,3}\z/;
}

You can do it in one step, but there's some redundancy involved:

my $num_pat = qr/[-+]?[0-9]{1,3}\.[0-9]{1,3}/;
my @nums = $val =~ /^($num_pat)\s+($num_pat)\s+($num_pat)\z/
   or die "Invalid\n";
my $val = "3.4 -22.352 4.0";
my $length = $val =~ s/((^|\s)\S)/$1/g;
#determines the number of tokens

if ($length == 3)
{
     while($val=~/([-+]?[0-9]{1,3}\.[0-9]{1,3})/g)
     {
         print "$1\n";
     }
}

The /g allows you to loop through the string and extract values conforming to your restrictions (one at a time). It will do this until all of the "tokens" matching your pattern are iterated through. I like this solution because it's concise and doesn't require you to create an auxiliary array. It's also a more general answer than using three extractions in one's regex.

With Regex Only

This will require 3 chunks of numbers delimited by space each number will be popluated into it's respective group.

(?:(?:^)([-]?[0-9]*?[.]?[0-9]*?)(?=\s))(?:(?:\s)([-]?[0-9]*?[.]?[0-9]*?)(?=\s))(?:(?:\s)([-]?[0-9]*?[.]?[0-9]*?)(?=$)) enter image description here

Example

PHP Code Example: 
<?php
$sourcestring="3.4 -22.352 4.0";
preg_match_all('/(?:(?:^)([-]?[0-9]*?[.]?[0-9]*?)(?=\s))(?:(?:\s)([-]?[0-9]*?[.]?[0-9]*?)(?=\s))(?:(?:\s)([-]?[0-9]*?[.]?[0-9]*?)(?=$))/i',$sourcestring,$matches);
echo "<pre>".print_r($matches,true);
?>

$matches Array:
(
    [0] => Array
        (
            [0] => 3.4 -22.352 4.0
        )

    [1] => Array
        (
            [0] => 3.4
        )

    [2] => Array
        (
            [0] => -22.352
        )

    [3] => Array
        (
            [0] => 4.0
        )

)
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top