PHP: rename multidimensional array's keys - php

I have a multidimensional array with strings as keys. I want to perform a function (to manipulate the strings) on those keys and then write to a new array (i.e. leave the original array unchanged).
Example:
$oldArr = array(
"foo_old" => array("moo_old" => 1234, "woo_old" => 5678);
"bar_old" => array("car_old" => 4321, "tar_old" => 8765);
);
Becomes:
$newArr = array(
"foo_new" => array("moo_new" => 1234, "woo_new" => 5678);
"bar_new" => array("car_new" => 4321, "tar_new" => 8765);
);
This is just an example, the actual array has more levels/dimensions. Oh and my function doesn't replace "_old" with "_new", again, just an example.
I hope I made some sense, thanks in advance!

Edit: I added a function for printing out the changed array. You may include the code on a website and it will show the result. New edited code:
// array initialisation
oldArr = array();
$subArr1 = array();
$subArr2 = array();
$subArr1["moo_old"]=1234;
$subArr1["woo_old"]=5678;
$subArr2["car_old"]=4321;
$subArr2["tar_old"]=8765;
$oldArr["foo_old"]=$subArr1;
$oldArr["bar_old"]=$subArr2;
$oldArr; // make a copy of the array
// function which replaces recursivly the keys of the array
function renameArrayKeys( $oldArr ) {
$copyArr = $oldArr;
if( is_array( $oldArr) && count( $oldArr ) ) {
foreach ( $oldArr as $k => $v ) {
unset($copyArr[$k]); // removes old entries
$newKey = str_replace( '_old', '_new', $k );
if( is_array( $v ) ) {
$copyArr[ $newKey ] = renameArrayKeys( $v );
}
else {
$copyArr[ $newKey ] = $v;
}
}
return $copyArr;
}
}
// prints out the keys and values of the changed array
function printout($arr ){
foreach ($arr as $k => $val ) {
echo $k."=>".$val." | ";
if( is_array( $val ) ) {
printout( $val );
}
}
}
// calls the above functions
$changedArr = renameArrayKeys($oldArr);
printout($changedArr);

I'm probably slightly late, but recursion is the way forward with this!
$replace_from = "_old"; //can also be array i.e. array("foo_old", "bar_old")
$replace_to = "_new"; //can also be an array i.e. array("foo_new", "bar_new")
$oldArr = array(
"foo_old" => array("moo_old" => 1234, "woo_old" => 5678),
"bar_old" => array("car_old" => 4321, "tar_old" => 8765),
);
function replace($arr){
global $replace_from, $replace_to;
$newArr = array();
foreach($arr as $key => $value){
$newArr[str_replace($replace_from,$replace_to,$key)] = (is_array($value)) ? replace($value) : $value;
}
return $newArr;
}
print_r (replace($oldArr));

Something like this:
function renameKeys( $arr )
{
if( is_array( $arr ) && count( $arr ) ) {
foreach ( $arr as $k => $v ) {
$nk = str_replace( '_old', '_new', $k );
if( is_array( $v ) ) {
$v = renameKeys( $v );
}
$arr[ $nk ] = $v;
unset( $arr[$k] );
}
}
return $arr;
}
$oldArr = array(
"foo_old" => array("moo_old" => 1234, "woo_old" => 5678) ,
"bar_old" => array("car_old" => 4321, "tar_old" => 8765)
);
$nArr = renameKeys( $oldArr );
print_r( $nArr );

Closure version. This doesn't mess up the namespace.
<?php
$from = '_old';
$to = '_new';
$old_arr = array(
'foo_old' => array('moo_old' => 1234, 'woo_old' => 5678),
'bar_old' => array('car_old' => 4321, 'tar_old' => 8765),
);
$func = function ($arr) use (&$func, $from, $to) {
$new_arr = array();
foreach($arr as $k => $v){
$new_arr[str_replace($from, $to, $k)] = is_array($v) ? $func($v) : $v;
}
return $new_arr;
};
print_r($func($old_arr));

Related

Remove array key prefix recursively

