簡體   English   中英

Java中的java.util.Random和遞歸

[英]java.util.Random and recursion in Java

首先,我想說的是更多的一般性問題。 不是一個與我給出的具體示例有關的,而是一個概念性的話題。

示例1:我正在使用UUID.java創建一個真正隨機的字符串。 假設我永遠都不想生成相同的UUID。 這是一種情況的想法:(假設我在頂部保存/加載列表-這不是重點)

要點URL(我是StackExchange的新手,對不起!)

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。 這是另一個例子:

要點網址

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

}

重點:這是做這樣的事情的最有效方法嗎? 請記住,我給您提供了示例,所以它們很模糊。 最好不要為此使用任何庫,但是如果它們確實在效率上有實質性差異,請讓我知道它們。

請讓我知道您在回應中的想法:)

我建議您將生成的ID設置為連續數字,而不要使用雙精度或uuid。 如果希望它們對最終用戶隨機出現,請在base64中顯示數字的sha1。

評論中已經討論了一些要點。 在這里總結和闡述它們:

您兩次創建相同的double值的可能性很小。 大約有7 * 10 12個不同的double (假設隨機數生成器可以傳遞“大多數”值)。 對於UUID,兩次創建相同值的機會更低,因為存在2122 不同的UUID 如果您創建了足夠多的元素以至於發生碰撞的機會不可忽略,那么無論如何都會耗盡內存。

因此,這種方法在實踐中沒有意義。


但是,從純粹的理論角度來看:

性能

List用於此操作不是最佳選擇。 對您來說,“最佳情況”(也是迄今為止最常見的情況)是列表中包含新元素。 但是,要檢查是否包含該元素,這是最壞的情況:您必須檢查列表中的每個元素,只是要檢測到尚不存在新元素。 據說這是線性復雜度 ,或者簡稱為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 ...)

使用哈希表比使用列表更好。 生成您的候選值,檢查哈希表中是否存在沖突,如果沒有沖突,則接受該值。 如果使用列表,則生成新值是O(n)操作。 如果使用哈希表,則生成新值是O(1)操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM