Compare two arrays of different structures and find elements in PHP - php

Simple one, but still can't figure out.
I have two arrays of slightly different structures.
The first array contains members (as first level indexes, e.g. 4, 2) and their document ids (as second level indexes, e.g. 2, 3) and department tags for those documents:
array (
4 =>
array (
2 => 'support',
),
2 =>
array (
3 => 'billing',
),
)
The second array's first level index doesn't have any meaning, so could be get rid of. However, the second level index contains member ids (e.g. 4, 2) and department tags those members opened access to (the current user):
array (
0 =>
array (
4 =>
array (
'support' => 'support',
'billing' => 'billing',
),
),
1 =>
array (
2 =>
array (
'support' => 'support',
),
),
)
So I am trying to compile a list of documents that should be displayed to the current user.
For example, since member #4 has given access to support and billing the current user should be able to see document #2 (tagged as support) from that member.
And because member #2 has given access to only support tagged documents, the current user should not be able to see document #3 (tagged as billing).
So the above example should give only:
array(2)
How do I generate the final array of documents in PHP comparing two arrays?

It's possible to do what you want with loops and searches, but I'd consider this data structure unmaintainable and aim for changing it in the first place. Well, sometimes you can't, so here's how I'd do it:
$documents_data =[
4 => [2 => 'support'],
2 => [3 => 'billing']
];
$access_data = [
[4 => ['support' => 'support', 'billing' => 'billing']],
[2 => ['support' => 'support']]
];
// You need current user's data so having his id
// extract his access rights from second array
$user_id = 4;
function userData($user_id, $access_table) {
$access = [];
foreach ($access_table as $user_acc) {
if (key($user_acc) !== $user_id) { continue; }
$access = reset($user_acc);
break;
}
return [
'id' => $user_id,
'access' => $access
];
}
$user = userData($user_id, $access_data);
// Filter out documents (if any) not matching user's access rights
function userDocuments($user, $docs) {
if (empty($docs[$user['id']])) { return []; }
return array_filter(
$docs[$user['id']],
function ($doc_type) use ($user) {
return isset($user['access'][$doc_type]);
}
);
}
$allowed_docs = userDocuments($user, $documents_data);

Related

How to Reference/pull/sort by a specific key in a multidimensional array

I am writing a page that pulls images and image data out of a multidimensional array. I need to be able to click a button that calls a function to sort out the images by tags(IE tag_GlassDoor & tag_GlassWall) - basically to show only images that do or do not have that particular element (in this case im using 0 and 1 for yes and no), such as a glass door. I can currently make that array display the data, but I cant figure out how to sort the data by one of the array keys, or even really the syntax to pull a single value out at will.
$arrImages[] =
[
'img_sm'=>'image1.jpg',
'tag_GlassDoor'=>0,
'tag_GlassWall'=>1,
];
$arrImages[] =
[
'img_sm'=>'image2.jpg',
'tag_GlassDoor'=>1,
'tag_GlassWall'=>1,
];
Filtering is the answer, it can be used to filter one dimensional Arrays and multidimensional arrays.
the general implementation would be something like this:
$arr = array(
array(
'image' => "data",
'hasObject' => 1
),
array(
'image' => "data",
'hasObject' => 0
),
);
$finteredArray = array_filter($arr, function ($r) {
return (bool) $r['hasObject'];
});
print_r($finteredArray);
// it outputs:
// Array ( [0] => Array ( [image] => data [hasObject] => 1 ) )

Create tree structure in PHP with given depth, root and parent amount of children

