compare object properties and show diff in PHP - php

I'm searching for a way to show me the different properties/values from given objects...
$obj1 = new StdClass; $obj1->prop = 1;
$obj2 = new StdClass; $obj2->prop = 2;
var_dump(array_diff((array)$obj1, (array)$obj2));
//output array(1) { ["prop"]=> int(1) }
This works very well as long the property is not a object or array.
$obj1 = new StdClass; $obj1->prop = array(1,2);
$obj2 = new StdClass; $obj2->prop = array(1,3);
var_dump(array_diff((array)$obj1, (array)$obj2))
// Output array(0) { }
// Expected output - array { ["prop"]=> array { [1]=> int(2) } }
Is there a way to get rid of this, even when the property is another object ?!

Something like the following, which iterates through and does a recursive diff is the item in the array is itself an array could work:
Des similar work to array_diff, but it does a check to see if it is an array first (is_array) and if so, sets the diff for that key to be the diff for that array. Repeats recursively.
function recursive_array_diff($a1, $a2) {
$r = array();
foreach ($a1 as $k => $v) {
if (array_key_exists($k, $a2)) {
if (is_array($v)) {
$rad = recursive_array_diff($v, $a2[$k]);
if (count($rad)) { $r[$k] = $rad; }
} else {
if ($v != $a2[$k]) {
$r[$k] = $v;
}
}
} else {
$r[$k] = $v;
}
}
return $r;
}
It then works like this:
$obj1 = new StdClass; $obj1->prop = array(1,2);
$obj2 = new StdClass; $obj2->prop = array(1,3);
print_r(recursive_array_diff((array)$obj1, (array)$obj2));
/* Output:
Array
(
[prop] => Array
(
[1] => 2
)
)
*/

