Turn string into multi-dimensional array equivalent - php

I'm trying to parse a string from a URL and convert it into a PHP array, but so far I haven't been able to get it to work correctly.
The string contains comma separated values which are then grouped by periods, for example:
course.id,course.title,course.author,course.duration,course.trailer.video_key,course.trailer.duration,course.lessons.id,course.lessons.title,track.id,track.title,track.caption.srt,track.caption.vtt,track.caption.text
The Array equivalent to this string would be:
PHP:
$array = [
'course' => [
'id',
'title',
'author',
'duration',
'trailer' => [
'video_key',
'duration'
],
'lessons' => [
'id',
'title'
]
],
'track' => [
'id',
'title',
'caption' => [
'srt',
'vtt',
'text'
]
]
];
My current solution is this:
PHP:
$string = 'course.id,course.title,course.author,course.duration,course.trailer.video_key,course.trailer.duration,course.lessons.id,course.lessons.title,track.id,track.title,track.caption.srt,track.caption.vtt,track.caption.text';
$parameters = explode( ',', $string );
$array = array();
$inset = array();
foreach ( $parameters as $parameter ) {
$segments = explode( '.', $parameter );
if ( ! array_key_exists( $segments[0], $inset ) ) {
$array[ $segments[0] ] = array();
$inset[ $segments[0] ] = true;
}
$array[ $segments[0] ] = array_merge( $array[ $segments[0] ], addSegment( $segments, 1 ) );
}
function addSegment( $segments, $counter ) {
$results = array();
if ( end( $segments ) == $segments[ $counter ] ) {
return array( $segments[ $counter ] => null );
}
$results[ $segments[ $counter ] ] = addSegment( $segments, $counter + 1 );
return $results;
}
This somewhat works, however; it fails with simpler string like this one
course,track,lessons
I think this calls for recursion but I'm not good at writing this so I'm hoping someone has already done this and can help me.
Thank you.

You can achieve this without recursion - just double loop and use of &:
$arr = explode(",", "course.id,course.title,course.author,course.duration,course.trailer.video_key,course.trailer.duration,course.lessons.id,course.lessons.title,track.id,track.title,track.caption.srt,track.caption.vtt,track.caption.text");
foreach($arr as $e) {
$e = explode(".", $e);
$obj = &$res; //set the current place root
while(count($e) > 1) { // last element will be add as value
$key = array_shift($e);
if (!isset($obj[$key]))
$obj[$key] = [];
$obj = &$obj[$key]; //reset the current place
}
$obj[] = array_shift($e); // add the last as value to current place
}
$res will contain your desire output

Related

PHP: Use Variable as Multiple Keys in Multi-Dimensional-Array

