How to make a code DRY without repeat, PHP - php

i wondering how i can make it simpler...
the thing is
if($results['experience'] == 0) { loadViev('experience',$experience, $careerGoals,$mainSectionLines); }
if($results['Education'] == 0) { loadViev('Education',$graduate,$careerGoals,$mainSectionLines); }
if($results['Extra'] == 0) { loadViev('Extra',$extra,$careerGoals,$mainSectionLines); }
if($results['Licence'] == 0) { loadViev('Licence',$licence,$careerGoals,$mainSectionLines); }
if($results['Cert'] == 0) { loadViev('Certyfikaty',$cert,$careerGoals,$mainSectionLines); }
if($results['conferences'] == 0){ loadViev('Conferences',$conferences,$careerGoals,$mainSectionLines); }
if($results['Courses'] == 0) { loadViev('Courses',$Courses,$careerGoals,$mainSectionLines); }
if($results['Hobbys'] == 0) { loadViev('Hobby',$hobby,$careerGoals,$mainSectionLines); }
As you can see, if some "name" == 0 function will run, there is for now around 14 combination, i know there is possible to do it faster than copy and paste whole code 14 times...
result code:
$results =[];
foreach ($userChoice as $key => $value) {
$setVal = $value['key'];
$results[$setVal] = $value['order'];
}
Result only grab name of a section and order nr.
'$userChoice' is just a array with data
Do anybody have a idea how i can do it?
Also the thing is result collect all the section data (14 section) where as you can see i need only selected 8.

The only difference is the word being loaded and the name of the variable. The word remains the same, but the variable is lowercase.
As such, all you would need to do is loadViev the $setVal name (the word itself) as the first argument, and $$setval as the second. This executes the word as a variable, making use of variable variables.
Unfortunately you can't (easily) use something like strtolower() on a variable variable directly, so you'll have to convert these to lowercase independently first.
This can be seen in the following:
$results =[];
foreach ($userChoice as $key => $value) {
$setVal = $value['key'];
$results[$setVal] = $value['order'];
if ($value['order'] == 0) {
$lower = strtolower($setVal);
loadViev($setVal, $$lower, $careerGoals, $mainSectionLines);
}
}

How about creating a structure holding relevant values and iterating over it?
$mapping = [
['key' => 'experience', 'view' => 'experience','data' => $experience],
['key' => 'Education', 'view' => 'Education','data' => $graduate],
['key' => 'Extra', 'view' => 'Extra','data' => $extra],
...
...
];
foreach ($mapping as $m)
{
if ($results[$m['key']]==0)
{
loadViev($m['view'], $m['data'], $careerGoals,$mainSectionLines);
break;
}
}
if you can make your key/variable names consistent, you could simplify the code even further. E.g.
$validKeys = ['experience', 'education', ... ];
foreach($validKeys as $k)
{
if($results[$k] == 0)
{
loadviev($k, $$k, $careerGoals,$mainSectionLines)
}
}

Related

How can I repeat a specific iteration in a foreach loop in PHP?

