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
Related
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());
}
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.
I'm trying to Write a C# console app that will take input from a text file of numbers. From the numbers I need to find the number of numbers, the largest of the numbers, the smallest of the numbers and the average of the numbers.
int NumberCounter = 0;
string NumberLines;
//Code to Read the file and display Each Line seperately.
System.IO.StreamReader NumbersFile = new System.IO.StreamReader(#"c:\Numbers.txt");
while ((NumberLines = NumbersFile.ReadLine()) != null)
{
System.Console.WriteLine(NumberLines);
NumberCounter++;
}
NumbersFile.Close();
System.Console.WriteLine("There Are {0} lines.", NumberCounter);
Console.ReadLine();
As you can see, I've only got the count figured out right now. Trying to use any sort of equation to compare the Streamreader to anything else results in a type mismatch error. I'm a beginner here, so I'm a little lost.
Thanks in advance.
You could read all the values into a list and then iterate over them to calculate your various values, or you can do something like the following that can process extremely large files with ease:
string NumberLines;
var largest = int.MinValue;
var smallest = int.MaxValue;
var count = 0;
var total = 0;
using(var NumbersFile = new System.IO.StreamReader(#"c:\Numbers.txt"))
{
while ((NumberLines = NumbersFile.ReadLine()) != null)
{
var value = int.Parse(NumberLines);
count++;
largest = Math.Max(largest,value);
smallest = Math.Min(smallest,value);
total += value;
}
}
var average = total/count;
If you aren't tied to the StreamReader, then I would use the following:
var largest = int.MinValue;
var smallest = int.MaxValue;
var count = 0;
var total = 0;
foreach(var line in File.ReadLines(#"c:\Numbers.txt"))
{
var value = int.Parse(line);
count++;
largest = Math.Max(largest,value);
smallest = Math.Min(smallest,value);
total += value;
}
var average = total/count;
you can do this:
var text = File.ReadAllLines(#"c:\Numbers.txt");
var numbers = text.Where(l=>int.TryParse(l, out int _)).Select(l => int.Parse(l)).ToList();
var max = numbers.Max();
var min = numbers.Min();
var average = numbers.Average();
var total = numbers.Count();
Have a look at File.ReadLines which returns an IEnumerable<string>.
The IEnumerable can be used with LINQ which has handy methods for aggregation:
IEnumerable<string> numberStrings = File.ReadLines(#"c:\Numbers.txt");
IEnumerable<double> numbers = numberStrings.Select(s => double.Parse(s));
double smallest = numbers.Min();
double largest = numbers.Max();
double average = numbers.Average();
You could use File.ReadAllLines along with Int32.Parse and then use Linq for calculating Min,Max , Count and Average.
For Example,
var lines = File.ReadAllLines(filePath);
var numbers = lines.Select(x=>Int32.Parse(x)); // Or Double.Parse or... Depending on expected numeric type
var min = numbers.Min();
var max = numbers.Max();
var average = numbers.Average();
var count = numbers.Count();
You should start off by declaring a List<int> before your while loop in which to save the numbers you find in your file.
List<int> numbersInFile = new List<int>();
Then, you need to parse the content of each line to check if it is formatted as a valid number, in which case you'll want to add it to the aforementioned list.
if (int.TryParse(NumberLines, out int number))
{
numbersInFile.Add(number);
}
Finally, the List<> structure already implements handy methods such as .Average(), .Min() and .Max() which will return the values you seek.
int max = numbersInFile.Max();
I think if you will need in List,Stack,Queue,massive and m. I will use massive. So get Number, get digits of Number(in loop while number isn't 0: for it get number mod 10, divide number by 10),write Number to massive,getting Max,Average,Min,Count of massive by the same named functions. ))))
SO this is my code:
int NumberCounter = 0;
int[] massive = new int[1000]
string NumberLines;
//Code to Read the file and display Each Line seperately.
System.IO.StreamReader NumbersFile = new System.IO.StreamReader(#"c:\Numbers.txt");
while ((NumberLines = NumbersFile.ReadLine()) != null)
{
System.Console.WriteLine(NumberLines);
int number = int.Parse(NumberLines);
int number1 = number;
while(number1!=0)
{
s = number1 %1 0;
number1 /= 10;
Console.WriteLine(s);
}
massive[NumberCounter]=number;
NumberCounter++;
}
NumbersFile.Close();
Console.WriteLine(massive.Average());
Console.WriteLine(massive.Max());
Console.WriteLine(massive.Min());
System.Console.WriteLine("There Are {0} lines.", NumberCounter);
Console.ReadLine();
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
cpuSensorValues.Add(sensor.Value);
if (cpuSensorValues.Count == 300)
{
for (int i = 0; i < cpuSensorValues.Count; i++)
{
Logger.Write("The Current CPU Temperature Is ===> " + sensor.Value);
}
cpuSensorValues = new List<float?>();
}
I'm adding to the List<float?> cpuSensorValues every second a value.
Then when it's getting to 300 values I want to calculate the highest number the lowest number and the average number from the List write each number to the logger and then to make a new instance to the List.
The problem is how can I calculate these 3 numbers?
You could use Enumerable.Max(), Enumerable.Min(), and Enumerable.Average():
var min = cpuSensorValues.Min();
var max = cpuSensorValues.Max();
var avg = cpuSensorValues.Average();
However, this will enumerate the list 3 times (once per call). In practice, that's likely not going to be a problem, but if it is, you could just handle this yourself:
// Returns min/max/average, ignoring null elements
Tuple<float,float,float> ComputeStats(IEnumerable<float?> values)
{
float min = float.MaxValue;
float max = float.MinValue;
float total = 0.0;
int count = 0;
foreach(var value in values)
{
if (value.HasValue)
{
min = Math.Min(min, value.Value);
max = Math.Max(max, value.Value);
total += value.Value;
++count;
}
}
return Tuple.Create(min, max, total / count);
}
Given that you're adding the values to the list yourself - you could even keep a running total + min and max as you go. This would completely avoid needing to enumerate (or even store) the values. The code above could easily be modified to handle this without storing the values in a List<T> at all.