I have one user which I will consider the root element of my tree structure.
Afterwards I have an array of users coming from my database which I consider all the children elements of the tree.
The building of the tree needs to contain the following variables that determine the width and depth of the tree:
Variable: MATCHES_TREE_MAX_DEPTH (eg: 3)
Variable: MATCHES_TREE_ROOT_MAX_CHILDREN_AMOUNT (eg: 2)
Variable: MATCHES_TREE_PARENT_MAX_CHILDREN_AMOUNT (eg: 1)
This means that I want to create a tree structure that is depth 3 (this includes the root element, so I want 2 levels deeper). The root element has 2 children, while any children of children will have have maximum 1 child element.
The order the items are in my users array is the order I want to insert them in the tree (I want to insert them breadth first rather than depth first).
I have found the following generic function on SO: PHP - How to build tree structure list?
But I don't seem to be able to adapt it to my use case, since I do not have a parent-child relationship coming from my database.
This SO answer returns me an array that is looking similar, but I'm having issues reforming it to my use case:
PHP generate a tree by specified depth and rules
Example data looks like this:
Root user:
object(stdClass)[56]
public 'user_id' => string '1' (length=1)
public 'first_name' => string 'Dennis' (length=6)
Other users (children):
array (size=3)
0 =>
object(stdClass)[57]
public 'user_id' => string '2' (length=2)
public 'first_name' => string 'Tom' (length=3)
public 'street' => string 'Teststreet' (length=10)
1 =>
object(stdClass)[58]
public 'user_id' => string '3' (length=2)
public 'first_name' => string 'Mary' (length=1)
public 'street' => string 'Maryland avenue' (length=15)
2 =>
object(stdClass)[59]
public 'user_id' => string '4' (length=2)
public 'first_name' => string 'Jeff' (length=4)
public 'street' => string 'Teststreet' (length=10)
An example of the tree I want to achieve with the examples filled (taking into account the variables of 3 max depth, 2 children of root and 1 max children of any other element than the root):
Array
(
[userid] => 1
[name] => "Dennis"
[matches] => Array
(
[0] => Array
(
[userid] => 2
[name] => "Tom"
[street] => "Teststreet"
[matches] => Array
(
[0] => Array
(
[userid] => 4
[name] => "Jeff"
[street] => "Teststreet"
[matches] = Array()
)
)
)
[1] => Array
(
[userid] => 3
[name] => "Mary"
[street] => "Maryland avenue"
[matches] => Array
(
)
)
)
)
How do I create this tree structure given the 3 variables that determine the depth and children?
EDIT: I see that the original question is looking for a solution that works with the 3 constants. I've added to the code so it's possible to define constants and loop based on them, but I am not sure I understand how one is supposed to use all three variables. In particular, MATCHES_TREE_MAX_DEPTH seems out of place given your descriptions. You indicated only two levels of children and instructed that we should skip any additional items in your list. If you want code that defines still more levels of children, you'll need to be clearer about how you want your structure to grow.
Given the very narrow limits described to this problem in the additional comments, it seems like a waste of effort to agonize over loops to handle this problem. This tree can't ever have more than five elements so an iterative solution like this should work:
// function to convert from list objects to the array we want as output
function new_obj($obj) {
$ret = array(
"userid" => $obj->user_id,
"name" => $obj->first_name
);
if (isset($obj->street)) {
$ret["street"] = $obj->street;
}
$ret["matches"] = [];
return $ret;
}
// INPUT DATA
$root = (object)["user_id" => "1", "first_name" => "Dennis"];
$children = [
(object)[
"user_id" => "2",
"first_name" => "Tom",
"street" => "Teststreet"
],
(object)[
"user_id" => "3",
"first_name" => "Mary",
"street" => "Maryland avenue"
],
(object)[
"user_id" => "4",
"first_name" => "Jeff",
"street" => "Teststreet"
],
(object)[
"user_id" => "5",
"first_name" => "Arthur",
"street" => "Teststreet"
]
];
$result1 = new_obj($root);
// an iterative solution, only works for one trivial set of values for the constants defined
// but also does provide some insight into a more general solution
if (isset($children[0])) {
$result1["matches"][0] = new_obj($children[0]);
}
if (isset($children[1])) {
$result1["matches"][1] = new_obj($children[1]);
}
if (isset($children[2])) {
$result1["matches"][0]["matches"][0] = new_obj($children[2]);
}
if (isset($children[3])) {
$result1["matches"][1]["matches"][0] = new_obj($children[3]);
}
print_r($result1);
If you want to define constants/vars to specify child limits and then use loops, try this with the same $root and $children vars defined above.
// solution must use these constants:
define("MATCHES_TREE_MAX_DEPTH", 3);
define("MATCHES_TREE_ROOT_MAX_CHILDREN_AMOUNT", 2);
define("MATCHES_TREE_PARENT_MAX_CHILDREN_AMOUNT", 1);
$result2 = new_obj($root);
$i = 0;
while ($child = array_shift($children)) {
$result2["matches"][$i] = new_obj($child);
$i++;
if ($i >= MATCHES_TREE_ROOT_MAX_CHILDREN_AMOUNT) break;
}
$i = 0;
while ($grandchild = array_shift($children)) {
$child = $result2["matches"][$i];
if (count($child["matches"]) >= MATCHES_TREE_PARENT_MAX_CHILDREN_AMOUNT) {
// if we reach a child that has its max number of grandchildren, it's time to quit
break;
}
// otherwise, assign this child as a grandchild
$result2["matches"][$i]["matches"] = new_obj($grandchild);
// increment the counter and check if we cycle back to the first child of root
$i++;
if ($i >= count($result2["matches"])) {
$i = 0;
}
}
print_r($result2);

How to detect and avoid infinite loop in PHP

