Parsing MultiDimensional PHP Array to Remove Duplicate Key Values - php

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

Related

Recursive array map from multidimentional array

I need to build a map of all possibilities from multidimentional array in the following format:
$level['A1']['E1'] = true;
$level['E1']['A1'] = true;
$level['A1']['L1'] = true;
$level['L1']['A1'] = true;
$level['A1']['E2'] = true;
$level['E2']['A1'] = true;
$level['E1']['L1'] = true;
$level['L1']['E1'] = true;
$level['E1']['E2'] = true;
$level['E2']['E1'] = true;
Result needs to have all possible maps in another multidimentional array from A1 to A1, such as:
$level_maps['A1']['E1']['L1']['A1'] = true;
$level_maps['A1']['E1']['E2']['A1'] = true;
$level_maps['A1']['L1']['E1']['A1'] = true;
$level_maps['A1']['L1']['E1']['E2']['A1'] = true;
$level_maps['A1']['E2']['E1']['A1'] = true;
$level_maps['A1']['E2']['E1']['L1']['A1'] = true;
Initial array can be much bigger and I'm having a hard time with a recursive function.
Edit:
I have a simple class with the following function:
private function buildMap(array $levels, $parent) {
$map = array();
foreach ($levels as $level1 => $level2_vals) {
foreach($level2_vals as $level2 => $val) {
if ($parent == $level1) {
$map[$level2] = true;
unset($levels[$level1][$level2]);
$level3 = $this->buildMap($levels, $level2);
if(!empty($level3) && $level2 != $this->end_level) {
$map[$level2] = $level3;
}
else if($level2 != $this->end_level) {
$map[$level2] = false;
}
}
}
}
return $map;
}
calling it with:
$map[$this->start_level] = $this->buildMap($level, $this->start_level);
but it does not work correctly, especially on bigger sets of levels.
Sorry if I am unclear, was just looking for a recursive function example to start with.
Here how it work check the example bellow :
<?php
$level['A1']['E1'] = true;
$level['E1']['A1'] = true;
$level['A1']['L1'] = true;
$level['L1']['A1'] = true;
$level['A1']['E2'] = true;
$level['E2']['A1'] = true;
$level['E1']['L1'] = true;
$level['L1']['E1'] = true;
$level['E1']['E2'] = true;
$level['E2']['E1'] = true;
/// generate array key
$array_keys=array();
foreach($level as $key1 =>$values)
{
foreach($values as $key2 => $values2)
{
$array_keys[]=$key1.'-'.$key2;
}
}
/// split array parent and childrent on diffrent parts
$parent='A1';
$parent_keys=array();
$children_keys=array();
foreach($level as $key =>$val)
{
foreach($val as $key2 =>$val2)
{
if($key == $parent)
$parent_keys[$key.'-'.$key2]=true;
else
$children_keys[$key.'-'.$key2]=true;
}
}
$last_array=array();
foreach($parent_keys as $key =>$va)
{
$exp=explode('-',$key);
foreach($children_keys as $key2 =>$va2)
{
$arrunique=array();
if(!strchr($key2,$exp[1])){
$exp2=explode('-',$key2);
foreach($exp as $r)
{
$arrunique[$r]=$r;
}
foreach($exp2 as $r)
{
$arrunique[$r]=$r;
}
if(in_array($exp[1].'-'.$exp2[0],$array_keys))
$last_array[implode('-',$arrunique).'-'.$exp[0]]=true;
}
}
}
foreach($last_array as $key=>$va)
{
print $key."\n";
}
You can execute from sandbox : LINK

PHP recursive function nesting level reached

