Group related elements in array - php

I have an array in the following format:
[8106] => Array (
[id1] => 210470
[id2] => 216298
)
[8107] => Array (
[id1] => 210470
[id2] => 187145
)
[8108] => Array (
[id1] => 187145
[id2] => 216298
)
[8109] => Array (
[id1] => 187145
[id2] => 210470
)
[8110] => Array (
[id1] => 266533
[id2] => 249612
)
[8111] => Array (
[id1] => 249612
[id2] => 266533
)
I need to get it into the following format:
[0] => Array (
[0] => 266533
[1] => 249612
)
[1] => Array (
[0] => 187145
[1] => 210470
[2] => 216298
)
Basically, I need to extract all the ids, keep the relationships, but group them all together. I have a function to do this, but it takes forever (I am up to 30+ minutes on the number of rows I have to run through). Keys and order are unimportant. The relationship is all that is important. I am looking for a faster method. The function(s) I am using are below:
function getMatchingIDs($filteredArray)
{
$result = array();
$resultCount = 0;
foreach ($filteredArray as $details) {
$imaId1 = inMultiArray($details['id1'], $result);
$imaId2 = inMultiArray($details['id2'], $result);
if ($imaId1 === false && $imaId2 === false) {
$result[$resultCount++] = array(
$details['id1'],
$details['id2'],
);
} elseif (is_numeric($imaId1) === true && $imaId2 === false) {
$result[$imaId1][] = $details['id2'];
} elseif ($imaId1 === false && is_numeric($imaId2) === true) {
$result[$imaId2][] = $details['id1'];
} elseif ($imaId2 != $imaId1) {
$result[$imaId1] = array_merge($result[$imaId1], $result[$imaId2]);
unset($result[$imaId2]);
}
}
return $result;
}
function inMultiArray($elem, $array)
{
if (is_array($array) === true) {
// if the variable $elem is in the variable $array return true
if (is_array($array) === true && in_array($elem, $array) === true) {
return true;
}
// if $elem isn't in $array, then check foreach element
foreach ($array as $key => $arrayElement) {
// if $arrayElement is an array call the inMultiArray function to this element
// if inMultiArray returns true, than return is in array, else check next element
if (is_array($arrayElement) === true) {
$value = inMultiArray($elem, $arrayElement);
if ($value === true) {
return $key;
}
}
}
}
// if isn't in array return false
return false;
}
$filtered = getMatchingIDs($unfiltered);
EDIT: The original array describes relations between pairs of ids (not shown in the array). The desired output is that the relations are further defined. If you look in the original array, elements 8106-8109 are simply paired combinations of three ids. I need those three grouped together. Elements 8110 and 8111 are a distinct pair, just in a different order.

$newArray = array();
foreach ($array as $k => $v) {
$newArray[0][] = $v['id1'];
$newArray[1][] = $v['id2'];
}

What I finally ended up doing was in essence creating an index array. This array held all the positions of each value in the primary array.
So the following array
[0] => Array (
[0] => 266533
[1] => 249612
)
[1] => Array (
[0] => 187145
[1] => 210470
[2] => 216298
)
has an index of:
[187145] => 1
[210470] => 1
[216298] => 1
[249612] => 0
[266533] => 0
So instead of looking for the value in the primary multidimensional array, I check to see if it exists in the index array and process the data based on that. The results are that it now runs the entire process in <5 seconds instead of > 1 hour.
Thank you for your help.

Related

Sort array values based on parent/child relationship

