I've a client selling wine bottles. He uses boxes with space for 6 bottles, 12 bottles, 18 bottles and 21 bottles. But he only wants to accept orders which fit exactly into these boxes. There must not be any empty space inside.
E.g.
33 is ok: 1x21 and 2x6
48 is ok: 2x21 and 1x6 or 4x12
26 or 35 or 61 are not ok
For my first try was an straight simple way. I produce an array containing a lot of valid numbers, remove duplicates and order them.
$numbers = [];
$end = (int) $bottles/6 + 1;
for ($i=1; $i<=$end; $i++) {
$numbers[] = $i * 6;
$numbers[] = $i * 21;
$numbers[] = $i * 21 + 6;
$numbers[] = $i * 21 + 6 + 6;
$numbers[] = $i * 21 + 6 + 6 + 6;
}
$numbers = array_unique($numbers);
sort($numbers);
It looks like this:
Array
(
[0] => 6
[1] => 12
[2] => 18
[3] => 21
[4] => 24
[5] => 27
[6] => 30
[7] => 33
[8] => 36
[9] => 39
[10] => 42
[11] => 48
[12] => 54
[13] => 60
[14] => 63
....
I can check against my list. ok, fine!
But I want to make a "perfekt" solution fitting for all possible numbers, e.g. I want to know if 123456 is possible. You see, that the array must be very huge for getting this :-)
I tried an equation with 2 unknowns. Why only 2? Because 18 and 12 can be divided by 6. So my approch was:
bottles = 6a + 21b
"a" and "b" must be integer values and may contain zero. "bottles" is an integer value, too. I transformed it to:
bottles / 6 - 3,5b = a
But this doesn't help me to make a good algorithm... I think I'm on the right way, but how can I solve this quite elegant? Where are the algebra gurus? ;-)
To expand on maraca's comment, we're trying to solve the equation x = 6a + 21b over nonnegative integers. Since 6 and 21 are divisible by 3 (the greatest common divisor of 6 and 21), it is necessary that x is divisible by 3. Moreover, if x is less than 21, then it is necessary that x is divisible by 6.
Conversely, if x is divisible by 6, we can set a = x/6 and b = 0. If x is an odd multiple of 3, then x - 21 is divisible by 6; if x is at least 21, we can set a = (x - 21)/6 and b = 1. Every multiple of 3 is either odd or even (and hence divisible by 6), so this proves maraca's equivalence claim.
I found #vivek_23's comment challenging so I figured I would give it a try.
This code will optimize the amount to the smallest number of boxes to fill the order.
It does so by first trying with 21 boxes and if the result is not %6 then it will loop backwards to if it gets a sum that is %6 and split up the rest.
// 99 is challenging since 99 can be divided on 21 to 4, but the remainder is not % 6 (15).
// However the remainder 15 + 21 = 36 which is % 6.
// Meaning the "correct" output should be 3 x 21 + 2 x 18 = 99
$order = 99;
$b = [21 => 0, 18 => 0, 12 => 0, 6 => 0];
// number of 21 boxes possible
if($order >= 21){
$b[21] = floor($order/21);
$order -= $b[21]*21;
}
// if the remainder needs to be modified to be divisible on 6
while($order % 6 != 0){
if($b[21] > 0){
// remove one box of 21 and add the bottles back to the remainder
$order += 21;
$b[21]--;
}else{
// if we run out of 21 boxes then the order is not possible.
echo "order not possible";
exit;
}
}
// split up the remainder on 18/12/6 boxes and remove empty boxes
$b = array_filter(split_up($b, $order));
var_dump($b);
function split_up($b, $order){
// number of 18 boxes possible
if($order >= 18){
$b[18] = floor($order/18);
$order -= $b[18]*18;
}
// number of 12 boxes possible
if($order >= 12){
$b[12] = floor($order/12);
$order -= $b[12]*12;
}
// number of 6 boxes possible
if($order >= 6){
$b[6] = floor($order/6);
$order -= $b[6]*6;
}
return $b;
}
https://3v4l.org/EM9EF
You can reduce this homework to some simpler logic with 3 valid cases:
Multiples of 21.
Multiples of 6.
A combination of the above.
Eg:
function divide_order($q) {
$result['total'] = $q;
// find the largest multiple of 21 whose remainder is divisible by 6
for( $i=intdiv($q,21); $i>=0; $i-- ) {
if( ($q - $i * 21) % 6 == 0 ) {
$result += [
'q_21' => $i,
'q_6' => ( $q - $i * 21 ) / 6
];
break;
}
}
if( count($result) == 1 ) {
$result['err'] = true;
}
return $result;
}
var_dump(
array_map('divide_order', [99, 123456])
);
Output:
array(2) {
[0]=>
array(3) {
["total"]=>
int(99)
["q_21"]=>
int(3)
["q_6"]=>
int(6)
}
[1]=>
array(3) {
["total"]=>
int(123456)
["q_21"]=>
int(5878)
["q_6"]=>
int(3)
}
}
Then you can apply some simple logic to reduce multiple boxes of 6 into boxes of 12 or 18.
function winePacking(int $bottles): bool {
return ($bottles % 6 == 0 || ($bottles % 21) % 3 == 0);
}
https://3v4l.org/bTQHe
Logic Behind the code:
You're working with simple numbers, 6,12,18 can all be covered by mod 6, being that 6 goes into all 3 of those numbers. 21 we can just check a mod 21, and if it's somewhere in between then it's mod 21 mod 6.
Simple as that with those numbers.
What if you do something like so:
function boxes($number) {
if($number >= 21) {
$boxesOfTwentyOne = intval($number / 21);
$remainderOfTwetyOne = floor($number % 21);
if($remainderOfTwetyOne === 0.0) {
return $boxesOfTwentyOne . ' box(es) of 21';
}
$boxesOfTwentyOne = $boxesOfTwentyOne - 1;
$number >= 42 ? $textTwentyOne = $boxesOfTwentyOne . ' boxes of 21, ' : $textTwentyOne = '1 box of 21, ';
$sixesBoxes = floor($number % 21) + 21;
switch (true) {
case ($sixesBoxes == 24):
if($number >= 42) {
return $textTwentyOne . '1 box of 18 and 1 box of 6';
}
return '1 box of 18 and 1 box of 6';
break;
case ($sixesBoxes == 27):
return $boxesOfTwentyOne + 1 . ' box(es) of 21 and 1 box of 6';
break;
case ($sixesBoxes == 30):
if($number >= 42) {
return $textTwentyOne . '1 box of 18 and 1 box of 12';
}
return '1 box of 18 and 1 box of 12';
break;
case ($sixesBoxes == 33):
return $boxesOfTwentyOne + 1 . ' box(es) of 21 and 1 box of 12';
break;
case ($sixesBoxes == 36):
if($number >= 42) {
return $textTwentyOne . '2 boxes of 18';
}
return '2 boxes of 18';
break;
case ($sixesBoxes == 39):
return $boxesOfTwentyOne + 1 . ' box(es) of 21 and 1 box of 18';
break;
default:
return 'Not possible!';
break;
}
} else {
switch (true) {
case ($number == 6):
return '1 box of 6';
break;
case ($number == 12):
return '1 box of 12';
break;
case ($number == 18):
return '1 box of 18';
break;
default:
return 'Not possible!';
break;
}
}
}
EDIT: I have updated my answer, and now I think it is working properly. At least, it passed in all the tests I've made here.
This is actually array items summing up to a target where repetition is allowed problem.
Since in many cases multiple box configurations will come up, you may chose to use the shortest boxes sub list or may be the boxes sublist with the available boxes at hand in real life.
Sorry my PHP is rusty... the below algorithm is in JS but you may simply adapt it to PHP. Of course you may freely change your box sizes to accomodate any number of bottles. So for the given boxes and target 87 we get in total 20 different solutions like
[12,18,18,18,21], [12,12,21,21,21] ... [6,6,6,6,6,6,6,6,6,6,6,21]
function items2T([n,...ns],t){cnt++ //remove cnt in production code
var c = ~~(t/n);
return ns.length ? Array(c+1).fill()
.reduce((r,_,i) => r.concat(items2T(ns, t-n*i).map(s => Array(i).fill(n).concat(s))),[])
: t % n ? []
: [Array(c).fill(n)];
};
var cnt = 0, result;
console.time("combos");
result = items2T([6,12,18,21], 87)
console.timeEnd("combos");
console.log(result);
console.log(`${result.length} many unique ways to sum up to 87
and ${cnt} recursive calls are performed`);
The code is taken from a previous answer of mine.
Related
I have below array & code
$a = [
149 => 55,
130 => 10,
131 => 5,
132 => 5,
133 => 10,
134 => 10,
135 => 5
];
$rand = rand (0,(count($a)-1));
echo array_values($a)[$rand];
This will give majorly result as 5,10 instead of 55.
Total of value is 100% probability. Values can be in decimal as well like 55.55, 10.10, etc. but overall going to be 100%
I already followed https://www.geeksforgeeks.org/how-to-get-random-value-out-of-an-array-in-php/
But this is not giving perfect result as expected.
So which has highest probability should be selected majorly and randomly.
So result can be like this : 55, 55, 10, 10, 10, 55, 5, etc..
I found some useful link Generating random results by weight in PHP? where Probability = Weight
Right now, your array is this: -
55, 10, 5, 5, 10, 10, 5
Now, you should generate a random number between [0, 100), let's call it r.
Now, if r lies between [0, 55), select the value 55.
else if r lies between [55, 55 + 10 = 65), select the value 10.
else if r lies between [65, 65 + 5 = 70), select the value 5.
else if r lies between [70, 70 + 5 = 75), select the value 5.
else if r lies between [75, 75 + 10 = 85), select the value 10.
else if r lies between [85, 85 + 10 = 95), select the value 10.
else if r lies between [95, 95 + 5 = 100), select the value 5.
I am sure you would have got the idea...
So, for the general case, if you have an array named 'arr', this is the pseudocode: -
function SELECTPROB()
{
$r = generateRandomNumber(0, 100); //function to generate random number between 0 and 100, (100 exclusive)
$sum = 0;
foreach($arr as $i)
{
if($r >= $sum && $r < $sum + $i)
{
return $i
}
$sum = $sum + $i
}
return -1 //Should technically never reach upto this, but it can if your probability's sum is not 100
}
Here is an implementation similar to roulette wheel selection in GA.
a version of the answer by EReload but bounded to the sum rather than a 100.
$a = [
149 => 55,
130 => 10,
131 => 5,
132 => 5,
133 => 10,
134 => 10,
135 => 5
];
echo randSelect($a);
function randSelect($a) {
$values = array_values($a);
$sum = array_sum($values);
$rand = (rand(0,1000)/1000) * $sum;
$partialSum = 0;
for ($i=0; $i < count($values); $i++) {
$partialSum += $values[$i];
if($partialSum >= $rand){
return $values[$i];
// incase you are using something like array_count_values and are actually looking for the keys
// return array_keys($a)[$i];
}
}
}
As i understand, you want higher number appear more frequently in rand method no matter how many times smaller number appear in your array. You need unique your array first.
Random by sum weight is simple method for random, but you can control weight more freely by sum power instead of itself.
$a = [
149 => 55,
130 => 10,
131 => 5,
132 => 5,
133 => 10,
134 => 10,
135 => 5
];
$val_arr = array_unique(array_values($a));
function rand_by_sum($arr, $power=1){
$sum = 0;
$f_val = function($f)use($power){
return pow($f, $power);
};
foreach($arr as $f){
$sum += $f_val($f);
}
$rand = mt_rand(0, $sum);
$tmp_sum = 0;
foreach($arr as $f){
$tmp_sum += $f_val($f);
if($tmp_sum >= $rand) return $f;
}
}
for($i=0; $i< 10; $i++){
echo rand_by_sum($val_arr, $argv[1]) . " ";
}
echo "\n";
And here some test result with different pow
php test.php 0.5
55 5 10 55 5 55 55 5 55 55
php test.php 2
55 55 10 55 55 55 55 55 55 55
php test.php 1
55 10 55 55 55 55 55 55 55 10
To get value, you revert array as 55 => [149] then get result from random, and random again in values of reverted array
Looking for answer which works in all scenarios or for any number.
and
Values can be in decimal as well like 55.55, 10.10, etc. but overall going to be 100%
Although you are limiting the total weight to 100, the fact that you want to accommodate decimal values in that range means that you cannot assume a maximum of 100 units to pick from. If you have a granularity of tenths, then each unit to potentially pick from will be .1 . If specifying down to hundredths (like 55.55) then you will need a relative base unit of .01 at a time.
Because I'd prefer not to iterate by float values, I recommend that you scale up all of your values by a factor that eliminates all floats in the weight and the random number generator -- simply multiply by 10/100/1000 whatever you need to convert all of the weights into integers.
Now to make the shortest work of the iteration process:
Loop through your input array once to establish the longest decimal precision.
Pick a random integer between 0 and ((the sum of all weights minus 1) multiplied by 10 to the power of "the longest representing decimal length").
Loop through your input array again and simply check if the random integer is less than the current weight plus any previous weight(s); if not if so, break the loop and because the selected weighted, random number has been located.
Code: (Demo) -- the demo makes 10 iterations to aid in revealing the weighted effect
$valueWeights = [
149 => 55.555,
130 => 10.0050,
131 => 5,
132 => 5.2,
133 => 10,
134 => 10.24,
135 => 5
];
$mostDecimals = 0;
// not bothering to validate against infinite and extremely fringe case floats
foreach ($valueWeights as $value => $weight) {
$tempDecimals = 0;
while ((string)$weight !== (string)floor($weight)) {
$weight *= 10; // this is not permanently mutating the weight
++$tempDecimals;
}
$mostDecimals = max($mostDecimals, $tempDecimals);
}
echo "Most Decimals: {$mostDecimals}\n";
$factor = pow(10, $mostDecimals);
echo "Factor: " , $factor , "\n";
$totalWeight = (array_sum($valueWeights) - 1) * $factor;
for ($i = 0; $i < 10; ++$i) {
$rand = mt_rand(0, $totalWeight);
echo "\nRand: " , $rand , "\n";
$cumulativeScaledWeight = 0;
foreach ($valueWeights as $value => $weight) {
$cumulativeScaledWeight += $weight * $factor;
if ($rand < $cumulativeScaledWeight) {
echo "Value: {$value}\n";
break;
}
}
}
Output:
Most Decimals: 3
Factor: 1000
Rand: 52197
Value: 149
Rand: 33785
Value: 149
Rand: 4783
Value: 149
Rand: 24994
Value: 149
Rand: 76588
Value: 133
Rand: 77417
Value: 133
Rand: 40541
Value: 149
Rand: 80009
Value: 133
Rand: 14826
Value: 149
Rand: 52691
Value: 149
I think you could actually shuffle the array and pop an element, shuffle again and pop the element, that would be randomly and those numbers with greater probability would be first.
What you can do is to create another array with 100 numbers, representing the total probability and inserting in it the amount of numbers equal to its value, finally you shuffle it to pick an index random later. Then you will get an array of 100 numbers where there most repeated number is the most probable. Lastly you just have to pick a random index and create your array.
can you tell me if you are looking for something like this or if I am misunderstanding the problem
function getProb($array, $elements)
{
$myNewArray = [];
$myProbabilisticArray = $this->getProbabilisticArray($array);
for ($i=0; $i < $elements; $i++) {
$myNewArray[] = $myProbabilisticArray[array_rand($myProbabilisticArray)];
}
return $myNewArray;
}
function getProbabilisticArray($array) {
$myNewArray = [];
rsort($array);
$currentProbability = 0;
$accumulatedProbability = $array[0];
$currentPosition = 0;
while ($currentProbability < 100) {
if ($currentProbability > $accumulatedProbability) {
$currentPosition++;
$accumulatedProbability += $array[$currentPosition];
}
array_push($myNewArray, $array[$currentPosition]);
$currentProbability++;
}
shuffle($myNewArray);
return $myNewArray;
}
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
I have a n*n spiral matrix.
if N = 4
then matrix :
7 8 9 10
6 1 2 11
5 4 3 12
16 15 14 13
if N = 3
7 8 9
6 1 2
5 4 3
I want to get the diagonal values of this spiral matrix.
In the n=4 case diagonal values would be 7,1,3,13,10,2,4,16
I can do this by storing this matrix in array and traversing for each diagonal value.
Is there any better way to get these values.
To get the numbers on the main diagonal, we can notice that the values are
1 = 1
1 + 2 = 3
1 + 2 + 4 = 7
1 + 2 + 4 + 6 = 13
So the general formula is 1 + (sum i = 0 to k of 2*i) for k = 0, 1, 2, ...
Simplifying this, we get k^2 + k + 1 for k = 0, 1, 2, ...
In PHP, we can generate these via something like this:
function mainDiagonal($n) {
$values = array();
for ($k = 0; $k < $n; $k++) {
$values[] = $k*$k + $k + 1;
}
return $values;
}
To get the numbers on the antidiagonal for even N we see:
2 = 2
2 + 2 = 4
2 + 2 + 6 = 10
2 + 2 + 6 + 6 = 16
If we continue this pattern for larger matrices we see the general formula is
sum i = 0 to k of floor(i/2)*4 + 2 for k = 0, 1, 2, ...
Similarly for odd N we find the formula is
1 + (sum i = 0 to k of ceil(i/2)*4) for k = 0, 1, 2, ...
In PHP, we can generate these via something like this:
function antiDiagonal($n) {
$values = array();
if ($n % 2 == 0) {
for ($k = 0; $k < $n; $k++) {
$accum = 0;
for ($j = 0; $j <= $k; $j++) {
$accum += floor($j/2)*4 + 2;
}
$values[] = $accum;
}
} else {
for ($k = 0; $k < $n; $k++) {
$accum = 1;
for ($j = 0; $j <= $k; $j++) {
$accum += ceil($j/2)*4;
}
$values[] = $accum;
}
}
return $values;
}
Notice that the maximum value of k is one less than the dimension of the matrix.
Combining these functions, we obtain:
array_unique(array_merge(mainDiagonal($n), antiDiagonal($n)))
The problem can be divided into 4 parts: Find the numbers along the diagonal spoke in each quadrant. There are four quadrants, so we have four spokes:
Northwest (NW) spoke
Northeast (NE) spoke
Southwest (SW) spoke
Southeast (SE) spoke
For example, in your illustration of Ulam spiral, when N is even.
NW spoke have 1, 7, ...
NE spoke have 2, 10, ...
SW spoke have 4, 16, ...
SE spoke have 3, 13, ...
The problem is further subdivided into two cases:
N is even.
N is odd.
Case 1: N is even
Here are the formulas for each spoke:
NW spoke: f(n) = 4*n*n + 2*n + 1
NE spoke: g(n) = 4*n*n + 4n + 2
SW spoke: h(n) = 4*n*n + 8*n + 4
SE spoke: i(n) = 4*n*n + 6*n + 3
where n = 0, 1, 2, ...
For 4x4 matrix, compute the following set:
{f(0), f(1), g(0), g(1), h(0), h(1), i(0), i(1)}
It yields the diagonal values:
{1, 7, 2, 10, 4, 16, 3, 13}
In general, for an NxN matrix, when N is even, compute the following set to get the diagonal values:
{ f(0), ..., f(N/2 - 1),
g(0), ..., g(N/2 - 1),
h(0), ..., h(N/2 - 1),
i(0), ..., i(N/2 - 1) }
Case 2: N is odd
In your illustration of Ulam spiral, when N is odd, the formulas for each spoke are:
NW spoke: f(n) = 4*n*n + 2*n + 1
NE spoke: g(n) = 4*n*n + 4*n + 1
SW spoke: h(n) = 4*n*n + 1
SE spoke: i(n) = 4*n*n - 2*n + 1
where n = 0, 1, 2, ...
Note that f(0) = g(0) = h(0) = i(0) = 1.
For 3x3, compute the following set:
{f(0), f(1), g(1), h(1), i(1)}
It yields the following diagonal values:
{1, 7, 9, 5, 3}.
In general, for an NxN matrix, when N is odd, compute the following set to get the diagonal values:
{ f(0), ..., f((N - 1)/2,
g(0), ..., g((N - 1)/2),
h(0), ..., h((N - 1)/2),
i(0), ..., i((N - 1)/2) }
PHP Code
Finally, here is a PHP program that demonstrates what I have discussed above.
<?php
function ulam_diag($N)
{
$result = array();
if ($N % 2 == 0) {
for ($n = 0; $n < $N / 2; $n++) {
$result[] = 4*$n*$n + 2*$n + 1;
$result[] = 4*$n*$n + 4*$n + 2;
$result[] = 4*$n*$n + 8*$n + 4;
$result[] = 4*$n*$n + 6*$n + 3;
}
} else {
$result[] = 1;
for ($n = 1; $n <= ($N - 1) / 2; $n++) {
$result[] = 4*$n*$n + 2*$n + 1;
$result[] = 4*$n*$n + 4*$n + 1;
$result[] = 4*$n*$n + 1;
$result[] = 4*$n*$n - 2*$n + 1;
}
}
sort($result);
return $result;
}
print_r(ulam_diag(4));
print_r(ulam_diag(3));
?>
Output:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 7
[5] => 10
[6] => 13
[7] => 16
)
Array
(
[0] => 1
[1] => 3
[2] => 5
[3] => 7
[4] => 9
)
Here is the code one Ideone: http://ideone.com/F9jaC0
In case you are wondering how I arrived at the formulas, there are well established results for the four spokes of Ulam spiral. Here are the references:
https://oeis.org/A054569 (NW spoke in your illustration)
https://oeis.org/A016754 (NE spoke in your illustration)
https://oeis.org/A053755 (SW spoke in your illustration)
https://oeis.org/A054554 (SE spoke in your illustration)
The Ulam spirals in your illustrations are oriented differently from the popular representation of Ulam spirals, so I took these well known results and adjusted the offset of n for each formula, so that it works with your Ulam spiral. These adjustments are left as exercises to the reader. ;-)
Well for each row in a matrix we have two diagonal values. To obtain these two values i used two position(x1,y1) and (x2,y2) for main and anti diagonals.
Well I wrote this code:
<?php
function getSpiralDiagonal($spiralArr,$N){
$diagonalValueCount = $N*2;
$xIndexMainDiagonal = 0;
$yIndexMainDiagonal = 0;
$xIndexAntiDiagonal = 0;
$yIndexAntiDiagonal = $N-1;
while($diagonalValueCount > 0){
//checking for same position
if($yIndexMainDiagonal == $yIndexAntiDiagonal){
echo $spiralArr[$xIndexMainDiagonal][$yIndexMainDiagonal].'<br>';
}else{
echo $spiralArr[$xIndexMainDiagonal][$yIndexMainDiagonal].'<br>';
echo $spiralArr[$xIndexAntiDiagonal][$yIndexAntiDiagonal].'<br>';
}
$xIndexMainDiagonal++;
$yIndexMainDiagonal++;
$xIndexAntiDiagonal++;
$yIndexAntiDiagonal--;
$diagonalValueCount -= 2;
}
}
$spiralArr = array(array('7','8','9'),array('6','1','2'),array('5','4','3'));
getSpiralDiagonal($spiralArr,3);
?>
What is the logic behind google's pagination behaviour?
My paginator goes something like this:
[1] 2 3 ... 184 >
< 1 [2] 3 4 ... 184 >
< 1 2 [3] 4 5 ... 184 >
< 1 2 3 [4] 5 6 ... 184 >
< 1 ... 3 4 [5] 6 7 ... 184 >
< 1 ... 4 5 [6] 7 8 ... 184 >
< 1 ... 5 6 [7] 8 9 ... 184 >
< 1 ... 6 7 [8] 9 10 ... 184 >
Here is a live version of the above example: http://www.dev.thomaskile.me/?page=test-zone&module=Paginator.
I know why this is happening; I've set the amount of page numbers to be shown on each side of current page to two (2).
I would rather have the range of numbers to be equal like this:
[1] 2 3 4 5 6 7 8 ... 184 >
< 1 [2] 3 4 5 6 7 ... 184 >
< 1 2 [3] 4 5 6 7 ... 184 >
< 1 2 3 [4] 5 6 7 ... 184 >
< 1 ... 3 4 [5] 6 7 ... 184 >
< 1 ... 4 5 [6] 7 8 ... 184 >
< 1 ... 5 6 [7] 8 9 ... 184 >
< 1 ... 6 7 [8] 9 10 ... 184 >
It's at the beginning and the end I need to make some changes, but can't figure out how to make it an easy operation...
I would like to make it flexible as well. Meaning I would like to be able to change the number of wanted pages on each side, and have the script expand and calculate it all...
Here is my code so far:
/**
* page controller buttons
* #param str $this->querySting href="URL string"
* #param str $this->pageIdentifier $_GET['this-name']
* #param int $this->numPages Total amount of pages
* #param int $this->midRange Number of pages to show on each side of current page
*/
public function prevPage()
{
if ($this->currentPage > 1){
$prevPage = ($this->currentPage - 1);
return 'prev';
}
}
public function nextPage()
{
if ($this->currentPage < $this->numPages) {
$nextPage = $this->currentPage + 1;
return 'next';
}
}
public function firstPage()
{
if ($this->currentPage > ($this->midRange + 1)) { // if number of pages between "currentPage" and "firstPage" exceeds $midRange with 1...
$firstPage .= '1'; // ...show "first page"-link
if ($this->currentPage > ($this->midRange + 2)) { // if number of pages between $currentPage and "first page" exceeds $midRange with more than 1
$firstPage .= '…'; // add "..." between "1st page"-link and first page in $range
}
}
return $firstPage;
}
public function lastPage()
{
if ($this->currentPage < ($this->numPages - $this->midRange)) { // if number of pages between "currentPage" and "last page" is equal to $midRange
if (($this->currentPage < ($this->numPages - $this->midRange) - 1)) { // if number of pages between $currentPage and "last page" exceeds $range with more than two
$lastPage .= '…'; // add "..." between "last page"-link and last page in $range
}
$lastPage .= ''.$this->numPages.''; // show "last page"-link
}
return $lastPage;
}
# Range of pages between (prev first ...) and (... last next)
public function listPages()
{
for ($i = ($this->currentPage - $this->midRange); $i < (($this->currentPage + $this->midRange) + 1); $i++){
if (($i > 0) && ($i <= $this->numPages)) // if page number are within page range
{
if ($i == $this->currentPage) { $listPages .= '<a class="current">'.$i.'</a>'; } // if we're on current page
else { $listPages .= ''.$i.''; } // if not current page
}
}
return $listPages;
}
This is what I do for my Pagination.
$startPage = $currentPage - 4;
$endPage = $currentPage + 4;
if ($startPage <= 0) {
$endPage -= ($startPage - 1);
$startPage = 1;
}
if ($endPage > $totalPage)
$endPage = $totalPage;
if ($startPage > 1) echo " First ... ";
for($i=$startPage; $i<=$endPage; $i++) echo " {$i} ";
if ($endPage < $totalPage) echo " ... Last ";
I believe my code is self-explained, but I will try to explain it in plain English. First of all, you need to know two things before you can generate Pagination: $totalPage and $currentPage.
Step 1: Assuming that the current page is in mid-range. $startPage and $endPage store range of page that pagination try to generate.
Step 2: If $startPage is negative, then you need to make-up for $endPage.
Step 3: If $endPage excess $totalPage, then $endPage is the last page.
Step 4: Generating Pagination into HTML. (it is up to you how you want your pagination to look. I will simply use plain text to represent my pagination)
if ($startPage > 1) echo " First ... ";
for($i=$startPage; $i<=$endPage; $i++) echo " {$i} ";
if ($endPage < $totalPage) echo " ... Last ";
Fixed flaw to my previous logic
$startPage = ($curPage < 5)? 1 : $curPage - 4;
$endPage = 8 + $startPage;
$endPage = ($totalPage < $endPage) ? $totalPage : $endPage;
$diff = $startPage - $endPage + 8;
$startPage -= ($startPage - $diff > 0) ? $diff : 0;
if ($startPage > 1) echo " First ... ";
for($i=$startPage; $i<=$endPage; $i++) echo " {$i} ";
if ($endPage < $totalPage) echo " ... Last ";
This conversation was a great start for me! But I wanted a paginator closer to the intentions of the original question, that:
1) Could be contained in a function with variables to alter the total pages, current page, and number of pages on each side of the current to show.
2) Maintains a constant width, similar to the original post:
< [1] 2 3 4 5 6 7 ... 99 >
< 1 [2] 3 4 5 6 7 ... 99 >
< 1 2 [3] 4 5 6 7 ... 99 >
< 1 2 3 [4] 5 6 7 ... 99 >
< 1 2 3 4 [5] 6 7 ... 99 >
< 1 ... 4 5 [6] 7 8 ... 99 >
< 1 ... 5 6 [7] 8 9 ... 99 >
< 1 ... 92 93 [94] 95 96 ... 99 >
< 1 ... 93 94 [95] 96 97 98 99 >
< 1 ... 93 94 95 [96] 97 98 99 >
< 1 ... 93 94 95 96 [97] 98 99 >
< 1 ... 93 94 95 96 97 [98] 99 >
< 1 ... 93 94 95 96 97 98 [99] >
3) Continues to display the number "2" rather than "..." in cases where you would have 1 ... 3
4) Same thing for the end.
So here's what I did. I am coding in a different language (coffeescript), but it should function as good sudo-code anyway:
get_pages_array = (total_page, each_side, curr_page) ->
if total_page <= (2*each_side)+5
# in this case, too few pages, so display them all
start_page = 1
end_page = total_page
else if curr_page<=each_side+3
# in this case, curr_page is too close to the beginning
start_page = 1
end_page = (2*each_side)+3
else if curr_page >= total_page - (each_side+2)
# in this case, curr_page is too close to the end
start_page = total_page - (2*each_side) - 2
end_page = total_page
else
# regular case
start_page = curr_page - each_side
end_page = curr_page + each_side
return_me = []
if start_page> 1
return_me.push "1"
if start_page>2
return_me.push "..."
for x in [start_page..end_page]
return_me.push x
if end_page<total_page-1
return_me.push "..."
if end_page<total_page
return_me.push total_page
return return_me
I am using this code for each_side = 2, so that's where I'm sure it works.
EDIT: fixed logic as per #Vextil
Here's a Python program that shows how to do this correctly:
def main():
num_pages = 13
page = 12
window = 5
start = page - window
end = page + window - 1
if start <= 0:
end = end - start + 1
start = 1
if end > num_pages:
end = num_pages
start = max(end - (window * 2) + 1, 1)
for no in range(start, end + 1):
print "{}*".format(no) if page == no else no
if __name__ == '__main__':
main()
This is pure awesome! I think I got this paginator to work the way I described.
Please, have a look and try it out here http://dev.thomaskile.me/?page=test-zone&module=Paginator and let me know...
After a lot of logical math studying I finally came to this conclusion:
In order to make this act so differently on different levels, there have to be some if, elsef-s to handle the logic on each level seperatly. I'll try to explain, but find it hard to do in a good way...
These are the levels I'm talking about:
If currentPage == firstPage :
Calculate how many pages to show after currentPage starting from 2nd page.
This calculation needed to be done based on how many page boxes there would be at the most. (midRange value is a key factor here)
[1] 2 3 4 5 6 7 8 ... 184 >
elseif currentPage is in between firstPage and midRange value maxed out.
Reduce pages in range by one to prevent moving the whole paginator to the right once prevPage is added.
Calculate pages to show before and after currentPage to keep the amount of pages equal trough the whole thing.
< 1 [2] 3 4 5 6 7 ... 184 >
< 1 2 [3] 4 5 6 7 ... 184 >
< 1 2 3 [4] 5 6 7 ... 184 >
elseif midRange value is maxed out on each side. Meaning we're in the middle somewhere.
midRange pages + the current page + midRange pages. Quite straight forward i guess...
< 1 ... 3 4 [5] 6 7 ... 184 >
...
...
...
< 1 ... 178 179 [180] 181 182 ... 184 >
elseif currentPage is in between midRange value and lastPage
Almost the same as in the beginning. Difference was to calculate a static pagenumber to start pages from, then calculate pages to show before/after current page...
(this, by the way, has been my headache this weekend)
< 1 ... 178 179 180 [181] 182 183 184 >
< 1 ... 178 179 180 181 [182] 183 184 >
< 1 ... 178 179 180 181 182 [183] 184 >
elseif currentPage == numPages (number of tatal pages).
Pretty much same as firstPage operation... calculating how many pages needed to fill the whole thing up and calculate where to start from...
What I need to do now is to make the code itself better...
< 1 ... 178 179 180 181 182 183 [184] >
The "problem" in my case was that the whole paginator should calculate everything based on the midRange value and nothing else.
For me to execute this paginator in any of my future project, all I have to do is:
$paginator = new paginator((int)); // e.g. number of total results from a db request
I might in most cases need to add a personal querystring to make sure the a href is working:
$paginator->set_queryString('my querystring');
And that's pretty much all. I've set up a couple of optional functions like this:
$paginator->set_resultsPerPage((int));
$paginator->set_midRange((int));
$paginator->set_pageIdentifier('querystring-pageNumber-identifier-name-for-get'); // whatever I needed
Finally i display the paginator page controller like this:
$paginator->pageController('full'); // full, med, min for different styles.
If non of these are good enough, i could just call each button like this:
$paginator->prevPage();
$paginator->firstPage();
$paginator->listPages();
$paginator->lastPage();
$paginator->nextPage();
$paginator->pageJumper();
$paginator->perPageSelector();
I assume your pagination have this structure:
number_of_active_page + separate(...) + page(184) + next_page(>)
You can set number_of_active_page become 8
( include prev_page(<) + pages ( ... and page number )
[1] 2 3 4 5 6 7 8 ... 184 >
[number_of_active_page(set to 8)] + separate + page + next_page
< 1 ... 3 4 [5] 6 7 ... 184 >
import math
size = 3
len = 13
for page in range(1,10):
if(( size*(page-1) ) >len):
startPoint = (size*(page-1)) - (size*(page- math.ceil(len/size)))
else:
startPoint = ( size*(page-1) )
if((startPoint +size)>len):
endPoint = len
else:
endPoint = (startPoint +size -1)
print("Page = "+str(page))
print("start = " +str(startPoint))
print("end = " +str(endPoint))
print()
Hear is a simple example of pagination display:
$paginationDisplay = ""; // Initialize the pagination output variable
// This code runs only if the last page variable is not equal to 1,
// if it is only 1 page we require no paginated links to display
if ($lastPage != "1"){
// This shows the user what page they are on, and the total number of pages
$paginationDisplay .= 'Page <strong>' . $pn .
'</strong> of ' . $lastPage. 'last';
// If we are not on page 1 we can place the Back button
if ($pn != 1) {
$previous = $pn - 1;
$paginationDisplay .= ' <a href="' .
$_SERVER['PHP_SELF'] . '?pn=' . $previous . '"> Back</a> ';
}
// Lay in the clickable numbers display here between the Back and Next links
$paginationDisplay .= '<span>' . $centerPages . '</span>';
// If we are not on the very last page we can place the Next button
if ($pn != $lastPage) {
$nextPage = $pn + 1;
$paginationDisplay .= ' <a href="' .
$_SERVER['PHP_SELF'] . '?pn=' . $nextPage . '"> Next</a> ';
}
}
This is the pagination logic I have
$pLinks = 5; // Links per page
$pMids = 3;
$pTot = 10; // Total page
$pSel = 1 // Selected page
if (($pSel <= $pMids) || ($pTot <= $pLinks)) {
$sPage = 1;
$ePage = ($pTot <= $pLinks) ? $pTot : $pLinks;
} else {
$etPage = $pSel + ($pMids - 1);
$ePage = ($etPage <= $pTot) ? $etPage : $pTot;
$sPage = $ePage - ($pLinks - 1);
}
if ($pSel > $sPage) {
$sL = 'First';
$sN = '«';
} else {
$sL = 'First';
$sN = '«';
}
if ($pSel < $ePage) {
$eL = 'End';
$eN = '»';
} else {
$eL = 'End';
$eN = '»';
}
$pOptions = '';
$pOptions .= '<span class="iPage">'.$pSel.'/'.$pTot.'</span>';
$pOptions .= '<span class="renderFL">'.$sL.'</span>';
$pOptions .= '<span class="renderPN">'.$sN.'</span>';
for ($i = $sPage; $i <= $ePage; $i++) {
if($i != $pSel) {
$pOptions .= '<span>'.$i.'</span>';
} else {
$pOptions .= '<span class="selected">'.$i.'</span>';
}
}
$pOptions .= '<span class="renderPN">'.$eN.'</span>';
$pOptions .= '<span class="renderFL">'.$eL.'</span>';
The result would be look like this:
1 -> [1] 2 3 4 5
2 -> 1 [2] 3 4 5
3 -> 1 2 [3] 4 5
..
5 -> 3 4 [5] 6 7
6 -> 4 5 [6] 7 8
..
8 -> 6 7 [8] 9 10
9 -> 6 7 8 [9] 10
10 -> 6 7 8 9 [10]
.
//Prev
.
for($number = 1; $number <= $num_pages; $number++)
{
if($page == $number)
{
$navigator .= "<b>[$number]</b> ";
}
else
{
$navigator .= "<a href='?c=".$_SESSION['cID']".&rows=".$per_page."&page=$number'>$number</a> ";
}
}
.
//Next
.
This is the snippet that prints number of pages.
Sample output:
Previous 1 2 3 4 [5] 6 7 8 9 10 Next
5 is the current page.
Problem: page numbers are shown in sequence with no restrictions. If i have 100 pages, all numbers show up.
Question: I need my paging numbers appear as the following...
Assume we only have 7 ($num_pages) pages:
Previous 1 2 [3] 4 5 6 7 Next
Assume we have 90 pages:
[1] 2 3 4 5 6 7 ... 90 Next
Assume user clicked the 7th page:
Previous 1 ... 5 6 [7] 8 9 10 11 ... 90 Next
Assume user clicked 11th page:
Previous 1 ... 9 10 [11] 12 13 14 15 ... 90 Next
Assume user clicked 15th page:
Previous 1 ... 13 14 [15] 16 17 18 19 ... 90 Next
Assume user clicked 90th page:
Previous 1 ... 84 85 86 87 88 89 [90]
Any help will be appreciated.
$radius = 3;
for($i = 1; $i <= $total; $i++){
if(($i >= 1 && $i <= $radius) || ($i > $current - $radius && $i < $current + $radius) || ($i <= $total && $i > $total - $radius)){
if($i == $current) echo "<b>".$i."</b>";
}
elseif($i == $current - $radius || $i == $current + $radius) {
echo "... ";
}
}
This should be more than enough to get you started at least
$count = 7; // number to show
// start at half threshold down from the current location.
$number = $current - round($count/2);
if( $number > 1 ) echo '...';
else $ // increase to have number start at 1.
for( $number; $number < $number + $count; $number++)
{
// your for loop as normal
}
if( $number < $total ) echo '...';
An elegant solution for this kind of thing is to use "logarithmic page navigation". See my answer to this question (PHP code included):
How to do page navigation for many, many pages? Logarithmic page navigation