【问题标题】:Generate unique random number on each call在每次调用时生成唯一的随机数
【发布时间】:2013-10-01 01:00:30
【问题描述】:

在一次采访中,我被要求编写一个方法,该方法每次调用时都会生成唯一的 5 位随机数。例如:如果我调用该方法并得到 22222,那么在下一次调用中我不应该得到 22222。

我写了如下代码:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

public class RandomNumberGen {


    private static ArrayList arr=new ArrayList();
    private static int k=-1;
    public RandomNumberGen(){
        for (int i=10000;i<99999;i++){
            arr.add(i);
        }
        Collections.shuffle(arr);
    }
    public static void main(String[] args) {
        for(int m=0;m<10;m++){
            try {
                System.out.println(new RandomNumberGen().randomNumbermethod());
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }


    }
    public Integer randomNumbermethod() throws Exception{
        k++;
        if(k>=arr.size()){
            throw new Exception("No more number available");
        }else return (Integer) arr.get(k);
    }

}

答案已被接受,但现在我被要求避免内存浪费。 我的问题在这里,你可以看到我只使用了 10 个数字。所以 arraylist 占用的其余空间是内存浪费。有没有一种方法可以在不使用额外内存的情况下实现同样的事情。 我的意思是,每次调用都可以使用哪个唯一编号,这样就不会浪费这么多内存。

【问题讨论】:

  • 如果你只想要 10 个值,你可以保留一个已经选择的值的集合并检查随机值不在其中。对于更多的值,这变得越来越可怕,但 10 就可以了
  • Arraylist 当然可以用于这样的集合,但某种集合(如哈希集)可能会更有效
  • @sp00n 这个问题与唯一性无关
  • @sp00m 我看到了那个帖子,但我的问题与这种方法浪费内存有关

标签: java arrays collections random


【解决方案1】:

启示:

  • 为了避免两次返回相同的值,您需要跟踪您已经生成了哪些数字。这可能会非常消耗内存。
  • 您最终会用完数字。您可以跟踪您返回的多少个数字(或仍有多少个可用)并识别您是否用完了数字,而不是继续搜索未使用的随机数。

生成的数字可以在集合(集合)中进行跟踪。这意味着每个号码有 32 位的开销(在跟踪可用或生成的号码时)加上收集开销。另一种可能性是使用 boolean-array 并标记已使用的插槽。同样,这是一个开销,因为布尔值通常存储为 32 位值。
但是有一种更便宜的方式来存储布尔值:作为整数中的压缩位。这就是java.util.BitSet 所做的,所以每个布尔值将占用一位。

BitSet 的解决方案并跟踪可用号码的数量:

public class RandomNumbers {

    private final Random random = new Random();
    private final BitSet used = new BitSet();
    private final int min = 10000;
    private final int max = 99999;
    private final int numbersAvailable = max - min + 1;

    public static void main (String[] args) {

        RandomNumbers randomNumbers = new RandomNumbers();
        for (int i = 0; i < 100; i++) {
            System.out.println(randomNumbers.nextRandom());
        }
    }

    public int nextRandom () throws NoSuchElementException {

        while (numbersAvailable > 0) {
            int rnd = min + random.nextInt(max - min + 1);
            if (!used.get(rnd)) {
                used.set(rnd);
                numbersAvailable--;
                return rnd;
            }
        }
        throw new NoSuchElementException();
    }
}

【讨论】:

  • 这应该是正确的答案。在我的 VM(Linux 上的 Oracle 1.7 64 位)中,分析器显示 BitSet 的内存消耗少于 50K。另外,虽然HashSetBitSet 都提供了摊销的常量查找/插入时间。 BitSet get / set 操作围绕HashSet get / add 运行。如果我是面试官,我会很高兴看到程序员使用BitSet 来解决这类问题。
【解决方案2】:

您的方法确实非常适合创建大量唯一值,但是如果您只创建少量唯一值,则只需跟踪使用的值以保证唯一性会更有效

import java.util.Collection;
import java.util.HashSet;
import java.util.Random;

public class UniqueRandom {

