Forget not modifying the original collection Laravel 5.7 - php

I'm attempting to to remove random elements from a collection while iterating through a for loop. The idea is that each time it goes through the for loop, the items in the collection available doesn't include items found and removed in previous iterations. I've attempted to use forget on the initial collection, but i'm still finding doubles in the results. Any help would be appreciated, thanks!
$data = SomeModel::get();
for($i = 1; $i <= $max; $i++) {
$random = $data->random($numberToPick);
foreach($random as $option) {
$data->forget($option->id);
}
}
I dd($data) at the end, and do indeed see the items selected are removed from the final collection, but as mentioned, i'm still getting some randoms that have keys matching previous iterations.

The problem here is that forget will remove the item by its index. You are giving as argument the id of the object inside the collection, but its index is not the same as the id.
Try this:
$data = SomeModel::get()->keyBy('id');
for($i = 1; $i <= $max; $i++) {
$random = $data->random($numberToPick);
foreach($random as $option) {
$data->forget($option->id);
}
}

Related

Is there a way to generate new array variables in a loop in PHP?

I am looking for a way to create new arrays in a loop. Not the values, but the array variables. So far, it looks like it's impossible or complicated, or maybe I just haven't found the right way to do it.
For example, I have a dynamic amount of values I need to append to arrays. Let's say it will be 200 000 values. I cannot assign all of these values to one array, for memory reasons on server, just skip this part.
I can assign a maximum amount of 50 000 values per one array. This means, I will need to create 4 arrays to fit all the values in different arrays. But next time, I will not know how many values I need to process.
Is there a way to generate a required amount of arrays based on fixed capacity of each array and an amount of values? Or an array must be declared manually and there is no workaround?
What I am trying to achieve is this:
$required_number_of_arrays = ceil(count($data)/50000);
for ($i = 1;$i <= $required_number_of_arrays;$i++) {
$new_array$i = array();
foreach ($data as $val) {
$new_array$i[] = $val;
}
}
// Created arrays: $new_array1, $new_array2, $new_array3
A possible way to do is to extend ArrayObject. You can build in limitation of how many values may be assigned, this means you need to build a class instead of $new_array$i = array();
However it might be better to look into generators, but Scuzzy beat me to that punchline.
The concept of generators is that with each yield, the previous reference is inaccessible unless you loop over it again. It will be in a way, overwritten unlike in arrays, where you can always traverse over previous indexes using $data[4].
This means you need to process the data directly. Storing the yielded data into a new array will negate its effects.
Fetching huge amounts of data is no issue with generators but one should know the concept of them before using them.
Based on your comments, it sounds like you don't need separate array variables. You can reuse the same one. When it gets to the max size, do your processing and reinitialize it:
$max_array_size = 50000;
$n = 1;
$new_array = [];
foreach ($data as $val) {
$new_array[] = $val;
if ($max_array_size == $n++) {
// process $new_array however you need to, then empty it
$new_array = [];
$n = 1;
}
}
if ($new_array) {
// process the remainder if the last bit is less than max size
}
You could create an array and use extract() to get variables from this array:
$required_number_of_arrays = ceil($data/50000);
$new_arrays = array();
for ($i = 1;$i <= $required_number_of_arrays;$i++) {
$new_arrays["new_array$i"] = $data;
}
extract($new_arrays);
print_r($new_array1);
print_r($new_array2);
//...
I think in your case you have to create an array that holds all your generated arrays insight.
so first declare a variable before the loop.
$global_array = [];
insight the loop you can generate the name and fill that array.
$global_array["new_array$i"] = $val;
After the loop you can work with that array. But i think in the end that won't fix your memory limit problem. If fill 5 array with 200k entries it should be the same as filling one array of 200k the amount of data is the same. So it's possible that you run in both ways over the memory limit. If you can't define the limit it could be a problem.
ini_set('memory_limit', '-1');
So you can only prevent that problem in processing your values directly without saving something in an array. For example if you run a db query and process the values directly and save only the result.
You can try something like this:
foreach ($data as $key => $val) {
$new_array$i[] = $val;
unset($data[$key]);
}
Then your value is stored in a new array and you delete the value of the original data array. After 50k you have to create a new one.
Easier way use array_chunk to split your array into parts.
https://secure.php.net/manual/en/function.array-chunk.php
There's non need for multiple variables. If you want to process your data in chunks, so that you don't fill up memory, reuse the same variable. The previous contents of the variable will be garbage collected when you reassign it.
$chunk_size = 50000;
$number_of_chunks = ceil($data_size/$chunk_size);
for ($i = 0; $i < $data_size; $i += $chunk_size) {
$new_array = array();
foreach ($j = $i * $chunk_size; $j < min($j + chunk_size, $data_size); $j++) {
$new_array[] = get_data_item($j);
}
}
$new_array[$i] serves the same purpose as your proposed $new_array$i.
You could do something like this:
$required_number_of_arrays = ceil(count($data)/50000);
for ($i = 1;$i <= $required_number_of_arrays;$i++) {
$array_name = "new_array_$i";
$$array_name = [];
foreach ($data as $val) {
${$array_name}[] = $val;
}
}

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.

