2
int[] howMany = {2, 3, 4};
String[] elements = {"", "", ""};
String[][] village = new String[3][3];

I'd like to create a 2D village array from the elements. In howMany array there are the numbers of the elements' occurrence. How can I randomly insert these elements (with maximum values in howMany) into the 2D array?

Ákos
  • 31
  • 6

6 Answers6

4

You can hold a ArrayList with all the possible elements that can go in the village.

In your case, the list would look something like this:

someList = new ArrayList<String>();
for(int i = 0; i < elements.length; i++) {
    for(int j = 0; j < howMany[i]; j++) {
        someList.add(elements[i]);
    }
}

With this, we have a list that contains all our possible elements. Now we would start filling the matrix with our random elements, discarding every element that we used one by one.

Random rand = new Random();
int n;
for(int x = 0; x < village.length; x++)
    for(int y = 0; y < village[0].length; y++)
        n = rand.nextInt(someList.size());
        village[x][y] = someList.get(n);
        someList.remove(n);
    }
}

Obviously, in my example you should import Random and ArrayList.

Aleksa
  • 171
  • 5
2

If you will treat 2D 3x3 array like 1D 1x9 array you can use Collections.shuffle(list) to get random number of elements in it.

  1. Fill 1x9 container with all needed elements (2x, 3x, 4x)
  2. shuffle elements in 1x9 container
  3. move elements to 3x3 array

Demo:

public static String[][] generate3x3Village() {
    int[] howMany = {2, 3, 4};
    String[] elements = {"", "", ""};
    String[][] village = new String[3][3];

    //1. fill 1x9 array with all necessary elements
    String[] tmp = new String[9];
    int index = 0;
    for (int i = 0; i < elements.length; i++) {
        for (int j = 0; j < howMany[i]; j++) {
            tmp[index++] = elements[i];
        }
    }

    //2. shuffle
    Collections.shuffle(Arrays.asList(tmp));

    //3. convert 1x9 array to 3x3 array
    for (int i = 0; i < tmp.length; i++) {
        village[i / 3][i % 3] = tmp[i];
    }

    return village;
}

Usage:

public static void showVillage(String[][] village) {
    for (String[] row : village) {
        System.out.println(Arrays.toString(row));
    }
}

public static void main(String[] args) throws Exception {
    for (int i = 0; i < 3; i++) {//lets see few villages
        String[][] village = generate3x3Village();
        showVillage(village);
        System.out.println("-----------");
    }
}

Output:

