Как Вы эффективно генерируете список K неповторяющиеся целые числа между 0 и верхняя граница N [дубликат]

typedef typename Tail::inUnion dummy;

Однако я не уверен, что реализация inUnion верна. Если я правильно понимаю, этот класс не должен быть создан, поэтому вкладка «fail» никогда не будет автоматически терпеть неудачу. Возможно, было бы лучше указать, находится ли тип в объединении или нет с простым булевым значением.

template  struct Contains;

template 
struct Contains >
{
    enum { result = Contains::result };
};

template 
struct Contains >
{
    enum { result = true };
};

template 
struct Contains
{
    enum { result = false };
};

PS: Посмотрите на Boost :: Variant

PS2: посмотрите на typelists , особенно в книге Андрея Александреску: Modern C ++ Design

29
задан Community 23 May 2017 в 11:47
поделиться

12 ответов

случайный модуль из библиотеки Python делает его чрезвычайно легким и эффективным:

from random import sample
print sample(xrange(N), K)

sample функция возвращает список уникальных элементов K, выбранных из данной последовательности.
xrange "эмулятор списка", т.е. он ведет себя как список последовательных чисел, не создавая его в памяти, которая делает его сверхбыстрым для задач как этот.

12
ответ дан DzinX 28 November 2019 в 01:58
поделиться

Вот способ сделать это в O (N) без дополнительного устройства хранения данных. Я вполне уверен, это не чисто случайное распределение, но это, вероятно, достаточно близко для многого использования.

/* generate N sorted, non-duplicate integers in [0, max[  in O(N))*/
 int *generate(int n, int max) {
    float step,a,v=0;
    int i;    
    int *g = (int *)calloc(n, sizeof(int));
    if ( ! g) return 0;

    for (i=0; i<n; i++) {
        step = (max-v)/(float)(n-i);
        v+ = floating_pt_random_in_between(0.0, step*2.0);
        if ((int)v == g[i-1]){
          v=(int)v+1;             //avoid collisions
        }
        g[i]=v;
    }
    while (g[i]>max) {
      g[i]=max;                   //fix up overflow
      max=g[i--]-1;
    }
    return g;
 }
-1
ответ дан AShelly 28 November 2019 в 01:58
поделиться

Ускорьте тривиальный алгоритм путем хранения чисел K в хранилище хеширования. Знание K перед запуском устраняет всю неэффективность вставки в карту хеша, и Вы все еще извлекаете пользу из быстрого поиска.

1
ответ дан Bill the Lizard 28 November 2019 в 01:58
поделиться

Генерируйте массив 0...N-1, заполнился a[i] = i.

Тогда переставляют первое K объекты.

Перестановка:

  • Запускаются J = N-1
  • Выбор случайное число 0...J (скажите, R)
  • подкачка a[R] с a[J]
    • с тех пор R может быть равна J, элемент может быть подкачан с собой
  • , вычитают 1 от [1 111] и повторение.

Наконец, возьмите K последние элементы.

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

Работы в [1 114] O (K) и O (N) время, требует O (N) устройство хранения данных.

часть перестановки называют перестановка Фишера-Йетса или перестановка Knuth , описывают в 2-м объеме [1 119] Искусство Программирования.

2
ответ дан ivan_pozdeev 28 November 2019 в 01:58
поделиться

Следующий код (в C, неизвестном источнике), кажется, решает проблему чрезвычайно хорошо:

 /* generate N sorted, non-duplicate integers in [0, max[ */
 int *generate(int n, int max) {
    int i, m, a;    
    int *g = (int *)calloc(n, sizeof(int));
    if ( ! g) return 0;

    m = 0;
    for (i=0; i<max; i++) {
        a = random_in_between(0, max - i);
        if (a < n - m) {
            g[m] = i;
            m ++;
        }
    }
    return g;
 }

кто-либо знает, где я могу найти больше драгоценных камней как этот?

3
ответ дан tucuxi 28 November 2019 в 01:58
поделиться

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

Выбор блочный шифр, такой как ЧАЙ или XTEA. Используйте XOR сворачивание для сокращения размера блока до самого маленького питания двух больших, чем набор, из которого Вы выбираете. Используйте случайное семя в качестве ключа к шифру. Для генерации элемента n в перестановке зашифруйте n с шифром. Если выходное число не находится в Вашем наборе, зашифруйте это. Повторитесь, пока число не будет в наборе. В среднем необходимо будет сделать меньше чем два шифрования на сгенерированное число. Это обладает дополнительным преимуществом, которое, если Ваше семя криптографически безопасно, Ваша вся перестановка - также.

я записал об этом в намного большем количестве деталей здесь .

5
ответ дан Nick Johnson 28 November 2019 в 01:58
поделиться

В Искусство Программирования, Объем 2: получисловые Алгоритмы, Третий Выпуск , Knuth описывает следующий алгоритм выборки выбора:

Алгоритм S (Метод выборки выбора). Выбрать записи n наугад из ряда N, где 0 < n в‰ ¤ N.