I am trying to sort an array to ensure that the parent of any item always exists before it in the array. For example:
Array
(
[0] => Array
(
[0] => 207306
[1] => Bob
[2] =>
)
[1] => Array
(
[0] => 199730
[1] => Sam
[2] => 199714
)
[2] => Array
(
[0] => 199728
[1] => Simon
[2] => 207306
)
[3] => Array
(
[0] => 199714
[1] => John
[2] => 207306
)
[4] => Array
(
[0] => 199716
[1] => Tom
[2] => 199718
)
[5] => Array
(
[0] => 199718
[1] => Phillip
[2] => 207306
)
[6] => Array
(
[0] => 199720
[1] => James
[2] => 207306
)
)
In the above array this "fails" as [1][2] (Sam) does not yet exist and nor does [4][2] (Tom).
The correct output would be as, in this case, as both Sam and Tom's parents already exist before they appear in the array:
Array
(
[0] => Array
(
[0] => 207306
[1] => Bob
[2] =>
)
[1] => Array
(
[0] => 199714
[1] => John
[2] => 207306
)
[2] => Array
(
[0] => 199730
[1] => Sam
[2] => 199714
)
[3] => Array
(
[0] => 199728
[1] => Simon
[2] => 207306
)
[4] => Array
(
[0] => 199718
[1] => Phillip
[2] => 207306
)
[5] => Array
(
[0] => 199716
[1] => Tom
[2] => 199718
)
[6] => Array
(
[0] => 199720
[1] => James
[2] => 207306
)
)
I found an answer https://stackoverflow.com/a/12961400/1278201 which was very close but it only seems to go one level deep (i.e. there is only ever one parent) whereas in my case there could be 1 or 10 levels deep in the hierarchy.
How do I sort the array so no value can appear unless its parent already exists before it?
This will trivially order the array (in O(n)) putting first all those with no parent, then these whose parent is already in the array, iteratively, until there's no children having the current element as parent.
# map the children by parent
$parents = ['' => []];
foreach ($array as $val) {
$parents[$val[2]][] = $val;
}
# start with those with no parent
$sorted = $parents[''];
# add the children the current nodes are parent of until the array is empty
foreach ($sorted as &$val) {
if (isset($parents[$val[0]])) {
foreach ($parents[$val[0]] as $next) {
$sorted[] = $next;
}
}
}
This code requires PHP 7, it may not work in some cases under PHP 5. - for PHP 5 compatibility you will have to swap the foreach ($sorted as &$val) with for ($val = reset($sorted); $val; $val = next($sorted)):
# a bit slower loop which works in all versions
for ($val = reset($sorted); $val; $val = next($sorted)) {
if (isset($parents[$val[0]])) {
foreach ($parents[$val[0]] as $next) {
$sorted[] = $next;
}
}
}
Live demo: https://3v4l.org/Uk6Gs
I have two different version for you.
a) Using a "walk the tree" approach with recursion and references to minimize memory consumption
$data = [
[207306,'Bob',''], [199730,'Sam',199714],
[199728,'Simon',207306], [199714,'John',207306],
[199716, 'Tom',199718], [199718,'Phillip',207306],
[199720,'James',207306]
];
$list = [];
generateList($data, '', $list);
var_dump($list);
function generateList($data, $id, &$list) {
foreach($data as $d) {
if($d[2] == $id) {
$list[] = $d; // Child found, add it to list
generateList($data, $d[0], $list); // Now search for childs of this child
}
}
}
b) Using phps built in uusort()function (seems only to work up to php 5.x and not with php7+)
$data = [
[207306,'Bob',''], [199730,'Sam',199714],
[199728,'Simon',207306], [199714,'John',207306],
[199716, 'Tom',199718], [199718,'Phillip',207306],
[199720,'James',207306]
];
usort($data, 'cmp');
var_dump($data);
function cmp($a, $b) {
if($a[2] == '' || $a[0] == $b[2]) return -1; //$a is root element or $b is child of $a
if($b[2] == '' || $b[0] == $a[2]) return 1; //$b is root element or $a is child of $b
return 0; // both elements have no direct relation
}
I checked this works in PHP 5.6 and PHP 7
Sample array:
$array = Array(0 => Array(
0 => 207306,
1 => 'Bob',
2 => '',
),
1 => Array
(
0 => 199730,
1 => 'Sam',
2 => 199714,
),
2 => Array
(
0 => 199728,
1 => 'Simon',
2 => 207306,
),
3 => Array
(
0 => 199714,
1 => 'John',
2 => 207306,
),
4 => Array
(
0 => 199716,
1 => 'Tom',
2 => 199718,
),
5 => Array
(
0 => 199718,
1 => 'Phillip',
2 => 207306,
),
6 => Array
(
0 => 199720,
1 => 'James',
2 => 207306,
),
);
echo "<pre>";
$emp = array();
//form the array with parent and child
foreach ($array as $val) {
$manager = ($val[2] == '') ? 0 : $val[2];
$exist = array_search_key($val[2], $emp);
if ($exist)
$emp[$exist[0]][$val[0]] = $val;
else
//print_R(array_search_key(199714,$emp));
$emp[$manager][$val[0]] = $val;
}
$u_emp = $emp[0];
unset($emp[0]);
//associate the correct child/emp after the manager
foreach ($emp as $k => $val) {
$exist = array_search_key($k, $u_emp);
$pos = array_search($k, array_keys($u_emp));
$u_emp = array_slice($u_emp, 0, $pos+1, true) +
$val +
array_slice($u_emp, $pos-1, count($u_emp) - 1, true);
}
print_R($u_emp); //print the final result
// key search function from the array
function array_search_key($needle_key, $array, $parent = array())
{
foreach ($array AS $key => $value) {
$parent = array();
if ($key == $needle_key)
return $parent;
if (is_array($value)) {
array_push($parent, $key);
if (($result = array_search_key($needle_key, $value, $parent)) !== false)
return $parent;
}
}
return false;
}
Find the below code that might be helpful.So, your output is stored in $sortedarray.
$a=array(array(207306,'Bob',''),
array (199730,'Sam',199714),
array(199728,'Simon',207306),
array(199714,'John',207306),
array(199716,'Tom',199718),
array(199718,'Phillip',207306),
array(199720,'James',207306));
$sortedarray=$a;
foreach($a as $key=>$value){
$checkvalue=$value[2];
$checkkey=$key;
foreach($a as $key2=>$value2){
if($key<$key2){
if ($value2[0]===$checkvalue){
$sortedarray[$key]=$value2;
$sortedarray[$key2]=$value;
}else{
}
}
}
}
print_r($sortedarray);
What about this approach:
Create an empty array result.
Loop over your array and only take the items out of it where [2] is empty and insert them into result.
When this Loop is done you use a foreach-Loop inside a while-loop. With the foreach-Loop you take every item out of your array where [2] is already part of result. And you do this as long as your array contains anything.
$result = array();
$result[''] = 'root';
while(!empty($yourArray)){
foreach($yourArray as $i=>$value){
if(isset($result[$value[2]])){
// use the next line only to show old order
$value['oldIndex'] = $i;
$result[$value[0]] = $value;
unset($yourArray[$i]);
}
}
}
unset($result['']);
PS: You may run into trouble by removing parts of an array while walking over it. If you do so ... try to solve this :)
PPS: Think about a break condition if your array have an unsolved loop or a child without an parent.
you can use your array in variable $arr and use this code it will give you required output.
function check($a, $b) {
return ($a[0] == $b[2]) ? -1 : 1;
}
uasort($arr, 'check');
echo '<pre>';
print_r(array_values($arr));
echo '</pre>';

