简体   繁体   中英

StackOverflow while generating random numbers without having two numbers fall in a certain range

I need to write a program to generate some random numbers without having two numbers fall in a certain range. Unfortunately, I need to do it at school and the only language I can use on the computers is Java (I would otherwise do it in C++). I know very little Java, and my program is giving me a stack overflow after generating 20 or so numbers. Could someone please explain why? The program is somewhat ugly, but I really need it ready in a hurry.

import java.io.*;
import java.util.*;

public class RandomNumbers {
public static int GenerateNumber(int previousNumber, int[] numberUsed) {
    Random random = new Random();
    int number = random.nextInt(39);
    if (previousNumber >= 1 && previousNumber <= 9) {
        while (number >= 1 && number <= 9)
            number = random.nextInt(39) + 1;
    } else if (previousNumber >= 10 && previousNumber <= 17) {
        while (number >= 10 && previousNumber <= 17)
            number = random.nextInt(39) + 1;
    } else if (previousNumber >= 18 && previousNumber <= 32) {
        while (number >= 18 && previousNumber <= 32)
            number = random.nextInt(39) + 1;
    } else if (previousNumber >= 33 && previousNumber <= 41) {
        while (number >= 32 && number <= 41)
            number = random.nextInt(39) + 1;
    }
    return number;
}

public static void main(String[] args) {
    int[] numberUsed;
    numberUsed = new int[40];
    for (int i = 0; i < 40; ++i) {
        numberUsed[i] = 0;
    }
    int previousNumber = 0;
    for (int y = 0; y < 40; ++y) {
        int number = 1;
        while (numberUsed[ number = GenerateNumber
                         (previousNumber, numberUsed) ] != 0);
        numberUsed[number] = 1;
        previousNumber = number;
        System.out.println(y);
    }
}
}

EDIT: Okay, so now, for some reason, the for loop (the one with y as a counter) is not running 40 times when I include the while loop. Can someone explain why this is happening?

Your function will eventually have a problem to ever return to the main.

You will face a recursion problem in

if (numberUsed[number] != 0) {
    return GenerateNumber(previousNumber, numberUsed);
}

As the array get's filled up with 1s, this will continue to execute the function call instead of actually successfully returning. It will execute the function recursively, eventually causing the stack overflow.

When you call the function again, your parameters (previousNumber and numberUsed) are identical to the last execution, nothing changes. You keep running into the same problem since the chance of hitting a non-zero is so high, so the recursion just stacks up forever and eventually crashes.

You should get rid of the if statement in the function and just return the number back to main and do your checks there.

You start with a zero'd out array, that you gradually fill with 1s and never reset them to 0. When most of the elements of the array are non-zeros, you have a very high chance of calling GenerateNumber recursively in a very deep nesting, which is what you experience.

Also, you never change previousNumber in GenerateNumber , so you will always go down the same path, regardless of the random number generated.

After staring at this for a while, I think I figured out a working solution. It's still really ugly, but you seem to be OK with that.

public static int GenerateNumber(int previousNumber, 
                                int[] numberUsed) {
    Random random = new Random();
    int number = random.nextInt(39);
    while (numberUsed[number] != 0) {
        number = random.nextInt(39);
        if (previousNumber >= 1 && previousNumber <= 9) {
            while (number >= 1 && number <= 9)
                number = random.nextInt(39) + 1;
        } else if (previousNumber >= 10 && previousNumber <= 17) {
            while (number >= 10 && previousNumber <= 17)
                number = random.nextInt(39) + 1;
        } else if (previousNumber >= 18 && previousNumber <= 32) {
            while (number >= 18 && previousNumber <= 32)
                number = random.nextInt(39) + 1;
        } else if (previousNumber >= 33 && previousNumber <= 41) {
            while (number >= 32 && number <= 41)
                    number = random.nextInt(39) + 1;
        }
    }
    return number;
}

On a side note the run-time will also be atrocious as you get towards the end of the list, but it shouldn't stack overflow. Also it might infinite loop now that I think about it, you probably need to redesign this entirely.

StackOverFlow happens because of overlapping of methods/constructors, etc. I here,

if (numberUsed[number] != 0) {
        return GenerateNumber(previousNumber, numberUsed);
    }

this code is inside the GenerateNumber method and it is keep on calling the same method. That's the reason. If you want to check it, remove that part and try, it will compile fine. To get rid of this, eliminate the above process.

You can do it by using a loop, while loop, and putting all of your if else statements inside that

This code detects a cul-de-sac and keeps restarting until it reaches the end (typically it does only one or two retries). It also directly chooses a random number from the collected sequence of allowed choices, so no stochastic runtime for any single try.

import java.util.Arrays;
import java.util.BitSet;
import java.util.Random;

public class RandomNumberGenerator {
  static final Random random = new Random();
  static final BitSet usedNumbers = new BitSet(40);
  static final int[] ticks = {0, 1, 10, 18, 33, 41};
  static int previousNumber;

  public static int generateNumber() {
    for (int i = 1; i < ticks.length; i++)
      if (previousNumber < ticks[i])
        return generateOutsideRange(ticks[i-1], ticks[i]);
    return generateOutsideRange(0, 0);
  }

  static int generateOutsideRange(int low, int high) {
    final int[] numsToChoose = new int[40];
    int choiceLimit = 0;
    for (int i = (low > 1? 1 : high); i < 41; i = (++i == low? high : i))
      if (!usedNumbers.get(i)) numsToChoose[choiceLimit++] = i;
    if (choiceLimit == 0) throw new CulDeSacException();
    final int r = numsToChoose[random.nextInt(choiceLimit)];
    usedNumbers.set(r);
    previousNumber = r;
    return r;
  }

  public static void main(String[] args) {
    while (true) try {
      usedNumbers.clear();
      previousNumber = -1;
      final int[] rands = new int[40];
      for (int i = 0; i < rands.length; i++) rands[i] = generateNumber();
      System.out.println(Arrays.toString(rands));
      break;
    } catch (CulDeSacException e) { System.out.println("Retry"); }
  }
}

class CulDeSacException extends RuntimeException {}

Is this the spected behavior? "Shuffling the Array"

    ArrayList<Integer> numbers = new ArrayList<Integer>();
    int[] scrambled = new int[40];
    Random rnd = new Random();
    for(int i=0;i<scrambled.length;i++){
        numbers.add(i+1);
    }
    int idx=0;
    while(!numbers.isEmpty()){
        int pos = rnd.nextInt(numbers.size());

        scrambled[idx] = numbers.get(pos);
        numbers.remove(pos);

        idx++;
    }
    for(int i=0;i<scrambled.length;i++){
        System.out.println(scrambled[i]);
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM