#php #random #weighted
#php #Случайный #взвешенный
Вопрос:
У меня есть массив записей из базы данных (хотя база данных не имеет отношения к этому вопросу — в конечном итоге она становится массивом «строк», каждая строка представляет собой массив со строковыми ключами, соответствующими имени поля). Например:
$items = array(
1 => array('id' => 1, 'name' => 'John', 'created' => '2011-08-14 8:47:39'),
2 => array('id' => 2, 'name' => 'Mike', 'created' => '2011-08-30 16:00:12'),
3 => array('id' => 5, 'name' => 'Jane', 'created' => '2011-09-12 2:30:00'),
4 => array('id' => 7, 'name' => 'Mary', 'created' => '2011-09-14 1:18:40'),
5 => array('id' => 16, 'name' => 'Steve', 'created' => '2011-09-14 3:10:30'),
//etc...
);
Что я хочу сделать, это перетасовать этот массив, но каким-то образом придать больший «вес» элементам с более поздней «созданной» временной меткой. Случайность не обязательно должна быть идеальной, и точный вес на самом деле не имеет значения для меня. Другими словами, если есть какой-то быстрый и простой метод, который вроде как кажется случайным людям, но не является математически случайным, я согласен с этим. Кроме того, если это нелегко сделать с «бесконечным континуумом» временных меток, было бы неплохо присвоить каждой записи день или неделю и просто выполнить взвешивание на основе того, в какой день или неделю они находятся.
Предпочтителен относительно быстрый / эффективный метод, поскольку такая рандомизация будет происходить при каждой загрузке определенной страницы на моем веб-сайте (но если это невозможно сделать эффективно, я согласен периодически запускать ее и кэшировать результат).
Ответ №1:
Вы можете использовать, например. эта функция сравнения:
function cmp($a, $b){
$share_of_a = $a['id'];
$share_of_b = $b['id'];
return rand(0, ($share_of_a $share_of_b)) > $share_of_a ? 1 : -1;
}
а затем используйте это следующим образом:
usort($items, 'cmp');
Он сравнивает два элемента массива на основе их идентификаторов (это проще, и они назначаются на основе даты создания — у более новых элементов идентификаторы больше). Сравнение выполняется случайным образом, с разными шансами на успех для каждого элемента, что дает больше шансов более новым элементам. Чем больше идентификатор (чем новее элемент), тем больше шансов, что он появится в начале.
Например, элемент с id=16
имеет в 16 раз больше шансов, чем элемент id=1
, появиться раньше в результирующем списке.
Комментарии:
1. Спасибо за ваш ответ. Я думаю, что, возможно, я ввел вас в заблуждение, хотя — это просто совпадение, что у меня есть идентификаторы в порядке самых последних. Мне действительно нужно, чтобы вес был основан на дате «создания». Однако я рассмотрю возможность использования вашего алгоритма и посмотрю, смогу ли я использовать его для целочисленного значения метки времени вместо id.
2. На самом деле, я только что протестировал это, преобразовав «созданную» временную метку в временную метку unix с помощью функции strtotime(). Проблема в том, что он всегда возвращает элементы в одном и том же точном порядке — это не дает достаточной «случайности». (Я хочу, чтобы порядок несколько отличался при каждом запуске).
3. @JordanLev: Если он возвращает точно такие же результаты, то вы, вероятно, делаете что-то неправильно (это должно отличаться хотя бы немного). В случае временных меток эпохи Unix различия между временными метками невелики по сравнению с самими временными метками — я бы действительно выбрал IDs. Кроме того, вероятно, существует некоторое ограничение, и вы, вероятно, ограничены использованием 32-битных целых чисел, поэтому я предлагаю использовать меньшие числа, чем временные метки Unix. В качестве альтернативы вы можете использовать не временные метки, а количество секунд, прошедших с наименьшей временной метки в базе данных. Попробуйте несколько раз и проверьте, отличаются ли результаты.
Ответ №2:
Как насчет разделения его на блоки по дате, рандомизации каждого блока, а затем объединения их обратно в один список?
Комментарии:
1. Это было бы недостаточно случайным — я не хочу, чтобы все элементы за один день предшествовали всем элементам следующего дня. Вместо этого я хочу, чтобы первыми были «многие» элементы, которые являются недавними, но также и некоторые менее свежие и т.д.
2. хахах ну, разбиение его на куски по часам, минутам или секундам также было бы возможно.
Ответ №3:
//$array is your array
$mother=array();
foreach($array as $k->$v) $mother[rand(0,count($array))][$k]=$v;
ksort($mother);
$child=array();
foreach($mother as $ak->$av)
foreach($av as $k->$v) $child[$k]=$v;
$array=$child;
или вы можете использовать shuffle()
Комментарии:
1. Я не понимаю, как это удовлетворяет требованию придания большего «веса» более свежим элементам.
Ответ №4:
Частично вдохновившись ответом от @Tadeck , я придумал решение. Это довольно многословно, если бы кто-нибудь мог упростить это, это было бы здорово. Но, похоже, это работает просто отлично:
//Determine lowest and highest timestamps
$first_item = array_slice($items, 0, 1);
$first_item = $first_item[0];
$min_ts = strtotime($first_item['created']);
$max_ts = strtotime($first_item['created']);
foreach ($items as $item) {
$ts = strtotime($item['created']);
if ($ts < $min_ts) {
$min_ts = $ts;
}
if ($ts > $max_ts) {
$max_ts = $ts;
}
}
//bring down the min/max to more reasonable numbers
$min_rand = 0;
$max_rand = $max_ts - $min_ts;
//Create an array of weighted random numbers for each item's timestamp
$weighted_randoms = array();
foreach ($items as $key => $item) {
$random_value = mt_rand($min_rand, $max_rand); //use mt_rand for a higher max value (plain old rand() maxes out at 32,767)
$ts = strtotime($item['created']);
$ts = $ts - $min_ts; //bring this down just like we did with $min_rand and $max_rand
$random_value = $random_value $ts;
$weighted_randoms[$key] = $random_value;
}
//Sort by our weighted random value (the array value), with highest first.
arsort($weighted_randoms, SORT_NUMERIC);
$randomized_items = array();
foreach ($weighted_randomsas $item_key => $val) {
$randomized_items[$item_key] = $items[$item_key];
}
print_r($randomized_items);