2011-12-04 19 views
17

Quindi dire che hoRaccogliere più elementi casuali da una lista in Java

List<String> teamList = new LinkedList<String>() 
teamList.add("team1"); 
teamList.add("team2"); 
teamList.add("team3"); 
teamList.add("team4"); 
teamList.add("team5"); 
teamList.add("team6"); 

Esiste un modo semplice di scegliere ... diciamo 3 i 6 elementi in questa lista in modo randomizzato senza prendere lo stesso elemento due volte (o più volte)?

risposta

47

Prova questo:

public static List<String> pickNRandom(List<String> lst, int n) { 
    List<String> copy = new LinkedList<String>(lst); 
    Collections.shuffle(copy); 
    return copy.subList(0, n); 
} 

Sto assumendo che non vi siano elementi ripetuti nella lista di input, anche io prendo la precauzione di mischiare una copia per lasciare indisturbata la lista originale. Si chiama così:

List<String> randomPicks = pickNRandom(teamList, 3); 
+1

Questa è davvero una buona idea! Grazie mille! – Yokhen

+1

protezione utilizzabile da IndexOutOfBoundsException: return n> copy.size()? copy.subList (0, copy.size()): copy.subList (0, n); – pawegio

+4

Mischiare l'intera lista quando hai solo bisogno di 3 elementi è molto dispendioso per elenchi di grandi dimensioni. – Eyal

2

Usa

Collections.shuffle(teamList); 

per randomizzare l'elenco, quindi rimuovere le squadre, uno alla volta dalla lista tramite teamList.remove(0);

Ad esempio:

List<String> teamList = new LinkedList<String>(); 
    teamList.add("team1"); 
    teamList.add("team2"); 
    teamList.add("team3"); 
    teamList.add("team4"); 
    teamList.add("team5"); 
    teamList.add("team6"); 

    java.util.Collections.shuffle(teamList); 

    String[] chosen3 = new String[3]; 
    for (int i = 0; i < chosen3.length && teamList.size() > 0; i++) { 
    chosen3[i] = teamList.remove(0); 
    } 
+0

davvero una buona idea supponendo che va bene al cambiamento l'ordine. Altrimenti puoi farti una copia locale (un po 'più cara, ma funziona). Personalmente userei 'remove (teamList.size() - 1)' in modo che se l'implementazione cambia in una lista diversa ha la più alta possibilità di essere efficiente, ma 'remove (0)' funziona anche :) – corsiKa

+0

Grazie, che è utile, ma sto cercando di mantenere intatto l'elenco originale. Basta scegliere l'elemento ma non cancellarlo in modo che io possa averlo per un uso successivo. Immagino che una soluzione alternativa potrebbe essere quella di creare un secondo elenco con tutti gli elementi dell'elenco originale al suo interno e quindi fare ciò che si suggerisce di fare. Grazie comunque :) – Yokhen

+1

Lol, abbiamo appena detto in parte la stessa cosa. Grazie glowcoder. – Yokhen

6

creare una serie di int, e inserire numeri casuali tra 0 e la lunghezza dell'elenco meno uno in un ciclo, mentre la dimensione dell'insieme non è uguale al numero desiderato di elementi casuali. Attraversa il set e seleziona gli elementi dell'elenco come indicato dai numeri nell'insieme. In questo modo manterresti la tua lista originale intatta.

+0

funzionerà anche. 1+ –

+1

Un buon approccio a meno che non si vogliano selezionare 999 elementi da un elenco di 1000 elementi :) – waxwing

+1

Questa è un'ottima idea :) ma tenere traccia di un elenco alternativo di indici può risultare un po 'complicato. Ma grazie comunque :) – Yokhen

5

L'approccio shuffle è il più idiomatico: dopo di ciò, i primi elementi K sono esattamente ciò di cui avete bisogno.