Converting Boolean values inside array into if statement

What is the best way to convert array values in to if statement. The array values should be compared by AND and the arrays by OR. I can do this using EVAL but looking for something other than EVAL.
$arr = Array(
[0] => Array
(
[0] => false
[1] => true
)
[1] => Array
(
[0] => true
[1] => false
)
[2] => Array
(
[0] => false
)
[3] => Array
(
[0] => true
)
[4] => Array
(
[0] => true,
[1] => true,
[2] => true
)
)
I want to build comparison dynamically for if statement..
For example
if( ($arr[0][0] && $arr[0][1]) || ($arr[1][0] && $arr[1][1]) .... and so on )
$result = array_reduce($arr, function ($result, array $values) {
return $result || array_reduce($values, function ($result, $value) {
return $result && $value;
}, true);
}, false);
if ($result) ...
One array_reduce can elegantly do this for one array, you simply want to do it for two nested array levels.
What you are doing is generating a single boolean from values in an array. This is known as reduce operation. In php, we can do this with array_reduce(..).
array_reduce($input,
function($prev, $item) {
//Short-circuit if we already deduced it to be true
if($prev) {
return $prev;
}
//If one value is false, the entire thing is false
foreach($item as $k => $v) {
if(!$v) {
return false;
}
}
//All were true
return true;
}, false);

Group rows by one column, only create deeper subarrays when multiple rows in group

I have a multi-dimensional array like this:
Array
(
[0] => Array
(
[id] => 1
[email_id] => ok#gmail.com
[password] => test
)
[1] => Array
(
[id] => 2
[email_id] => check#gmail.com
[password] => test
)
[2] => Array
(
[id] => 3
[email_id] => an#gmail.com
[password] => pass
)
)
In the above array, password value is the same in two different rows. I need to merge the arrays which have duplicate values to get the following output:
Array
(
[0] => Array
(
[0] => Array
(
[id] => 1
[email_id] => ok#gmail.com
[password] => test
)
[1] => Array
(
[id] => 2
[email_id] => check#gmail.com
[password] => test
)
)
[1] => Array
(
[id] => 3
[email_id] => an#gmail.com
[password] => pass
)
)
How to do this? I've tried array_merge() & foreach() loops, but I can't get this output.
Try,
$arr = array( array('id'=>1, 'email_id'=>'ok#gmail.com', 'password'=>'test'),
array('id'=>2, 'email_id'=>'check#gmail.com', 'password'=>'test'),
array('id'=>3, 'email_id'=>'an#gmail.com', 'password'=>'pass'));
$new_arr = array();
foreach($arr as $k => $v) {
if( is_array($arr[$k+1]) && $arr[$k]['password'] === $arr[$k + 1]['password'] )
$new_arr[] = array($arr[$k], $arr[$k+1]);
else if( in_array_recursive($arr[$k]['password'], $new_arr) === FALSE )
$new_arr[] = $v;
}
function in_array_recursive( $val, $arr) {
foreach( $arr as $v ) {
foreach($v as $m) {
if( in_array($val, $m ) )
return TRUE;
}
}
return FALSE;
}
print_r($new_arr);
Demo
You only want to create more depth in your output array if there is more than one entry in the group. I personally wouldn't want that kind of variability in a data structure because the code that will print the data will need to go to extra trouble to handle associative rows of data that might be on different levels.
Anyhow, here's how I would do it with one loop...
Foreach Loop Code: (Demo)
$result = [];
foreach ($array as $row) {
if (!isset($result[$row['password']])) {
$result[$row['password']] = $row; // save shallow
} else {
if (isset($result[$row['password']]['id'])) { // if not deep
$result[$row['password']] = [$result[$row['password']]]; // make original deeper
}
$result[$row['password']][] = $row; // save deep
}
}
var_export(array_values($result)); // print without temporary keys
Functional Code: (Demo)
var_export(
array_values(
array_reduce(
$array,
function($result, $row) {
if (!isset($result[$row['password']])) {
$result[$row['password']] = $row;
} else {
if (isset($result[$row['password']]['id'])) {
$result[$row['password']] = [$result[$row['password']]];
}
$result[$row['password']][] = $row;
}
return $result;
},
[]
)
)
);

