detect where elements in array moved - php

I have to be able to detect all changes in a "guild" (like an activity feed), with new and old data. The data is presented in this fashion, as an array:
{"GUILDMASTER":["foo"],"OFFICER":["bar","baz"],"MEMBER":["foobar","foobaz"]}
I need to detect if, for example, "bar" moves from his current rank down one (to "MEMBER"), it will output an array something like this:
[{"user":"bar","was_found_in":"OFFICER","now_found_in":"MEMBER"}]
What I currently have, below, only detects if a member has joined of left, is there any way to extend this to fulfill what I want it to?
function compareThem($a, $b) {
$flat_new = call_user_func_array('array_merge', $a);
$flat_old = call_user_func_array('array_merge', $b);
$rm = array();
if($flat_new != $flat_old) {
$new_old = array_diff($flat_new, $flat_old);
$old_new = array_diff($flat_old, $flat_new);
$diff = array_merge($new_old, $old_new);
foreach ($diff as $key => $value) {
if(in_array($value, $flat_new) && !in_array($value, $flat_old)) {
$rm[] = array("new"=>true, "left"=>false, "user"=>$value);
} else if(in_array($value, $flat_old) && !in_array($value, $flat_new)) {
$rm[] = array("new"=>false, "left"=>true, "user"=>$value);
}
}
}
return $rm;
}
$new = array("GUILDMASTER" => array("foo"), "OFFICER" => array("bar", "baz"), "MEMBER" => array("foobar", "foobaz"));
$old = array("GUILDMASTER" => array("foo"), "OFFICER" => array("bar", "baz"), "MEMBER" => array("foobar"));
compareThem($new,$old) // will output [{"new":true,"left":false,"user":"foobaz"}]

You can do this. It detects additions, deletions, and modifications:
// your array before the change
$before = ["GUILDMASTER" => ["foo"], "OFFICER" => ["bar","baz"], "MEMBER" => ["foobar","foobaz"]];
// your array after the change (foo was moved from GUILDMASTER to OFFICER)
$after = ["GUILDMASTER" => [], "OFFICER" => ["bar","baz", "foo"], "MEMBER" => ["foobar","foobaz"]];
// create lookup table to check the position a person previously held
$positions_before = [];
foreach($before as $position => $people) {
foreach($people as $person) {
$positions_before[$person] = $position;
}
}
// scan new positions...
$changes = [];
foreach($after as $position => $people) {
foreach($people as $person) {
// track when a new person is added (they wont be in the table)
if(!isset($positions_before[$person])) {
$changes[] = ["user" => $person, "was_found_in" => "DIDNT_EXIST", "now_found_in" => $position];
// track when a change is detected (different value than table)
}elseif($positions_before[$person] != $position) {
$changes[] = ["user" => $person, "was_found_in" => $positions_before[$person], "now_found_in" => $position];
}
// remove this person from the table after parsing them
unset($positions_before[$person]);
}
}
// anyone left in the lookup table is in $before and $after
// so track that they were removed/fired
foreach($positions_before as $person => $position) {
$changes[] = ["user" => $person, "was_found_in" => $position, "now_found_in" => "REMOVED"];
}
print_r($changes);
outputs [user] => foo [was_found_in] => GUILDMASTER [now_found_in] => OFFICER

Related

Better way to dynamically access values in nested array

