Random.Next - Am I silently creating new instances? - c#

Random.Next() randomness failures are almost always caused by creating and then using multiple instances of System.Random with the same seed, either with a time seed or a manual one. However, this is the only instance creation code in my class:
System.Random rNG;
if (string.IsNullOrEmpty(Map.Seed))
{
rNG = new System.Random();
}
else
{
rNG = new System.Random(Map.Seed.GetHashCode());
}
Looping through this second attempt code correctly creates random numbers:
var resourceRoll = rNG.Next(0, this.ResourceByRoll.Count);
var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
.Where(row => row["Resource"].Equals(
this.ResourceByRoll[resourceRoll]
))
Looping through this original attempt code often creates the same number twice in a row:
var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
.Where(row => row["Resource"].Equals(
this.ResourceByRoll[rNG.Next(0, this.ResourceByRoll.Count)]
))
Am I somehow silently creating a new instance of System.Random when using a Random.Next call as a dictionary index? Why does my original code often return the same number twice in a row?
If it matters:
This class is a Unity script
I am using System.Random, not UnityEngine.Random
My complete class is below:
using Assets.Code.Tools;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using UnityEngine;
public class Map : MonoBehaviour
{
public static int Length { get; set; }
public static int Width { get; set; }
public static int ResourceChanceDenominator { get; set; }
public static string Seed { get; set; }
private static int[,] objectGrid;
private DataTable ProcGenResourceTable { get; set; }
private Dictionary<int, string> ResourceByRoll { get; set; }
private List<GameObject> prefabTrees;
private List<GameObject> prefabStones;
private void Start()
{
this.prefabTrees = GeneralTools.GetPrefabsWithTag("Tree");
this.prefabStones = GeneralTools.GetPrefabsWithTag("Stone");
GenerateMap();
}
public void GenerateMap()
{
var procGenResourceTable = Resources.Load("ProcGenResourceTable") as TextAsset;
if (procGenResourceTable != null)
{
this.ProcGenResourceTable = GeneralTools.GetDataTableFromCSV(procGenResourceTable, "|", true, false);
}
else
{
Console.WriteLine("ProcGenResourceTable could not be found");
return;
}
Map.objectGrid = new int[Map.Width, Map.Length];
this.ResourceByRoll = GetPopulatedResourceByRollDictionary();
System.Random rNG;
if (string.IsNullOrEmpty(Map.Seed))
{
rNG = new System.Random();
}
else
{
rNG = new System.Random(Map.Seed.GetHashCode());
}
for (var i = 0; i < Map.Length; i++)
{
for (var j = 0; j < Map.Width; j++)
{
var roll = rNG.Next(Map.ResourceChanceDenominator);
if (roll == 1)
{
// var resourceRoll = rNG.Next(0, this.ResourceByRoll.Count);
var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
.Where(row => row["Resource"].Equals(
this.ResourceByRoll[rNG.Next(0, this.ResourceByRoll.Count)]
))
select new
{
ModelFamily = row["Model Family"],
Tags = row["Tags"]
};
foreach (var row in resourceRow)
{
GameObject resource = null;
switch (row.ModelFamily)
{
case "Tree":
resource = Instantiate(this.prefabTrees[rNG.Next(this.prefabTrees.Count - 1)], new Vector3(i, 0, j), new Quaternion());
break;
case "Stone":
resource = Instantiate(this.prefabStones[rNG.Next(this.prefabStones.Count - 1)], new Vector3(i, 0, j), new Quaternion());
break;
default:
resource = Instantiate(this.prefabTrees[rNG.Next(this.prefabTrees.Count - 1)], new Vector3(i, 0, j), new Quaternion());
break;
}
var tagsListForResource = row.Tags.ToString().Split(new char[] { '|' }).ToList();
if (tagsListForResource.Contains("Resource"))
{
resource.tag = "Resource";
}
}
}
}
}
}
private Dictionary<int, string> GetPopulatedResourceByRollDictionary()
{
var resourceByRoll = new Dictionary<int, string>();
foreach (DataRow row in this.ProcGenResourceTable.Rows)
{
if (!string.IsNullOrEmpty(row["Weight"].ToString()))
{
for (var i = 0; i < Convert.ToInt32(row["Weight"]); i++)
{
resourceByRoll.Add(resourceByRoll.Count, row["Resource"].ToString());
}
}
}
return resourceByRoll;
}
}

