Recursive Array Exploding - php

I have string structured like
(cat,dog,fish) && (drinks) && (milk,water)
I need to convert to an array list like
cat drinks milk
cat drinks water
dog drinks milk
dog drinks water
fish drinks milk
fish drinks water
I've thought about doing it with a loop that takes each group and inserts them into an array like
0th pass:
fill the array with the first row
(cat,dog,fish) && (drinks) && (milk,water)
1st pass:
detect the first group and split it while removing the source
cat && (drinks) && (milk,water)
dog && (drinks) && (milk,water)
fish && (drinks) && (milk,water)
2nd pass
....
then loop this each time take the line split it add it to the end and remove the original.
Do you have a better idea? and in PHP?
For those who wonder It is part of sentence parsing code I'm writing.
Thanks

use normal string parsing to get 3 arrays which correspond to their groupings. I think you can figure it out with explode()
Then generate the "cartesian product" of the 3 arrays
$iterators = array(
new ArrayIterator(array('cat', 'dog', 'fish'))
, new ArrayIterator(array('drinks'))
, new ArrayIterator(array('milk', 'water'))
);
$citer = new CartesianProductIterator($iterators);
foreach ($citer as $combo) {
printf("[%s]\n", join(',', $combo));
}
Using
class CartesianProductIterator implements Iterator {
protected $iterators;
function __construct(array $iters) {
$this->iterators = $iters;
}
function rewind() {
foreach ($this->iterators as $it) {
$it->rewind();
}
}
function current() {
$values = array();
foreach ($this->iterators as $it) {
$values[] = $it->current();
}
return $values;
}
function key() {
return null;
}
function next() {
/*
loop them in reverse, but exclude first
why? example, odometer: 55199
you always check the rightmost digit first to see if incrementing it would roll it over and need to be "rewound" to 0,
which causes the digit to the left to increase as well, which may also cause it to roll over as well, and so on...
looping in reverse operates from right column to the left.
we dont rewind the first column because if the leftmost column is on its last element and needs to roll over
then this iterator has reached its end, and so rewind() needs to be explicitly called
*/
for ($i = count($this->iterators) - 1; $i > 0; --$i) {
$it = $this->iterators[$i];
$it->next();
if ($it->valid()) {
// were done advancing because we found a column that didnt roll over
return;
} else {
$it->rewind();
}
}
//if execution reached here, then all of the columns have rolled over, so we must attempt to roll over the left most column
$this->iterators[0]->next();
}
function valid() {
return $this->iterators[0]->valid();
}
}

Once i needed to make every combination of similar sets. I had a recursive function which was actually very resource-intensive on big array (9 parts containing 5 items each) but i can try to adjust it for you:
$input=array(array("cat","dog","fish"),array("drinks"),array("milk","water"));
$output=array();
function combination($string,$level)
{
global $output;
global $input;
if (isset($input[$level]))
{
$item=$input[$level];
if (is_array($item))
{
foreach ($item as $i)
combination($string." ".$i,$level+1);
}
else
combination($string." ".$item,$level+1);
}
else
$output[]=$string;
}
combination("",0);
var_export($output);
However converting your string into input array is different problem, which i am not sure how to solve so i will keep it up to you.

Related

DFS Recursion Space Usage PHP