I need a way to dynamically access values in a nested array using an index map. What i want to achieve is looping over an array with data and extract some values that can be in any level of the nesting and save it to a bi-dimensional array.
So far I've come up with the following code, which works quite well, but I was wondering if there is a more efficient way to do this.
<?php
// Sample data
$array = array();
$array[0]['code'] = "ABC123";
$array[0]['ship'] = array("name" => "Fortune", "code" => 'FA');
$array[0]['departure'] = array("port" => "Amsterdam", "code" => "AMS");
$array[0]['document'] = array("type" => "Passport", "data" => array("valid" => '2022-03-18', 'number' => 'AX123456') );
$array[1]['code'] = "QWERT67";
$array[1]['ship'] = array("name" => "Dream", "code" => 'DR');
$array[1]['departure'] = array("port" => "Barcelona", "code" => "BRC");
$array[1]['document'] = array("type" => "Passport", "data" => array("valid" => '2024-12-09', 'number' => 'DF908978') );
// map of indexes of $array I need in my final result array. The levels of the nested indexes is subdivided by ":"
$map = array("code", "ship:name", "departure:port", "document:type", "document:data:number");
$result = array();
// loop array for rows of data
foreach($array as $i => $row){
// loop map for indexes
foreach($map as $index){
// extract specific nested values from $row and save them in 2-dim array $result
$result[$i][$index] = xpath_array($index, $row);
}
}
// print out result
print_r($result);
// takes path to value in $array and returns given value
function xpath_array($xpath, $array){
$tmp = array();
// path is subdivded by ":"
$elems = explode(":", $xpath);
foreach($elems as $i => $elem){
// if first (or ony) iteration take root value from array and put it in $tmp
if($i == 0){
$tmp = $array[$elem];
}else{
// other iterations (if any) dig in deeper into the nested array until last item is reached
$tmp = $tmp[$elem];
}
}
// return found item (can be value or array)
return $tmp;
}
Any suggestion?
This was quite tricky for me, i used Recursive function, first we normalize array keys to obtain key as you want like this document:type, then we normalize array to obtain all at same level :
/**
* #param array $array
* #param string|null $key
*
* #return array
*/
function normalizeKey(array $array, ?string $key = ''): array
{
$result = [];
foreach ($array as $k => $v) {
$index = !empty($key) && !\is_numeric($key) ? $key.':'.$k : $k;
if (true === \is_array($v)) {
$result[$k] = normalizeKey($v, $index);
continue;
}
$result[$index] = $v;
}
return $result;
}
/**
* #param array $item
* #param int $level
*
* #return array
*/
function normalizeStructure(array $item, int $level = 0): array
{
foreach ($item as $k => $v) {
$level = isset($v['code']) ? 0 : $level;
if (true === \is_array($v) && 0 === $level) {
$item[$k] = normalizeStructure($v, ++$level);
continue;
}
if (true === \is_array($v) && 0 < $level) {
$item = \array_merge($item, normalizeStructure($v, ++$level));
unset($item[$k]);
continue;
}
}
return $item;
}
$data = normalizeStructure(normalizeKey($array));
I edited your data set to add more nests:
// Sample data
$array = array();
$array[0]['code'] = "ABC123";
$array[0]['ship'] = array("name" => "Fortune", "code" => 'FA');
$array[0]['departure'] = array("port" => "Amsterdam", "code" => "AMS");
$array[0]['document'] = array("type" => "Passport", "data" => array("valid" => '2022-03-18', 'number' => 'AX123456'));
$array[1]['code'] = "QWERT67";
$array[1]['ship'] = array("name" => "Dream", "code" => 'DR');
$array[1]['departure'] = array("port" => "Barcelona", "code" => "BRC");
$array[1]['document'] = array("type" => "Passport", "data" => array("valid" => '2024-12-09', 'number' => 'DF908978', 'check' => ['number' => '998', 'code' => 'itsWell', 'inception' => ['border' => 'finalInception']]));
With these data, you should finally receive this result:
/*
Array
(
[0] => Array
(
[code] => ABC123
[ship:name] => Fortune
[ship:code] => FA
[departure:port] => Amsterdam
[departure:code] => AMS
[document:type] => Passport
[document:data:valid] => 2022-03-18
[document:data:number] => AX123456
)
[1] => Array
(
[code] => QWERT67
[ship:name] => Dream
[ship:code] => DR
[departure:port] => Barcelona
[departure:code] => BRC
[document:type] => Passport
[document:data:valid] => 2024-12-09
[document:data:number] => DF908978
[document:data:check:number] => 998
[document:data:check:code] => itsWell
[document:data:check:inception:border] => finalInception
)
)
*/
Recursivity seems to be like Inception, everything is nested and you can lose your mind in 😆, mine was already lost in.

How to append value in array with keys without getting overriding

