Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP - Recursively set each array element's key to the value of a child element when given the childs key name

I'll start by showing a non-recursive example

Non- recursive example

$given_key_name = 'site_id';

$rows[] = array(
    'site_id' => '0',
    'language_id' => '1',
    'name' => 'sitename',
    'description' =>'site desc',
);

$results = array();
foreach($rows as $row){
    $key_value = $row[$given_key_name];
    unset($row[$given_key_name]);
    $results[$key_value] = $row;
}

//  OR This method is faster than the forloop

$results = array_combine(array_column($rows, $given_key_name),$rows);
foreach($results as &$row){
    unset($row[$given_key_name]); 
}

$results Equals

$results[0] = array( 
    'language_id' => '1',
    'name' => 'sitename',
    'description' =>'site desc',
);

Simple, the key name has been set to the value of the given child element. But I would like to be able to nest and unnest by using multiple key names.

Example

$given_key_names = array('site_id', 'language_id');

In this case the required result would be.

$results[0][1] = array( 'name' => 'sitename', 'description' =>'site desc', );

Explanation

The first keys value has been used as the first key in the $results array and a new empty array is created as its value. $results[0] = array();

As there is a second key, its value is set as a key to the newly created array and its value is also a new empty array. $results[0][1] = array();

As there are no more keys the empty array is populated with the remaining values

$results[0][1] = array( 'name' => 'sitename', 'description' =>'site desc', );

so i would like two functions nestByKeyNames and unNestByKeyName.

NestByKeyNames Function

Christians Answer solves this

function nestByKeyNames($arrayRows, $arrayKeyOrder){

    // Prepare resulting array
    $arrayResult = array();

    // Cycle the input array
    foreach($arrayRows as $someRow){
        // We will acomplish this using references
        $current = &$arrayResult;

        // get the current level
        foreach($arrayKeyOrder as $someKey){
            $someValue = $someRow[$someKey];
            if(isset($current[$someValue])){
                $current = &$current[$someValue];
            }else{
                $current[$someValue] = array();
                $current = &$current[$someValue];
            }
            unset($someRow[$someKey]);
        }
        $current = $someRow;
    }
    return $arrayResult;
}

I wonder whether array_combine(array_column($arrayRows, $key_name),$arrayRows); could be used instead of the first iteration to improve performance?

This represents the results from a mysql select statement.

$rows = array(
    array(
        'pri_id_1' =>1,
        'pri_id_2' =>1,
        'pri_id_3' =>1,
        'col_1' =>'col_value_1111',
        'col_2' =>'col_value_1112',
        'col_3' =>'col_value_1113',
    ),
    array(
        'pri_id_1' =>1,
        'pri_id_2' =>2,
        'pri_id_3' =>1,
        'col_1' =>'col_value_1211',
        'col_2' =>'col_value_1212',
        'col_3' =>'col_value_1213',
    ),
    array(
        'pri_id_1' =>1,
        'pri_id_2' =>3,
        'pri_id_3' =>1,
        'col_1' =>'col_value_1311',
        'col_2' =>'col_value_1312',
        'col_3' =>'col_value_1313',
    )
);

$keyNames = array('pri_id_1','pri_id_2','pri_id_3');
$results = nestByKeyNames($rows, $keyNames);

The following output is produced

Array
(
    [1] => Array
        (
            [1] => Array
                (
                    [1] => Array
                        (
                            [col_1] => col_value_1111
                            [col_2] => col_value_1112
                            [col_3] => col_value_1113
                        )

                )

            [2] => Array
                (
                    [1] => Array
                        (
                            [col_1] => col_value_1211
                            [col_2] => col_value_1212
                            [col_3] => col_value_1213
                        )

                )

            [3] => Array
                (
                    [1] => Array
                        (
                            [col_1] => col_value_1311
                            [col_2] => col_value_1312
                            [col_3] => col_value_1313
                        )

                )

        )

)

UnNestByKeyNames Function

unNestByKeyNames should be able to take this output and convert it back to the original array providing that it is given the key names. Christians Answer did not solves this as it doesnt work with a single key name but i can tell its very close.

function unNestByKeyNames($arrayRows, $arrayKeyOrder){


}

$keyNames = array('pri_id_1','pri_id_2','pri_id_3');
$rows = unNestKeyNames($results, $keyNames);

My true goal is to take the results from MYSQL SELECT statement and populate a form using the same naming convention by using nestByKeyNames.

e.g.

<input name="rows[1][1][1][col_1]" value="col_value_1" />

and then convert the $_POST request back into an MYSQL INSERT statement by first using unNestByKeyNames.

From this i will create an INSERT statement.

function returnValues($rows, $column_names){

    //validation has been removed for clarity

    $implode_VALUES = array();

    foreach ($rows as $key => $row) {
        $implode_row_values = array();
        foreach ($column_names as $column_name) {
            $implode_row_values[$column_name] = $row[$column_name];
        }
        if($implode_row_values){
            $implode_VALUES[] = " ('" . implode("','", $implode_row_values) . "') ";
        }
    }
    return $implode_VALUES;
}

$implode_COLUMNS = array('pri_id_1','pri_id_2','pri_id_3','col_1','col_2','col_3');

$implode_VALUES = returnValues($rows, $implode_COLUMNS)

$sql = "INSERT INTO table_name (" . implode(',', $implode_COLUMNS) . ") VALUES " . implode(',', $implode_VALUES);

The final result should produce a sql statement like so

