2

I want to create an algorithm to generate objects. It will take two seeds: a hard seed (1 per universe/per game, set up in ini file) and a 'soft' seed that is passed to the function by each object that uses the function. (The soft seed belongs to the object and does not change between function calls). The purpose of this function is to output a random number (between 0-100) that is 'unique' for each object, but is repeatable: the output number is the same every time the object is generated (so long as the hard seed remains the same).

Declaring the Random outside of the function and using nextDouble() gives me good distribution but would mean that objects won't be repeatable if they generate out of order (which is possible, the program will generate objects in different orders).

Recreating the Random within the function gives me a bad number distribution.

Is my uneven distribution due to bad seeding/seed weights? How can I improve this function?

The following is Pseudo-code: a test class I set up:

  public class SeedRandomTest {
    int hardSeed = 100;
    Random generator = new Random(hardSeed);
    public SeedRandomTest(){
        for (int i = 0; i < 10; i++){
            algorithmRand(i);
        }
    }

    public void algorithmRand(int seed){
        //seed would be a argument, hardseed from config properties


        int i = 0;
        int genSeed = hardSeed + seed*100;
    //      Random generator = new Random(genSeed);
        generator.setSeed(genSeed);
        double j = (generator.nextDouble() * (100));
        i = (int) j;



        System.out.println("num = "+ i);
     }
  }
iceburg
  • 53
  • 2
  • 10
  • 1
    How do you know the distribution is bad, how did you test it? – arynaq Oct 12 '13 at 21:23
  • I'm running the test program which generates 10 numbers and the "bad" distribution times all 10 of my numbers are 1 away from each other: (76, 74, 75, 73, 72 ect) where as with 'good' distribution I'm getting numbers from 9 to 90 and everything in between. Perhaps not distribution but variety is the better word? EDIT: I may have tested incorrectly and not been feeding it any real seed variety, I'll check... – iceburg Oct 12 '13 at 21:26
  • 1
    You were right arynaq, I just wasn't feeding it much variety in seeds. Oh gosh. Well, thank you for saving me from beating my head against the wall for any longer! – iceburg Oct 12 '13 at 21:34
  • Instead of `nextDouble` you can use `nextInt(100)` I would multiply the hard seed by a prime number (once when you initialise the class) and add the "soft" seed. Note: in the current case hardSeed = 100 and hardSeed = 200 will have almost all the same numbers, just offset by 1. – Peter Lawrey Oct 12 '13 at 21:39
  • Thanks Peter! Why do we multiply by a prime instead of just using the unaltered 'hard' seed? Also, you were right about the numbers being offset by 1, how did you know that? Thanks! – iceburg Oct 12 '13 at 21:52
  • Possible duplicate: http://stackoverflow.com/questions/34243919/javascript-repeatable-random-number-based-on-multiple-inputs/34244303 – LarsH Jan 05 '17 at 15:56

1 Answers1

1

Assuming I understand the problem...

I changed the structure of your code a bit - you shouldn't really have that for-loop in the constructor.

There are two general approaches to the problem of generating unique numbers in a range:
(there will be a map of seed to generated number for both)

(Both give repeatable output.)

  • Generate 0-100, shuffle them. Return the next element when requested.

    public class SeedRandomTest
    {
       Random generator;
       List<Integer> numbers;
       Map<Integer, Integer> map;
    
       public SeedRandomTest(int hardSeed, int limit)
       {
          generator = new Random(hardSeed);
          numbers = new LinkedList<Integer>();
          map = new HashMap<Integer, Integer>();
          for (int i = 0; i <= limit; i++)
          {
             numbers.add(i);
          }
          Collections.shuffle(numbers);
       }
    
       public int algorithmRand(int seed)
       {
          /* generate a unique number for seed,
               or retrieve that number if already generated */
          if (map.containsKey(seed))
             return map.get(seed);
          int i = numbers.remove(0);
          map.put(seed, i);
          return i;
       }
    
       public static void main(String[] args)
       {
          SeedRandomTest seedRandomTest = new SeedRandomTest(89453, 100);
          for (int i = 0; i < 10; i++)
          {
             System.out.println(seedRandomTest.algorithmRand(i));
          }
       }
    }
    
  • Keep a set of already generated numbers and, when generating, generate until the number isn't in the set.

    The danger of this approach is - if you've generated most of the available numbers, it could take really long to find a number that hasn't already been generated.

    public class SeedRandomTest
    {
       Random generator;
       Set<Integer> numbers;
       Map<Integer, Integer> map;
       int limit;
    
       public SeedRandomTest(int hardSeed, int limit)
       {
          generator = new Random(hardSeed);
          numbers = new HashSet<Integer>();
          map = new HashMap<Integer, Integer>();
          this.limit = limit;
       }
    
       public int algorithmRand(int seed)
       {
          /* generate a unique number for seed,
               or retrieve that number if already generated */
          if (map.containsKey(seed))
             return map.get(seed);
          int i;
          do
          {
             i = generator.nextInt(limit+1);
          }
          while (numbers.contains(i));
          numbers.add(i);
          map.put(seed, i);
          return i;
       }
    
       public static void main(String[] args)
       {
          SeedRandomTest seedRandomTest = new SeedRandomTest(32785, 100);
          for (int i = 0; i < 10; i++)
          {
             System.out.println(seedRandomTest.algorithmRand(i));
          }
       }
    }
    
Bernhard Barker
  • 54,589
  • 14
  • 104
  • 138