Se K è molto inferiore alla lunghezza della lista, potresti voler essere più veloce. In questo caso, scorrere l'elenco, scambiando a caso l'elemento corrente con se stesso o con qualsiasi elemento dopo di esso. Dopo l'elemento K-th, fermati e restituisci il prefisso K: sarà già perfettamente mescolato e non dovrai preoccuparti del resto dell'elenco.

(ovviamente, si desidera utilizzare ArrayList qui)

2

Tutte le buone idee, ma la mischia è costosa. Il metodo più efficiente (IMO) farebbe un ciclo controllato dal conteggio e selezionerebbe un intervallo casuale compreso tra 0 e n; dove n inizialmente è uguale alla lunghezza della tua lista.

In ogni iterazione del ciclo si scambia l'elemento selezionato con l'elemento su n-1 dell'elenco e si diminuisce di uno. In questo modo eviti di selezionare lo stesso elemento due volte e non devi tenere un elenco separato di elementi selezionati.

+0

sembra una buona alternativa, ma mi richiederà un po 'più di lavoro. Grazie comunque :) – Yokhen

+0

Questo è equivalente alla soluzione di alf, giusto? Tranne che mette gli elementi all'inizio. – waxwing

4

È inoltre possibile utilizzare reservoir sampling.

ha il vantaggio che non è necessario conoscere la dimensione della lista sorgente in anticipo (ad esempio, se si è data un Iterable invece di un List.) Inoltre è efficace anche quando la lista sorgente non è random- accesso, come il LinkedList nel tuo esempio.

+0

Grazie per la risposta. Comunque potresti spiegare più in dettaglio? Non ho molta familiarità con il metodo che hai appena spiegato. Grazie. – Yokhen

+0

@Yokhen, l'idea è che se (per esempio) stai scegliendo 3 elementi, e hai appena considerato il 40esimo elemento nella lista di input, a quel punto hai scelto 3 di 40 elementi in modo che l'ultimo elemento abbia un 3/40 possibilità di essere nell'array di output. Se osservate lo pseudocodice nell'articolo di Wikipedia, vedrete che è l'ultima operazione ('r ← random (0 .. i); if (r finnw

+0

+1 Eccellente alternativa. – trashgod

0

Ecco un modo per farlo utilizzando flussi di Java, senza dover creare una copia della lista originale o mischiare esso:

public static List<String> pickRandom(List<String> list, int n) { 
    if (n > list.size()) { 
     throw new IllegalArgumentException("not enough elements"); 
    } 
    Random random = new Random(); 
    return IntStream 
      .generate(() -> random.nextInt(list.size())) 
      .distinct() 
      .limit(n) 
      .mapToObj(list::get) 
      .collect(Collectors.toList()); 
} 

Nota: Si può diventare inefficiente quando n è troppo vicino alla lista dimensione per liste enormi.

0
int[] getRandoms(int[] ranges, int n, int[] excepts) { 
    int min = ranges[0]; 
    int max = ranges[1]; 

    int[] results = new int[n]; 
    for (int i = 0; i < n; i++) { 
     int randomValue = new Random().nextInt(max - min + 1) + min; 
     if (ArrayUtils.contains(results, randomValue) || ArrayUtils.contains(excepts, randomValue)) { 
      i--; 
     } else { 
      results[i] = randomValue; 
     } 
    } 
    return results; 
} 

classe util

public static class ArrayUtils { 

    public static boolean contains(int[] array, int elem) { 
     return getArrayIndex(array, elem) != -1; 
    } 

    /** Return the index of {@code needle} in the {@code array}, or else {@code -1} */ 
    public static int getArrayIndex(int[] array, int needle) { 
     if (array == null) { 
      return -1; 
     } 
     for (int i = 0; i < array.length; ++i) { 
      if (array[i] == needle) { 
       return i; 
      } 
     } 
     return -1; 
    } 
} 

utilizzando

int[] randomPositions = getRandoms(new int[]{0,list.size()-1}, 3, new int[]{0,1}); 

sarà casuali 3 articoli nel proprio elenco eccetto collo 0 e la voce 1

Problemi correlati