I misunderstood what was going on - this is not related to repeated random numbers.
The answer to Michael Welch's question is that I am seeing trees and rocks generate on the same coordinates.
I expected this code to only generate one row of results:
var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
.Where(row => row["Resource"].Equals(
this.ResourceByRoll[rNG.Next(0, this.ResourceByRoll.Count)]
))
However, it is sometimes returns more, because .Where() does some looping of its own that re-rolls the number each time.
Having multiple rows returned causes the foreach block to generate multiple prefabs at the coordinate instead of just one.

Related

Shuffle in same order

I have a list of sprites and list of strings. I would like to shuffle this. The shuffle works but differently for both sprites and strings. Can the order of the shuffle for both be the same?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ShuffleList : MonoBehaviour
{
public List<Sprite> iconSprite = new List<Sprite>();
public List<string> priceText = new List<string>();
// Start is called before the first frame update
void Start()
{
iconSprite.Shuffle();
priceText.Shuffle();
}
}
public static class IListExtensions {
/// <summary>
/// Shuffles the element order of the specified list.
/// </summary>
public static void Shuffle<T>(this IList<T> ts) {
var count = ts.Count;
var last = count - 1;
for (var i = 0; i < last; ++i) {
var r = UnityEngine.Random.Range(i, count);
var tmp = ts[i];
ts[i] = ts[r];
ts[r] = tmp;
}
}
}
You need to shuffle both lists at once. So instead of an extension-method that only has a single parameter, just use a normal method having two lists as params.
public static void Shuffle<T1, T2>(IList<T1> list1, IList<T2> list2)
{
var count = ts.Count;
var last = count - 1;
for (var i = 0; i < last; ++i) {
var r = UnityEngine.Random.Range(i, count);
var tmp1 = list1[i];
var tmp2 = list2[i];
list1[i] = list1[r];
list1[r] = tmp1;
list2[i] = list2[r];
list2[r] = tmp2;
}
}
Alternativly you can also use the same seed for the random-generator, which will lead to identical sequences being generated:
public static void Shuffle<T>(this IList<T> ts, long seed)
{
UnityEngine.Random.InitState(seed);
var count = ts.Count;
var last = count - 1;
for (var i = 0; i < last; ++i) {
var r = UnityEngine.Random.Range(i, count);
var tmp = ts[i];
ts[i] = ts[r];
ts[r] = tmp;
}
}
Now you can provide the same seed for both calls to the function, e.g.:
void Start()
{
var seed = Datetime.Now.Ticks;
iconSprite.Shuffle(seed);
priceText.Shuffle(seed); // use the exact same seed again to produce the same random sequence
}

Weighted Job Scheduler - Retrieve Job Details

I am learning C# and want to created a weighted job scheduler.
The max profit is working fine. But I need to be able to get the JobIDs associated with it so I am trying to save it in an array jobsId, similar to this implementation in C++: https://onlinegdb.com/a9wx3nHoN.
But then in this snippet, I am getting an error: Wrong number of indices inside []; expected 2
if (profitSum > dp[i-1]) {
dp[i] = profitSum;
jobsId[i,0] = jobsId[task]; // Problem is here
jobsId[i].Append(jobs[i].id).ToArray(); //And here
}
What am I doing wrong? Please help, thanks!
Codes can be accessed here:
https://rextester.com/NXM85235
Alternatively, here is my entire snippet:
using System.IO;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
class Program
{
public int JobScheduling(int[] startTime, int[] endTime, int[] profit) {
var jobs = startTime
.Select((_, i) => new // create custom obj
{
id = i,
s = startTime[i],
e = endTime[i],
p = profit[i],
}
)
.OrderBy(x => x.e) // sort by end-time
.ToArray();
int[] dp = new int[jobs.Length];
int [,] jobsId = new int[,] {{jobs[0].id}};
int profitSum = jobs[0].p;
int task = -1;
dp[0] = jobs[0].p;
for (var i = 1; i < jobs.Length; i++) {
for (var j = i-1; j >= 0; j--) {
if (jobs[j].e <= jobs[i].s) {
task = j;
break;
}
}
if (task != -1) {
profitSum += dp[task];
}
if (profitSum > dp[i-1]) {
dp[i] = profitSum;
jobsId[i,0] = jobsId[task]; // Problem is here
jobsId[i].Append(jobs[i].id).ToArray();
}
}
// Need to implement this
for (var iter = 0; iter < jobsId.Length; iter++) {
Console.WriteLine(jobsId[iter,0]);
}
return dp[jobs.Length-1];
}
public static void Main(string[] args)
{
int[] startTime = { 1,3,6,2 };
int[] endTime = { 2,5,7,8 };
int[] profit = { 50,20,100,200 };
// Creating object
Program job = new Program();
Console.WriteLine(job.JobScheduling(startTime, endTime, profit));
}
}
int [,] jobsId = new int[,] {{jobs[0].id}};
The multi-dimensional 3d array that you are using has a fixed size .i.e. only one. You can't add any more elements to that array. Instead, try using List< List < int > >.
jobsId[i,0] = jobsId[task]; // problem here
Here jobsId is a 2d-array. You can't access the individual rows. You can only access elements within. For this too you need to create an array of array .i.e List< List < int > >.
And I could not figure out why are trying to get an array here.
jobsId[i].Append(jobs[i].id).ToArray();

