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
Related
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.
I have an array that contains any number of elements, and is allowed to be a multidimensional array, too. My testing example of such array data is:
$arr = array(
array('Material-A', 'Material-B'),
array('Profile-A', 'Profile-B', 'Profile-C'),
array('Thread-A', 'Thread-B'),
// ... any number of elements
);
From this multidimensional array I need to create a single array that is linear in the following format.
$arrFormated = array(
'Material-A',
'Material-A_Profile-A',
'Material-A_Profile-A_Thread-A',
'Material-A_Profile-A_Thread-B',
'Material-A_Profile-A_Thread-C',
'Material-A_Profile-B',
'Material-A_Profile-B_Thread-A',
'Material-A_Profile-B_Thread-B',
'Material-A_Profile-B_Thread-C',
'Material-A_Profile-C',
'Material-A_Profile-C_Thread-A',
'Material-A_Profile-C_Thread-B',
'Material-A_Profile-C_Thread-C',
'Material-B',
'Material-B_Profile-A',
'Material-B_Profile-A_Thread-A'
// Repeat similar pattern found above, etc...
);
For a recursive function, the best that I've been able to come up with thus far is as follows:
private function showAllElements($arr)
{
for($i=0; $i < count($arr); $i++)
{
$element = $arr[$i];
if (gettype($element) == "array") {
$this->showAllElements($element);
} else {
echo $element . "<br />";
}
}
}
However, this code is no where close to producing my desired results. The outcome from the above code is.
Material-A
Material-B
Profile-A
Profile-B
Profile-C
Thread-A
Thread-B
Could somebody please help me with the recursive side of this function so I may get my desired results?
I'd generally recommend thinking about what you want to be recursive. You tried to work with the current element in every recursion step, but your method needs to look at the next array element of the original Array in each recursion step. In this case, it's more useful to pass an index to your recursive function, because the 'current element' (the $arr in showAllElements($arr)) is not helpful.
I think this code should do it:
$exampleArray = array(
array('Material-A', 'Material-B'),
array('Profile-A', 'Profile-B', 'Profile-C'),
array('Thread-A', 'Thread-B','Thread-C'),
// ... any number of elements
);
class StackOverflowQuestion37823464{
public $array;
public function dumpElements($level = 0 /* default parameter: start at first element if no index is given */){
$return=[];
if($level==count($this->array)-1){
$return=$this->array[$level]; /* This is the anchor of the recursion. If the given index is the index of the last array element, no recursion is neccesarry */
}else{
foreach($this->array[$level] as $thislevel) { /* otherwise, every element of the current step will need to be concatenated... */
$return[]=$thislevel;
foreach($this->dumpElements($level+1) as $stringifyIt){ /*...with every string from the next element and following elements*/
$return[]=$thislevel.'_'.$stringifyIt;
}
}
}
return $return;
}
}
$test=new StackOverflowQuestion37823464();
$test->array=$exampleArray;
var_dump($test->dumpElements());
I have created an array list with the following code:
<?php
$ids = array();
if (mysql_num_rows($query1))
{
while ($result = mysql_fetch_assoc($query1))
{
$ids["{$result['user_id']}"] = $result;
}
}
mysql_free_result($query1);
?>
Now, i need to read two elements from the array. The first is the current and the second one is the next element of array. So, the simplified process is the following:
i=0: current_element (pos:0), next_element (pos:1)
i=1: current_element (pos:1), next_element (pos:2)
etc
To do this, i have already written the following code, but i cant get the next element for each loop!
Here is the code:
if (count($ids))
{
foreach ($ids AS $id => $data)
{
$userA=$data['user_id'];
$userB=next($data['user_id']);
}
}
The message i receive is: Warning: next() expects parameter 1 to be array, string given in array.php on line X
Does anyone can help? Maybe i try to do it wrongly.
The current, next, prev, end functions work with the array itself and place a position mark on the array. If you want to use the next function, perhaps this is the code:
if (is_array($ids))
{
while(next($ids) !== FALSE) // make sure you still got a next element
{
prev($ids); // move flag back because invoking 'next()' above moved the flag forward
$userA = current($ids); // store the current element
next($ids); // move flag to next element
$userB = current($ids); // store the current element
echo(' userA='.$userA['user_id']);
echo('; userB='.$userB['user_id']);
echo("<br/>");
}
}
You'll get this text on the screen:
userA=1; userB=2
userA=2; userB=3
userA=3; userB=4
userA=4; userB=5
userA=5; userB=6
userA=6; userB=7
userA=7; userB=8
You get the first item, then loop over the rest and at the end of each loop you move the current item as the next first item ... the code should explain it better:
if (false !== ($userA = current($ids))) {
while (false !== ($userB = next($ids))) {
// do stuff with $userA['user_id'] and $userB['user_id']
$userA = $userB;
}
}
Previous answer
You can chunk the arrays into pairs:
foreach (array_chunk($ids, 2) as $pair) {
$userA = $pair[0]['user_id']
$userB = $pair[1]['user_id']; // may not exist if $ids size is uneven
}
See also: array_chunk()
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.
I have the following code:
$friendsOrdered= new SplPriorityQueue();
... // Code to fill up the priority queue
print_r($friendsOrdered);
$i = 0;
foreach($friendsOrdered as $f) {
}
echo "<br><br>";
print_r($friendsOrdered);
When I look at the output of the prints I can see that after the loop the priority queue is empty. Is there a way of stopping the foreach from removing elements?
Thanks!
You cannot stop the loop from dequeuing elements, as that is the nature of the data structure. You could, however, create a copy of the object before the loop so your data isn't lost:
$friendsOrdered_copy = clone $friendsOrdered;
foreach($friendsOrdered_copy as $f) {
// ...
}