Java - Arrays - checking for duplicates in same input - java

I'm trying to get the secret code from 1 - 40, not to repeat any numbers. How am I able to compare each of them and not get any duplicates?
I have extensively looked through Java documentation and asked my lectures and I can't get a working answer. I understand the concept, what I'm meant to do, but just can't get the coding right.
Here is my code:
public static void main(String[] args) {
int[] secret = new int[5];
//int[] secret = {0,0,0,0,0};
int[][] num = new int[3][5];
int correctL1 = 0;
int correctL2 = 0;
int correctL3 = 0;
for (int i = 0; i<5; i++){ // to get secret numbers.
secret[i] = (int) ((Math.random() * (40 - 1)) + 1);
}
System.out.println(Arrays.toString(secret));
}
I have tried putting this into the loop to get another number but it's still giving me duplicates.
if(((secret[i] == secret[0]) || (secret[i] == secret[1]) ||
(secret[i] == secret[2]) || (secret[i] == secret[3]) ||
(secret[i] == secret[4])) || (secret[i] != 0)) {
secret[i] = ((int) ((Math.random() * (40 - 1)) + 1));
}

In Java 8, you can use Random#ints to easily generate an array of distinct random numbers:
int[] secret = new Random().ints(1, 40).distinct().limit(5).toArray();
Ideone Demo
Otherwise, you can generate a Set<Integer> by using a while loop:
Set<Integer> secret = new HashSet<>();
Random gen = new Random();
while (secret.size() < 5) {
secret.add(gen.nextInt(40 - 1) + 1);
}

Simply use a Set<Integer> to keep the already generated numbers and iterate as long as the generated number is part of the generated numbers.
Set<Integer> existing = new HashSet<>();
for (int i = 0; i<5; i++){ // to get secret numbers.
// Loop until the generated number is not part of the already generated numbers
int value;
do {
value = (int) ((Math.random() * (40 - 1)) + 1);
} while (!existing.add(value));
secret[i] = value;
}

Why not creating a Set<Integer> (so you will not get any duplicate) and iterate until its size is 5 ?

Just use Set Collection..for better performance you can use below code
Set<Integer> data=new HashSet<Integer>();
If you want to get all elements by ascending order,you can use TreeSet
OR Other Logic is Every collection has contains method which returns boolean for e.g.data.contains(o)

while(true){
int[] secret = new int[5];
for (int i = 0; i < 5; i++) { // to get secret numbers.
secret[i] = (int) ((Math.random() * (40 - 1)) + 1);
}
System.out.println("Array:" + Arrays.toString(secret));
Set<Integer> mySet = new HashSet<Integer>();
for (int i = 0; i < 5; i++) { // to get secret numbers.
mySet.add(secret[i]);
}
if (mySet.size() == secret.length) {
System.out.println("OK");
break;
} else {
System.out.println("Not ok");
}
}

Related

Random numbers in an array without duplicates