i'm getting result in a foreach loop now i want to form a new array with keys and form that array with my new data. Now when i try to assign data it gets override to the previous one as it's not getting new index. how can i achive that so far i have done that:
foreach ($result as $key) {
$pickup_location = $key->locationid;
if (isset($pickup_location)) {
$pickup_location = $this->db->get_where('locations', array('id' => $pickup_location ))->row();
if (!empty($pickup_location)) {
$supplier_dashboard['pickup_location_name'] = $pickup_location->name_en;
}
}
$dropoff_location = $key->location_dropoff;
if (isset($dropoff_location)) {
$dropoff_location = $this->db->get_where('locations', array('id' => $dropoff_location ))->row();
if (!empty($dropoff_location)) {
$supplier_dashboard['dropoff_location_name'] = $dropoff_location->name_en;
}
}
$car_make = $key->car_id;
if (isset($car_make)) {
$car_details = $this->db->get_where('chauffeur_rates', array('chauffeur_id' => $car_make))->row();
if (!empty($car_details)) {
$supplier_dashboard['car_make'] = $car_details->chauffeur_make;
}
}
}
return $supplier_dashboard;
}
and my resulting array is:
Array
(
[pickup_location_name] => Seoul Downtown
[dropoff_location_name] => Disneyland Paris
[car_make] => makecar
)
however i have atleast 7 location names and car makes instead of getting added as a new array it overrides the previous one, i should have get the result as
Array
[0](
[pickup_location_name] => Seoul Downtown
[dropoff_location_name] => Disneyland Paris
[car_make] => makecar
)
Array
[1](
[pickup_location_name] => Seoul 1
[dropoff_location_name] => Disneyland 1
[car_make] => makecar
)
Array
[2](
[pickup_location_name] => Seoul 2
[dropoff_location_name] => Disneyland 2
[car_make] => makecar
)
... upto 7
Every time you looping the key of the array is always the same that's why you are getting overide the results. You need to put a key that is unique. You can try this
$i = 0; //First key
foreach ($result as $key){
$supplier_dashboard[$i]['pickup_location_name'] = $pickup_location->name_en;
$i++; //add +1 to the key so the next element not overrides
}
You have to add $key to array in which your record get added
use
$supplier_dashboard[$key]['pickup_location_name'] = //your code
instead of
$supplier_dashboard['pickup_location_name'] = //your code
Instead of adding the items the your array, you are directly setting the value in the top most collection.
Instad of
$myobject['foo'] = $value;
You need to do (where $iteration is the current position in your loop)
$myobject[$iteration]['foo'] = $value;
Try the following :
$carItemNumber = 0;
foreach ($result as $key) {
$pickup_location = $key->locationid;
if (isset($pickup_location)) {
$pickup_location = $this->db->get_where('locations', array('id' => $pickup_location ))->row();
if (!empty($pickup_location)) {
$supplier_dashboard[$carItemNumber]['pickup_location_name'] = $pickup_location->name_en;
}
}
$dropoff_location = $key->location_dropoff;
if (isset($dropoff_location)) {
$dropoff_location = $this->db->get_where('locations', array('id' => $dropoff_location ))->row();
if (!empty($dropoff_location)) {
$supplier_dashboard[$carItemNumber]['dropoff_location_name'] = $dropoff_location->name_en;
}
}
$car_make = $key->car_id;
if (isset($car_make)) {
$car_details = $this->db->get_where('chauffeur_rates', array('chauffeur_id' => $car_make))->row();
if (!empty($car_details)) {
$supplier_dashboard[$carItemNumber]['car_make'] = $car_details->chauffeur_make;
}
}
$carItemNumber++;
}
return $supplier_dashboard;

Php Array key=> value searching

