2

I have this array:

$versions = [
    '1',
    '1.0.2.4',
    '1.1.0',
    '1.12.547.8'
];

And I want:

$versions = [
    '001',
    '001.000.002.004',
    '001.001.000',
    '001.012.547.008'
];

But, if I have this:

$versions = [
    '1',
    '1.12'
];

I want this:

$versions = [
    '01',
    '01.12'
];

In other words, each string chunk must have same len of max chunk length considering every chunk throughout the array.

Explanation:

I have an array with variable elements number. Each element is a string containing a variable amount of groups of digits -- each separated by a dot. Each digit group has a variable length. I want to pad each sequence of digits so that all array sub-groups have the same length as the max group length.

In the first example, max length is 3 (longest number: 547), so all groups of final array have 3 digits (001, 000, ...). In the second example, max length is 2 (12), so all groups have 2 digits (01, 01, 12).

My solution:

$max = max(
    array_map(
        'strlen',
         call_user_func_array(
            'array_merge',
            array_map(
                function($row) {
                    return explode( '.', $row );
                },
                $versions
            )
        )
    )
);
foreach($versions as &$version)
{
    $version = preg_replace(
        '/\d*(\d{' . $max . '})/',
        '\1',
        preg_replace(
            '/(\d+)/',
            str_repeat('0', $max) . '\1',
            $version
        )
    );
}

Explained:

$tmp = array_map(
    function($row) {
        return explode('.', $row );
    },
    $versions
);
$tmp = call_user_func_array('array_merge', $tmp);
$tmp = array_map( 'strlen', $tmp );
$max = max($tmp);

$tmp = preg_replace('/(\d+)/', str_repeat('0', $max ) . '\1', $version );
$version = preg_replace( '/\d*(\d{' . $max . '})/', '\1', $tmp);

Someone have any ideas on how to simplify the process?

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
LVA
  • 103
  • 6

2 Answers2

5

Well you can just simplify everything what you already did:

First we can implode() the array into a string and match all numbers with preg_match_all().

To then get the longest number we just use array_map() with strlen() as callback and get the longest number with max().

Then when you know your pad length you can go through your array with preg_replace_callback() and pad your numbers with str_pad().

Code:

<?php

    $versions = [ '1', '1.0.2.4', '1.1.0', '1.12.547.8' ];

    preg_match_all("/\d+/", implode(".", $versions), $m);
    $max = max(array_map("strlen", $m[0]));

    $new = preg_replace_callback("/(\d+)/", function($m)use($max){
        return str_pad($m[1], $max, "0", STR_PAD_LEFT);
    }, $versions);

    print_r($new);

?>
Rizier123
  • 58,877
  • 16
  • 101
  • 156
0

My modern take on this task resembles @Rizier123's approach, but should be a little more efficient because it doesn't it doesn't use a preg_match_all() call or iterated strlen() calls to determine the padding length.

My snippet uses a different native function to pad each encountered numeric substring, but it otherwise iterates the array of delimited values in the same way that @Rizier123's snippet does. My preg_replace_callback() technique uses PHP7.4's arrow function syntax and the pattern does not bother using a capture group.

Code: (Demo)

$pad = strlen(
    max(
        explode(
            '.',
            implode('.', $versions)
        )
    )
);

var_export(
    preg_replace_callback(
        '/\d+/',
        fn($m) => sprintf("%0{$pad}d", $m[0]),
        $versions
    )
);
mickmackusa
  • 43,625
  • 12
  • 83
  • 136