Расчет ограниченного декартова произведения - PHP

РЕДАКТИРОВАТЬ 1 - с момента публикации я узнал, что основной вопрос заключается в том, как найти КАРТОВСКИЙ ПРОДУКТ (теперь идите в Google), но не только потому, что мне не нужна каждая химическая завивка, я хочу найти декартовы продукты, которые используют тот же подмассив Ключ никогда не должен повторяться более одного раза на пермутацию И мой `` дополнительный '' вопрос больше о том, как минимизировать рабочую нагрузку, которую потребует декартово произведение (я должен сказать, принимая небольшую частоту ошибок) -

Представьте себе ... у меня четыре повара и четыре рецепта, у каждого повара есть оценка за каждый рецепт, и сегодня я бы хотел, чтобы каждый повар приготовил одно блюдо (но ни одно блюдо не должно быть приготовлено дважды), и решение должно быть основано на лучшем. (наивысший общий балл) перестановка для всех четырех (так что, возможно, повар не добьется своего личного рекорда).

Я поместил данные в многомерный массив как таковой

 array(
   array (1,2,3,4),
   array (35,0,0,0),
   array (36,33,1,1),
   array (20,20,5,3)
 )
  • у него такое же количество пар значений в каждом подмассиве, что и количество подмассивов (если это кому-то помогает)

  • на самом деле количество подмассивов достигнет максимум 8 (поэтому максимальное количество разрешений = 8 !, приблизительно 40 000, а не 8 ^ 8, потому что многие комбинации недопустимы)

  • выбор данных в этом формате является гибким, если это помогает

Я пытаюсь создать второй массив, который будет выводить лучшую (т.е. САМОЕ ВЫСОКОЕ значение) возможную комбинацию подмассивов в соответствии с КЛЮЧАМИ, где может использоваться только ОДИН из каждого подмассива.

- поэтому здесь каждый подмассив [0] [1] [2] [3] будет использоваться один раз для каждой перестановки, а каждый subarrayKey [0] [1] [2] [3] будет использоваться один раз на перестановку, в моей реальной проблеме Я использую связанные массивы, но это лишнее.

Таким образом, в примере будет создан массив как таковой newArray (35,33,5,4) // обратите внимание, что [2] [0] не использовался

ИДЕАЛЬНО я бы предпочел не производить ВСЕ химические завивки, а, скорее, КАК-ТО отбросить многие комбинации, которые явно не подходят лучше всего.

Есть идеи, как начать? Я бы принял псевдокод.

Для примера SO о декартовом произведении см. Вывод всех комбинаций в двумерном массиве PHP

ИЗМЕНИТЬ 2, чтобы узнать больше о том, как сделать декартовы продукты более эффективными и, возможно, почему это должно быть конкретным для конкретного случая, если вы хотите увидеть, можете ли вы срезать углы (с риском) Эффективный алгоритм декартового произведения


person EnglishAdam    schedule 26.04.2013    source источник
comment
Если max (N подмассивов) = 8, то max (perm) = 4 ^ 8 = 65536.   -  person HamZa    schedule 26.04.2013
comment
Из того, что я понимаю в этой проблеме, каждый повар может приготовить только один рецепт, поэтому количество фактических перестановок составляет 4 * 3 * 2 * 1 = 4! = 24. Имея это в виду, было бы не так сложно вычислить его грубой силой, если бы было всего 4 повара / рецепта.   -  person Pudge601    schedule 26.04.2013
comment
Спасибо ребята! Я действительно считаю, что это 8! что = 40,320.   -  person EnglishAdam    schedule 26.04.2013


Ответы (4)


Извините, но это будет скорее логическая схема, чем код ...

Мне не совсем понятно, является ли массив (1,2,3,4) баллами для первого блюда или для первого приготовления, но я, вероятно, использовал бы такой массив, что

$array[$cook_id][$dish_number] = $score;

asort () каждый массив так, чтобы $ array [$ cook_id] = array ($ low_scored_dish, ..., $ high);

Считайте взвешенное предпочтение того или иного повара приготовить блюдо как разницу между оценкой лучшего блюда и другого.

В качестве очень простого примера готовим блюда a, b, c и 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

После 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);

