PHP - find value comparing two multi-dimensional arrays - php

I've here two multi-dimensional arrays.
How would you do to get the image_to_get value in the $b array thanks to the $a array ?
$a = array(
'thumbs' => array(
'0' => array(
'thumb1a' => array(
'0' => array(
'thumb1' => ""
)
)
)
)
);
$b = array(
'thumbs' => array(
'0' => array(
'thumb1a' => array(
'0' => array(
'thumb1' => "image_to_get"
)
),
'thumb2' => 'image2',
'thumb3' => 'image3',
'thumb4' => 'image4',
'thumb5' => 'image5',
)
)
);

You can try with:
function getAPath($array) {
if (empty($array)) {
return array();
}
$key = key($array);
return array_merge(array($key), getAPath($array[$key]));
}
function getBValue($array, $path) {
$key = array_shift($path);
if (is_null($key) || empty($array)) {
return $array;
}
return getBValue($array[$key], $path);
}
$aPath = getAPath($a);
$bValue = getBValue($b, $aPath);
var_dump($bValue);
First function getAPath flatterns your $a array into:
array (size=5)
0 => string 'thumbs' (length=6)
1 => int 0
2 => string 'thumb1a' (length=7)
3 => int 0
4 => string 'thumb1' (length=6)
Second function getBValue walks through the $b array using $aPath.
Below lovely one-liners ;-)
function getAPath($array) {
return empty($array) ? array() : array_merge(array($key = key($array)), getAPath($array[$key]));
}
function getBValue($array, $path) {
return (is_null($key = array_shift($path)) || empty($array)) ? $array : getBValue($array[$key], $path);
}

Related

Iterating over multidimensional array in PHP

