Question

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;
?>
Était-ce utile?

La solution 2

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.

Autres conseils

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 = '';
    }
}

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',
)
---
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top