Как рандомизировать массив записей PHP, придав больший вес более свежим элементам?

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