Over the past few days, I've been thinking about how to deal with iterating over keys in a multidimensional array, and I just cannot figure it out. The problem is, I don't know how deep the array might be - I want my code to be able to handle arrays of any depth.
The array itself comes from Advanced Custom Fields, but that's not too important. I need to iterate over the array, run a function on every array key which starts with field_ (to convert it from field_* to its proper name like post_title or something), and reconstruct the array with the same structure and values (although the order is not important). The array looks like this:
array (size=12)
'field_5b23d04fef8a6' => string '' (length=0)
'field_5b23d04fefa99' => string '' (length=0)
'field_5b23d04fefe85' => string '' (length=0)
'field_5b23d04ff0077' => string '' (length=0)
'field_5b23d04ff026c' => string '' (length=0)
'field_5b23d0bdb3c1a' => string 'Version 1' (length=9)
'field_5b23d0f48538b' => string '' (length=0)
'field_5b23d0f485772' => string '' (length=0)
'field_5b23d0d52be2d' => string '' (length=0)
'field_5b5ed10a6a7bc' => string '' (length=0)
'field_5b5ed10a6bcf5' =>
array (size=1)
0 =>
array (size=1)
'field_5b5ed10acd264' =>
array (size=1)
0 =>
array (size=6)
'field_5b5ed10b0c9ca' => string '0' (length=1)
'field_5b5ed10b0cce2' => string 'TEST1234' (length=8)
'field_5b5ed10b0d0fd' => string 'Download title' (length=14)
'field_5b5ed10b0d4e2' => string 'EN' (length=2)
'field_5b5ed10b0d72e' => string 'A00' (length=3)
'field_5b5ed10b0df27' => string '887' (length=3)
'field_5b23d088500a4' => string '' (length=0)
What would be the best way to handle this? I've looked at recursive functions and ResursiveArrayIterator already, but none of the examples I found were close enough to let me figure out what I need.
You can recursively call the same function if it finds a nested array like this:
$input = array(
'field_5b23d04fef8a6' => '',
'field_5b23d04fefa99' => '',
'field_5b23d04fefe85' => '',
'field_5b23d04ff0077' => '',
'field_5b23d04ff026c' => '',
'field_5b23d0bdb3c1a' => 'Version 1',
'field_5b23d0f48538b' => '',
'field_5b23d0f485772' => '',
'field_5b23d0d52be2d' => '',
'field_5b5ed10a6a7bc' => '',
'field_5b5ed10a6bcf5' => array(
array(
'field_5b5ed10acd264' => array(
array(
'field_5b5ed10b0c9ca' => '0',
'field_5b5ed10b0cce2' => 'TEST1234',
'field_5b5ed10b0d0fd' => 'Download title',
'field_5b5ed10b0d4e2' => 'EN',
'field_5b5ed10b0d72e' => 'A00',
'field_5b5ed10b0df27' => '887',
),
),
),
),
'field_5b23d088500a4' => '',
);
// recursively re-key array
function dostuff($input){
// always refer to self, even if you rename the function
$thisfunction = __function__;
$output = array();
foreach($input as $key => $value){
// change key
$newkey = (is_string($key) ? preg_replace('/^field_/', 'post_title_', $key) : $key);
// iterate on arrays
if(is_array($value)){
$value = $thisfunction($value);
}
$output[$newkey] = $value;
}
return $output;
}
var_dump(dostuff($input));
So I was looking at this and to my knowledge there is no wrapper function for recursion with callbacks, so here it is:
// general function for recursively doing something
// $input -> array() / the array you wan to process
// $valuefunction -> callable | null / function to run on all values *
// $keyfunction -> callable | null / function to run on all keys *
// * at least one has to defined or there is nothing to do
// callable has two inputs
// $input -> current branch
// $depth -> (int) how deep in the structure are we
// i.e: recursion($some_array, function($branch, $depth){something..}, 'trim');
function recursion($input, $valuefunction = false, $keyfunction = false){
if(!is_array($input)){
trigger_error('Input is '.gettype($input).'. Array expected', E_USER_ERROR);
return null;
}
if(!is_callable($valuefunction)){$valuefunction = false;}
if(!is_callable($keyfunction)){$keyfunction = false;}
if(!$valuefunction && !$keyfunction){
trigger_error('Input is unchanged!', E_USER_WARNING);
return $input;
}
// use recursion internally, so I can pass stuff by reference
// and do the above checks only once.
$recurse = function(&$branch, $depth = 0) use (&$recurse, &$valuefunction, &$keyfunction){
$output = array();
foreach($branch as $key => $value){
$key = $keyfunction ? $keyfunction($key, $depth) : $key;
$output[$key] = (is_array($value) ?
$recurse($value, $depth + 1) :
($valuefunction ?
$valuefunction($value, $depth) :
$value
)
);
}
return $output;
};
return $recurse($input);
}
$valuefunction = function($value, $depth){
return is_string($value) ? $depth.'_'.$value : $value;
};
function keyfunction($key){
return is_string($key) ? preg_replace('/^field_/', 'post_title_', $key) : $key;
}
var_dump(recursion($input, $valuefunction, 'keyfunction'));
Or for your example:
var_dump(recursion($input, 0, function($key){
return is_string($key) ? preg_replace('/^field_/', 'post_title_', $key) : $key;
}));
You could do something like this:
$arr = [
'a',
'b',
'c',
[
'd',
'e',
'f',
[
'g',
'h',
'i',
],
],
];
class MyIterator
{
public function iterate( $array )
{
foreach ( $array as $a ) {
if ( is_array( $a ) ) {
$this->iterate($a);
} else {
echo $a;
}
}
}
}
$iterator = new MyIterator();
$iterator->iterate($arr);
It prints this:
abcdefghi
You can iterate over array recursively like this
function recursiveWalk($array, callable $x)
{
$result = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$result[$key] = recursiveWalk($value, $x);
} else {
$result[$key] = $x($value);
}
}
return $result;
}
Here example:
$array = [
"aaa" => 1,
"sub1" => [
"xxx" => 2,
"sub2" => [
"yyy" => 3,
"ttt" => 4
]
]
];
print_r(recursiveWalk($array, function ($x) {
return $x + 1;
}));
Array
(
[aaa] => 2
[sub1] => Array
(
[xxx] => 3
[sub2] => Array
(
[yyy] => 4
[ttt] => 5
)
)
)

Push same key to associative array in PHP