Good day. I have a parcer function that taker an array of string like this:
['str','str2','str2','*str','*str2','**str2','str','str2','str2']
And recursivelly elevates a level those starting with asterix to get this
['str','str2','str2',['str','str2',['str2']],'str','str2','str2']
And the function is:
function recursive_array_parser($ARRAY) {
do {
$i = 0;
$s = null;
$e = null;
$err = false;
foreach ($ARRAY as $item) {
if (!is_array($item)) { //if element is not array
$item = trim($item);
if ($item[0] === '*' && $s == null && $e == null) { //we get it's start and end if it has asterix
$s = $i;
$e = $i;
} elseif ($item[0] === '*' && $e != null)
$e = $i;
elseif (!isset($ARRAY[$i + 1]) || $s != null && $e != null) { //if there are no elements or asterix element ended we elevate it
$e = $e == NULL ? $i : $e;
$head = array_slice($ARRAY, 0, $s);
$_x = [];
$inner = array_slice($ARRAY, $s, $e - $s + 1);
foreach ($inner as $_i)
$_x[] = substr($_i, 1);
$inner = [$_x];
$tail = array_slice($ARRAY, $e + 1, 999) or [];
$X = array_merge($head, $inner);
$ARRAY = array_merge($X, $tail);
$s = null;
$e = null;
$err = true;
}
} else {
$ARRAY[$i] = recursive_array_parser($ARRAY[$i]); //if the item is array of items we recur.
}
$i++;
if ($err == true) {
break 1;
}
}
} while ($err);
return $ARRAY;
}
When this function runs, i get "Fatal error: Maximum function nesting level of '200' reached, aborting!" error.
I know it has something to do with infinite recursion, but i can't track the particular place where it occurs, and this is strange.
I don't normally rewrite code, but your code can be reduced and simplified while, from what I can see, getting the desired result. See if this works for you:
$a = array('a','b','c','*d','*e','**f','g','*h');
print_r($a);
$a = recursive_array_parser($a);
print_r($a);
function recursive_array_parser($array)
{
$ret = array();
for($i=0; $i<sizeof($array); $i++)
{
if($array[$i]{0}!='*') $ret[] = $array[$i];
else
{
$tmp = array();
for($j=$i; $j<sizeof($array) && $array[$j]{0}=='*'; $j++)
{
$tmp[] = substr($array[$j],1);
}
$ret[] = recursive_array_parser($tmp);
$i = $j-1;
}
}
return $ret;
}
Note that it isn't possible for $array[$i] to be an array, so that check is removed. The recursion takes place on the temp array created when a * is found. The $i is closer tied to $array to reset it properly after parsing the series of * elements.
Here's my solution. No nested loops.
function recursive_array_parser($arr) {
$out = array();
$sub = null;
foreach($arr as $item) {
if($item[0] == '*') { // We've hit a special item!
if(!is_array($sub)) { // We're not currently accumulating a sub-array, let's make one!
$sub = array();
}
$sub[] = substr($item, 1); // Add it to the sub-array without the '*'
} else {
if(is_array($sub)) {
// Whoops, we have an active subarray, but this thing didn't start with '*'. End that sub-array
$out[] = recursive_array_parser($sub);
$sub = null;
}
// Take the item
$out[] = $item;
}
}
if(is_array($sub)) { // We ended in an active sub-array. Add it.
$out[] = recursive_array_parser($sub);
$sub = null;
}
return $out;
}

PHP: Getting difference between two multidimensional arrays

I have two arrays like
$a1= array(
array('a'=>1,'b'=>2, 'c'=>3), // similar to $a2[0]
array('a'=>3,'b'=>4, 'c'=>5), // similar to $a2[1]
array('a'=>9,'b'=>6, 'c'=>9)
);
$a2= array(
array('a'=>1,'b'=>2, 'c'=>3),
array('a'=>3,'b'=>4, 'c'=>5),
array('a'=>5,'b'=>6, 'c'=>7),
array('a'=>11,'b'=>4, 'c'=>13),
array('a'=>14,'b'=>6, 'c'=>3)
);
I want a resulting array that does't have common values like
$arrayResult= array(
array('a'=>9,'b'=>6, 'c'=>9),// from $a1[3]
array('a'=>5,'b'=>6, 'c'=>7),// from $a2[2]
array('a'=>11,'b'=>4, 'c'=>13),// from $a2[3]
array('a'=>14,'b'=>6, 'c'=>3)// from $a2[4]
);
I have tried array_udiff, and also separate function but I'm unable to get the right thing.
Hmm fast solution, not optimized
$result = array();
foreach($a1 as $va1) {
$found = false;
foreach($a2 as $va2) {
$x = array_diff($va1, $va2);
if (empty($x)) {
$found = true;
}
}
if (!$found) {
$result[] = $va1;
}
}
foreach($a2 as $va2) {
$found = false;
foreach($a1 as $va1) {
$x = array_diff($va2, $va1);
if (empty($x)) {
$found = true;
}
}
if (!$found) {
$result[] = $va2;
}
}
var_dump($result);
EDIT: A little optimized (unseting values that were found before):
$a1= array(
array('a'=>1,'b'=>2, 'c'=>3), // similar to $a2[0]
array('a'=>3,'b'=>4, 'c'=>5), // similar to $a2[1]
array('a'=>9,'b'=>6, 'c'=>9)
);
$a2= array(
array('a'=>1,'b'=>2, 'c'=>3),
array('a'=>3,'b'=>4, 'c'=>5),
array('a'=>5,'b'=>6, 'c'=>7),
array('a'=>11,'b'=>4, 'c'=>13),
array('a'=>14,'b'=>6, 'c'=>3)
);
$i=0;
$result = array();
foreach($a1 as $ka1 => $va1) {
$found = false;
foreach($a2 as $ka2 => $va2) {
$i++;
$x = array_diff($va1, $va2);
if (empty($x)) {
$found = true;
unset($a2[$ka2], $a1[$ka1]);
}
}
if (!$found) {
$result[] = $va1;
}
}
foreach($a2 as $ka2 => $va2) {
$found = false;
foreach($a1 as $ka1 => $va1) {
$i++;
$x = array_diff($va2, $va1);
if (empty($x)) {
unset($a2[$ka2], $a1[$ka1]);
$found = true;
}
}
if (!$found) {
$result[] = $va2;
}
}
var_dump($result);echo $i;
function check_diff_multi($array1, $array2){
$result = array();
foreach($array1 as $key => $val) {
if(isset($array2[$key])){
if(is_array($val) && is_array($array2[$key])){
$result[$key] = check_diff_multi($val, $array2[$key]);
}
} else {
$result[$key] = $val;
}
}
return $result;
}
//call this function
check_diff_multi($a1,$a2);

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.

compare object properties and show diff in 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!

Categories