【问题标题】:java.util.Random and recursion in JavaJava 中的 java.util.Random 和递归
【发布时间】:2015-04-13 21:57:34
【问题描述】:

首先我想说这是一个比较笼统的问题;与我给出的具体示例无关,而只是一个概念性主题。

示例 #1: 我正在使用 UUID.java 创建一个真正的随机字符串。假设我永远不想生成相同的 UUID。这里有一个想法: (假设我在顶部保存/加载列表 - 这不是重点)

Gist URL (I'm new to StackExchange- sorry!)

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class Example {

    /**
     * A final List<String> of all previous UUIDs generated with
     * generateUniqueID(), turned into a string with uuid.toString();
     */
    private static final List<String> PREVIOUS = new ArrayList<String>();

    /**
     * Generates a truly unique UUID.
     * 
     * @param previous
     *            A List<String> of previous UUIDs, converted into a string with
     *            uuid.toString();
     * @return a UUID generated with UUID.randomUUID(); that is not included in
     *         the given List<String>.
     */
    public static UUID generateUniqueID(List<String> previous) {
        UUID u = UUID.randomUUID();
        if (previous.contains(u.toString())) {
            return generateUniqueID(previous);
        }
        return u;
    }

    /**
     * Generates a truly unique UUID using the final List<String> PREVIOUS
     * variable defined at the top of the class.
     * 
     * @return A truly random UUID created with generateUniqueID(List<String>
     *         previous);
     */
    public static UUID generateUniqueID() {
        UUID u = generateUniqueID(PREVIOUS);
        PREVIOUS.add(u.toString());
        return u;
    }

}

示例 #2:好吧,也许 UUID 是一个不好的示例,所以让我们使用 Random 和 double。这是另一个例子:

Gist URL

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Example2 {

    /**
     * A final List<Double> of all previous double generated with
     * generateUniqueDouble(), turned into a string with Double.valueOf(d);
     */
    private static final List<Double> PREVIOUS = new ArrayList<Double>();

    /**
     * The RANDOM variable used in the class.
     */
    private static final Random RANDOM = new Random();

    /**
     * Generates a truly unique double.
     * 
     * @param previous
     *            A List<Double> of previous doubles, converted into a Double
     *            with Double.valueOf(d);
     * @return a UUID generated with UUID.randomUUID(); that is not included in
     *         the given List<Double>.
     */
    public static double generateUniqueDouble(List<Double> previous) {
        double d = RANDOM.nextDouble();
        if (previous.contains(Double.valueOf(d))) {
            return generateUniqueDouble(previous);
        }
        return d;
    }

    /**
     * Generates a truly unique double using the final List<Double> PREVIOUS
     * variable defined at the top of the class.
     * 
     * @return A truly random double created with generateUnique(List<Double>
     *         previous);
     */
    public static double generateUnique() {
        double d = RANDOM.nextDouble();
        PREVIOUS.add(Double.valueOf(d));
        return d;
    }

}

要点:这是做这种事情的最有效方法吗?请记住,我给了你一些例子,所以它们很模糊。最好我不想为此使用任何库,但如果它们确实在效率上有很大差异,请告诉我。

请在回复中告诉我你的想法:)

【问题讨论】:

  • 你要生成多少东西?
  • @Aify 实际上我很可能会产生很多。
  • @Aify 抱歉回答含糊,没有意识到输入是发送。当用户从指定服务器下载包时,我使用它来生成一次性密钥。
  • 首先想到的问题是你最终会耗尽内存,因为你将所有内容都存储在一个列表中 - 特别是如果你要生成很多内容时跨度>
  • 如果你生成了很多这样的东西,而且它们绝对不能碰撞,为什么不让它们顺序排列呢?

标签: java recursion


【解决方案1】:

我建议您将生成的 ID 设为序列号,而不是双精度数或 uuid。如果您希望它们在最终用户看来是随机的,请在 base64 中显示数字的 sha1。

