2011-12-04 21 views
17

Así que decir que tengoEscoja múltiples elementos al azar de una lista en 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"); 

¿Hay una manera sencilla de recoger ... digamos 3 sobre los 6 elementos en esta lista de manera aleatoria sin recoger la misma elemento dos veces (o más veces)?

Respuesta

47

Prueba esto:

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); 
} 

Estoy asumiendo que no existen elementos que se repiten en la lista de entrada, también tomo la precaución de barajar una copia para dejar la lista original sin ser molestada. Se llama así:

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

¡De hecho, es una muy buena idea! ¡Muchas gracias! – Yokhen

+1

protección utilizable de IndexOutOfBoundsException: return n> copy.size()? copy.subList (0, copy.size()): copy.subList (0, n); – pawegio

+4

Mezclar toda la lista cuando solo necesita 3 elementos es un desperdicio de listas grandes. – Eyal

2

Uso

Collections.shuffle(teamList); 

para aleatorizar la lista, a continuación, quitar los equipos de uno a la vez de la lista a través de teamList.remove(0);

Por ejemplo:

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

Una muy buena idea asumir que está bien para el cambio el orden. De lo contrario, puede hacerse una copia local (un poco más cara, pero funciona). Yo personalmente usaría 'remove (teamList.size() - 1)' de modo que si la implementación cambia a una lista diferente tiene la mayor posibilidad de ser eficiente, pero 'remove (0)' también funciona :) – corsiKa

+0

Gracias, eso es útil, pero estoy tratando de mantener la lista original intacta. Simplemente elija el elemento pero no lo borre para que pueda tenerlo para usarlo más adelante. Supongo que una solución podría ser crear una segunda lista con todos los elementos de la lista original y luego hacer lo que sugiere hacer. Gracias de todos modos :) – Yokhen

+1

Lol, acabamos de decir en parte lo mismo. Gracias glowcoder. – Yokhen

6

crear un conjunto de enteros, y coloque los números aleatorios entre 0 y la longitud de la lista menos uno en un bucle, mientras que el tamaño del conjunto no es igual al número deseado de elementos aleatorios. Vaya a través del conjunto y seleccione los elementos de la lista como lo indican los números en el conjunto. De esta manera mantendría intacta su lista original.

+0

que funcionará también. 1+ –

+1

Un buen enfoque, a menos que desee elegir 999 elementos de una lista de 1000 elementos :) – waxwing

+1

Esa es una muy buena idea :) pero hacer un seguimiento de una lista alternativa de índices puede ser un poco complicado. Pero gracias de todos modos :) – Yokhen

5

El enfoque shuffle es el más idiomático: después de eso, los primeros elementos K son exactamente lo que necesita.

Si K es mucho menor que la longitud de la lista, es posible que desee ser más rápido. En este caso, itere a través de la lista, intercambiando aleatoriamente el elemento actual consigo mismo o con cualquiera de los elementos posteriores. Después del elemento K-ésimo, detén y devuelve el prefijo K: ya estará perfectamente barajado, y no necesitas preocuparte por el resto de la lista.

(obviamente, desea utilizar ArrayList aquí)

2

Todas las buenas ideas, pero barajar es caro. El método más eficiente (IMO) sería hacer un ciclo de recuento controlado y elegir un int aleatorio entre 0 y n; donde n inicialmente es igual a la longitud de su lista.

En cada iteración del ciclo, intercambia el elemento seleccionado con el elemento en n-1 en la lista y disminuye n en uno. De esta forma evitará elegir el mismo elemento dos veces y no tendrá que mantener una lista separada de los elementos seleccionados.

+0

que suena como una buena alternativa, pero me requerirá un poco más de trabajo. Gracias de todos modos :) – Yokhen

+0

Esto es equivalente a la solución de alf, ¿verdad? Excepto que pone los elementos al comienzo. – waxwing

4

También puede usar reservoir sampling.

Tiene la ventaja de que no necesita conocer el tamaño de la lista fuente de antemano (por ejemplo, si le dan un Iterable en lugar de List). También es eficiente incluso cuando la lista fuente no es aleatoria acceso, como el LinkedList en su ejemplo.

+0

Gracias por la respuesta. Sin embargo, ¿podría explicar más en detalle? No estoy familiarizado con el método que acabas de explicar. Gracias. – Yokhen

+0

@Yokhen, la idea es que si (por ejemplo) eliges 3 elementos, y que acabas de considerar el 40 ° elemento en la lista de entrada, en ese momento has elegido 3 de 40 elementos para que el último elemento tenga un 3/40 posibilidad de estar en la matriz de salida. Si miras el pseudocódigo en el artículo de Wikipedia, verás que esa es la última operación ('r ← random (0 .. i); if (r finnw

+0

+1 Excelente alternativa. – trashgod

0

Aquí es una forma de hacer que el uso de flujos de Java, sin tener que crear una copia de la lista original o arrastrando los pies que:

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: Puede llegar a ser ineficientes cuando n es demasiado cerca de la lista tamaño para listas enormes

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; 
} 

clase 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; 
    } 
} 

usando

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

será al azar 3 elementos de la lista, salvo el punto 0 y el punto 1

Cuestiones relacionadas