Since arrays are not treated as objects in php, I have to manually reference them in order to recursively fill an array or constantly copy the new created/filled arrays from recursive calls.
"return" myRecursiveFunc() is not an option because that would end the function at the wrong time.
One way: Referencing
public function depthFirstSearchInOrder()
{
$list = []; //otherwise php screams that only variables can be referenced (inside traverseInOrder)
//Otherwise I would have passed an empty [] as second parameter
return traverseInOrder($this->root, $list);
}
function traverseInOrder($node, &$list)
{
if ($node->left) {
traverseInOrder($node->left, $list);
}
$list[] = $node->value;
if ($node->right) {
traverseInOrder($node->right, $list);
}
return $list;
}
Second way: Creating/Copying arrays
public function depthFirstSearchInOrder()
{
return traverseInOrder($this->root);
}
function traverseInOrder($node, $list = [])
{
if ($node->left) {
$list = traverseInOrder($node->left, $list);
}
$list[] = $node->value;
if ($node->right) {
$list = traverseInOrder($node->right, $list);
}
return $list;
}
Doesn't the second way eat up way more space?
I'm creating new Arrays for each recursive call which I'm then copying again in $list.
Or am I just confused at the moment?
If I was to traverse this tree
// 9,
// 4, 20
// 1, 6, 15 170
Using the second way
I would start with 9
Go left and call recursive (because of 4)
Go left and call recursive (because of 1)
I fill my empty array with 1 and return the array [1]
I'm back to value 4 where I now assign to $list = [1] (which now is a copy of [1] NOT a reference, right? So the array [1] still exists afterwards somewhere?
I push 4 in the array [1,4] and go right (because of 6)
I push 6 to the array [1,4,6] and return this array [1,4,6]
I'm back to value 4 where I now assign to $list = [1,4,6] (which now is a copy of [1,4,6] NOT a reference, right? So the array [1,4,6] still exists afterwards somewhere?
And so on and so forth...
Back to my question:
Isn't the second way more space hungry? Or am I just confused at the moment?

SPL InfiniteIterator is not working with next or prev

I wanted to over an array infinitely like a circular array.
I have the following setup using InfiniteIterator1, which iterates over the $players infinitely. But actually, I want to know the next player and the previous player from this loop like below
$players = new InfiniteIterator(new ArrayIterator(['Shobi', 'Jomit']));
foreach ($players as $player) {
echo $player; // current player name comes properly infinetly
echo next($players); // next player should come
echo current($players); // current player should come
echo prev($players); //previous player should come
}
but next() and prev() always return null
From the doc, I can see those methods are returning void, but is there any way I can extend the InfiniteIterator and achieve this mechanism required?
How do I make next() and prev() work with InfiniteIterator?
Edit current() returns current item, (meaning, it works properly in the logic)
If instead of using iterators, you can just use an array and a pointer to the 'current' entry and then use a while(true) loop which will just keep going (you can always add a break to stop it for testing or some condition). The various parts of the logic check if the current player is the last one - so the next one is the start one -
or if it's the first item so the previous is the end item. Also the increment resets once it gets to the end and starts over again...
$players = ['Shobi', 'Jomit'];
$playerKey = 0;
$playerCount = count($players);
while(true) {
echo $players[($playerKey+1)%$playerCount].PHP_EOL; // next player
echo $players[$playerKey].PHP_EOL; // current player
echo $players[($playerKey>0)?$playerKey-1:$playerCount-1].PHP_EOL; //previous player
$playerKey = ( $playerKey+1 == $playerCount )?0:$playerKey+1;
}
You can go nuts with PHP iterators and do something like the following:
$array = ['Shobi', 'Jomit', 'John', 'Jane', 'Smith'];
// Use NoRewindIterator to prevent MultipleIterator rewinding.
$players1 = new NoRewindIterator(new InfiniteIterator(new ArrayIterator($array)));
// Go to the end of array, i.e. set prev player.
for ($i = 0, $size = count($array); $i < $size - 1; $i++) {
$players1->next();
}
$players2 = new InfiniteIterator(new ArrayIterator($array));
$players2->next();
// Use NoRewindIterator to prevent MultipleIterator rewinding.
$players3 = new NoRewindIterator(new InfiniteIterator(new ArrayIterator($array)));
$players3->next(); // Go to the second player, i.e. next player
// MultipleIterator will traverse three iterators at once.
// Since the pointer in each iterator differs in one position, we will have prev, curr and next.
$players = new MultipleIterator(MultipleIterator::MIT_NEED_ALL|MultipleIterator::MIT_KEYS_ASSOC);
$players->attachIterator($players1, 'prev');
$players->attachIterator($players2, 'curr');
$players->attachIterator($players3, 'next');
$i = 0;
foreach ($players as $player) {
print_r($player);
if (++$i >= 10) {
break;
}
}
Please, see the demo.
Posting an answer which I finally came up with.
The infinite iterator was not really working for my case. Especially because I was not able to move the pointer back using the prev() function. So I did implement the Iterator Interface and override the next and prev methods accordingly.
So that when next() is called and if it is at the end of the array, it will rewind the pointer to the beginning, likewise, if prev() is called and the pointer is already at the beginning, it will move the pointer to the end of the internal array. This worked for me perfectly.
Relevant code
<?php
class CircularIterator implements Iterator
{
...
...
public function next()
{
next($this->entries);
if (!$this->current()) {
$this->rewind();
}
}
public function prev()
{
prev($this->entries);
if (!$this->current()) {
$this->end();
}
}
....
....
}
full implementation and sample code - Link