I know this question has been answered out there, but those solutions don't fit in with the way I'm going about, so I'm enquiring to see if there is a simpler solution.
I'm using the set interface and I need there to be 6 random numbers and you can't have duplicates in the set interface.
This is what I've currently got, the way I I'm doing it is not ideal and often causes crashes.
public void drawLotto(){ //The validation I have here I know isn't the most effective way and is-
Random r = new Random();//resource comsuning but this was the only way I could think of doing it.
int draw[] = new int[6];
int min = 1;
for(int i = 0; i < draw.length; i++){
draw[i] = r.nextInt(lotteryMax-min) + min;
lotteryDraw.add(draw[i]);
}
int size = lotteryDraw.size();
if(size != 6){
drawLotto();
}
for(int i = 0; i < draw.length; i++){
System.out.println(draw[i] + " ,");
}
System.out.println();
}
``
Thank you, any help is appreciated.
The reason you have problems is because you recursively call drawLotto(), which will in turn create a new instance of the Random. If drawLotto() cannot create a correct list, it will have to do a full retry of all 6 numbers. This might cause your application to use a high amount of memory, resulting in the crash you experience
One way you could do this is by keep looping until you find 6 unique numbers:
public void drawLotto(){
Random r = new Random();
Set<Integer> draw = new HashSet<>();
int min = 1;
int lotteryMax = 50;
while(draw.size() < 6){
draw.add(r.nextInt(lotteryMax-min) + min);
}
String lotteryDrawing = draw.stream().map(String::valueOf).collect(Collectors.joining(" ,"));
System.out.println(lotteryDrawing);
}
Though you have to make sure that your lotteryMax is higher than the number you need
check this out
public void drawLotto(){
Random random = new Random();
while(lotteryDraw.size()<6) {
lotteryDraw.add(random.nextInt(max-min)+min);
}
lotteryDraw.forEach(System.out::println);
}
Use a set if you want to avoid duplicate values.
Example:
public static Set <Integer> drawLotto() { //The validation I have here I know isn't the most effective way and is-
Random r = new Random(); //resource comsuning but this was the only way I could think of doing it.
int draw[] = new int[6];
int min = 1;
int lotteryMax = 10;
Set<Integer> lotteryDraw = new HashSet<Integer>();
for (int i = 0; i < draw.length; i++) {
draw[i] = r.nextInt(lotteryMax - min) + min;
lotteryDraw.add(draw[i]);
}
int size = lotteryDraw.size();
if (size != 6) {
return drawLotto();
} else {
return lotteryDraw;
}
}

Using a LinkedHashSet to store random numbers but has duplicates

I have an app that generates a random arithmetic expression. It then generates a correct answer in a random position (tag) and three subsequent incorrect answers allowing the user to choose.
I originally went with an ArrayList but it provides duplicates. I then converted the ArrayList contents in the for loop to a Set and it still produced duplicates. I then changed the initial ArrayList to a LinkedHashSet and alas, still duplicates. i'm thinking it might be the placement in my for loop but I've tried messing around with position and I either encounter duplicates or Index problems.
I don't want to use Java 8's:List<String> deDupStringList3 = strList.stream().distinct().collect(Collectors.toList()); as it's API/Version specific.
For Loop Code:
List<Integer> answers = new ArrayList<>(new HashSet<Integer>(5));
answers.clear();
int incorrectAnswer;
for (int i = 0; i < 4; i++) {
if (i == locCorrectAnswer) {
answers.add(value);
} else {
if (operator == '+') {
incorrectAnswer = value + rand.nextInt(10) + 1;
} else {
incorrectAnswer = value - rand.nextInt(10) + 1;
}
while (incorrectAnswer == value) {
if (operator == '+') {
incorrectAnswer = value + rand.nextInt(10) + 1;
} else {
incorrectAnswer = value - rand.nextInt(10) + 1;
}
}
answers.add(incorrectAnswer);
}
}
For Loop code (with ArrayList and conversion to Set):
ArrayList <Integer> answers;
Set<Integer> s = new LinkedHashSet<>(answers);
int incorrectAnswer;
for (int i = 0; i < 4; i++) {
if (i == locCorrectAnswer) {
answers.add(value);
} else {
if (operator == '+') {
incorrectAnswer = value + rand.nextInt(10) + 1;
} else {
incorrectAnswer = value - rand.nextInt(10) + 1;
}
while (incorrectAnswer == value) {
if (operator == '+') {
incorrectAnswer = value + rand.nextInt(10) + 1;
} else {
incorrectAnswer = value - rand.nextInt(10) + 1;
}
}
answers.add(incorrectAnswer);
}
s.addAll(answers);
answers.clear();
answers.addAll(s);
}
I'm sure its a simple error but I can't see it.
You can also try the following.
Set<Integer> s = new HashSet<>();
//correct value
s.add(value);
while (s.size() < 4)
{
//incorrect value
s.add( value + rand.nextInt(10) + 1);
}
List<Integer> answers = new ArrayList<>(s);
//shuffle
Collections.shuffle(answers);
Just create your Set of answers like this:
Set<String> answers = new LinkedHashSet<String>();
Now every value can be there just 1 time. It can be added without getting an error but it will be only one time in the list.
And you can still iterate/loop through it.
You're still calling answers.addAll(s) in the loop. While there won't be any duplicates in s in any given iteration, you will still get duplicates from different iterations.
Since you don't want any duplicates, there's no reason to use Lists in the first place. Any Set will remove duplicates, and LinkedHashSet will preserve insertion order.