How to count the listed items?

I have a txt file with infos about some mobile phones.
Type;Size;Color;New-OrUsed;Warrantee(month);Price(HUF)
iPhone SE;64;Gold;New;0;95000
iPhone 6S;64;Silver;New;3;130000
iPhone 5S;16;Black;New;5;60000
iPhone 5S;32;Gold;New;6;75000
iPhone 5S;32;RoseGold;New;8;66500
iPhone 7;32;Black;Used;10;135000
iPhone X;256;Silver;New;12;400000
iPhone 6S;128;Silver;New;3;173000
iPhone 8;128;Gold;New;12;256000
iPhone 7;64;Red;Used;4;155000
iPhone 8 Plus;64;Silver;New;4;285000
iPhone 6S Plus;64;Black;Used;8;180000
iPhone 7 Plus;32;Red;Used;6;192000
I would like to list all of them, like below:
Type (Only once, whatever how many of them);How many does the text have of this type; how many color does the text have of this type
iPhone 5S;3;3
I could list the types with a hashset, but I have no idea how to count the different colors, and the number of the type.
My code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Collections;
namespace nyilvantartas
{
class Program
{
struct adatok
{
public string tipus, szin, allapot;
public int meret, garancia, ar;
}
static void Main(string[] args)
{
StreamReader f = new StreamReader("termekek.txt", Encoding.Default);
HashSet<string> tipusok = new HashSet<string>();
List<int> hanyvan = new List<int>();
string[] hanyvann = new string[20];
string s;
string[] ss = new string[4];
adatok[] adatok = new adatok[1000];
int db = 0;
s = f.ReadLine();
while (!f.EndOfStream)
{
s = f.ReadLine();
ss = s.Split(';');
adatok[db].tipus = ss[0];
adatok[db].meret = int.Parse(ss[1]);
adatok[db].szin = ss[2];
adatok[db].allapot = ss[3];
adatok[db].garancia = int.Parse(ss[4]);
adatok[db].ar = int.Parse(ss[5]);
db++;
}
f.Close();
int ezustar = 0, gari=0;
double legolcsobb = 500000,legdragabb=0;
for (int i = 0; i < db; i++)
{
tipusok.Add(adatok[i].tipus);
if (adatok[i].szin=="Silver")
{
ezustar += adatok[i].ar;
}
if (adatok[i].ar>legdragabb)
{
legdragabb = adatok[i].ar;
}
if (adatok[i].ar<legolcsobb)
{
legolcsobb = adatok[i].ar;
}
gari += adatok[i].garancia;
}
legdragabb /= legolcsobb;
gari /= db;
string[] tipusokk = new string[13];
for (int i = 0; i < db; i++)
{
tipusok.Add(adatok[i].tipus);
}
for (int i = 0; i < db; i++)
{
hanyvann[i] = adatok[i].tipus;
}
Console.WriteLine("2.Feladat: {0} db iPhone található a listában.",+db);
Console.WriteLine("3.Feladat: Az összes ezüst színű készülék {0} Ft-ba kerülne",+ezustar);
Console.WriteLine("4.Feladat:");
foreach (var item in tipusok)
{
Console.WriteLine(item);
}
Console.WriteLine("5.Feladat: Átlagossan {0} hónap garanciát kapunk",gari);
Console.WriteLine("6.Felaadat: {0}-szor kerül többe a legdrágább a legolcsóbbnál.",legdragabb);
Console.ReadKey();
}
}
}
Create a class to hold the phone information:
class PhoneInfo {
public string type;
public int size;
public string Color;
public string newOrUsed;
public int warrantyMonth;
public decimal price;
}
Read in the text file and create a List of objects. I would suggest reading about String.Split.
List<PhoneInfo> phones;
Use LINQ to query the List.
var phoneCounts = from p in phones
group p by p.type into pg
select new {
type,
countOfType = pg.Count(),
countOfColors = pg.Select(p => p.color).Distinct().Count()
};
I could not open the link you sent, you can use hashset or any other data structure, but i prefer to use anonymous data,even you can build your own class and parse your data.
var rawData = System.IO.File.ReadAllText("aa.txt");
var data = rawData.Split(new[] {Environment.NewLine}, StringSplitOptions.None).Select(k =>
{ var segments = k.Split(';');
return new
{
Type = segments[0],
Size = segments[1],
Color = segments[2],
IsNew = segments[3],
Warranty = segments[4],
Price = segments[5]
};});
var report = data.GroupBy(k => k.Type).Select(p =>
new
{
Type = p.Key,
TypesCount = p.Count(),
ColorVarityCount = p.Select(o => o.Color).Distinct().Count()
});

