Exclude range within random range between negative and positive float. c# unity - c#

Have a variable that I give a random range between -2.0f and 2.0f (Random.Range(-2.0f, 2.0f); in Unity/C#), but I don't want it to pick a number between -0.6f and 0.6f.
How do I got about doing this?
http://docs.unity3d.com/ScriptReference/Random.Range.html

The loop thing seems kind of wasteful to me. After all, you could end up generating a bunch of unusable numbers. Why not generate two numbers from the two allowable ranges, then select at random one to return?
var candidates = new[] {Random.Range(-2.0f, -0.6f), Random.Range(0.6f, 2.0f)};
return candidates.OrderBy(c => Random.Next()).First();
You could also do one random number and then decide at random if you should return it as a negative or positive number. The code below also avoids the OrderBy method, which I am informed will not work with Unity and iOS
var rand = Random.Range(0.6f, 2.0f);
if (Random.Range(0, 100) <= 50)
{
rand = rand * -1f;
}
return rand;
Also, though it is, strictly speaking, outside the scope of your original question, if you cannot assume that both ranges are the same (in absolute values), and you also cannot use OrderBy, you could write something like this:
var candidates = new[] {Random.Range(-2.0f, -0.6f), Random.Range(0.6f, 2.0f)};
var idx = Random.Range(0,2); // this should give you either 0 or 1
return candidates[idx];

How about:
var delta = Random.Range(-1.4f, 1.4f);
var result = delta >= 0 ? delta + 0.6f : delta - 0.6f;
Or perhaps:
var delta = Random.Range(-1.4f, 1.4f);
var abs = Mathf.Abs(delta);
var sign = Mathf.Sign(delta);
var result = sign*(0.6f + abs);
EDIT: Changed things around to avoid using the ternary operator and removed a remark that was wrong. Thanks #sh1!

Casey's answer looks fine. I just don't know how to make it work with other values. The answer below provides a re-usable code that will easily work with other values.
It generates the first number. That number it will use to determine if it should generate a number below the min number you want to exclude or a number above the max number you want to exclude. The second number it generates is the actual random number.
float Range(float min, float max, float ignoreFrom, float ignoreToEnd)
{
int dirRand = UnityEngine.Random.Range(1, 3); //1 to 2 random number
float finalRandomNum = 0;
//If 1, generate number below ignoreFrom, min
if (dirRand == 1)
{
ignoreFrom += 0.00001f; //To make make ignoreFrom exclusive (Remove if you want to include it)
finalRandomNum = UnityEngine.Random.Range(ignoreFrom, min);
}
//If 2, generate number above ignoreToEnd
else if (dirRand == 2)
{
ignoreToEnd += 0.00001f; //To make make ignoreToEnd exclusive (Remove if you want to include it)
finalRandomNum = UnityEngine.Random.Range(ignoreToEnd, max);
}
return finalRandomNum;
}
To test it lets generate 80 random numbers between -2.0f and 2.0f with exclusion min -0.6f and max 0.6f.
for (int i = 0; i < 80; i++)
{
Debug.Log(Range(-2f, 2f, -0.6f, 0.6f));
}

Related

Generating random number between two numbers and ignoring numbers from the set

I am looking to generate a random number between two 2 digit numbers and if the random number is one of the numbers from the set, then ignore this number and re-randomize it. I am facing issue with the looping part. That is, if the number from the list is found twice, it would not loop again.
How do I achieve this?
public float[] nums_to_ignore = new float[] {64.0f, 80.0f, 90.0f, 92.0f, 85.0f, 73.0f, 86.0f};
public float random_num;
void Start()
{
random_num = Random.Range(60.0f, 95.0f);
for(int i = 0; i<nums_to_ignore.Length;i++)
{
if(random_num == nums_to_ignore[i])
{
random_num = Random.Range(60.0f, 95.0f); //Keep looping till you get a number not from the list
}
}
}
It would be more efficient to use a HashSet<float> for your numbers to ignore, as HashSet<T>.Contains has O(1) complexity.
You can continue to repeat your random generation using a do..while loop:
public HashSet<float> nums_to_ignore
= new HashSet<float> { 64.0f, 80.0f, 90.0f, 92.0f, 85.0f, 73.0f, 86.0f };
public float random_num;
void Start()
{
do random_num = Random.Range(60.0f, 95.0f);
while (nums_to_ignore.Contains(random_num));
}
The above would still work if you keep nums_to_ignore as a float[], but Enumerable.Contains has O(n) complexity.
Update
As pointed out by #derHugo, float values are approximations, and equality comparisons may yield incorrect results.
Fortunately, Unity has a method that is designed for comparing floats in this way: Mathf.Approximately:
void Start()
{
do random_num = Random.Range(60.0f, 95.0f);
while (nums_to_ignore.Any(n => Mathf.Approximately(n, random_num)));
}
Unfortunately, this means that GetHashCode cannot be used reliably with float, and the benefits of hashing cannot be achieved.
id go about it like this
Random random = new Random();
List<int> numbers = new List<int>();
int min = 60, max = 95;
for (int i = 0; i <= 100; i++)
{
int num;
do
num = random.Next(min,max);
while (numbers.Contains(num));
numbers.Add(num);
}
you can replace random.next by unitys Random.Range, i was sleeping on you using unity. the for i 0 to 100 loop was just there as an example how to fill the list you can easily replace it with a methode instead

Setting random variables C# that equal add up to a total

I'm creating a game in which someone opens a chest and the chest will give them a random prize. The maximum I can give out is 85,000,000 in 10,000 chests which is 8,500 average however I want some to make it so some chests will be below this value and above and to be able to set a min lose of 2,500 and max win 250,000 but still get the total value of 85,000,000.
I'm really struggling to come up with an algorithm for this using my C# knowledge.
Here goes some OOP. You have Player class. Which stores some info - amount of gold he has, chests left to open, and total amount of gold in chests he will find.
public class Player
{
private int gold = 0;
private int goldLeftInChests = 85000000;
private int chestsToOpen = 10000;
private Random random = new Random();
public void OpenChest()
{
if (chestsToOpen == 0)
return; // or whatever you want after 10000 chests.
int goldInChest = CalculateGoldInNextChest();
goldLeftInChests -= goldInChest;
chestsToOpen--;
gold += goldInChest;
}
private int CalculateGoldInNextChest()
{
if (chestsToOpen == 1)
return goldLeftInChests;
var average = goldLeftInChests / chestsToOpen;
return random.Next(average);
}
}
When next chest is opened, gold in chest is calculated and player data ajusted - we add some gold to player and reduce total amount of gold in chests, and chests left to open.
Calculating gold in a chest is very simple. We get average amount left and calculate number between 1 and average. First time this value will always be below 8500. But next time average will be little bit bigger. So player will have chance to find more than 8500. If he will be unlucky again, average will grow. Or it will be reduced if palyer gets lot of gold.
UPDATE: As #Hans pointed, I didn't count min and max restrictions for gold in chests. Also there is a problem in #Hans solution - you should move gold between 10000 chests lot of time to get some chests close to 250000 value. And you have to fill and keep all 10000 values. Next problem I thought about was random numbers distribution in .NET. Values have equal probability on all interval we are using. So if we are generating value from 2500 to 250000, chance that we'll get value around 8500 (average) is like 12000 (8500±6000) vs 235500 (250000-12000-2500). That means generating default random numbers from given range will give you lot of big numbers in the begining, and then you will stick near lowest boundary (2500). So you need random numbers with different distribution - Gaussian variables. We still want to have 8500 gold with highest probablity, and 250000 with lowest probability. Something like that:
And last part - calculation. We need to update only one method :)
private int CalculateGoldInNextChest()
{
const int mean = 8500;
var goldPerChestRange = new Range(2500, 250000);
var averageRange = new Range(mean - 2500, mean + 2500);
if (chestsToOpen == 1)
return goldLeftInChests;
do
{
int goldInChest = (int)random.NextGaussian(mu: mean, sigma: 50000);
int averageLeft = (goldLeftInChests - goldInChest) / (chestsToOpen - 1);
if (goldPerChestRange.Contains(goldInChest) && averageRange.Contains(averageLeft))
return goldInChest;
}
while (true);
}
Note: I used range to make code more readable. Running tests several times produces nice top values more than 200000.
pseudocode algoritm:
use an array of chests
index of array is chest number; length of array is amount of chests
value in array is amount in chest at that index
initial value is total amount divided by number of chests
now repeat a number of times (say: 10 times the number of chests)
get two random chests
work out the maximum amount you can transfer from chest 1 to chest 2, so that 1 doesn't get below the minimum and 2 doesn't get above the maximum
get a random value below that maximum and transfer it
Now try and implement this in C#.
This should be a good starting point. Each chest gets filled randomly with the limits adapting to make sure the remaining chests can also get valid values.
Random rand = new Random();
int[] chests = new int[numOffChests];
int remaining = TotalValue;
for(int i = 0; i < numOffChests; i++)
{
int minB = Math.Max(remaining / (numOffChests - i), maxChestValue);
int maxB = Math.Min(remaining - (numOffChests - i * minChestValue), maxChestValue);
int val = rand.Next(minB, maxB);
remaining -= val;
chests[i] = val;
}
The distribution has to be heavily skewed to get that range of values with that mean. Try an exponential formula, X=exp(a*U+b)+c where U is uniform on [0,1]. Then the conditions are
-2,500 = exp(b)+c
250,000 = exp(a+b)+c
8,500 = integral(exp(a*u+b), u=0..1)
= exp(b)/a*(exp(a)-1)+c
= 252,500/a+c
which gives the two equations
250,000+2,500*exp(a) = c*(1-exp(a))
8,500 = 252,500/a+c
A bit of graphical and numerical solution gives the "magic" numbers
a = 22.954545,
b = -10.515379,
c = -2500.00002711621
Now fill 10,000 chests according to that formula, compute the sum over the chest prices and distribute the, with high probability small, excess in any pattern you like.
If you want to hit the upper and lower bounds more regularly, increase the bounds at the basis of the computation and cut the computed value if outside the original bounds.
I assume that a probabilistic function gives the chance of a win/lose value V to occur. Let's say that the probability for V is proportional to (250000-V)**2, giving fewer chances to get high prizes.
To simplify some rounding issues, let's also assume that win/lose are multiple of 100. You may then make the following (untested) computations:
int minwin = -2500 ;
int maxwin = 250000;
int chestcount = 10000;
int maxamount = 85000;
// ----------- get probabilities for each win/lose amount to occur in all chests ----------
long probtotal = 0 ;
List<long> prob = new List<long> ;
for (long i=minwin;i<=maxwin;i++) if (i%100==0)
{ long ii=(maxwin-i)*(maxwin-i) ; prob.Add((float)ii) ; probtotal+=ii ; }
for (int i=0;i<prob.Count;i++) prob[i]=prob[i]/(probtotal) ;
for (int i=0;i<prob.Count;i++)
Console.writeLine("Win/lose amount"+((i-minwin)*100).ToString()+" probability="+(proba[i]*100).ToString("0.000")) ;
// Transform "prob" so as to indicate the float count of chest corresponding to each win/lose amount
for (int i=0;i<prob.Count;i++) prob[i]=prob[i]*chestcount ;
// ---------- Set the 10000 chest values starting from the highest win -------------
int chestindex=0 ;
List<int> chestvalues = new List<int>();
float remainder = 0 ;
int totalwin=0 ;
for (int i=0;i<prob.Count;i++)
{
int n = (int)(prob[i]+remainder) ; // only the integer part of the float ;
remainder = prob[i]+remainder-n ;
// set to n chests the win/lose amount
int curwin=(i-minwin)*100 ;
for (int j=0;j<n && chestvalues.count<chestcount;j++) chestvalues.Add(curwin) ;
totalwin+=curwin ;
}
// if stvalues.count lower than chestcount, create missing chestvalues
for (int i=chestvalues.Count;i<chestcount;i++) chestvalues.Add(0) ;
// --------------- due to float computations, we perhaps want to make some adjustments --------------
// i.e. if totalwin>maxamount (not sure if it may happen), decrease some chestvalues
...
// --------------- We have now a list of 10000 chest values to be randomly sorted --------------
Random rnd = new Random();
SortedList<int,int> randomchestvalues = new SortedList<int,int>() ;
for (int i=0;i<chestcount;i++) randomchestvalues.Add(rnd.Next(0,99999999),chestvalues[i]) ;
// display the first chests amounts
for (int i=0;i<chestcount;i++) if (i<234)
{ int chestamount = randomchestvalues.GetByIndex(i) ; Console.WriteLine(i.ToString()+":"+chestamount) ; }
}

