从0到9中获取四个唯一的随机数字的最佳方法是什么?

8

我想生成四个随机数字,范围在0到9之间。使用Java的Random类可以轻松生成四个随机数字。

    Random random = new Random();

    int numbers[] = new int[4];

    for(int i=0;i<4;i++){

        numbers[i] = random.nextInt(10);

    }

通过这种方法,我可以轻松地得到一个由四个数字组成的数组,例如93694702等。在这种情况下,四个数字中可能会有重复的数字,而我不希望出现这样的重复数字。
在这里,我希望上述数组中的所有四位数字都是唯一的,以便我可以得到像95431234等输出。
为此,我考虑了以下方法。
  1. 生成一个随机数并将其分配为第一个数字。
  2. 生成一个随机数,并检查是否与第一个数字不同,如果不同,则分配为第二个数字;否则重新生成随机数并重复此过程。
是否有比以上方法更好的方法,可以让我轻松快速地获得四个唯一的随机数字?
欢迎提出任何建议。

随机数的顺序重要吗?它们总是可以升序排列,还是也需要随机呢? - chux - Reinstate Monica
1
事实上,那里给出的答案适用于这里 - 如果您想要域的大部分(这里是40%),则洗牌很好,但如果只是一小部分,则不是。原因是在后一种情况下,您会洗很多您根本不想要的数字(甚至多达20倍)。 - MSalters
https://stackoverflow.com/questions/8116872/generate-random-numbers-in-array/8116947#8116947 - paxdiablo
5个回答

32

您可以使用Collections.shuffle

// generate a List that contains the numbers 0 to 9
List<Integer> digits = IntStream.range(0,10).boxed().collect(Collectors.toList());
// shuffle the List
Collections.shuffle (digits);
// take the first 4 elements of the List
int numbers[] = new int[4];
for(int i=0;i<4;i++){
    numbers[i] = digits.get(i);
}

4
@SagarGautam 我认为你应该接受这个答案,它显然比你所接受的那个答案更好。 - lexicore
7
不一定,因为这个答案的运行时间为“n”,其中“n”是可供选择的元素数量,而被接受的答案的期望运行时间远远低于“n”。 - SpaceTrucker
1
@SagarGautam,你不理解什么?如果你不熟悉Java 8 Streams,第一行可能会让人困惑,但你可以用普通的for循环替换它:List<Integer> digits = new ArrayList<>(); for (int i = 0; i < 10; i++) digits.add(i); - Eran
1
@SagarGautam 这段代码创建了一个数字0..9的列表,然后随机打乱它并取出前4个元素,从而给您4个随机数字。 - lexicore
3
@lexicore并没有说这是“显然更好的答案”,这只是一个更完整的答案。在很多方面,Amer的算法比Eran的更优秀(尽管Eran的更容易被重复使用)。 - corsiKa
显示剩余4条评论

9
你可以使用Set实现这个功能,核心思想是生成随机数,将其放入集合中,并且重复此过程直到集合中有4个元素。完成后,你会在集合中获得4个独特的随机数。
Set<Integer> randomSet = new HashSet<>();

while(randomSet.size() <4) 
   randomSet.add //add your generated random number

这是一个不错的问题,但是Set会按照添加的顺序排序数字,例如1234、4567、6789。如何避免这种情况? - Sagar Gautam
@SagarGautam 如果你想保持顺序,你必须使用TreeSet而不是HashSet。 - Amer Qarabsa
4
集合本身并没有顺序。 - lexicore
@AmerQarabsa 我以前不太了解Set,并且我一直假定所有的集合都具有这个属性。非常感谢。 - Sagar Gautam
5
如果你使用LinkedHashSet替代HashSet,实际上你也会得到随机顺序。 - escitalopram
显示剩余2条评论

7
如果您可以创建一个快速的函数f,将自然数映射到满足您要求的数字集合中,那么您只需要生成一个随机数。此时,您的运行时间受f的限制。只要您能创建一个相当快的f,这是最有效的方法。
最简单的解决方案是将所有满足条件的数字放入一个数组中,并将随机数作为索引创建到该数组中。-> O(1)

1
这将是时间最优的,而不是空间最优的。我们甚至不知道“最优”应该意味着什么。 - lexicore
1
@lexicore的“轻松快捷”意味着时间最优。 - David K
在这些可能的随机序列中只有5040个,将它们存储为byte[],并在函数f中使用索引应该可以起到作用,而且不会浪费太多空间。例如,在Oracle HotSpot JVM上,包括头和引用开销,大约需要20176字节。 - Jörg W Mittag
@JörgWMittag:这个表的理论最小值为8359字节。如果您可以将每个“数字集”存储为short,则只需要10080字节。 - Mooing Duck

7

正如您所见,有许多方法可以达到您的目标。这是我的建议:

Random random = new Random();

// prepare all valid digits
List<Integer> from = new ArrayList<Integer>(Arrays.asList(0,1,2,3,4,5,6,7,8,9));

// take set in an random order
int numbers[] = new int[4];
for(int i = 0; i < numbers.length; i++){
    numbers[i] = from.remove (random.nextInt (from.size()));
}

for (int num : numbers) {
   System.out.println(num); // when you prefer this
}

0

编辑

由于Collections.shuffle也使用Fisher-Yates算法。但是这个变体是随机选择序列的起始点。 就像洗牌一副牌并从中间选择4张牌与洗牌一副牌并从顶部选择4张牌一样。

这里提到了Fisher-Yeats洗牌算法的一个变体,链接在这里https://softwareengineering.stackexchange.com/questions/199644/efficient-way-to-shuffle-objects

    public int[] shuffle() {
        int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        Random r = new Random();
        for (int i = a.length; i > 1; i--) {
            swap(a, i - 1, r.nextInt(i));
        }

        int[] result = new int[4];
        // Variant :: Randomly choosing the starting point of the 
        // sequence, since we need only four number.
        System.arraycopy(a, r.nextInt(a.length - 4), result, 0, 4);
        return result;
    }

    private void swap(int[] a, int i, int i1) {
        int temp = a[i];
        a[i] = a[i1];
        a[i1] = temp;
    }

Reference:https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle


基本上与@Eran的答案相同。这正是Collections.shuffle的实现方式。 - lexicore
这是一个变量,我也是随机选择起始点。就像洗牌一样,然后从中间选择4张牌与从顶部选择4张牌相比。 - mirmdasif
6
这句话的意思是:“这就像洗一副牌,然后从中间选出4张牌与从顶部选出4张牌的区别。”那为什么这个区别很重要呢? - lexicore
我会分享一个图表 :) - mirmdasif
请问,是哪个图表? - lexicore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接