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.
Related
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);
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);
This is primarily a PHP answer, but the methodology may in fact be language agnostic.
After running a CSV file through a simple parser, I was given a multidimensional array that resembled something akin to this:
array( 'estimated' =>
array(
array( "TITLE" => 'MAIN', "CURR_PERF" => 100, "POT_PERF" => 75 ),
array( "TITLE" => 'HEAT', "CURR_PERF" => 90, "POT_PERF" => 60 ),
array( "TITLE" => 'CO2', "CURR_PERF" => 56, "POT_PERF" => 40 ),
),
'actual' =>
array(
array( "TITLE" => 'MAIN', "CURR_PERF" => 100, "POT_PERF" => 75 ),
array( "TITLE" => 'HEAT', "CURR_PERF" => 89 , "POT_PERF" => 75),
array( "TITLE" => 'CO2', "CURR_PERF" => 40, "POT_PERF" => 20 ),
);
);
Now, horrific data structure to one side, without refactoring of the underlying parser - how would be the best way to ensure that you can access these in a specific order? Without necessarily touching the underlying parser?
If you loop through using a for()/foreach() loop you're only going to be able to read them in a linear order - increasing or decreasing down the elements. You're not necessarily going to be able to drill down and get the specific value required.
For instance, the CSV file could express the values for estimated in a different order to the values for actual; and it may be required that they are output in yet another order.
For example, here are three different orders off the top of my head:
-> MAIN HEAT CO2
-> HEAT MAIN CO2
-> CO2 HEAT MAIN
Furthermore, as is quite usual, the label names in the CSV file are not exactly user-friendly - so they need to be 'translated' (if you like) in to something more human-friendly. Naturally, without the use of lots of if() statements if preferable!
Given it's a very specific use case, but it's something I've seen before with regards to arrays being serialised - and often finding out that they are actually nested.
I've posted one possible solution, but would be happy to see others. (In the past when I've done similar I've never accepted my own answer ;)) I'm sure there must be a more elegant way than the one I've devised..!
This was the quickest (and neatest) solution I could come up with on the spot at the time, but it's not one that I take very much pride in..! It involves multiple loops, the use of several arrays and seems to be an over-engineered mechanism for doing something which should surely be pretty simplistic?
First of all I've created an associative array to use as a dictionary to look up translations for text strings found in the CSV file:
$tbl = array( "MAIN"=>"Main Costs",
"TOTAL"=>"Total Costs",
"CO2"=>"Gas expended" );
Next up, I created an array to use as an 'index' - I entered the key values here in the order I would like them to be accessed in the application:
$index = array( "MAIN", "TOTAL", "CO2" );
I then created two blank arrays and populated them with the data from the child arrays, by using a loop I was able to make them associative - allowing me to specify use the TITLE fields as keys:
$estimated = array();
$actual = array();
foreach( $bills['estimated'] as $bill ){
$estimated[ $bill['title'] ] = $bill;
}
foreach( $bills['actual'] as $bill ){
$actual[ $bill['title'] ] = $bill;
}
By doing this I could loop through them in a specific order, regardless of the order they were parsed in, like so:
for($i=0; $i<3; $i++){
$bill = $estimated[ $index[ $i ] ];
printf(" %s: %d ", $tbl[ $index[ $i ] ], $bill['CURR_PERF'] );
}
Which would output the following, in the order I specified:
// 1. Main Costs: 100
// 2. Total Costs: 90
// 3. Gas Expended: 56
Naturally, this order could easily be changed if required. It does however:
require using an array specifically to act as an index
require using two loops simply to initialise
uses a total of four extra arrays
If this array was assigned to variable $ary like:
$ary = array(
'estimated' =>
array(
array('TITLE' => 'MAIN', 'CURR_PERF' => 100, 'POT_PERF' => 75),
array('TITLE' => 'HEAT', 'CURR_PERF' => 90, 'POT_PERF' => 60),
array('TITLE' => 'CO2', 'CURR_PERF' => 56, 'POT_PERF' => 40),
),
'actual' =>
array(
array('TITLE' => 'MAIN', 'CURR_PERF' => 100, 'POT_PERF' => 75),
array( 'TITLE' => 'HEAT', 'CURR_PERF' => 89 , 'POT_PERF' => 75),
array( 'TITLE' => 'CO2', 'CURR_PERF' => 40, 'POT_PERF' => 20 ),
);
);
Your estimated array could be accessed like:
$newVar = $ary['estimated'][2]['CURR_PERF'];
$newVar would be 56. You can reassign a value as well, like:
$ary['estimated'][0]['POT_PERF'] = 300;
That first array in the estimated array used to be 75 and is now 300.
This is how you get and set specific values in a Multi-dimensional Array.
To sort the arrays, if you need to loop over and maintain their indexes, see PHP's uasort() function. This may take some work, on your part, to develop a cmp_function, but should do what you're looking for in a different way.
See,
http://www.php.net/manual/en/function.uasort.php
and
http://www.php.net/manual/en/function.usort.php ,
which explains cmp_function better.
I am trying to build a travel itinerary system. The user selects the dates of travel, and then may add items to each day.
I have an array of dates, stored in a session in the format:
array(
(int) 0 => '2012-08-25',
(int) 1 => '2012-08-26',
(int) 2 => '2012-08-27'
)
They will then choose attractions, which I wish to store in an array in the format:
array(
(int) 0 => array(
'Attraction' => array(
'attraction_id' =>'1',
'name' => 'Place One',
)
),
(int) 1 => array(
'Attraction' => array(
'attraction_id' => '2',
'name' => 'Place Two',
)
),
I'd like to be able to output:
2012-08-25
Place One
Place Two
2012-08-26
nothing here yet!
2012-08-27
nothing here yet!
So, each item of the first array contains an array of items, if that makes sense. I am struggling with the logic of associating the keys of the days array with the items array.
I looked at array_merge but that doesn't seem to do what I need.
Is there an easy way to achieve this?
This code does exactly what you ask. Unfortunately, I fear your question doesn't reflect your aim given the example. Using keys to link data will led to 1-1 relationship, where as you seem to need a 1-n. You should have a foreign key field in the attraction array, like date_id.
$array= array();
foreach($dates as $date_key=>$date){
$array[$date]=array();
foreach($attractions as $attraction_key=>$attraction){
if($date_key==$attraction_key){
$array[$date][]=$attraction['Attraction']['name'];
}
}
}
I have an array in PHP that is simply strings. These strings represent relationships that can occur between different distances of family. It looks like this:
0 => 'Unknown',
1 => 'Parent',
2 => 'Grandparent',
3 => 'Sibling',
4 => 'Child',
5 => 'Grandchild',
6 => 'Spouse',
7 => 'Self',
9 => 'Step Parent',
10 => 'Step Sibling',
11 => 'Step Child',
etc...
This array works great, but I'm running into a problem where I would like to be able to sort these items in a purely custom way (specifically closer-to-father away starting with Spouse/Sibling ending with most distant relations like X-in-law), but I also would like the list to be added to over time. My initial thought was to simply order them as they needed to be ordered in the array, but that does not allow me to add relations at a later date (though it's doubtful this would ever happen, I'd rather be safe than sorry). The best solution I could come up with (an seemingly a good one in my mind) was to make a simple PHP object that would hold both the name of the relation and an arbitrary "sort" value like this new Relation('Unknown', 0);. The problem is, it appears that you cannot instantiate objects while making an array with the X => Y syntax, as I have syntax errors when I try to write this:
0 => new Relation('Unknown', 0),
1 => new Relation('Grandparent', 1),
etc...
Is there a way that this could work or possibly a better solution? It seems at this point I may have to generate the array the first time it is requested (in a getter) using Array pushes then store it, but I'd rather have it nicely formatted. Am I just out of luck?
Edit - To clarify: The keys right now are being used as the stored value in the database. In essence, we are using the array as an enum in other languages.
Use the array as a map, such as relationship_name => relationship_distance. If it is more convenient, you can do relationship_distance => relationship_name. Make the relationship_distance values arbitrary, with gaps in between so that intermediate distances can be added later. For example:
'Sibling' => 1,
'Parent' => 10,
'Grandparent' => 20,
'Greatgrandparent' => 30,
'Uncle' => 13,
'BrotherInLaw' => 17,
Then you can sort the map by they keys or the values and add new entries as needed.
You could write this in a array. What happens to be a syntax error is if you wrote that in a class property.
You can't do this:
class A {
public $relations = array(
0 => new Relation('Unknown', 0),
1 => new Relation('Grandparent', 1)
);
}
But you could do the init in the constructor.
class A {
public $relations;
public function __construct() {
$this->relations = array(
0 => new Relation('Unknown', 0),
1 => new Relation('Grandparent', 1)
);
}
}
PS: if the keys is just 0,1,... you could omit them.