【讨论】:

  • 感谢您的回答,这很可能是我将使用的,而不仅仅是显示序号。只是一个小问题-在您解释的方法中询问显示数字的最有效方法是否“离题”?或者,我应该做更多研究并提出另一个问题吗?
  • 那是另一个问题。有后续问题很好,但如果它们是不同的问题,请单独发布。
  • 这是个好主意。不确定是否符合要求;取决于可以猜测未来值是否重要。
  • 我不得不将推荐答案改为 Marco13 的,不是因为我不喜欢你的答案,而是因为他提供了 很多 示例,不过还是感谢你的帮助!跨度>
  • 我会赞成你的回答,但我是该网站的新手,没有足够的声望点,抱歉!
【解决方案2】:

在 cmets 中已经讨论了一些要点。在这里总结和阐述它们:

您不太可能两次创建相同的double 值。 There are roughly 7*1012 different double values(假设随机数生成器可以提供“大部分”)。对于 UUID,两次创建相同值的机会甚至更低,因为 there are 2122 different UUIDs。如果您创建了足够多的元素以产生不可忽略的碰撞机会,那么无论如何您都会耗尽内存。

所以这种方法在实践中没有意义。


但是,从纯理论的角度来看:

性能

在此操作中使用List 不是最佳选择。对您而言,“最佳情况”(也是迄今为止最常见的情况)是新元素包含在列表中。但是为了检查元素是否被包含,这是最坏的情况:你必须检查列表中的每一个元素,只是为了检测到新元素还没有出现。据说这是线性的complexity,或简称为O(n)。您可以使用不同的数据结构来更快地检查是否包含元素,即在 O(1) 中。例如,您可以替换该行

private static final List<Double> PREVIOUS = new ArrayList<Double>();

private static final Set<Double> PREVIOUS = new HashSet<Double>();

性能和正确性

(这里一般指递归方式)

性能

从性能的角度来看,当递归很容易被迭代解决方案替换时,您不应该使用递归。在这种情况下,这将是微不足道的:

public static double generateUniqueDouble(List<Double> previous) {
    double d = RANDOM.nextDouble();
    while (previous.contains(d)) {
        d = RANDOM.nextDouble();
    }
    PREVIOUS.add(d);
    return d;
}

(它可以写得更紧凑一点,但现在没关系)。

正确性

这更微妙:当有很多递归调用时,你可能会得到一个StackOverflowError。所以你应该从不使用递归除非你可以证明递归会结束(或者更好:它会在“几步之后”结束)。

但这是你的主要问题:

算法有缺陷。您无法证明它能够创建新的随机数。对于double(或UUID)值,即使是单个新元素已经包含在PREVIOUS 元素集合中的可能性也非常低。但它不是零。没有什么可以阻止随机数生成器连续数万亿次无限期地创建随机数0.5


(再次:这些纯粹是理论上的考虑。但与实践相距不远,因为它们乍一看:如果您没有创建随机的double 值,而是随机的byte 值,那么,在 256 之后调用,将不会返回“新”值 - 您实际上会收到 StackOverflowError...)

【讨论】:

  • 感谢您非常详细的评论,对我帮助很大,我相信它会帮助其他人!我不敢相信我忘记了一个while循环。不过,我有一个小问题。这一行:while (previous.contains(d)) 显然在条件为真时运行循环,但是,我们不需要相反的while (!previous.contains(d)) 吗?无论如何,再次感谢您的广泛回答,我将修改我的策略:)
  • 也许我是lockng咖啡,但我认为while (previous.contains(d))应该是正确的:只要生成的数字包含在集合中,就应该创建一个新的。
  • 啊,我的坏伙伴,重读一遍。昨晚真的很累,对不起
【解决方案3】:

使用哈希表比使用列表更好。生成您的候选值,检查哈希表中的冲突,如果没有冲突则接受它。如果使用列表,则生成新值是 O(n) 操作。如果使用哈希表,生成新值是O(1) 操作。

【讨论】:

  • 嗯,我肯定会为此更多地研究哈希表。然而,当你说“如果没有碰撞就接受它”,如果它是一个碰撞会发生什么?我应该像在示例中那样使用递归吗?
  • 如果发生冲突,则生成一个新的随机值并重试。使用循环这样做会比递归更有效,但老实说,我怀疑如果你做计时你会看到差异。
猜你喜欢
  • 1970-01-01
  • 2011-05-04
  • 2019-08-04
  • 2010-12-08
  • 2011-07-13
  • 1970-01-01
  • 2020-06-28
  • 2013-06-20
  • 2014-12-25
相关资源
最近更新 更多