Why is there a strong correlation between random number and correct guesses?

In trying to test whether knowing the history of a random number could help predict the future results, I found a strong, unexpected correlation between the average of the number generated, and the number of correct guesses.
The test was supposed to simulate flipping a coin (heads = 0, tails = 1) and if previous attempts were biased towards heads then guess tails and vice versa.
Why is the sum of the generated numbers always nearly equal to the number of correct guesses in the following LinqPad program?
void Main()
{
var rnd = new Random();
var attempts = 10000000;
var correctGuesses = 0;
long sum = 0;
decimal avg = 0.5m;
for (int i = 0; i < attempts; i++)
{
var guess = avg < 0.5m ? 1 : 0;
var result = rnd.Next(0, 2);
if (guess == result)
{
correctGuesses += 1;
}
sum += result;
avg = (decimal)sum/(decimal)attempts;
}
attempts.Dump("Attempts");
correctGuesses.Dump("Correct Guesses");
avg = (decimal)sum / (decimal)attempts;
avg.Dump("Random Number Average");
}
Have a made an error in the code? Is this a natural relationship? I expected the averages to converge at 0.5 as I increased the number of attempts because the distribution is fairly even - I tested this with 10bn calls to Random.Next(0,2) - but I did not expect the sum of generated numbers to correlate to the number of correct guesses.
Your error is this line:
avg = (decimal)sum/(decimal)attempts;
Makes no sense to divide the sum (based over i to that point) by attempts. Divide by i (EDIT: more precisely i+1) instead for avg to give you something meaningful.
The Random class, without a seed, generates a random number using the current time as seed, meaning that a call of the rnd.Next method in your cycle will result in the same number several times over, depending on how fast your machine goes through the cycle.

