Как получить семя из Random в Java?

Я создаю глубокий клон для некоторого объекта. Объект содержит Random.

Является ли хорошей практикой извлечение начального числа из Random? Если да, то как? Нет Random.getSeed().


person Marnix    schedule 14.05.2011    source источник
comment
Я предоставил способ получить чистую копию объекта Random (соответственно его подкласса), поэтому я получил класс CopyableRandom extends Random implements Copyable<CopyableRandom>. stackoverflow.com/a/18531276/1809463   -  person mike    schedule 30.08.2013


Ответы (7)


Random должен быть случайным. Обычно вы хотите, чтобы два Random производили разные числа, а не одинаковые числа.

Вы можете скопировать Random, используя сериализацию/десериализацию, и получить поле «seed», используя отражение. (Но я сомневаюсь, что вы должны это делать)

Если последовательность не критична для вас, вы можете считать, что клоном Random является он сам или любой new Random()

person Peter Lawrey    schedule 14.05.2011
comment
Извиняюсь за некромантию треда, но есть по крайней мере один малоизвестный вариант использования для сохранения последовательности генератора случайных чисел при выполнении, который я могу назвать навскидку: видеоигры, особенно стратегические, где невезение - это то, что игрок предполагается управлять. Ярким примером является X-COM, который изначально сохранял состояние генератора случайных чисел в сохраненной игре, чтобы игроки не могли быстро сохраняться перед каждым выстрелом, который делают их солдаты, и перезагружаться, если он промахивается. (Расширение имеет настройку в конфигурации для явного включения резервного копирования.) - person millimoose; 12.10.2015
comment
@millimoose, в этом случае вы можете повторно задавать Random даже в секунду или каждые 100 мс, чтобы вы могли воспроизвести последние 100 мс + - person Peter Lawrey; 13.10.2015
comment
Это ничего не сделало бы против накопления сохранений - игрок мог получить разные результаты в зависимости от того, в каком интервале 100 мс он был предпринят, что, по сути, ничего не дало бы. В видеоигре, которая делает упор на долгосрочную стратегию — в отличие от азартных игр — и позволяет сохранять и загружать, вы не хотите, чтобы игрок знал результат следующего броска. Вы хотите обратное - игрок не должен иметь возможность избежать судьбы и каждый раз перебрасывать, пока результат не станет выгодным. (Например, перезаряжайте несколько раз, пока ваш солдат не попадет или пока инопланетянин не промахнется.) - person millimoose; 14.10.2015
comment
Другой случай, когда это полезно, — это использование генератора случайных чисел для генерации данных для тестирования. Я пишу тесты таким образом, чтобы они задавали начальное число таким образом, чтобы каждый прогон был одинаковым; в противном случае я не могу воспроизвести обнаруженные ошибки. Также было бы полезно иметь возможность найти текущее начальное значение, чтобы можно было возобновить последовательность с того места, где она остановилась. - person Chris Westin; 01.04.2020
comment
Существует множество случаев, когда вы хотите воссоздать случайный объект с тем же начальным числом. Плохой ответ: вам просто нужно инициализировать Random с помощью семени random.nextLong() и принять это к сведению. - person Jack; 04.12.2020

Гораздо более простой способ получить семя — создать его и сохранить как семя. Я использую этот метод для игры и хочу дать игроку возможность создать точно такой же мир, если он того пожелает. Итак, сначала я создаю случайный объект без начального числа, затем позволяю ему генерировать случайное число и использовать его в другом случайном объекте в качестве начального значения. Всякий раз, когда игроку нужно семя уровня, я его где-то храню. По умолчанию игра по-прежнему случайна.

    Random rand = new Random();
    //Store a random seed
    long seed = rand.nextLong();
    //Set the Random object seed
    rand.setSeed(seed);

    //do random stuff...

    //Wonder what the seed is to reproduce something?
    System.out.println(seed);
person Madmenyo    schedule 17.07.2015
comment
Это можно использовать для создания двух Random с одинаковым состоянием. При клонировании сгенерируйте начальное число с помощью rand.nextLong() и заполните как исходный Random, так и его копию этим начальным числом. С этого момента и оригинал, и копия генерируют одинаковые числа. - person Markus Klinik; 10.07.2020

Что вы можете сделать, так это получить системное время самостоятельно, затем заполнить его генератором случайных чисел и сохранить его где-нибудь или распечатать, чтобы вы могли использовать его позже.

long rgenseed = System.currentTimeMillis();
Random rgen = new Random();
rgen.setSeed(rgenseed);
System.out.println("Random number generator seed is " + rgenseed);
person Asher E    schedule 21.06.2012
comment
Семя внутренне меняется по мере того, как вы продолжаете использовать случайный экземпляр. - person mike; 30.08.2013
comment
Это позволит вам только перезапустить последовательность с самого начала, а не возобновить ее с того места, где она была. - person Chris Westin; 01.04.2020