INSERT INTO table_name (pri_id_1,pri_id_2,pri_id_3,col_1,col_2,col_3) VALUES ('1','1','1','NEW_value_1111','NEW_value_1112','NEW_value_1113') , ('1','2','1','NEW_value_1211','NEW_value_1212','NEW_value_1213') , ('1','3','1','NEW_value_1311','NEW_value_1312','NEW_value_1313')

What I Would like

  • Improvement suggestions on the 'nestByKeyNames' function (performance/ does it have bugs)
  • help producing 'unNestByKeyNames' code
  • Improvement suggestions on my '$rows to mysql INSERT' approach
  • examples of how i could make any of my code perform better.
like image 433
TarranJones Avatar asked Nov 23 '15 17:11

TarranJones


1 Answers

This was trickier than I first imagined but I believe I have a messy solution.

First of all, this is the data I am working with. dumpr is a custom function that formats var_dump better.

$arrayKeyOrder = array(
    'site_id',
    'language_id'
);

$original = array(
    array(
        'site_id' => '0',
        'language_id' => '1',
        'name' => 'sitename',
        'description' =>'site desc',
    ),

    array(
        'site_id' => '0',
        'language_id' => '2',
        'name' => 'sitename',
        'description' =>'site desc',
    ),

    array(
        'site_id' => '1',
        'language_id' => '1',
        'name' => 'sitename',
        'description' =>'site desc',
    ),

    array(
        'site_id' => '2',
        'language_id' => '1',
        'name' => 'sitename',
        'description' =>'site desc',
    ),
);

$zipped = doZip($original, $arrayKeyOrder);
$unzipped = unZip($zipped, $arrayKeyOrder);

dumpr($original);
dumpr($zipped);
dumpr($unzipped);

Here is the zip and unzip functions:

function doZip($arrayRows, $arrayKeyOrder){

    // Prepare resulting array
    $arrayResult = array();

    // Cycle the input array
    foreach($arrayRows as $someRow){
        // We will acomplish this using references
        $current = &$arrayResult;

        // get the current level
        foreach($arrayKeyOrder as $someKey){
            $someValue = $someRow[$someKey];
            if(isset($current[$someValue])){
                $current = &$current[$someValue];
            }else{
                $current[$someValue] = array();
                $current = &$current[$someValue];
            }
            unset($someRow[$someKey]);
        }

        $current = $someRow;
    }

    return $arrayResult;
}


function unZip($arrayRows, $arrayKeyOrder, $arrayValues = array(), $depth = 0){

    $arrayResults = array();

    if($depth < count($arrayKeyOrder)){
        foreach($arrayRows as $key => $value){
            $arrayValues[$depth] = $key;
            $arrayResults[] =  unZip($value, $arrayKeyOrder, $arrayValues, $depth + 1);
        }
    }else{
        $extra = array_combine($arrayKeyOrder, $arrayValues);
        $result = array_merge($extra, $arrayRows);
        return $result;
    }

    if($depth == 0){
        for($i = 1; $i < count($arrayKeyOrder); $i++){
            $arrayResults = call_user_func_array('array_merge', $arrayResults);
        }        
    }

    return $arrayResults;
}

And finally, here is the output. let me know if this is what you were asking for and if it worked OK on a larger data-set.

/vhost/virtual/sandbox/public/index.php:54
array(4) {
    [0] = array(4) {
        [site_id] = string(1) "0"
        [language_id] = string(1) "1"
        [name] = string(8) "sitename"
        [description] = string(9) "site desc"
    }
    [1] = array(4) {
        [site_id] = string(1) "0"
        [language_id] = string(1) "2"
        [name] = string(8) "sitename"
        [description] = string(9) "site desc"
    }
    [2] = array(4) {
        [site_id] = string(1) "1"
        [language_id] = string(1) "1"
        [name] = string(8) "sitename"
        [description] = string(9) "site desc"
    }
    [3] = array(4) {
        [site_id] = string(1) "2"
        [language_id] = string(1) "1"
        [name] = string(8) "sitename"
        [description] = string(9) "site desc"
    }
}

/vhost/virtual/sandbox/public/index.php:55
array(3) {
    [0] = array(2) {
        [1] = array(2) {
            [name] = string(8) "sitename"
            [description] = string(9) "site desc"
        }
        [2] = array(2) {
            [name] = string(8) "sitename"
            [description] = string(9) "site desc"
        }
    }
    [1] = array(1) {
        [1] = array(2) {
            [name] = string(8) "sitename"
            [description] = string(9) "site desc"
        }
    }
    [2] = array(1) {
        [1] = array(2) {
            [name] = string(8) "sitename"
            [description] = string(9) "site desc"
        }
    }
}

/vhost/virtual/sandbox/public/index.php:56
array(4) {
    [0] = array(4) {
        [site_id] = int(1) 0
        [language_id] = int(1) 1
        [name] = string(8) "sitename"
        [description] = string(9) "site desc"
    }
    [1] = array(4) {
        [site_id] = int(1) 0
        [language_id] = int(1) 2
        [name] = string(8) "sitename"
        [description] = string(9) "site desc"
    }
    [2] = array(4) {
        [site_id] = int(1) 1
        [language_id] = int(1) 1
        [name] = string(8) "sitename"
        [description] = string(9) "site desc"
    }
    [3] = array(4) {
        [site_id] = int(1) 2
        [language_id] = int(1) 1
        [name] = string(8) "sitename"
        [description] = string(9) "site desc"
    }
}
like image 190
Christian Avatar answered Nov 08 '22 11:11

Christian