Начните с повара «а», который на 50 баллов (по весу) предпочитает блюдо 0 своему следующему лучшему блюду. Повар 'b' также предпочитает dih 0, но вес следующего блюда равен 0. Следовательно, вероятно (хотя еще не уверен, что готовить 'a' должно получиться блюдо 0.

Считайте блюдо 0 зарезервированным и переходите к приготовлению «b». За исключением блюда 0, повар 'b' предпочитает блюдо 1. Ни один другой повар не предпочитает блюдо 1, поэтому повар 'b' назначает блюдо 1.

По умолчанию Cook 'c' получает блюдо 2.

Это ОЧЕНЬ удобный пример, когда каждый повар может приготовить что-то личное, но я надеюсь, что это иллюстрирует некоторую логику, которая сработает.

Сделаем менее удобным:

$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);

Начните снова с готовить «a» и убедитесь, что предпочтительнее 0, но на этот раз с весом 25. Готовьте «b» предпочитает с весом 50, а готовьте «c» предпочитает с весом 75. Повар «c» выигрывает блюдо 0 .

Возвращаясь к списку доступных поваров, «a» предпочитает 1 с весом 50, а «b» - с весом 0. «a» получает блюдо 1, а «b» - блюдо 2.

Это все еще не решает всех сложностей, но это шаг в правильном направлении. Иногда предположение, сделанное для первого сочетания повар / блюдо, оказывается неверным.

ПУТЬ менее удобно:

$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' получает 0, так как это максимум, и никто другой, кто предпочитает 0, не имеет большего веса 'b' выигрывает 1 с весом 149 'd' выигрывает 2, поскольку 'c' не имеет предпочтения из доступных вариантов ' c 'получает 3

оценка: 200 + 149 + 147 + 16 = 512

Хотя это хорошее предположение, полученное без проверки каждой перестановки, оно может быть неверным. Отсюда спросите: «Если бы один повар торговал с другим поваром, увеличилось бы общее количество?»

Ответ ДА, a (0) + d (2) = 200 + 16 = 216, но a (2) + d (0) = 148 + 69 = 217.

Я оставлю вам написать код для "наилучшего предположения" с использованием взвешенного подхода, но после этого вот вам хорошее начало:

// 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);

Я не могу найти пример счетчика, чтобы показать, что это не работает, но я с подозрением отношусь к случаю, когда ($ array [$ cook1] [$ spread1] + $ array [$ cook2] [$ plate2]) = = ($ массив [$ cook1] [$ блюдо2] + $ массив [$ cook2] [$ блюдо1])

Может быть, кто-нибудь еще ответит на этот вопрос «А что, если?».

Учитывая эту матрицу, где элементы в скобках являются «выбранными»

[a1]   a2   a3
 b1   [b2]  b3
 c1    c2  [c3]

Если a1 + b2 == a2 + b1, то «a» и «b» не будут переключать тарелки. Случай, в котором я не уверен на 100%, - это то, что существует такая матрица, что это лучший выбор:

 a1   [a2]   a3
 b1    b2   [b3]
[c1]   c2    c3

Для перехода от первого состояния ко второму требуется два переключателя, первый из которых кажется произвольным, поскольку он не меняет общую сумму. Но только через это произвольное изменение может быть сделано последнее переключение.

Я попытался найти пример 3x3, такой, что на основе модели «взвешенного предпочтения», о которой я писал выше, будет выбран первый, но также такой, что реальный оптимальный выбор будет дан вторым. Мне не удалось найти пример, но это не значит, что его не существует. Мне сейчас не хочется больше заниматься матричной алгеброй, но, возможно, кто-то продолжит с того места, где я остановился. Черт возьми, может быть, случая и не существует, но я подумал, что должен указать на проблему.

Если это сработает, и вы начнете с правильного выбора, приведенный выше код будет повторяться только 64 раза (8x8) для 8 поваров / блюд. Если выбор неправильный и у первого повара есть изменение, тогда оно увеличится до 72. Если у 8-го повара есть изменение, оно будет до 128. Возможно, что do-while повторится несколько раз, но я сомневаюсь он будет приближаться к циклам ЦП, необходимым для суммирования всех 40k комбинаций.