I'm trying to add arrays to an associative array in PHP. I know this isn't how you're supposed to use the keys but I'm parsing the array to XML which needs te same <line> tag.
Desired array:
array(
'line' => array(
// Ean-artikelcode
'Article_Eancode' => 8710624618216,
// Leveranciersartikelcode
'Article_Supplier_Partno' => 22304
),
'line' => array(
'Article_Eancode' => 8710622648216,
'Article_Supplier_Partno' => 22304
)
);
Which I am trying to get with this code:
$artikelenFormatted = array();
$artikelen = array(
'a',
'b',
'c'
);
foreach ($artikelen as $art) {
$artikelenFormatted['line'] = array(
"Article_Eancode" => "a",
"Article_Supplier_Partno" => "b"
);
}
Which produces:
array (size=1)
'line' =>
array (size=2)
'Article_Eancode' => string 'a' (length=1)
'Article_Supplier_Partno' => string 'b' (length=1)
Because $array['line'] keeps getting overwritten so there aren't multiple entries
How would I do this?
EDIT: Sample of the desired XML
<Lines>
<Line>
<Article_Eancode>87XXXXXXXXXXX</Article_Eancode>
<Article_Supplier_Partno>22304</Article_Supplier_Partno>
</Line>
<Line>
<Article_Eancode>87XXXXXXXXXXX</Article_Eancode>
<Article_Supplier_Partno>22303</Article_Supplier_Partno>
</Line>
<Line>
<Article_Eancode>87XXXXXXXXXXX</Article_Eancode>
<Article_Supplier_Partno>22324</Article_Supplier_Partno>
</Line>
<Line>
<Article_Eancode>87XXXXXXXXXXX</Article_Eancode>
<Article_Supplier_Partno>22305</Article_Supplier_Partno>
</Line>
<Line>
<Article_Eancode>87XXXXXXXXXXX</Article_Eancode>
<Article_Supplier_Partno>22323</Article_Supplier_Partno>
</Line>
</Lines>
An array cannot have two (or more) of the same key. Consider; what would $array['line'] return?
What you're looking for is:
foreach ($artikelen as $art) {
$artikelenFormatted['line'][] = array(
"Article_Eancode" => "a",
"Article_Supplier_Partno" => "b"
);
}
Notice the [] after ['line']. This will make $artikelenFormatted['line'] an array where each element is an array of the data.
Edit:
To get it to work with XML, use the following:
foreach ($artikelen as $art) {
$artikelenFormatted[]['line'] = array(
"Article_Eancode" => "a",
"Article_Supplier_Partno" => "b"
);
}
And amend the array_to_xml function you reference to:
function new_array_to_xml( $data, &$xml_data ) {
foreach( $data as $key => $value ) {
if( is_array($value) ) {
if( is_numeric($key) ){
array_to_xml($value, $xml_data);
}
else
{
$subnode = $xml_data->addChild($key);
array_to_xml($value, $subnode);
}
} else {
$xml_data->addChild("$key",htmlspecialchars("$value"));
}
}
}
I solved this by using:
foreach ( $artikelen as $art ) {
$artikelenFormatted [] = array (
"Article_Eancode" => "a",
"Article_Supplier_Partno" => "b"
);
Which would output
array (size=2)
0 =>
array (size=2)
'Article_Eancode' => string 'a' (length=1)
'Article_Supplier_Partno' => string 'b' (length=1)
1 =>
array (size=2)
'Article_Eancode' => string 'a' (length=1)
'Article_Supplier_Partno' => string 'b' (length=1)
And editing my array_to_xml() function to change numeric keys to the string 'line' which produces the right XML
private function array_to_xml($entries, &$tmpXML) {
foreach ( $entries as $key => $value ) {
if (is_array ( $value )) {
if (! is_numeric ( $key )) {
$subnode = $tmpXML->addChild ( "$key" );
$this->array_to_xml ( $value, $subnode );
} else {
$subnode = $tmpXML->addChild ( "line" );
$this->array_to_xml ( $value, $subnode );
}
} else {
$tmpXML->addChild ( "$key", htmlspecialchars ( "$value" ) );
}
}
}
Thanks for everyone's input
You can't have the same keys but you can have :
EDIT 1 :
array(
[0] => array(
'line' => array(
// Ean-artikelcode
'Article_Eancode' => 8710624618216,
// Leveranciersartikelcode
'Article_Supplier_Partno' => 22304
)),
[1] => array(
'line' => array(
'Article_Eancode' => 8710622648216,
'Article_Supplier_Partno' => 22304
))
);
And you can do it like this :
foreach ($artikelen as $art) {
$artikelenFormatted[] = array(
'line' => array(
"Article_Eancode" => "a",
"Article_Supplier_Partno" => "b"
);
}

Counting distinct values in multidimensional array

I'm having real problems trying to figure this one out.
I have a PHP array which looks like this:
$info = array();
$info[0] = array(
'car' => 'Audi',
'previous_car' => 'BMW'
);
$info[1] = array(
'car' => 'Audi',
'previous_car' => 'Seat'
);
$info[2] = array(
'car' => 'Audi',
'previous_carg' => 'BMW'
);
$info[3] = array(
'car' => 'BMW',
'previous_car' => 'BMW'
);
$info[4] = array(
'car' => 'Ford',
'previous_car' => 'Seat'
);
I need to do some sorting on this, so the result looks like this:
Array (
car [
'Audi' => 3,
'BMW' => 1,
'Ford' => 1
],
previous_car [
'BMW' => 3,
'Seat' => 2
]
);
I need to count distinct occurrences of a value in the same key, but the search is made upon couple of arrays. I was trying to use array_value_count(), but I doesn't work well on multidimensional arrays.
I am trying to avoid the looping, since it can be overkill if the array is large.
I will be very grateful for all the help.
If you're running PHP 5.5, you can use:
$newArray = array(
'car' => array_count_values(array_column($info, 'car')),
'previous_car' => array_count_values(array_column($info, 'previous_car'))
);
var_dump($newArray);
For versions of PHP prior to 5.5
$newArray = array(
'car' => array_count_values(
array_map(
function($value) {
return $value['car'];
},
$info
)
),
'previous_car' => array_count_values(
array_map(
function($value) {
return $value['previous_car'];
},
$info
)
)
);
var_dump($newArray);
In a more object orientated way you can solve it as follows
$values = new ArrayObject();
$iterator = new RecursiveArrayIterator($info);
iterator_apply($iterator, 'countDistinct', array($iterator, $values));
function countDistinct($iterator, $values) {
while ( $iterator -> valid() ) {
if ( $iterator -> hasChildren() ) {
countDistinct($iterator -> getChildren(), $values);
} else {
if (!$values->offsetExists($iterator->key())) {
$values->offsetSet($iterator->key(), new ArrayObject());
}
if (!$values->offsetGet($iterator->key())->offsetExists($iterator->current())) {
$values->offsetGet($iterator->key())
->offsetSet($iterator->current(), 1);
} else {
$values->offsetGet($iterator->key())
->offsetSet($iterator->current(),
$values->offsetGet($iterator->key())->offsetGet($iterator->current()) + 1);
}
}
$iterator -> next();
}
}
Sure, with this example you do not avoid the loop. But with the ArrayObject and the RecursiveArrayIterator you will have some memory and performance advantages.
The result of this will exactly match your expected result, which you can easyliy iterate with the getIterator() function of the ArrayObject.
You can write a function that will sort your data but for now check this out:
http://www.php.net/manual/en/function.array-multisort.php
Here is what might help you:
$returnArray = array('car' => NULL, 'previous_car' => NULL);
foreach($info as $newInfo) {
$returnArray['car'][] = $newInfo['car'];
$returnArray['previous_car'][] = $newInfo['previous_car'];
}
$ret['car'] = array_count_values($returnArray['car']);
$ret['previous_car'] = array_count_values($returnArray['previous_car']);
var_dump($ret);
This returns:
array (size=2)
'car' =>
array (size=3)
'Audi' => int 3
'BMW' => int 1
'Ford' => int 1
'previous_car' =>
array (size=2)
'BMW' => int 3
'Seat' => int 2

Effective and elegant way to get an item from array

For example, i've got an array like this:
$a = array(
0 => array(
'foo' => 42
),
1 => array(
'foo' => 143
),
2 => array(
'foo' => 4
)
);
And i need to get an element with a maximum value in 'foo'. Current code is this:
$foos = array_map(function($v) {
return $v['foo'];
}, $a);
$keys = array_keys($foos, max($foos));
$winner = $a[$keys[0]];
print_r($winner);
Which is a scary thing.
You can try with:
$input = array(
array('foo' => 42),
array('foo' => 143),
array('foo' => 4),
);
$output = array_reduce($input, function($a, $b) {
return $a['foo'] > $b['foo'] ? $a : $b;
});
Output:
array (size=1)
'foo' => int 143
Most elegant?
$maxValue = max($a);
http://www.php.net/manual/en/function.max.php
Works even with multi-dimensional:
<?php
$a = array(
0 => array(
'foo' => 42
),
1 => array(
'foo' => 143
),
2 => array(
'foo' => 4
)
);
$maxValue = max($a);
print_r($maxValue["foo"]);
echo $maxValue["foo"];
Output with echo 143.
Output with print_r() Array ( [foo] => 143 )
Just run the foreach yourself. Readable and easy.
$highest_foo = PHP_INT_MIN;
$result = null;
foreach ($a as $key=>$foo) {
if ($foo['foo'] > $highest_foo) {
$result = $a[$key];
$highest_foo = $foo['foo'];
}
}
print_r($result);
$array = array(
0 => array(
'foo' => 42
),
1 => array(
'foo' => 143
),
2 => array(
'foo' => 4
)
);
$maxs = array_keys($array, max($array));
print_r(max($array));//Highest value
print_r($maxs);//Key of hishest value

Php algorithm - How to achieve that without eval

I have a class that keeps data stores/access data by using words.separated.by.dots keys and it behaves like the following:
$object = new MyArray()
$object->setParam('user.name','marcelo');
$object->setParam('user.email','some#email.com');
$object->getParams();
/*
array(
'user' => array(
'name' => 'marcelo',
'email' => 'some#email.com'
)
);
*/
It is working, but the method unsetParam() was horribly implemented. That happened because i didn't know how to achieve that without eval() function. Although it is working, I found that it was a really challenging algorithm and that you might find fun trying to achieve that without eval().
class MyArray {
/**
* #param string $key
* #return Mura_Session_Abstract
*/
public function unsetParam($key)
{
$params = $this->getParams();
$tmp = $params;
$keys = explode('.', $key);
foreach ($keys as $key) {
if (!isset($tmp[$key])) {
return $this;
}
$tmp = $tmp[$key];
}
// bad code!
$eval = "unset(\$params['" . implode("']['", $keys) . "']);";
eval($eval);
$this->setParams($params);
return $this;
}
}
The test method:
public function testCanUnsetNestedParam()
{
$params = array(
'1' => array(
'1' => array(
'1' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
'2' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
)
),
'2' => 'something'
);
$session = $this->newSession();
$session->setParams($params);
unset($params['1']['1']['1']);
$session->unsetParam('1.1.1');
$this->assertEquals($params, $session->getParams());
$this->assertEquals($params['1']['1']['2'], $session->getParam('1.1.2'));
}
Is this it?
<?php
$params = array(
'1' => array(
'1' => array(
'1' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
'2' => array(
'1' => 'one',
'2' => 'two',
'3' => 'three',
),
)
),
'2' => 'something'
);
function unsetParam( &$array, $paramString ) {
$cur =& $array;
$splitted = explode( ".", $paramString );
$len = count( $splitted ) - 1;
for( $i = 0; $i < $len; ++$i ) {
if( isset( $cur[ $splitted[ $i ] ] ) ) {
$cur =& $cur[ $splitted[ $i ] ];
}
else {
return false;
}
}
unset( $cur[ $splitted[$i] ] );
}
unsetParam( $params, "1.1.1");
print_r( $params );
/*
Array
(
[1] => Array
(
[1] => Array
(
[2] => Array
(
[1] => one
[2] => two
[3] => three
)
)
)
[2] => something
)
*/
You could make your code easier if you only split to a multidimension array in your getParams method:
class MyArray {
private $params = array();
public function setParam($key, $value) {
$this->params[$key] = $value;
}
/**
* #param string $key
* #return Mura_Session_Abstract
*/
public function unsetParam($key)
{
unset($this->params[$key]);
return $this;
}
public function getParams() {
$retval = array();
foreach ($this->params as $key => $value) {
$aux = &$retval;
foreach (explode(".", $key) as $subkey) {
if (!isset($aux[$subkey])) $aux[$subkey] = array();
$aux = &$aux[$subkey];
}
$aux = $value;
}
return $retval;
}
}
#gustavotkg and #Esailija have both offered some great ideas. Here is another simple, easy to understand, and short approach that avoids unset() altogether (which can get quirky in some cases).
This would, of course, be most useful when $params is limited to less than, say, 1k-10k of values (which starts to get a bit pricey in the CPU/memory dept):
<?php
$params = array(
'1' => array(
'1' => array(
'1' => array(
'1' => 'one-one',
'2' => 'one-two',
'3' => 'one-three',
),
'2' => array(
'1' => 'two-one',
'2' => 'two-two',
'3' => 'two-three',
),
)
),
'2' => 'something'
);
function filterParams($params, $refKey, $base = '') {
$newvals = array();
foreach($params as $k=>$v) {
$joinedKey = $base? $base . '.' . $k : $k;
if( $joinedKey != $refKey ) {
$newvals[$k] = is_array($v)? filterParams($v, $refKey, $joinedKey) : $v;
}
}
return $newvals;
}
var_dump(filterParams($params, '1.1.2'));

Categories