PHP: Split an odd number into three groups - php

I'm counting the number of elements in a section and it's an even number like 34, I would like to split them into three logical groups, like:
Group 1: 1 to 12 (12 items)
Group 2: 13-24 (12 items)
Group 3: 25-34 (10 items)
I want the PHP code to logically create three groups of items where the first two sets can hold equal number of items and the rest can go into the last set. There won't be more than three sets.
$whole_elements=33;
$group1 = ($whole_elements) / 3;
This kind of code would not work as it would return a value with decimal point.

You should divide your number by 3, round the result to obtain the first 2 numbers, then use a modulo to get the last one:
<?php
$whole_elements=34;
$group1 = $group2 = round($whole_elements / 3);
$group3 = $whole_elements % $group1;
?>
Then obtaining the "groups" is a matter of slicing the original array:
<?php
$group1_array = array_slice($original_array, 0, $group1);
$group2_array = array_slice($original_array, $group1, $group1);
$group3_array = array_slice($original_array, -$group3);
?>
In fact, using this method, you don't need to determine $group2 in the first bit of code as it's the same as $group1

$whole_elements = 34;
$third = ceil($whole_elements / 3);
$group1 = $group2 = $third;
$group3 = $whole_elements - 2*$third;
echo 'one: ' . $group1 . '<br/>two: ' . $group2 . '<br/>three: ' . $group3;
one: 12
two: 12
three: 10

Related

Php count list of digits [duplicate]

This question already has answers here:
Get the sum of all digits in a numeric string
(13 answers)
Closed 5 years ago.
I am getting o/p like "11111" and I want to sum all these digits that should become 5. But if I use count count it is showing one only i.e, 1.Rather it should show 5.
Below is my code,
$count = count($inventory['product_id']);
$product_total = $count;
echo $product_total;//o/p => 1.
I need echo $product_total;//o/p => 5.
You can use the following using str_split to get an array with all characters (in your case digits) and using array_sum to get the sum of all the digits:
$digits = "11112";
$arrDigits = str_split($digits);
echo array_sum($arrDigits); //6 (1 + 1 + 1 + 1 + 2)
Demo: https://ideone.com/tZwi9J
Count is used for counting array elements.
What you can do in PHP, is to iterate over a string using either a foreach (not 100% sure) or for loop for this and accessing the elements like array elements by their index:
$str = '111111123545';
$sum = 0;
for ($i = 0; $i < strlen($str); $i++) {
$sum += intval($str[$i]);
}
print $sum; // prints 26
Alternativly, you can split the string using no delimiter and using the array_sum() function on it:
$str = '111111123545';
$sum = array_sum(str_split($str));
print $sum; // prints 26
array_sum(str_split($number));
Another possible way to count the list of digits in PHP is:
// match only digits, returns counts
echo preg_match_all( "/[0-9]/", $str, $match );
// sum of digits
echo array_sum($match[0]);
Example:
$ php -r '$str="s12345abas"; echo "Count :".preg_match_all( "/[0-9]/", $str, $match ).PHP_EOL; echo "Sum :".array_sum($match[0]).PHP_EOL;'
Count :5
Sum :15

Extract one number from another number in PHP [duplicate]

This question already has answers here:
What is the best way to validate a credit card in PHP?
(8 answers)
Closed 7 years ago.
so tonight i was trying to create a function that will check if a credit card is valid or not.
However i'm stuck here.
In my calcul, i get number such as 10, 56, 30... number with 2 numbers.
(I mean, 1 is a number with 1 number just like 2, 3, 4 ,5 6, 7 , 8 ,9.. number with two numbers would be 10 ans higher.)
What I need to do is :
Get the first number and add it to a new variable, and do the same thing with another variable.
Example:
I have this number -> 23
I need to :
$var1 = 2;
$var2 = 3;
I wanted to use the function subtr, but it looks like it doesn't works with numbers ..
Thanks for reading !!
I hope you get something from this. Casting the number into a string first and then split the number using substr() after that cast the splitted value to integer again:
$num = 23;
$str_num = (string)$num;
$var1 = (int)substr($str_num, 0, 1);
$var2 = (int)substr($str_num, 1, 1);
Or using a pure numbers:
$num = 23;
$var2 = $num % 10;
$var1 = ($num - $var2) / 10;
Credit card numbers can be validated using an algorithm called the Luhn Algorithm.
If you need this in a project, don't reinvent the wheel. Check out this project on github.
Here's a way to do this using purely numbers (without ever casting to strings). This'll also work on any length of numbers assigning it to $var1, $var2, ... , $varn for n length number.
$num = 23;
$count = 1;
while ($num > 0) {
$var = "var".$count++;
$$var = $num % 10;
$num = intval($num / 10);
}
a numeric solution
$num = 23;
$var1 = floor($num / 10);
$var2 = $num % 10;
echo "$var1 $var2";

