0

I need to split an age into its components where the age is expressed as eg. 27y5m6w2d or any combination of those values. eg. 2w3d or 27d or 5y2d etc. Result has to be up to 4 variables $yrs, $mths, $wks and $days containing the appropriate numeric values.

I can do it with this code but am hoping there is something more efficient:

$pos = strpos($age, 'y');
if ($pos !== false)
   list($yrs, $age) = explode('y', $age);
$pos = strpos($age, 'm');
if ($pos !== false)
   list($mths, $age) = explode('m', $age);
$pos = strpos($age, 'w');
if ($pos !== false)
   list($wks, $age) = explode('w', $age);
$pos = strpos($age, 'd');
if ($pos !== false)
   list($days, $age) = explode('d', $age);

If you have a suggestion, please run it in a 10,000 iteration loop and advise the results. The code above runs in an average of 0.06 seconds for 10,000 iterations. I use this code to test:

<?php
$startTime = microtime(true);

// code goes here

echo "Time:  " . number_format(( microtime(true) - $startTime), 4) . " Seconds<br>"; 
echo 'y='.$yrs.' m='.$mths.' w='.$wks.' d='.$days;
?>

3 Answers 3

1

I would suggest using regular expression matching with preg_match_all() like this:

$input = '2w3d'
$matches = array();
preg_match_all('|(\d+)([ymwd])|', $input, $matches, PREG_SET_ORDER);

Where the output array $matches will hold all matches in this pattern:

$matches = array(
    // 0 => matched string, 1 => first capture group, 2 => second capture group 
    0 => array( 0 => '2w', 1 => '2', 2 => 'w' ),
    1 => array( 0 => '3d', 1 => '3', 2 => 'd' )
);

EDIT :
Process this result like so:

$yrs = $mths = $wks = $days = 0;
foreach($matches as $match) {
    switch($match[2]) {
        case 'y': $yrs = (int)$match[1]; break;
        case 'm': $mths = (int)$match[1]; break;
        case 'w': $wkss = (int)$match[1]; break;
        case 'd': $days = (int)$match[1]; break;
    }
}


EDIT 2: Hacky alternative
Makes use of character comparison and takes around 0.4 seconds for 100.000 iterations.

$number = '';
for($j = 0, $length = strlen($input); $j < $length; $j++) {
    if($input[$j] < 'A') {
        $number .= $input[$j];
    } else {
        switch($input[$j]) {
            case 'y': $yrs = (int)$number; break;
            case 'm': $mths = (int)$number; break;
            case 'w': $wks = (int)$number; break;
            case 'd': $days = (int)$number; break;
        }
        $number = '';
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Does not produce the 4 required variables containing the numeric values.
@user2605793 added another code fragment that should fit your needs.
@user2605793 Sorry, I forgot to add the PREG_SET_ORDER flag to preg_match_all() to change the match array layout, now it should work.
Runs in roughly 0.1 seconds versus 0.06 seconds for my original code for 10000 iterations.
For me at least execution time greatly varies and I would say that for such a tiny code 10.000 cycles are yet not enough. I added a perfectly "hacky" algorithm that will take ~0.4 seconds for 100.000 iterations. 10.000 iterations vary between 0.035 and 0.08 seconds.
|
0

I'd go with the following approach.

$age = '27y5m6w2d';

// Split the string into array of numbers and words
$arr = preg_split('/(?<=[ymdw])/', $age, -1, PREG_SPLIT_NO_EMPTY);

foreach ($arr as $str) 
{
    $item = substr($str, -1); // Get last character
    $value = intval($str);    // Get the integer

    switch ($item) 
    {
        case 'y':
            $year = $value;
            break;        
        case 'm':
            $month = $value;
            break;
        case 'd':
            $day = $value;
            break;
        case 'w':
            $week = $value;
            break;
    }
}

The code is more readable and slightly faster. I tested this with 10000 iterations and it took around just 0.0906 seconds.

6 Comments

Those leave the y/m/w/d in place. I need just the numeric parts. They also do not yield the variables $yrs, $mths etc. and it is not simple to extract them because $arr[0] can be either years, months, weeks or days so I am back to square 1.
I appreciate the help thanks but I is that going to be significantly more efficient? Far less readable code.
I put your code and my code in a 10,000 iteration loop and ran them each 10 times against 3y6m2w7d. My code ran in an average of 0.06 seconds. Your code ran in an average of 0.2 seconds, more than 3 times as slow.
@user2605793: Yes, I agree. The previous solution was really a hack — I've updated the answer now.
@user2605793: Mind sharing the code you used for benchmarking?
|
0

You don't need to bloat your code with a lookup array or a switch block.

Your input strings are predictably formatted (in order), so you can write a single regex pattern containing optional capture groups at each of the expected "units" in the input string. While using named capture groups provides some declarative benefit, it also bloats the regex pattern and the output array -- so I generally don't prefer to use them.

You will notice that there is a repeated format in the regex: (?:(\d+)unitLetter)?. This makes modifying/extending the pattern very simple. All of those subpatterns make the targeted substring "optional" and the final letter in the subpattern distinguishes what unit of time is being isolated.

In this case, the match output structure goes:

  • [0] : the full string match (we don't need it)
  • [1] : yrs
  • [2] : mths
  • [3] : wks
  • [4] : days

Code: (Demo)

$strings = ['27y5m6w2d', '1m1w', '2w3d', '999y3w', '27d', '5y2d'];
foreach ($strings as $string) {
    preg_match('~(?:(\d+)y)?(?:(\d+)m)?(?:(\d+)w)?(?:(\d+)d)?~', $string, $m);
    var_export([
        'yrs' => $m[1] ?? '',
        'mths' => $m[2] ?? '',
        'wks' => $m[3] ?? '',
        'days' => $m[4] ?? '',
    ]);
    echo "\n---\n";
}

Output:

array (
  'yrs' => '27',
  'mths' => '5',
  'wks' => '6',
  'days' => '2',
)
---
array (
  'yrs' => '',
  'mths' => '1',
  'wks' => '1',
  'days' => '',
)
---
array (
  'yrs' => '',
  'mths' => '',
  'wks' => '2',
  'days' => '3',
)
---
array (
  'yrs' => '999',
  'mths' => '',
  'wks' => '3',
  'days' => '',
)
---
array (
  'yrs' => '',
  'mths' => '',
  'wks' => '',
  'days' => '27',
)
---
array (
  'yrs' => '5',
  'mths' => '',
  'wks' => '',
  'days' => '2',
)
---

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.