As there is no iterator in PHP, the only way to loop through an array without getting the length of the array is to use foreach loop.
Let say I have the following loop:
foreach ($testing_array as $testing_entry) {
$result = my_testing_api_call($testing_entry);
if ($result == 'server dead')
break;
else if ($result == 'done') {
// do something to handle success code
continue;
}
else {
sleep(5);
// I want to retry my_testing_api_call with current $testing entry, but don't know what to write
}
}
One way to do that is to use for loop instead.
for ( $i=0; $i < count($testing_array); $i++ ) {
$result = my_testing_api_call($testing_entry[$i]);
if ($result == 'server dead')
break;
else if ($result == 'done') {
// do something to handle success code
continue;
}
else {
sleep(5);
$i--; //so it will repeat the current iteration.
}
}
The problem is that the $testing_array is not originally using number as index, so I have to do some data massage to use a for loop. Is there a way I can repeat a specific iteration in a foreach loop?
Perhaps a do-while will work for you.
Untested Code:
foreach ($testing_array as $testing_entry) {
do {
$result = my_testing_api_call($testing_entry);
if ($result == 'server dead') {
break 2; // break both loops
} elseif ($result == 'done') {
// do something to handle success code
} else {
sleep(5);
// I want to retry my_testing_api_call with current $testing entry, but don't know what to write
}
} while ($result !== 'done');
}
Or a single loop structure that destroys the input array as it iterates.
Untested Code:
$result = '';
while ($testing_array && $result !== 'server dead') {
$result = my_testing_api_call(current($testing_array));
if ($result == 'done') {
// do something to handle success code
array_shift($testing_array);
} elseif ($result !== 'server dead') {
sleep(5); // I want to retry my_testing_api_call with current $testing entry, but don't know what to write
}
}
Or you can use your for loop by indexing $test_array with array_values() if you don't need the keys in your // do something.
$testing_array = array_values($testing_array);
for ($i=0, $count=count($testing_array); $i < $count; ++$i) {
$result = my_testing_api_call($testing_entry[$i]);
if ($result == 'server dead') {
break;
} else if ($result == 'done') {
// do something to handle success code
} else {
sleep(5);
--$i; //so it will repeat the current iteration.
}
}
If you do need the keys down script, but you want to use for, you could store an indexed array of keys which would allow you to use $i to access the keys and maintain data synchronicity.
My final suggestion:
Use while (key($testing_array) !== null) {...} to move the pointer without destroying elements.
Code: (Demo)
$array1 = [
"one" => 1,
"two" => 2,
"three" => 3,
"four" => 4
];
while (key($array1)!==null) { // check if the internal pointer points beyond the end of the elements list or the array is empty
$current = current($array1);
$key = key($array1);
echo "$key => $current\n"; // display current key value pair
if ($current < 3) { // an arbitrary condition
echo "\t";
$array1[$key] = ++$current; // increment the current value
} else { // current value is satisfactory
echo "\t(advance pointer)\n";
next($array1); // move pointer
}
}
Output:
one => 1
one => 2
one => 3
(advance pointer)
two => 2
two => 3
(advance pointer)
three => 3
(advance pointer)
four => 4
(advance pointer)
You are trying to handle two different things in your loop, that makes it hard to write clean control flow. You could separate the retry-logic from the result handling:
function call_api_with_retry($entry) {
do {
if (is_defined($result)) sleep(5);
$result = my_testing_api_call($entry);
} while ($result != 'done' && $result != 'server dead');
return $result;
}
foreach ($testing_array as $testing_entry) {
$result = call_api_with_retry($testing_entry);
if ($result == 'server dead')
break;
else if ($result == 'done') {
// do something to handle success code
continue;
}
}
(there might be syntax errors, it's been a while since I wrote PHP code)
To repeat a single specific iteration you need to add a control mechanism, it is not an intended behavior after all.
There are many suggestions here, all of them are kinda over-engineered.
PHP is a high level derivate of C and both languages have the 'goto' operator, it has a bad reputation because people have historically used it too much.
In the end a foreach/while loop is internally nothing else than a 'goto' operation.
Here is the code:
foreach ($array as $key => $value)
{
restart:
if ($value === 12345) goto restart;
}
That's how this should be done just keep in mind that this can cause an endless loop, like in the example.
Look at the next complicated version without goto:
foreach ($array as $key => $value) while(1)
{
if ($value === 12345) continue;
break;
}
This is essentially the same as the goto, just with more code.
If you'd want to "break" or "continue" the foreach loop you'd write "break 2" or "continue 2"

create associative array from collection in laravel for checking purposes

I am having an hard time trying to create an associative array from a collection in Laravel. The array should then be used for case checking.
I get my collection like this:
$collected_items = $user->collected_items()->where('model_id', '=', $model_id)->get();
I need to extract only some relevant data from this collection like 'color_id'
I need to check the color_id because I should run different code if the color_id = 0, 1 or 2
Since I don't want to do a DB query for every case I thought I'd better eager load the data and then put it in an associative array;
However for the life of me I can't create this array
I tried:
$array_of_colors = []
foreach ($collected_items as $i) {
if ($i['color_id'] == 0) {
$array_of_colors += ['black' => $i->model_color_id];
}
if ($i['color_id'] == 1) {
$array_of_colors += ['colored' => $i->model_color_id];
}
if ($i['color_id'] == 2) {
$array_of_colors += ['white' => $i->model_color_id];
}
}
Then I would use the $array_of_colors to check if I have a case of black then do something, is white something else etc etc.
Instead of doing it that way I highly recommend using the Collection functions available in Laravel. In particular I think filter() will work well for you.
https://laravel.com/docs/5.4/collections#method-filter
Something like this:
$black_items = $collected_items->filter(function ($value, $key) {
return $value->model_color_id == 1;
});
$array_of_colors = []
foreach ($collected_items as $i) {
if ($i['color_id'] == 0) {
$array_of_colors[] = ['black' => $i->model_color_id];
}
if ($i['color_id'] == 1) {
$array_of_colors[] = ['colored' => $i->model_color_id];
}
if ($i['color_id'] == 2) {
$array_of_colors[] = ['white' => $i->model_color_id];
}
}

Sorting racers / players by places

I have data about some racers presented in the following form:
array(
array(name => "the first racer", places => [1,3,1,5,6,2,6,7,8]),
array(name => "the second racer", places => [2,4,2,5,7])
...
)
Advise the best way to sort them so that the first racers were who have better places. For example If the first racer has at least one first place and the other not, the first is higher in the list. If they both have the first places, compare the number of first place. If the number equal too, compare the second places and so on.
My solution (It does not look very elegant. Maybe it can be done somehow easier):
$racers = array(
array('name' => "the first racer", 'places' => [1,3,1,5,6,2,6,7,8,9]),
array('name' => "the second racer", 'places' => [1,3,1,5,6,2,6,7,8]),
array('name' => "the third racer", 'places' => [2,3,2,5,7,10]),
array('name' => "the fourth racer", 'places' => [2,3,10,6,6,10]),
array('name' => "the fifth", 'places' => [2,3,2,5,7,10,1]),
);
usort($racers, function($prev, $next) {
// order places for every racer
sort($prev['places']);
sort($next['places']);
//compare each place with each other
foreach ($prev['places'] AS $key => $prevRacerPlace) {
// if all values are equal, we compare the number of races
if (!isset($next['places'][$key])) {
return -1;
}
$nextRacerPlace = $next['places'][$key];
$diff = $prevRacerPlace - $nextRacerPlace;
if ($diff !== 0) {
return $diff;
}
}
// if all values are equal, we compare the number of races
if (count($next['places']) > count($prev['places'])) {
return 1;
}
});
var_dump($racers);
It would be a quite nice to make some preparations before custom sorting. So we avoid a nested sorts in lambda function:
foreach ($racers as $index => $racer) {
$racers[$index]['sorted_places'] = $racer['places'];
sort($racers[$index]['sorted_places']);
}
In sorting lambda function we compare a heads of prepared sorted places and return a first defined value. If racer B top place result better than A, return 1. If racer A top place result better than B, return -1. On equal results continue checks for next top places.
usort($racers, function ($a, $b) {
unset($value);
do {
$topA = array_shift($a['sorted_places']);
$topB = array_shift($b['sorted_places']);
if (is_null($topA) && is_null($topB)) {
$value = 0;
} elseif (is_null($topA)) {
$value = 1;
} elseif (is_null($topB)) {
$value = -1;
} elseif ($topA > $topB) {
$value = 1;
} elseif ($topA < $topB) {
$value = -1;
}
} while (!isset($value));
return $value;
});
Here is one more algorithm, but I think that Max Zuber's solution is more efficient. Anyway:
Define how many places were for each racers by array_count_values
foreach ($racers as &$racer) {
$racer['number_places'] = array_count_values($racer['places']);
}
and sorting
usort($racers, function($current, $next) {
$next_places = $next['number_places'];
$current_places = $current['number_places'];
for ($i=1; $i<=max($next_places, $current_places); $i++) {
if (!isset($current_places[$i]) && !isset($next_places[$i])) {
continue;
}
if (!isset($current_places[$i])) {
return 1;
}
if (!isset($current_places[$i])
|| $current_places[$i] > $next_places[$i])
{
return -1;
}
}
});

php to set order of preference of an Array Sequence using string length

$records = array(
'123PP' => 3.63,
'123DDD' => 9.63,
'123D' => 6.63,
'123PPPP' => 9.63,
'123DD' => 9.63,
'123P' => 2.63,
'123PPP' => 1.53
);
After looping through the records, I have to get only one value
whose key should be 123D because the order of preference is:
123D, 123P, 123DD, 123PP, 123DDD, 123PPP, 123PPPP...
For e.g.:
If 123D is not found in the array, then 123P is the answer.
If 123P is not found in the array, then 123DD is the answer.
And I have found a solution :
foreach ($records as $key => $value) {
if (empty($this->minLength)) {
$this->invoiceTax = $value;
$this->minLength = strlen($key);
}
elseif (strpos($key, 'P') !== false && (strlen($key) < $this->minLength)) {
$this->invoiceTax = $value;
$this->minLength = strlen($key);
}
elseif (strpos($key, 'D') !== false && (strlen($key) <= $this->minLength)) {
$this->invoiceTax = $value;
$this->minLength = strlen($key);
}
But I want to know if this code can be optimised by not storing the string length of every key.
This function could easily be tidied but this is something that could be solved with recursion. This means that if 123D is in the array the code will be highly optimised and only run once, twice for 123P, three times for 123DD, etc.
function GetMostPref($records, $key = "123", $next = "D", $depth = 0)
{
if($depth == count($records))
{
// Hit end of array with nothing found
return false;
}
if(strpos($next, 'D') !== false)
{
// Currently looking at a 'D...' key.
// Next key is the same length as this key just with Ps.
$nextKey = str_repeat('P', strlen($next));
}
else if(strpos($next, 'P') !== false)
{
// Currently looking at a 'P...' key.
// Next key has one extra char and is all Ds.
$nextKey = str_repeat('D', strlen($next)+1);
}
else
{
// Key not valid
return false;
}
if(array_key_exists($key.$next, $records))
{
// Found the key in the array so return it.
return $records[$key.$next];
}
else
{
// Recursive call with the next key and increased depth.
return GetMostPref($records, $key, $nextKey, $depth + 1);
}
}
// Testing
$records = array(
'123PP' => 3.63,
'123DDD' => 9.63,
'123D' => 6.63,
'123PPPP' => 9.63,
'123DD' => 9.63,
'123P' => 2.63,
'123PPP' => 1.53
);
// Finds 123D and returns 6.63
echo GetMostPref($records);
function prepareFunctionCall(){
$records = array('123PP' => 3.63,'123DDD' => 9.63,'123PPPP' => 9.63
,'123DD' => 9.63,'123P' => 2.63,'123PPP' => 1.53);
// '123D' => 6.63,
foreach($records as $key=>$value){
$tmp = strlen($key).$key;
$arTmp[$tmp] = $value;
}
getFirstValue($arTmp);
}
function getFirstValue($pArray){
ksort($pArray);
reset($pArray);
print key($pArray).' = '.current($pArray);
}
This is an alternative for the good solution provided by MatthewMcGovern.
I provide the alternative, because this function makes use of the php functions ksort, reset and current. Those functions are created for this type of situation and if possible then would I advise you to rewrite the keys of the array before finding out which key is the first key to select. That is what I did with the addition of the strlen. But that is suboptimal compared to rewriting the keys at the moment of collecting the data. The core of the function are the calls to the functions ksort, reset and current.

How to loop through arrays of different lengths and run a function inside the loop

I'm trying to create a function that will loop through an array of various lengths. During the loop, a function is run to see if the immediately prior item (the item at current key minus 1) matches what is in the array.
Here are two examples of arrays:
$terms1 = array(
0 => 'MEL',
1 => 'Appliances',
2 => 'Clothes Dryers',
3 => 'Clothes dryers - electric'
);
$terms2 = array(
0 => 'Clothes Dryers',
1 => 'Clothes dryers - electric'
);
And here is the function to be run within the loop... this function will return a value and then I will compare that to what is in the array in the immediately prior location (current key minus 1). This pulls from a db.
getParent($terms1[3]); //Would output the value I want to compare to $terms1[2]
I've tried something like this:
$fail = null;
foreach(array_reverse($terms1, true) as $key => $value){
if($key > 0){
$priorkey = $key - 1;
if(getParent($terms1[$key]) != $terms1[$priorkey]){
$fail = true;
}
}
}
return $fail;
I think I need a recursive function... any help or nudges in the right direction would be appreciated.
$prev = null;
foreach ($terms1 as $v) {
if ($prev == getParent($v)) {
$fail = true;
break;
}
$prev = $v;
}
I don't understand why your code doesn't work, but if you add a break; after $fail = true; it will run faster and return the same result. There's no need to check the rest after the first failure.

Categories