I have an array in the following format (each child array has parent's key as a prefix):
$input = array(
'seo_text' => array(
'seo_text_title' => '',
'seo_text_description' => '',
'seo_text_button' => array(
'seo_text_button_text' => '',
'seo_text_button_url' => '',
'seo_text_button_new_tab_enabled' => '',
),
),
);
I want to convert it into the following format:
$input = array(
'seo_text' => array(
'title' => '',
'description' => '',
'button' => array(
'text' => '',
'url' => '',
'new_tab_enabled' => '',
),
),
);
I'm trying to write a recursive function, but it's not working the way it's suppose to be working.
A solution:
function removeKeyPrefix(array $array, string $prefix = ''): array
{
$newArray = [];
$prefixLength = strlen($prefix);
foreach ($array as $key => $value) {
if (substr($key, 0, $prefixLength) === $prefix) {
$newKey = substr($key, $prefixLength);
} else {
$newKey = $key;
}
$newArray[$newKey] = is_array($value) ? removeKeyPrefix($value, $key.'_') : $value;
}
return $newArray;
}
$input = removeKeyPrefix($input);
Online demo on 3v4l
function sanitizeKeys(array $items, $previousKey = '') : array
{
$previousKey .= "_";
return array_reduce(
array_keys($items),
function($result, $key) use ($previousKey, $items) {
$newKey = (0 === strpos($key, $previousKey)) ? substr_replace($key, '', 0, strlen($previousKey)) : $key;
$result[$newKey] = is_array($items[$key]) ? sanitizeKeys($items[$key], $key) : $items[$key];
return $result;
}, []
);
};
sanitizeKeys($input);
Make a function to change last occurrence in the slug
function fun($array,$slug){
foreach($array as $key=>$val){
$last = end(explode($slug,$key));
if(is_array($val)){
$val = fun($val,$key."_");
}
$array[$last] = $val;
unset($array[$key]);
}
return $array;
}
$input["seo_text"] = fun($input["seo_text"],"seo_text_");
print_r($input);
Live demo : https://eval.in/934155
Output is
Array
(
[seo_text] => Array
(
[title] =>
[description] =>
[button] => Array
(
[text] =>
[url] =>
[new_tab_enabled] =>
)
)
)
For if you have many element in second level use foreach to call function like below
foreach($input as $key=>$val){
$input[$key] = fun($val,$key."_");
}
print_r($input);
https://eval.in/934156

Dynamically creating a multidimensional array based on paths

So I've got a list of paths, such as:
path/to/directory/file1
path/directory/file2
path2/dir/file3
path2/dir/file4
And I'd like to convert them into a multidimensional array like this:
array(
path => array(
to => array(
directory => array(
file1 => someValue
),
),
directory => array(
file2 => someValue
),
),
path2 => array(
dir => array(
file3 => someValue,
file4 => someValue
)
)
)
My first thought was to explode() the paths into segments and set up the array using a foreach loop, something like this:
$arr = array();
foreach ( $path as $p ) {
$segments = explode('/', $p);
$str = '';
foreach ( $segments as $s ) {
$str .= "[$s]";
}
$arr{$str} = $someValue;
}
But this doesn't work, and since the number of segments varies, I've kinda got stumped. Is there away to do this?
If somevalue can be an empty array:
<?php
$result = array();
$input = [
'path/to/directory/file1',
'path/directory/file2',
'path2/dir/file3',
'path2/dir/file4',
];
foreach( $input as $e ) {
nest( $result, explode('/', $e));
}
var_export($result);
function nest(array &$target, array $parts) {
if ( empty($parts) ) {
return;
}
else {
$e = array_shift($parts);
if ( !isset($target[$e]) ) {
$target[$e] = [];
}
nest($target[$e], $parts);
}
}
Here is the solution and a easy way
Just Reverse the whole exploded array and start creating array within a Array
$path[1] = "path/to/directory/file1";
$path[2] = "path/directory/file2";
$path[3] = "path2/dir/file3";
$path[4] = "path2/dir/file4";
$arr = array();
$b = array();
$k = 0;
foreach($path as $p) {
$c = 0;
$segments = explode('/', $p);
$reversed = array_reverse($segments);
foreach($reversed as $s) {
if ($c == 0) {
$g[$k] = array($s => "somevalue");
} else {
$g[$k] = array($s => $g[$k]);
}
$c++;
}
$k++;
}
var_dump($g);
Thanks so much VolkerK! Your answer didn't quite answer my question but it got me on the right track. Here's the version I ended up using to get it to work:
$result = array();
$input = [
'path/to/directory/file1' => 'someValue',
'path/directory/file2' => 'someValue',
'path2/dir/file3' => 'someValue',
'path2/dir/file4' => 'someValue',
];
foreach( $input as $e=>$val ) {
nest( $result, explode('/', $e), $val);
}
var_export($result);
function nest(array &$target, array $parts, $leafValue) {
$e = array_shift($parts);
if ( empty($parts) ) {
$target[$e] = $leafValue;
return;
}
if ( !isset($target[$e]) ) {
$target[$e] = [];
}
nest($target[$e], $parts, $leafValue);
}
I basically just added the somevalue as $leafValue and moved the base case around so that it would add the leafValue instead of a blank array at the end.
This results in:
Array
(
[path] => Array
(
[to] => Array
(
[directory] => Array
(
[file1] => someValue
)
)
[directory] => Array
(
[file2] => someValue
)
)
[path2] => Array
(
[dir] => Array
(
[file3] => someValue
[file4] => someValue
)
)
)
Thanks a lot!
It can be done without recursion
$path = array(
'path/to/directory/file1',
'path/directory/file2',
'path2/dir/file3',
'path2/dir/file4');
$arr = [];
$someValue = 'someValue';
foreach ( $path as $p ) {
$segments = explode('/', $p);
$str = '';
$p = &$arr;
foreach ( $segments as $s ) {
if (! isset($p[$s] ) ) $p[$s] = array();
$p = &$p[$s];
}
$p = $someValue;
}
print_r($arr);

Search array by key branch

I have an array that looks like this:
$array = array (
[level_1] => array (
[level_2] => array (
[level_3] => something
)
),
[level_12] => array (
[level_2] => somethingelse
),
[level_13] => array (
[level_22] => array (
[level_3] => something
)
),
);
The keys or values aren't always unique but the branches are.
And I have a string that looks like this:
$string = 'level_1-level_2-level_3';
Those are the keys for a branch.
And I need to somehow get the value from the array based on that string?
Like this:
$string_array = explode('-', $string);
$array[$string_array[0]][$string_array[1]][$string_array[2]] // something
But since the depth can be different this is not a viable solution...
Try this simple example, no need for a recursive function:
function get_item( $path, $array )
{
$paths = explode( '-', $path );
$result = $array;
foreach ( $paths as $path) {
isset( $result[$path] ) ? $result = $result[$path] : $result = false;
}
return $result;
}
$path = 'level_1-level_2-level_3';
echo get_item( $path, $array );
Try this:
$array = array (
'level_1' => array (
'level_2' => array (
'level_3' => 'something'
)
),
'level_12' => array (
'level_2' => 'somethingelse'
),
'level_13' => array (
'level_22' => array (
'level_3' => 'something'
)
),
);
$string = 'level_1-level_2-level_3';
$keys = explode('-', $string);
echo getItemIterative($keys, $array);
echo "\n";
echo getItemRecursive($keys, $array);
function getItemIterative($keys, $array)
{
$value = null;
foreach ($keys as $key) {
if ($value == null) {
$value = $array[$key];
}
if (is_array($value) && array_key_exists($key, $value)) {
$value = $value[$key];
}
}
return $value;
}
function getItemRecursive($keys, $array)
{
$key = array_shift($keys);
$value = $array[$key];
if (empty($keys)) {
return $value;
} else {
return getItemRecursive($keys, $value);
}
}
Make a $result variable which initially points to the root of the array, and loop through the levels of your $string_array 'til $result points at the leaf you were looking for.
// stuff you already have:
$array = array(...); // your big array
$string = 'level_1-level_2-level_3';
$string_array = explode('-', $string);
// new stuff:
$result = $array;
foreach ($string_array as $level) {
$result = $result[$level];
}
echo $result; // 'something'
Working example: Ideone

How can I convert this M-N mysql array in this other array in PHP? (examples inside)

Well, is pretty common to get an array like this in relations M-N in mysql:
[0]
'book_id' => 1
'title' => 'title'
'author_id' => 1
'author_name' => 'name1'
[1]
'book_id' => 1
'title' => 'title'
'author_id' => 2
'author_name' => 'name2'
So, there is an elegant/easy way to convert this kind or arrays in something like this?
'book_id' => 1
'title' => 'title'
'authors' => array( 0 => array( 'author_id' => 1, 'author_name' => 'name1' ),
1 => array( 'author_id' => 2, 'author_name' => 'name2' ) )
I don't find any script or combinations of functions that made this... and is pretty common, maybe I have not searched correctly.. don't know how to call the problem...
Any ideas or experiences?
Thanks :)
PS: I don't want to use GROUP BY + GROUP CONCACT in MySQL, I found that pretty ugly solution...
EDIT:
I'm working in something generic, not only to solve this specific problem..
I would do the JOIN in SQL and the post-process the results into a nested array in PHP code.
$bookkeys = array_flip(array("book_id", "title"));
$authorkeys = array_flip(array("author_id", "author_name"));
$stmt = $dbh->query("...");
$books = array();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
if (!array_key_exists($row["book_id"], $books)) {
$books[ $row["book_id"] ] = array_intersect_key($row, $bookkeys);
}
$books[ $row["book_id"] ]["authors"][] = array_intersect_key($row, $authorkeys);
}
If you want the final array to be an ordinal array instead of keyed by book_id, you can convert it:
$books = array_values($books);
A generic solution for multiple sub-keys:
$rows = collapse_rows($rows, 'book_id', array(
'author_id' => array('author_name'),
));
function collapse_rows($rows, $key, $subkeys){
# make a map of all fields we don't perform a simple copy on
$skip_fields = array();
foreach ($subkeys as $k => $v){
$skip_fields[$k] = 1;
foreach ($v as $v2) $skip_fields[$v2] = 1;
}
# now build our output
$out = array();
foreach ($rows as $row){
$row2 =& $out[$row[$key]];
# simple fields first
foreach ($row as $k => $v){
if (!$skip_fields[$k])){
$row2[$k] = $v;
}
}
# now subkeys
foreach ($subkeys as $k => $v){
$sub_row = array($k => $row[$k]);
foreach ($v as $v2) $sub_row[$v2] = $row[$v2];
$row2[$k][$sub_row[$k]] = $sub_row;
}
}
return $out;
}
This lets you pass the primary key and a hash of sub keys and fields to aggregate by.
Well, I have a "solution" based in Bill Karwin suggestion:
$key = 'authors';
$output = array();
if ( is_array( $elements ) )
{
$all_keys = array_keys( $elements[0] );
$conflicted_keys = call_user_func_array( 'array_diff_assoc', $elements );
$conflicted_keys = array_keys( $conflicted_keys );
$good_keys = array_diff( $all_keys, $conflicted_keys );
$conflicted_keys_fliped = array_flip( $conflicted_keys );
$good_keys_fliped = array_flip( $good_keys );
foreach ( $elements as $row )
{
if ( !array_key_exists( $row[$good_keys[0]], $output ) ) {
$output[ $row[$good_keys[0]] ] = array_intersect_key($row, $good_keys_fliped);
}
$output[ $row[$good_keys[0]] ][ $key ][] = array_intersect_key( $row, $conflicted_keys_fliped );
}
}
$output = array_values($output);
var_dump($output);
Don't know if is the most eficient/correct.. On other hand, this will not work if there are more than one type of conflict... patches will be good received :)
thanks to all!
Something simple like this would work:
$out = array();
foreach ($rows as $row){
$out[$row['book_id']]['book_id'] = $row['book_id'];
$out[$row['book_id']]['title'] = $row['title'];
$out[$row['book_id']]['authors'][$row['author_id']] = array(
'author_id' => $row['author_id'],
'author_name' => $row['author_name'],
);
}
Rows are keyed by book ID and then author ID so we only have one output row per book and then per author inside that row.
Well, this is the funcion that finally I will use, it is based completly in the idea of Cal but trying to use native functions as said Bill Karvin:
protected function collapse_rows( $rows, $key, $subkeys ) {
if ( is_array( $rows ) )
{
// make a map of all fields we don't perform a simple copy on
$skip_fields = array();
foreach( $subkeys as $sub ) {
$skip_fields = array_merge( $skip_fields, array_reverse( $sub ) );
}
$skip_fields = array_flip( $skip_fields );
// now build our output
$out = array();
foreach ( $rows as $row ) {
// simple fields first
if ( !array_key_exists( $row[$key], $out ) ) {
$out[ $row[$key] ] = array_diff_key( $row, $skip_fields );
}
// now subkeys
foreach ( $subkeys as $k => $v ) {
$value = array_intersect_key( $row, array_flip( $subkeys[$k] ) );
if ( !empty( $value ) ) {
$out[ $row[$key] ][ $k ][] = $value;
}
}
}
return array_values( $out );
}
return $rows;
}
Once done, I don't think it has a too much good performance... I wonder how other people solve this kind of problems...
Whatever, thanks to both!