SimpleSpriteSequence and getRandom

I have an Gameobject that contains several prefabs, As in the picture below
public SimpleSpriteSequence birds;
Randomizer setBirds;
setBirds = new Randomizer(birds.sprites);
int index = setBirds.getRandom();
birds.setCurrentSpriteIndex(index);
In the image below we see the population of the array
Invoke("Interface", 1f);
Now with the help of the random I get a random bird from the array. But the problem is that the bird can be repeated. All I want is that at least 6 times in a row when the Invoke("Interface", 1f); function does not repeat the bird.So in principle I have to do so 6 times the bird does not repeat itself. To be a random of these birds but 6 times to be different birds. No I know if I explained it properly, but I hope you understand the idea.
Here is code but it is written in console application since i do not have your code and it is writen with custom class of Birds but you will adapt it to your code. If you need help with it i will help you.
class Birds
{
public int birdID;
public string birdName;
//public Sprite birdSprite;
}
class Program
{
static Random rnd = new Random();
static Birds[] birds = new Birds[10];
static int nBirdsSpawned = 0;
static List<int> spawnedBirds = new List<int>();
static void Main(string[] args)
{
//Database of my birds ;)
birds[0] = new Birds();
birds[1] = new Birds();
birds[2] = new Birds();
birds[3] = new Birds();
birds[4] = new Birds();
birds[5] = new Birds();
birds[6] = new Birds();
birds[7] = new Birds();
birds[8] = new Birds();
birds[9] = new Birds();
birds[0].birdID = 0;
birds[0].birdName = "Bird 1";
birds[1].birdID = 1;
birds[1].birdName = "Bird 2";
birds[2].birdID = 2;
birds[2].birdName = "Bird 3";
birds[3].birdID = 3;
birds[3].birdName = "Bird 4";
birds[4].birdID = 4;
birds[4].birdName = "Bird 5";
birds[5].birdID = 5;
birds[5].birdName = "Bird 6";
birds[6].birdID = 6;
birds[6].birdName = "Bird 7";
birds[7].birdID = 7;
birds[7].birdName = "Bird 8";
birds[8].birdID = 8;
birds[8].birdName = "Bird 9";
birds[9].birdID = 9;
birds[9].birdName = "Bird 10";
int i = 0;
while (i < 100)
{
RandomSpawn();
i++;
}
Console.Write("Finished");
Console.ReadKey();
}
static void CheckForBirdSpawn(int birdID)
{
if (nBirdsSpawned <= 6)
{
if (!spawnedBirds.Contains(birdID))
{
spawnedBirds.Add(birdID);
SpawnBird(birdID);
}
else
{
Console.WriteLine("Bird " + birds[birdID].birdName + " is already spawned!");
}
}
else
{
SpawnBird(birdID);
}
}
static void SpawnBird(int birdID)
{
Console.WriteLine(birds[birdID].birdName);
nBirdsSpawned++;
}
static void RandomSpawn()
{
int r = rnd.Next(0, 10);
CheckForBirdSpawn(r);
}
}
Until first 6 birds are spawned it check if bird is already spawned and if it is it doesn't allow it's spawning. After 6 birds are spawned it allows every bird to be spawned.
Here is output in console app:
I know this is a mainly code reply but how about you try something a little like this?
Populate a list of the bird indexes.
Pick a random index within that list.
Decrease all already spawned birds' Values.
If any reach zero, re-add the birds index into the availableBird list.
Spawn the bird, while adding it's index as Key and the amount of spawning delay needed until it can be spawned again as the Value.
Remove the birds index from the list.
Do the spawn whenever necessary, this will spawn every frame since it is found in a Coroutine which has an infinite while loop.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class burd : MonoBehaviour {
public bool spawning;
private System.Random rnd = new System.Random();
private Dictionary<int, int> spawnedBurdies = new Dictionary<int, int>();
private List<int> availableBurdies = new List<int>();
// Use this for initialization
void Start () {
for (int index = 0; index < 10; index++)
{
availableBurdies.Add(index);
}
StartCoroutine(SpawnBurdies());
}
private IEnumerator SpawnBurdies()
{
while (true)
{
if (spawning)
{
int burdIndex = rnd.Next(availableBurdies.Count);
spawnBurd(availableBurdies[burdIndex]);
availableBurdies.RemoveAt(burdIndex);
}
yield return new WaitForFixedUpdate();
}
}
private void spawnBurd(int burdIndex)
{
Debug.Log("Spawned burd #" + burdIndex);
List<int> burdieKeys = new List<int>();
foreach (var burdie in spawnedBurdies) { burdieKeys.Add(burdie.Key); }
foreach (var burdieKey in burdieKeys)
{
spawnedBurdies[burdieKey]--;
if(spawnedBurdies[burdieKey] <= 0)
{
spawnedBurdies.Remove(burdieKey);
availableBurdies.Add(burdieKey);
}
}
spawnedBurdies.Add(burdIndex, 6);
}
}
This solution would save you from attempting to spawn birds that can't be spawned; but if there are less birds than the minimum spawns to wait for, an Argument out of range error would be thrown.