Array Filter and Array Values taking a long time on large PHP array of objects

I have a piece of code running a simulation.
public function cleanUpHouses(\DeadStreet\ValueObject\House\Collection $collection)
{
$houses = $collection->getHouses();
$housesLength = count($houses);
$filterValues = false;
for($i = 0; $i < $housesLength; $i++) {
if(!$this->houseModel->hasBeenAttacked($houses[$i])) {
break;
}
$houses[$i]->setCurrentAttackers(0);
if($this->houseModel->requiresDestroying($houses[$i])) {
$houses[$i] = null;
$filterValues = true;
}
}
if($filterValues) {
$houses = array_values(array_filter($houses));
}
$collection->setHouses($houses);
return $collection;
}
However, $collection contains an array ($getHouses) of up to and over 1 million results, although it will never need to iterate over all of these results, the line $houses = array_values(array_filter($houses)) is taking ages due to the sheer size of the array, (up to 3 seconds each time this line is ran).
I have to keep the array index numeric, and there can be no null values in this array.
I was hoping unset($array[$i]) would shift the array elements after the element being unset 'down' in key, so if I was to unset($array[5]), then $array[6] would become $array[5], however it doesn't seem to work like this.
The break conditional is there because, on an iteration if the house under iteration hasn't been attacked, then it's safe to assume any other house after that in the array has also not been attacked.
Is there an optimal, less resource heavy way to achieve this?
I can't really restructure this at the moment as it's in unit tests and I need it finishing ASAP, the approach isn't great, but eh.
I think the most painless way to achieve this is something like this:
While you are looping through the array of houses, and you need to unset something in the array, you can cheat the loop itself.
if($this->houseModel->requiresDestroying($houses[$i])) {
// $houses[$i] = null;
// when you unset the $i house in the array,
// you can simply switch it with the last one in the array, keeping in mind,
// that this may break your logic with the break condition, so will want to change that as well.
$lastHouse = $houses[$housesLength - 1];
$houses[$i] = $lastHouse;
unset($houses[$housesLength - 1]);
$i--;
$housesLength--; // by doing the top two lines we would make the loop check the last house again.
$shouldBreak = false; // this will keep your logic with the break if later.
// $filterValues = true; // you can remove this line here.
}
You would want to set up a variable for the break condition before the for loop starts.
$shouldBreak = true;
for($i = 0; $i < $housesLength; $i++) {
...
And now for the condition itself
if(!$this->houseModel->hasBeenAttacked($houses[$i]) && true === $shouldBreak) {
break;
} else {
$shouldBreak = true; // we set $shouldBreak = false when we unset the last house,
// so we would want to keep checking the houses not to break the logic.
}
We only will remove the last element in the array, so it will be kept numeric.

Iterating over one dimension of array until a value found

I have an array as follows
$array[$id][$iterator][test1][test2][a][b][c][d]
I would like to test for each $iterator for the first instance of test1=not null (if so use a, b, c d)
else use the first instance of test2=not null use (a, b, c, d)
else use "---"
I don't know how to get a loop to break on first instance of a finding test1 and test2 or if there is a better construct to use than a loop in this case?
To break an iteration or loop
Just tell it to take a break :)
foreach ($array as $item) {
if ($item == 'I the great overlord command you to stop iterating!') {
break;
}
}
If you wanna...
if you just want to know if a certain value is in your array, you can do it like this:
if (in_array('The value you are looking for', $thatArrayItShouldBeIn)) {
die("Its in! Its in! Yippie!");
}
From the comments
Tell me if I'm wrong, but from your comment I come to understand you want your iteration to break at a certain loop?
foreach ($array as $key => $item) {
// Keys start at 0, so the first item is 0
if ($key == 3) {
// We'll stop looping at the 4th iteration
break;
}
}
Taking yet another look
If you really have an array with alot of layers and you just want to check one value for each iteration you can just do:
foreach ($array[$id]['iterator'] as $item) {
if ($item['test1'] == true) {
// Do something
}
} }

