So, this question is more about needing an explanation of why certain code works than it is about needing the code problem answered. I recently did a Codility sample test where you had to write a function in any desired language that could accept an array of varying integers and return each index in the array that was an Equilibrium Index. I wrote my answer in PHP and got a pretty horrible score so I went looking at other people's solutions and found some that worked great and got perfect scores, but they did something very specific that I couldn't figure out the mathematical logic for making that leap.
First, here is my function I wrote to answer the question:
function solution($A) {
for($i=0; $i<=count($A); $i++){
$leftTotal = 0;
$rightTotal = 0;
for($j=0; $j<count($A); $j++){
if($j < $i && isset($A[$j])){
$leftTotal += $A[$j];
} else if($j > $i && isset($A[$j])) {
$rightTotal += $A[$j];
}
}
if($leftTotal === $rightTotal){
return $i;
}
}
return -1;
}
This answer received a 95% Correctness score but only an 18% Performance score. I knew nested for loops was going to kill me on Performance, but I couldn't think of any way to get rid of it until I went and looked at some other solutions.
This is an example that I found on this Github page:
function equi($A) {
$lower_elements_sum = 0;
$higher_elements_sum = array_sum($A);
for ($i = 0, $cnt = count($A); $i < $cnt; $i++) {
if (isset($A[$i - 1])) {
$lower_elements_sum += $A[$i - 1];
}
$higher_elements_sum -= $A[$i];
if ($lower_elements_sum == $higher_elements_sum) {
return $i;
}
}
return -1;
}
So, I understand everything that this code is doing, and structurally, it's not really THAT different from what I did. What is driving me crazy is how they knew that in order to get an Equilibrium Index they could do this whole function working from the outside in and starting their $higher_elements_sum and the array_sum value of the array passed into the function.
How did they know that would work that way? I literally had to write out their function's results on pen and paper to double check because I just couldn't make the leap from having to find the left and right values each time vs what they did and just kept building off of the same value for left and right totals.
Can anyone help me understand and fill in the gaps between my answer and theirs because I'm at a complete loss for how I could have gotten from A to B?
similar but different, the solution I found scored 100 and 100
function solution($A) {
// write your code in PHP7.0
$left_sum = 0;
$right_sum = array_sum($A);
foreach($A as $k => $v) {
if($k > 0) {
$left_sum += $A[($k -1)];
}
$right_sum -= $v;
if($left_sum === $right_sum) {
return $k;
}
}
return -1;
}
Related
So I am trying to optimize a piece of php code that basically runs the same operations on two different datasets, based on user input. What would be a better and more optimized approach?
//$input = //user input
//$a = [1,2,3 .....];
//$b = [a,b,c .....];//both are same length - n
case 1 :
for($i =0; $i<n; $i++) {
if($input == 'a')
//doSomething with $a[i] - code here
else
//doSomething with $b[i] - code here
}
case 2 :
if($input == 'a') {
for($i =0; $i<n; $i++) {
//doSomething with $a[i] - code here
}
}
else {
for($i =0; $i<n; $i++) {
//doSomething with $b[i] - code here
}
}
case 3 :
if($input == 'a') {
for($i =0; $i<n; $i++) {
doSomething($a[i]);
}
}
else {
for($i =0; $i<n; $i++) {
doSomething($b[i]);
}
}
the operation is same in all cases
Better is always difficult to quantify, but if you want to do exactly the same processing on the inputs, just picking the one dataset depending on the input, you may be better off just setting an input array and just processing that...
if($input == 'a')
$dataset = $a;
else
$dataset = $b;
foreach ( $dataset as $dataItem ) {
//doSomething data code here
}
This might not really be the answer you're looking for, but honestly, I would just go for whatever conveys the actual use case the best and not try to optimize too much.
Performance-wise, unless the operation that checks the $input takes a lot of time (orders of magnitude more than a simple comparison), or if the operation on $dataset is very short (comparable to the input check), it simply won't matter.
Assuming $input does not change while you're processing your dataset, I'd go for case three. You're making it clear that it is the same operation, and the only difference between the different if branches is the dataset you're working with. If you're familiar with ternary operators, I'd even use this:
for($i = 0; $i < n; $i++) {
doSomething(($input == $a) ? $a[i] : $b[i]);
}
I'd take code readability over micro-optimizations any day, as long as you don't actually have performance issues with this piece of code.
As a bonus, have a read at this question about optimization: https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil
I have a function where i input a level, and it returns the XP:
this is gotten from runescape 1-99 formula:
function experience($L) {
$a=0;
for($x=1; $x<$L; $x++) {
$a += floor($x+300*pow(2, ($x/7)));
}
return floor($a/4);
}
this means:
level 54 would return 150872 XP.
But, how would i go the other way around, to input 150872 and make it return 54?
and, whats the way to go when xp might be 150873, but its still level 54 to return?
How would i approatch?
Wants:
experience(152439) -> 54
One inefficient but easy solution is to just continuously call the experience function in a loop, increasing the level each time, until you reach a level that returns an experience value above the one you are looking for, and then return the level before that:
function level($experience) {
$returned = 0;
$level = 0;
while ($returned <= $experience) {
$level++;
$returned = experience($level);
}
return $level - 1;
}
Demo: http://sandbox.onlinephpfunctions.com/code/820d659feb28a00dd87a21d01bd2414cbc66d300
I need all possible combinations in math sense (without duplicates) where n=30 and k=18
function subcombi($arr, $arr_size, $count)
{
$combi_arr = array();
if ($count > 1) {
for ($i = $count - 1; $i < $arr_size; $i=$i+1) {
$highest_index_elem_arr = array($i => $arr[$i]);
foreach (subcombi($arr, $i, $count - 1) as $subcombi_arr)
{
$combi_arr[] = $subcombi_arr + $highest_index_elem_arr;
}
}
} else {
for ($i = $count - 1; $i < $arr_size; $i=$i+1) {
$combi_arr[] = array($i => $arr[$i]);
}
}
return $combi_arr;
}
function combinations($arr, $count)
{
if ( !(0 <= $count && $count <= count($arr))) {
return false;
}
return $count ? subcombi($arr, count($arr), $count) : array();
}
$numeri="01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30";
$numeri_ar=explode(".",$numeri);
$numeri_ar=array_unique($numeri_ar);
for ($combx = 2; $combx < 19; $combx++)
{
$combi_arr = combinations($numeri_ar, $combx);
}
print_r($combi_arr);
It works but it terminates with an out of memory error, of course, number of combinations is too large.
Now I do not need exactly all the combinations. I need only a few of them.
I'll explain.
I need this work for a statistical study over Italian lotto.
I have the lotto archive in this format saved in $archivio array
...
35.88.86.03.54
70.72.45.18.09
55.49.35.30.43
15.52.49.41.72
74.26.54.77.90
33.14.56.42.11
08.79.41.01.52
82.33.32.83.43
...
A full archive is available here
https://pastebin.com/tut6kFXf
newer extractions are on top.
I tried (unsuccessfully) to modify the function to do this
for each 18 numbers combination found by the function combinations, the function should check if there are min. 3 numbers in one of the first 30 rows of $archivio. If "yes", the combination must not be saved in combination array, this combination has no statistical value for my need. If "no", the combination must be saved in combination array, this combination has great statistical value for my need.
In this way the total combinations will be no more than some hundred or thousand and I will avoid the out of memory and I'll have what I need.
The script time will be surely long but there should not be out of memory using the way above.
Anyone is able to help me in this ?
Thank you
I am building a little game and got stuck in developing the leveling system. I created a function that will exponentially increase the experience required for the next level. However, I am not sure how to turn it around so that I can put in the amount of experience a user has gained and get the corresponding level.
PHP function
function experience($level, $curve = 300) {
// Preset value to prevent notices
$a = 0;
// Calculate level cap
for ($x = 1; $x < $level; $x++) {
$a += floor($x+$curve*pow(2, ($x/7)));
}
// Return amount of experience
return floor($a/4);
}
The issue
I am wondering how I can reverse engineer this function in order to return the correct level for a certain amount of experience.
Using the above function, my code would output the following:
Level 1: 0
Level 2: 83
Level 3: 174
Level 4: 276
Level 5: 388
Level 6: 512
Level 7: 650
Level 8: 801
Level 9: 969
Level 10: 1154
What I am looking for is a way to invert this function so that I can input a certain amount and it will return the corresponding level.
A 1000 experience should return level 9 for example.
Plugging the values into excel and creating a trend line, I got the following equation:
y = 1.17E-09x^3 - 4.93E-06x^2 + 1.19E-02x + 6.43E-02
So your reverse engineered equation would be
function level($xp) {
$a = 1.17e-9;
$b = -4.93e-6;
$c = 0.0119;
$d = 0.0643
return round($a*pow($xp, 3) + $b*pow($xp,2) + $c * $xp + $d);
}
Results are accurate to within 1dp, but if your $curve changes, you'd need to recalculate. I also haven't extended higher than level 10.
Other options include caching the results of the lookup:
$levelXpAmounts = array()
function populateLevelArray($curve=300) {
$levelXpAmounts[$curve] = array();
for($level = $minlevel; $level <= $maxLevel; $level++) {
$levelXpAmounts[$curve][$level] = experience($level);
}
}
//at game load:
populateLevelArray()
Then, your reverse lookup would be
function level($xp, $curve=300) {
if (!array_key_exists($levelXpAmounts, curve)
populateLevelArray($curve);
for($level = $minlevel; $ level <= $maxLevel; $level++) {
if ($xp < $levelXpAmounts[$curve][$level]) {
return $level - 1;
}
}
}
That way, the iteration through all the levels is only done once for each different value of $curve. You can also replace your old experience() function with a (quite likely faster) lookup.
Note: it's been a while since I've written any php, so my syntax may be a little rusty. I apologize in advance for any errors in that regard.
You can do another function called level which uses the experience function to find the level:
function level($experience)
{
for ($level = 1; $level <= 10; $level++) {
if ($experience <= experience($level)) {
return $level;
}
}
}
function experience($level, $curve = 300)
{
$a = 0;
for ($x = 1; $x < $level; $x++) {
$a += floor($x+$curve*pow(2, ($x/7)));
}
return floor($a/4);
}
var_dump(level(1000));
You can clearly work the math here and find a reverse formula. Not sure whether it will be a nice and easy formula, so I would suggest you an alternative approach which is easy to implement.
Precalculate the results for all the levels you realistically want your person to achieve (I highly doubt that you need more than 200 levels, because based on my estimation you will need tens of billions exp points).
Store all these levels in the array: $arr = [0, 83, 174, 276, 388, 512, 650, ...];. Now your array is sorted and you need to find a position where your level should fit.
If you are looking for 400 exp points, you see that it should be inserted after 5-th position - so it is 5-th level. Even a simple loop will suffice, but you can also write a binary search.
This task could be solved in other way. This is method of partial sums.
Let's assume, you have a class , which stores an array of exponential values calculated by function:
function formula($level, $curve){ return floor($level+$curve*pow(2, ($level/7)));}
$MAX_LEVEL = 90;
function calculateCurve($curve){
$array = [];
for($i =0; $i< $MAX_LEVEL; $i++) $array.push(formula($i, $curve));
return $array;
}
Now we can calculate experience, needed for a level:
$curve = calculateCurve(300);
function getExperienceForLevel($level, $curve){
$S = 0;
for($i =0; $i < level; $i++) $S += $curve[$i];
}
And calculate level for experience:
function getLevelForExperience($exp, $curve){
for($i =0; $i < $MAX_LEVEL; $i++){
$exp -= $curve[$i];
if($exp < 0) return $i-1;
}
return $MAX_LEVEL;
}
I assume there could index problems - I didn't tested the code, but I suppose that main idea is clearly explained.
Pros:
Code cleaner, There no magic numbers and interpolation coeficients.
You can easy change your learning curve.
Possibility to improve and make calculating functions as O(1);
Cons:
There is an $curve array to store, or calculate somewhere.
Also. you could make even more advanced version of this:
function calculateCurve($curve){
$array = [];
$exp = 0;
for($i =0; $i< $MAX_LEVEL; $i++) {
$exp += formula($i, $curve);
$array.push($exp);
}
return $array;
}
Now calculating experience have O(1) complexity;
function getExperienceForLevel($level, $curve){
return $curve[min($MAX_LEVEL, $level)];
}
Perhaps not the best way, but it's working.
function level($experience, $curve = 300)
{
$minLevel = 1;
$maxLevel = 10;
for($level = $minLevel; $level <= $maxLevel; $level++)
{
if(experience($level, $curve) <= $experience && $experience < experience($level + 1, $curve))
{
return $level;
}
}
return $maxLevel;
}
function find_highest_prime_factor($n)
{
for ($i = 2; $i <= $n; $i++)
{
if (bcmod($n, $i) == 0) //its a factor
{
return max($i, find_highest_prime_factor(bcdiv($n,$i)));
}
}
if ($i == $n)
{
return $n; //it's prime if it made it through that loop
}
}
UPDATE: This is the correct answer, my bad!
Get rid of the final if statement otherwise if $i!=sqrt($n) because sqrt($n) is not an integer you have an undefined return value
function find_highest_prime_factor($n){
for ($i = 2; $i <= sqrt($n); $i++) //sqrt(n) is the upperbound
{
if (bcmod($n, $i) == 0) //its a factor
{
return max($i, find_highest_prime_factor(bcdiv($n,$i)));
}
}
return $n; //it's prime if it made it through that loop
}
Line 11 should be:
if ($i == ceil(sqrt($n)))
Starting at 2 and stepping by 1 is inefficient. At least check 2 separately and then loop from 3 stepping 2 each time. Using a 2-4 wheel would be even faster.
When you recurse your code starts again at trial factor 2. It would be better to pass a second parameter holding the factor you have reached so far. Then the recursion wouldn't have go back over old factors that have already been tested.