Generate 4 differents numbers randomly

I am a novice in Xamarin ,
I want to generate randomly 4 numbers which are in a list and this 4 numbers must be different .
In the example below I have a list of Ids and I am trying to pick 4 id randomly in the list and those 4 Ids must be each differents.
Here is my methode, I cannot see how I can continue and make it simple :
public MyWordsList()
{
InitializeComponent();
Dictionary<int, int> WordId = new Dictionary<int, int>();
int u= 0;
// TestAnswer.IsVisible = false;
foreach (var w in mywords)
{
WordId[u] = w.ID;
u++;
}
Random rnd = new Random();
// this is not ok because I can have the same number
word11.Text = WordsList[rnd.Next(1, 20)];
word12.Text = WordsList[rnd.Next(1, 20)];
word13.Text = WordsList[rnd.Next(1, 20)];
word14.Text = WordsList[rnd.Next(1, 20)];
}
If you have a better solution, I will take.
Thanks
You can write a short generic function which picks X amount of random and unique items from a specified collection and returns them:
private static IEnumerable<T> GetUniqueRandomItems<T>(int count, IList<T> allItems)
{
if (new HashSet<T>(allItems).Count < count)
{
throw new ArgumentException(nameof(allItems));
}
Random random = new Random();
HashSet<T> items = new HashSet<T>();
while (items.Count < count)
{
T value = allItems[random.Next(0, allItems.Count)];
items.Add(value);
}
return items;
}
You can later use it like this:
string[] randomIds = GetUniqueRandomItems(4, WordsList).ToArray();
word11.Text = randomIds[0];
word12.Text = randomIds[1];
word13.Text = randomIds[2];
word14.Text = randomIds[3];
call a method like the following:
private int CreateUniqueRandom(int min, int max, ICollection<int> existingNums)
{
var rnd = new Random();
var newNum = rnd.Next(min, max);
while (existingNums.Contains(newNum))
newNum = rnd.Next(min, max);
return newNum;
}
Passing through a list of the numbers that you have created so far
You probably won't need this, but just to show a method of unique random number generation with no duplicate check:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var randoms = GenerateRandoms(10, 1, 10).OrderBy(v => v);
foreach (var random in randoms)
{
Console.WriteLine(random);
}
Console.ReadLine();
}
private static int[] GenerateRandoms(int randomCount, int min, int max)
{
var length = max - min + 1;
if (randomCount > length) { throw new ArgumentException($"Cannot generate {randomCount} random numbers between {min} and {max} (inclusive)."); }
var values = new List<int>(length);
for (var i = 0; i < length; i++)
{
values.Insert(i, min + i);
}
var randomGenerator = new Random();
var randoms = new List<int>();
for (var i = 0; i < randomCount; i++)
{
var val = randomGenerator.Next(0, values.Count);
randoms.Add(values[val]);
values.RemoveAt(val);
}
return randoms.ToArray();
}
}
}

Categories

Resources