HI I am fairly new to php.
I have an array
$arr = array(0 => array('GID'=>1,'groupname'=>"cat1",'members'=>array(0=>array('mid'=>11,'mname'=>'wwww'),1=>array('mid'=>12,'mname'=>'wswww'))),
1 => array('GID'=>2,'groupname'=>"cat2",'members'=>array(0=>array('mid'=>13,'mname'=>'gggwwww'),1=>array('mid'=>14,'mname'=>'wvvwww'))),
2 => array('GID'=>3,'groupname'=>"cat1",'members'=>array(0=>array('mid'=>15,'mname'=>'wwddsww')),1=>array('mid'=>16,'mname'=>'wwwdddw')));
ie...,I have GID,groupname,mid(member id),mname(member name).I want to insert a new mid and mname into a group if it is already in the array ,if it is not exists then create a new subarray with these elements..I also need to check a member id(mid) is also present.........................I used the code but its not working fine............. if (!empty($evntGroup)) {
foreach ($evntGroup as $k => $group) {
if ($group['GID'] == $group_id) {
foreach($group as $j=> $mem){
if($mem['mid'] == $mem_id){
unset($evntGroup[$k]['members'][$j]['mid']);
unset($evntGroup[$k]['members'][$j]['mname']);
}
else{
$evntGroup[$k]['members'][] = array(
'mid' => $mem_id,
'mname' => $mem_name);
}}
} else {
$evntGroup[] = array(
'GID' => $group_id,
'groupname' => $Group['event_group_name'],
'members' => array(
0 => array(
'mid' => $mem_id,
'mname' => $mem_name
)
)
);
}
}
} else {
$evntGroup[$i]['GID'] = $group_id;
$evntGroup[$i]['groupname'] = $Group['event_group_name'];
$evntGroup[$i]['members'][] = array(
'mid' => $mem_id,
'mname' => $mem_name);
$i++;
}
In the form of a function, the easiest solution will look something like this:
function isGidInArray($arr, $val) {
foreach($arr as $cur) {
if($cur['GID'] == $val)
return true;
}
return false;
}
You've updated your question to specify what you want to do if the specified GID is found, but that's just a trivial addition to the loop:
function doSomethingIfGidInArray($arr, $val) {
foreach($arr as $cur) {
if($cur['GID'] == $val) {
doSomething();
break; //Assuming you only expect one instance of the passed value - stop searching after it's found
}
}
}
There is unfortunately no native PHP array function that will retrieve the same index of every array within a parent array. I've often wanted such a thing.
Something like this will match if GID equals 3:
foreach( $arr as $item ) {
if( $item['GID'] == 3 ) {
// matches
}
}
There is the code
function updateByGid(&$array,$gid,$groupname,$mid,$mname) {
//For each element of the array
foreach ($array as $ii => $elem) {
//If GID has the same value
if ($elem['GID'] == $gid) {
//Insert new member
$array[$ii]['members'][]=array(
'mid'=>$mid,
'mname'=>$mname);
//Found!
return 0;
}
}
//If not found, create new
$array[]=array(
'GID'=>$gid,
'groupname'=>$groupname,
'members'=>array(
0=>array(
'mid'=>$mid,
'mname'=>$mname
)
)
);
return 0;
}

Taking a string of period separated properties and converting it to a json object in php

I'm fairly sure I'm missing something blindingly obvious here but here it goes.
I am working on updating a search function in an application which was running a loop and doing a very large number of sql queries to get object / table relations to one large query that returns everything. However the only way I could think to return relations was period separated, what I am now wanting to do is take the flat array of keys and values and convert it into an associative array to then be jsonified with json_encode.
For example what I have is this...
array(
"ID"=>10,
"CompanyName"=>"Some Company",
"CompanyStatusID"=>2,
"CompanyStatus.Status"=>"Active",
"addressID"=>134,
"address.postcode"=>"XXX XXXX",
"address.street"=>"Some Street"
);
And what I want to turn it into is this...
array(
"ID"=>10,
"CompanyName"=>"Some Company",
"CompanyStatusID"=>2,
"CompanyStatus"=>array(
"Status"=>"Active"
),
"addressID"=>134,
"address"=>array(
"postcode"=>"XXX XXXX",
"street"=>"Some Street"
)
);
Now I'm sure this should be a fairly simple recursive loop but for the life of me this morning I can't figure it out.
Any help is greatly appreciated.
Regards
Graham.
Your function was part way there mike, though it had the problem that the top level value kept getting reset on each pass of the array so only the last period separated property made it in.
Please see updated version.
function parse_array($src) {
$dst = array();
foreach($src as $key => $val) {
$parts = explode(".", $key);
if(count($parts) > 1) {
$index = &$dst;
$i = 0;
$count = count($parts)-1;
foreach(array_slice($parts,0) as $part) {
if($i == $count) {
$index[$part] = $val;
} else {
if(!isset($index[$part])){
$index[$part] = array();
}
}
$index = &$index[$part];
$i++;
}
} else {
$dst[$parts[0]] = $val;
}
}
return $dst;
}
I am sure there is something more elegant, but quick and dirty:
$arr = array(
"ID"=>10,
"CompanyName"=>"Some Company",
"CompanyStatusID"=>2,
"CompanyStatus.Status"=>"Active",
"addressID"=>134,
"address.postcode"=>"XXX XXXX",
"address.street"=>"Some Street"
);
$narr = array();
foreach($arr as $key=>$val)
{
if (preg_match("~\.~", $key))
{
$parts = split("\.", $key);
$narr [$parts[0]][$parts[1]] = $val;
}
else $narr [$key] = $val;
}
$arr = array(
"ID" => 10,
"CompanyName" => "Some Company",
"CompanyStatusID" => 2,
"CompanyStatus.Status" => "Active",
"addressID" => 134,
"address.postcode" => "XXX XXXX",
"address.street" => "Some Street",
"1.2.3.4.5" => "Some nested value"
);
function parse_array ($src) {
$dst = array();
foreach($src as $key => $val) {
$parts = explode(".", $key);
$dst[$parts[0]] = $val;
if(count($parts) > 1) {
$index = &$dst[$parts[0]];
foreach(array_slice($parts, 1) as $part) {
$index = array($part => $val);
$index = &$index[$part];
}
}
}
return $dst;
}
print_r(parse_array($arr));
Outputs:
Array
(
[ID] => 10
[CompanyName] => Some Company
[CompanyStatusID] => 2
[CompanyStatus] => Array
(
[Status] => Active
)
[addressID] => 134
[address] => Array
(
[street] => Some Street
)
[1] => Array
(
[2] => Array
(
[3] => Array
(
[4] => Array
(
[5] => Some nested value
)
)
)
)
)

