najlepszy sposób na wybranie losowego podzbioru ze zbioru?

Mam zbiór obiektów w wektorze, z którego chciałbym wybrać losowy podzbiór (np. 100 przedmiotów wraca; wybrać 5 losowo). W moim pierwszym (bardzo pochopnym) przejściu zrobiłem niezwykle proste i być może zbyt sprytne rozwiązanie: {]}

Vector itemsVector = getItems();

Collections.shuffle(itemsVector);
itemsVector.setSize(5);

Chociaż ma to tę zaletę, że jest ładne i proste, podejrzewam, że nie będzie zbyt dobrze skalowane, tj. Kolekcje.shuffle () musi być przynajmniej O (n). Moja mniej mądra alternatywa to

Vector itemsVector = getItems();

Random rand = new Random(System.currentTimeMillis()); // would make this static to the class    

List subsetList = new ArrayList(5);
for (int i = 0; i < 5; i++) {
     // be sure to use Vector.remove() or you may get the same item twice
     subsetList.add(itemsVector.remove(rand.nextInt(itemsVector.size())));
}

Wszelkie sugestie dotyczące lepszych sposobów na losowe podzbiór ze zbioru?

Author: Alexander, 2008-09-26

10 answers

Jon Bentley omawia to w "perłach programowania" lub "więcej pereł programowania". Musisz być ostrożny z procesem selekcji N Of m, ale myślę, że pokazany kod działa poprawnie. Zamiast losowego przetasowywania wszystkich elementów, możesz dokonać losowego przetasowania tylko pierwszych n pozycji - co jest użytecznym zapisem, gdy N Na wykresie dziennym widać, że na wykresie dziennym widać, że na wykresie dziennym znajduje się 10000$. house, więc nie mogę tego formalnie sprawdzić.

 8
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-05 15:02:27

@Jonathan,

Myślę, że to jest rozwiązanie, o którym mówisz:

void genknuth(int m, int n)
{    for (int i = 0; i < n; i++)
         /* select m of remaining n-i */
         if ((bigrand() % (n-i)) < m) {
             cout << i << "\n";
             m--;
         }
}
Jest na stronie 127 Perły programowania autorstwa Jona Bentleya i opiera się na implementacji Knutha.

EDIT: właśnie widziałem kolejną modyfikację na stronie 129:

void genshuf(int m, int n)
{    int i,j;
     int *x = new int[n];
     for (i = 0; i < n; i++)
         x[i] = i;
     for (i = 0; i < m; i++) {
         j = randint(i, n-1);
         int t = x[i]; x[i] = x[j]; x[j] = t;
     }
     sort(x, x+m);
     for (i = 0; i< m; i++)
         cout << x[i] << "\n";
}

Jest to oparte na idei, że"...musimy przetasować tylko pierwsze m elementy tablicy..."

 7
Author: daniel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-28 11:59:30

Jeśli próbujesz wybrać K różne elementy z listy n, metody podane powyżej będą O (n) lub o (kn), ponieważ usunięcie elementu z wektora spowoduje, że tablica przesunie wszystkie elementy w dół.

Ponieważ prosisz o najlepszy sposób, zależy to od tego, co możesz zrobić z listą wejściową.

Jeśli jest dopuszczalne modyfikowanie listy wejściowej, jak w przykładach, możesz po prostu zamienić losowe elementy K na początek listy i zwrócić je in O (k)time like this:

public static <T> List<T> getRandomSubList(List<T> input, int subsetSize)
{
    Random r = new Random();
    int inputSize = input.size();
    for (int i = 0; i < subsetSize; i++)
    {
        int indexToSwap = i + r.nextInt(inputSize - i);
        T temp = input.get(i);
        input.set(i, input.get(indexToSwap));
        input.set(indexToSwap, temp);
    }
    return input.subList(0, subsetSize);
}

Jeśli lista musi skończyć się w tym samym stanie, w którym się rozpoczęła, możesz śledzić pozycje, które podmieniłeś, a następnie powrócić do pierwotnego stanu po skopiowaniu wybranej sublisty. Nadal jest to rozwiązanie O (k).