[, , ]
[, , ]
[, , ]
-----------
[, , ]
[, , ]
[, , ]
-----------
[, , ]
[, , ]
[, , ]
-----------
Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • Wow, I couldn't even expect that the `Collections.shuffle(Arrays.asList(tmp))` would work! But actually it works because the `Arrays.asList(tmp)` under the hood just wraps an original array. – Alex Shavlovsky Apr 17 '21 at 18:39
  • @AlexShavlovsky Yes, but it only works with arrays declared to hold non-primitive elements. In case of array like `int[] numbers` although it can be used along with `Arrays.asList(numbers)` it will NOT generate `List` nor `List` (sine generic types can't represent primitive type) BUT `List` (which will hold array itself). – Pshemo Apr 17 '21 at 18:44
  • Thanks for the clarification. I have found an explanation in Effective Java by Joshua Bloch "Item 42 - Use varargs judiciously" – Alex Shavlovsky Apr 17 '21 at 19:13
  • @AlexShavlovsky Also here is explanation by our site Top 1 user Jon Skeet: https://stackoverflow.com/a/1467940 – Pshemo Apr 17 '21 at 19:15
1

Here's one way to do it. Using the Random class from java.util.Random, simply loop through each element, and for however many times it occurs in howMany, use a while loop to loop through random coordinates until it reaches one that hasn't been filled yet. Fill it in with the current element, and continue.

    int[] howMany = {2, 3, 4};
    String[] elements = {"", "", ""};
    String[][] village = new String[3][3];
    Random rand = new Random();
    for (int i = 0; i < elements.length; i++)
    {
        for (int j = 0; j < howMany[i]; j++)
        {
            int x = rand.nextInt(village[0].length);
            int y = rand.nextInt(village.length);
            while (village[y][x] != null)
            {
                x = rand.nextInt(village[0].length);
                y = rand.nextInt(village.length);
            }
            village[y][x] = elements[i];
        }
    }
    for (int i = 0; i < village.length; i++)
    {
        for (int j = 0; j < village[0].length; j++)
        {
            System.out.print(village[i][j]);
        }
        System.out.println();
    }
1

here is another way to do it:

    int arrSize = 3;
    int[] howMany = {2, 3, 4};
    String[] elements = {"", "", ""};
    String[][] village = new String[arrSize][arrSize];
    
    int[] histogram = new int[arrSize];
    
    for(int i = 0; i < arrSize; i++)
    {
        for(int j = 0; j < arrSize; j++)
        {
            boolean found_element = false;
            while(!found_element)
            {
                int randIndex = getRandomNumber(0, arrSize);
                
                if(histogram[randIndex] < howMany[randIndex])
                {
                    village[i][j] = elements[randIndex];
                    histogram[randIndex]++;
                    found_element = true;
                }
            }
        }
    }
    
    for(int i = 0; i < arrSize; i++)
    {
        for(int j = 0; j < arrSize; j++)
        {
            System.out.printf(village[i][j]);
        }
        System.out.println();
    }

and here is the 'getRandomNumber' function:

public static int getRandomNumber(int min, int max) {
    return (int) ((Math.random() * (max - min)) + min);
}
AlonB
  • 21
  • 2
1
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

    static void buildVillage(int[] howMany, String[] elements, String[][] village) {
        List<Integer> flatElIds = IntStream.range(0, howMany.length)
                .flatMap(i -> IntStream.range(0, howMany[i]).map(__ -> i)).boxed().collect(Collectors.toList());
        Collections.shuffle(flatElIds);
        String[] flatElements = flatElIds.stream().map(x -> elements[x]).toArray(String[]::new);
        for (int i = 0, xs = village[0].length, ys = village.length; i < ys; i++)
            village[i] = Arrays.copyOfRange(flatElements, i * xs, (i + 1) * xs);
    }
Alex Shavlovsky
  • 321
  • 3
  • 8
1

I like the Answer by Aleksa and the Answer by Pshemo.

Here are three variations on their themes. No big breakthroughs here, but perhaps interesting.

record

Java 16 offers the new records feature. A record is a brief way to write a class whose main purpose is to communicate data, transparently and immutably. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.

We can use a record to mate the pairs of inputs, the emoji character being used as an icon along with the number of occurrences we want to see of that character. This makes our intent more clear and explicit than using two separate arrays as inputs.

Note that a record can be defined locally, within a method. (So too with interfaces and enums, as of Java 16, by the way.)

record EmojiCount ( String emoji , int count ) {}

Define your inputs as a unmodifiable list.

final List < EmojiCount > emojiCounts =
        List.of(
                new EmojiCount( "" , 2 ) ,
                new EmojiCount( "" , 3 ) ,
                new EmojiCount( "" , 4 )
        );

Use a stream to get a count of all the emoji characters to be generated.

final int countEmoji = emojiCounts.stream().mapToInt( emojiCount -> emojiCount.count ).sum();

// Define the village.
final int width = 3, height = 3;
if ( countEmoji > ( width * height ) ) throw new IllegalStateException( "The village is not large enough to contain all your desired emoji icons. Message # 1348468a-1102-4ad4-bc39-426fbe9c86a3." );
final String[][] village = new String[ width ][ height ];

Collections.nCopies

Generate all the emoji characters we need, repetitively. We can make a List for each kind of emoji, using Collections.nCopies to fill each list. We join those lists into a master list. Then shake them up randomly by calling Collections.shuffle.

List < String > emojis = new ArrayList <>( countEmoji );
for ( EmojiCount emojiCount : emojiCounts )
{
    emojis.addAll( Collections.nCopies( emojiCount.count , emojiCount.emoji ) );
}
Collections.shuffle( emojis );

We take those generated emojis and distribute them across the rows and columns of our village grid, our two-dimensional array. All we need is pair of for loops nested, to access each row, then each column. As we go, we increment an index number to move through our list of emojis to be placed in the grid.

int index = 0;
for ( int x = 0 ; x < width ; x++ )
{
    for ( int y = 0 ; y < height ; y++ )
    {
        village[ x ][ y ] = emojis.get( index );
        index++;
    }
}

For each row in two-dimensional array

Lastly, dump to console. Note the compact for loop to dump a two-dimensional array, using this for-each syntax: for ( String[] row : village ).

System.out.println( "emojis = " + emojis );
String output = Arrays.toString( village );
for ( String[] row : village ) System.out.println( Arrays.toString( row ) );

Full code example

Pull all that code together.

package work.basil.demo.village;

import java.util.*;

public class App
{
    public static void main ( String[] args )
    {
        // Define the emoji characters we use as icons.
        record EmojiCount( String emoji , int count ) {}  // New record feature in Java 16. Compact way to write an immutable data-carrier class.
        final List < EmojiCount > emojiCounts =
                List.of(
                        new EmojiCount( "" , 2 ) ,
                        new EmojiCount( "" , 3 ) ,
                        new EmojiCount( "" , 4 )
                );
        final int countEmoji = emojiCounts.stream().mapToInt( emojiCount -> emojiCount.count ).sum();

        // Define the village.
        final int width = 3, height = 3;
        if ( countEmoji > ( width * height ) ) throw new IllegalStateException( "The village is not large enough to contain all your desired emoji icons. Message # 1348468a-1102-4ad4-bc39-426fbe9c86a3." );
        final String[][] village = new String[ width ][ height ];

        // Populate the village.

        // Generate all the emoji icons.
        List < String > emojis = new ArrayList <>( countEmoji );
        for ( EmojiCount emojiCount : emojiCounts )
        {
            emojis.addAll( Collections.nCopies( emojiCount.count , emojiCount.emoji ) );
        }
        Collections.shuffle( emojis );

        // Lay out the terrain.
        int index = 0;
        for ( int x = 0 ; x < width ; x++ )
        {
            for ( int y = 0 ; y < height ; y++ )
            {
                village[ x ][ y ] = emojis.get( index );
                index++;
            }
        }

        // Dump to console.
        System.out.println( "emojis = " + emojis );
        String output = Arrays.toString( village );
        for ( String[] row : village ) System.out.println( Arrays.toString( row ) );
    }
}

When run.

emojis = [, , , , , , , , ]
[, , ]
[, , ]
[, , ]
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154