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
Related
Using a predefined array of numbers, how can I use PHP to generate a multi-dimensional array which groups all factor-pairs by their product?
Input array:
$array = array(1,2,3,4,5,6,7,8);
I want to display all factor-pairs for each product group that have more than one factor-pair.
In a case where there are no product groups that have more than one factor-pair, No pairs Found should be displayed.
Given the above input, this is my expected result:
1 6 and 2 3 // product group = 6
1 8 and 2 4 // product group = 8
2 6 and 3 4 // product group = 12
3 8 and 4 6 // product group = 24
*note as the input array increases in size, the output will display more than two factor-pairs per group.
This is my code from C++:
void findPairs(int arr[], int n)
{
bool found = false;
unordered_map<int, pair < int, int > > H;
for (int i=0; i<n; i++)
{
for (int j=i+1; j<n; j++)
{
// If product of pair is not in hash table,
// then store it
int prod = arr[i]*arr[j];
if (H.find(prod) == H.end())
H[prod] = make_pair(i,j);
// If product of pair is also available in
// then print current and previous pair
else
{
pair<int,int> pp = H[prod];
cout << arr[pp.first] << " " << arr[pp.second]
<< " and " << arr[i]<<" "<<arr[j]<<endl;
found = true;
}
}
}
// If no pair find then print not found
if (found == false)
cout << "No pairs Found" << endl;
}
This is your C++ code "translated" to PHP (mostly by search & replace).
90% of the translation was achieved by removing the variable types and prepending the variable names with $. The array PHP type is a mixture of array, list and map (aka hash, dictionary) and can be used for both $H and the values it contains (pairs of values).
function findPairs(array $arr, $n)
{
$found = false;
$H = array();
for ($i=0; $i<$n; $i++)
{
for ($j=$i+1; $j<$n; $j++)
{
// If product of pair is not in hash table,
// then store it
$prod = $arr[$i]*$arr[$j];
if (! array_key_exists($prod, $H))
$H[$prod] = array($i,$j);
// If product of pair is also available in
// then print current and previous pair
else
{
$pp = $H[$prod];
echo $arr[$pp[0]], " ", $arr[$pp[1]]
, " and ", $arr[$i], " ", $arr[$j], "\n";
$found = true;
}
}
}
// If no pair find then print not found
if ($found == false)
echo "No pairs Found\n";
}
$array = array(1,2,3,4,5,6,7,8);
findPairs($array, count($array));
And this is its output:
1 6 and 2 3
1 8 and 2 4
2 6 and 3 4
3 8 and 4 6
The simplest solution will work fine with a small array like your example but will use a lot of memory for bigger inputs. Basically, first calculate all products by using a nested loop. For every product, create a list of inputs that generate the product. Mind you that there might be more than 2 ways to get a certain result, so you might get an output like 1 12 and 2 6 and 3 4 for bigger lists.
For an input of size N, you need to store ((N -1) * N) / 2 tuples in memory, so that's something to keep in mind.
$input = [1, 2, 3, 4, 5, 6, 7, 8];
$products = [];
foreach ($input as $index1 => $value1) {
// Assuming you only want unique combinations, only combine this value with the other values coming after it
for ($index2 = $index1 + 1; $index2 < count($input); $index2++) {
$value2 = $input[$index2];
$product = $value1 * $value2;
// Make sure there is an entry in the $products array for adding this input to
if (!isset($products[$product])) {
$products[$product] = [];
}
// Add this input (formatted) to the list of possible inputs resulting in this product
$products[$product][] = sprintf('%d %d', $value1, $value2);
}
}
// Print all inputs resulting in the same products, if there are more than 1 way to produce the same output
foreach ($products as $inputs) {
if (count($inputs) > 1) {
echo implode(' and ', $inputs), PHP_EOL;
}
}
Will output
1 6 and 2 3
1 8 and 2 4
2 6 and 3 4
3 8 and 4 6
PHP code demo
<?php
ini_set("display_errors", 1);
$result=array();
$array = array(1,2,3,4,5,6,7,8);
$counter=0;
$noOfPairs=3;
while (count($result)!=$noOfPairs)
{
shuffle($array);
getPair($array);
}
print_r($result);
function getPair($array)
{
global $result;
$product=$array[0]*$array[1];
if(isset($result[$product]))
{
return false;
}
$result[$product][]=array($array[0],$array[1]);
unset($array[0]);
unset($array[1]);
foreach($array as $key1 => $value1)
{
foreach($array as $key2 => $value2)
{
if($value1*$value2==$product)
{
$result[$product][]=array($value1,$value2);
break;
}
}
if(count($result[$product])==2)
{
break;
}
}
if(count($result[$product])==1)
{
unset($result[$product]);
}
}
I didn't speed test my method but I think it is more direct and easier to read.Basically, it generates the full multi-dim array, then filters out any subarrays that have only one pair, then if there are subarrays remaining, it displays them. Simple.
My method performs without any count() calls and without incrementing key variables. It uses the very fast isset() call to filter the result array and array_walk() to iterate and implode() the qualifying subarrays.
As a bonus feature, I've used range() to dynamically generate the input array which is determined by entering the highest value for the array. Of course, if you want to find pairs for [3,4,5], then you'll have to modify this process or simply revert to your original style -- it depends on your project expectations.
Code: (Demo)
function findPairs($maxfactor){
$array=range(1,$maxfactor); // spare yourself having to write out the array
foreach($array as $v1){
$array=array_slice($array,1); // shrink array as you go to avoid needless iterations
foreach($array as $v2){
$result[$v1*$v2][]="$v1 $v2"; // generate multi-dim output array using products as outer keys
}
}
$result=array_filter($result,function($a){return isset($a[1]);}); // remove elements with < 2 pairs
if(!$result){
echo "No pairs found";
}else{
array_walk($result,function($a){echo implode(' and ',$a),"\n";});
}
}
findPairs(8);
Output:
1 6 and 2 3
1 8 and 2 4
2 6 and 3 4
3 8 and 4 6
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
I have the following array
$example=array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
$limit=4 // 4 at the beginning only...
//it used to get incremented automatically to 8,12,16....
At first i want 1,2,3,4as an output for which i have done
foreach($example as $eg)
{
if($eg>$limit)
continue;
}
and i am easily getting 1,2,3,4 at the first then 1,2,3,4,5,6,7,8 then1,2,3,4,5,6,7,8,9,10,11,12
But now i what i want is 1,2,3,4 at the very beginning then 5,6,7,8 then 9,10,11,12 lyk this... how can i get that???
please do help me... :)
AS the
foreach($example as $eg)
{
if($eg>$limit)
continue;
}
is returning only 1,2,3,4 at $limit=4 and 1,2,3,4,5,6,7,8 at $limit=8
i need 1,2,3,4 at $limit=4 and 5,6,7,8 at $limit=8
Have you tried using the helpful built-in functions?
array_chunk($example,$limit);
Alternatively, for more page-like behaviour:
$pagenum = 2; // change based on page
$offset = $pagenum * $limit;
array_slice($example,$offset,$limit);
You would first chunk the array so each chunk has 4 elements, then loop through each chunk:
To change the numbers shown depending on which group, you could do:
$group = $_GET['group'];
$items = array_chunk($example, ceil(count($example)/4)[$group-1];
echo implode(", ", $items);
Then you can go to
yoursite.com/page.php?group=1
And it will output
1, 2, 3, 4
And when you go to
yoursite.com/page.php?group=2
It will output
5, 6, 7, 8
etc.
You can try something like this
$example=array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
$limit = 8;
$limit -= 4;
for($i = $limit; $i < ($limit + 4); $i++)
{
echo $example[$i].' ';
}
Output
//for $limit 4 output 1 2 3 4
//for $limit 8 output 5 6 7 8
//for $limit 16 output 13 14 15 16
I am looking to figure out how to use a database of sales completions to influence split testing.
For example, say I have four different page layouts and for each I have the following stats:
Version 1: 6 sales,
Version 2: 1 sale,
Version 3: 3 sales,
Version 4: 4 sales,
Then it would make sense to have version 1 shown most often, and version 4 being next, while version 2 should hardly be shown at all.
Any ideas how I can achieve this?
Very simple solution, mainly depends on how your data looks currently as to what solution is easiest though.
$sales = array
(
1 => 6,
2 => 1,
3 => 3,
4 => 4
);
$weight = array();
foreach ($sales AS $layout => $num_sales)
{
for ($i = 0; $i < $num_sales; $i++)
{
$weight[] = $layout;
}
}
/*
$weight = array
(
1, 1, 1, 1, 1, 1,
2,
3, 3, 3,
4, 4, 4, 4
);
*/
// Pick a random one to use
$layout_to_use = $weight[rand(0, count($weight))];
Let's say you have display the layouts with following weights:
Version 1: 6
Version 4: 4
Version 3: 3
Version 2: 1
Sum of weight is 14 and weight 6 means that you want to show page approximately 6 times in 14 requests.
If you were using database (which I assume you do it would be)
SELECT SUM(weights) FROM version;
Easiest way to implement random selection with different probabilities of hitting item is to sum weights, sort items by their weights and than just iterate trough all items until you hit zero:
$i = rand( 1, $sum);
foreach( $items as $item){
$i -= $item->weight;
if( $i <= 0){
break;
}
}
// $item is now your desired $item
$items should be sorted list of class Item{ public $weight; ... }, because it's most probable that the first element will be used (second the second and so on) and least iterations will be required
What's happening inside:
$i = 12;
// first iteration, $weight = 6
$i = 6; // condition didn't match
// second iteration, $weight = 4
$i = 2; // condition didn't match
// third iteration, $weight = 3
$i = -1; // condition matched
// using version 3
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.