Delete the whole row from an array in php

How do I delete the whole line from an array? When the delete-button is pressed it should delete the whole line.
my array looks like that:
$liste[0][0] = email-user1
$liste[0][1]= password-user1
$liste[1][0] = email-user2
$liste[1][1]= password-user2
So if I delete the user one, the user2 should just take the place from user1(which should just disappear).
if (isset($_GET['delete'])){
$id=key($_GET['delete']);
for ($i = 0; $i < count($liste); $i++){
if ("$i"=="$id"){
unset($liste[$id][0]);
unset($liste[$id][1]);
unset($liste[$id][2]);
}
else{
}
}
update
I'm using array_splice($liste, $id, 1); now but everytime I try to save it to the file I get an error: implode(): Invalid arguments passed. For saving it to the file, I use the following function:
function saveDataToFile($fileName, $liste){
$file=fopen($fileName,"w");
for ($i = 0; $i < count($liste); $i++) {
$zArray=$liste[$i];
$zeile=implode("|", $zArray);
if(strlen($zeile) > 0){
$zeile=$zeile."\r\n";
fwrite($file, $zeile);
}
}
fclose($datei);
}
Try the below code:
$liste[0][0] = "email-user1";
$liste[0][1]= "password-user1";
$liste[1][0] = "email-user2";
$liste[1][1]= "password-user2";
$liste[2][0] = "email-user3";
$liste[2][1]= "password-user3";
unset($liste[1]); // say you want to delete this row
$new_arr = $liste;
unset($liste);
$i=0;
foreach($new_arr as $value){
$liste[$i] = $value;
$i++;
}
You can use array_splice() method:
array_splice($liste, $id, 1);
$liste[0][0], $liste[0][1] and $liste[0][2] are in real nothing else than a value array(value, value, value) (the inner array) which is assigned to $liste[0] (the outer array)
unsetting this (outer) array value $liste[0] is enough:
unset($liste[$id]);
If you care about the keys of this array (I see you loop from 0..n), you need to reindex your array using:
$liste = array_values($liste);
This will make your array behaving more like arrays normally do in other programming languages
Good practice is to use foreach instead of for. In this case you don't need to reindex:
for ($liste as $key=>$value){
if ("$key"=="$id"){
unset($liste[$key]);
}
But anyway you don't have to loop through an array just for finding a key. It's enough doing this:
if (isset($liste[$id])) { /* optional: check if the key exists */ }
unset($liste[$id]);

How to get data from multiple collection in loop

When I try to get data from multiple collections code it is giving me data only from the first collection
i.e collections are project_0, project_1, project_2, project_3
for($i = 0; $i <= 3; $i++){
$dm->getClassMetadata('\Application\Document\Product')->setCollection('product_'. $i);
$record = $dm->getRepository('\Application\Document\Product')->findOneBy($condition);
print_r($record);
}
I tried to clear flush but noting is working. Please let me know the right way to do it?
Executing the code above will save the information only from the last collection in $record as you are overwriting the data in it with every iteration.
To fix it, you can create an array, let's say $records = array(); and then in each iteration you can do something like this:
array_push($records, $dm->getRepository('\Application\Document\Product')->findOneBy($condition));
After you're done, you'll have all the data in $records. I hope that helps.

Retrieve POST items with different names

I'm passing some values with POST to another file.
One (or more) of those values are:
Equip1, Equip2, Equip3, ..., EquipN
I'm able to know how many EquipN I have, but not know how to get their values.
I thought this works, but it's not:
for ($Equip_aux=1; $Equip_aux<=$Number_of_Equip; $Equip_aux++) {
$aux = "'Equipamento".$Equip."'";
$VALUE = $_POST[$aux];
}
Any help, please?
I'm able to know how many EquipN I have, but not know how to get their values.
Here's the corrected version of your for loop:
for ($i = 1; $i <= $Number_of_Equip; $i++) {
$aux = "Equip$i";
$VALUE = $_POST[$aux];
}
Notice that you can also loop trough the post values with foreach:
foreach ($_POST as $p) {
...
}

Categories