Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
For example I got this integer 110
Let Nx be an integer
Is it possible to generate N1 to N8 (N1 to N8 is an integer) using PHP that confine the following rules:
N1 + N2 + N3 + N4 + N5 + N6 + N7 + N8 = 110 ?
Got no idea what function (e.g. rand(), mt_rand(), using loops etc.) can PHP do that.
the shortest solution for your problem that i can think of would be:
$m = 110; // desired sum
$v = 8; // desired elements
$x = []; // result
for($i = 1; $i < $v; $i++)
$x[$i] = rand(1, $m - array_sum($x) - (($v - $i) * $v));
$x[0] = $m-array_sum($x);
// print results
print_r($x);
printf("Elements sum to: %d\n",array_sum($x));
which results in
Array
(
[1] => 16
[2] => 13
[3] => 15
[4] => 23
[5] => 5
[6] => 7
[7] => 1
[0] => 30
)
Elements sum to: 110
so hurray, just what you wanted, although it could be optimized for nicer spread. but let me explain line for line.
first i just initialized three variables. $m is the desired sum you want to have for all of your elements. $v is the number of elements (N's) you want to generate. and $x holds an array with all N's that where calculated, so the result we are interested in:
$m = 110; // desired sum
$v = 8; // desired elements
$x = []; // result
now we start a loop through the elements we want to calculate. notice we start with 1, because we will be using 0 for calculating last value later on:
for($i = 1; $i < $v; $i++)
and here now comes the "brainwork" we are setting the current element $x[$i] to a random value. in PHP we can pass min and max values to the random function, so i chose 1 as min to avoid 0's and the max i chose i shall explain a bit more detailed at the end, but obviously we want to avoid having 110 as first value and the rest zeros and this is how we do so:
$x[$i] = rand(1, $m - array_sum($x) - (($v - $i) * $v));
in the last step now we need to make sure that we will end up with the desired sum, so instead of going random, i simply set the element to the desired maximum sum minus the sum of all existing elements:
$x[0]=$m-array_sum($x);
the last rows of the code just print out the results, but as promised, let me explain the "complicated" max part of the random function that i chose.
$m - array_sum($x) - (($v - $i) * $v)
the one PHP function that comes really handy here is the array_sum function, which adds up all elements of an array. if we would just call rand(1,110) on every element, then we would could easily end up with a sum higher than 110, so we subtract the sum of all existing elements from the max value to avoid this on first hand: $m - array_sum($x). so if last three generated elements add up to 43 then next element can be no higher than 67 (110 - 43). we still could end up with a result like (12,7,24,5,62,0,0,0) and we don't wan't zeros, so we will have to subtract the number of elements left $v - $i which will avoid the zeros.
again we still could end up with a result like (12,7,24,5,59,1,1,1) and that does not look nice, so last optimization i did was to multiply this by an arbitrary number to leave more space for higher values - in this case i just used the $v (the number of elements) as arbitrary number, but you can play around with this value i.e. (($v - $i) * 12)) until you get desired result.
Thanks a lot! Almost the same as what I wanted.
$m = 110; // desired sum
$v = 8; // desired elements
$x = []; // result
for($i = 1; $i < $v; $i++)
$x[$i] = mt_rand(1, $m - array_sum($x) - (($v - $i) * $v));
$x[] = $m-array_sum($x);
// print results
print_r($x);
printf("Elements sum to: %d\n",array_sum($x));
I've changed rand() into mt_rand() which might rand things faster
And $x[0] = $m-array_sum($x); => $x[] = $m-array_sum($x);
So that the Array can start from 1 and ends at 8
Array
(
[1] => 16
[2] => 13
[3] => 15
[4] => 23
[5] => 5
[6] => 7
[7] => 1
[8] => 30
)
Elements sum to: 110
Related
How can I generate a sequence of numbers like
1 2 4 8 11 12 14 18 ...
(plus 10 every 4 numbers) with the following additional requirements:
using only one loop
output should stop when a value in the sequence is greater than a specified input
Examples
$input = 24;
1 2 4 8 11 12 14 18 21 22 24
$input = 20;
1 2 4 8 11 12 14 18
Here's what I tried so far:
<?php
// sample user input
$input = 20;
$number = 1;
$counter = 0;
$array = array();
//conditions
while ($counter < 4) {
$counter++;
array_push($array, $number);
$number += $number;
}
//outputs
for ($x = 0; $x < count($array); $x++) {
echo $array[$x];
echo " ";
}
Code: (Demo)
function arrayBuilder($max,$num=1){
$array=[];
for($i=0; ($val=($num<<$i%4)+10*floor($i/4))<=$max; ++$i){
$array[]=$val;
}
return $array;
}
echo implode(',',arrayBuilder(28)),"\n"; // 1,2,4,8,11,12,14,18,21,22,24,28
echo implode(',',arrayBuilder(28,2)),"\n"; // 2,4,8,16,12,14,18,26,22,24,28
echo implode(',',arrayBuilder(20)),"\n"; // 1,2,4,8,11,12,14,18
echo implode(',',arrayBuilder(24)),"\n"; // 1,2,4,8,11,12,14,18,21,22,24
This method is very similar to localheinz's answer, but uses a technique introduced to me by beetlejuice which is faster and php version safe. I only read localheinz's answer just before posting; this is a matter of nearly identical intellectual convergence. I am merely satisfying the brief with the best methods that I can think of.
How/Why does this work without a lookup array or if statements?
When you call arrayBuilder(), you must send a $max value (representing the highest possible value in the returned array) and optionally, you can nominate $num (the first number in the returned array) otherwise the default value is 1.
Inside arrayBuilder(), $array is declared as an empty array. This is important if the user's input value(s) do not permit a single iteration in the for loop. This line of code is essential for good coding practices to ensure that under no circumstances should a Notice/Warning/Error occur.
A for loop is the most complex loop in php (so says the manual), and its three expressions are the perfect way to package the techniques that I use.
The first expression $i=0; is something that php developers see all of the time. It is a one-time declaration of $i equalling 0 which only occurs before the first iteration.
The second expression is the only tricky/magical aspect of my entire code block. This expression is called before every iteration. I'll try to break it down: (parentheses are vital to this expression to avoid unintended results due to operator precedence
( open parenthesis to contain leftside of comparison operator
$val= declare $val for use inside loop on each iteration
($num<<$i%4) because of precedence this is the same as $num<<($i%4) meaning: "find the remainder of $i divided by 4 then use the bitwise "shift left" operator to "multiply $num by 2 for every "remainder". This is a very fast way of achieving the 4-number pattern of [don't double],[double once],[double twice],[double three times] to create: 1,2,4,8, 2,4,8,16, and so on. bitwise operators are always more efficient than arithmetic operators.The use of the arithmetic operator modulo ensure that the intended core number pattern repeats every four iterations.
+ add (not concatenation in case there is any confusion)
10*floor($i/4) round down $i divided by 4 then multiply by 10 so that the first four iterations get a bonus of 0, the next four get 10, the next four get 20, and so on.
) closing parenthesis to contain leftside of comparison operator
<=$max allow iteration until the $max value is exceeded.
++$i is pre-incrementing $i at the end of every iteration.
Complex solution using while loop:
$input = 33;
$result = [1]; // result array
$k = 0; // coeficient
$n = 1;
while ($n < $input) {
$size = count($result); // current array size
if ($size < 4) { // filling 1st 4 values (i.e. 1, 2, 4, 8)
$n += $n;
$result[] = $n;
}
if ($size % 4 == 0) { // determining each 4-values sequence
$multiplier = 10 * ++$k;
}
if ($size >= 4) {
$n = $multiplier + $result[$size - (4 * $k)];
if ($n >= $input) {
break;
}
$result[] = $n;
}
}
print_r($result);
The output:
Array
(
[0] => 1
[1] => 2
[2] => 4
[3] => 8
[4] => 11
[5] => 12
[6] => 14
[7] => 18
[8] => 21
[9] => 22
[10] => 24
[11] => 28
[12] => 31
[13] => 32
)
On closer inspection, each value in the sequence of values you desire can be calculated by adding the corresponding values of two sequences.
Sequence A
0 0 0 0 10 10 10 10 20 20 20 20
Sequence B
1 2 4 8 1 2 4 8 1 2 4 8
Total
1 2 4 8 11 12 14 18 21 22 24 28
Solution
Prerequisite
The index of the sequences start with 0. Alternatively, they could start with 1, but then we would have to deduct 1, so to keep things simple, we start with 0.
Sequence A
$a = 10 * floor($n / 4);
The function floor() accepts a numeric value, and will cut off the fraction.
Also see https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
Sequence B
$b = 2 ** ($n % 4);
The operator ** combines a base with the exponent and calculates the result of raising base to the power of exponent.
In PHP versions prior to PHP 5.6 you will have to resort to using pow(), see http://php.net/manual/en/function.pow.php.
The operator % combines two values and calculates the remainder of dividing the former by the latter.
Total
$value = $a + $b;
Putting it together
$input = 20;
// sequence a
$a = function ($n) {
return 10 * floor($n / 4);
};
// sequence b
$b = function ($n) {
return 2 ** ($n % 4);
};
// collect values in an array
$values = [];
// use a for loop, stop looping when value is greater than input
for ($n = 0; $input >= $value = $a($n) + $b($n) ; ++$n) {
$values[] = $value;
}
echo implode(' ', $values);
For reference, see:
http://php.net/manual/en/control-structures.for.php
http://php.net/manual/en/function.floor.php
http://php.net/manual/en/language.operators.arithmetic.php
http://php.net/manual/en/function.implode.php
For an example, see:
https://3v4l.org/pp9Ci
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
Given a non-empty array, return true if there is a place to split the array so that the sum of the numbers on one side is equal to the sum of the numbers on the other side.
Break the problem up into smaller problems.
The array we will be using is:
$array = [1, 2, 3, 4, 5];
First thing first:
Understand the problem
In order to compare the left side with the right side you need to split the array until the sums of both sides are equal, or until there are no more items to sum.
Visually that would look something like this:
• ••••
•• •••
••• ••
•••• •
Problem: Can the array be split into two parts?
It is always a good idea to find out if what you are trying to do is even possible, or if doing it even makes sense.
With this problem we need at least 2 items in the array in order to split it into two parts. So, if the array has fewer than 2 items then we can safely return false and call it a day:
if (count($array) < 2) {
return false;
}
Problem: How to go through the array
The array needs to be split after each item in the array. So, we need to go through the array item by item.
| • | • | • | • | • |
0 1 2 3 4 5
We only need to split the array at 1, 2, 3 and 4. In other words, we should start after the first item and stop before the last item:
$length = count($array);
for ($i = 1; $i < $length; $i++) {
echo "Split after {$i}\n";
}
Problem: How to get the left/right side from the array
Getting the left and right side is a simple matter of extracting them from the array.
$left = array_slice($array, 0, $i);
$right = array_slice($array, $i);
If you put that into the loop and output it you will get something like this:
1 | 2 3 4 5
1 2 | 3 4 5
1 2 3 | 4 5
1 2 3 4 | 5
Problem: Sum and compare
PHP has a array_sum() function that sums values in arrays:
if (array_sum($left) === array_sum($right)) {
return true;
}
One solution
function my_func($array) {
if (count($array) < 2) {
return false;
}
$length = count($array);
for ($i = 1; $i < $length; $i++) {
$left = array_slice($array, 0, $i);
$right = array_slice($array, $i);
if (array_sum($left) === array_sum($right)) {
return true;
}
}
return false;
}
var_dump(my_func([1, 2, 3, 4, 5])); // false
var_dump(my_func([7, 1, 2, 3, 1])); // true: 7 = (1 + 2 + 3 + 1)
(Assuming our input array contains numbers.)
Start by adding all the elements of the array to each other (total sum). Then iterate through the array and add each element to each other as we go (cumulative sum). If during our iteration we discover that our cumulative sum is equal to half the total sum, we know that the other members of the array will also add up to the half sum, and hence the array can be split.
<?php
function can_split(array $numbers) {
$total_sum = array_sum($numbers);
$half_sum = $total_sum / 2;
$cumulative_sum = 0;
foreach($numbers as $num) {
$cumulative_sum += $num;
if($cumulative_sum == $half_sum)
return true;
}
}
var_dump(can_split(array(1))); // null
var_dump(can_split(array(1,2,3))); // true
var_dump(can_split(array(10,1,9))); // true
var_dump(can_split(array(3,5,9))); // null
var_dump(can_split(array(1.5,2,3,3,3.5))); // true
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 3
[6] => 1
)
I was wondering how one would go about working out the average percentage difference between the current value in an array and the next value. If the next value were a larger one, it would perform like so. (ie keys [0]-[1] 1/2 * 100 = 50). If it were a smaller value it would perform like so. (ie keys [4]-[5] = 3/5 * 100 = -60).
The following will represent what I am aiming to do with these percentage calculations.
1/2 * 100
2/3 * 100
3/4 * 100
4/5 * 100
3/5 * 100 (negative)
1/3 * 100 (negative)
Total : total/count
This will iterate through the list and then work out the average from the count. I have looked into splitting arrays but don't see how else I could do this.
$count = count($num);
foreach ($num as $value) {
array_chunk($num, 1);
if($value<$value){
$total1 = $total1 + ($value/$value)*100;
}
if($value>$value){
$total2 = $total2 + ($value/$value)*100;
}
}
$average = (($total1-$total2)/$count);
print($average);
I understand the above code is incorrect, but I hope it reveals where I am getting at with this.
Any help would be greatly appreciated.
You don't want to use foreach as you'll always be needing two array elements. Note that this snippet does not protect you from 0 values. These will make your script fail.
$num = array(1, 2, 3, 4, 5, 3, 1);
$total = 0;
// The number of percent changes is one less than
// the size of your array.
$count = count($num) - 1;
// Array indexes start at 0
for ($i = 0; $i < $count; $i++) {
// The current number is $num[$i], and the
// next is $num[$i + 1]; knowing that it's
// pretty easy to compare them.
if ($num[$i] < $num[$i + 1]) {
$total += (100 * $num[$i] / $num[$i + 1]);
}
else {
$total += (-100 * $num[$i + 1] / $num[$i]);
};
};
echo ($total / $count);
I need a simple function to do some math for my inputs to generate max number of fixed values ..
i know its all about the math not the function itself but i have no idea how to create this kind of math .. Below i wrote a simple logic for the math
lets say we have an object that will cost fixed numbers of 4 elements like this
<?php
$a = 140 ;
$b = 180 ;
$c= 85 ;
$d = 110 ;
?>
and 4 inputs like this :
<?php
$w = 14000 ;
$x = 1000 ;
$y = 800 ;
$z = 1100 ;
?>
if we compare the cost of the object to the inputs we will notice that for $a vs $w we may create 10 objects ..
but for $b vs $x it can't create more than 5 objects .
what i need to do is to distribute or separate the numbers in the 4 inputs between each others to get the max result of the first 4 values .
actually i don't know if there is some pre-defined function within PHP to do this or should i create some math manually .
and if not i will need any ideas to create this kind of math
Updates
i got this formula from stockoverflow math site but have no idea how to apply it in PHP ..
Something like this work for ya?
$costs = array(140,180,85,110);
$avaliable = array(14000,1000,800,1100);
$i = 0;
while($i < count($costs)):
$max[] = floor($avaliable[$i]/$costs[$i]);
$i++;
endwhile;
Output: Array ( [0] => 100 [1] => 5 [2] => 9 [3] => 10 )
I have below a function (from a previous question that went unanswered) that creates an array with n amount of values. The sum of the array is equal to $max.
function randomDistinctPartition($n, $max) {
$partition= array();
for ($i = 1; $i < $n; $i++) {
$maxSingleNumber = $max - $n;
$partition[] = $number = rand(1, $maxSingleNumber);
$max -= $number;
}
$partition[] = $max;
return $partition;
}
For example: If I set $n = 4 and $max = 30. Then I should get the following.
array(5, 7, 10, 8);
However, this function does not take into account duplicates and 0s. What I would like - and have been trying to accomplish - is to generate an array with unique numbers that add up to my predetermined variable $max. No Duplicate numbers and No 0 and/or negative integers.
Ok, this problem actually revolves around linear sequences. With a minimum value of 1 consider the sequence:
f(n) = 1 + 2 + ... + n - 1 + n
The sum of such a sequence is equal to:
f(n) = n * (n + 1) / 2
so for n = 4, as an example, the sum is 10. That means if you're selecting 4 different numbers the minimum total with no zeroes and no negatives is 10. Now go in reverse: if you have a total of 10 and 4 numbers then there is only one combination of (1,2,3,4).
So first you need to check if your total is at least as high as this lower bound. If it is less there is no combination. If it is equal, there is precisely one combination. If it is higher it gets more complicated.
Now imagine your constraints are a total of 12 with 4 numbers. We've established that f(4) = 10. But what if the first (lowest) number is 2?
2 + 3 + 4 + 5 = 14
So the first number can't be higher than 1. You know your first number. Now you generate a sequence of 3 numbers with a total of 11 (being 12 - 1).
1 + 2 + 3 = 6
2 + 3 + 4 = 9
3 + 4 + 5 = 12
The second number has to be 2 because it can't be one. It can't be 3 because the minimum sum of three numbers starting with 3 is 12 and we have to add to 11.
Now we find two numbers that add up to 9 (12 - 1 - 2) with 3 being the lowest possible.
3 + 4 = 7
4 + 5 = 9
The third number can be 3 or 4. With the third number found the last is fixed. The two possible combinations are:
1, 2, 3, 6
1, 2, 4, 5
You can turn this into a general algorithm. Consider this recursive implementation:
$all = all_sequences(14, 4);
echo "\nAll sequences:\n\n";
foreach ($all as $arr) {
echo implode(', ', $arr) . "\n";
}
function all_sequences($total, $num, $start = 1) {
if ($num == 1) {
return array($total);
}
$max = lowest_maximum($start, $num);
$limit = (int)(($total - $max) / $num) + $start;
$ret = array();
if ($num == 2) {
for ($i = $start; $i <= $limit; $i++) {
$ret[] = array($i, $total - $i);
}
} else {
for ($i = $start; $i <= $limit; $i++) {
$sub = all_sequences($total - $i, $num - 1, $i + 1);
foreach ($sub as $arr) {
array_unshift($arr, $i);
$ret[] = $arr;
}
}
}
return $ret;
}
function lowest_maximum($start, $num) {
return sum_linear($num) + ($start - 1) * $num;
}
function sum_linear($num) {
return ($num + 1) * $num / 2;
}
Output:
All sequences:
1, 2, 3, 8
1, 2, 4, 7
1, 2, 5, 6
1, 3, 4, 6
2, 3, 4, 5
One implementation of this would be to get all the sequences and select one at random. This has the advantage of equally weighting all possible combinations, which may or may not be useful or necessary to what you're doing.
That will become unwieldy with large totals or large numbers of elements, in which case the above algorithm can be modified to return a random element in the range from $start to $limit instead of every value.
I would use 'area under triangle' formula... like cletus(!?)
Im really gonna have to start paying more attention to things...
Anyway, i think this solution is pretty elegant now, it applies the desired minimum spacing between all elements, evenly, scales the gaps (distribution) evenly to maintain the original sum and does the job non-recursively (except for the sort):
Given an array a() of random numbers of length n
Generate a sort index s()
and work on the sorted intervals a(s(0))-a(s(1)), a(s(1))-a(s(2)) etc
increase each interval by the
desired minimum separation size eg 1
(this necessarily warps their
'randomness')
decrease each interval by a factor
calculated to restore the series sum
to what it is without the added
spacing.
If we add 1 to each of a series we increase the series sum by 1 * len
1 added to each of series intervals increases sum by:
len*(len+1)/2 //( ?pascal's triangle )
Draft code:
$series($length); //the input sequence
$seriesum=sum($series); //its sum
$minsepa=1; //minimum separation
$sorti=sort_index_of($series) //sorted index - php haz function?
$sepsum=$minsepa*($length*($length+1))/2;
//sum of extra separation
$unsepfactor100=($seriesum*100)/($seriesum+sepsum);
//scale factor for original separation to maintain size
//(*100~ for integer arithmetic)
$px=series($sorti(0)); //for loop needs the value of prev serie
for($x=1 ; $x < length; $x++)
{ $tx=$series($sorti($x)); //val of serie to
$series($sorti($x))= ($minsepa*$x) //adjust relative to prev
+ $px
+ (($tx-$px)*$unsepfactor100)/100;
$px=$tx; //store for next iteration
}
all intervals are reduced by a
constant (non-random-warping-factor)
separation can be set to values other
than one
implementantions need to be carefuly
tweaked (i usualy test&'calibrate')
to accomodate rounding errors.
Probably scale everything up by ~15
then back down after. Intervals should survive if done right.
After sort index is generated, shuffle the order of indexes to duplicate values to avoid runs in the sequence of collided series.
( or just shuffle final output if order never mattered )
Shuffle indexes of dupes:
for($x=1; $x<$len; $x++)
{ if ($series($srt($x))==$series($srt($x-1)))
{ if( random(0,1) )
{ $sw= $srt($x);
$srt($x)= $srt($x-1);
$srt($x-1)= $sw;
} } }
A kind of minimal disturbance can be done to a 'random sequence' by just parting dupes by the minimum required, rather than moving them more than minimum -some 'random' amount that was sought by the question.
The code here separates every element by the min separation, whether duplicate or not, that should be kindof evenhanded, but overdone maybe. The code could be modified to only separate the dupes by looking through the series(sorti(n0:n1..len)) for them and calculating sepsum as +=minsep*(len-n) for each dupe. Then the adjustment loop just has to test again for dupe before applying adjustment.