2011-12-04 124 views
17

所以說我有選擇從列表中選擇多個隨機元素在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"); 

有沒有撿一個簡單的方法......說了3此列表中隨機方式的6個元素,而不選擇相同元素兩次(或更多次)?

回答

47

試試這個:

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

我假設有在輸入列表中沒有重複的元素,也是我取的預防措施洗牌的原稿不受干擾。這就是所謂的:

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

這實際上是個不錯的主意! 非常感謝! – Yokhen

+1

IndexOutOfBoundsException的可用保護: return n> copy.size()? copy.subList(0,copy.size()):copy.subList(0,n); – pawegio

+4

當您只需要3個元素時,將整個列表進行整理對於大型列表來說是非常浪費的。 – Eyal

2

使用

Collections.shuffle(teamList); 

隨機化列表,然後通過teamList.remove(0);

刪除的球隊之一,在從列表中選擇時間。例如:

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

一個很好的主意假設它的好變化命令。否則,你可以讓自己成爲一個本地副本(有點貴,但它的工作原理)。我個人會使用'remove(teamList.size() - 1)',這樣如果實現更改爲不同的列表,它有效率最高,但remove(0)'也可以。:) – corsiKa

+0

謝謝,那是有幫助的,但我試圖讓原始列表完好無損。只需選擇該元素,但不要將其刪除,以便以後使用。 我想一個解決方法可能是創建第二個列表,其中包含原始列表的所有元素,然後執行您的建議。 謝謝你:) – Yokhen

+1

大聲笑,我們只是說了部分相同的事情。感謝glowcoder。 – Yokhen

6

創建一組整數的和將循環中的0和列表長度之間的隨機數字減1,而該集合的大小不等於期望數量的隨機元素。瀏覽集合,並按照集合中的數字指示選擇列表元素。這樣可以保持原始列表的完整性。

+0

也可以工作。 1+ –

+1

好方法,除非你想從1000元素列表中選擇999元素:) – waxwing

+1

這是一個非常好的主意:)但跟蹤備用列表的索引可能會有點棘手。 但是,無論如何謝謝你:) – Yokhen

5

shuffle方法是最習慣的:在此之後,第一個K元素正是你所需要的。

如果K遠小於列表的長度,您可能希望速度更快。在這種情況下,遍歷列表,隨機交換當前元素與其自身或其後的任何元素。在第K個元素之後,停止並返回K-前綴:它已經完全洗牌了,並且不需要關心列表的其餘部分。

(很明顯,你想使用ArrayList這裏)

2

所有的好主意,但洗牌是昂貴的。更高效的方法(IMO)將執行一個計數控制循環並在0和n之間選取一個隨機int;其中n最初等於列表的長度。

在循環的每次迭代中,您將所選項與列表中的n-1項交換,並將n減1。通過這種方式,您可以避免選取相同的元素兩次,而不必保留一個單獨的選定項目列表。

+0

聽起來像是一個很好的選擇,但它會需要我多一點工作。無論如何感謝:) – Yokhen

+0

這相當於阿爾夫的解決方案,對吧?除了他將元素放在開頭。 – waxwing

4

您也可以使用reservoir sampling

它的優點是您不需要事先知道源列表的大小(例如,如果給予Iterable而不是List)。即使源列表不是隨機的,訪問,如你的例子中的LinkedList

+0

感謝您的回答。但是,你能否詳細解釋一下?我對你剛剛解釋的方法很不熟悉。謝謝。 – Yokhen

+0

@Yokhen,這個想法是,如果(例如)你選擇了3件商品,並且你剛剛在輸入列表中考慮了第40件商品,那麼你已經選擇了40件商品中的3件,因此最後一件商品的商品尺寸爲3件/ 40個機會在輸出數組中。如果你看維基百科文章中的僞代碼,你會發現這是最後一個操作('​​r←random(0..i); if(r finnw

+0

+1非常好的選擇。 – trashgod

0

下面是一個使用Java流做這件事的方式,而無需創建原始列表的副本或洗牌它:

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

注:它可以成爲低效當n太靠近名單規模巨大的名單。

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

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

使用

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

它會在你的列表中隨機3個項目,除了項目0和項目1