in case of tl;dr: setting up array with references and then changing a copy of said array still preserves references and elements of all copies are changed at the same time. Need a workaround of replacing reference with the value that it points to.
I'm getting a complicated problem with using references in PHP Arrays :(
I have a flat array of unique elements with two properties: order and level.
Order represents element's order number from 1 to n in like table of contents way. Level represents which level "subchapter" the element is.
Example:
First brackets are element IDs which are random but unique:
[1][order:1][level:1]
----[7][order:2][level:2]
----[4][order:3][level:2]
---- ----[2][order:4][level:3]
[3][order:5][level:1]
[6][order:6][level:1]
----[5][order:7][level:2]
.
.
.
Remember, this is flat array of elements, above its just a visual representation.
Now I have tried to put them into array of form:
[1][children] => [
[7],
[4][children] => [
[2]
]
]
[3],
[6][children] => [
[5]
]
Which would represent a tree structure -ish..
I do this by first ordering them by order:
foreach($elements as $element){
$ordered_elements[$element['order']] = $element;
}
Then I shift each element under the correct parent:
foreach($ordered_elements as &$child){
if($child['level'] > 1){
$ordered_elements[$last_parent[$child['level']-1]]['children'][$child['content_id']] = &$child; // I think this is problematic line!!!
}
$last_parent[$child['level']] = $child['sort_order'];
}
Some of the elements stayed on root (first) level that shouldnt be there:
foreach($ordered_elements as &$child){
if($child['level'] == 1){
$ordered_elements[$child['content_id']] = $child;
}
unset($ordered_elements[$child['sort_order']]);
}
Now when this is done, the template array is ready. Now I start getting some data from query with element_id and user_id.
I want to set up a new table "users" that would have this previously made array for each user and I would be able to change its elements per user.
users[1]['elements'] = $ordered_elements;
users[2]['elements'] = $ordered_elements;
This function should return element by reference from user's own pool of elements, so we can change it directly into the users[x]['elements'][x]:
function &get_element_from_array(&$array, $searchValue){
$status = false;
foreach($array as $id => &$subtree) {
if ($id === $searchValue) {
return $subtree;
}
if (isset($subtree['children'])) {
$subsearch = &$this->get_element_from_array($subtree['children'], $searchValue);
if ($subsearch != false) {
return $subsearch;
}
}
}
return $status;
}
That means If i want to change element 5 from user 2 I need to call it like this:
$element = &get_element_from_array(users[2]['elements'], 5);
$element['visited'] = true;
This is where the problem occurs: I have just changed this element in user2 AND user1 array of elements.
I hope I didnt wrote this thing too long, was going for good explanation.
Related
I have a list of objects, each object could belong as a child of another object and each one has an element ('parent_id') that specifies which object it's a child of.
An example of what the expected tree should look like after children are properly imbedded:
Current Array
element 1
element 2
element 3
element 4
element 5
Organized into a hierarchy
-----------------------
element 2
-children:
element 1
-children
element 5
element 3
-children:
element 4
There should be two top elements, the first element would contain the element1 as a child, which would itself contain element 5 as a child. The third element would contain element 4 as a child.
The final result should produce a proper tree structure from it, however I'm running into an issue that when I generate the tree some of the elements are null.
From what I understand it is because of the weird scoping rules PHP has and that the last reference in the loop remains bound even after the end of the loop. So I included the unset($var) statements after each loop, however I'm still getting null values pushed into the tree.
Here is the fully contained example of the code:
$response = array(
array( "kind"=> "t1", "data" => array( "id" => 25, "parent_id" => 30 )),
array("kind"=> "t1", "data" => array( "id" => 30,"parent_id" => 0)),
array("kind"=> "t1", "data" => array("id" => 32, "parent_id" => 0 )),
array("kind"=> "t1", "data" => array("id" => 33,"parent_id" => 32)),
array("kind"=> "t1", "data" => array("id" => 35,"parent_id" => 25))
);
$json_str = json_encode($response);
$tree = array();
foreach($response as &$firstObj)
{
$parentFound = null;
foreach($response as &$secondObject)
{
if($firstObj['data']['parent_id'] == $secondObject['data']['id'])
{
$parentFound = &$secondObject;
break;
}
}
unset($secondObject);
if($parentFound)
{
$parentFound['data']['children'] = array(&$firstObj);
}
else{
$tree[] = $firstObj;
}
}
unset($firstObj);
print_r($tree);
The expected tree should contain only the topmost elements that are not children of other elements, the children should be embedded through references into the appropriate spaces of the top tree elements.
While I would probably opt for a recursive approach on a professional project, I've managed to produce a non-recursive approach using references like your posted code but with a single loop.
Code: (Demo)
$result = [];
foreach ($array as $obj) {
$id = $obj['data']['id'];
$parentId = $obj['data']['parent_id'];
if (isset($ref[$id])) { // child array populated before parent encountered
$obj['children'] = $ref[$id]['children']; // don't lose previously stored data
}
$ref[$id] = $obj;
if (!$parentId) {
$result[] = &$ref[$id]; // push top-level reference into tree
} else {
$ref[$parentId]['children'][] = &$ref[$id]; // push into parent-specific collection of references
}
}
var_export($result);
For a stacking process (not a recursive one) to build a hierarchical structure, your sample data is a little unexpected in that a child id integer is less than a parent id. I mean, in nature, parents are born first and then children are born. With your sample data, the input could not be pre-sorted by id value before looping -- this would have ensured that all parents where declared before children were encountered.
As a consequence, my snippet needs to push previously encountered children data into a newly encountered parent so that the declaration of the parent as a reference does not erase the cached children data.
I extended your sample data by one extra row while testing. If you find any breakages with my script, please supply new test data that exposes the issue.
I found the solution with the help of GPT/Bing:
So while I was unsetting the other variables, I wasn't unsetting the $parentFound, which has to be done as well.
Another thing was that I was not saving the item by reference when saving the item to the tree, which also has to be done in order for the whole reference tree to work.
The final code is:
$json_str = json_encode($response);
$tree = array();
foreach($response as &$firstObj)
{
$parentFound = null;
foreach($response as &$secondObject)
{
if($firstObj['data']['parent_id'] == $secondObject['data']['id'])
{
$parentFound = &$secondObject;
break;
}
}
if($parentFound)
{
$parentFound['data']['children'] = array(&$firstObj);
}
else{
$tree[] = &$firstObj; //has to be passed by reference
}
unset($secondObject);
unset($parentFound); //have to also include the $parentFound in the unset
}
unset($firstObj);
print_r($tree);
I have an array with subarrays, each subarray has 'type' (int) and a 'datetime'. I need to loop trought the array and create new arrays (or objects) with groups of these subarrays in a special way: every 'type' could be [0-3] 0 means startdate and 3 means enddate (1 and 2 are in between for other purposes). Every new array with these subarrays should start with 0 and end with 3.
Im not using any framework, only PHP5 and jQuery. The master array comes from a loop I made from a SQL query with GROUP_CONCAT. I just passed the concat fields to a master array but I need to regroup the subarrays to create some type of registries.
Here is the array I got with subarrays. They are already sorted by datetime, every type '0' means "start date / start new registry" and '3' means "end date / end registry".
$array = [
["tipo"=>0,"fechahora"=>"2019-05-26 09:35:30"],
["tipo"=>1,"fechahora"=>"2019-05-26 10:30:15"],
["tipo"=>2,"fechahora"=>"2019-05-26 10:43:12"],
["tipo"=>3,"fechahora"=>"2019-05-26 14:30:26"],
["tipo"=>0,"fechahora"=>"2019-05-26 15:35:22"],
["tipo"=>3,"fechahora"=>"2019-05-26 16:35:31"],
["tipo"=>0,"fechahora"=>"2019-05-27 08:31:57"],
["tipo"=>1,"fechahora"=>"2019-05-27 10:27:22"],
["tipo"=>2,"fechahora"=>"2019-05-27 10:38:31"],
["tipo"=>3,"fechahora"=>"2019-05-27 14:20:38"],
["tipo"=>0,"fechahora"=>"2019-05-28 09:39:42"],
["tipo"=>1,"fechahora"=>"2019-05-28 11:43:08"],
["tipo"=>2,"fechahora"=>"2019-05-28 11:53:19"],
["tipo"=>3,"fechahora"=>"2019-05-28 14:43:31"],
["tipo"=>0,"fechahora"=>"2019-05-29 10:30:22"],
["tipo"=>3,"fechahora"=>"2019-05-29 14:38:46"]
];
I need to create a new array with subarrays or objects by registry, a registry is an element who have startdate (type 0), somedates in between (type 1,2) and a enddate (type 3). Its important The next subarray have the startdate more current than the older subarray enddate. Every "tipo"+"fechahora" array have more fields (signature, address, etc) so I need to keep these as subarrays:
$newarray = [
'registry0' => [
"startdate"=> ["tipo"=>0,"fechahora"=>"2019-05-26 09:35:30"],
"pauses"=> [
["tipo"=>1,"fechahora"=>"2019-05-26 10:30:15"],
["tipo"=>2,"fechahora"=>"2019-05-26 10:43:12"]
],
"enddate" => ["tipo"=>3,"fechahora"=>"2019-05-26 14:30:26"]
],
'registry1' => [
"startdate"=> ["tipo"=>0,"fechahora"=>"2019-05-26 15:35:22"],
"pauses"=> [],
"enddate" => ["tipo"=>3,"fechahora"=>"2019-05-26 16:35:31"]
],
'registry2' => [
"startdate"=> ["tipo"=>0,"fechahora"=>"2019-05-27 08:31:57"],
"pauses"=> [
["tipo"=>1,"fechahora"=>"2019-05-27 10:27:22"],
["tipo"=>2,"fechahora"=>"2019-05-27 10:38:31"]
],
"enddate" => ["tipo"=>3,"fechahora"=>"2019-05-27 14:20:38"]
]
];
I could use arrays or objects, I dont know hot to develop a loop to regroup arrays like this (starting at one field value and ending with another and so on). I dont even know if there is a simpler way. Any help would be appreciated.
If this could help: I need the registries this way to put into an HTML table, each registry in a row.
EDIT:
Iknow I have to use a loop but I dont know how to "get first element with type 0, create an array, include element, include all other element until type 3, close array, create new array and so on..."
In some code:
$newarray = [];
foreach($array as $element) {
if ($element["tipo"]==0) {
//new subarray
//include this in subarray
}
// include all "tipos" 1,2 in subarray
if ($element["tipo"]==3) {
//include this in subarray
//finish subarray
}
//incude subarray in $newarray
}
return $newarray;
I dont know how to continue.
For this case a simple foreach followed by switch case will do,
$result = [];
$i = 0;
$var = 3; // you can manage this at your will
foreach ($array as $key => $value) {
switch ($value['tipo']) { // passing tipo value here
case 0:
// it will capture for same record for current value of $i
$result['registry' . $i]['startdate'] = $value;
break;
case $var:
// detects last entry and will increment
$result['registry' . $i++]['enddate'] = $value;
break;
default:
// if not 0 and $var then push it in pauses
$result['registry' . $i]['pauses'][] = $value;
break;
}
}
Demo Link.
It is just a case of looping over each of the original array values and creating an entry in a temporary array ($temp in this case) with any values according to type. Then when a type 3 is found, adding this to the $newarray...
$newarray = [];
$temp = [];
$count = 0;
foreach ( $array as $entry ) {
if ( $entry['tipo'] == 0 ) {
$temp = [ 'startdate' => $entry ];
}
else if ( $entry['tipo'] == 3 ) {
$temp["enddate"] = $entry;
$newarray["registry".$count++] = $temp;
}
else {
$temp["pauses"][] = $entry;
}
}
I have the following array:
$array = array(
array("2018","2019"),
"Jan",
array("France","Germany")
);
I need a matrix that crosses all the elements of the array; e.g:
array(
array("2018","Jan","France"),
array("2018","Jan","Germany"),
array("2019","Jan","France"),
array("2019","Jan","Germany")
);
meaning, 2 x 2 x 1 arrays
but this can be that I have more elements that are or are not arrays then:
$array = array(
array("2018","2019"),
"Jan",
array("France","Germany"),
array("prod1","prod2","prod3"),
'Act'
);
In this case I would get 2 x 2 x 1 x 4 x 1 arrays in the end.
Any idea on how to achieve this?
Is that what you are looking for ?
$dst = array(array());
foreach ($array as $idx => $val) {
foreach ($dst as $tmp_idx => $tmp_array) {
if (is_array($val)) {
foreach ($val as $sub_idx => $sub_val) {
$dst[] = array_merge($dst[$tmp_idx], array(count($dst[$tmp_idx]) => $sub_val));
}
} else {
$dst[] = array_merge($dst[$tmp_idx], array(count($dst[$tmp_idx]) => $val));
}
unset($dst[$tmp_idx]);
}
}
I declare the array with an empty array in it.
A first foreach iterates through the main array to access all the categories, whatever their number.
A second foreach iterates through the result array.
A third foreach - or not, depending if the category contains several or a single entry - iterates through each entry of the current category and merges an array containing only that current category in the result currently iterated on (from $dst). The merge is pushed into the result array. The result stored in $dst are assumed to be temporary and will be copied and completed with each new value, making them progressively contain one entry from each category.
At the end of each iteration on $dst, the current temporary result is removed from the array since it has no purpose : if the foreach iterated on it, that means that it was incomplete.
At the end, the only entries in $dst that remain are these who weren't iterated on, meaning that every entry from each category were considered when they were created, meaning that they are complete.
I'm available for any clarification may the need arise.
Trying to figure out how to parse a collection and put multiple items into the same key in another collection.
Currently I'm doing this using an array and then I make a collection out of it, but the items inside are not of type Collection, each key is an array and I can't use methods like first() on those arrays. Yes, I can use [0] instead, but I'd prefer to have access to methods available for collections.
$some_array = [];
// Parsing the existing collection using foreach
foreach ($items_collection as $item) {
// Doing some checks
if ($item->some_attribute1 == 1
&& #$item->some_relation->some_attribute2
) {
// Putting the item into the array with a specific dynamic key
$some_array[$item->some_relation->some_attribute2][] = $item->some_relation;
}
else if ($item->some_attribute1 == 0
&& #$item->some_relation->some_attribute3) {
// Putting the item into the array with a specific dynamic key
$some_array[$item->some_relation->some_attribute3][] = $item->some_relation;
}
}
// Defining a new Collection
$new_collection = new Collection();
// Parsing the array of groups of items and putting them in the newly created Collection by their key
foreach ($some_array as $key => $key_items) {
$new_collection->put($key, $key_items);
}
If to make something like this
$some_collection = new Collection();
foreach ($items_collection as $item) {
if ($item->some_attribute1 == 1
&& #$item->some_relation->some_attribute2
) {
$some_collection->put($item->some_relation->some_attribute2, $item->some_relation);
}
else if ($item->some_attribute1 == 0
&& #$item->some_relation->some_attribute3) {
$some_collection->put($item->some_relation->some_attribute3, $item->some_relation);
}
}
then instead of storing all the items in the same key the new items will just override the old ones. Is there a way to put multiple items in the same key using put()?
Thank you in advance!
Seems that the issue was that I wasn't converting the $key_items into a collection in the last foreach.
Now I just used the collect() method on $key_items to make it into a Collection and everything works now.
foreach ($some_array as $key => $key_items) {
$new_collection->put($key, collect($key_items));
}
I hope someone will find this workaround useful until a more elegant solution will be found.
Finding the right title for this was next to impossible.
Imagine this scenario:
We have an array that contains certain product tags. The key is each tag's unique id and the value is its label:
Available Tags
Array (
[3] => Sweet
[4] => Sour
[5] => Bitter
[6] => Winter
[7] => Organic
)
We have another array which contains the tags that have been selected. The selection has a specific order which is defined by the key, while the value represents the id (of the actual tag we see in array #1).
Selected Tags in Specific Order
Array (
[10] => 4
[20] => 3
[30] => 7
)
My theoretical Approach
Certainly i could go about foreach-ing through the second array, collecting the appropriate values (that correspond to the first array's entries) in a new array. Then i could iterate over the first array and add all the values (to the new array) which are not yet present in the new array.
Quite honestly - that doesn't feel very professional. Unfortunately, i have no idea how to do this better.
Question
How can i neatly sort the first array (Available Tags) by using the chronology defined by the second array (Selected Tags)?
Note
I want to end up with all items from the first array. Not just the ones that are listed in the second one.
In case someone's curious: this is for multiple-selects which are sortable. Items which have been selected are sortable and must therefore appear in the right order. The other items order doesn't matter. My server-side data handler class gives me these two arrays as described, so that's what i got to work with.
Here's a solution that uses uksort(). Elements of the $tags array that are not present in the $order array are sorted to the end, and the relative order between them is undefined.
function my_sort($a, $b) {
global $order;
if(in_array($a, $order)) {
if(in_array($b, $order)) {
// Both $a and $b have an order
return array_search($a, $order) - array_search($b, $order);
}
else {
// Only $a has an order, so it goes before $b
return -1;
}
}
else if(in_array($b, $order)) {
// Only $b has an order, so it goes before $a
return 1;
}
else {
// Neither $a or $b has an order, so we don't care how they're sorted
return 0;
}
}
uksort($tags, 'my_sort');
I think you can just loop in your second array and build a new one using keys
$new = array();
foreach($array2 as $key => $val)
{
$new_array[] = $array1[$val];
}
Now the selected items are ordered in your $new_array
Sample