S1. [Инициализировать]. Набор t в†ђ 0, m в†ђ 0. (Во время этого алгоритма m представляет количество записей, выбранных до сих пор, и t является общим количеством входных записей, с которыми мы имели дело.)

S2. [Генерируйте U.] Генерируют случайное число U, равномерно распределенное между нулем и один.

S3. [Тест]. Если (N †“t) U ≥ n †“m, перейдите к шагу S5.

S4. [Выбор]. Выберите следующую запись для образца и увеличьте m и t на 1. Если m < n, перейдите к шагу S2; иначе образец завершен, и алгоритм завершается.

S5. [Пропуск]. Пропустите следующую запись (не включайте ее в образец), увеличьте t на 1 и вернитесь к шагу S2.

, за которым реализация может быть легче следовать, чем описание. Вот реализация языка Common LISP, которые выбирают n случайных участников из списка:

(defun sample-list (n list &optional (length (length list)) result)
  (cond ((= length 0) result)
        ((< (* length (random 1.0)) n)
         (sample-list (1- n) (cdr list) (1- length)
                      (cons (car list) result)))
        (t (sample-list n (cdr list) (1- length) result))))

И вот реализация, которая не использует рекурсию, и которая работает со всеми видами последовательностей:

(defun sample (n sequence)
  (let ((length (length sequence))
        (result (subseq sequence 0 n)))
    (loop
       with m = 0
       for i from 0 and u = (random 1.0)
       do (when (< (* (- length i) u) 
                   (- n m))
            (setf (elt result m) (elt sequence i))
            (incf m))
       until (= m n))
    result))
12
ответ дан Vebjorn Ljosa 28 November 2019 в 01:58
поделиться

Моим решением является ориентированный C++, но я уверен, что это могло быть переведено в другие языки, так как это довольно просто.

  • Первый, генерируйте связанный список с элементами K, идущими от 0 до K
  • Тогда, пока список не пуст, генерируйте случайное число между 0, и размер вектора
  • Берут тот элемент, продвигают его в другой вектор и удаляют его из исходного списка

, Это решение только включает два повторения цикла, и никакие поиски хэш-таблицы или что-либо вида. Таким образом в фактическом коде:

// Assume K is the highest number in the list
std::vector<int> sorted_list;
std::vector<int> random_list;

for(int i = 0; i < K; ++i) {
    sorted_list.push_back(i);
}

// Loop to K - 1 elements, as this will cause problems when trying to erase
// the first element
while(!sorted_list.size() > 1) {
    int rand_index = rand() % sorted_list.size();
    random_list.push_back(sorted_list.at(rand_index));
    sorted_list.erase(sorted_list.begin() + rand_index);
}                 

// Finally push back the last remaining element to the random list
// The if() statement here is just a sanity check, in case K == 0
if(!sorted_list.empty()) {
    random_list.push_back(sorted_list.at(0));
}
1
ответ дан Nik Reiman 28 November 2019 в 01:58
поделиться

Это - Код Perl. Grep является фильтром, и поскольку всегда я не тестировал этот код.

@list = grep ($_ % I) == 0, (0..N);
  • I = интервал
  • N = Верхняя граница

Только получите числа, которые соответствуют Вашему интервалу через оператор модуля.

@list = grep ($_ % 3) == 0, (0..30);

возвратится 0, 3, 6... 30

Это - псевдо код Perl. Вы, возможно, должны настроить его, чтобы заставить его компилировать.

-2
ответ дан J.J. 28 November 2019 в 01:58
поделиться

Версия Выборки Водохранилища довольно проста:

my $N = 20;
my $k;
my @r;

while(<>) {
  if(++$k <= $N) {
    push @r, $_;
  } elsif(rand(1) <= ($N/$k)) {
    $r[rand(@r)] = $_;
  }
}

print @r;

Это - $N, случайным образом выбрал строки из STDIN. Замените <> / $ _ материал с чем-то еще, если Вы не используете строки из файла, но это - довольно простой алгоритм.

0
ответ дан Michael Cramer 28 November 2019 в 01:58
поделиться

Шаг 1: Создайте список целых чисел.
Шаг 2: Выполните Перемешивание Кнута .

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

Основной алгоритм Knuth Shuffle состоит в том, что вы начинаете со списка целых чисел. Затем вы заменяете первое целое число любым числом в списке и возвращаете текущее (новое) первое целое число. Затем вы меняете второе целое число на любое число в списке (кроме первого) и возвращаете текущее (новое) второе целое число. Затем ... и т.д ...

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

1
ответ дан 28 November 2019 в 01:58
поделиться

Если список отсортирован, например, если вы хотите извлечь K элементов из N, но не заботитесь об их относительном порядке, в статье Предлагается эффективный алгоритм для последовательной случайной выборки. Выборка (Джеффри Скотт Виттер, Транзакции ACM по математическому программному обеспечению , том 13, № 1, март 1987 г., страницы 56-67).

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