In a normal array you can select this way
$key='example';
echo $array[$key];
How about in a multidimensional?
$keys='example[secondDimension][thirdDimension]';
echo $array[$keys];
What's the proper way to go about this?
i think this solution is good.
note that you have to wrap all keys with "[" and "]".
$array = array(
'example' => array(
'secondDimension' => array(
'thirdDimension' => 'Hello from 3rd dimension',
)
),
);
function array_get_value_from_plain_keys($array, $keys)
{
$result;
$keys = str_replace(array('[', ']'), array("['", "']"), $keys); // wrapping with "'" (single qoutes)
eval('$result = $array' . $keys . ';');
return $result;
}
$keys = '[example][secondDimension][thirdDimension]'; // wrap 1st key with "[" and "]"
echo array_get_value_from_plain_keys($array, $keys);
Learn more about eval() function
if you also want to check if the value is defined or not then you can use this function
function array_check_is_value_set_from_plain_keys($array, $keys)
{
$result;
$keys = str_replace(array('[', ']'), array("['", "']"), $keys); // wrapping with "'" (single qoutes)
eval('$result = isset($array' . $keys . ');');
return $result;
}
Giving a better name to that function will be appreciated ^^
Here is a solution without using eval:
$array = [
'example' => [
'secondDimension' => [
'thirdDimension' => 'Hello from 3rd dimension',
],
],
];
$keys = '[example][secondDimension][thirdDimension]';
function get_array_value( $array, $keys ) {
$keys = explode( '][', trim( $keys, '[]' ) );
return get_recursive_array_value( $array, $keys );
}
function get_recursive_array_value( $array, $keys ) {
if ( ! isset( $array[ $keys[0] ] ) ) {
return null;
};
$res = $array[ $keys[0] ];
if ( count( $keys ) > 1 ) {
array_shift( $keys );
return get_recursive_array_value( $res, $keys );
} else {
return $res;
}
}
echo get_array_value( $array, $keys );
Want you to use the $b array to follow the nested keys of the $a array and get the value in $c ?
<?php
$a = [ 'key_1' => [ 'key_2' => [ 'key_3' => 'value', ], ], ] ;
$b = ['key_1', 'key_2', 'key_3', ] ;
if ($b)
{
$c = $a ; // $a is not copied (copy-on-write)
foreach($b as $k)
if (isset($c[$k]))
$c = $c[$k] ;
else
{
unset($c);
break;
}
var_dump($c);
}
/*
output :
string(5) "value"
*/
or do you want to generate the $b array for an arbitrary formed string and get $c as a reference ?
<?php
$a = [ 'key_1' => [ 'key_2' => [ 'key_3' => 'value', ], ], ] ;
$b = '[key_1][key_2][key_3]';
if ($b !== '')
{
$b = explode('][', trim($b, '[]'));
$c = &$a ;
foreach($b as $k)
if (isset($c[$k]))
$c = &$c[$k] ;
else
{
unset($c);
break;
}
}
var_dump($c);
$c = 'new val' ;
unset($c);
var_dump($a['key_1']['key_2']['key_3']);
/*
output :
string(5) "value"
string(7) "new val"
*/
You have to use a separate variable for each dimension of the array. A common pattern you see with multidimensional arrays where you need to do something with the 2nd dimension is something like this:
$pets = [
'dog' => ['Jack', 'Fido', 'Woofie'],
'cat' => ['Muggles', 'Snowball', 'Kitty'],
];
// Loop through keys in first dimension
foreach ($pets as $type => $names) {
foreach ($names as $index => $name) {
// And do something with second dimension using the variable
// you've gained access to in the foreach
$pets[$type][$index] = strtoupper($name);
}
}

PHP how to keep the matched item's key with array_search and array_column?

How can I keep the matched item's key with array_search and array_column?
$items = array(
'meta-title' => [
"code" => 'meta-title'
],
'meta-keywords' => [
"code" => 'meta-keywords'
],
);
$key = array_search('meta-title', array_column($items, 'code'));
var_dump($key); // 0
The result I am after:
'meta-title'
Any ideas?
Your array_columns() call returns a numerically indexed array of strings (based on the keys from the 1st level), not the array that you're wanting to search (i.e. an array of the 'code' values from the 2nd level). You might be better off iterating through $items and building an array (key/value pairs) based on searching the arrays you're iterating through:
$items = array(
'meta-title' => [
'code' => 'meta-title'
],
'meta-keywords' => [
'code' => 'meta-keywords'
],
);
$results = array();
foreach ($items as $key => $value) {
$result = array_search('meta-title', $value);
if ($result !== false) {
array_push($results, array($key => $result));
}
}
http://sandbox.onlinephpfunctions.com/code/71934db55c67657f0336f84744e05097d00eda6d
Here is an object oriented approach that allows the column and search value to be set at run time. As a class it's more reusable and somewhat self documenting.
<?php
$items = array(
'meta-title' => [
"code" => 'meta-title'
],
'meta-keywords' => [
"code" => 'meta-keywords'
],
);
/**
* Search all records of a recordset style array for a column containing a value
* Capture the row into matches member for later use.
*/
class ColumnSearch {
private $key;
private $search;
public $matches=array();
public function __construct( $key, $search ){
$this->key = $key;
$this->search = $search;
}
public function search( array $items ){
// #todo validate $items is like a recordset
$matches = array_filter( $items, array( $this, "_filter"), ARRAY_FILTER_USE_BOTH );
$this->matches = $matches;
return count($matches);
}
private function _filter( $row, $rowKey ){
return ( $row[$this->key] == $this->search );
}
}
$search = new ColumnSearch( 'code', 'meta-title' );
$occurances = $search->search( $items );
// return value indicates how many were found, in case of multiples...
echo $occurances ." ". PHP_EOL;
// the matched row will be in matches member.
var_dump($search->matches);
// there might be more than 1, not in your example but this is very generic code.
// grab just the keys, then get the current
echo current( array_keys($search->matches) ) . PHP_EOL;
echo "New Search for value that doesn't exist.". PHP_EOL;
$newSearch = new ColumnSearch( 'code', 'title' );
$count = $newSearch->search( $items );
if( 0 == $count ){
echo "Nothing found.". PHP_EOL;
}
echo current( array_keys( $newSearch->matches) );
http://sandbox.onlinephpfunctions.com/code/83b306bfc30ef2a055cf49501bdeb5cb2e5b5ed7

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!

