I have a code that generates a user specified amount of random number from a range of also user specified numbers without repeats. The problem I have is that for a while the program works fine and then it just keeps giving the same number it seems, which breaks it.
Here's the whole code, some of it is in hungarian but if you want to try it for yourself to see what the problem really is: the program first asks you the range of numbers(first the minimum then the maximum number) and then the amount of numbers you want generated. After that you will see the your numbers generated... or not if it decides to not work. If you did get your numbers if you press "1" it will generate again with the same data you provided, pressing "2" will allow you to give different parameters and pressing "0" will quit the program.
If needed I can translate the program to help solve it.
using System;
using System.Collections.Generic;
namespace numgen
{
class Program
{
static int quantity, min, max, temp;
static bool check = false;
static int control = 2;
public static void Main(string[] args)
{
var gen = new List<int>();
Random rnd = new Random();
while(control > 0) {
Console.Clear();
if(control == 2) {
Console.Write("Add meg a minimum számot: ");
min = int.Parse(Console.ReadLine());
Console.Write("Add meg a maximum számot: ");
max = int.Parse(Console.ReadLine());
Console.Write("Add meg a hány számot kérsz: ");
quantity = int.Parse(Console.ReadLine());
}
gen.Clear();
//gen.Add(rnd.Next(min, max));
while(gen.Count < quantity) {
temp = rnd.Next(min, max);
foreach(int num in gen) {
if(temp == num) {
check = true;
break;
}
}
if(!check) {
gen.Add(temp);
//Uncomment to see number getting generated and when the program breaks
//Console.WriteLine(gen[gen.Count-1]);
}
}
gen.Sort();
foreach(int num in gen){
Console.WriteLine(num);
}
Console.WriteLine("\n[2] Új adatok megadása");
Console.WriteLine("[1] Számok újragenerálása");
Console.WriteLine("[0] Kilépés");
control = int.Parse(Console.ReadLine());
}
}
}
}
A HashSet is an excellent choice for this task. Elements are added as a key--so it will refuses any value that is already in the set.
Try something like this:
private static void TestHashSet(int min, int max, int quantity)
{
var gen = new HashSet<int>();
Random rnd = new Random();
while (gen.Count < quantity)
{
var temp = rnd.Next(min, max);
gen.Add(temp);
}
gen.AsEnumerable().OrderBy(s => s).ToList().ForEach(x => Console.WriteLine(x));
}
You are not setting check back to false, so your code breaks after the first duplicate is found.
You're not setting check back to false at any point in time.
Once you get the first collision you cease adding value to the list, so while (gen.Count < quantity) will never be true.
You might want to consider using a keyed Dictionary which will handle the indexing for you rather than some logic.
Also you're not checking if quantity is greater than the provided range. If it is you will always get collisions.
Consider a keyed dictionary, to take the logic checking away and let the Dictionary object take care of it.
var gen = new Dictionary<int, int>();
if (quantity > (max - min))
throw new ArgumentOutOfRangeException("Quantiy must be smaller than range");
while (gen.Count < quantity)
{
temp = rnd.Next(min, max);
if(!gen.ContainsKey(temp))
gen.Add(temp, temp);
}
foreach (int num in gen.Values)
{
Console.WriteLine(num);
}
Issues found in your script
When you set check = true;, you Never change it to false. gen.Add() never happens.
if someone selects minimum 2, max 3 and ask for 10, it will always have duplicate values.. Your code doesnt allow that and will run infinite loop. Calculate total numbers that can be generated between the two numbers and see if it can produce required quantity.
Not an issue but I would recommend using gen = new List<int>(); instead of Clear()
You can use Contains method to quickly check if the value is part of the list or not (instead of iterating over the entire list each time).
Updated your code and got it working like this,
while (control > 0)
{
Console.Clear();
if (control == 2)
{
Console.Write("Add meg a minimum számot: ");
min = int.Parse(Console.ReadLine());
Console.Write("Add meg a maximum számot: ");
max = int.Parse(Console.ReadLine());
Console.Write("Add meg a hány számot kérsz: ");
quantity = int.Parse(Console.ReadLine());
}
else if (control == 1)
gen = new List<int>();
if (max - min < quantity)
Console.WriteLine($"You cannot generate {quantity} between [{min} and {max}]");
else
{
while (gen.Count < quantity)
{
temp = rnd.Next(min, max);
if (!gen.Contains(temp))
gen.Add(temp);
}
gen.Sort();
gen.ForEach(x => Console.WriteLine(x));
}
Console.WriteLine("\n[2] Új adatok megadása");
Console.WriteLine("[1] Számok újragenerálása");
Console.WriteLine("[0] Kilépés");
control = int.Parse(Console.ReadLine());
}
Related
What I need is:
e.g:
Sum should be equal to:
120 (user input)
Number of numbers/items:
80 (user input)
Range of numbers to be used in set(from):
0 (user input)
Range of numbers to be used in set(to):
4 (user input)
Output:
1,1,3,2,1,1,0,0,1,1,2,1,0,2,3,3,1,2,0,0,0,1,3,2,3,1,0,0,2,3,2,3,2,2,1,1,0,0,2,0,1,0,1,1,3,3,1,3,1,0,0,3,2,1,0,0,2,1,2,3,0,3,1,1,3,3,2,2,1,1,3,1,3,3,3,3,3,1,2,0
These are all numbers that are between 0 and 4, their sum is 120 and are 80 in total.
What i've done is:
static void Main(string[] args)
{
bool loopOn = true;
Program p = new Program();
Console.WriteLine("____________________________________________________________________________");
Console.WriteLine("");
Console.WriteLine("Sum should be equal to:");
int sum = int.Parse(Console.ReadLine());
Console.WriteLine("Number of items:");
int items = int.Parse(Console.ReadLine());
Console.WriteLine("Range(from):");
int from = int.Parse(Console.ReadLine());
Console.WriteLine("Range(to):");
int to = int.Parse(Console.ReadLine());
while (loopOn == true)
{
List<int> number_list = p.createNumberSet(items, from, to);
if (number_list.Sum() == sum)
{
loopOn = false;
Console.WriteLine("____________________________________________________________________________");
Console.WriteLine("Start");
number_list.ForEach(Console.WriteLine);
Console.WriteLine("Stop");
Console.WriteLine("____________________________________________________________________________");
}
}
Console.WriteLine("Press any key to exit....");
Console.ReadLine();
}
public List<int> createNumberSet(int itemNumber, int range_from, int range_to)
{
List<int> number_set = new List<int>();
Random r = new Random();
for (int i = 0; itemNumber > i; i++)
{
number_set.Add(r.Next(range_from, range_to));
}
return number_set;
}
But this seems extremely in-efficent and doesn't seem to work with a lot of other examples. Does anyone have a better way of doing this?
Well, I am a bit lazy right now, so this is just an idea
Keep the first part:
bool loopOn = true;
Program p = new Program();
Console.WriteLine("____________________________________________________________________________");
Console.WriteLine("");
Console.WriteLine("Sum should be equal to:");
int sum = int.Parse(Console.ReadLine());
Console.WriteLine("Number of items:");
int items = int.Parse(Console.ReadLine());
Console.WriteLine("Range(from):");
int from = int.Parse(Console.ReadLine());
Console.WriteLine("Range(to):");
int to = int.Parse(Console.ReadLine());
Now, first of all, check is a solution exists:
if (from * items > sum) {
// There is no solution, handle accordingly
}
Let's focus on the interesting part now:
First create the list of necessary items
int[] number_set = new int[items];
for(int i = 0; i < items; i++) {
number_set[i] = from;
}
Find the difference between the wanted sum and the current sum of the list
int left_to_add = sum - from * items;
int idx = 0;
Random r = new Random();
while(left_to_add > 0) {
int toAdd = 0;
if (left_to_add < range_to - range_from) {
toAdd = r.Next(1, left_to_add);
} else {
toAdd = r.Next(1, range_to - range_from);
}
left_to_add -= toAdd;
number_set[idx] += toAdd;
idx++;
}
What's left to do is, convert the array to a list and shuffle it.
(I forgot that you actually can access list items by index, so there is no need to use an array as I did here)
At the algorithm level, this is what I would try:
Determine the number of each element, n[0], n[1], n[2], n[3] in your example (i.e. number of 0, number of 1 ...) and then generate a simple sequence by concatenating n[0] "0", n[1] "1", n[2] "2" and n[3] "3". Finally, a random sequence is obtained by performing a random permutation on this simple sequence.
The problem is therefore to determine the n[i].
The first step is to determine the average values of these n[i]. It your example, it is simple, as we can take average n_av[i]=20 for all index i.
In a more general case, we have to insure that
sum_i n_av[i]*i = sum_target (120 here) (1)
knowing that
sum_i (n[i]) = n = 80 here. (2)
In the general case, there is no necessary one unique good solution. I will try to propose an example of solution here if you provide an example of a difficult scenario.
The second step consists in selecting some random n[i] values around these average values. One possibility is to generate rounded Gaussian variables: we already know the averages, we just need to determine the variances. One possibility is to consider the variance that we will get if we were generating directly the random values, i.e. by considering the variance of the corresponding binomial variable :
var = n p(1-p). Here p[i] = n_av[i]/n
The last step consists in adjusting the values of the n[i] such that the sum of the n[i] is equal to the target. This is simply obtained by slightly increasing or decreasing some n[i] values.
For my program, I've prompted the user to put 20 names into an array (the array size is 5 for testing for now), this array is then sent to a text document. I need to make it so that it will randomly pick a name from the list and display it (which I have done). But I now need to make it increase the chances of a name being picked, how would I go about doing this?
Eg. I want to increase the chances of the name 'Jim' being picked from the array.
class Programt
{
static void readFile()
{
}
static void Main(string[] args)
{
string winner;
string file = #"C:\names.txt";
string[] classNames = new string[5];
Random RandString = new Random();
Console.ForegroundColor = ConsoleColor.White;
if (File.Exists(file))
{
Console.WriteLine("Names in the text document are: ");
foreach (var displayFile in File.ReadAllLines(file))
Console.WriteLine(displayFile);
Console.ReadKey();
}
else
{
Console.WriteLine("Please enter 5 names:");
for (int i = 0; i < 5; i++)
classNames[i] = Console.ReadLine();
File.Create(file).Close();
File.WriteAllLines(file, classNames);
Console.WriteLine("Writing names to file...");
winner = classNames[RandString.Next(0, classNames.Length)];
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("\nThe winner of the randomiser is: {0} Congratulations! ", winner);
Thread.Sleep(3000);
Console.Write("Completed");
Thread.Sleep(1000);
}
}
}
There's two ways of doing this. You can either produce a RNG with a normal distribution targeting one number.
Or the simpler way is a translational step. Generate in the range 0-100 and then produce code which translates to the answer in a biased way e.g.
0-5 : Answer 1
6-10: Answer 2
11-90: Answer 3
91-95: Answer 4
96-100: Answer 5
This gives an 80% chance of picking Answer 3, the others only get a 5% chance
So where you currently have RandString.Next(0, classNames.Length) you can replace that with a function something like GetBiasedIndex(0, classNames.Length, 3)
The function would look something like this (with test code):
public Form1()
{
InitializeComponent();
int[] results = new int[5];
Random RandString = new Random();
for (int i = 0; i < 1000; i++)
{
var output = GetBiasedIndex(RandString, 0, 4, 3);
results[output]++;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 5; i++)
{
builder.AppendLine(results[i].ToString());
}
label1.Text = builder.ToString();
}
private int GetBiasedIndex(Random rng, int start, int end, int target)
{
//Number between 0 and 100 (Helps to think percentage)
var check = rng.Next(0, 100);
//There's a few ways to do this next bit, but I'll try to keep it simple
//Allocate x% to the target and split the remaining y% among all the others
int x = 80;//80% chance of target
int remaining = 100 - x;//20% chance of something else
//Take the check for the target out of the last x% (we can take it out of any x% chunk but this makes it simpler
if (check > (100 - x))
{
return target;
}
else
{
//20% left if there's 4 names remaining that's 5% each
var perOther = (100 - x) / ((end - start) - 1);
//result is now in the range 0..4
var result = check / perOther;
//avoid hitting the target in this section
if (result >= target)
{
//adjust the index we are returning since we have already accounted for the target
result++;
}
//return the index;
return result;
}
}
and the output:
52
68
55
786
39
If you're going to call this function repeatedly you'll need to pass in the instance of the RNG so that you don't reset the seed each call.
If you want to target a name instead of an index you just need to look up that name first and have an else condition for when that name isn't found
I've built a Keno game, and it picks 20 random numbers out of an arraylist, NumbersToPickFrom. I generate a random number and then check if that number is currently in the numbers available to pick from. If it is, I add it to my arraylist of numbers that will be used as the lottery numbers, randomPicks, and then I remove it from the available numbers. If it's not in the list, that means it's already been picked, and I need a new number. I use a goto to start over. I thought it was working fine, but it seems I've been getting duplicates. Am I using the ArrayList.Remove function wrong? If it's removed, I shouldn't be getting duplicates in my final listing of random picks. If anybody can see where I missed something, that would be helpful. The below code is just the code involved in what I'm talking about.
private void GeneratePicks()
{
for (int i = 1; i <= 20; )
{
Retry:
int rInt = GenerateRandomPick();
if (NumbersToPickFrom.Contains(rInt))
{
RandomPicks.Add(rInt);
NumbersToPickFrom.Remove(rInt);
i++;
//PickBox.Text += rInt + " ,";
RandomPicks.Sort();
}
else
{
goto Retry;
}
}
}
private int GenerateRandomPick()
{
int rInt = rand.Next(1,81);
return rInt;
}
private void initializeArray()
{
for (int i = 1; i <= 80; i++)
{
NumbersToPickFrom.Add(i);
}
}
I ran your code and didn't get any duplicates at all.
Nevertheless, the approach of repeatedly picking random numbers and comparing to the shrinking list isn't the best way to do this.
Try this instead:
RandomPicks =
Enumerable
.Range(1, 80)
.OrderBy(n => rand.Next())
.Take(20)
.OrderBy(n => n)
.ToList();
I found that your code is working fine.
I added the following public variables to make it work though (on my machine)
List<int> NumbersToPickFrom = new List<int>();
List<int> RandomPicks = new List<int>();
Random rand = new Random();
Though on the second run, i found that the number of items in RandomPicks have doubled and there were duplicates as well, so I changed initializeArray() as below
private void initializeArray()
{
for (int i = 1; i <= 80; i++)
{
NumbersToPickFrom.Add(i);
}
RandomPicks.Clear(); // Added this to clear the existing values in the list.
}
If you want to do it "old school" and actually watch what's happening, change your GeneratePicks() method to this:
private void GeneratePicks()
{
RandomPicks = new List<int>();
initializeArray();
for (int i = 0; i < 20; ++i)
{
int randomIndex = rand.Next(1, 80 - i);
int randomPick = NumbersToPickFrom[randomIndex];
RandomPicks.Add(randomPick);
NumbersToPickFrom[randomIndex] = NumbersToPickFrom[80 - i - 1];
}
RandomPicks.Sort();
}
This will run through exactly 20 times and guarantee non-duplicates.
I can't see any error in your code, and tested your code by running it 100000 times and didn't get any duplicates.
There are however better methods for getting random numbers. One easy improvement would be to randomly pick a number from the NumbersToPickFrom list instead of just picking a number, then you don't need the inner loop.
There is a nicer way to pick lottery numbers. You can loop through the numbers and calculate the probability that each number should be picked. The probability of a number being picked is PicksLeft / NumbersLeft, for example the probability for the number 1 to be picked is 20 / 80, and then the probability changes depending on which numbers are picked:
private void GeneratePicks() {
int pick = 20;
for (int n = 1; pick > 0; n++) {
if (rand.Next(81 - n) < pick) {
RandomPicks.Add(n);
pick--;
}
}
}
As the numbers are picked in order, you don't even have to sort the numbers afterwards, and you don't need the NumbersToPickFrom list.
I am new to C#. I have been working on this program and researching but am not getting anywhere. The goal is to have the user enter numbers (how many is up to the user). when they enter a 0, it will stop the program and display the minimum number entered, the maximum number entered, and the average of all numbers entered. I am not getting any errors and I am getting. If someone can please point me in the right direction.
The WriteLines are returning:
Lowest number is 0
Highest number is 0
Average is: 0
Count: 5
Here is my code:
int LOWEST =0;
int HIGHEST=0;
const int STOP = 0;
double average = 0;
int input;
int count = 0;
Console.WriteLine("Enter a number. You can end the program at anytime by entering 0");
input = Convert.ToInt32(Console.ReadLine());
while (input != STOP)
{
for (int i=0; input != STOP; i++)
{
Console.WriteLine("Enter a number. You can end the program at anytime by entering 0");
input = Convert.ToInt32(Console.ReadLine());
count++;
var Out = new int[] { input };
LOWEST = Out.Min();
HIGHEST = Out.Max();
average = Out.Average();
if ((input > LOWEST) || (input < HIGHEST))
{
LOWEST = Out.Min();
}
if (input > HIGHEST)
{
HIGHEST = Out.Max();
}
}
}
Console.WriteLine("Lowest number is {0}", LOWEST);
Console.WriteLine("Highest number is {0}", HIGHEST);
Console.WriteLine("Average is {0}", average);
Console.WriteLine("Count: {0}", count);
Console.ReadLine();
On each run you are constructing a new array of integers:
var Out = new int[] { input };
After this line, Out contains one item: the last input. Calling Min, Max and Average on it will return the last value. Which is zero if you ended the program.
instead of creating a new array each time, you want to create a List<int> at the beginning of your program and then add each input to it. You can then use the whole list of values to calculate Min, Max and Average.
Eventually you can change your code into something like this:
const int STOP = 0;
int input = -1;
List<int> Out = new List<int>();
while (input != STOP)
{
Console.WriteLine("Enter a number. You can end the program at anytime by entering 0");
input = Convert.ToInt32(Console.ReadLine());
if (input == STOP) break;
Out.Add(input);
}
Console.WriteLine("Lowest number is {0}", Out.Min());
Console.WriteLine("Highest number is {0}", Out.Max());
Console.WriteLine("Average is {0}", Out.Average());
Console.WriteLine("Count: {0}", Out.Count);
Console.ReadLine();
List<int> numbers = new List<int>();
numbers.Add(10);
numbers.Add(30);
numbers.Add(20);
numbers.Add(0);
numbers.Max();
numbers.Min();
numbers.Average();
returns 30, 0 and 15.
Before your loop, you should probably make Out an extensible data structure analogous to an array, the List.
List<int> Out = new List<int>();
then each loop, you can
Out.Add(input);
Since this sounds like an exercise for the reader, you can then traverse your list and compute the average from all data values.
Alternately, before the loop, you could declare
int n = 0;
int total = 0;
and each loop, do
n += 1;
total += input;
From these, you should be easily able to compute the average.
I have a file that stores exam scores for a class of students. I am trying to write a program that successfully opens the file, reads in the exam scores, finds the average score, the highest score, and the lowest score, and prints these out. The average score should be printed with 2 digits after the decimal point.
This is what I have so far:
static void Main()
{
string myData = "";
int temp = 0;
int max = 0;
int min = 0;
double average = 0;
StreamReader fileReader = new StreamReader("data.txt");
do
{
myData = fileReader.ReadLine();
if (myData != null)
{
max = int.Parse(myData);
temp = int.Parse(myData);
if (temp > max)
temp = max;
}
} while (myData != null);
fileReader.Close();
Console.ReadLine();
}//End Main()
I don't exactly know how to proceed. How do I read in a new line and assign it to temp? I don't think I'm doing it right.
Here is one way that will make your teacher sad :P
static void Main(string[] args)
{
var file = new StreamReader("scores.txt");
var split = file.ReadToEnd().Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
IEnumerable<int> ints = split.Select(x => int.Parse(x));
Console.WriteLine("Total Scores:" + ints.Count());
Console.WriteLine("Max:" + ints.Max());
Console.WriteLine("Min:" + ints.Min());
Console.WriteLine("Average:" + ints.Average().ToString("0.00"));
Console.ReadLine();
}
While this is technically correct, this is counter-productive to understanding algorithms (however basic they may be), and I suggest you look at the other answers. But this demonstrates how versatile the .NET framework is.
<3 LINQ
The first error I found is that max will always be exactly equal to temp because you assign them the same value, so this if will never be true:
max = int.Parse(myData);
temp = int.Parse(myData);
if (temp > max)
Not bad. Here's some pseudocode:
File.ReadAllLines to get an array of strings (call this lines)
Foreach line in lines
Parse the double (? is each line guaranteed to be an int?)
if max < parsed, max = parsed
if min > parsed, min = parsed
sum += parsed
Print out min, max, (sum / lines.count).ToString("000.00")
Assumes the file looks like:
25
12
33.5
100
75
...
Since this is homework, I'll just give you some clues.
Right now, you're setting "max" each time through the loop. Try only setting temp, and see what happens. You might want to consider defaulting max (when you create it) to a very small number instead of 0.
Also, you'll need to do something similar for "min", but default it to a very large number.
int max = int.MinValue;
int min = int.MaxValue;
In order to get the average, you'll need to have a sum and a count, and keep track of those. Then, at the end, use a double to compute the average, and print. To get 2 decimal places, you can use average.ToString("N") - the "N" format does a nicely formatted number with 2 decimal places by default.
How about you separate out the processes? Do the reading and fill a list of integers with the contents of the file. Then perform the processing for min / max and average later on.
Isolating issues help you focus on them. I like to call this noise reduction. As a contractor I get to work on a lot of messy code and one of the reasons they are hard to understand is that too much is going on at the same time. If you simplify whats going on the code almost writes itself. This is also called Separation of Concerns. This is a very important programming principle.
After you've isolated the issues and got the code working you can then try to put it all together again so the process is more efficient (if you do it inline with the file reading then you will only hold one line in memory at a time).
For starters, you need to get rid of the line "max = int.Parse(myData)". Otherwise, you'll keep overwriting max with the current value.
I take it that this is the general gist of your assignment. You should be leery of copying this code.
double count = 0.0;
double min = double.MaxValue;
double max = double.MinValue;
double total = 0.0;
using(StreamReader sr = new StreamReader(#"c:\data.txt"))
{
while (!sr.EndOfStream)
{
String line = sr.ReadLine();
double value = double.Parse(line.Trim());
if (value < min) min = value;
if (value > max) max = value;
total += value;
count++;
}
}
Console.WriteLine("Min: {0}", min);
Console.WriteLine("Max: {0}", max);
Console.WriteLine("Avg: {0}", (total / count).ToString("0.00"));
Console.ReadLine();
static void Main(string[] args)
{
const string filename = #"data.txt";
bool first = true;
int min=0, max=0, total=0;
var lines = File.ReadAllLines(filename);
foreach (var line in lines)
{
var score = int.Parse(line.Trim());
if (first)
{
min = max = total = score;
first = false;
continue;
}
if (score < min)
min = score;
if (score > max)
max = score;
total += score;
}
if (first)
{
Console.WriteLine("no input");
return;
}
var average = (double)total/lines.Length;
Console.WriteLine(string.Format("Min: {0}, Max: {1}, Average: {2:F2}", min, max, average));
}
Hey thanks to everybody who helped out and offered suggestions, here is my final code implementation (without using arrays)
using System;
using System.IO;
class Program
{
static void Main()
{
string line = "";
int value = 0;
int max = 0;
int min = 100;
int total = 0;
double count = 0.0;
double average = 0;
StreamReader fileReader = new StreamReader(#"data.txt");
do
{
line = fileReader.ReadLine();
if (line != null)
{
value = int.Parse(line);
if (value > max)
max = value;
if (value < min)
min = value;
total += value;
count++;
}
} while (line != null);
average = total / count;
Console.WriteLine("Max: {0}", max);
Console.WriteLine("Min: {0}", min);
Console.WriteLine("Avg: {0:f2}", average);
Console.ReadLine();
}//End Main()
}//End class Program