/* Sampling according to [Vitter87].
 * 
 * Bibliography
 * [Vitter 87]
 *   Jeffrey Scott Vitter, 
 *   An Efficient Algorithm for Sequential Random Sampling
 *   ACM Transactions on MAthematical Software, 13 (1), 58 (1987).
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <string>
#include <iostream>

#include <iomanip>

#include <boost/random/linear_congruential.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/random/uniform_real.hpp>

using namespace std;

// This is a typedef for a random number generator.
// Try boost::mt19937 or boost::ecuyer1988 instead of boost::minstd_rand
typedef boost::minstd_rand base_generator_type;

    // Define a random number generator and initialize it with a reproducible
    // seed.
    // (The seed is unsigned, otherwise the wrong overload may be selected
    // when using mt19937 as the base_generator_type.)
    base_generator_type generator(0xBB84u);
    //TODO : change the seed above !
    // Defines the suitable uniform ditribution.
    boost::uniform_real<> uni_dist(0,1);
    boost::variate_generator<base_generator_type&, boost::uniform_real<> > uni(generator, uni_dist);



void SequentialSamplesMethodA(int K, int N) 
// Outputs K sorted random integers out of 0..N, taken according to 
// [Vitter87], method A.
    {
    int top=N-K, S, curr=0, currsample=-1;
    double Nreal=N, quot=1., V;

    while (K>=2)
        {
        V=uni();
        S=0;
        quot=top/Nreal;
        while (quot > V)
            {
            S++; top--; Nreal--;
            quot *= top/Nreal;
            }
        currsample+=1+S;
        cout << curr << " : " << currsample << "\n";
        Nreal--; K--;curr++;
        }
    // special case K=1 to avoid overflow
    S=floor(round(Nreal)*uni());
    currsample+=1+S;
    cout << curr << " : " << currsample << "\n";
    }

void SequentialSamplesMethodD(int K, int N)
// Outputs K sorted random integers out of 0..N, taken according to 
// [Vitter87], method D. 
    {
    const int negalphainv=-13; //between -20 and -7 according to [Vitter87]
    //optimized for an implementation in 1987 !!!
    int curr=0, currsample=0;
    int threshold=-negalphainv*K;
    double Kreal=K, Kinv=1./Kreal, Nreal=N;
    double Vprime=exp(log(uni())*Kinv);
    int qu1=N+1-K; double qu1real=qu1;
    double Kmin1inv, X, U, negSreal, y1, y2, top, bottom;
    int S, limit;
    while ((K>1)&&(threshold<N))
        {
        Kmin1inv=1./(Kreal-1.);
        while(1)
            {//Step D2: generate X and U
            while(1)
                {
                X=Nreal*(1-Vprime);
                S=floor(X);
                if (S<qu1) {break;}
                Vprime=exp(log(uni())*Kinv);
                }
            U=uni();
            negSreal=-S;
            //step D3: Accept ?
            y1=exp(log(U*Nreal/qu1real)*Kmin1inv);
            Vprime=y1*(1. - X/Nreal)*(qu1real/(negSreal+qu1real));
            if (Vprime <=1.) {break;} //Accept ! Test [Vitter87](2.8) is true
            //step D4 Accept ?
            y2=0; top=Nreal-1.;
            if (K-1 > S)
                {bottom=Nreal-Kreal; limit=N-S;}
            else {bottom=Nreal+negSreal-1.; limit=qu1;}
            for(int t=N-1;t>=limit;t--)
                {y2*=top/bottom;top--; bottom--;}
            if (Nreal/(Nreal-X)>=y1*exp(log(y2)*Kmin1inv))
                {//Accept !
                Vprime=exp(log(uni())*Kmin1inv);
                break;
                }
            Vprime=exp(log(uni())*Kmin1inv);
            }
        // Step D5: Select the (S+1)th record
        currsample+=1+S;
        cout << curr << " : " << currsample << "\n";
        curr++;
        N-=S+1; Nreal+=negSreal-1.;
        K-=1; Kreal-=1; Kinv=Kmin1inv;
        qu1-=S; qu1real+=negSreal;
        threshold+=negalphainv;
        }
    if (K>1) {SequentialSamplesMethodA(K, N);}
    else {
        S=floor(N*Vprime);
        currsample+=1+S;
        cout << curr << " : " << currsample << "\n";
        }
    }


int main(void)
    {
    int Ntest=10000000, Ktest=Ntest/100;
    SequentialSamplesMethodD(Ktest,Ntest);
    return 0;
    }

$ time ./sampling|tail

выдает следующий результат на моем ноутбуке

99990 : 9998882
99991 : 9998885
99992 : 9999021
99993 : 9999058
99994 : 9999339
99995 : 9999359
99996 : 9999411
99997 : 9999427
99998 : 9999584
99999 : 9999745

real    0m0.075s
user    0m0.060s
sys 0m0.000s
0
ответ дан 28 November 2019 в 01:58
поделиться