person thatthatisis    schedule 26.04.2013
comment
Мне очень нравится думать (+1), и я полностью ценю ваше время и усилия, большое вам спасибо. Я буду смотреть на эту проблему (снова) завтра весь день и определенно попробую это мышление, проблема в том, что, как вы думали, вопрос также о предельных значениях и различиях в попытке максимизировать назначение. (ps вы думали о начальных массивах, как я хотел, извините за путаницу) - person EnglishAdam; 26.04.2013
comment
Узнав, что я пытаюсь сократить путь к полному декартовому поиску продукта, я принимаю, что мне придется признать, что любые сокращения с помощью прогнозирования времени будут генерировать ошибки. Другая проблема для меня заключается в том, что это объединение в пары необходимо выполнять 20 раз в секунду в рамках гораздо более крупного игрового цикла. Я думаю, что это могло бы значительно сэкономить по сравнению с 40 000 циклов (и, мальчик, я понял, что если кто-то заинтересован в продолжении этой взвешенной идеи, есть даже другие вопросы по SO, которые хотят решить эту проблему). Однако я собираюсь посмотреть, смогу ли я вызвать в воображении ДЕЙСТВИТЕЛЬНО грубые и готовые идеи. - person EnglishAdam; 27.04.2013

У меня может быть отправная точка для вас с этим алгоритмом, который пытается выбирать поваров на основе их отношения максимального балла к сумме баллов (таким образом, пытаясь выбрать поваров, которые действительно хороши в одном рецепте, но плохо разбираются в остальных рецептах, чтобы сделать этот рецепт)

$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']]);
    }
}

Для предоставленного набора данных он действительно находит то, что кажется оптимальным ответом (35,33,5,4). Однако он все же не идеален, например, с массивом:

$cooks = array(
    array(1,2,3,4),
    array(35,0,33,0),
    array(36,33,1,1),
    array(20,20,5,3)
);

Идеальным ответом будет (20,33,33,4), однако этот алгоритм вернет (35,33,5,4).

Но поскольку вопрос был о том, с чего начать, я полагаю, что этого, по крайней мере, может быть достаточно как чего-то, с чего начать: P

person Pudge601    schedule 26.04.2013
comment
Спасибо, милый, я очень ценю ваши усилия и ясность, +1, хотя одной логики недостаточно (как вы показали), она определенно может помочь в попытке сократить количество комбинаций по 40К. .. еще раз спасибо, я буду погружен в это весь день завтра, так что посмотрим, что будет! - person EnglishAdam; 26.04.2013
comment
Чао! все еще на этом ... просто глядя на ваш код, я не вижу, как вы проверяете, дублируются ли повар или рецепт. Любые идеи? - person EnglishAdam; 28.04.2013
comment
Когда я выбираю повара / рецепт, я затем снимаю все, что готовлю, и снимаю этот рецепт с оставшихся поваров. - person Pudge601; 29.04.2013

Попробуй это

$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 );
person Subhra Sekhar Mukhopadhyay    schedule 26.04.2013
comment
Спасибо, но ... выводит массив ([0] = ›36 [1] =› 33 [2] = ›5 [3] =› 4), который просто является самым высоким для каждого из них, имея cook [2], составляющую 2 рецепта. [0] и [1] - person EnglishAdam; 26.04.2013

Хорошо, вот решение, которое позволяет вам найти лучшую замену одного повара на один рецепт, и ни один повар не работает дважды, и ни один рецепт не создается дважды.

Спасибо за код для вычисления допустимости массивов к oreilly ... http://docstore.mik.ua/orelly/webprog/pcook/ch04_26.htm

СООБРАЖЕНИЯ:

  • Количество поваров и количество рецептов одинаковы.

  • Превышение матрицы 5 на 5, как здесь, очень быстро станет очень большим. (см. часть 2, которая будет опубликована в ближайшее время)

Логика: перестановка массива назначает место, а также просто включается (то есть, что делает комбинация), так почему бы тогда не назначить каждый ключ такого массива рецепту, перестановка гарантирует повар не повторяется, а ключи гарантируют, что рецепт не повторится.

Пожалуйста, дайте мне знать, есть ли улучшения или ошибки в моем мышлении или моем коде, но вот они!

<?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/>";  




?>
person EnglishAdam    schedule 28.04.2013