How to make certain elements in an array rarer when using random selection?

I think it’d be easier if explain this in bullet form.

  • I got a string array of “worlds”.
  • I want it to be able to choose a random “world” every time.
  • However, I want some to be rarer than others.
  • And then, if that world has already been previously chosen, it can’t be chosen again. (I think this could be accomplished by making bools?)

Is there any possible way to do that with this current code? Or does it need a completely different construct?

string [] worlds {"thisworld","thatworld","etc"};

public void changeWorld () { currentWorld = worlds [Random.Range (0, worlds.Length)];}

Good day.

IT’s simple.

As you have all the worlds in a array, you can do something like this:

string[] worlds  // As you have now
int[] worldsRarity  // Number from 0 (common) to 99 (extra rare) of each world, in the same order as the string array
bool[] worldsUsed // bool variable for each world. Once one world is chosen, change its bool to "true"

So now only need to:

A method that does this:

create random number (lets call it Rand) from 0 to worlds.length

Check worldsUsed[Rand], if its true, make "return;" and call this methid again. If false:

do this to see if it can be selected for rarity issues:

if (Random.Range (0,100) >= worldsRarity[Rand])  //if its true, then load the world[Rand] and change worldsUsed[Rand] to true

if it was false, restart the method again.

So, you have a method that selects random worlds only once and is more probably that selects a common worlds than a rare world.

Bye!

Doing a random range from 0 to length of the array, is saying that each entry has the same probability of being selected.

An example:

world1 - Probability 1
world2 - Probability 1
world3 - Probability 1

So with a total of 3 probability, they each have a 1 in 3 chance of being picked.

What you want is something more like this:

world1 - Probability 10
world2 - Probability 5
world3 - Probability 1

With a total probability of 16, the first world has a 10 in 16 change of being pick, the second is 5 in 16 and third is 1 in 16.

To accomplish this in code, you’ll need to track the world’s probability, so as with another array with matching indexes. You’d add up the probabilities then do a random range from using that total. Then check if that number lands within a probability.

    string[] worlds;
	int[] prob;
	bool[] chosen;

	private void Start()
	{
		worlds = new string[] { "thisworld", "thatworld", "someworld", "otherworld" };
		prob = new int[] { 10, 10, 5, 1 };
		chosen = new bool[worlds.Length];
	}

	public void changeWorld()
	{
		int totalvalue = 0;
		int runningvalue = 0;

		// get sum of probability of world that can be picked
		for (int i = 0; i < worlds.Length; i++) {
			// if not already chosen before
			if (!chosen*) {*

_ totalvalue += prob*;_
_
}_
_
}*_

* // choose a random number with the total*
* int hitvalue = Random.Range(1, totalvalue + 1); // adjust for exclusive max with ints*

* // run through checking for hits*
* for (int i = 0; i < worlds.Length; i++) {*
* // if not already chosen before*
_ if (!chosen*) {
runningvalue += prob;
if (hitvalue < runningvalue) {
// probability hit*

currentWorld = worlds*;
chosen = true;
}
}
}*_

* }*
Let me know if you have issues with this or have further questions on this topic.

private string worlds = new string
{
“thisworld”,
“thatworld”,
“etc”
};

// This array does not represent real "probabilities", it represents weights, meaning
// Given two worlds whose indices are i and j,
// If worldWeights *== 2 and worldWeights[j] == 1,*

// The world i as twice chances to be chosen comparing to world j
// Instead of two arrays, you could use an array of structs{ public string world, public float weight }
private float[] worldWeights = new float[]
{
80f,
15f,
5f
};

public void ChangeWorld()
{
if( worlds.Length != worldWeights.Length )
throw new System.InvalidOperationException(“The number of weights does not match the number of worlds”);

currentWorld = GetWorld();
}

private string GetWorld()
{
float worldWeightsSum = GetWorldWeightsSum();
float random = Random.Range(0, worldWeightsSum);
float weightsSum = 0 ;
for( int i = 0 ; i < worlds.Length ; ++i )
{
if( weightsSum <= worldWeights )
{
weightsSum += worldWeights ;
// Reset the weight to 0
// in order to prevent the world from being picked the next time
// you call ChangeWorld()
worldWeights = 0 ;
return worlds*;*
}
}
}

private float GetWorldWeightsSum()
{
float sum = 0 ;
for( int i = 0 ; i < worldWeights.Length ; ++i )
sum += worldWeights*;*
return sum ;
}

The simplest way to achieve that with your current structure would be to simply create another list/array which would contain the name of the worlds in the frequency set for them. Then the probability of randomly selecting an item is directly proportional to how many times that item appears in this list.

For example, something like this (Haven’t tested the code much but you will get the idea):

public class Main : MonoBehaviour 
	{
		#region Public Fields
		// Array of choices
		public string[] Worlds = new string[3] { "thisworld", "thatworld", "etc" };
		// Array of rarity values. Lower = More rare
		public int[] Rarity = new int[3] { 20, 10, 1 };
		#endregion
		//
		#region Protected Fields
		#endregion
		//
		#region Private Fields
		// The list from which the worlds will be randomly picked up
		private List<string> m_Worlds = new List<string>();
		#endregion

		#region Unity Methods
		// Use this for initialization
		private void Start ()
		{
			for (int i = 0; i < Worlds.Length; i++)
			{
				if (Rarity.Length > i)
				{
					for (int j = 0; j < Rarity*; j++)*
  •  			{*
    

m_Worlds.Add(Worlds*);
_
}_
_
} _
_
}_
_
}*_

* // Update is called once every frame*
* private void Update()*
* {*
* if (Input.GetKeyDown(KeyCode.Space))*
* {*
* Debug.Log("Next World :: " + GetWorld());*
* }*
* }*

* #endregion*

* #region Public Methods*
* public string GetWorld()*
* {*
* if (m_Worlds.Count > 0)
_
{_
_
// get a random item from the list*_
* string world = m_Worlds[UnityEngine.Random.Range(0, m_Worlds.Count)];
_
// remove all occurances of that item from the list*_
* m_Worlds.RemoveAll(item => item == world);
_
// return the value to the calling function*_
* return world;*
* }*
* return null;*
* }*
* #endregion*