Peter’s blog ✴ Week 287 ✴ 16 September 2024

THE WEEKLY CHALLENGE
Strong and valid

The Perl Camel

Task 2

Valid number

You are given a string, $str. Write a script to find if it is a valid number.

Conditions for a valid number:

  • An integer number followed by an optional exponent.
  • A decimal number followed by an optional exponent.
  • An integer number is defined as an optional sign '-' or '+' followed by digits.

A decimal number is defined as an optional sign '-' or '+' followed by one of the following:

  • Digits followed by a dot '.'.
  • Digits followed by a dot '.' followed by digits.
  • A dot '.' followed by digits.

An exponent is defined as 'e' or 'E' followed by an integer number.

Examples


Example 1
Input: $str = "1"
Output: true

Example 2
Input: $str = "a"
Output: false

Example 3
Input: $str = "."
Output: false

Example 4
Input: $str = "1.2e4.2"
Output: false

Example 5
Input: $str = "-1."
Output: true

Example 6
Input: $str = "+1E-8"
Output: true

Example 7
Input: $str = ".44"
Output: true

Analysis

No doubt someone will come up with one regular expression that does all of that, but I'm not that clever.

I have a main regular expression that more or less does it, but is too generous in two ways:

  • It allows almost anything after the [Ee]
  • It allows '.' as well as '5.' or '.5'

Each of these is then dealt with in separate regexes.

Perl Weekly’s review

from PW issue 687

Dealing the task in multiple stages is the coolest approach and easy to follow. DIY tool on top is bonus, you would definitely love to play.

Try it 

Try running the script with any input:



example: 123.456e-99

Script


#!/usr/bin/perl

# Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge

use v5.26;    # The Weekly Challenge - 2024-09-16
use utf8;     # Week 287 - task 2 - Valid number
use warnings; # Peter Campbell Smith
binmode STDOUT, ':utf8';

my @tests;

say qq[\nValid numbers:\n      \$str     Valid?\n     -----     ------];
@tests = ('23', '-23', '+23', '34.1', '-34.1', '+34.1', '.7', '-.7', '23e0', '23e-1', '23E+2', '-23e10', '+34.5e-10');
valid_number($_) for @tests;

say qq[\nInvalid numbers:\n      \$str     Valid?\n     -----     ------];
@tests = ('two', '23-', '23.1.2', 'e', 'e1', '34e1.2', '34e++6', '.e5', '+.e5', '123E', '.', '.e6' );
valid_number($_) for @tests;

sub valid_number {
    
    my ($str, $str2, $valid, $a, $b);
    
    $str = $_[0];
    $valid = 0;
    
    # basic match              $a------------  $b-----
    if (($a, $b) = $str =~ m|^([-+]?\d*\.?\d*)([Ee]?.*)$|) {

        # but if [Ee] is present it must be followed by optional sign and 1+ digits
        $valid = 1 if ($b eq '' or ($a ne '' and $b =~ m|^[Ee][-+]?\d+$|));
    }
    
    # decimal point must be preceded or followed by a digit (or both)
    if ($valid and $str =~ m|\.|) {
        $valid = 0 unless ($str =~ m|\d\.| or $str =~ m|\.\d|);
    }
    
    printf("%10s %10s\n", $str, ('false', 'true')[$valid]);
}

9 lines of code

Output from script


Valid numbers:
      $str     Valid?
     -----     ------
        23       true
       -23       true
       +23       true
      34.1       true
     -34.1       true
     +34.1       true
        .7       true
       -.7       true
      23e0       true
     23e-1       true
     23E+2       true
    -23e10       true
 +34.5e-10       true

Invalid numbers:
      $str     Valid?
     -----     ------
       two      false
       23-      false
    23.1.2      false
         e      false
        e1      false
    34e1.2      false
    34e++6      false
       .e5      false
      +.e5      false
      123E      false
         .      false
       .e6      false

 

Any content of this website which has been created by Peter Campbell Smith is in the public domain