Related
I've got this task, which I honestly don't understand what exactly to do.
It my be because of my English level, or mathmatics level, but this is really something I can not make sense of. Could you help be at least to understand the task ?
My php knowledge is very well, at least I thought so...
The task is this :
"Carry" is a term of an elementary arithmetic. It's a digit that you transfer to column with higher significant digits when adding numbers.
This task is about getting the sum of all carried digits.
You will receive an array of two numbers, like in the example. The function should return the sum of all carried digits.
function carry($arr) {
// ...
}
carry([123, 456]); // 0
carry([555, 555]); // 3 (carry 1 from ones column, carry 1 from tens column, carry 1 from hundreds column)
carry([123, 594]); // 1 (carry 1 from tens column)
Support of arbitrary number of operands will be a plus:
carry([123, 123, 804]); // 2 (carry 1 from ones column, carry 1, carry 1 from hundreds column)
Background information on "carry": https://en.m.wikipedia.org/wiki/Carry_(arithmetic)
For this task, we don't actually need the numbers written under the equals line, just the numbers which are carried. Importantly, the carried numbers need to be used when calculating subsequent columns.
Before looping each column of integers, reverse the order of the columns so that looping from left-to-right also iterates the lowest unit column and progresses to higher unit columns (ones, then tens, then hundreds, etc).
For flexibility, my snippet is designed to handle numbers of dynamic length. If processing potential float numbers, you could merely multiply all number by a power of 10 to convert all values to integers. My snippet is not designed to handled signed integers.
Code: (Demo)
function sumCarries(array $array) {
$columns = ['carries' => []];
// prepare matrix of 1-digit integers in columns -- ones, tens, hundreds, etc
foreach ($array as $integer) {
$columns[] = str_split(strrev($integer));
}
// sum column values in ascending order and populate carry values
// subsequent column sums need to include carried value
for ($i = 0, $len = strlen(max($array)); $i < $len; ++$i) {
$columns['carries'][$i + 1] = (int)(array_sum(array_column($columns, $i)) / 10);
}
// sum all populated carry values
return array_sum($columns['carries']);
}
$tests = [
[123, 456], // no carries in any column
[555, 555], // 1 ones, 1 tens, 1 hundreds
[123, 594], // 1 tens
[123, 123, 804], // 1 ones, 1 hundreds
[99, 9, 99, 99, 99], // 4 ones, 4 hundreds
[9,9,9,9,9,9,9,9,9,9,9,9], // 10 ones
];
var_export(array_map('sumCarries', $tests));
Output:
array (
0 => 0,
1 => 3,
2 => 1,
3 => 2,
4 => 8,
5 => 10,
)
Since it's homework, I'm not going to fully answer the question, but explain the pieces you seem confused about so that you can put them together.
1 11 111 111 <- these are the carry digits
555 555 555 555 555
+ 555 -> + 555 -> + 555 -> + 555 -> + 555
----- ----- ----- ----- -----
0 10 110 1110
For a better example of two digits, let's use 6+6. To get the carry digit you can use the modulus operator where 12 % 10 == 2. So, (12 - (12 % 10)) / 10 == 1.
Thank you again. #Sammitch
I got it to make it work. Actually the problem was my English Math Level. The term "Carry digits" had no meaning at all for me. I was completely focusing on something else.
Here is my code : It may be far from perfect, but it does the job :)
function carry($arr) {
$sum_ones = 0;
$sum_tens = 0;
$sum_hunds = 0;
$arrCount = count($arr);
foreach($arr as $key){
$stri = (string)$key;
$foo[] = array(
"hunds" => $stri[0],
"tens" => $stri[1],
"ones" => $stri[2]
);
}
$fooCount = count($foo);
for($i=0; $i<$fooCount; $i++) {
$sum_ones+= $foo[$i]["ones"];
$sum_tens+= $foo[$i]["tens"];
$sum_hunds+= $foo[$i]["hunds"];
}
$sum1 = ($sum_ones - ($sum_ones % 10)) / 10;
$sum10 = ($sum_tens - ($sum_tens % 10)) / 10;
$sum100 = ($sum_hunds - ($sum_hunds % 10)) / 10;
return ($sum1 + $sum10 + $sum100);
}
$arr = array(555, 515, 111);
echo carry($arr);
I have a set of items. I need to randomly pick one. The problem is that they each have a weight of 1-10. A weight of 2 means that the item is twice as likely to be picked than a weight of 1. A weight of 3 is three times as likely.
I currently fill an array with each item. If the weight is 3, I put three copies of the item in the array. Then, I pick a random item.
My method is fast, but uses a lot of memory. I am trying to think of a faster method, but nothing comes to mind. Anyone have a trick for this problem?
EDIT: My Code...
Apparently, I wasn't clear. I do not want to use (or improve) my code. This is what I did.
//Given an array $a where $a[0] is an item name and $a[1] is the weight from 1 to 100.
$b = array();
foreach($a as $t)
$b = array_merge($b, array_fill(0,$t[1],$t));
$item = $b[array_rand($b)];
This required me to check every item in $a and uses max_weight/2*size of $a memory for the array. I wanted a COMPLETELY DIFFERENT algorithm.
Further, I asked this question in the middle of the night using a phone. Typing code on a phone is nearly impossible because those silly virtual keyboards simply suck. It auto-corrects everything, ruining any code I type.
An yet further, I woke up this morning with an entirely new algorithm that uses virtual no extra memory at all and does not require checking every item in the array. I posted it as an answer below.
This ones your huckleberry.
$arr = array(
array("val" => "one", "weight" => 1),
array("val" => "two", "weight" => 2),
array("val" => "three", "weight" => 3),
array("val" => "four", "weight" => 4)
);
$weight_sum = 0;
foreach($arr as $val)
{
$weight_sum += $val['weight'];
}
$r = rand(1, $weight_sum);
print "random value is $r\n";
for($i = 0; $i < count($arr); $i++)
{
if($r <= $arr[$i]['weight'])
{
print "$r <= {$arr[$i]['weight']}, this is our match\n";
print $arr[$i]['val'] . "\n";
break;
}
else
{
print "$r > {$arr[$i]['weight']}, subtracting weight\n";
$r -= $arr[$i]['weight'];
print "new \$r is $r\n";
}
}
No need to generate arrays containing an item for every weight, no need to fill an array with n elements for a weight of n. Just generate a random number between 1 and total weight, then loop through the array until you find a weight less than your random number. If it isn't less than the number, subtract that weight from the random and continue.
Sample output:
# php wr.php
random value is 8
8 > 1, subtracting weight
new $r is 7
7 > 2, subtracting weight
new $r is 5
5 > 3, subtracting weight
new $r is 2
2 <= 4, this is our match
four
This should also support fractional weights.
modified version to use array keyed by weight, rather than by item
$arr2 = array(
);
for($i = 0; $i <= 500000; $i++)
{
$weight = rand(1, 10);
$num = rand(1, 1000);
$arr2[$weight][] = $num;
}
$start = microtime(true);
$weight_sum = 0;
foreach($arr2 as $weight => $vals) {
$weight_sum += $weight * count($vals);
}
print "weighted sum is $weight_sum\n";
$r = rand(1, $weight_sum);
print "random value is $r\n";
$found = false;
$elem = null;
foreach($arr2 as $weight => $vals)
{
if($found) break;
for($j = 0; $j < count($vals); $j ++)
{
if($r < $weight)
{
$elem = $vals[$j];
$found = true;
break;
}
else
{
$r -= $weight;
}
}
}
$end = microtime(true);
print "random element is: $elem\n";
print "total time is " . ($end - $start) . "\n";
With sample output:
# php wr2.php
weighted sum is 2751550
random value is 345713
random element is: 681
total time is 0.017189025878906
measurement is hardly scientific - and fluctuates depending on where in the array the element falls (obviously) but it seems fast enough for huge datasets.
This way requires two random calculations but they should be faster and require about 1/4 of the memory but with some reduced accuracy if weights have disproportionate counts. (See Update for increased accuracy at the cost of some memory and processing)
Store a multidimensional array where each item is stored in the an array based on its weight:
$array[$weight][] = $item;
// example: Item with a weight of 5 would be $array[5][] = 'Item'
Generate a new array with the weights (1-10) appearing n times for n weight:
foreach($array as $n=>$null) {
for ($i=1;$i<=$n;$i++) {
$weights[] = $n;
}
}
The above array would be something like: [ 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 ... ]
First calculation: Get a random weight from the weighted array we just created
$weight = $weights[mt_rand(0, count($weights)-1)];
Second calculation: Get a random key from that weight array
$value = $array[$weight][mt_rand(0, count($array[$weight])-1)];
Why this works: You solve the weighted issue by using the weighted array of integers we created. Then you select randomly from that weighted group.
Update: Because of the possibility of disproportionate counts of items per weight, you could add another loop and array for the counts to increase accuracy.
foreach($array as $n=>$null) {
$counts[$n] = count($array[$n]);
}
foreach($array as $n=>$null) {
// Calculate proportionate weight (number of items in this weight opposed to minimum counted weight)
$proportion = $n * ($counts[$n] / min($counts));
for ($i=1; $i<=$proportion; $i++) {
$weights[] = $n;
}
}
What this does is if you have 2000 10's and 100 1's, it'll add 200 10's (20 * 10, 20 because it has 20x the count, and 10 because it is weighted 10) instead of 10 10's to make it proportionate to how many are in there opposed the minimum weight count. So to be accurate, instead of adding one for EVERY possible key, you are just being proportionate based on the MINIMUM count of weights.
I greatly appreciate the answers above. Please consider this answer, which does not require checking every item in the original array.
// Given $a as an array of items
// where $a[0] is the item name and $a[1] is the item weight.
// It is known that weights are integers from 1 to 100.
for($i=0; $i<sizeof($a); $i++) // Safeguard described below
{
$item = $a[array_rand($a)];
if(rand(1,100)<=$item[1]) break;
}
This algorithm only requires storage for two variables ($i and $item) as $a was already created before the algorithm kicked in. It does not require a massive array of duplicate items or an array of intervals.
In a best-case scenario, this algorithm will touch one item in the original array and be done. In a worst-case scenario, it will touch n items in an array of n items (not necessarily every item in the array as some may be touched more than once).
If there was no safeguard, this could run forever. The safeguard is there to stop the algorithm if it simply never picks an item. When the safeguard is triggered, the last item touched is the one selected. However, in millions of tests using random data sets of 100,000 items with random weights of 1 to 10 (changing rand(1,100) to rand(1,10) in my code), the safeguard was never hit.
I made histograms comparing the frequency of items selected among my original algorithm, the ones from answers above, and the one in this answer. The differences in frequencies are trivial - easy to attribute to variances in the random numbers.
EDIT... It is apparent to me that my algorithm may be combined with the algorithm pala_ posted, removing the need for a safeguard.
In pala_'s algorithm, a list is required, which I call an interval list. To simplify, you begin with a random_weight that is rather high. You step down the list of items and subtract the weight of each one until your random_weight falls to zero (or less). Then, the item you ended on is your item to return. There are variations on this interval algorithm that I've tested and pala_'s is a very good one. But, I wanted to avoid making a list. I wanted to use only the given weighted list and never touch all the items. The following algorithm merges my use of random jumping with pala_'s interval list. Instead of a list, I randomly jump around the list. I am guaranteed to get to zero eventually, so no safeguard is needed.
// Given $a as the weighted array (described above)
$weight = rand(1,100); // The bigger this is, the slower the algorithm runs.
while($weight>0)
{
$item = $a[array_rand($a)];
$weight-= $item[1];
}
// $item is the random item you want.
I wish I could select both pala_ and this answer as the correct answers.
I'm not sure if this is "faster", but I think it may be more "balance"d between memory usage and speed.
The thought is to transform your current implementation (500000 items array) into an equal-length array (100000 items), with the lowest "origin" position as key, and origin index as value:
<?php
$set=[["a",3],["b",5]];
$current_implementation=["a","a","a","b","b","b","b","b"];
// 0=>0 means the lowest "position" 0
// points to 0 in the set;
// 3=>1 means the lowest "position" 3
// points to 1 in the set;
$my_implementation=[0=>0,3=>1];
And then randomly picks a number between 0 and highest "origin" position:
// 3 is the lowest position of the last element ("b")
// and 5 the weight of that last element
$my_implemention_pick=mt_rand(0,3+5-1);
Full code:
<?php
function randomPickByWeight(array $set)
{
$low=0;
$high=0;
$candidates=[];
foreach($set as $key=>$item)
{
$candidates[$high]=$key;
$high+=$item["weight"];
}
$pick=mt_rand($low,$high-1);
while(!array_key_exists($pick,$candidates))
{
$pick--;
}
return $set[$candidates[$pick]];
}
$cache=[];
for($i=0;$i<100000;$i++)
{
$cache[]=["item"=>"item {$i}","weight"=>mt_rand(1,10)];
}
$time=time();
for($i=0;$i<100;$i++)
{
print_r(randomPickByWeight($cache));
}
$time=time()-$time;
var_dump($time);
3v4l.org demo
3v4l.org have some time limitation on codes, so the demo didn't finished. On my laptop the above demo finished in 10 seconds (i7-4700 HQ)
ere is my offer in case I've understand you right. I offer you take a look and if there are some question I'll explain.
Some words in advance:
My sample is with only 3 stages of weight - to be clear
- With outer while I'm simulating your main loop - I count only to 100.
- The array must to be init with one set of initial numbers as shown in my sample.
- In every pass of main loop I get only one random value and I'm keeping the weight at all.
<?php
$array=array(
0=>array('item' => 'A', 'weight' => 1),
1=>array('item' => 'B', 'weight' => 2),
2=>array('item' => 'C', 'weight' => 3),
);
$etalon_weights=array(1,2,3);
$current_weights=array(0,0,0);
$ii=0;
while($ii<100){ // Simulates your main loop
// Randomisation cycle
if($current_weights==$etalon_weights){
$current_weights=array(0,0,0);
}
$ft=true;
while($ft){
$curindex=rand(0,(count($array)-1));
$cur=$array[$curindex];
if($current_weights[$cur['weight']-1]<$etalon_weights[$cur['weight']-1]){
echo $cur['item'];
$array[]=$cur;
$current_weights[$cur['weight']-1]++;
$ft=false;
}
}
$ii++;
}
?>
I'll use this input array for my explanation:
$values_and_weights=array(
"one"=>1,
"two"=>8,
"three"=>10,
"four"=>4,
"five"=>3,
"six"=>10
);
The simple version isn't going to work for you because your array is so large. It requires no array modification but may need to iterate the entire array, and that's a deal breaker.
/*$pick=mt_rand(1,array_sum($values_and_weights));
$x=0;
foreach($values_and_weights as $val=>$wgt){
if(($x+=$wgt)>=$pick){
echo "$val";
break;
}
}*/
For your case, re-structuring the array will offer great benefits.
The cost in memory for generating a new array will be increasingly justified as:
array size increases and
number of selections increases.
The new array requires the replacement of "weight" with a "limit" for each value by adding the previous element's weight to the current element's weight.
Then flip the array so that the limits are the array keys and the values are the array values.
The selection logic is: the selected value will have the lowest limit that is >= $pick.
// Declare new array using array_walk one-liner:
array_walk($values_and_weights,function($v,$k)use(&$limits_and_values,&$x){$limits_and_values[$x+=$v]=$k;});
//Alternative declaration method - 4-liner, foreach() loop:
/*$x=0;
foreach($values_and_weights as $val=>$wgt){
$limits_and_values[$x+=$wgt]=$val;
}*/
var_export($limits_and_values);
$limits_and_values looks like this:
array (
1 => 'one',
9 => 'two',
19 => 'three',
23 => 'four',
26 => 'five',
36 => 'six',
)
Now to generate the random $pick and select the value:
// $x (from walk/loop) is the same as writing: end($limits_and_values); $x=key($limits_and_values);
$pick=mt_rand(1,$x); // pull random integer between 1 and highest limit/key
while(!isset($limits_and_values[$pick])){++$pick;} // smallest possible loop to find key
echo $limits_and_values[$pick]; // this is your random (weighted) value
This approach is brilliant because isset() is very fast and the maximum number of isset() calls in the while loop can only be as many as the largest weight (not to be confused with limit) in the array.
FOR YOUR CASE, THIS APPROACH WILL FIND THE VALUE IN 10 ITERATIONS OR LESS!
Here is my Demo that will accept a weighted array (like $values_and_weights), then in just four lines:
Restructure the array,
Generate a random number,
Find the correct value, and
Display it.
EDIT 1 -since posting I have learnt that the underlying question is about how to find the CARTESIAN PRODUCT (now go google), but not only because I don't want every perm, I want to find the cartesian products that use the same subarray Key never more than once per permuation AND my 'extra' question then is more about how to minimise the workload that a cartesian product would require (accepting a small error rate, I have to say)-
Imagine... I have four cooks and four recipes, each cook has a score for each recipe and today I'd like each cook to make one dish (but no dish should be made twice) and the decision should be based on the best (highest total scores) permutation for all four (so maybe a cook won't make his personal best).
I have put the data into a multi-dimensional array as such
array(
array (1,2,3,4),
array (35,0,0,0),
array (36,33,1,1),
array (20,20,5,3)
)
it has the same number of valuepairs in each sub array as the number of sub-arrays (if that helps any)
in reality the number of sub-arrays would reach a maximum of 8 (max perms therefore =8!, approx 40,000 not 8^8 because many combinations are not allowed)
the choice of having the data in this format is flexible if that helps
I am trying to create a second array that would output the best (ie HIGHEST value) possible combination of the sub-arrays as per KEYs where only ONE of each subarray can be used
--so here each subarray[0][1][2][3] would be used once per permutation
and each subarrayKey [0][1][2][3] would be used once per permutaion, in my actual problem I'm using associated arrays, but that is extra to this issue.--
So the example would create an array as such
newArray (35,33,5,4) // note that [2][0] was not used
IDEALLY I would prefer to not produce the ALL perms but rather, SOMEHOW, discard many combinations that would clearly not be best fit.
Any ideas for how to start? I would accept pseudo code.
For an example on SO about Cartesian Product, see PHP 2D Array output all combinations
EDIT 2
for more on making cartesian products more efficient, and maybe why it has to be case specific if you want to see if you can cut corners (with risk) Efficient Cartesian Product algorithm
Apologies, but this is going to be more of a logic layout than code...
It's not quite clear to me whether the array(1,2,3,4) are the scores for the first dish or for the first cook, but I would probably use an array such that
$array[$cook_id][$dish_number] = $score;
asort() each array so that $array[$cook_id] = array($lowest_scored_dish,...,$highest);
Consider a weighted preference for a particular cook to make a dish to be the difference between the score of the best dish and another.
As a very simple example, cooks a,b,c and dishes 0,1,2
$array['a'] = array(0=>100, 1=>50, 2=>0); // cook a prefers 0 over 1 with weight 50, over 2 with weight 100
$array['b'] = array(0=>100, 1=>100, 2=>50); // cook b prefers 0,1 over 2 with weight 50
$array['c'] = array(0=>50, 1=>50, 2=>100); // cook c prefers 2 with weight 50
After asort():
$array['a'] = array(0=>100, 1=>50, 2=>0);
$array['b'] = array(0=>100, 1=>100, 2=>50);
$array['c'] = array(2=>100, 0=>50, 1=>50);
Start with cook 'a' who prefers dish 0 over his next best dish by 50 points (weight). Cook 'b' also prefers dih 0, but with a weight of 0 over the next dish. Therefore it's likely (though not yet certain that cook 'a' should make dish 0.
Consider dish 0 to be reserved and move on to cook 'b'. Excluding dish 0, cook 'b' prefers dish 1. No other cook prefers dish 1, so cook 'b' is assigned dish 1.
Cook 'c' gets dish 2 by default.
This is a VERY convenient example where each cook gets to cook something that's a personal max, but I hope it's illustrative of some logic that would work out.
Let's make it less convenient:
$array['a'] = array(0=>75, 1=>50, 2=>0);
$array['b'] = array(0=>100, 1=>50, 2=>50);
$array['c'] = array(0=>100, 1=>25, 2=>25);
Start again with cook 'a' and see that 0 is preferred, but this time with weight 25. Cook 'b' prefers with a weight of 50 and cook 'c' prefers with a weight of 75. Cook 'c' wins dish 0.
Going back to the list of available cooks, 'a' prefers 1 with a weight of 50, but 'b' prefers it with weight 0. 'a' gets dish 1 and 'b' gets dish 2.
This still doesn't take care of all complexities, but it's a step in the right direction. Sometimes the assumption made for the first cook/dish combination will be wrong.
WAY less convenient:
$array['a'] = array(0=>200, 1=>148, 2=>148, 3=>0);
$array['b'] = array(0=>200, 1=>149, 2=>0, 3=>0);
$array['c'] = array(0=>200, 1=>150, 2=>147, 3=>147);
$array['d'] = array(0=>69, 1=>18, 2=>16, 3=>15);
'a' gets 0 since that's the max and no one else who prefers 0 has a higher weight
'b' wins 1 with a weight of 149
'd' wins 2 since 'c' doesn't have a preference from the available options
'c' gets 3
score: 200+149+147+16 = 512
While that's a good guess that's gathered without checking every permutation, it may be wrong. From here, ask, "If one cook traded with any one other cook, would the total increase?"
The answer is YES, a(0)+d(2) = 200+16 = 216, but a(2)+d(0) = 148+69 = 217.
I'll leave it to you to write the code for the "best guess" using the weighted approach, but after that, here's a good start for you:
// a totally uneducated guess...
$picks = array(0=>'a', 1=>'b', 2=>'c', 3=>'d');
do {
$best_change = false;
$best_change_weight = 0;
foreach ($picks as $dish1 => $cook1) {
foreach ($picks as $dish2 => $cook2) {
if (($array[$cook1][$dish1] + $array[$cook2][$dish2]) <
($array[$cook1][$dish2] + $array[$cook2][$dish1]))
{
$old_score = $array[$cook1][$dish1] + $array[$cook2][$dish2];
$new_score = $array[$cook1][$dish2] + $array[$cook2][$dish1];
if (($new_score - $old_score) > $best_change_weight) {
$best_change_weight = $new_score - $old_score;
$best_change = $dish2;
}
}
}
if ($best_change !== false) {
$cook2 = $picks[$best_change];
$picks[$dish1] = $cook2;
$picks[$dish2] = $cook1;
break;
}
}
} while ($best_change !== false);
I can't find a counter example to show that this doesn't work, but I'm suspicious of the case where
($array[$cook1][$dish1] + $array[$cook2][$dish2])
==
($array[$cook1][$dish2] + $array[$cook2][$dish1])
Maybe someone else will follow up with an answer to this "What if?"
Given this matrix, where the items in brackets are the "picks"
[a1] a2 a3
b1 [b2] b3
c1 c2 [c3]
If a1 + b2 == a2 + b1, then 'a' and 'b' will not switch dishes. The case I'm not 100% sure about is if there exists a matrix such that this is a better choice:
a1 [a2] a3
b1 b2 [b3]
[c1] c2 c3
Getting from the first state to the second requires two switches, the first of which seems arbitrary since it doesn't change the total. But, only by going through this arbitrary change can the last switch be made.
I tried to find an example 3x3 such that based on the "weighted preference" model I wrote about above, the first would be selected, but also such that the real optimum selection is given by the second. I wasn't able to find an example, but that doesn't mean that it doesn't exist. I don't feel like doing more matrix algebra right now, but maybe someone will pick up where I left off. Heck, maybe the case doesn't exist, but I thought I should point out the concern.
If it does work and you start with the correct pick, the above code will only loop through 64 times (8x8) for 8 cooks/dishes. If the pick is not correct and the first cook has a change, then it will go up to 72. If the 8th cook has a change, it's up to 128. It's possible that the do-while will loop several times, but I doubt it will get up near the CPU cycles required to sum all of the 40k combinations.
I may have a starting point for you with this algorithm that tries to choose cooks based on their ratio of max score to sum of scores (thus trying to choose chefs who are really good at one recipe but bad at the rest of the recipes to do that recipe)
$cooks = array(
array(1,2,3,4),
array(35,0,0,0),
array(36,33,1,1),
array(20,20,5,3)
);
$results = array();
while (count($cooks)) {
$curResult = array(
'cookId' => -1,
'recipe' => -1,
'score' => -1,
'ratio' => -1
);
foreach ($cooks as $cookId => $scores) {
$max = max($scores);
$ratio = $max / array_sum($scores);
if ($ratio > $curResult['ratio']) {
$curResult['cookId'] = $cookId;
$curResult['ratio'] = $ratio;
foreach ($scores as $recipe => $score) {
if ($score == $max) {
$curResult['recipe'] = $recipe;
$curResult['score'] = $score;
}
}
}
}
$results[$curResult['recipe']] = $curResult['score'];
unset($cooks[$curResult['cookId']]);
foreach ($cooks as &$cook) {
unset($cook[$curResult['recipe']]);
}
}
For the dataset provided, it does find what seems to be the optimum answer (35,33,5,4). However, it is still not perfect, for example, with the array:
$cooks = array(
array(1,2,3,4),
array(35,0,33,0),
array(36,33,1,1),
array(20,20,5,3)
);
The ideal answer would be (20,33,33,4), however this algorithm would return (35,33,5,4).
But since the question was asking for ideas of where to start, I guess this at least might suffice as something to start from :P
Try this
$mainArr = array(
array (1,2,3,4) ,
array (35,0,0,0) ,
array (36,33,1,1) ,
array (20,20,5,3)
);
$i = 0;
foreach( $mainArr as $subArray )
{
foreach( $subArray as $key => $value)
{
$newArr[$key][$i]=$value;
$i++;
}
}
$finalArr = array();
foreach( $newArr as $newSubArray )
{
$finalArr[] = max($newSubArray);
}
print_r( $finalArr );
OK here is a solution that allows you to find the best permutation of one cook to one recipe and no cook works twice and no recipe is made twice.
Thanks for the code to calculate perm of arrays goes to o'reilly...
http://docstore.mik.ua/orelly/webprog/pcook/ch04_26.htm
CONSIDERATIONS:
The number of cooks and the number of recipes are the same.
Going above a 5 by 5 matrix as here will get very big very fast. (see part 2 to be posted shortly)
The logic:
A permutation of an array assigns a place as well as just being included (ie what a combination does), so why not then assign each key of such an array to a recipe, the permutation guarantees no cook is repeated and the keys guarantee no recipe is repeated.
Please let me know if there are improvements or errors in my thinking or my code but here it is!
<?php
function pc_next_permutation($p, $size) {
//this is from http://docstore.mik.ua/orelly/webprog/pcook/ch04_26.htm
// slide down the array looking for where we're smaller than the next guy
for ($i = $size - 1; $p[$i] >= $p[$i+1]; --$i) { }
// if this doesn't occur, we've finished our permutations
// the array is reversed: (1, 2, 3, 4) => (4, 3, 2, 1)
if ($i == -1) { return false; }
// slide down the array looking for a bigger number than what we found before
for ($j = $size; $p[$j] <= $p[$i]; --$j) { }
// swap them
$tmp = $p[$i]; $p[$i] = $p[$j]; $p[$j] = $tmp;
// now reverse the elements in between by swapping the ends
for (++$i, $j = $size; $i < $j; ++$i, --$j) {
$tmp = $p[$i]; $p[$i] = $p[$j]; $p[$j] = $tmp;
}
return $p;
}
$cooks[441] = array(340=>5,342=>43,343=>50,344=>9,345=>0);
$cooks[442] = array(340=>5,342=>-33,343=>-30,344=>29,345=>0);
$cooks[443] = array(340=>5,342=>3,343=>0,344=>9,345=>10,);
$cooks[444] = array(340=>25,342=>23,343=>20,344=>19,345=>20,);
$cooks[445] = array(340=>27,342=>27,343=>26,344=>39,345=>50,);
//a consideration: this solution requires that the number of cooks equal the number of recipes
foreach ($cooks as $cooksCode => $cooksProfile){
$arrayOfCooks[]=$cooksCode;
$arrayOfRecipes = (array_keys($cooksProfile));
}
echo "<br/> here is the array of the different cooks<br/>";
print_r($arrayOfCooks);
echo "<br/> here is the array of the different recipes<br/>";
print_r($arrayOfRecipes);
$set = $arrayOfCooks;
$size = count($set) - 1;
$perm = range(0, $size);
$j = 0;
do {
foreach ($perm as $i) { $perms[$j][] = $set[$i]; }
} while ($perm = pc_next_permutation($perm, $size) and ++$j);
echo "<br/> here are all the permutations of the cooks<br/>";
print_r($perms);
$bestCombo = 0;
foreach($perms as $perm){
$thisScore =0;
foreach($perm as $key =>$cook){
$recipe= $arrayOfRecipes[$key];
$cookScore =$cooks[$cook][$recipe];
$thisScore = $thisScore+$cookScore;
}
if ($thisScore>$bestCombo){
$bestCombo=$thisScore;
$bestArray= $perm;
}
}
echo "<br/> here is the very best array<br/>";
print_r ($bestArray);
echo "<br/> best recipe assignment value is:".$bestCombo."<br/><br/>";
?>
I want to construct an array of 3 offers that output in a random order. I have the following code and whilst it does output 3 random offers it doesn't appear to be random. The first value in the generated array always seems to be from the 1st 2 records in my offers table. The offers table only has 5 records in it (I dont know if this is affecting things).
$arrayOfferCount = $offerCount-1;
$displayThisManyOffers = 3;
$range = range(0, $arrayOfferCount);
$vals = array_rand($range, $displayThisManyOffers);`
Any help or advice would be appreciated.
Working fine here. Benchmark it over lots of runs instead of just gut feeling... here it is for 1,000 tries:
<?php
$offerCount = 5;
$arrayOfferCount = $offerCount-1;
$displayThisManyOffers = 3;
$range = range(0, $arrayOfferCount);
for($i = 0; $i < 1000; $i++) {
$vals = array_rand($range, $displayThisManyOffers);
foreach($vals as $val) {
$counts[$val]++;
}
}
sort($counts);
print_r($counts);
Generates:
Array
(
[0] => 583
[1] => 591
[2] => 591
[3] => 610
[4] => 625
)
I know that mt_rand() is much better PRNG.
However, in your case you need to let the database select them for you
SELECT * FROM ads ORDER BY RAND() LIMIT 0, 3
It is probably randomly picking which to display, but displaying them in the same order they appear in your array. If you do it enough times (~20) you should get the third one to show up once if this is the case (chances of choosing exactly the last 3 out of 5 would be 1 in 5*4, so around every 20th one you'll see the third option appear).
array_rand seems not to work properly sometimes (see PHP-Manual comments).
Workaround: Get the array size and pick a random index using the function mt_rand
I am trying to create a little php script that can make my life a bit easier.
Basically, I am going to have 21 text fields on a page where I am going to input 20 different numbers. In the last field I will enter a number let's call it the TOTAL AMOUNT. All I want the script to do is to point out which numbers from the 20 fields added up will come up to TOTAL AMOUNT.
Example:
field1 = 25.23
field2 = 34.45
field3 = 56.67
field4 = 63.54
field5 = 87.54
....
field20 = 4.2
Total Amount = 81.90
Output: field1 + fields3 = 81.90
Some of the fields might have 0 as value because sometimes I only need to enter 5-15 fields and the maximum will be 20.
If someone can help me out with the php code for this, will be greatly appreciated.
If you look at oezis algorithm one drawback is immediately clear: It spends very much time summing up numbers which are already known not to work. (For example if 1 + 2 is already too big, it doesn't make any sense to try 1 + 2 + 3, 1 + 2 + 3 + 4, 1 + 2 + 3 + 4 + 5, ..., too.)
Thus I have written an improved version. It does not use bit magic, it makes everything manual. A drawback is, that it requires the input values to be sorted (use rsort). But that shouldn't be a big problem ;)
function array_sum_parts($vals, $sum){
$solutions = array();
$pos = array(0 => count($vals) - 1);
$lastPosIndex = 0;
$currentPos = $pos[0];
$currentSum = 0;
while (true) {
$currentSum += $vals[$currentPos];
if ($currentSum < $sum && $currentPos != 0) {
$pos[++$lastPosIndex] = --$currentPos;
} else {
if ($currentSum == $sum) {
$solutions[] = array_slice($pos, 0, $lastPosIndex + 1);
}
if ($lastPosIndex == 0) {
break;
}
$currentSum -= $vals[$currentPos] + $vals[1 + $currentPos = --$pos[--$lastPosIndex]];
}
}
return $solutions;
}
A modified version of oezis testing program (see end) outputs:
possibilities: 540
took: 3.0897309780121
So it took only 3.1 seconds to execute, whereas oezis code executed 65 seconds on my machine (yes, my machine is very slow). That's more than 20 times faster!
Furthermore you may notice, that my code found 540 instead of 338 possibilities. This is because I adjusted the testing program to use integers instead of floats. Direct floating point comparison is rarely the right thing to do, this is a great example why: You sometimes get 59.959999999999 instead of 59.96 and thus the match will not be counted. So, if I run oezis code with integers it finds 540 possibilities, too ;)
Testing program:
// Inputs
$n = array();
$n[0] = 6.56;
$n[1] = 8.99;
$n[2] = 1.45;
$n[3] = 4.83;
$n[4] = 8.16;
$n[5] = 2.53;
$n[6] = 0.28;
$n[7] = 9.37;
$n[8] = 0.34;
$n[9] = 5.82;
$n[10] = 8.24;
$n[11] = 4.35;
$n[12] = 9.67;
$n[13] = 1.69;
$n[14] = 5.64;
$n[15] = 0.27;
$n[16] = 2.73;
$n[17] = 1.63;
$n[18] = 4.07;
$n[19] = 9.04;
$n[20] = 6.32;
// Convert to Integers
foreach ($n as &$num) {
$num *= 100;
}
$sum = 57.96 * 100;
// Sort from High to Low
rsort($n);
// Measure time
$start = microtime(true);
echo 'possibilities: ', count($result = array_sum_parts($n, $sum)), '<br />';
echo 'took: ', microtime(true) - $start;
// Check that the result is correct
foreach ($result as $element) {
$s = 0;
foreach ($element as $i) {
$s += $n[$i];
}
if ($s != $sum) echo '<br />FAIL!';
}
var_dump($result);
sorry for adding a new answer, but this is a complete new solution to solve all problems of life, universe and everything...:
function array_sum_parts($n,$t,$all=false){
$count_n = count($n); // how much fields are in that array?
$count = pow(2,$count_n); // we need to do 2^fields calculations to test all possibilities
# now i want to look at every number from 1 to $count, where the number is representing
# the array and add up all array-elements which are at positions where my actual number
# has a 1-bit
# EXAMPLE:
# $i = 1 in binary mode 1 = 01 i'll use ony the first array-element
# $i = 10 in binary mode 10 = 1010 ill use the secont and the fourth array-element
# and so on... the number of 1-bits is the amount of numbers used in that try
for($i=1;$i<=$count;$i++){ // start calculating all possibilities
$total=0; // sum of this try
$anzahl=0; // counter for 1-bits in this try
$k = $i; // store $i to another variable which can be changed during the loop
for($j=0;$j<$count_n;$j++){ // loop trough array-elemnts
$total+=($k%2)*$n[$j]; // add up if the corresponding bit of $i is 1
$anzahl+=($k%2); // add up the number of 1-bits
$k=$k>>1; //bit-shift to the left for looking at the next bit in the next loop
}
if($total==$t){
$loesung[$i] = $anzahl; // if sum of this try is the sum we are looking for, save this to an array (whith the number of 1-bits for sorting)
if(!$all){
break; // if we're not looking for all solutions, make a break because the first one was found
}
}
}
asort($loesung); // sort all solutions by the amount of numbers used
// formating the solutions to getting back the original array-keys (which shoud be the return-value)
foreach($loesung as $val=>$anzahl){
$bit = strrev(decbin($val));
$total=0;
$ret_this = array();
for($j=0;$j<=strlen($bit);$j++){
if($bit[$j]=='1'){
$ret_this[] = $j;
}
}
$ret[]=$ret_this;
}
return $ret;
}
// Inputs
$n[0]=6.56;
$n[1]=8.99;
$n[2]=1.45;
$n[3]=4.83;
$n[4]=8.16;
$n[5]=2.53;
$n[6]=0.28;
$n[7]=9.37;
$n[8]=0.34;
$n[9]=5.82;
$n[10]=8.24;
$n[11]=4.35;
$n[12]=9.67;
$n[13]=1.69;
$n[14]=5.64;
$n[15]=0.27;
$n[16]=2.73;
$n[17]=1.63;
$n[18]=4.07;
$n[19]=9.04;
$n[20]=6.32;
// Output
$t=57.96;
var_dump(array_sum_parts($n,$t)); //returns one possible solution (fuc*** fast)
var_dump(array_sum_parts($n,$t,true)); // returns all possible solution (relatively fast when you think of all the needet calculations)
if you don't use the third parameter, it returns the best (whith the least amount numbers used) solution as array (whith keys of the input-array) - if you set the third parameter to true, ALL solutions are returned (for testing, i used the same numbers as zaf in his post - there are 338 solutions in this case, found in ~10sec on my machine).
EDIT:
if you get all, you get the results ordered by which is "best" - whithout this, you only get the first found solution (which isn't necessarily the best).
EDIT2:
to forfil the desire of some explanation, i commented the essential parts of the code . if anyone needs more explanation, please ask
1. Check and eliminate fields values more than 21st field
2. Check highest of the remaining, Add smallest,
3. if its greater than 21st eliminate highest (iterate this process)
4. If lower: Highest + second Lowest, if equal show result.
5. if higher go to step 7
6. if lower go to step 4
7. if its lower than add second lowest, go to step 3.
8. if its equal show result
This is efficient and will take less execution time.
Following method will give you an answer... almost all of the time. Increase the iterations variable to your taste.
<?php
// Inputs
$n[1]=8.99;
$n[2]=1.45;
$n[3]=4.83;
$n[4]=8.16;
$n[5]=2.53;
$n[6]=0.28;
$n[7]=9.37;
$n[8]=0.34;
$n[9]=5.82;
$n[10]=8.24;
$n[11]=4.35;
$n[12]=9.67;
$n[13]=1.69;
$n[14]=5.64;
$n[15]=0.27;
$n[16]=2.73;
$n[17]=1.63;
$n[18]=4.07;
$n[19]=9.04;
$n[20]=6.32;
// Output
$t=57.96;
// Let's try to do this a million times randomly
// Relax, thats less than a blink
$iterations=1000000;
while($iterations-->0){
$z=array_rand($n, mt_rand(2,20));
$total=0;
foreach($z as $x) $total+=$n[$x];
if($total==$t)break;
}
// If we did less than a million times we have an answer
if($iterations>0){
$total=0;
foreach($z as $x){
$total+=$n[$x];
print("[$x] + ". $n[$x] . " = $total<br/>");
}
}
?>
One solution:
[1] + 8.99 = 8.99
[4] + 8.16 = 17.15
[5] + 2.53 = 19.68
[6] + 0.28 = 19.96
[8] + 0.34 = 20.3
[10] + 8.24 = 28.54
[11] + 4.35 = 32.89
[13] + 1.69 = 34.58
[14] + 5.64 = 40.22
[15] + 0.27 = 40.49
[16] + 2.73 = 43.22
[17] + 1.63 = 44.85
[18] + 4.07 = 48.92
[19] + 9.04 = 57.96
A probably inefficient but simple solution with backtracking
function subset_sums($a, $val, $i = 0) {
$r = array();
while($i < count($a)) {
$v = $a[$i];
if($v == $val)
$r[] = $v;
if($v < $val)
foreach(subset_sums($a, $val - $v, $i + 1) as $s)
$r[] = "$v $s";
$i++;
}
return $r;
}
example
$ns = array(1, 2, 6, 7, 11, 5, 8, 9, 3);
print_r(subset_sums($ns, 11));
result
Array
(
[0] => 1 2 5 3
[1] => 1 2 8
[2] => 1 7 3
[3] => 2 6 3
[4] => 2 9
[5] => 6 5
[6] => 11
[7] => 8 3
)
i don't think the answer isn't as easy as nik mentioned. let's ay you have the following numbers:
1 2 3 6 8
looking for an amount of 10
niks solution would do this (if i understand it right):
1*8 = 9 = too low
adding next lowest (2) = 11 = too high
now he would delete the high number and start again taking the new highest
1*6 = 7 = too low
adding next lowest (2) = 9 = too low
adding next lowest (3) = 12 = too high
... and so on, where the perfect answer would simply
be 8+2 = 10... i think the only solution is trying every possible combination of
numbers and stop if the amaunt you are looking for is found (or realy calculate all, if there are different solutions and save which one has used least numbers).
EDIT: realy calculating all possible combiations of 21 numbers will end up in realy, realy, realy much calculations - so there must be any "intelligent" solution for adding numbers in a special order (lik that one in niks post - with some improvements, maybe that will bring us to a reliable solution)
Without knowing if this is a homework assignment or not, I can give you some pseudo code as a hint for a possible solution, note the solution is not very efficient, more of a demonstration.
Hint:
Compare each field value to all field value and at each iteration check if their sum is equal to TOTAL_AMOUNT.
Pseudo code:
for i through field 1-20
for j through field 1-20
if value of i + value of j == total_amount
return i and j
Update:
What you seem to be having is the Subset sum problem, given within the Wiki link is pseudo code for the algorithm which might help point you in the right direction.