This question is not about a bug in my PHP code (for the moment I don't have any PHP script, I am still thinking about the algorithm). Here is the problem:
I'm currently working on a mechanical piece manager which would be able to build a mechanical piece based on internal part (for example, I got a piece which is a Bike, and for that piece, I need 2 wheels, 1 handlebar, and for a wheel I need a tire etc).
Each internal part is also a mechanical piece in my database and is linked with a unique ID (and has a folder which contains many PDF, many 3D files, etc).
I got a GUI (in HTML) representing my database and each piece has a "Build" button to gather all files required to build internal piece.
For example:
My bike has the ID n°1, a wheel has the ID n°2 and the handlebar has the ID n°3.
The algorithm is pretty simple but vulnerable with infinite loop.
How could I do to avoid this following case: What if my bike (id 1) need a wheel (id 2), and my wheel need a bike...which needs a wheel which need a bike......?
Thank you very much,
During the execution of your build function, you would just keep track of all components that you have already produced a result for -- in a hash --, and if you encounter one of those again, you just ignore it.
Here is some boilerplate code you could use for inspiration:
// Sample "database":
$components = array(
1 => array (
"id" => 1,
"name" => "bike",
"needs" => array (
array ("id" => 2, "count" => 2), // 2 wheels
array ("id" => 3, "count" => 1), // 1 handlebar
),
"folder" => "/my/folders/bike"
),
2 => array(
"id" => 2,
"name" => "weel",
"needs" => array(
array("id" => 4, "count" => 1), // 1 tire
array("id" => 1, "count" => 1) // 1 wheel?? - erroneous information!
),
"folder" => "/my/folders/wheel"
),
3 => array(
"id" => 3,
"name" => "handlebar",
"needs" => array (),
"folder" => "/my/folders/handlebar"
),
4 => array(
"id" => 4,
"name" => "tire",
"needs" => array(),
"folder" => "/my/folders/tire"
)
);
// Build function: returns a list of folders related
// to all the parts of the given component.
function build($componentId, $components, $hash = array()) {
// Protection against infinite recursion:
if (isset($hash[$componentId])) return [];
$elem = $components[$componentId];
// set hash, keyed by component ID.
$hash[$componentId] = 1;
// Add folder for this component
$folders[] = $elem['folder'];
// Collect folders of dependent components recursively
foreach($elem['needs'] as $child ) {
// ... pass the hash as third argument
$folders = array_merge($folders, build($child["id"], $components, $hash));
}
return $folders;
}
// Example call: build component with ID 1, returning a list of folders:
print_r (build(1, $components));
The output of the above code would be:
Array
(
[0] => /my/folders/bike
[1] => /my/folders/wheel
[2] => /my/folders/tire
[3] => /my/folders/handlebar
)
So when the bike was encountered a second time, it was just ignored.
You should differentiate the child-parent relations.
A bike can contain a wheel as a child item and a wheel may have a bike as its parent item. Also one screw and tire can be child items of the wheel.
Navigation should have one direction, either from parent items to child items or the opposite.

set id for each element in recursive array

i want to set an id or index for each element and sub and the sub of the sub and so on
<?php
$data = array(
array(
'title' => 'a',
'children' => array(
array(
'title' => 'a-a',
'children' => array(
array(
'title' => 'a-a-a',
'children' => array(
)
)
)
),
array(
'title' => 'a-b',
'children' => array(
)
)
)
),
array(
'title' => 'b',
'children' => array(
)
)
);
?>
i'm looking for php code or function that work with recursion to add the index key witn number (desc) something like this output
<?php
$data = array(
array(
'index' => 1,
'children' => array(
array(
'index' => 2,
'children' => array(
array(
'index' => 3,
'children' => array(
)
)
)
),
array(
'index' => 4,
'children' => array(
)
)
)
),
array(
'index' => 5,
'children' => array(
)
)
);
?>
Once upon a time there a study that came to the conclusion that the biggest challenges a young padawan-programmer on his quest to become a Jedi-senior-programmers are:
[...] there are three major semantic hurdles which trip up novice imperative programmers. In order they are:
assignment and sequence
recursion / iteration
concurrency
I first encountered this theory in Jeff Atwood's blog entry: Separating Programming Sheep from Non-Programming Goats which is based on the scientific paper: The camel has two humps (working title)
This is the code that achieves what you want (minus the echo calls which are only to visualize the steps easier):
function addIndexRecursive(&$data) {
static $index = 0;
echo "The index is: $index" . PHP_EOL;
foreach ($data as &$item) {
$index++;
echo "The index was incremented: {$index}" . PHP_EOL;
$item = array('index' => $index) + $item;
echo "Add the index to the item with title: {$item['title']}" . PHP_EOL;
if (count($item['children']) > 0) {
echo "This item has children so call the index function" . PHP_EOL;
addIndexRecursive($item['children'], $index);
// this else branch is only for illustration purposes only
} else {
echo "There are no children therefore I stop" . PHP_EOL;
}
}
}
So lets dissect it a bit.
First thing to notice: I pass the array by reference. It's just a matter of taste, I prefer to modify the array directly rather then building another copy.
The static $index will help me keep track of the current index and will be incremented for every item is encountered. Lets see what kind of black magic this static does (according to Variable scope):
A static variable exists only in a local function scope, but it does not lose its value when program execution leaves this scope.
foreach ($data as &$item) again I want the elements to be passed by reference so I can modify them directly in the loop.
Now that we discussed the implementation details lets see what the back-trace of the function would be:
addIndexRecursive was call and the index is: 0
The index was incremented: 1
Add the index to the item with title: a
This item has children so call the function
addIndexRecursive was call and the index is: 1
The index was incremented: 2
Add the index to the item with title: a-a
This item has children so call the function
addIndexRecursive was call and the index is: 2
The index was incremented: 3
Add the index to the item with title: a-a-a
There are no children therefore I stop
The index was incremented: 4
Add the index to the item with title: a-b
There are no children therefore I stop
The index was incremented: 5
Add the index to the item with title: b
There are no children therefore I stop
You will keep running into these kind of problems so the sooner you grasp the recursion concept the better.
This is an example of 'nested arrays' where the individual arrays need:
1) an entry to be added: 'index' => <incrementing number> where there is a 'children' entry in the array.
2) an entry to be removed where: 'title' exists in the array.
As arrays can be 'nested' then 'recursion' is the most appropriate way to process the 'data' array.
I find it easier to modify the array 'in place' rather than build up the 'output' array. Therefore 'array references' (&$arrayEntry) are used quite a lot.
I use the supplied test data and test the 'required' array and 'indexed' array to be equal as regards 'structure'.
This is tested code: PHP 5.3.18 on Windows XP. Working code at: viper-7.com
The function that does the work:
function addChildrenIndex(&$destData, &$idxNew)
{
foreach($destData as $currentKey => &$currentValue) { // pass be reference so i can amend it
if (is_array($currentValue)) { // ensure is 'children' array
if (isset($currentValue['children'])) { // add the 'index' entry
$currentValue['index'] = $idxNew;
$idxNew++;
}
if (isset($currentValue['title'])) { // remove the 'title' entry!
unset($currentValue['title']);
}
// as it is an array we 'recurse' around it - always...
addChildrenIndex($currentValue, $idxNew);
}
}
}
Driving Code:
$idxNew = 1; // starting index.
// COPY the source data so i can test it later if required...
$indexedData = $data; // output array
addChildrenIndex($indexedData, $idxNew); // re-index the array 'in place'
// echo 'Required Array: <pre>';
// print_r($requiredData);
// echo '</pre>';
// echo 'OutputArray: <pre>';
// print_r($indexedData);
// echo '</pre>';
// test array structures to contain same entries...
var_dump($indexedData == $requiredData, 'ARE EQUAL?');
Required Test data: see question
Indexed Output:
Array
(
[0] => Array
(
[children] => Array
(
[0] => Array
(
[children] => Array
(
[0] => Array
(
[children] => Array
(
)
[index] => 3
)
)
[index] => 2
)
[1] => Array
(
[children] => Array
(
)
[index] => 4
)
)
[index] => 1
)
[1] => Array
(
[children] => Array
(
)
[index] => 5
)
)

Retrieve value of child key in multidiensional array without knowing parent key

Given this multidimensional array, I'm trying to retrieve the value of one of the child keys:
$movieCast = Array(
'1280741692' => Array(
...
, 'userid' => 62
, 'country_id' => '00002'
...
)
, '1280744592' => Array(
...
, 'userid' => 62
, 'country_id' => '00002'
...
)
)
How can I retrieve the value of country_id?
The top-level array key could be anything and the value of country_id will always be the same for a specific user. In this example, user #62's country_id will always be 00002.
You have to iterate through the outer array:
foreach ($outer as $inner) {
//do something with $inner["country_id"]
}
Another option is to build an array with the contry_ids (example uses PHP >=5.3 functionality, but that can be worked around easily in earlier versions):
array_map(function ($inner) { return $inner["country_id"]; }, $outer);
EDIT If the ids are all the same, even easier. Do:
$inner = reset($outer); //gives first element (and resets array pointer)
$id = $inner["country_id"];
a more general-purpose solution using php 5.3:
function pick($array,$column) {
return array_map(
function($record) use($column) {
return $record[$column];
},
$array
);
}
You need to use this:
array_column($movieCast, 'country_id')
The result will be:
array (
0 => '00002',
1 => '00002',
)

Categories