Hi I want to use math.random to set the likely for it to repeat

I want to make a program that will generate a random number. I know I need to use Math.random() for this but I want to set a ratio for a number that can repeat. for example, I want the compiler to give a number from 1-10 but I want 5 repeat 3 times more than another number. how do I do that? please help thank you.
for exactly that case:
private static final int[] CHOICES = new int[] { 1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9, 10 };
public static int strangeRandom() {
return CHOICES[ThreadLocalRandom.current().nextInt(CHOICES.length)];
}
selects randomly from a given set of choices & you control the choices so that 5 is more likely.
First, you should use the Random class, not Math.random(). For one, it has the nice helper method nextInt(int n) that generates a random integer between 0 and n-1 (inclusive).
In your particular case, you want a number 1-10, so generate 0-9 and add 1, i.e. nextInt(10) + 1.
But you want the number 5 to occur 3 times more often. A quick dirty way is the generate two extra number (1-12) and map them to 5.
Random rnd = new Random();
int num = rnd.nextInt(12) + 1;
if (num == 11 || num == 12)
num = 5;
As I said, quick and dirty, but it does the trick for your particular case.
Now, for a more generic solution, you want to be able to specify weighted probabilities. Numbers 1-4, 6-10 has a weight of 1, and 5 has a weight of 3.
What you then do is sum the weight (12), and generate a random number, then find the number where the accumulated weight exceeds the random number.
Here is a method for that:
private static int random(Random rnd, int ... weights) {
if (weights.length < 2)
throw new IllegalArgumentException("Need at least two weights");
int total = 0;
for (int weight : weights) {
if (weight <= 0)
throw new IllegalArgumentException("Invalid weight: " + weight);
if ((total += weight) < 0)
throw new IllegalArgumentException("Weight overflow");
}
for (int i = 0, val = rnd.nextInt(total); ; i++, val -= weights[i])
if (val < weights[i])
return i;
}
You could then call it like this:
Random rnd = new Random();
int num = random(rnd, 1,1,1,1,3,1,1,1,1,1) + 1;
Notice the +1, since the method is like Random.nextInt(n) and returns a number 0 to n-1, where n is the number of weights given.
You could easily turn it into a nice class, where the weights are given on the constructor, and the class manages the Random object for you.
Beware that performance will degrade as number of weights go up. There is a way to use TreeMap or binarySearch to improve that, but you need the class implementation, so it can prepare the data.
This will have the 5 come up at least 3 times as often as the other 9 numbers:
private static int getRandom ()
{
Random r = new Random();
int next = r.nextInt(100000) + 1;
if (next <= 25000)
{
return 5;
}
else
{
return r.nextInt(10) + 1;
}
}
Note: You can also preclude the 5 in the else case to try and hit the "3 times" likelihood better. I didn't for simplicity.
Another Variation:
Here is a variation that uses modulus of the nextInt() instead of 25000/100000 split.
Also, I put code in the else case that tries to preclude the 5 (without the infinite loop).
private static int getRandom ()
{
Random r = new Random();
int next = r.nextInt();
if (next % 4 == 0)
{
return 5;
}
else
{
int p = r.nextInt(10) + 1;
int tries = 0;
// try to not get a 5 for a few times
while (p == 5 && tries++ < 4)
{
p = r.nextInt(10) + 1;
}
return p;
}
}
Test Code:
public static void main(String[] args)
{
Map<Integer, Integer> frequecnyMap = new HashMap<Integer, Integer>();
for (int i = 0; i < 12000; i++)
{
int r = getRandom();
Integer n = frequecnyMap.get(r);
if (n == null)
{
frequecnyMap.put(r, 1);
}
else
{
frequecnyMap.put(r, n + 1);
}
}
System.out.println(frequecnyMap);
}
Sample Output (2nd Variation):
{1=971, 2=975, 3=995, 4=1037, 5=3025, 6=1042, 7=995, 8=976, 9=969, 10=1015}
{1=1016, 2=1019, 3=994, 4=968, 5=3068, 6=1030, 7=996, 8=914,
9=990, 10=1005}
{1=939, 2=944, 3=979, 4=986, 5=3023, 6=1040, 7=1007, 8=1046, 9=997, 10=1039}
If you want to have full control of your random generated numbers, you should do something like this:
public class MyRandom {
private int[] probability;
private long[] ntimes;
private long times;
public MyRandom(int[] probability) {
this.probability = new int[10];
System.arraycopy(probability, 0, this.probability, 0, probability.length);
ntimes=new long[10];
for(int i=0; i < ntimes.length; i++)
ntimes[i]=0;
times=0;
}
public void showProbability() {
for (long i : probability) {
System.out.print(i+" ");
}
System.out.println();
}
public int random() {
int t = 10;
int r = (int)Math.floor(Math.random()*10+1);
double p = 0;
if (times == 0)
p = 0;
else
p = ntimes[r-1]*100/times;
System.out.println("P: "+p +" : "+probability[r-1]);
while (p > probability[r-1] && t > 0) {
r = (int)Math.floor(Math.random()*10+1);
p = ntimes[r-1]*100/times;
t--;
}
ntimes[r-1]++;
times++;
return r;
}
public long getOcurrences(int i) {
return ntimes[i-1];
}
//This is an example of how to use it.
public static void main(String[] args) {
int[] p = {5, 5, 5, 5, 30, 5, 5, 5, 10, 15};
MyRandom mr = new MyRandom(p);
for (int i = 0; i < 2000; i++) {
int r = mr.random();
System.out.println("Id: "+i+" Number: "+r+" Ocurrences: "+mr.getOcurrences(r));
}
}
}

