С++. Взвешенный std::shuffle

Есть ли способ сделать красивую и элегантную взвешенную перетасовку, используя стандартную библиотеку? Есть std::discrete_distribution. Я хочу что-то вроде этого:

std::vector<T> data { N elements };
std::vector<int> weights { N weights };
std::shuffle(std::begin(data), std::end(data), something based on discrete distribution);

person Atin    schedule 07.05.2018    source источник
comment
Не могли бы вы уточнить (возможно, с примером), что вы подразумеваете под взвешенным перетасовкой?   -  person Bob__    schedule 07.05.2018
comment
comment
@Bob__ Да, выглядит хорошо ... Вы должны опубликовать это как ответ   -  person Severin Pappadeux    schedule 08.05.2018


Ответы (1)


Если целью OP является перетасовка списка r элементов

таким образом, что для заданного списка весов w элемент a[i] с весом w[i] должен быть первым элементом списка случайное перемешивание r с вероятностью w[i]/sum(w).

Как указано на странице, на которую ссылается Северин Паппадё:

Взвешенная случайная перетасовка аналогична взвешенной случайной выборке из списка a без замены. То есть выбрать с вероятностью w[i]/sum(w) элемент a[i] из a. Сохраните этот элемент в списке r. Затем удалите элемент a[i] из a и w[i] из w и выберите новый элемент измененного списка a, и так далее, пока a не станет пустым.

Я не знаю о таком алгоритме в стандартной библиотеке, но простой реализацией может быть:

#include <random>
#include <algorithm>
#include <iterator>

template <class D, class W, class URBG>
void weighted_shuffle
    ( D first, D last
    , W first_weight, W last_weight
    , URBG&& g )
{
    while (first != last and first_weight != last_weight)
    {
        std::discrete_distribution dd(first_weight, last_weight);
        auto i = dd(g);
        if ( i )
        {
            std::iter_swap(first, std::next(first, i));
            std::iter_swap(first_weight, std::next(first_weight, i));
        }
        ++first;
        ++first_weight;
    }
}

Живой пример ЗДЕСЬ.

person Bob__    schedule 07.05.2018
comment
отлично. но почему if ( i )? - person JHBonarius; 08.05.2018
comment
@JHBonarius ну, я думаю, преждевременная оптимизация. Это просто для экономии ненужных затрат на замену и продвижение итераторов. В любом случае, случайная часть должна занимать больше времени. - person Bob__; 08.05.2018
comment
а, теперь я понял. Это предотвращает замену элемента самим собой. - person JHBonarius; 08.05.2018
comment
Мне нужно было сделать это: std::discrete_distribution‹int›dd(first_weight, last_weight) - person supo; 03.02.2019
comment
@supo Да, такой вывод типа - это функция C ++ 17. Если вы можете ориентироваться только на C++11, вы можете сделать что-то вроде этого: wandbox.org/permlink/Bf3MIVP1avVIYjAG - person Bob__; 03.02.2019
comment
@Bob__ Честно говоря, ветвление может быть даже медленнее, чем случайный дополнительный обмен. - person Solomon Ucko; 23.04.2019
comment
@SolomonUcko Ну, да, это может быть или нет. Когда я писал это, я думал о контейнерах, где std::next(first, i) будет O(n) (обратите внимание, что std::shuffle требует случайных итераторов), так что имело смысл оптимизировать этот крайний случай. Конечно, в таких случаях было бы лучше скопировать данные в вектор и вместо этого преобразовать его. В общем, согласен, это преждевременная оптимизация и, наверное, я ее уберу. - person Bob__; 24.04.2019