Help with refactoring PHP code - php

I had some troubles implementing Lawler's algorithm but thanks to SO and a bounty of 200 reputation I finally managed to write a working implementation:
Lawler's Algorithm Implementation Assistance
I feel like I'm using too many variables and loops there though so I am trying to refactor the code. It should be simpler and shorter yet remain readable.
Does it make sense to make a class for this? Any advice or even help with refactoring this piece of code is welcomed:
<?php
/*
* #name Lawler's algorithm PHP implementation
* #desc This algorithm calculates an optimal schedule of jobs to be
* processed on a single machine (in reversed order) while taking
* into consideration any precedence constraints.
* #author Richard Knop
*
*/
$jobs = array(1 => array('processingTime' => 2,
'dueDate' => 3),
2 => array('processingTime' => 3,
'dueDate' => 15),
3 => array('processingTime' => 4,
'dueDate' => 9),
4 => array('processingTime' => 3,
'dueDate' => 16),
5 => array('processingTime' => 5,
'dueDate' => 12),
6 => array('processingTime' => 7,
'dueDate' => 20),
7 => array('processingTime' => 5,
'dueDate' => 27),
8 => array('processingTime' => 6,
'dueDate' => 40),
9 => array('processingTime' => 3,
'dueDate' => 10));
// precedence constrainst, i.e job 2 must be completed before job 5 etc
$successors = array(2=>5,
7=>9);
$n = count($jobs);
$optimalSchedule = array();
for ($i = $n; $i >= 1; $i--) {
// jobs not required to precede any other job
$arr = array();
foreach ($jobs as $k => $v) {
if (false === array_key_exists($k, $successors)) {
$arr[] = $k;
}
}
// calculate total processing time
$totalProcessingTime = 0;
foreach ($jobs as $k => $v) {
if (true === array_key_exists($k, $arr)) {
$totalProcessingTime += $v['processingTime'];
}
}
// find the job that will go to the end of the optimal schedule array
$min = null;
$x = 0;
$lastKey = null;
foreach($arr as $k) {
$x = $totalProcessingTime - $jobs[$k]['dueDate'];
if (null === $min || $x < $min) {
$min = $x;
$lastKey = $k;
}
}
// add the job to the optimal schedule array
$optimalSchedule[$lastKey] = $jobs[$lastKey];
// remove job from the jobs array
unset($jobs[$lastKey]);
// remove precedence constraint from the successors array if needed
if (true === in_array($lastKey, $successors)) {
foreach ($successors as $k => $v) {
if ($lastKey === $v) {
unset($successors[$k]);
}
}
}
}
// reverse the optimal schedule array and preserve keys
$optimalSchedule = array_reverse($optimalSchedule, true);
// add tardiness to the array
$i = 0;
foreach ($optimalSchedule as $k => $v) {
$optimalSchedule[$k]['tardiness'] = 0;
$j = 0;
foreach ($optimalSchedule as $k2 => $v2) {
if ($j <= $i) {
$optimalSchedule[$k]['tardiness'] += $v2['processingTime'];
}
$j++;
}
$i++;
}
echo '<pre>';
print_r($optimalSchedule);
echo '</pre>';

I would make it a class. I find it easier to refactor an algorithm when all necessary variables are encapsulated as class members, rather than remembering what values I have to pass in and out every time I extract a method.
You should set your inputs to the algorithm in the constructor and then have a generic execute method. This would allow you to conform to both the command and strategy patterns more easily.
Make all your loop and conditional bodies into individual protected functions. With appropriate naming, that will increase the readability immensely, and make it much easier to alter the algorithm through inheritance.

Related

How to push with a regularity elements in an array php