String together values of parent nodes from a multidimensional PHP array

I have a table of workplaces and their parent ids. Each parent could have any number of levels.
id workplace parent
1 WCHN 0
2 Acute Services 1
3 Paediatric Medicine 2
4 Surgical Services 2
5 Nursing and Midwifery 1
6 Casual Pool 5
7 Clinical Practice 5
I need to create a single select input that lists the workplaces with all their parent workplaces, a bit like this:
<select>
<option>WCHN > Acute Services > Paediatric Medicine</option>
<option>WCHN > Acute Services > Surgical Services</option>
<option>WCHN > Nursing and Midwifery > Casual Pool</option>
<option>WCHN > Nursing and Midwifery > Clinical Practice</option>
</select>
By modifying this solution I've been able to turn my flat list into a multidimensional array and output pairs of values, but no the full paths.
<?php
public function list_all_workplaces()
{
$temp = $result = array();
$list = array();
// Get the data from the DB
$table = mysql_query("SELECT * FROM workplaces");
// Put it into one dimensional array with the row id as the index
while ($row = mysql_fetch_assoc($table)) {
$temp[$row['workplace_id']] = $row;
}
// Loop the 1D array and create the multi-dimensional array
for ($i = 1; isset($temp[$i]); $i++)
{
if ($temp[$i]['parent'] > 0)
{
$tmpstring = ($temp[$i]['workplace']); // workplace title
// This row has a parent
if (isset($temp[$temp[$i]['parent']])) {
$list[$i] = $temp[$temp[$i]['parent']]['workplace']." > ".$tmpstring;
//example output: Acute Services > Paediatric Medicine
// The parent row exists, add this row to the 'children' key of the parent
$temp[$temp[$i]['parent']]['children'][] =& $temp[$i];
} else {
// The parent row doesn't exist - handle that case here
// For the purposes of this example, we'll treat it as a root node
$result[] =& $temp[$i];
}
} else {
// This row is a root node
$result[] =& $temp[$i];
}
}
// unset the 1D array
unset($temp);
//Here is the result
print_r($result);
}
Example print_r() output:
[1] => WCHN > Acute Services
[2] => Acute Services > Paediatric Medicine
Where do I go from here to get all of the parent workplaces into the select options?
I can't see how your code will produce the print_r output you say you're seeing, as you're not ever using the string you create. But something along these lines should get you on your way - it generates the strings you need.
Replace your for loop with the following:
foreach ($temp as $i => $details)
{
$parentID = $details['parent'];
$tmpstring = ($details['workplace']);
if ($parentID > 0 && isset($temp[$parentID]))
{
$temp[$parentID]['children'][] =& $temp[$i];
while ($parentID > 0 && isset($temp[$parentID]))
{
$tmpstring = $temp[$parentID]['workplace']." > ".$tmpstring;
$parentID = $temp[$parentID]['parent'];
}
}
$result[] = $tmpstring;
}
As #Syx said, you'll need to return something from your function too, probably $result, and then use that to generate your <select>.
I don't understand how you even use your function if there is no return value.
If your array is built correctly and the way you want it and your asking about getting your results into an html select field then that's the easy part.
Let's get back to your function real quick
.
You don't have return $result listed in your function so it's not going to return anything when called.
You'll want to add that to the end of the function to start.
You'll then loop through the array to start your html processing.
$data = list_all_workplaces()
foreach( $data as $key => $value )
{
$options = "<option value='{$key}'>{$value}</option>";
}
echo "<select>{$options}</select>";

Categories