    static Random rnd=new Random();
    
    public static void main(String args[]){
        Collection<Integer> alreadyChosen = new HashSet<Integer>();
        for(int i=0;i<10;i++){
            System.out.println(getNextUniqueRandom (alreadyChosen));
        }
    }
    
    
    public static int getNextUniqueRandom(Collection<Integer> alreadyChosen){
        if (alreadyChosen.size()==90000){ //hardcoded 5 figure numbers, consider making a variable
             throw new RuntimeException("All 5 figure IDs used");
        }


        boolean unique=false;
        int value=0;
        while(unique==false){
            value=rnd.nextInt(90000)+10000;
            unique=!alreadyChosen.contains(value);
        }
        alreadyChosen.add(value);
        return value;
    }
    
}

当只需要一小部分可用范围时,这种方法非常有效,但随着碰撞变得越来越普遍,这种方法变得越来越慢。您应该选择的具体实现在很大程度上取决于您需要获得多少值。

注意事项

  • 如前所述,随着值的增加,这将变得非常非常慢 选择,应该让最终用户清楚,甚至更好;多次调用后更改算法

【讨论】:

  • 它也返回 4 位和 3 位数字。
  • @AnujKumarJha 已调整为只返回 5 位唯一数字
  • 如果你已经使用了所有的随机数,方法将无限循环,如果有机会找到一个新的,你可以检查alreadyChosen.size()
  • 甚至更好:一旦alreadyChosen 包含所有数字的 50%,则切换到尚未选择的数字的随机列表并每次弹出一个数字,直到列表为空。速度更快,内存消耗减半。
  • @killer_PL 如果我是真的写这个,我会写它以便它在(比如说)10000 次调用之后改变它的方法,但是对于一个 SO 答案来说这一切都太复杂了。但是,我已经更明确地说明了这些观点
【解决方案3】:
private static int number = 10000;
public int getNextUniqueRandomNumber() {
   return number++;
}

【讨论】:

    【解决方案4】:

    看起来很简单。一个更简单且内存使用量更少的解决方案是创建一个包含您想要的所有数字的集合,如下所示:

    Random random = new Random();
    Set<Integer> randomNumbers = new HashSet<Integer>(10);
    while(randomNumbers.size() < 10)
        randomNumbers.add( new Integer(random.nextInt(89999) + 10000) );
    

    并查看所有内容:

    for(Integer randomNumber : randomNumbers){
        System.out.println(randomNumber);
    }
    

    这将保证集合属性的唯一性并大大提高您的内存使用率。

    【讨论】:

    • 假设您事先知道需要多少个 ID,这确实很好
    【解决方案5】:

    只是

    (int)(Math.random()*89999)+10000
    

    编辑后:(只是在编辑之前不理解)-您可以将生成的数字放入HashSet,然后随机检查集合是否包含新数字(如果您多次使用它会很慢,但我认为这个如果您不需要很多数字,这是一个很好的解决方案。

    根据我的评论:在超过大约 50% 的数字后,我会创建一个剩余数字的集合以供选择,与您的相同,但您应该在课堂上记录,它可以在 50% 的结果使用后冻结片刻并给出能够将此因素设置为客户端。

    也许有更好的方法,取决于生成的数字中必须有“多少随机性”(例如序列生成器的数学方法)

    【讨论】:

    • 我只需要int/integer number。而且这个方法可以多次调用,直到所有5位数字都结束。我只是举了一个例子,调用它10次来显示ememry浪费。跨度>
    • 所以我会选择一个可能的结果人口因素。在超过大约 50% 的数字后,我会创建一个剩余数字的集合来选择,和你的一样(你可以垃圾现有的集合)。
    • 修改后的答案并转换为整数
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多