php, long and deep matrix

I have a deep and long array (matrix). I only know the product ID.
How found way to product?
Sample an array of (but as I said, it can be very long and deep):
Array(
[apple] => Array(
[new] => Array(
[0] => Array([id] => 1)
[1] => Array([id] => 2))
[old] => Array(
[0] => Array([id] => 3)
[1] => Array([id] => 4))
)
)
I have id: 3, and i wish get this:
apple, old, 0
Thanks
You can use this baby:
function getById($id,$array,&$keys){
foreach($array as $key => $value){
if(is_array( $value )){
$result = getById($id,$value,$keys);
if($result == true){
$keys[] = $key;
return true;
}
}
else if($key == 'id' && $value == $id){
$keys[] = $key; // Optional, adds id to the result array
return true;
}
}
return false;
}
// USAGE:
$result_array = array();
getById( 3, $products, $result_array);
// RESULT (= $result_array)
Array
(
[0] => id
[1] => 0
[2] => old
[3] => apple
)
The function itself will return true on success and false on error, the data you want to have will be stored in the 3rd parameter.
You can use array_reverse(), link, to reverse the order and array_pop(), link, to remove the last item ('id')
Recursion is the answer for this type of problem. Though, if we can make certain assumptions about the structure of the array (i.e., 'id' always be a leaf node with no children) there's further optimizations possible:
<?php
$a = array(
'apple'=> array(
'new'=> array(array('id' => 1), array('id' => 2), array('id' => 5)),
'old'=> array(array('id' => 3), array('id' => 4, 'keyname' => 'keyvalue'))
),
);
// When true the complete path has been found.
$complete = false;
function get_path($a, $key, $value, &$path = null) {
global $complete;
// Initialize path array for first call
if (is_null($path)) $path = array();
foreach ($a as $k => $v) {
// Build current path being tested
array_push($path, $k);
// Check for key / value match
if ($k == $key && $v == $value) {
// Complete path found!
$complete= true;
// Remove last path
array_pop($path);
break;
} else if (is_array($v)) {
// **RECURSION** Step down into the next array
get_path($v, $key, $value, $path);
}
// When the complete path is found no need to continue loop iteration
if ($complete) break;
// Teardown current test path
array_pop($path);
}
return $path;
}
var_dump( get_path($a, 'id', 3) );
$complete = false;
var_dump( get_path($a, 'id', 2) );
$complete = false;
var_dump( get_path($a, 'id', 5) );
$complete = false;
var_dump( get_path($a, 'keyname', 'keyvalue') );
I tried this for my programming exercise.
<?php
$data = array(
'apple'=> array(
'new'=> array(array('id' => 1), array('id' => 2), array('id' => 5)),
'old'=> array(array('id' => 3), array('id' => 4))
),
);
####print_r($data);
function deepfind($data,$findfor,$depth = array() ){
foreach( $data as $key => $moredata ){
if( is_scalar($moredata) && $moredata == $findfor ){
return $depth;
} elseif( is_array($moredata) ){
$moredepth = $depth;
$moredepth[] = $key;
$isok = deepfind( $moredata, $findfor, $moredepth );
if( $isok !== false ){
return $isok;
}
}
}
return false;
}
$aaa = deepfind($data,3);
print_r($aaa);
If you create the array once and use it multiple times i would do it another way...
When building the initial array create another one
$id_to_info=array();
$id_to_info[1]=&array['apple']['new'][0];
$id_to_info[2]=&array['apple']['new'][2];

Categories