tags:

views:

608

answers:

7

I have sets of 5, 6 and 7 digit numbers. I need them to be displayed in the 000/000/000 format. So for example:

12345 would be displayed as 000/012/345

and

9876543 would be displayed as 009/876/543

I know how to do this in a messy way, involving a series of if/else statements, and strlen functions, but there has to be a cleaner way involving regex that Im not seeing.

+16  A: 

sprintf and modulo is one option

function formatMyNumber($num)
{
    return sprintf('%03d/%03d/%03d',
                   $num / 1000000,
                  ($num / 1000) % 1000,
                   $num % 1000);
}
Adam Pierce
That works perfectly. Now I have to figure out why. hehe. Dont fully understand the code.
Yegor
The '%' operator gives you the modulo (that is, the remainder of a division). The sprintf function takes format strings - %03d means 'show an integer with 3 digits and left-pad with zeros'.
Adam Pierce
As for the math, try following it through with a calculator and you'll be able to see how the division shifts to the right and the mod operations mask off sets of 3 digits.
Dana the Sane
Damn, very very elegant solution. First thing that popped into my mind was substr and str_repeat, but yours beats mine by miles.
changelog
A: 

Here's how I'd do it in python (sorry I don't know PHP as well). I'm sure you can convert it.

def convert(num): #num is an integer
    a = str(num)
    s = "0"*(9-len(a)) + a
    return "%s/%s/%s" % (s[:3], s[3:6], s[6:9])

This just pads the number to have length 9, then splits the substrings. That being said, it seems the modulo answer is a bit better.

Claudiu
It's a good answer for Python, but in PHP you can't multiply strings, slice strings easily, or do the percent sign thing (forget what it's called) in the last line. Converting it to PHP would result in very ugly code. :P
yjerem
I know PHP was designed to be similar to Perl, so would 0x(9-length($a)) . $a work?
Brad Gilbert
+5  A: 
$padded = str_pad($number, 9, '0', STR_PAD_LEFT);
$split = str_split($padded, 3);
$formatted = implode('/', $split);
eyelidlessness
+1  A: 

OK, people are throwing stuff out, so I will too!

number_format would be great, because it accepts a thousands separator, but it doesn't do padding zeroes like sprintf and the like. So here's what I came up with for a one-liner:

function fmt($x) {
    return substr(number_format($x+1000000000, 0, ".", "/"), 2);
}
joelhardi
It's only a one-liner because it nests function calls, and it's not particularly readable or obvious what it does.
eyelidlessness
Well, I didn't claim it was one command! It computes an integer sum, uses number_format() to insert '/' as thousands separator, and strips the leading 2 characters. Readable except for number_format() having obscure syntax, and very little computation ... Adam's version is faster though.
joelhardi
+4  A: 

You asked for a regex solution, and I love playing with them, so here is a regex solution!
I show it for educational (and fun) purpose only, just use Adam's solution, clean, readable and fast.

function FormatWithSlashes($number)
{
    return substr(preg_replace('/(\d{3})?(\d{3})?(\d{3})$/', '$1/$2/$3',
            '0000' . $number),
            -11, 11);
}

$numbers = Array(12345, 345678, 9876543);
foreach ($numbers as $val)
{
    $r = FormatWithSlashes($val);
    echo "<p>$r</p>";
}
PhiLho
Amusingly, I'm a regex junkie, and I just couldn't bring myself to do this with a regex.
eyelidlessness
Indeed, but I love little challenges like those! ;-)
PhiLho
+1  A: 

Minor improvement to PhiLho's suggestion:

You can avoid the substr by changing the regex to:

function FormatWithSlashes($number)
{
    return preg_replace('/^0*(\d{3})(\d{3})(\d{3})$/', '$1/$2/$3',
            '0000' . $number);
}

I also removed the ? after each of the first two capture groups because, when given a 5, 6, or 7 digit number (as specified in the question), this will always have at least 9 digits to work with. If you want to guard against the possibility of receiving a smaller input number, run the regex against '000000000' . $number instead.

Alternately, you could use

substr('0000' . $number, -9, 9);

and then splice the slashes in at the appropriate places with substr_replace, which I suspect may be the fastest way to do this (no need to run regexes or do division), but that's really just getting into pointless optimization, as any of the solutions presented will still be much faster than establishing a network connection to the server.

Dave Sherohman
Good, I leave my solution as how not to do it... I made it evolve until it worked, and overlooked the simple final improvements! :-D
PhiLho
+1  A: 

This would be how I would write it if using Perl 5.10 .

use 5.010;

sub myformat(_;$){

  # prepend with zeros
  my $_ = 0 x ( 9-length($_[0]) ) . $_[0];

  my $join = $_[1] // '/'; # using the 'defined or' operator `//`

  # m// in a list context returns ($1,$2,$3,...)
  join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;

}

Tested with:

$_ = 11111;
say myformat;
say myformat(2222);
say myformat(33333,';');
say $_;

returns:

000/011/111
000/002/222
000;033;333
11111

Back-ported to Perl 5.8 :

sub myformat(;$$){
  local $_ = @_ ? $_[0] : $_

  # prepend with zeros
  $_ = 0 x ( 9-length($_) ) . $_;

  my $join = defined($_[1]) ? $_[1] :'/';

  # m// in a list context returns ($1,$2,$3,...)
  join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;

}
Brad Gilbert
NOTE: I have tested the Perl5.10 version, but I haven't tested the back-ported version.
Brad Gilbert