My solution will recursively diff a stdClass and all it nested arrays and stdClass objects.
It is meant to be used for comparison of rest api responses.
function objDiff($obj1, $obj2):array {
$a1 = (array)$obj1;
$a2 = (array)$obj2;
return arrDiff($a1, $a2);
}
function arrDiff(array $a1, array $a2):array {
$r = array();
foreach ($a1 as $k => $v) {
if (array_key_exists($k, $a2)) {
if ($v instanceof stdClass) {
$rad = objDiff($v, $a2[$k]);
if (count($rad)) { $r[$k] = $rad; }
}else if (is_array($v)){
$rad = arrDiff($v, $a2[$k]);
if (count($rad)) { $r[$k] = $rad; }
// required to avoid rounding errors due to the
// conversion from string representation to double
} else if (is_double($v)){
if (abs($v - $a2[$k]) > 0.000000000001) {
$r[$k] = array($v, $a2[$k]);
}
} else {
if ($v != $a2[$k]) {
$r[$k] = array($v, $a2[$k]);
}
}
} else {
$r[$k] = array($v, null);
}
}
return $r;
}
Here is a comparison function that I built using the pattern:
function objEq(stdClass $obj1, stdClass $obj2):bool {
$a1 = (array)$obj1;
$a2 = (array)$obj2;
return arrEq($a1, $a2);
}
function arrEq(array $a1, array $a2):bool {
foreach ($a1 as $k => $v) {
if (array_key_exists($k, $a2)) {
if ($v instanceof stdClass) {
$r = objEq($v, $a2[$k]);
if ($r === false) return false;
}else if (is_array($v)){
$r = arrEq($v, $a2[$k]);
if ($r === false) return false;
} else if (is_double($v)){
// required to avoid rounding errors due to the
// conversion from string representation to double
if (abs($v - $a2[$k]) > 0.000000000001) {
return false;
}
} else {
if ($v != $a2[$k]) {
return false;
}
}
} else {
return false;
}
}
return true;
}
Usage:
$apiResponse = apiCall(GET, $objId);
$responseObj = json_decode($apiResponse);
// do stuff ...
if(!objEq($myObj, $responseObj) apiCall(PUT, $myObj, $objId);
Note that the apiCall function is just a mock to illustrate the concept.
Also this solution is incomplete because it does not take into account any key->value pairs that are unique to obj2. In my use case this is not required and could be neglected.
NB: I borrowed heavily from Peter Hamiltons contribution. If you like what I did then please upvote his solution. Thanks!

Related

Parsing MultiDimensional PHP Array to Remove Duplicate Key Values

Having some issues parsing my multidimensional php array and removing duplicates. I've spent a good four hours trying to figure out what I'm doing wrong with no luck. If someone could help me out that would wonderful.
Format of multidimensional array:
Array(Array("id"=>?, "step_num"=>?, "desc"=>?))
Example data set:
Array(
[0]=> Array([id]=>1, [step_count]=>1, [desc]=>"Something"),
[1]=> Array([id]=>2, [step_count]=>1, [desc]=>"Something New"),
[2]=> Array([id]=>3, [step_count]=>1, [desc]=>"Something Newest")
)
Here's how I am trying to only have the step_count with the most recent desc by comparing id values: ($subStepsFound has the same format as the above array and $results is an empty array to begin with)
foreach($subStepsFound AS $step){
$found = false;
$removeEntry = false;
$index = 0;
foreach($results AS $key=>$result){
if($step['step_count'] == $result['step_count']){
$found = true;
if($step['id'] > $result['id']){
$removeEntry = true;
}
}
if($removeEntry === true){
$index = $key;
}
}
if($removeEntry === true){
//unset($results[$index]);
$results[$index] = $step;
}
if($found === false){
$results[] = $step;
}
}
Expected output of the resulting array:
Array(
[0]=> Array([id]=>4, [step_count]=>1, [desc]=>"Something Newest")
)
See 1., 2., 3. in comments:
foreach($subStepsFound AS $step){
$found = false;
$removeEntry = false;
$index = 0;
foreach($results AS $key=>$result){
if($step['step_count'] == $result['step_count']){
$found = true;
if($step['id'] > $result['id']){
$removeEntry = true;
}
}
if($removeEntry === true){
$results[$key] = $step; // 2. UP TO HERE
$removeEntry = false; // 3. RESET $removeEntry
}
}
/*
if($removeEntry === true){
//unset($results[$index]);
$results[$index] = $step; // 1. MOVE THIS...
}
*/
if($found === false){
$results[] = $step;
}
}
print_r($results);
Online example: http://sandbox.onlinephpfunctions.com/code/1db78a8c08cbee9d04fe1ca47a6ea359cacdd9e9
/*super_unique: Removes the duplicate sub-steps found*/
function super_unique($array,$key){
$temp_array = array();
foreach ($array as &$v) {
if (!isset($temp_array[$v[$key]])) $temp_array[$v[$key]] =& $v;
}
$array = array_values($temp_array);
return $array;
}
$results = super_unique($subStepsFound, 'step_count');

merge many array based on a common key php

I wasn't so clear in my first question, so i deleted it and here is a reformulation;
I have those arrays:
$open = array(array("FAI1","34"),array("FAI2","34"),array("FAI3","34"));
$click = array(array("FAI2","52"),array("FAI1","68"),array("FAI3","99"));
$unsubscribe = array(array("FAI2","103"),array("FAI3","67"),array("FAI1","102"));
$def_sent = array(array("FAI1","34",24),array("FAI2","34",23),array("FAI3","34",27));
$SB = array(array("FAI2","103"),array("FAI3","67"),array("FAI1","102"));
$HB = array(array("FAI2","103"),array("FAI3","67"),array("FAI1","102"));
I searched for a function to merge them and get a result like this:
$result = array(array("FAI1",34,68,102,34,24,102,102)
,array("FAI2","34",23.....),
array("FAI3","34",27....));
and to do this, i used the function, in the php online documentation, and this is the function
function array_merge_recursive() {
$arrays = func_get_args();
$base = array_shift($arrays);
foreach ($arrays as $array) {
reset($base);
while (list($key, $value) = #each($array)) {
if (is_array($value) && #is_array($base[$key])) {
$base[$key] = array_merge_recursive($base[$key], $value);
} else {
$base[$key] = $value;
}
}
}
return $base;
}
But instead of getting the result above i got this:
FAI1|34
FAI2|34
FAI3|34
FAI2|52
FAI1|68
FAI3|99
...
So i need some help to reformulate this function to get the expected result.
Try this function:
function array_merge_rec() {
$arrays = func_get_args();
$result = array();
foreach ($arrays as $arg) {
if (is_array($arg)) {
foreach ($arg as $item) {
if (!isset($result[$item[0]])) {
$result[$item[0]] = $item;
} else {
$result[$item[0]][] = $item[1];
}
}
} else {
echo "$arg skippend because it isn't array\n";
}
}
return array_values($result);
}
Does it help?

How to generate permutaion and combination result

i have this array
$dataArray = array
(
array(1,11,111),
array(2,22,222),
array(3,33,333),
array(4,44,444)
);
I also Google for it but no useful PHP script found..
Result using permutation combination method
1,11,111
1,111,11
11,111
1,11,
2,11,111
2,11,1
.
.
.
.
and yes i have already tried it with permutation combination method
function permutations(array $array, $r=false)
{
switch (count($array)) {
case 1:
return $array[0];
break;
}
$keys = array_keys($array);
$a = array_shift($array);
$k = array_shift($keys); // Get the key that $a had
$b = permutations($array, 'recursing');
$return = array();
foreach ($a as $v) {
if($v)
{
foreach ($b as $v2) {
if($r == 'recursing')
$return[] = array_merge(array($v), (array) $v2);
else
$return[] = array($k => $v) + array_combine($keys, $v2);
}
}
}
return $return;
}
$x = permutations($dataArray);
echo "<pre>";
print_r($x);

Remove duplicate values on an array with a condition in PHP

I want to remove some duplicate values on an array, but there is a condition that the script has to ignore the array that contains a specific word.
Below code is adapted from PHP: in_array.
$array = array( 'STK0000100001',
'STK0000100002',
'STK0000100001', //--> This should be remove
'STK0000100001-XXXX', //--> This should be ignored
'STK0000100001-XXXX' ); //--> This should be ignored
$ignore_values = array('-XXXX');
if(make_unique($array, $ignore_values) > 0) {
//ERROR HERE
}
The function to make the array unique is:
function make_unique($array, $ignore) {
$i = 0;
while($values = each($array)) {
if(!in_array($values[1], $ignore)) {
$dupes = array_keys($array, $values[1]);
unset($dupes[0]);
foreach($dupes as $rmv) {
$i++;
}
}
}
return $i;
}
I have tried to use if(!in_array(str_split($values[1]), $ignore)) ... but it just the same.
The array should become like:
STK0000100001
STK0000100002
STK0000100001-XXXX
STK0000100001-XXXX
How to do that?
Try this one, just remove the print_r(); inside the function when using in production
if(make_unique($array, $ignore_values) > 0) {
//ERROR HERE
}
function make_unique($array, $ignore) {
$array_hold = $array;
$ignore_val = array();
$i = 0;
foreach($array as $arr) {
foreach($ignore as $ign) {
if(strpos($arr, $ign)) {
array_push( $ignore_val, $arr);
unset($array_hold[$i]);
break;
}
}
$i++;
}
$unique_one = (array_unique($array_hold));
$unique_one = array_merge($unique_one,$ignore_val);
print_r($unique_one);
return count($array) - count($unique_one);
}
This should work for >= PHP 5.3.
$res = array_reduce($array, function ($res, $val) use ($ignore_values) {
$can_ignore = false;
foreach ($ignore_values as $ignore_val) {
if (substr($val, 0 - strlen($ignore_val)) == $ignore_val) {
$can_ignore = true;
break;
}
}
if ( $can_ignore || ! in_array($val, $res)) {
$res[] = $val;
}
return $res;
}, array()
);
Otherwise
$num_of_duplicates = 0;
$res = array();
foreach ($array as $val) {
$can_ignore = false;
foreach ($ignore_values as $ignore_val) {
if (substr($val, 0 - strlen($ignore_val)) == $ignore_val) {
$num_of_duplicates++;
$can_ignore = true;
break;
}
}
if ( $can_ignore || ! in_array($val, $res)) {
$res[] = $val;
}
}
Edit: Added duplicate count to the second snippet.

Converting keys of an array/object-tree to lowercase

I am currently optimizing a PHP application and found one function being called around 10-20k times, so I'd thought I'd start optimization there:
function keysToLower($obj)
{
if(!is_object($obj) && !is_array($obj)) return $obj;
foreach($obj as $key=>$element)
{
$element=keysToLower($element);
if(is_object($obj))
{
$obj->{strtolower($key)}=$element;
if(!ctype_lower($key)) unset($obj->{$key});
}
else if(is_array($obj) && ctype_upper($key))
{
$obj[strtolower($key)]=$element;
unset($obj[$key]);
}
}
return $obj;
}
Most of the time is spent in recursive calls (which are quite slow in PHP), but I don't see any way to convert it to a loop.
What would you do?
This version doesn't account for associative arrays since my data doesn't have any, but is nearly 10 times faster than the original version. Most of the work was done by Gumbo, the major speedup comes from using references and creating a new object instead of unsetting the old keys.
function &keysToLower(&$obj)
{
if(is_object($obj))
{
$newobj = (object) array();
foreach ($obj as $key => &$val)
$newobj->{strtolower($key)} = keysToLower($val);
$obj=$newobj;
}
else if(is_array($obj))
foreach($obj as &$value)
keysToLower($value);
return $obj;
}
Foreach is using an internal copy that is then traversed. Try it without:
function keysToLower($obj)
{
$type = (int) is_object($obj) - (int) is_array($obj);
if ($type === 0) return $obj;
reset($obj);
while (($key = key($obj)) !== null)
{
$element = keysToLower(current($obj));
switch ($type)
{
case 1:
if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
{
unset($obj->{$key});
$key = $keyLowercase;
}
$obj->{$key} = $element;
break;
case -1:
if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
{
unset($obj[$key]);
$key = $keyLowercase;
}
$obj[$key] = $element;
break;
}
next($obj);
}
return $obj;
}
Or use references to avoid that a copy is used:
function &keysToLower(&$obj)
{
$type = (int) is_object($obj) - (int) is_array($obj);
if ($type === 0) return $obj;
foreach ($obj as $key => &$val)
{
$element = keysToLower($val);
switch ($type)
{
case 1:
if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
{
unset($obj->{$key});
$key = $keyLowercase;
}
$obj->{$key} = $element;
break;
case -1:
if (!is_int($key) && $key !== ($keyLowercase = strtolower($key)))
{
unset($obj[$key]);
$key = $keyLowercase;
}
$obj[$key] = $element;
break;
}
}
return $obj;
}
You might also want to lookup array_change_key_case()
I assume you don't care about casting to array...
function keys_to_lower($o) {
if (is_object($o)) {
$o = (array)$o;
}
if (is_array($o)) {
return array_map('keys_to_lower', array_change_key_case($o));
}
else {
return $o;
}
}
here a example using lambda:
$multiArrayChangeKeyCase = function (&$array) use (&$multiArrayChangeKeyCase) {
$array = array_change_key_case($array);
foreach ($array as $key => $row)
if (is_array($row))
$multiArrayChangeKeyCase($array[$key]);
};
array_combine(array_map("strtolower", array_keys($a)), array_values($a))
A some what late response to a old thread but, there's a native function that does this, you could wrap it up something along these lines.
function setKeyCasing($thing, $case = CASE_LOWER) {
return array_change_key_case((array) $thing, $case);
}

Categories