Filter array by keys and populate new multi-dimensional array from mutated keys and original values

Viewed 95

I need to extract data from elements with keys that start with foo. from the below array:

[
    'name' => 'Bar',
    'location' => 'Baz',
    'foo.2021-02-01' => '50000.00',
    'foo.2021-03-01' => '50000.00',
    'foo.2021-04-01' => '50000.00',
    'foo.2021-05-01' => '',
]

After identifying qualifying keys, I need to create a new indexed array of associative rows using the date substring from the original keys like so:

[
    ['date' => '2021-02-01', 'value' => '50000.00'],
    ['date' => '2021-03-01', 'value' => '50000.00'],
    ['date' => '2021-04-01', 'value' => '50000.00'],
    ['date' => '2021-05-01', 'value' => ''],
]

I've been able to extract the keys like so:

$keys = array_keys($theData[0]);
foreach ( $keys as $key ) {
    if ( preg_match( '/foo.*/', $key ) ) {
        $line = explode('.', $key);
        $item[]['name'] = $line[1];
    }
}

but I'm losing the values.

I then tried looping through the array manually and rebuilding the desired outcome, but the keys will change so I don't know how future-proof that would be.

Is there a wildcard approach I can take to achieve this?

4 Answers

You almost had it:

<?php
$theData = [
    'name' => 'Bar',
    'location' => 'Baz',
    'foo.2021-02-01' => '50000.00',
    'foo.2021-03-01' => '50000.00',
    'foo.2021-04-01' => '50000.00',
    'foo.2021-05-01' => ''
];

$item = [];

// No need for array_keys(), foreach() can already do this
foreach( $theData as $key => $value )
{
    // check if the key starts with foo.
    // Regular expressions are heavy; if you'd like then substitute with:
    // if ( substr( $key, 0, 4 ) === 'foo.' )
    if ( preg_match( '/^foo\\./', $key ) )
    {
        // foo. is 4 chars long so substring from the fourth index till the end
        $item[] = [
            'date' => substr( $key, 4 ),
            'value' => $value
        ];
    }
}

var_dump( $item );

Output:

array(4) {
  [0]=>
  array(2) {
    ["date"]=>
    string(10) "2021-02-01"
    ["value"]=>
    string(8) "50000.00"
  }
  [1]=>
  array(2) {
    ["date"]=>
    string(10) "2021-03-01"
    ["value"]=>
    string(8) "50000.00"
  }
  [2]=>
  array(2) {
    ["date"]=>
    string(10) "2021-04-01"
    ["value"]=>
    string(8) "50000.00"
  }
  [3]=>
  array(2) {
    ["date"]=>
    string(10) "2021-05-01"
    ["value"]=>
    string(0) ""
  }
}

A simple loop, checking for the key starting with foo. and then a little code to replace foo. in the key with nothing will do the trick

If you have PHP8 or >

$arr = [
    'name' => 'Bar',
    'location' => 'Baz',
    'foo.2021-02-01' => '50000.00',
    'foo.2021-03-01' => '50000.00',
    'foo.2021-04-01' => '50000.00',
    'foo.2021-05-01' => ''
];

$new = [];
foreach ($arr as $k => $v){
    if ( str_starts_with( $k , 'foo.' ) ) {
        $new[] = ['date' => str_replace('foo.', '', $k), 'value' => $v];
    }
}
print_r($new);

RESULT

Array
(
    [0] => Array
        ([date] => 2021-02-01, [value] => 50000.00)
    [1] => Array
        ([date] => 2021-03-01, [value] => 50000.00)
    [2] => Array
        ([date] => 2021-04-01, [value] => 50000.00)
    [3] => Array
        ([date] => 2021-05-01, [value] => )
)

Alternatively, for PHP versions prior to PHP8

$new = [];
foreach ($arr as $k => $v){
    if ( strpos( $k , 'foo.') !== FALSE && strpos( $k , 'foo.') == 0 ) {
        $new[] = ['date' => str_replace('foo.', '', $k), 'value' => $v];
    }
}

Using str_starts_with and explode

$arr = [];
foreach ($theData as $k => $v){
  if (str_starts_with($k, "foo."))
    $arr[] = ["date" => explode(".", $k)[1], "value" => $v];
}
var_dump($arr);

sscanf() is an ideal function to call which will both check for qualifying strings and extract the desired trailing date value. It doesn't use regex, but it does require a placeholder %s to target the date substring. If a given string doesn't qualify, no element is pushed into the result array.

Code: (Demo) (without compact())

$result = [];
foreach ($array as $key => $value) {
    if (sscanf($key, 'foo.%s', $date)) {
        // $result[] = ['date' => $date, 'value' => $value];
        $result[] = compact(['date', 'value']); 
    }
}
var_export($result);

If you remove the optional technique of using compact(), this solution makes fewer function calls than all other answers on this page.

I would probably only use regex if I wanted to strengthen the validation for qualifying key strings. (Demo)

$result = [];
foreach ($array as $key => $value) {
    if (preg_match('~^foo\.\K\d{4}-\d{2}-\d{2}$~', $key, $m)) {
        $result[] = ['date' => $m[0], 'value' => $value]; 
    }
}
var_export($result);
Related