How to merge arrays but no numerical keys in fewest lines in php?

$result = array_merge($arr1,$arr2);
I want to exclude numerical values of $arr2,is there an option for this?
Edit after comment:
$arr1 = array('key' => 1);
$arr2 = array('test',1 => 'test', 'key2' => 2);
after processing I need the result to be:
array('key' => 1,'key2' => 2);
Excluding numerical keys
It seems that you want to array_filter your $arr2's keys, first:
function not_numeric( $object ) {
return !is_numeric( $object );
}
$no_numeric_keys = array_filter( array_keys( $arr2 ), not_numeric );
$no_numeric_array = array_intersect_keys( $arr2, $no_numeric_keys );
$result = array_merge( $arr1, $no_numeric_array );
I'm guessing that this would work, after using $result = array_merge($arr1,$arr2);:
foreach ($result as $key => $value) {
if (is_numeric($key)) {
unset($result[$key]);
}
}
Edit:
In as few lines as possible (1) – as requested in the new title:
foreach ($result as $key => $value) { if (is_numeric($key)) { unset($result[$key]); } }
Just loop through each array and test if keys are strings:
$output = array();
foreach($arr1 as $key => $value) {
if(is_string($key)) {
$output[$key] = $value;
}
}
foreach($arr2 as $key => $value) {
if(is_string($key)) {
$output[$key] = $value;
}
}
Edit:
Since you said elegant...
function merge_arrays_string_keys()
{
$output = array();
foreach(func_get_args() as $arr)
{
if(is_array($arr))
{
foreach($arr as $key => $value) {
if(is_string($key) {
$output[$key] = $value;
}
}
}
}
return $output;
}
elegant, huh?
$test = array('test', 1 => 'test', 'key2' => 2, 33, 3 => 33, 'foo' => 'bar');
$test_non_num = array_intersect_key(
$test,
array_flip(
array_diff(
array_keys($test),
array_filter(array_keys($test), 'is_int'))));
print_r($test_non_num); // key2=>2, foo=>bar
Use this code , it will also do the require thing.
<?php
$result = array ( 1,"pavunkumar","bks", 123 , "3" ) ;
array_walk($result,'test_print');
print_r ( $result ) ;
function test_print( $val , $key )
{
global $result;
if ( gettype ( $val ) == 'integer' )
{
unset ( $result[$key] ) ;
}
}
array_diff_ukey($m=$arr2+$arr1,$m,function($k){return is_string($k);})

Categories