Using PHP to solve an addition and multiplication formula

Right now I feel like my head is going to explode trying to figure this out as I'm a beginner in PHP. I'm using wordpress and in my post I'm looping a series of custom fields which will output a number.
<?php echo get_post_meta ($card_id, 'card-cost', true); ?>
This will then output the numbers from the custom fields so for example lets say the post has 8 of these fields the numbers will show
2
2
3
4
2
5
5
3
What I need is to grab all the same numbers and add them together and set them to a variable
so for 2s we have 3 set so I I would assume something like this
$total2 = 2+2+2
$total3 = 3+3
$total4 = 4
$total5 = 5
The first thing I'm not sure about is how to execute the loop inside php so instead of printing the above numbers we can store them in php to than add them together.
Once we have them how do we have php pick out just the 2's to add together than just the 3s.
try this
<?php
$arr = array(2,4,2,5,8,9,8,9,8,8,8,9,4);
$aa = array_count_values($arr);
foreach($aa as $k=>$ar)
{
echo ($k * $ar) . '</br>';
}
?>
$myNumbers = [2, 2, 3, 4, 2, 5, 5, 3];
$results = [];
foreach ($myNumbers as $number){
$results[$number] = $results[$number] + $number;
}
// $results[2] will be the 2s, etc
print_r($results);

Awkward criteria when generating random sequence

