Creating random data C# - c#

I'm trying to create a heart beat monitor and need some false information to feed into my program to check it works.
One of the requirements of my program is to set off an alarm if the heart rate integer drops below a certain figure.
However the problem I have is, if the heart rate drops below the minimum heart rate integer before it sets off the alarm and then bounces straight back up again, that's not a test for the alarm, see pseudo code:
If heart_rate < 60:
Count = Count + 1
If heart _rate > 60:
Reset Count variable
If Count = 10:
Set off alarm
So I need to generate data where it simulates a heart rate dropping below 60 (patient dieing).
Would the quickest way to do this create a list or text file that the program reads from to simulate the situation or is there a funky way that can simulate random numbers that can set off the alarm and will stay around that integer once the alarm has been set off?
Sorry if this sounds super confusing, I'm a python programmer, having to program in C#.

Your problem seams to be a "data stream testing" one. Manually generating data streams is not something I would personally characterize as "quick" (or even "sane"...). You either get an existing data set or you generate it (but not manually, unless it's a relatively small number of data points: 20~30).
That being said, you need to test something, which means you need to know that when a target case (heart rate below 60), your system correctly catches the "exception". This, in turn, means that you need to know where it happens, i.e. the exact moment in time.
My advice is: automatically generate a data set, full of "normal" values. At different time point (i.e. position in the data stream) manually insert "exception" values (59), with different frequencies, to see if the system resets the counter (less than 10 occurrences) or if it raises an alarm (at least 10 occurrences).
You would then have the following initial data stream:
80 78 78 81 80 81 ... 75 76 80 // say 100 values ("data points")
// 0 1 2 3 4 5 97 98 99 -> indices ("time points")
If you insert a "false alarm" (80 78 59 59 78) at index 2, for example, you know that your system should reset the counter. You would do the same for the "alarm"; you'll know the index (time point) when the alarm should be raised.
Edit: the part about "manually" inserting the targeted values should be interpreted as "empirically selecting an index of an array (or a list) filled with random data, where the data should be inserted".

public static class HeartrateGenerator
{
static Random random = new Random();
public static IEnumerable<int> GenerateHeartrate(
int totalSequenceLength,
int dropsBelow60After,
int bouncesBackAfter = -1)
{
// NOTE: check input data
int i = 0;
// return values > 60
while (i < dropsBelow60After)
{
i++;
yield return 60 + random.Next() % 60;
}
if (bouncesBackAfter > 0)
// return values < 60
while (i < bouncesBackAfter)
{
i++;
yield return random.Next() % 60;
}
// return normal values again
while (i < totalSequenceLength)
{
i++;
yield return 60 + random.Next() % 60;
}
}
}
example usage:
HeartrateGenerator.GenerateHeartrate(100, 20, 28);

Try this: you might want to change the rand.Next(min,max) to use normal range of heartbeat or whatever range you want to see for a heartbeat value.
public class Program
{
public void Main(string[] args)
{
Random rand = new Random();
bool flag = true;
int count = 0;
while (flag)
{
if(count==10)
{
Console.WriteLine ("dead");
flag = false;
break;
}
Thread.Sleep(1000);
var heart_rate = rand.Next(50,70);
Console.WriteLine (heart_rate);
if (heart_rate < 60)
{
count++;
}
else if(heart_rate > 60)
{
count=0;
}
}
}
}

Related

C#: Random.NextDouble method stalling out application

I'm receiving some inconsistent behavior from the Random.NextDouble().
Regularly, the console would freeze and the cpu usage would increase dramatically until I closed it down. I ran the debugger and found that the cause of the freezing was Random.NextDouble(). I added some lines for debugging purposes, but the code is as follows:
double generateCatenationRate()
{
double catenation = 999.999; //random value to see if it pops up down there
double uniformValue;
double int_covalence = covalence.original;
double dist = int_covalence - 4;
int counter = 0;
while (true)
{
counter++;
uniformValue = utils.getRandomDouble(); //goes bad here!
if (uniformValue <= 0.15)
{
catenation = Math.Abs(utils.normalize(dist, 0, 4)) + uniformValue;
if (catenation < 0 || catenation > 1)
{
if (counter > 10000)
{
Console.WriteLine("Went nuclear!");
break; //break so console doesn't stall out
}
continue;
}
else
{
break;
}
}
}
Console.WriteLine("Took "+counter+" iterations.");
return 1 - catenation;
}
And:
public static double getRandomDouble()
{
Init();
return random.NextDouble();
}
Lastly:
private static void Init()
{
if (random == null) random = new Random();
}
It typically does not stall out, but running it several times successively produces output such as:
Took 4 iterations.
Took 3 iterations
Took 3 iterations.
Took 23 iterations.
Took 12 iterations.
Took 4 iterations.
Went nuclear!
Took 10007 iterations.
Can anyone explain why Random.NextDouble() occasionally seems to create an infinite loop? Looking around, I suspect it has something to do with how the values are seeded, but any insight would be appreciated; would love to fix this issue.
Thank you!
EDIT: covalence.original is always an integer between 1 and 8 (inclusive). normalize() performs min-max normalization, producing a number from 0-1 based on an input and a range. Neither of these seem to contribute to the problem, however.
If I understand correctly then the value of dist and utils.normalize(dist, 0, 4) never changes.
So if int_covalence = 8 then dist = 4 and utils.normalize(dist, 0, 4) = 1, correct?
Since the chance of generating 0.0 is pretty small, that will make catenation virtually always greater than 1 and the check if (catenation < 0 || catenation > 1) always true.
why not just generate the samples directly rather than using rejection sampling?
public static double generateCatenationRate(Random rng, double coval_norm) {
double low = Math.abs(coval_norm) + 0.15;
double delta = 1. - low;
if (delta < 0) {
throw new IllegalArgumentException("impossible given covalence");
}
return low + delta * rng.nextDouble();
}
where coval_norm is whatever you get back from utils.normalize. if we write it this way we get visibility of the "impossible" condition and can do something about it, rather than just looping.

Coin Change Using Loops Only, No Arrays in C#

So I've very recently got started with programming due to one of the subjects I have at class. So far the most complex thing we've learned are loops, we know if, while and for SO I have to manage with that.
In this exercise, program asks for the price of whatever you bought and the money spent. We have unlimited coins of value 50 20 10 5 2 and 1. If you spent more than it costs, the program will give you the change.
Example:
Price?: 44.
Money Paid?: 100.
Change is 56: 50 5 1.
I've tried making it simpler so that I can jump into the whole thing after understanding it.
My logic here was trying to make a similar program but using one coin only.
int change, payment, price;
if (payment == price)
{
Console.Write("\nNo change needed, you paid with the exact value.\n");
}
else if (payment < price)
{
Console.Write("\nNot enough money.\n");
}
else {
Console.Write("Change is {0}: ", change);
for (loop100 = 0; loop100 < change; loop100+=100)
{
if (change > 100)
{
Console.Write(100 + (" "));
}
}
}
Program prints
Price?: 1000 /*lets say 1000*/
Payment?: 1500
Change is 500: 100 100 100 100 100
Now, on paper this is what I am looking for. But the issue comes when setting another for loop with a different value. If I copy my for/if loop but changing loop100 for lets say a loop50and setting it up for a 50 value coin, first of all, it goes on a endless loop of 50s, and secondly I wouldnt get the most efficient combination of 100+50 coins but the change with each type of coin. I'd get 100- 5 times and 50- 10 times.
I've put literally hours into this problem and I can't solve it. I looked online and everyone seems to do it with Arrays but my teacher does not want us to use Arrays as we havent learned yet about those and it is much more simple than that.
What kind of for loop should I try? Did I make it too much harder than it already is?
My personal preference is to use a while loop for this. Inside that you can use an embedded if statement or for loop to move through a range of coins values:
/// <summary>
/// Ouput the change from the paid amount for the specified price.
/// </summary>
/// <param name="price">An int indicating the price of the purchase.</param>
/// <param name="paid">An int indicating the total amount paid.</param>
private static void outputChange(int price, int paid)
{
int change = paid - price;
int[] coins = new[] { 50, 20, 10, 5, 2, 1 };
if (change > 0)
{
Console.Write("Change: ");
while (change > 0)
{
for (int i = 0; i < coins.Length; i++)
{
if (change >= coins[i])
{
Console.Write($"{coins[i]} ");
change -= coins[i];
break;
}
}
}
Console.Write("\n");
}
else
Console.WriteLine("No change needed, you paid with the exact value.");
}
This works by finding the next coin that is larger than or equal to the remaining change, and then subtracting that value from change. The while loop will continue until change is 0 (or below, which is not expected).
Examples:
outputChange(44, 100);
outputChange(23, 100);
outputChange(86, 100);
outputChange(100, 100);
Output:
Change: 50 5 1
Change: 50 20 5 2
Change: 10 2 2
No change needed, you paid with the exact value.
Edit following OP comment
OP added a comment stating that they would prefer not to use an array for the coins:
Could the same while+for statement be used by creating a value for
every single type of coin? I really have to avoid using arrays for now
In this case, another (less elegant) method could use an if statement that calculated the next coin, see below:
/// <summary>
/// Ouput the change from the paid amount for the specified price.
/// </summary>
/// <param name="price">An int indicating the price of the purchase.</param>
/// <param name="paid">An int indicating the total amount paid.</param>
private static void outputChange(int price, int paid)
{
int change = paid - price;
if (change > 0)
{
Console.Write("Change: ");
while (change > 0)
{
int coin = 0;
if (change >= 50)
coin = 50;
else if (change >= 20)
coin = 20;
else if (change >= 10)
coin = 10;
else if (change >= 5)
coin = 5;
else if (change >= 2)
coin = 2;
else if (change >= 1)
coin = 1;
if (coin > 0)
{
Console.Write($"{coin} ");
change -= coin;
}
else
break;
}
Console.Write("\n");
}
else
Console.WriteLine("No change needed, you paid with the exact value.");
}
While loop would probably work better. Keep track of how much change is left to deliver.
while( change >= 100)
{
change -= 100;
Console.Write(100 + (" "));
}
while( change >= 50)
{
change -= 50;
Console.Write(50 + (" "));
}

c# Normalized power for Polar cycle

List<int> NPower = new List<int>();
List<double> list = new List<double>();
try
{
for (int i = 1; i < dataGridView1.Rows.Count; i++)
{
for (int n = 0; n < i + 30; n++)
{
NPower.Add(Convert.ToInt32(dataGridView1.Rows[i + n].Cells[6].Value));
}
}
average = NPower.Average();
total = Math.Pow(average, 4);
NPower.Clear();
}
catch (Exception)
{
average = NPower.Average();
NP = Convert.ToInt32(Math.Pow(average, (1.0 / 3.0)));
label19.Text = "Normalised Power: " + NP.ToString();
NPower.Clear();
}
Hi so i'm trying to calculate the normalized power for a cycling polar cycle. I know that for the normalized power you need to:
1) starting at the 30 s mark, calculate a rolling 30 s average (of the preceeding time points, obviously).
2) raise all the values obtained in step #1 to the 4th power.
3) take the average of all of the values obtained in step #2.
4) take the 4th root of the value obtained in step #3.
I think i have done that but the normalized power comes up with 16 which isnt correct. Could anyone look at my code to see if they could figure out a solution. Thankyou, sorry for my code i'm still quite new to this so my code might be in the incorrect format.
I'm not sure that I understand your requirements or code completely, but a few things I noticed:
Since you're supposed to start taking the rolling average after 30 seconds, shouldn't i be initialized to 30 instead of 1?
Since it's a rolling average, shouldn't n be initialized to the value of i instead of 0?
Why is the final result calculated inside a catch block?
Shouldn't it be Math.Pow(average, (1.0 / 4.0)) since you want the fourth root, not the third?