Это может быть хорошей практикой в ​​зависимости от вашей цели. В большинстве случаев вам не нужно извлекать текущее начальное число. Например, если ваша цель состоит в том, чтобы иметь два генератора Random, которые генерируют одну и ту же последовательность значений, вам не нужно извлекать случайное начальное число: вы просто создаете эти два объекта Random с одним и тем же (заранее установленным) начальным числом.

Java не предоставляет стандартный способ получения начального числа из объекта Random. Если вам действительно нужно это число, вы можете обойти его: сериализовать свой объект Random, сериализовать другой объект Random (с другим начальным числом), найти 8 байтов, в которых эти две строки различаются, и получить начальное значение из этих 8 байтов.

Вот как это сделать с сериализацией:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Random;
public class SeedGetter {
  static long getSeed(Random random) {
    byte[] ba0, ba1, bar;
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(new Random(0));
      ba0 = baos.toByteArray();
      baos = new ByteArrayOutputStream(128);
      oos = new ObjectOutputStream(baos);
      oos.writeObject(new Random(-1));
      ba1 = baos.toByteArray();
      baos = new ByteArrayOutputStream(128);
      oos = new ObjectOutputStream(baos);
      oos.writeObject(random);
      bar = baos.toByteArray();
    } catch (IOException e) {
      throw new RuntimeException("IOException: " + e);
    }
    if (ba0.length != ba1.length || ba0.length != bar.length)
      throw new RuntimeException("bad serialized length");
    int i = 0;
    while (i < ba0.length && ba0[i] == ba1[i]) {
      i++;
    }
    int j = ba0.length;
    while (j > 0 && ba0[j - 1] == ba1[j - 1]) {
      j--;
    }
    if (j - i != 6)
      throw new RuntimeException("6 differing bytes not found");
    // The constant 0x5DEECE66DL is from
    // http://download.oracle.com/javase/6/docs/api/java/util/Random.html .
    return ((bar[i] & 255L) << 40 | (bar[i + 1] & 255L) << 32 |
            (bar[i + 2] & 255L) << 24 | (bar[i + 3] & 255L) << 16 |
            (bar[i + 4] & 255L) << 8 | (bar[i + 5] & 255L)) ^ 0x5DEECE66DL;
  }
  public static void main(String[] args) {
    Random random = new Random(12345);
    if (getSeed(random) != 12345)
      throw new RuntimeException("Bad1");
    random.nextInt();
    long seed = getSeed(random);
    if (seed == 12345)
      throw new RuntimeException("Bad2");
    Random random2 = new Random(seed);
    if (random.nextInt() != random2.nextInt())
      throw new RuntimeException("Bad3");
    System.out.println("getSeed OK.");
  }
}
person pts    schedule 14.05.2011
comment
Взгляните на мое решение. Там легче получить семена. Заголовок stackoverflow.com/questions/18493319/ - person mike; 30.08.2013

Это можно сделать с помощью отражения, хотя есть небольшая особенность:

Random r = ...;  //this is the random you want to clone
long theSeed;
try
{
    Field field = Random.class.getDeclaredField("seed");
    field.setAccessible(true);
    AtomicLong scrambledSeed = (AtomicLong) field.get(r);   //this needs to be XOR'd with 0x5DEECE66DL
    theSeed = scrambledSeed.get();
}
catch (Exception e)
{
    //handle exception
}
Random clonedRandom = new Random(theSeed ^ 0x5DEECE66DL);

Магическое число 0x5DEECE66DL взято из исходного кода Random.java, где семенам дается «начальная схватка»:

private static final long multiplier = 0x5DEECE66DL;
private static final long mask = (1L << 48) - 1;
//...
private static long initialScramble(long seed) {
    return (seed ^ multiplier) & mask;
}

который XOR их со случайным числом и усекает их до 48 бит. Таким образом, чтобы воссоздать исходное состояние, мы должны выполнить операцию XOR для исходного состояния, которое мы извлекли.

person KevinL    schedule 26.03.2015

Интересный парадокс... Я бы не назвал клонированный объект Random случайным - в качестве обходного пути вы можете попробовать следующее: когда вы клонируете свой объект, вы можете самостоятельно установить начальное значение в обоих экземплярах Random с одинаковыми ценность.

person Lukasz    schedule 14.05.2011

Причина, по которой я здесь, заключается в том, что мне нужно помнить семя на случай, если мне понадобится воссоздать то, что произошло в конкретном запуске программы. Код, который я использовал:

long seed = random.nextLong();
random.setSeed(seed);

Хотя это не совсем то, о чем просят, я думаю, что это, вероятно, то, что требуется.

person Keith Whittingham    schedule 06.03.2016