What wrong with this implement of this arcsine approximate in C#

This is a formula to approximate arcsine(x) using Taylor series from this blog
This is my implementation in C#, I don't know where is the wrong place, the code give wrong result when running:
When i = 0, the division will be 1/x. So I assign temp = 1/x at startup. For each iteration, I change "temp" after "i".
I use a continual loop until the two next value is very "near" together. When the delta of two next number is very small, I will return the value.
My test case:
Input is x =1, so excected arcsin(X) will be arcsin (1) = PI/2 = 1.57079633 rad.
class Arc{
static double abs(double x)
{
return x >= 0 ? x : -x;
}
static double pow(double mu, long n)
{
double kq = mu;
for(long i = 2; i<= n; i++)
{
kq *= mu;
}
return kq;
}
static long fact(long n)
{
long gt = 1;
for (long i = 2; i <= n; i++) {
gt *= i;
}
return gt;
}
#region arcsin
static double arcsinX(double x) {
int i = 0;
double temp = 0;
while (true)
{
//i++;
var iFactSquare = fact(i) * fact(i);
var tempNew = (double)fact(2 * i) / (pow(4, i) * iFactSquare * (2*i+1)) * pow(x, 2 * i + 1) ;
if (abs(tempNew - temp) < 0.00000001)
{
return tempNew;
}
temp = tempNew;
i++;
}
}
public static void Main(){
Console.WriteLine(arcsin());
Console.ReadLine();
}
}
In many series evaluations, it is often convenient to use the quotient between terms to update the term. The quotient here is
(2n)!*x^(2n+1) 4^(n-1)*((n-1)!)^2*(2n-1)
a[n]/a[n-1] = ------------------- * --------------------- -------
(4^n*(n!)^2*(2n+1)) (2n-2)!*x^(2n-1)
=(2n(2n-1)²x²)/(4n²(2n+1))
= ((2n-1)²x²)/(2n(2n+1))
Thus a loop to compute the series value is
sum = 1;
term = 1;
n=1;
while(1 != 1+term) {
term *= (n-0.5)*(n-0.5)*x*x/(n*(n+0.5));
sum += term;
n += 1;
}
return x*sum;
The convergence is only guaranteed for abs(x)<1, for the evaluation at x=1 you have to employ angle halving, which in general is a good idea to speed up convergence.
You are saving two different temp values (temp and tempNew) to check whether or not continuing computation is irrelevant. This is good, except that you are not saving the sum of these two values.
This is a summation. You need to add every new calculated value to the total. You are only keeping track of the most recently calculated value. You can only ever return the last calculated value of the series. So you will always get an extremely small number as your result. Turn this into a summation and the problem should go away.
NOTE: I've made this a community wiki answer because I was hardly the first person to think of this (just the first to put it down in a comment). If you feel that more needs to be added to make the answer complete, just edit it in!
The general suspicion is that this is down to Integer Overflow, namely one of your values (probably the return of fact() or iFactSquare()) is getting too big for the type you have chosen. It's going to negative because you are using signed types — when it gets to too large a positive number, it loops back into the negative.
Try tracking how large n gets during your calculation, and figure out how big a number it would give you if you ran that number through your fact, pow and iFactSquare functions. If it's bigger than the Maximum long value in 64-bit like we think (assuming you're using 64-bit, it'll be a lot smaller for 32-bit), then try using a double instead.