Dynamic formation of variable name in PHP?

The code below dynamically concatenates keys to an existing array $options_pool. So the final form should be: $options_pool[ $base_key ][ $first_key ][ $second_key ]... This is so I can dynamically assign a value to the elements of the array $options_pool which is multidimensional.
foreach( $this->post_vars as $name => $value ) {
//Look for $name key in array $options_pool if it exists.
//Use multi_array_key_exists() to handle the task
//It should return something like "fruit:mango:apple_mango"
//Then dynamically call $options_pool based on the data. Like so: $options_pool[ 'fruit' ][ 'mango' ][ 'apple_mango' ] = $value;
$match_key = multi_array_key_exists( $name, $options_pool );
$option_keys = explode( ':', $match_key );
$option_keys_length = count( $option_keys );
$option_name_array = array();
if( 0 < $option_keys_length ) {
for( $c = $option_keys_length; $c > 0; $c-- ) {
$sub_keys = '$options_pool';
for( $c_sub = 0; $c_sub < $c ; $c_sub++ ) {
$sub_keys .= '[ $option_keys[ '. $c_sub . ' ] ]';
}
$option_name_array[] = $sub_keys;
}
foreach( $option_name_array as $f_var_name ) {
//the following line should equal to: $options_pool[ 'fruit' ][ 'mango' ][ 'apple_mango' ] = $value;
$f_var_name = $value;
}
}
}
//The $options_pool array
$options_pool = array( 'car' => '', 'animals' => '', 'fruit' => array( 'mango' => array( 'apple_mango' => '' ));
I think the logic is correct except that this portion of the code:
foreach( $option_name_array as $f_var_name ) {
$f_var_name = $value; //particularly this line
}
doesn't work? I've tested printing the value of $f_var_name and the result is correct but it doesn't really call the array?
That is incorrect, you are right.
The variable name will always be $options_pool.
You can pass the keys as explode(':', $name) and later assign them.
By the way, your code at
foreach( $option_keys as $option_keys_value ) {
$option_key_names[] = $option_keys_value;
}
Do you realize that it just copies $option_keys as $option_key_names just as if you had written $option_key_names = $option_keys; (in this code) ?
Maybe with a stack you could do this iteratively but with recursion it is just more natural, as you see here
function getVariableToWrite(&$reference, $nested_opts, $write) {
if(empty($nested_ops)) // Base case
return $reference = write;
if(isset($reference[array_shift($nested_ops)]))
return getVariableToWrite($reference, $nested_ops, $write);
throw new Exception("The key does not exist..");
}
And then just
foreach( $this->post_vars as $name => $value ) {
// Your work over here until...
$match_key = "fruit:mango:apple_mango";
$option_keys = explode( ':', $match_key );
getVariableToWrite($options_pool, $option_keys, $value);
}
Will this do the work?
In your foreach, you need to pass the value by reference, so you can edit it.
foreach( $option_name_array as &$f_var_name ){
$f_var_name = $value;
}
Try this...
foreach( $option_name_array as $key => $f_var_name ) {
$option_name_array[$key] = $value; //particularly this line
}

PHP: rename multidimensional array's keys

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));

Categories