在Java中从列表中选择多个随机元素

在Java中从列表中选择多个随机元素,java,list,random,Java,List,Random,所以说我有 List<String> teamList = new LinkedList<String>() teamList.add("team1"); teamList.add("team2"); teamList.add("team3"); teamList.add("team4"); teamList.add("team5"); teamList.add("team6"); List teamList=newlinkedlist() 团队列表。添加(“团队1”)

所以说我有

List<String> teamList = new LinkedList<String>()
teamList.add("team1");
teamList.add("team2");
teamList.add("team3");
teamList.add("team4");
teamList.add("team5");
teamList.add("team6");
List teamList=newlinkedlist()
团队列表。添加(“团队1”);
团队列表。添加(“团队2”);
团队列表。添加(“团队3”);
团队列表。添加(“团队4”);
团队列表。添加(“团队5”);
团队列表。添加(“团队6”);
有没有一种简单的方法来挑选。。。以随机方式说出此列表中的6个元素中的3个,而不选择同一元素两次(或多次)?

使用

Collections.shuffle(teamList);
要随机化列表,请通过
teamList从列表中一次删除一个团队。删除(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);
  }
List teamList=newlinkedlist();
团队列表。添加(“团队1”);
团队列表。添加(“团队2”);
团队列表。添加(“团队3”);
团队列表。添加(“团队4”);
团队列表。添加(“团队5”);
团队列表。添加(“团队6”);
java.util.Collections.shuffle(团队列表);
String[]chosen3=新字符串[3];
对于(int i=0;i0;i++){
chosen3[i]=teamList.remove(0);
}

创建一组整数,将0到列表长度减1之间的随机数放入循环中,而集合的大小不等于所需的随机元素数。浏览集合,并按照集合中的数字选择列表元素。这种方法可以保持原始列表的完整性。

洗牌方法是最惯用的:在这之后,前K个元素正是您所需要的

如果K远小于列表的长度,您可能希望更快。在本例中,遍历列表,随机地将当前元素与其自身或其后的任何元素交换。在第K个元素之后,停止并返回K前缀:它已经被完全洗牌了,您不需要关心列表的其余部分

(显然,您希望在此处使用
ArrayList

尝试以下方法:

public static List<String> pickNRandom(List<String> lst, int n) {
    List<String> copy = new ArrayList<String>(lst);
    Collections.shuffle(copy);
    return n > copy.size() ? copy.subList(0, copy.size()) : copy.subList(0, n);
}
公共静态列表pickNRandom(列表lst,int n){
列表副本=新阵列列表(lst);
收藏。洗牌(复制);
返回n>copy.size()?copy.subList(0,copy.size()):copy.subList(0,n);
}
我假设输入列表中没有重复的元素,同时我也采取了洗牌副本的预防措施,以保持原始列表不受干扰。像这样使用它:

List<String> randomPicks = pickNRandom(teamList, 3);
List randomPicks=pickNRandom(团队列表,3);

所有的想法都很好,但洗牌很昂贵。更有效的方法(IMO)是执行计数控制循环,并选择0和n之间的随机整数;其中n最初等于列表的长度

在循环的每次迭代中,将选定项与列表中n-1处的项交换,并将n减1。这样可以避免两次拾取同一元素,并且不必保留所选项目的单独列表。

也可以使用


它的优点是,您不需要事先知道源列表的大小(例如,如果给您一个
Iterable
而不是
list
),而且即使源列表不是随机访问,它也是有效的,就像示例中的
LinkedList

下面是一种使用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());
}
公共静态列表pickRandom(列表列表,int n){
如果(n>list.size()){
抛出新的IllegalArgumentException(“元素不足”);
}
随机=新随机();
返回IntStream
.generate(()->random.nextInt(list.size()))
.distinct()
.限制(n)
.mapToObj(列表::get)
.collect(Collectors.toList());
}
注意:当
n
太接近大型列表的列表大小时,它可能会变得效率低下

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

除了第0项和第1项之外,它将随机选择列表中的3项,如果可以更改顺序,这是一个非常好的主意。否则,您可以自己制作一个本地副本(稍微贵一点,但它可以工作)。我个人会使用
remove(teamList.size()-1)
,这样,如果实现更改为其他列表,则效率最高,但是
remove(0)
也有效:)谢谢,这很有帮助,但我正在尝试保持原始列表的完整性。只需选择元素,但不要删除它,以便我可以将其用于以后的使用。我想一个解决办法是创建第二个列表,其中包含原始列表的所有元素,然后按照您的建议执行。无论如何,谢谢你:)哈哈,我们刚才说了一部分相同的话。谢谢glowcoder。@Yokhen实际上你可以得到一个从0到N-1的列表,其中
N=teamList.size()
,将其洗牌,并使用前K个数字作为原始列表的索引。这实际上是一个非常好的主意!非常感谢你!针对IndexOutOfBoundsException的可用保护:返回n>copy.size()?copy.subList(0,copy.size()):copy.subList(0,n);当您只需要3个元素时,对整个列表进行洗牌对于大型列表来说是非常浪费的。如果传入的列表是不可变的,洗牌将中断。@ArunavSanyal
集合中的几个方法将中断,如果您查看文档,它没有提到支持不可变性。Java API的构建并没有考虑到不可变性,可以肯定的是,任何修改集合的方法都希望集合是可变的。除非您想从1000个元素的列表中选择999个元素,否则这是一个很好的主意:),但跟踪备用索引列表可能会有点棘手。但无论如何还是要谢谢你:)@waxwing是的,那需要一段时间:-)一般来说,如果R,所需的随机项目数,大于列表长度的一半,从列表中删除N-R元素会更便宜,然后把随机生成的数据集中除了那些以外的所有数据都拿出来。嗯..我参加聚会迟到了..但我忍不住要问..这个解决方案如何防止在数据集中添加重复的数字?我可能会在
int
集合中获得重复的数字,并从主列表中更改相同的值两次。@TarunChauhan集合数据结构消除重复。在ot
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});