What I need to do to generate a sequence of non-repeating integers within a given range that meets the specific criteria that I have?
Here are the criteria:
Use only the numbers between 1 and MAX (let's say 9).
Numbers cannot repeat within the sequence except:
2a. Two of the first 5 numbers from the sequence must be repeated.
2b. These two numbers must be repeated at random points within the last 5 places in the final sequence (the last 5 includes the repeats).
For example:
SET: 1,2,3,4,5,6,7,8,9
Random Sequence (with repeats):
2,4,6,9,3,1,5,2,8,7,3
r, , , ,r, , ,x, , ,x
Here I have indicated the numbers that were randomly selected to be repeated (out of the first 5 in the random sequence) with an r and the insertion points where they were randomly placed (into the last 5 of the final sequence) with an x.
Any help in figuring this out is much appreciated. Actual use will be a bit more complicated than this, but I know what I will need to do once I can get this far.
Edit
To clarify a little more, I have 1-20, and I need a 22 digit random sequence. Every number must be used, two will be used twice as discussed in my original post. I chose 10 above to simplify a little. I should be able to adapt the logic you've all given.
I assume when you say "non-repeating" you mean "distinct" (unique) as opposed to "eventually becomes periodic" (as in "the digits of pi do not repeat")
Generate n distinct integers in your range.
Pick two from the first 5. Call these a and b.
Remove the last 3 from the list.
Insert a at position 0, 1, 2, or 3 in the sublist.
Insert b at position 0, 1, 2, 3, or 4 in the sublist.
Add the sublist back to the end of the list.
Removal of the sublist is not necessary but makes it easier to conceptualize.
Not obvious what to do if n+2 is less than 10. In particular, this algorithm may crash for n < 5 and return the wrong result for n=7.
If I understand you correctly, you have 1 to N random numbers that must be used in a 10-set permutation with some specific criteria about repeats. In php, I suggest this (not counting php-internals) O(n) solution:
//Generate a full list of keys
$source = range(1, MAX);
//NOTE: if MAX < 10, you must pad the array
//Get a random group of 10 of the keys
$input = array_rand(array_flip($source), 10);
//Shuffle (can be done later as well; this is the randomization).
//array_rand() does not change order.
shuffle($input);
//Select the first of 5 that must be repeated in the last 5
$one = rand(0, 4);
$onev = $input[$one];
//Remove this array key to prevent collisions with the second of 5
$input = array_diff($input, array($onev));
//Select a random index in the last 5 to be replaced with $one
$rep = rand(5, 9);
$repv = $input[$rep];
//Remove this array key to prevent collisions with the other to-be-replaced
$input = array_diff($input, array($repv));
//Acquire the new keys list of input now that two elements have been removed
$keys = array_slice(array_keys($input), 0, 3);
//Select the second-of-5 to replace in the last 5. No worry of collision now.
$two = array_rand($keys, 1);
$two = $keys[$two];
//Select the second from the last-of-5 to be replaced by $two
//No worry of collision because the other index is removed.
$keys = array_slice(array_keys($input), 4, 8);
$rept = array_rand($keys, 1);
$rept = $keys[$rept];
//Replace one of the last-of-five with one of the first-of-five
$input[$rept] = $input[$two];
//Restore removed keys as well as perform replacement of other last-of-five
$input[$one] = $onev;
$input[$rep] = $onev;
//re-randomize based on shuffle
ksort($input);
No loops, no conditionals.
A word of warning on this solution. I wouldn't use it for a large set of numbers. If I were doing this same solution for a much larger set, I would use array_splice to drop chosen members from the array. As you get a much larger space, finding an unused number in your range becomes quite expensive, and demands a better solution than the brute force method below.
This will build half of your target set. You will call it twice, once for each half.
function build_half($min, $max, $num_elements, $arr = array() ){
while( count($arr) <= $num_elements)
{
$candidate = rand($min, $max);
if( !in_array($candidate, $arr))
{
array_push($arr, $candidate);
}
}
return $arr;
}
This will grab $this_many elements from the array.
function random_grab($arr, $this_many){ // don't try this on the subway
$nums_to_repeat = array();
// catch some edge cases...
if( $this_many > count($arr) )
{
return FALSE;
}
else if( $this_many == count($arr) )
{
return shuffle($arr);
}
while( count($nums_to_repeat) <= $this_many)
{
$rand_key = rand(0, count($arr) - 1);
if( ! in_array($arr[$rand_key], $nums_to_repeat))
{
array_push($nums_to_repeat, $arr[$rand_key]);
}
}
return $nums_to_repeat;
}
This is a fairly specialized case, but could be made more general by allowing the offset floor and ceiling to be passed in as parameters. For your problem they would be 5 and 9, so we just derive them directly.
function random_insert_2nd_half($target, $source){
$offsets_consumed = array();
$num_elements = count($target);
while( count($source) > 0 )
{
$offset = rand( ($num_elements/2), $num_elements - 1);
if( ! in_array( $offset, $offsets_consumed)
{
$arr[$offset] = array_pop($nums_to_repeat);
}
}
}
Ok so after having done all that, let's put it to work.
// Generate the first half of the array
$my_array = $repeated_nums = array();
$my_array = build_half(1, 10, 5);
// then grab the 2 random numbers from that first half.
$repeated_nums = random_grab($my_array, 2);
// So now we have our random numbers and can build the 2nd half of the array.
// we'll just repeat the call to the first function.
$my_array = build_half(1, 10, 5, $my_array);
// Then swap out two of the values in the second half.
$my_array = random_insert_2nd_half($my_array, $repeated_nums);
// at this point $my_array should match what you are looking for.
Hope this gets you on your way:
$max = 20; // max value
$repeats = 2; // numbers to be repeated
$nums = range(1, $max);
shuffle($nums);
$halfPoint = ceil($max / 2);
$firstHalf = array_slice($nums, 0, $halfPoint);
$repeaters = array_intersect_key($firstHalf, array_flip(array_rand($firstHalf, $repeats)));
$secondHalf = array_merge(array_slice($nums, $halfPoint), $repeaters);
shuffle($secondHalf);
$result = array_merge($firstHalf, $secondHalf);
var_dump(join(',', $result));
To generate distinct numbers within a range you can use something like this:
$arr_num = array();
while(count($arr_num)<=7)
{
$num = rand(1, 9);
if (!in_array($num, $arr_num))
{
$arr_num[] = $num;
}
}
$arr_num now has 8 distinct elements. Pick five elements of the array:
for ($i=0; $i<=4; $i+=1)
{
$new_arr[$i] = $arr_num[$i];
}
Now pick two numbers from $new_arr numbers:
$r1 = array_rand($new_arr);
$r2 = array_rand($new_arr);
Now you can insert these numbers into the original array at two of the last random positions. Hope it helped!
$max = 15;
$array = array(1, $max);
for($x = 1; $x <= $max; $x++)
{ $array[$x] = rand(1, $max); }
$firstDup = $array[rand(1,5)];
$secondDup = $firstDup;
do { $firstDup = $array[rand(1,5)];
} while($firstDup == $secondDup);
do { $array[rand($max-5,$max)] = $firstDup;
} while(!in_array($firstDup,array_slice($array,$max-5,5)));
do { $array[rand($max-5,$max)] = $secondDup;
} while(!in_array($secondDup,array_slice($array,$max-5,5)));

Replace duplicate values in array with new randomly generated values

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.

Categories