I am working with PHP array to store the comma separated values of product Ids and then in turn use them to show as recent seen products.
Implementation is as below:
$product_data = array();
$array_ids_a_display = array();
$check_status = array();
$array_check= array();
$_COOKIE['Pr'] = [1,2,3,4,5,6,7,8]
Then i am Taking the last point of the stored string
$array_check = explode(',',substr($_COOKIE['IdProduto'],0,-1));
Now, i check of products are enabled then store them into one other array
foreach($array_check as $id_a_checar){
$check_status = $this->model->getProduct($id_a_checar);
if($check_status['status']){ // If status is 1
$array_ids_a_display[$contprods++] = $id_a_checar;
}
}
if($s['limit']>count($array_ids_a_display)){
//If the number of valid products < number of products of module
$s['limit'] = count($array_ids_a_display);
//will show,then reconfigures the number of products that will show
}
}
where $s['limit'] comes from backend , let us say 6 to limit the number of products.
Now, i will reverse the array to get latest visited product at first place like as
$last_ended = array_reverse($array_ids_a_display);
array_splice($last_ended,0,(int)$s['limit']);
foreach ($last_ended as $result) {
$product_data[$result] = $this->product->getProduct($result);
}
Now here comes the problem, as i am only getting 3 products in $product_data array but is shall get 6 products.
I hope that there is issue with array_splice because if i will comment array_splice then i am getting all stores products in cookies as result.
Mysql query is working very fine.
Please advice how to get latest 6 values from array
Here you are:
$last_ended = array(1, 2, 3, 4, 5, 6, 7, 8);
$last_ended = array_reverse($last_ended);
//here is what you missed:
$last_ended = array_splice($last_ended, 0, 6);
print_r($last_ended);
//returns Array ( [0] => 8 [1] => 7 [2] => 6 [3] => 5 [4] => 4 [5] => 3 )
You need to assign your $last_ended var into array_splice result.
<?php
// Get the last 6 entries of an array...
$arr = array(
1,
2,
3,
4,
5,
6,
7,
8
);
$entries = array_splice($arr, -6);
// returns [3, 4, 5, 6, 7, 8]
}
$maxResultLength = 6;
$someArray = [1,2,3,4,5,6,7,8,9,10,'a','b','c','d','e','f','g','h'];
$startIndex = (count($someArray) >= $maxResultLength) ? count($someArray) - $maxResultLength : 0;
$lastSixElements = array_slice($someArray, $startIndex, $maxResultLength);
or
$maxResultLength = 6;
$someArray = [1,2,3,4,5,6,7,8,9,10,'a','b','c','d','e','f','g','h'];
$lastSixElements = array_splice($someArray, -6);
Related
I've got a foreach where I create an array out of id's, based on the submitted selected checkboxes from my form (which are `checkbox[$id]. So I end up with:
Where 1, 2 and 3 are the submitted id's from the form. So far so good.
Now I also have an input field amount[$id]in my form. When selecting a checkbox, I can enter an amount for that row and submit the results. I need to add the values of amount to my array if id's. My end result should look like this:
[1 => ['amount' => '10'], 2 => ['amount' => '12'], 3 => ['amount' => '5'] // And so on
I tried merging, and array_push, but I seem to be doing it wrong, since I cannot figure it out. Any pointers?
Something like this should work:
$result = [];
$ids = [1,2,3]; // I suppose it is `$_POST['ids']`
$amounts = [1 => 10, 2 => 11, 3 => 22]; // I suppose it is `$_POST['amount']`
foreach ($ids as $id) {
if (!empty($amounts[$id])) {
$result[$id] = ['amount' => $amounts[$id]];
}
}
Using array_combine as advised in comments can be used only if sizes of arrays are equal. So if you have something like:
$ids = [1,2,4];
$amounts = [1 => 10, 2 => 11, 3 => 0, 4 => 22];
print_r(array_combine($ids, $amounts)); // PHP Warning
And second fact - array_combine won't create values as arrays. So
$ids = [1,2,3];
$amounts = [1 => 10, 2 => 11, 3 => 10];
print_r(array_combine($ids, $amounts)); // no subarrays here
Preamble:
This question is not about to learn PHP, nor is it code that I plan to use in a productive environment. I just want so see and learn better ways to do this work, as I did in my approach. So please only correct my code or show me better, faster or shorter solutions for doing it. The problem itself has already been solved. Thank you!
The Problem:
Some days ago a user asked a question here on SO. His problem has got my attention, because I wanted to find a way to solve his needs.
He wanted to get all possible key combinations of an PHP array, where the sum of the values is 100, or as close as possible to 100. He had given us an example array, which I will use for my examples too:
$array = array(25, 30, 50, 15, 20, 30);
For example, one result should be [2, 4, 5], because 50 + 20 + 30 is 100.
$sum = $array[2] + $array[4] + $array[5]; // = 100
I think the basic idea should be clear. Now let's take a look at my work ...
My Approach:
So this problem has got my attention as a developer. At first, I thought it would be pretty simple. Just do some addition and check the result. But then I've noticed that there some points to keep in mind ...
There are plenty of combinations to test. For the example array, there would be up to 720 (6! = 1*2*3*4*5*6 = 720) possible permutations. To get all possible combinations, I wanted to get all possible permutations of the array first.
But that was only the half truth. Because there could be double values in the array (as in your example the 30), we could not get all possible permutations of the array values, we had to get all possible permutations of the array keys instead.
So I've used the pc_permut function of the php cookbook and modified it for my needs. It will return all possible permutations in an array of keys.
/**
* gets all possible permutations of $array
* #param array $array
* #param array $permutations
* #return array
*/
function permutations($array, $permutations = array()) {
if( !empty($array) ) {
$result = array();
for( $i = count($array) - 1; $i >= 0; --$i ) {
$newItems = $array;
$newPerms = $permutations;
list($values) = array_splice($newItems, $i, 1);
array_unshift($newPerms, $values);
$result = array_merge($result, permutations($newItems, $newPerms));
}
}
else {
$result = array($permutations);
}
return $result;
}
The result of this function is a multidimensional array, containing all permutations in an ordered key array.
Array (
[0] => Array (
[0] => 0
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[1] => Array (
[0] => 1
[1] => 0
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[...
)
So, for now I got all permutations to work with. The calculation of the possible combinations is not so hard at all. I'll just loop through the permutation, increment the sum until they've reached 100 or above and return the key combination.
But I find out that I missed one thing. As I got all possible permutations, there are even some results doubled in the list. To explain, this two results are basically the same:
[2, 4, 5]; // 50 + 20 + 30 = 100
[4, 5, 2]; // 20 + 30 + 50 = 100
I've ended up sorting the keys after calculation and use them as index in the resulting array. So it would be sure, that every combination only exists once in the result. This is my combinations function:
/**
* gets all possible key combinations of $array with a sum below or equal $maxSum
* #param array $array
* #param integer $maxSum
* #return array
*/
function combinations($array, $maxSum) {
// get all permutations of the array keys
$permutations = permutations(array_keys($array));
$combinations = array();
// loop all permutations
foreach( $permutations as $keys ) {
// create a container for each permutation to store calculation
$current = array(
"sum" => 0,
"keys" => array()
);
// now loop through the permutation keys
foreach( $keys as $key ) {
// if the addition is still between or equal $maxSum
if( $current["sum"] + $array[$key] <= $maxSum ) {
// increment the sum and add key to result
$current["sum"] += $array[$key];
$current["keys"][] = $key;
}
}
// to be sure each combination only exists once in the result
// order the keys and use them as array index
sort($current["keys"]);
$combinations[join("", $current["keys"])] = $current;
}
// remove the created key-index from array when finished
return array_values($combinations);
}
The execution is simple straight forward:
$array = array(25, 30, 50, 15, 20, 30);
print_r(combinations($array, 100));
The result is an array, containing all combinations. For our example array there are eleven possible combinations. The result looks like this:
Array (
[0] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 1
[2] => 3
[3] => 4
)
)
[1] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 2
[2] => 3
)
)
[...
Since I've written this script as an answer of the original question, I'll ask myself, if there would be another, even better way to do the work. Maybe there is a way without permutations, or a way to exclude same combinations from calculation or the resulting array. I know that I could execute the calculation directly in the permutations function too, but this would be basically the same work flow.
I would really like to get some advises, tips or improvements from you. I think here is some potential to improve the script, but I have actually no idea how to. But I'm sure it could be done more simple and straight forward ...
Thanks for your time! :)
Sorting the array leads to some possibilities: here is the one i'm thinking of:
I denote a(selectedIndexes) the element composed of all elements of selectedIndexes e.g.
a({25, 30, 30}) = (25, 30, 30)
P(n) is the set of all combinations of the indexes 1 to n and for clarity sake my arrays starts at index 1 (thus P(2)={1, 2, (1, 2)})
I'm using 2 break conditions explained in the pseudocode below. 1st one is the first element of aSorted = the allowed sum.
2nd one is the sum is too small compared to aSorted first element
selectedIndexes = {}
sum = 100
aSorted = {15, 20, 25, 30, 30, 50} //starting values for the example
//to clarify the following function
aSum = {15, 35, 60, 90}
function n(aSorted, sum, selectedIndexes){
compute aSum //precisely search in aSum for the index at which
//the elements are bigger than sum, and cut
answer = (P(count(aSum))) X a(selectedIndexes) // with X being the cartesian product
for (i=count(aSum)+1; i<=count(aSorted); i++){
newASorted = splice(aSorted, count(aSum))
// 1st break condition
if(newASorted is empty) return answer
// 2nd break condition the new sum < the first element of aSorted
if (aSorted(i)<sum && sum-aSorted(i)>=aSorted(1)){
answer += n(newASorted, sum-aSorted(i), push(selectedIndexes,
i))
}
}
return answer
}
The complexity of this algorithm feels quadratic(after a quick check it's more like of order n^log2(n)) in regard to the number of elements in the array
To make it less abstract, let's develop the example(warning i trust the example more than the pseudocode although i didn't see inaccuracies myself in the pseudocode):
n({15, 20, 25, 30, 30, 50}, 100, {}) = P(4) + n({15, 20, 25, 30, 30}, 50, {6}) + n({15, 20, 25, 30}, 70, {5})
Starting by developing the first n function of the right side of the equation
n({15, 20, 25, 30, 30}, 50, {5}) = (P(2) X {6}) + n({15, 20, 25, 30}, 20, {5, 6}) + n({15, 20, 25}, 20, {4, 6}) + n({15, 20}, 25, {3, 6})
n({15, 20, 25, 30}, 20, {5, 6}) = (P(1) X {(5, 6)}) // + n({15}, 0, {2, 5, 6}) (but 0<15) break condition 2
n({15, 20, 25}, 20, {4, 6}) = P(1) X {(4, 6)} // and break condition 2
n({15, 20}, 25, {3, 6}) = P(1) X {(3, 6)} // + n({15}, 5, {2, 3, 6}) (but 5<15) break condition 2
Now developing the second n function of the right side of the equation
n({15, 20, 25, 30}, 70, {5}) = (P(3) X {5}) + n({15, 20, 25}, 40, {4, 5})
n({15, 20, 25}, 40, {4, 5}) = (P(2) X {(4, 5)}) + n({15, 20}, 15, {3, 4, 5})
n({15, 20}, 15, {3, 4, 5}) = P(1) x {(3, 4, 5)} // + n({}, 0, {1, 3, 4, 5}) new break condition aSum is empty
I have an array of values (non unique) and I need to work out how many times I can pull out x (e.g 3) unique items from that array.
e.g.
[5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5]
What is the largest quantity of unique items of length 3 (e.g. [5, 4, 1]) I can retrieve?
For context, this is for an offer system in a shopping cart. The array of values is the product ids, and I need to know how many times I can apply a specific offer that requires 3 different items from the array of ids in order to be valid.
Thanks for any help - just ask if anything is unclear and I'll try to explain. If I've missed an existing question that answers this please let me know and I'll close this question.
Here's one way you can follow:
$basket = array(5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5);
$length = 3; //unique set size
function getSetCount($basket, $length) {
$counts = array_count_values($basket); //count of each ID
$totalCount = count($basket); //count of all elements
//maximum amount of sets
$max = floor($totalCount / $length);
//since every ID can be only once in a set, it all above $max occurences won't be able to fit any set.
$reducedCounts = array_map(function ($el) use ($max) {
return min($el, $max);
}, $counts);
//final result is "all elements that should fit in sets / size of set
return floor(array_sum($reducedCounts) / $length);
}
If you would like to print them:
function printSets($basket, $length) {
$counts = array_count_values($basket); //count of each ID
while(!empty($counts)) {
arsort($counts);//reverse sort with keeping the keys
$set = array_slice(array_keys($counts),0,$length); //get the set
echo implode(',', $set) . '<br/>';
foreach($set as $id) { //reduce counts
$counts[$id]--;
}
$counts = array_filter($counts); //clean zeros
}
}
The code above may not handle proparly some edge cases. But this is the idea.
Basically array_count_values() counts values' occurences and returns an array of value => count pairs. Then its easy to manipulate this data.
If i understand you correctly:
function getChunksCount($products)
{
// get unique ids
$uniqueProducts = array_unique($products);
// count unique ids
$count = count($uniqueProducts);
// let's get the number of chucks available
$chunkSize = 3;
// round to the floor
return floor($count / $chunkSize);
}
No complex logic or processing at all. Next time try to write down what exactly needs to be done in what order, and the solution might become pretty obvious :)
You can do it using array_unique and array_slice.
$arr = [5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5];
$new_arr = array_slice(array_unique($arr), 0, 3);
print_r($new_arr); //Array ( [0] => 5 [1] => 4 [2] => 1 )
You can use array_count_values.
<?php `$array = array(5, 5, 4, 1, 2, 3, 4, 2, 1, 3, 5, 5); $vals = array_count_values($array); echo 'No. of NON Duplicate Items: '.count($vals).'<br><br>'; print_r($vals);`?>
Output is -Array ( [5] => 4 [4] => 2 1 => 2 [2] => 2 [3] => 2 )
I'm working with a dataset coming back from a very complex view with multiple subselects and joins against several tables in a very large and convoluted database.
Each record has a structure like this:
MainValue = XXTS10, qtyPlaceholder1, qtyPlaceholder2, qtyPlaceholder3..., actualQty = 3, qtyPlaceholderKey = 1, color = blue.
MainValue = XXTS10, qtyPlaceholder1, qtyPlaceholder2, qtyPlaceholder3..., actualQty = 10, qtyPlaceholderKey = 3, color = blue.
MainValue = XXTS10, qtyPlaceholder1, qtyPlaceholder2, qtyPlaceholder3..., actualQty = 9, qtyPlaceholderKey = 2, color = blue.
So for each color and MainValue values, there are multiple records. I need to set the value of each qtyPlaceholder based on the actualQty where the qtyPlaceholderKey will tell me what value to put in each and derive only one record out of many so that the final single record looks like this:
MainValue = XXTS10, qtyPlaceholder1 = 3, qtyPlaceholder2 = 9, qtyPlaceholder3 = 10, color = blue.
I know I've done this hundreds of times over the years, but I'm just having mental block creating the proper looping structure and conditionals to create a single record out of many with the values mapped to the placeholders correctly. Trying to accomplish this in PHP, but it may be a good idea to reexamine the view and see if it can be adjusted, but I really don't want to go down that path if I can help it.
Any suggestions?
You can do this in SQL using conditional aggregation. Here is a select form of the query:
select MainValue,
max(case when qtyPlaceholderKey = 1 then actualQty end) as qtyPlaceholder1,
max(case when qtyPlaceholderKey = 2 then actualQty end) as qtyPlaceholder2,
max(case when qtyPlaceholderKey = 3 then actualQty end) as qtyPlaceholder3,
color
from complexview v
group by MainValue, color;
Loop through your array and create a new array indexed by
MainValue
color
qtyPlaceholderKey with a value of actualValue
Then 'flatten' the new array by looping through the new array and assigning key value pairs for MainValue, color and all qtyPlaceholderKeys and their corresponding actualValues.
$dbrows = array(
array(
'MainValue' => 'XXTS10',
'actualQty' => 3,
'qtyPlaceholderKey' => 1,
'color' => 'blue',
),
array(
'MainValue' => 'XXTS10',
'actualQty' => 9,
'qtyPlaceholderKey' => 2,
'color' => 'blue',
),
array(
'MainValue' => 'XXTS10',
'actualQty' => 10,
'qtyPlaceholderKey' => 3,
'color' => 'blue',
),
);
$values = array();
foreach($dbrows as $r) {
$values[$r['MainValue']][$r['color']][$r['qtyPlaceholderKey']] = $r['actualQty'];
}
$result = array();
foreach($values as $mainValue => $colorValues) {
foreach($colorValues as $color => $qtyPlaceholderValues) {
$row = array('MainValue' => $mainValue, 'color' => $color);
foreach($qtyPlaceholderValues as $qtyPlaceholderKey => $actualQty) {
$row["qtyPlaceholderKey$qtyPlaceholderKey"] = $actualQty;
}
$result[] = $row;
}
}
print_r($result);
Demo
I'm using jsTree to view hierarchical data that is stored in a mySQL database as a nested set (left, right, level, etc.). This is working fine, but I need to allow users to import data by uploading a CSV file. When they do so, any existing data in the table will be removed so I don't have to worry about updating the left/right fields.
The data they will be uploading will be in this format:
"Code","Title"
"100","Unit 100"
"200","Unit 200"
"101","Task 101: This is a task"
"102","Task 102: Another task"
"201","Task 201: Yet another"
"300","Unit 300"
"301","Task 301: Another one"
Everything will be a child of a main "Group" that is a level 1 node. All of the "codes" divisible by 100 (ie. 100, 200, 300) will be level 2 (parent nodes.. children of "Group"). All others will be level 3 (child) nodes of their respective parent nodes (ie. 101 and 102 are children of 100, 201 is a child of 200, etc.)
The resulting table in mySQL should look like this:
id parent_id position left right level title
1 0 0 1 18 0 ROOT
2 1 0 2 17 1 Group
3 2 0 3 8 2 Unit 100
4 2 1 9 12 2 Unit 200
5 3 0 4 5 3 Task 101: This is a task
6 3 1 6 7 3 Task 102: Another task
7 4 0 10 11 3 Task 201: Yet another
8 2 2 13 16 2 Unit 300
9 8 0 14 15 3 Task 301: Another one
The tree would then look like this:
My question is: using PHP, what is the best method to accomplish this? I already have code in place that pulls the data contained in the uploaded CSV file and stores it in an array, but I'm not sure what the logic to convert this to a nested set should look like.
Right now, the data is stored in a 2-dimensional array called $data (in the format $data[$col][$row]):
$data[0][0] = "Code";
$data[0][1] = "100";
$data[0][2] = "200";
$data[0][3] = "101";
$data[0][4] = "102";
$data[0][5] = "201";
$data[0][6] = "300";
$data[0][7] = "301";
$data[1][0] = "Title";
$data[1][1] = "Unit 100";
$data[1][2] = "Unit 200";
$data[1][3] = "Task 101: This is a task";
$data[1][4] = "Task 102: Another task";
$data[1][5] = "Task 201: Yet another";
$data[1][6] = "Unit 300";
$data[1][7] = "Task 301: Another one";
Array ( [0] => Array ( [0] => Code [1] => 100 [2] => 200 [3] => 101 [4] => 102 [5] => 201 [6] => 300 [7] => 301 ) [1] => Array ( [0] => Title [1] => Unit 100 [2] => Unit 200 [3] => Task 101: This is a task [4] => Task 102: Another task [5] => Task 201: Yet another [6] => Unit 300 [7] => Task 301: Another one ) )
Any help would be very much appreciated. I now have the parent_id, position, and level being calculated correctly... I just need to figure out the left/right part. Here is the code I'm currently using (thanks for getting me started Matteo):
$rows = array();
// insert ROOT row
$rows[] = array(
'id' => 1,
'parent_id' => 0,
'position' => 0,
'left' => 1,
'right' => 10000, // just a guess, will need updated later
'level' => 0,
'title' => 'ROOT',
);
echo "<br>";
print_r($rows[0]);
// insert group row
$rows[] = array(
'id' => 2,
'parent_id' => 1,
'position' => 0,
'left' => 2,
'right' => 9999, // just a guess, will need updated later
'level' => 1,
'title' => 'Group',
);
echo "<br>";
print_r($rows[1]);
// next ID to be used
$id = 3;
// keep track of code => ID correspondence
$map = array();
// parse data
for ($i = 1, $c = count($data[0]); $i < $c; ++$i) {
// save ID in the map
$map[$data[0][$i]] = $id;
// initialize the current row
$row = array(
'id' => $id,
'parent_id' => 1,
'position' => 0,
'left' => 0,
'right' => 0,
'level' => 1,
'title' => $data[1][$i],
);
// if the code is multiple of 100
if ($data[0][$i] % 100 == 0) {
$row['parent_id'] = 2;
$row['level'] = 2;
$row['position'] = (floor($data[0][$i] / 100)) - 1;
} else {
// get parent id from map
$row['parent_id'] = $map[floor($data[0][$i] / 100) * 100];
$row['level'] = 3;
$row['position'] = $data[0][$i] % 100;
}
// add the row
$rows[] = $row;
++$id;
echo "<br>";
print_r($row);
}
Given your $data array, you could parse it like this:
// this will contain all the rows to be inserted in your DB
$rows = array();
// insert ROOT row
$rows[0] = array(
'id' => 1,
'parent_id' => 0,
'position' => 0,
'level' => 0,
'left' => 1,
'right' => 10000,
'title' => 'ROOT',
);
// insert group row
$rows[1] = array(
'id' => 2,
'parent_id' => 1,
'position' => 0,
'level' => 1,
'left' => 2,
'right' => 9999,
'title' => 'Group',
);
// keep trace of code => ID correspondence
$map = array();
// next ID to be used
$id = 3;
// keep father => sons relationship
$tree = array();
// keep trace of code => row index correspondence
$indexes = array();
// next row index
$index = 2;
// parse your data
for ($i = 1, $c = count($data[0]); $i < $c; ++$i) {
// current code
$code = $data[0][$i];
// save ID in the map
$map[$code] = $id;
// update the indexes map
$indexes[$code] = $index;
// prepare the current row
$row = array(
'id' => $id,
'title' => $data[1][$i],
)
// get the value of code mod 100
$mod = $code % 100;
// if the code is multiple of 100
if ($mod == 0) {
// the parent_id is 2
$row['parent_id'] = 2;
// it is level two
$row['level'] = 2;
// compute position
$row['position'] = floor($code / 100) - 1;
}
else {
// get the parent code
$parent = floor($code / 100) * 100;
// get parent id from map using parent code
$row['parent_id'] = $map[$parent];
// it is level three
$row['level'] = 3;
// save position
$row['position'] = $mod;
// save in relationship tree
$tree[$parent][] = $code;
}
// add the row
$rows[$index] = $row;
// prepare next id
++$id;
// update row index
++$index;
}
// sort the relationship tree base on the parent code (key)
ksort($tree, SORT_NUMERIC);
// next left value
$left = 3;
// now, using the relationship tree, assign left and right
foreach ($tree as $parent => $sons) {
// calculate parent left value
$parentLeft = $left;
// prepare next left value
++$left;
// to be sure that the sons are in order
sort($sons, SORT_NUMERIC);
// assign values to sons
foreach ($sons as $son) {
// index in the rows array
$index = $indexes[$son];
// set left value
$rows[$index]['left'] = $left;
// set right value
$rows[$index]['right'] = $left + 1;
// increment left value
$left += 2;
}
// calculate parent right value
$parentRight = $left;
// prepare next left value
++$left;
// index of parent in the rows array
$index = $indexes[$parent];
// set the values
$rows[$index]['left'] = $parentLeft;
$rows[$index]['right'] = $parentRight;
}
// update group row right value
$rows[1]['right'] = $left;
// prepare next left value
++$left;
// update root row right value
$rows[0]['right'] = $left;
At this point, you can insert all the rows one at a time.
EDIT: now the script should handle all the required values correctly.
I would use Doctrine2 with a Nested Set Extension. You could use a nice and convenient API and don't have to worry about the nested set implementation:
See
http://www.gediminasm.org/article/tree-nestedset-behavior-extension-for-doctrine-2
or http://wildlyinaccurate.com/simple-nested-sets-in-doctrine-2
There are several extensions on github. Actually, i don't know which one is best.
https://github.com/l3pp4rd/DoctrineExtensions
https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior
https://github.com/blt04/doctrine2-nestedset
List item
If your data is flat, you could parse for keywords like 'Unit' or 'Task' to arrange your elements to the needed hierarchical order.