Arrays.sort not filling array, overwriting values that are already in the array

I need to generate an array int[] randomNumbers of random numbers with no duplicates. To do this, I make an array with all values that can go into randomNumbers, then use a random number generator to pick one out of the list, check if it's already in randomNumbers, and if it isn't, put it in randomNumbers.
(I want numbers between 1 and max, not 0 and max-1)
To be able to use Arrays.sort(int[]), the list needs to be sorted. So I use a third array, with the same values as randomNumbers called sortedNumbers, and sort it on every iteration:
public int[] uniqueRandom(int max, int numRequired) {
if (max < numRequired) {
numRequired = max;
}
int[] randomNumbers = new int[numRequired];
int[] sortedNumbers = new int[numRequired];
int[] sequentialNumbers = new int[max];
for (int i = 1; i < max; i++) {
sequentialNumbers[i] = i;
System.out.println(sequentialNumbers[i]);
}
int p = 0;
while (p < numRequired) {
int j = r.nextInt(max) + 1;
System.out.println("J:" + j);
if (Arrays.binarySearch(sortedNumbers, j) >= 0) {
System.out.println("Number Found:" + Arrays.binarySearch(randomNumbers, j));
} else {
randomNumbers[p] = j;
sortedNumbers[p] = j;
Arrays.sort(sortedNumbers);
for (int i = 0; i < randomNumbers.length; i++) {
System.out.println("rNum[" + i + "]:" + randomNumbers[i]);
}
System.out.println("\n");
for (int i = 0; i < randomNumbers.length; i++) {
System.out.println("sNum[" + i + "]:" + sortedNumbers[i]);
}
p++;
}
}
return randomNumbers;
}
My issue is that I'm getting an output where sortedNumbers is overwriting values. For uniqueRandom(5, 5) the output is:
J:2
rNum[0]:2
rNum[1]:0
rNum[2]:0
rNum[3]:0
rNum[4]:0
sNum[0]:0
sNum[1]:0
sNum[2]:0
sNum[3]:0
sNum[4]:2
J:2 // 2 already in the list, try again
J:2
J:4
rNum[0]:2
rNum[1]:4
rNum[2]:0
rNum[3]:0
rNum[4]:0
sNum[0]:0
sNum[1]:0
sNum[2]:0
sNum[3]:2
sNum[4]:4
J:5
rNum[0]:2
rNum[1]:4
rNum[2]:5
rNum[3]:0
rNum[4]:0
sNum[0]:0
sNum[1]:0
sNum[2]:2
sNum[3]:4
sNum[4]:5
J:2
J:3
rNum[0]:2
rNum[1]:4
rNum[2]:5
rNum[3]:3
rNum[4]:0
sNum[0]:0 // Should be:
sNum[1]:0 // 2
sNum[2]:2 // 3
sNum[3]:3 // 4
sNum[4]:5 // 5
J:4
rNum[0]:2
rNum[1]:4
rNum[2]:5
rNum[3]:3
rNum[4]:4
sNum[0]:0
sNum[1]:0
sNum[2]:2
sNum[3]:3
sNum[4]:4
So you can see the issue. I'm using java 1.7, and have no idea why it's doing this!
To solve your problem I would use a Set, that assure us to have unique results.
Below snipest will generate array with required number of unique integers.
Set<Integer> uniqueNumbers = new HashSet<Integer>();
Random r = new Random();
while(uniqueNumbers.size() < numRequired) {
uniqueNumbers.add(r.nextInt(maxRandom) + 1);
}
return uniqueNumbers.toArray(new Integer[0]);
You are putting the new number into both arrays using the same index. Your rNum array is filling from top down, but the sorted array is not: Each time you sort it, the new value moves down in the array and the zeros are always at the top. I think you could fix it by always putting the new number in the first position of the sorted array:
sortedNumbers[0] = j;
When you input J=5
the sortedNUm[] is
sNum[0]:0
sNum[1]:0
sNum[2]:2
sNum[3]:4
sNum[4]:5
next when you input J=3 (your p=3)
after
sortedNumbers[p] = j;
your sNUM[3] which is 4 is replaced by 3
hence after sorting it becomes
sNum[0]:0 // Should be:
sNum[1]:0 // 2
sNum[2]:2 // 3
sNum[3]:3 // 4
sNum[4]:5 // 5
notice 4 is not present
I suggest you initialize the array to -1 or 0 and add the variables at the start of array
like
sortedNumbers[0]=j;
and after Arrays.sort(); the first position will always be empty to add more numbers
While it doesn't answer the question, here is an alternative which is O(n) and work well provided max is not large.
public static void main(String[] args) {
System.out.println(Arrays.toString(uniqueRandom(20, 10)));
}
public static int[] uniqueRandom(int max, int numRequired) {
int[] possible = new int[max];
int[] ret = new int[numRequired];
for (int i = 0; i < max; i++)
possible[i] = i + 1;
Random r = new Random();
int numLeft = max;
for (int i = 0; i < numRequired; i++) {
int idx = r.nextInt(numLeft);
ret[i] = possible[idx];
if (idx < --numLeft)
possible[idx] = possible[numLeft];
}
return ret;
}
prints
[4, 10, 12, 19, 8, 3, 15, 1, 14, 7]
What I am trying to say is that perhaps you could make it simpler.
There are a few issues with your code:
since you only increment p when the new number j doesn't already exist in the arrays, that, combined with the fact that you sort the sortedArray first leads to the value actually being placed sometimes over an existing value (which shifted position due to the sort)
I don't understand what's the use of the sequentialNumbers array...
Here's an example which should work:
private static Random r = new Random();
public static void main(String[] args) {
System.out.println(Arrays.toString(uniqueRandom(10, 10)));
}
public static int[] uniqueRandom(int max, int numRequired) {
if (max < numRequired) {
numRequired = max;
}
int[] randomNumbers = new int[numRequired];
int[] sortedNumbers = new int[numRequired];
Arrays.sort(sortedNumbers);
int p = 0;
while (p < numRequired) {
int j = r.nextInt(max) + 1;
if(Arrays.binarySearch(sortedNumbers, j)<0) {
randomNumbers[p] = j;
System.arraycopy(randomNumbers, 0, sortedNumbers, 0, randomNumbers.length);
Arrays.sort(sortedNumbers);
p++;
}
}
return randomNumbers;
}