I have an array of posts ids and an array of videos ids. I need to mix both to have something like "First is a video, then 3 normal posts then another video then 5 normal posts.. repeat... until the end. (ie : Video,normal,normal,normal,Video,normal*5 and repeat..)
My code works but how can i make it more clean and concise ?
$a1 = $video_posts_arr;
$a2 = $standard_posts_arr;
$res = [];
$tc1 = count($a1);
$tc2 = count($a2);
$tc = $tc1 + $tc2;
$i1 = 0;
$i2 = 0;
for ($i = 0; $i < $tc; $i++) {
// first video
if (isset( $a1[$i1] )) {
array_push($res,$a1[$i1]);
$i1++;
}
// next 3 normals
if (isset( $a2[$i2]) ) {
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
}
// next video
if (isset( $a1[$i1] )) {
array_push($res,$a1[$i1]);
$i1++;
}
// next 5 normals
if (isset( $a2[$i2]) ) {
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
array_push($res,$a2[$i2]);
$i2++;
}
}```
It's kind of hard to tell from your wording, but I'm assuming you're wanting to go back and forth between 3 "regular" posts, then 5, then 3, etc, between each "video" post. So as we loop through the video posts, we have to toggle back and forth between those options, and then use that plus a marker/counter to know where to insert each video post in the list of regular posts.
$a1 = array("v1", "v2", "v3", "v4", "v5");
$a2 = array("p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10", "p11", "p12", "p13", "p14", "p15", "p16");
$skipNum = 3;
$currentPos = 1;
array_unshift($a2, array_shift($a1));
foreach($a1 as $v)
{
$currentPos+= $skipNum;
array_splice($a2, $currentPos, 0, $v);
$currentPos++;
if($skipNum == 3)
{
$skipNum = 5;
}
else
{
$skipNum = 3;
}
}
var_dump($a2);
DEMO
how can i make it more clean and concise ?
Here's what I think you should do...
Iterate on the video array and inject each video element into the standard post array at the variable positions without making any replacements.
Code: (Demo)
$video_posts_arr = range('a','e');
$standard_posts_arr = range(1, 16);
$pos = 0;
foreach ($video_posts_arr as $index => $video) {
array_splice($standard_posts_arr, $index + $pos, 0, $video);
$pos += $index & 1 ? 5 : 3;
}
var_export($standard_posts_arr);
Using the $index of the videos array spares having to use incrementation while iterating.
I am using a ternary expression with a bitwise condition to increase the $pos based on whether $index is odd. I should express these variables to better explain what is happening. As shown in this Demo, these are the values generated as $index increases:
$index = 0, $pos = 0; splicePoint = 0, isOdd = false
$index = 1, $pos = 3; splicePoint = 4, isOdd = true
$index = 2, $pos = 8; splicePoint = 10, isOdd = false
$index = 3, $pos = 11; splicePoint = 14, isOdd = true
$index = 4, $pos = 16; splicePoint = 20, isOdd = false
Output:
array (
0 => 'a',
1 => 1,
2 => 2,
3 => 3,
4 => 'b',
5 => 4,
6 => 5,
7 => 6,
8 => 7,
9 => 8,
10 => 'c',
11 => 9,
12 => 10,
13 => 11,
14 => 'd',
15 => 12,
16 => 13,
17 => 14,
18 => 15,
19 => 16,
20 => 'e',
)
The above script is demonstrated using a ratio of videos to standard posts which results in no consecutive videos.
If your video to standard post ratio is too great, then the above script will simply append all remaining/extra videos to the end of the standard posts array.
If you would like to ensure that the final element in your standard posts array is not a video, then you can add a conditional break like this:
$pos = 0;
foreach ($video_posts_arr as $index => $video) {
$injection_pos = $index + $pos;
if (!isset($standard_posts_arr[$injection_pos])) {
break;
}
array_splice($standard_posts_arr, $injection_pos, 0, $video);
$pos += $index & 1 ? 5 : 3;
}
Or to allow a maximum of one video after the last standard post, move the break after the array_splice() call and account for the injected video element like this:
$pos = 0;
foreach ($video_posts_arr as $index => $video) {
$injection_pos = $index + $pos;
array_splice($standard_posts_arr, $injection_pos, 0, $video);
if (!isset($standard_posts_arr[$injection_pos + 1])) {
break;
}
$pos += $index & 1 ? 5 : 3;
}
Maybe something like that?
This solution uses array_merge and array_slice to create result. This methods does not edit original arrays, just creates new one based on arguments.
$a1 = array("v1", "v2", "v3");
$a2 = array("a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10");
$output = array_merge(
array_slice($a1, 0, 1), //take one arg from a1. Not smartest to be honest but works. Maybe someone will give better idea here.
array_slice($a2, 0, 3), //take first 3 elements from a2
array_slice($a1, 1, 1),
array_slice($a2, 3, 5),
array_slice($a2, 300, 500), //it is save :)
);
print_r($output);
# [0] => v1
# [1] => a1
# [2] => a2
# [3] => a3
# [4] => v2
# [5] => a4
# [6] => a5
# [7] => a6
# [8] => a7
# [9] => a8
# [10] => a9
# [11] => a10
array_slice that takes only one argument array_slice($a1, 0, 1) looks bit cryptic but at least it is save to use.
This'll create new array instead of editing existing one. It comes with some additional overhead (You probably should not worry about it).
http://sandbox.onlinephpfunctions.com/code/9b23381b31ab7ed48f1941cbca981071eaf250af

Futz testing arsort() dependent code?

It seems that a large, complicated codebase depends on the order arsort produces. Before I dive in to discern what's actually happening in like 50 classes -- is there a simple way to shuffle items with equal values?
In other words, if the input is
['foo' => 3, 'bar' => 3, 'baz' => 3, 'this' => 2, 'that' => 2]
I'd like to get
['baz' => 3, 'bar' => 3, 'foo' => 3, 'this' => 2, 'that' => 2]
one run maybe, then
['baz' => 3, 'bar' => 3, 'foo' => 3, 'that' => 2, 'this' => 2]
on another random run.
How about something like this? (Untested)
Worst Case complexity: O(k)
Note: Written for algorithmic clarity and not PHP details...
function shuffleInput( $data ) {
// Separate into sets.
$sets = [];
foreach ( $data as $k => $v ) {
$sets[$v][] = $k;
}
// Shuffle & Join.
$data = [];
foreach ( $sets as $v => &$set ) {
shuffle( $set );
foreach( $set as $k ) {
$data[$k] = $v;
}
}
return $data;
}
Depending on the size of your input, it might be a better idea to unset every element in $data in the first loop instead of just creating a new array. This applies if data is very large and memory is precious to you - as well as reducing any sudden spikes & dips in memory usage.
Also, if you're going to continously shuffle the same $data around you might want to separate out the making of $sets to some other place or at least allow the developer to pass/get it as a side effect.
If you do not want to deal with shuffle, but rather prefer to check all permutations of the array, then you can do something like this:
$arr = array('foo' => 3, 'bar' => 3, 'baz' => 3, 'this' => 2, 'that' => 2);
$keys = array_keys($arr);
$indexes = range(0, count($arr) - 1);
pc_permute($indexes, $perms);
var_dump($perms);
function pc_permute($items, &$ret = array(), $perms = array( )) {
if (empty($items)) {
$ret[] = $perms;
} else {
for ($i = count($items) - 1; $i >= 0; --$i) {
$newitems = $items;
$newperms = $perms;
list($foo) = array_splice($newitems, $i, 1);
array_unshift($newperms, $foo);
pc_permute($newitems, $ret, $newperms);
}
}
}
Array $perms will give all permutations of the indexes, key name by index you can get from $keys and value by key or index (use array_slice) from $arr :)
ps: but you should understand - more elements you have in the original array, more permutations you will find. if there are n elements then there will be n! permutations. for n = 5 there are 120 permutations.

Is there an elegant way to count values of an array over a given interval/step to generate histogram data?

I want to get generate histogram data for the following array. array_count_values() would be great to use, except it just counts values for an exact value match. How can I elegantly do the same, but bundle values by a given step or interval?
$dataArray = array(385,515,975,1136,2394,2436,4051,4399,4484,4768,4768,4849,4856,4954,5020,5020,5020,5020,5020,5020,5020,5020,5020,5052,5163,5200,5271,5421,5421,5442,5746,5765,5903,5992,5992,6046,6122,6205,6208,6239,6310,6360,6416,6512,6536,6543,6581,6609,6696,6699,6752,6796,6806,6855,6859,6886,6906,6911,6923,6953,7016,7072,7086,7089,7110,7232,7278,7293,7304,7309,7348,7367,7378,7380,7419,7453,7454,7492,7506,7549,7563,7721,7723,7731,7745,7750,7751,7783,7791,7813,7813,7814,7818,7833,7863,7875,7886,7887,7902,7907,7935,7942,7942,7948,7973,7995,8002,8013,8013,8015,8024,8025,8030,8038,8041,8050,8056,8060,8064,8071,8081,8082,8085,8093,8124,8139,8142,8167,8179,8204,8214,8223,8225,8247,8248,8253,8258,8264,8265,8265,8269,8277,8278,8289,8300,8312,8314,8323,8328,8334,8363,8369,8390,8397,8399,8399,8401,8436,8442,8456,8457,8471,8474,8483,8503,8511,8516,8533,8560,8571,8575,8583,8592,8593,8626,8635,8635,8644,8659,8685,8695,8695,8702,8714,8715,8717,8729,8732,8740,8743,8750,8756,8772,8772,8778,8797,8828,8840,8840,8843,8856,8865,8874,8876,8878,8885,8887,8893,8896,8905,8910,8955,8970,8971,8991,8995,9014,9016,9042,9043,9063,9069,9104,9106,9107,9116,9131,9157,9227,9359,9471);
// if array_count_values accepted a step value I could do this:
print_r(array_count_values($dataArray,1000));
// expected result:
// array(1000 => 3, 2000 => 1, ... 10000 => 15);
// ^0-1000 ^[385,515,975]
What do you recommend?
In the event I have to loop through all values manually, is there an elegant way to round all values to a given interval?
$step = 1000;
$result = array_count_values(
array_map(
function ($value) use ($step) {
return (int) ceil($value / $step) * $step;
},
$dataArray
)
);
var_dump($result);
The rounding solution seems pretty straight forward:
$step_size = 10;
$data = array(10, 20, 24, 30, 35, 50);
foreach ($data as $index => $value) {
$data[$index] = round($value / $step_size) * $step_size;
}
// array(10, 20, 20, 30, 40, 50);
You can build the output directly to avoid mapping the entire data array just to make use of array_count_values(); below is a more generic implementation that allows the mapping to be done outside of the function itself:
function array_count_values_callback(array $data, callable $fn)
{
$freq = [];
foreach ($data as $item) {
$key = $fn($item);
$freq[$key] = isset($freq[$key]) ? $freq[$key] + 1 : 1;
}
return $freq;
}
print_r(array_count_values_callback($dataArray, function($item) {
return ceil($item / 1000) * 1000;
}));
Here is a simple solution where you loop through your $dataArray,
$step_size = 1000;
$histogramArray = array();
foreach ($dataArray as $v) {
$k = (int)ceil($v / $step_size) * $step_size;
if (!array_key_exists($k, $histogramArray)) $histogramArray[$k] = 0;
$histogramArray[$k]++;
}
And the output would be,
Array
(
[1000] => 3
[2000] => 1
[3000] => 2
[5000] => 8
[6000] => 21
[7000] => 25
[8000] => 46
[9000] => 110
[10000] => 15
)

Array of points to one point in middle

I got array of points:
array(
array('x' => ...,'y' => ...),
array('x' => ...,'y' => ...)
...
)
What is the best way to make those points one, that is in "avarge" position? Is pairing and then pairing and then pairing... a good algorithm?
I would give myself -1 for this question, as it seems to be really easy, but I am working on project for more than 20 hours now, and my brain went off.
Hmm... is it as simple as counting avarge x and avarge y?
The best solution would be to turn your computer off and go to sleep for a couple of good hours. Then wake up rested and ready for a new programming session. This solution in based on an assumption that those 20 hours you have assigned this project was without any proper breaks.
While this isn't a direct answer to your question, it will certainly help you get there by yourself. Don't underestimate the power of a nap.
Take a look at this function I wrote to loop through all the elements of your multidimensional array and return their average after adding all of the elements together.
print_r(getAveragePoints(array(array('x' => 1,'y' => 3),array('x' => 2,'y' => 4))));
function getAveragePoints($arrays = array()) {
if(!empty($arrays)) {
$i=0;
$x = 0;
$y = 0;
foreach($arrays as $array) {
// this would take avg
$x += $array['x']; // x
$y += $array['y']; // y
$i++;
}
$avgX = $x / $i;
$avgY = $y / $i;
return array($avgX,$avgY);
} else {
return array(0,0);
}
}
Simple average option:
$pointArray = array(
array('x' => 1,'y' => 2),
array('x' => 2,'y' => 5),
array('x' => 3,'y' => 3) ,
array('x' => 4,'y' => 6) ,
array('x' => 4,'y' => 5) ,
);
$valueCount = count($pointArray);
$midpoint = array_map(
function($value) use($valueCount) {
return $value / $valueCount;
},
array_reduce(
$pointArray,
function($summary, $value) {
return array(
'x' => $summary['x'] += $value['x'],
'y' => $summary['y'] += $value['y']
);
},
array('x' => 0, 'y' => 0)
)
);
var_dump($midpoint);

Highest/Maximum difference between two values in an array

So, I need to find out the highest possible difference between any two random values of an indexed array and I am not sure if my answer is correct, coz you know it's way too simple for the weight of this question:
function($array)
{
asort($array);
$diff = $array(sizeof($array) - 1) - $array(0);
return $diff;
}
I am sure this is correct, then again I always have my doubts!
You are right that the largest difference you will find is between the maximum value and the minimum value. However, you could achieve this more efficiently (O(N) instead of O(N log N)) by simply scanning the array to find the min and max values without sorting.
Just to wrap your head around the logic, here is a manual way of doing this:
$myarray = array(
'this' => 2,
'that' => 14,
'them' => -5,
'other' => 200,
'nothing' => 42,
'somethingelse' => 1,
'you' => 10,
'me' => 30);
foreach ($myarray as $key => $value) {
if (!isset ($min) || $value < $min) { $min = $value; }
if (!isset ($max) || $value > $max) { $max = $value; }
}
$diff = $max - $min;
echo $diff;

Categories