Printing an array in a method, from a different class?

I'm a fairly inexperienced programmer, and i'm currently working on a Console Application project. It's basically a little 'mathematics game'; the application generates two random numbers, that have either been added, subtracted, multiplied or divided against each other randomly. The answer is shown on screen and the user has to pick from the menu which is the right mathematical operator, once the correct answer is picked the application then displays on screen how long it took for the user in milliseconds to input the correct answer.
Now I want to save the times of the players in an array that can be called up later with all the scores. I need to include a method in this programme and I figured a method to save the times into an array would be suitable. I seem to have stumbled across a little problem though.
I'm not quite sure what's wrong:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Mathgame
{
class Program
{
}
class arrayclass
{
public static void saveInArray(int duration)
{
int[] TopTenScores = {000,1000,2000,3000,4000,5000,6000,7000,8000,9000};
if (duration < 1000)
{
duration = TopTenScores[000];
}
else if ((duration >= 1000) && (duration <= 1999))
{
duration = TopTenScores[1000];
}
else if ((duration >= 2000) && (duration <= 2999))
{
duration = TopTenScores[2000];
}
else if ((duration >= 3000) && (duration <= 3999))
{
duration = TopTenScores[3000];
}
else if ((duration >= 4000) && (duration <= 4999))
{
duration = TopTenScores[4000];
}
else if ((duration >= 5000) && (duration <= 5999))
{
duration = TopTenScores[5000];
}
else if ((duration >= 6000) && (duration <= 6999))
{
duration = TopTenScores[6000];
}
else if ((duration >= 7000) && (duration <= 7999))
{
duration = TopTenScores[7000];
}
else if ((duration >= 8000) && (duration <= 8999))
{
duration = TopTenScores[8000];
}
else if ((duration >= 9000) && (duration <= 9999))
{
duration = TopTenScores[9000];
}
Console.WriteLine(TopTenScores);
}
static void Main(string[] args)
{
int intInput, num1, num2, incorrect, array1;
float answer;
string input;
System.Random randNum = new System.Random();
Console.WriteLine("Welcome to the Maths game!");
Console.WriteLine("(Apologies for the glitchiness!)");
Console.WriteLine();
Console.WriteLine("Please choose from the following options:");
Console.WriteLine();
retry:
Console.WriteLine("1 - Test your Maths against the clock!");
Console.WriteLine("2 - Exit the application.");
Console.WriteLine("3 - Top scores");
Console.WriteLine();
input = Console.ReadLine();
intInput = int.Parse(input);
if (intInput == 1)
{
goto start;
}
else if (intInput == 2)
{
goto fin;
}
else if (intInput == 3)
{
array1 = array1.saveInArray;
goto retry;
}
Now, in the last 'else if' statement in the code, you can see my variable array1 trying to call the method, but no matter what I do I keep getting errors.
This is the only error I have at the moment, but I have a feeling soon as I resolve that error, another will come up. For now i'm just determined to get past this error:
'int' does not contain a definition for 'saveInArray' and no extension method 'saveInArray' accepting a first argument of type 'int' could be found (are you missing a using directive or an assembly reference?).
Any help would be kindly appreciated, apologies in advanced for my ugly written code! And thank you to any help that I receive!
Regards,
Omar.
Okay,
There's quite a bit in this code that needs fixing TBH.
I'll start with your error first:
You are declaring the variable array1 as an integer. In C# integers are primitive types. This means that they have no methods and no class members. So, when you call array1.saveInArray the compiler is basically saying "the type integer doesn't have any methods... I can't find an appropriate method to match your call".
Instead of calling array1.saveInArray I think what you meant to call was arrayclass.saveInArray(x).
Notice the x in that call above. I'm passing a variable called x which is of type int into the function saveInArray().
This brings us to the second error. If saveInArray was a property, then you could just go arrayclass.saveInArray. However, it is a function which requires an argument... namely an integer.
When you call arrayclass.saveInArray(someInteger) you are passing someInteger as an argument into a method. The method is then free to use this argument to do its calculations.
That should fix your most basic errors and hopefully you can compile.
Moving on to some other errors that will cause you problems at runtime:
In the method saveInArray you are declaring an integer array called TopTenScores.
You are declaring this fine... however later on when you are indexing into TopTenScores, you are using an index that is way out of range of TopTenScores.
Here is your declaration of TopTenScores:
int[] TopTenScores = {000,1000,2000,3000,4000,5000,6000,7000,8000,9000};
Notice that there are 10 numbers in this declaration. This means that the max index you can have is 9. Why? Because arrays start indexing at 0 in C#.
I think you might be thinking that this array is associative... however this is not the case. When you do TopTenScores[1000] you are saying "give me the value at index 1000". This value does not exists and you will get a runtime error.
Instead, you would want to call TopTenScores[1] if you wanted to access the value 1000.
Also, you are not overwriting the default value with the new top score, rather you are overwriting the new top score with the default value. I don't think this is intended. Instead, switch your calls from this: duration = TopTenScores[1000];
to this: TopTenScores[1] = duration;
Edit: Lastly, as the commenter pointed out, using goto is bad practice and greatly discouraged. You will understand why later on as you start to understand program flow and organization better. For now, it is best to try and avoid the habit of using goto. goto maps to a low level system construct, which we should avoid when using a higher level language like C#. Your code can get confusing and error prone quickly with using goto.
Anyways, feel free to ask more questions/comment etc if you have questions or you need to clarify something. Welcome to StackOverflow!
One of your problems is that you're missing the fundamental concept of what an array is. Think of an array as a row of numbered mailboxes. This line of code:
int[] TopTenScores = {000,1000,2000,3000,4000,5000,6000,7000,8000,9000};
is creating an array of 10 integers that look something like this in memory:
Index Value
----- -----
0 0
1 1000
2 2000
3 3000
4 4000
5 5000
6 6000
7 7000
8 8000
9 9000
It is unclear how this is a useful structure to represent your top scores, and I'm not sure what your saveInArray method is trying to do. In that method, here's how one of your lines of code is interpreted:
duration = TopTenScores[1000];
What that means is "take what's at index 1000 of TopTenScores and store it in duration." As you can see from the table above, there is no index 1000, and besides, that code has nothing to do with saving a top score.
Another problem you're having is that you don't seem to have your algorithm in place for accomplishing the task. For the top ten functionality, try breaking down what needs to be done into instructions on how you would do it manually if you were a score keeper. It would be something like this:
I keep track of the top scores by stacking index cards with the scores on them in order of highest to lowest. At first, I have no index cards.
I can do two things: record scores and tell someone the top ten scores.
When someone asks me to record a score, I:
Write the score down on an index card
Look through the stack of previous scores in order until I find a score lower than this new score.
If I find a score that was lower than this new one, I place the card on top of the lower score.
Otherwise, if this score is the lowest one of the bunch, including if this is the first score recorded, I'll place the score at the bottom of the stack.
When someone asks me to tell them the top 10 scores, I:
Go through the first 10 cards or all the cards if I have less than 10.
For each of those scores, write them down in order.
Give the list of scores to the one requesting them.
It may seem silly to do this, but most programs are really only sequences of simple steps, and as a programmer, you need to be able to determine those steps before translating them into a language that the compiler can understand. Once you formulate the problem in simple terms, you can start translating it into code for example:
// You can think of a role a person would do for a manual process as a class
// in a program.
public class ScoreKeeper
{
// Our high score list (stack of cards) is empty to begin with. Unlike
// arrays, lists allow us insert items rather than placing them in
// numbered slots.
private List<int> _scores = new List<int>();
// This is the method for when someone asks us to record a score. The
// "score" parameter is the new score which you can think of as being
// written on a card.
public void RecordScore(int score)
{
// Go through each of the existing scores. "i" is the index in the
// list.
for (int i = 0; i < _scores.Count; i++)
{
// See if the new score is less than the score at index #i
if (_scores[i] < score)
{
// It is lower than this new score. Insert the new score
// above that score.
_scores.Insert(i, score);
// We're done. Stop looping and exit RecordScore.
return;
}
}
// If we get here, we found no scores lower than this new one. Add
// this score to the bottom of the stack.
_scores.Add(score);
}
// This is the method for when someone asks us for the top 10 scores.
// Notice that we return an array of integers, which will represent
// our piece of paper we hand back to the one requesting the scores.
public int[] GetTop10Scores()
{
// We start with a blank piece of paper.
int[] result = new int[10];
// Go through the scores. The first 10 are the top 10 because
// RecordScore puts them in order. We also need to make sure
// we don't try to get more scores than we've recorded.
for (int i = 0; i < 10 && i < _scores.Count; i++)
{
// Write down the score on the paper
result[i] = _scores[i];
}
// Send back the list of scores to the requester
return result;
}
}
Now, inside of your main program, you can create a ScoreKeeper and ask it to do its score keeping:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to the Maths game!");
Console.WriteLine("(Apologies for the glitchiness!)");
Console.WriteLine();
Console.WriteLine("Please choose from the following options:");
Console.WriteLine();
// This object keeps track of scores
ScoreKeeper scoreKeeper = new ScoreKeeper();
bool keepRunning = true;
while (keepRunning)
{
Console.WriteLine("1 - Test your Maths against the clock!");
Console.WriteLine("2 - Exit the application.");
Console.WriteLine("3 - Top scores");
Console.WriteLine();
string input = Console.ReadLine();
int intInput = int.Parse(input);
if (intInput == 1)
{
// You should avoid gotos. Try writing a method instead
// Play the game and get the player's score.
int newScore = PlayGame();
// Have the score keeper record the new score.
scoreKeeper.RecordScore(newScore);
}
else if (intInput == 2)
{
keepRunning = false;
}
else if (intInput == 3)
{
// Get the top scores from the score keeper
int[] topScores = scoreKeeper.GetTop10Scores();
// Print each score
for (int i = 0; i < topScores.Length; i++)
{
Console.WriteLine("{0}: {1}", i + 1, topScores[i]);
}
}
}
}
private static int PlayGame()
{
// Put your game logic in here. Return the score.
}
}
Once you become more familiar with the fundamentals of programming, you'll find there are existing classes you can reuse, like SortedList, that can already take care of common tasks like maintaining an ordered list.

What's wrong in terms of performance with this code? List.Contains, random usage, threading?

I have a local class with a method used to build a list of strings and I'm finding that when I hit this method (in a for loop of 1000 times) often it's not returning the amount I request.
I have a global variable:
string[] cachedKeys
A parameter passed to the method:
int requestedNumberToGet
The method looks similar to this:
List<string> keysToReturn = new List<string>();
int numberPossibleToGet = (cachedKeys.Length <= requestedNumberToGet) ?
cachedKeys.Length : requestedNumberToGet;
Random rand = new Random();
DateTime breakoutTime = DateTime.Now.AddMilliseconds(5);
//Do we have enough to fill the request within the time? otherwise give
//however many we currently have
while (DateTime.Now < breakoutTime
&& keysToReturn.Count < numberPossibleToGet
&& cachedKeys.Length >= numberPossibleToGet)
{
string randomKey = cachedKeys[rand.Next(0, cachedKeys.Length)];
if (!keysToReturn.Contains(randomKey))
keysToReturn.Add(randomKey);
}
if (keysToReturn.Count != numberPossibleToGet)
Debugger.Break();
I have approximately 40 strings in cachedKeys none exceeding 15 characters in length.
I'm no expert with threading so I'm literally just calling this method 1000 times in a loop and consistently hitting that debug there.
The machine this is running on is a fairly beefy desktop so I would expect the breakout time to be realistic, in fact it randomly breaks at any point of the loop (I've seen 20s, 100s, 200s, 300s).
Any one have any ideas where I'm going wrong with this?
Edit: Limited to .NET 2.0
Edit: The purpose of the breakout is so that if the method is taking too long to execute, the client (several web servers using the data for XML feeds) won't have to wait while the other project dependencies initialise, they'll just be given 0 results.
Edit: Thought I'd post the performance stats
Original
'0.0042477465711424217323710136' - 10
'0.0479597267250446634977350473' - 100
'0.4721072091564710039963179678' - 1000
Skeet
'0.0007076318358897569383818334' - 10
'0.007256508857969378789762386' - 100
'0.0749829936486341141122684587' - 1000
Freddy Rios
'0.0003765841748043396576939248' - 10
'0.0046003053460705201359390649' - 100
'0.0417058592642360970458535931' - 1000
Why not just take a copy of the list - O(n) - shuffle it, also O(n) - and then return the number of keys that have been requested. In fact, the shuffle only needs to be O(nRequested). Keep swapping a random member of the unshuffled bit of the list with the very start of the unshuffled bit, then expand the shuffled bit by 1 (just a notional counter).
EDIT: Here's some code which yields the results as an IEnumerable<T>. Note that it uses deferred execution, so if you change the source that's passed in before you first start iterating through the results, you'll see those changes. After the first result is fetched, the elements will have been cached.
static IEnumerable<T> TakeRandom<T>(IEnumerable<T> source,
int sizeRequired,
Random rng)
{
List<T> list = new List<T>(source);
sizeRequired = Math.Min(sizeRequired, list.Count);
for (int i=0; i < sizeRequired; i++)
{
int index = rng.Next(list.Count-i);
T selected = list[i + index];
list[i + index] = list[i];
list[i] = selected;
yield return selected;
}
}
The idea is that at any point after you've fetched n elements, the first n elements of the list will be those elements - so we make sure that we don't pick those again. When then pick a random element from "the rest", swap it to the right position and yield it.
Hope this helps. If you're using C# 3 you might want to make this an extension method by putting "this" in front of the first parameter.
The main issue are the using retries in a random scenario to ensure you get unique values. This quickly gets out of control, specially if the amount of items requested is near to the amount of items to get i.e. if you increase the amount of keys, you will see the issue less often but that can be avoided.
The following method does it by keeping a list of the keys remaining.
List<string> GetSomeKeys(string[] cachedKeys, int requestedNumberToGet)
{
int numberPossibleToGet = Math.Min(cachedKeys.Length, requestedNumberToGet);
List<string> keysRemaining = new List<string>(cachedKeys);
List<string> keysToReturn = new List<string>(numberPossibleToGet);
Random rand = new Random();
for (int i = 0; i < numberPossibleToGet; i++)
{
int randomIndex = rand.Next(keysRemaining.Count);
keysToReturn.Add(keysRemaining[randomIndex]);
keysRemaining.RemoveAt(randomIndex);
}
return keysToReturn;
}
The timeout was necessary on your version as you could potentially keep retrying to get a value for a long time. Specially when you wanted to retrieve the whole list, in which case you would almost certainly get a fail with the version that relies on retries.
Update: The above performs better than these variations:
List<string> GetSomeKeysSwapping(string[] cachedKeys, int requestedNumberToGet)
{
int numberPossibleToGet = Math.Min(cachedKeys.Length, requestedNumberToGet);
List<string> keys = new List<string>(cachedKeys);
List<string> keysToReturn = new List<string>(numberPossibleToGet);
Random rand = new Random();
for (int i = 0; i < numberPossibleToGet; i++)
{
int index = rand.Next(numberPossibleToGet - i) + i;
keysToReturn.Add(keys[index]);
keys[index] = keys[i];
}
return keysToReturn;
}
List<string> GetSomeKeysEnumerable(string[] cachedKeys, int requestedNumberToGet)
{
Random rand = new Random();
return TakeRandom(cachedKeys, requestedNumberToGet, rand).ToList();
}
Some numbers with 10.000 iterations:
Function Name Elapsed Inclusive Time Number of Calls
GetSomeKeys 6,190.66 10,000
GetSomeKeysEnumerable 15,617.04 10,000
GetSomeKeysSwapping 8,293.64 10,000
A few thoughts.
First, your keysToReturn list is potentially being added to each time through the loop, right? You're creating an empty list and then adding each new key to the list. Since the list was not pre-sized, each add becomes an O(n) operation (see MSDN documentation). To fix this, try pre-sizing your list like this.
int numberPossibleToGet = (cachedKeys.Length <= requestedNumberToGet) ? cachedKeys.Length : requestedNumberToGet;
List<string> keysToReturn = new List<string>(numberPossibleToGet);
Second, your breakout time is unrealistic (ok, ok, impossible) on Windows. All of the information I've ever read on Windows timing suggests that the best you can possibly hope for is 10 millisecond resolution, but in practice it's more like 15-18 milliseconds. In fact, try this code:
for (int iv = 0; iv < 10000; iv++) {
Console.WriteLine( DateTime.Now.Millisecond.ToString() );
}
What you'll see in the output are discrete jumps. Here is a sample output that I just ran on my machine.
13
...
13
28
...
28
44
...
44
59
...
59
75
...
The millisecond value jumps from 13 to 28 to 44 to 59 to 75. That's roughly a 15-16 millisecond resolution in the DateTime.Now function for my machine. This behavior is consistent with what you'd see in the C runtime ftime() call. In other words, it's a systemic trait of the Windows timing mechanism. The point is, you should not rely on a consistent 5 millisecond breakout time because you won't get it.
Third, am I right to assume that the breakout time is prevent the main thread from locking up? If so, then it'd be pretty easy to spawn off your function to a ThreadPool thread and let it run to completion regardless of how long it takes. Your main thread can then operate on the data.
Use HashSet instead, HashSet is much faster for lookup than List
HashSet<string> keysToReturn = new HashSet<string>();
int numberPossibleToGet = (cachedKeys.Length <= requestedNumberToGet) ? cachedKeys.Length : requestedNumberToGet;
Random rand = new Random();
DateTime breakoutTime = DateTime.Now.AddMilliseconds(5);
int length = cachedKeys.Length;
while (DateTime.Now < breakoutTime && keysToReturn.Count < numberPossibleToGet) {
int i = rand.Next(0, length);
while (!keysToReturn.Add(cachedKeys[i])) {
i++;
if (i == length)
i = 0;
}
}
Consider using Stopwatch instead of DateTime.Now. It may simply be down to the inaccuracy of DateTime.Now when you're talking about milliseconds.
The problem could quite possibly be here:
if (!keysToReturn.Contains(randomKey))
keysToReturn.Add(randomKey);
This will require iterating over the list to determine if the key is in the return list. However, to be sure, you should try profiling this using a tool. Also, 5ms is pretty fast at .005 seconds, you may want to increase that.

Categories

Resources