Eliminating Recursion

I've just been looking at the following piece of code
package test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(final String[] args) {
final int sizeA = 3;
final int sizeB = 5;
final List<int[]> combos = getAllCombinations(sizeA-1, sizeB);
int counter = 1;
for(final int[] combo : combos) {
System.out.println("Combination " + counter);
System.out.println("--------------");
for(final int value : combo) {
System.out.print(value + " ");
}
System.out.println();
System.out.println();
++counter;
}
}
private static List<int[]> getAllCombinations(final int maxIndex, final int size) {
if(maxIndex >= size)
throw new IllegalArgumentException("The maximum index must be smaller than the array size.");
final List<int[]> result = new ArrayList<int[]>();
if(maxIndex == 0) {
final int[] array = new int[size];
Arrays.fill(array, maxIndex);
result.add(array);
return result;
}
//We'll create one array for every time the maxIndex can occur while allowing
//every other index to appear, then create every variation on that array
//by having every possible head generated recursively
for(int i = 1; i < size - maxIndex + 1; ++i) {
//Generating every possible head for the array
final List<int[]> heads = getAllCombinations(maxIndex - 1, size - i);
//Combining every head with the tail
for(final int[] head : heads) {
final int[] array = new int[size];
System.arraycopy(head, 0, array, 0, head.length);
//Filling the tail of the array with i maxIndex values
for(int j = 1; j <= i; ++j)
array[size - j] = maxIndex;
result.add(array);
}
}
return result;
}
}
I'm wondering, how do I eliminate recursion from this, so that it returns a single random combination, rather than a list of all possible combinations?
Thanks
If I understand your code correctly your task is as follows: give a random combination of numbers '0' .. 'sizeA-1' of length sizeB where
the combination is sorted
each number occurs at least once
i.e. in your example e.g. [0,0,1,2,2].
If you want to have a single combination only I'd suggest another algorithm (pseudo-code):
Randomly choose the step-up positions (e.g. for sequence [0,0,1,1,2] it would be steps (1->2) & (3->4)) - we need sizeA-1 steps randomly chosen at sizeB-1 positions.
Calculate your target combination out of this vector
A quick-and-dirty implementation in java looks like follows
// Generate list 0,1,2,...,sizeB-2 of possible step-positions
List<Integer> steps = new ArrayList<Integer>();
for (int h = 0; h < sizeB-1; h++) {
steps.add(h);
}
// Randomly choose sizeA-1 elements
Collections.shuffle(steps);
steps = steps.subList(0, sizeA - 1);
Collections.sort(steps);
// Build result array
int[] result = new int[sizeB];
for (int h = 0, o = 0; h < sizeB; h++) {
result[h] = o;
if (o < steps.size() && steps.get(o) == h) {
o++;
}
}
Note: this can be optimized further - the first step generates a random permutation and later strips this down to desired size. Therefore it is just for demonstration purpose that the algorithm itself works as desired.
This appears to be homework. Without giving you code, here's an idea. Call getAllCombinations, store the result in a List, and return a value from a random index in that list. As Howard pointed out in his comment to your question, eliminating recursion, and returning a random combination are separate tasks.

Categories