Jeśli jednak nie możesz w ogóle modyfikować listy wejściowej, A k jest znacznie mniejsze niż n (np. Duplikuj, wyrzuć i zaznacz ponownie. Daje to O (kn / (n-k)), które jest nadal bliskie O(K), gdy n dominuje k. (na przykład, jeśli k jest mniejsze niż n / 2, to zmniejsza się do O (k)).

Jeśli k nie jest zdominowane przez n i nie możesz zmodyfikować listy, możesz równie dobrze skopiować oryginalną listę i użyć pierwszego rozwiązania, ponieważ O(n) będzie tak samo dobre jak O (k).

Jak zauważyli inni, jeśli zależy ci na silnej losowości, gdzie każda sublista jest możliwa( i bezstronna), będziesz zdecydowanie potrzebuję czegoś mocniejszego niż java.util.Random. Zobacz java.security.SecureRandom.

 4
Author: Dave L.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 16:22:11

Kilka tygodni temu napisałem skuteczną implementację tego. Jest w C# , ale tłumaczenie na Javę jest banalne(zasadniczo ten sam kod). Plusem jest to, że jest to również całkowicie bezstronne (czego niektóre z istniejących odpowiedzi nie są)- sposób na test, który jest tutaj.

Opiera się na Durstenfeld implementacji shuffle Fisher-Yates.

 4
Author: Greg Beech,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-10 13:28:41

Twoje drugie rozwiązanie użycia elementu losowego do wybrania wydaje się jednak brzmieć:

 2
Author: qualidafial,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-18 17:38:39

Ile kosztuje usunięcie? Bo jeśli to wymaga przepisania tablicy na nowy kawałek pamięci, to wykonałeś operacje O (5n) w drugiej wersji, a nie O(n), które chciałeś wcześniej.

Można utworzyć tablicę booleanów ustawioną na false, a następnie:

for (int i = 0; i < 5; i++){
   int r = rand.nextInt(itemsVector.size());
   while (boolArray[r]){
       r = rand.nextInt(itemsVector.size());
   }
   subsetList.add(itemsVector[r]);
   boolArray[r] = true;
}

To podejście działa, jeśli podzbiór jest mniejszy niż całkowity rozmiar o znaczny margines. Jak te rozmiary zbliżają się do siebie (tj. generator liczb. W takim przypadku zrobiłbym listę liczb całkowitych wielkości twojej większej tablicy, a następnie przetasował tę listę liczb całkowitych i wyciągnął z niej pierwsze elementy, aby uzyskać (nie kolidujące) indeces. W ten sposób masz koszt O (N) w budowaniu tablicy liczb całkowitych, a inny O(n) w shuffle, ale nie kolizje z wewnętrznego kontrolera while i mniej niż potencjalny o(5n), że usunięcie może kosztować.

 0
Author: mmr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 22:17:25

Osobiście wybrałbym Twoją wstępną realizację: bardzo zwięzłą. Testy wydajności pokażą, jak dobrze skaluje się. Zaimplementowałem bardzo podobny blok kodu w przyzwoicie nadużywanej metodzie i skalował się wystarczająco. Poszczególne kody opierały się również na tablicach zawierających >10 000 pozycji.

 0
Author: daniel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 22:18:16
Set<Integer> s = new HashSet<Integer>()
// add random indexes to s
while(s.size() < 5)
{
    s.add(rand.nextInt(itemsVector.size()))
}
// iterate over s and put the items in the list
for(Integer i : s)
{
    out.add(itemsVector.get(i));
}
 0
Author: Wesley Tarle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 23:56:48

To {[3] } jest bardzo podobne pytanie na stackoverflow.

Aby podsumować moje ulubione odpowiedzi z tej strony (furst one od użytkownika Kyle):

  • O (N) rozwiązanie : Przejrzyj listę i skopiuj element (lub odniesienie do niego) z prawdopodobieństwem (#potrzebne / #pozostały). Przykład: jeśli k = 5 I n = 100, to bierzesz pierwszy element z prob 5/100. Jeśli skopiujesz ten, to wybierasz następny z prob 4/99; ale jeśli nie wziąłeś pierwszego, prob to 5/99.
  • O (K log k) lub O (k2): Zbuduj posortowaną listę indeksów k (liczby w {0, 1, ..., n-1}) wybierając losowo liczbę = 43, to dodajesz do niego 1. Więc jeśli twój drugi wybór to 50, następnie dodajesz do niego 1 i masz {43, 51}. Jeśli następny wybór to 51, dodajesz2 to it to get {43, 51, 53}.

Oto jakiś pseudopyton-

# Returns a container s with k distinct random numbers from {0, 1, ..., n-1}
def ChooseRandomSubset(n, k):
  for i in range(k):
    r = UniformRandom(0, n-i)                 # May be 0, must be < n-i
    q = s.FirstIndexSuchThat( s[q] - q > r )  # This is the search.
    s.InsertInOrder(q ? r + q : r + len(s))   # Inserts right before q.
  return s 

Mówię, że złożoność czasowa jest O (k2) lub O (K log k), ponieważ zależy to od tego, jak szybko możesz wyszukać i wstawić do kontenera s. Jeśli s jest zwykłą listą, jedna z tych operacji jest liniowa, a otrzymasz k^2. Jeśli jednak chcesz zbudować s jako zrównoważone drzewo binarne, możesz wyjść O (K log K) czas.

 0
Author: Tyler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:24

Dwa rozwiązania chyba nie pojawiają się tutaj - korespondencja jest dość długa i zawiera kilka linków, jednak nie sądzę, aby wszystkie posty odnosiły się do problemu wybrania subst elementów K ze zbioru N elementów. [Przez "zbiór" odwołuję się do terminu matematycznego, tzn. wszystkie elementy pojawiają się raz, kolejność nie jest ważna].

Sol 1:

//Assume the set is given as an array:
Object[] set ....;
for(int i=0;i<K; i++){
randomNumber = random() % N;
    print set[randomNumber];
    //swap the chosen element with the last place
    temp = set[randomName];
    set[randomName] = set[N-1];
    set[N-1] = temp;
    //decrease N
    N--;
}

To wygląda podobnie do odpowiedzi, którą dał daniel, ale w rzeczywistości jest zupełnie inna. To jest O (k) czas uruchomienia.

Inny rozwiązaniem jest użycie matematyki: rozważmy indeksy tablicy jako Z_n i tak możemy wybrać losowo 2 liczby, x, które są współmierne do n, tzn. chhose gcd (x,n)=1, i inne, a, które są "punktem początkowym" - wtedy szereg: A % n,A+x % N, a+2*x % n,...a+(k-1) * x % n jest ciągiem odrębnych liczb (o ile K

 0
Author: user967710,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-04 12:38:57