Unnatural sort of PHP array - php

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.

Related

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.

Returning array is faster than returning an element of the array

I just conducted this very interesting experiment and the results came out quite surprising.
The purpose of the test was to determine the best way, performance-wise, to get an element of an array. The reason is that I have a configuration class which holds settings in an associative mufti-dimensional array and I was not quite sure I was getting these values the best way possible.
Data (it is not really needed for the question, but I just decided to include it so you see it is quite a reasonable amount of data to run tests with)
$data = array( 'system' => [
'GUI' =>
array(
'enabled' => true,
'password' => '',
),
'Constants' => array(
'URL_QUERYSTRING' => true,
'ERRORS_TO_EXCEPTIONS' => true,
'DEBUG_MODE' => true,
),
'Benchmark' =>
array(
'enabled' => false,
),
'Translations' =>
array(
'db_connection' => 'Default',
'table_name' =>
array(
'languages' => 'languages',
'translations' => 'translations',
),
),
'Spam' =>
array(
'honeypot_names' =>
array(
0 => 'name1',
1 => 'name2',
2 => 'name3',
3 => 'name4',
4 => 'name5',
5 => 'name6',
),
),
'Response' =>
array(
'supported' =>
array(
0 => 'text/html',
1 => 'application/json',
),
),]
);
Methods
function plain($file, $setting, $key, $sub){
global $data;
return $data[$file][$setting][$key][$sub];
}
function loop(...$args){
global $data;
$value = $data[array_shift($args)];
foreach($args as $arg){
$value = $value[$arg];
}
return $value;
}
function arr(){
global $data;
return $data;
}
Parameters (when calling the functions)
loop('system', 'Translations', 'table_name', 'languages');
plain('system', 'Translations', 'table_name', 'languages');
arr()['system']['Translations']['table_name']['languages'];
Leaving aside any other possible flaws and focusing on performance only, I ran 50 tests with 10000 loops. Each function has been called 500000 times in total. The results are in average seconds per 10000 loops:
loop: 100% - 0.0381 sec. Returns: languages
plain: 38% - 0.0146 sec. Returns: languages
arr: 23% - 0.0088 sec. Returns: languages
I was expecting loop to be quite slow because there is logic inside, but looking at the results of the other two I was pretty surprised. I was expecting plain to be the fastest because I'm returning an element from the array and for the opposite reason arr to be the slowest because it returns the whole array.
Given the outcome of the experiment I have 2 questions.
Why is arr almost 2 times faster than plain?
Are there any other methods I have missed that can outperform arr?
I said this in the comment, but I decided it's pretty close to an answer. Your question basically boils down to why is 2+2; not faster than just plain 2;
Arrays are just objects stored in memory. To return an object from a function, you return a memory address (32 or 64 bit unsigned integer), which implies nothing more than pushing a single integer onto the stack.
In the case of returning an index of an array, that index really just represents an offset from the base address of the array, so everytime you see a square bracket-style array access, internally PHP (rather the internal C implementation of PHP) is converting the 'index' in the array into an integer that it adds to the memory address of the array to get the memory address of the stored value at that index.
So when you see this kind of code:
return $data[$file][$setting][$key][$sub];
That says:
Find me the address of $data. Then calculate the offset that the string stored in $file is (which involves looking up what $file is in memory). Then do the same for $setting, $key, and $sub. Finally, add all of those offsets together to get the address (in the case of objects) or the value (in the case of native data types) to push on to the stack as a return value.
It should be no surprise then that returning a simple array is quicker.
That's the way PHP works. You expect, that a copy of $data is returned here. It is not.
What you acutaly have, is a pointer (Something like an ID to a specific place in the memory), which reference the data.
What you return, is the reference to the data, not the data them self.
In the plain method, you search for the value first. This cost time. Take a look at this great article, which show, how arrays are working internal.
Sometimes Code say's more then words. What you assume is:
function arr(){
global $data;
//create a copy
$newData = $data;
//return reference to $newData
return $newData;
}
Also you should not use global, that is bad practise. You can give your array as a parameter.
//give a copy of the data, slow
function arr($data) {
return $data;
}
//give the reference, fast
function arr(&$data) {
return $data;
}

Doctrine: Update using collection

The Problem/How
Pass angularJS the array result of a query that includes many joins.
Using angularJS to sort with ui-sortable reorders the dataset when sorting.
Pass data back to PHP and use synchronizeWithArray to save back (creates a collection).
Doctrine doesn't like receiving the data of the collection back in a different order than it outputs.
** If all I change are values - without reordering elements it saves with no problems.
Update: http://www.doctrine-project.org/jira/browse/DC-346
Noticed it was an old bug they never fixed, is there anything to still do what I want?
Details
$model = Doctrine_Core::getTable('TableName')->findOneById(...);
$model->synchronizeWithArray(array);
$model->save();
Doctrine (1.2) / mysql throws an Integrity error, duplicate primary key id = 2 - it is trying to change the id field.
When I reorder the elements with ui-sortable, it moves the arrays within 'Fields' around while also updating the 'position' value.
This is example data:
The problem would be array 0 and array 1 swap places - causing doctrine to cause primary key error as it tries to change the ids over.
array( // the root of the array is part of one table
id => 1001,
label => 'xxx',
Fields => array( // related table data
0 => array(
id => 1,
position => 0,
name => 'item1'
),
1 => array(
id => 2,
position => 1,
name => 'item2'
),
2 => array(
id => 3,
position => 2,
name => 'item3'
),
3 => array(
id => 4,
position => 3,
name => 'item4'
)
)
)
Well I guess we could either look on the Doctrine side and try to fix this bug, or make it work with the way Doctrine operates. The second option is probably easier.
Why don't you just save the order of the data, and put it back in that order before feeding it back to doctrine? One way to do that would be in angular. Where ever you get the array of data in angular, call saveOrder(), and before feeding it back, call reOrder():
var order = {};
function saveOrder(data)
{
for(var key in data)
{
if(data.hasOwnProperty(key))
{
order[data[key].id] = key;
}
}
}
function reOrder(data)
{
var ordered = [];
for(var key in data)
{
if(data.hasOwnProperty(key))
{
ordered[order[data[key].id]] = data[key];
}
}
return ordered;
}

Accessing multi-dimensional, unordered, associative arrays in an ordered fashion using an incremental loop

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.

associating two separate arrays for itinerary system

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'];
}
}
}

Categories