Compare each element in an array to each other

I need to compare a 1-dimensional array, in that I need to compare each element of the array with each other element. The array contains a list of strings sorted from longest to the shortest. No 2 items in the array are equal however there will be items with the same length. Currently I am making N*(N+1)/2 comparisons (127.8 Billion) and I'm trying to reduce the number of over all comparisons.
I have implemented a feature that basically says: If the strings are different in length by more than x percent then don't bother they not equal, AND the other guys below him aren't equal either so just break the loop and move on to the next element.
I am currently trying to further reduce this by saying that: If element A matches element C and D then it stands to reason that elements C and D would also match so don't bother checking them (i.e. skip that operation). This is as far as I've factored since I don't currently know of a data structure that will allow me to do that.
The question here is: Does anyone know of such a data structure? or Does anyone know how I can further reduce my comparisons?
My current implementation is estimated to take 3.5 days to complete in a time window of 10 hours (i.e. it's too long) and my only options left are either to reduce the execution time, which may or may not be possible, or distrubute the workload accross dozens of systems, which may not be practical.
Update: My bad. Replace the word equal with closely matches with. I'm calculating the Levenstein distance
The idea is to find out if there are other strings in the array which closely matches with each element in the array. The output is a database mapping of the strings that were closely related.
Here is the partial code from the method. Prior to executing this code block there is code that loads items into the datbase.
public static void RelatedAddressCompute() {
TableWipe("RelatedAddress");
decimal _requiredDistance = Properties.Settings.Default.LevenshteinDistance;
SqlConnection _connection = new SqlConnection(Properties.Settings.Default.AML_STORE);
_connection.Open();
string _cacheFilter = "LevenshteinCache NOT IN ('','SAMEASABOVE','SAME')";
SqlCommand _dataCommand = new SqlCommand(#"
SELECT
COUNT(DISTINCT LevenshteinCache)
FROM
Address
WHERE
" + _cacheFilter + #"
AND
LEN(LevenshteinCache) > 12", _connection);
_dataCommand.CommandTimeout = 0;
int _addressCount = (int)_dataCommand.ExecuteScalar();
_dataCommand = new SqlCommand(#"
SELECT
Data.LevenshteinCache,
Data.CacheCount
FROM
(SELECT
DISTINCT LevenshteinCache,
COUNT(LevenshteinCache) AS CacheCount
FROM
Address
WHERE
" + _cacheFilter + #"
GROUP BY
LevenshteinCache) Data
WHERE
LEN(LevenshteinCache) > 12
ORDER BY
LEN(LevenshteinCache) DESC", _connection);
_dataCommand.CommandTimeout = 0;
SqlDataReader _addressReader = _dataCommand.ExecuteReader();
string[] _addresses = new string[_addressCount + 1];
int[] _addressInstance = new int[_addressCount + 1];
int _itemIndex = 1;
while (_addressReader.Read()) {
string _address = (string)_addressReader[0];
int _count = (int)_addressReader[1];
_addresses[_itemIndex] = _address;
_addressInstance[_itemIndex] = _count;
_itemIndex++;
}
_addressReader.Close();
decimal _comparasionsMade = 0;
decimal _comparisionsAttempted = 0;
decimal _comparisionsExpected = (decimal)_addressCount * ((decimal)_addressCount + 1) / 2;
decimal _percentCompleted = 0;
DateTime _startTime = DateTime.Now;
Parallel.For(1, _addressCount, delegate(int i) {
for (int _index = i + 1; _index <= _addressCount; _index++) {
_comparisionsAttempted++;
decimal _percent = _addresses[i].Length < _addresses[_index].Length ? (decimal)_addresses[i].Length / (decimal)_addresses[_index].Length : (decimal)_addresses[_index].Length / (decimal)_addresses[i].Length;
if (_percent < _requiredDistance) {
decimal _difference = new Levenshtein().threasholdiLD(_addresses[i], _addresses[_index], 50);
_comparasionsMade++;
if (_difference <= _requiredDistance) {
InsertRelatedAddress(ref _connection, _addresses[i], _addresses[_index], _difference);
}
}
else {
_comparisionsAttempted += _addressCount - _index;
break;
}
}
if (_addressInstance[i] > 1 && _addressInstance[i] < 31) {
InsertRelatedAddress(ref _connection, _addresses[i], _addresses[i], 0);
}
_percentCompleted = (_comparisionsAttempted / _comparisionsExpected) * 100M;
TimeSpan _estimatedDuration = new TimeSpan((long)((((decimal)(DateTime.Now - _startTime).Ticks) / _percentCompleted) * 100));
TimeSpan _timeRemaining = _estimatedDuration - (DateTime.Now - _startTime);
string _timeRemains = _timeRemaining.ToString();
});
}
InsertRelatedAddress is a function that updates the database, and there are 500,000 items in the array.
OK. With the updated question, I think it makes more sense. You want to find pairs of strings with a Levenshtein Distance less than a preset distance. I think the key is that you don't compare every set of strings and rely on the properties of Levenshtein distance to search for strings within your preset limit. The answer involves computing the tree of possible changes. That is, compute possible changes to a given string with distance < n and see if any of those strings are in your set. I supposed this is only faster if n is small.
It looks like the question posted here: Finding closest neighbour using optimized Levenshtein Algorithm.
More info required. What is your desired outcome? Are you trying to get a count of all unique strings? You state that you want to see if 2 strings are equal and that if 'they are different in length by x percent then don't bother they not equal'. Why are you checking with a constraint on length by x percent? If you're checking for them to be equal they must be the same length.
I suspect you are trying to something slightly different to determining an exact match in which case I need more info.
Thanks
Neil

Categories

Resources