PHP function returning array index path

I've been trying to write a PHP function which searches the id index valeus in array, and once found, returns the path which lead to it's discovery.
Take the following array:
Array
(
[0] => Array
(
[id] => 1
[data] => Array
(
[0] => Array
(
[id] => 8
)
[1] => Array
(
[id] => 9
)
[2] => Array
(
[id] => 10
[data] => Array
(
[0] => Array
(
[id] => 15
[data] => Array
(
[0] => Array
(
[id] => 22
)
)
)
[1] => Array
(
[id] => 21
)
)
)
)
)
)
If looking for [id] => 21 it would return array(1,10). However, in numerous attempts I have failed. The set path should be set to the index id. However, I cannot figure it out. Any words of guidance are much appreciated.
This functions returns array(1,10) for OP example
(will leave that other answer just in case someone will look for normal "path searching")
function search_data($needle, $haystack) {
if (is_array($haystack)) {
foreach($haystack as $data) {
if ($data['id'] == $needle) return array();
if (isset($data['data'])) {
if (($path = search_data($needle, $data['data'])) !== false) return array_merge(array($data['id']), $path);
}
}
}
return false;
}
This functions returns array(0,'data',2,'data',1,'id') for OP example (i.e. full path to value)
Function which searches for $key => $value pair in array and returns the path:
function array_search_r($key, $value, $haystack, $strict = null) {
$strict = $strict ?: false;
if (is_array($haystack)) {
foreach($haystack as $k => $v) {
if ($strict ? ($k === $key && $v === $value) : ($k == $key && $v == $value)) return array($k);
if(($path = array_search_r($key, $value, $v, $strict)) !== false) return array_merge(array($k), $path);
}
}
return false;
}

PHP: Iterating through array?

I want a function that
searches through my array, and
returns all the
children to a specific node. What is
the most appropriate way to do this?
Will recursion be necessary in this case?
I have previously constructed a few quite complex functions that iterates with or without the help of recursion through multi-dimensional arrays and re-arranging them, but this problem makes me completely stuck and I can't just get my head around it...
Here's my array:
Array
(
[1] => Array (
[id] => 1
[parent] => 0
)
[2] => Array (
[id] => 2
[parent] => 1
)
[3] => Array (
[id] => 3
[parent] => 2
)
)
UPDATE:
The output which I want to get. Sorry for the bad example, but I'll blame it on lack of knowledge on how to format the stuff I need to do :)
function getAllChildren($id) {
// Psuedocode
return $array;
}
getAllChildren(1); // Outputs the following:
Array
(
[2] => Array (
[id] => 2
[parent] => 1
)
[3] => Array (
[id] => 3
[parent] => 2
)
)
$nodes = array( 1 => array ( 'id' => 1,
'parent' => 0
),
2 => array ( 'id' => 2,
'parent' => 1
),
3 => array ( 'id' => 3,
'parent' => 2
)
);
function searchItem($needle,$haystack) {
$nodes = array();
foreach ($haystack as $key => $item) {
if ($item['parent'] == $needle) {
$nodes[$key] = $item;
$nodes = $nodes + searchItem($item['id'],$haystack);
}
}
return $nodes;
}
$result = searchItem('1',$nodes);
echo '<pre>';
var_dump($result);
echo '</pre>';
Non-recursive version of the searchItem() function:
function searchItem($needle,$haystack) {
$nodes = array();
foreach ($haystack as $key => $item) {
if (($item['parent'] == $needle) || array_key_exists($item['parent'],$nodes)) {
$nodes[$key] = $item;
}
}
return $nodes;
}
(assumes ordering of the parents/children, so a child node isn't included in the array unless the parent is already there)
<?php
function searchItem($needle)
{
foreach ($data as $key => $item)
{
if ($item['id'] == $needle)
{
return $key;
}
}
return null;
}
?>
Check out the array_walk_recursive() function in PHP:
http://www.php.net/manual/en/function.array-walk-recursive.php

Categories