Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A restricted Cartesian Product Calculation - PHP

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

like image 540
EnglishAdam Avatar asked Oct 21 '22 11:10

EnglishAdam


1 Answers

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.

like image 109
thatthatisis Avatar answered Oct 24 '22 10:10

thatthatisis