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?
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ć.
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..."
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
.
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.
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ć:
-
W zależności od tego, jak wrażliwe są Twoje dane, sugeruję użycie jakiejś metody mieszania, aby wymieszać losowe ziarno liczb. Aby zapoznać się z dobrym studium przypadku, zobacz Jak nauczyliśmy się oszukiwać w pokera online (ale ten link jest 404 od 2015-12-18). Alternatywne adresy URL (znalezione przez wyszukiwarkę Google na tytule artykułu w podwójnych cudzysłowach) to:
- Jak nauczyliśmy się oszukiwać w Internecie Poker - najwyraźniej oryginalny wydawca.
- Jak nauczyliśmy się oszukiwać w pokera online
-
Jak nauczyliśmy się oszukiwać w pokera online
Wektor jest zsynchronizowany. Jeśli to możliwe, użyj ArrayList zamiast poprawić wydajność.
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ć.
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.
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));
}
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.
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
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