Reading text file until a space and storing several values - c#

I am creating an application to read and display Pokemon stats. Currently, I have 6 txt files, one for each stat. I have 6 arrays reading each txt file and displaying each stat in a label.
I want to condense this information into a single text file, splitting each stat by a " " to keep each Pokemon's data on a single line.
Current code if it helps explain the idea better:
using System;
using System.Windows.Forms;
using System.IO;
namespace Pokedex
{
public partial class Pokedex : Form
{
public Pokedex()
{
InitializeComponent();
}
//Read stat data from text files into string arrays
string[] HP = File.ReadAllLines("HP.txt");
string[] Atk = File.ReadAllLines("Atk.txt");
string[] Def = File.ReadAllLines("Def.txt");
string[] SpAtk = File.ReadAllLines("SpAtk.txt");
string[] SpDef = File.ReadAllLines("SpDef.txt");
string[] Spe = File.ReadAllLines("Spe.txt");
private void cbxPokemon_SelectedIndexChanged(object sender, EventArgs e)
{
//Get array index of currently selected Pokemon
int index = cbxPokemon.SelectedIndex;
//Get integer values out of the string arrays for appropriate use
int intHP = int.Parse(HP[index]);
int intAtk = int.Parse(Atk[index]);
int intDef = int.Parse(Def[index]);
int intSpAtk = int.Parse(SpAtk[index]);
int intSpDef = int.Parse(SpDef[index]);
int intSpe = int.Parse(Spe[index]);
//Update labels with stat values in string forme, could also assign intStat.ToString()
lblDexNum.Text = (index + 1).ToString("d3");
lblHP.Text = HP[index];
lblAtk.Text = Atk[index];
lblDef.Text = Def[index];
lblSpAtk.Text = SpAtk[index];
lblSpDef.Text = SpDef[index];
lblSpe.Text = Spe[index];
lblBST.Text = (intHP + intAtk + intDef + intSpAtk + intSpDef + intSpe).ToString();
//Update bar width based on stat value
barHP.Width = intHP;
barAtk.Width = intAtk;
barDef.Width = intDef;
barSpAtk.Width = intSpAtk;
barSpDef.Width = intSpDef;
barSpe.Width = intSpe;
//Disable Previous and Next buttons when they cannot be used
if (index == 0) { btnPrev.Enabled = false; }
else { btnPrev.Enabled = true; }
if (index == cbxPokemon.Items.Count - 1) { btnNext.Enabled = false; }
else { btnNext.Enabled = true; }
}
private void btnPrev_Click(object sender, EventArgs e)
{
cbxPokemon.SelectedIndex -= 1;
}
private void btnNext_Click(object sender, EventArgs e)
{
cbxPokemon.SelectedIndex += 1;
}
}
}
Ideally, the file would contain the 6 stats, for example "100 90 80 70 60 50" on each line, with each of those valued being stored.
Is there a simple way to read until a space, store that value, and keep doing that until the end of the line?

Yes, this is very simple to do. You can use the String.Split() method to break apart the lines into parsable pieces.
You can read the stats file in the same way you are now, except you would only need to read one file instead of six:
string[] lines = File.ReadAllLines("PokemonStats.txt");
Then, inside your cbxPokemon_SelectedIndexChanged method, you can retrieve the stats for a Pokemon like this:
//Get integer values out of the string for appropriate use
string line = lines[index];
string[] parts = line.Split(' ');
int intHP = int.Parse(parts[0]);
int intAtk = int.Parse(parts[1]);
int intDef = int.Parse(parts[2]);
int intSpAtk = int.Parse(parts[3]);
int intSpDef = int.Parse(parts[4]);
int intSpe = int.Parse(parts[5]);
Of course this assumes there will always be exactly 6 integer stats per line and they will all be in a specific order.

Try something like this:
static void Main(string[] args)
{
//list to store stats
List<string[]> pokemonStats = new List<string[]>();
//get a reader on the file
using (StreamReader reader = new StreamReader("TextFile1.txt"))
{
//while we still have lines to read
while (!reader.EndOfStream)
{
//get the line of stats
string line = reader.ReadLine();
//split it on the ' ' character and store it in our list of pokemon stats
pokemonStats.Add(line.Split(' '));
}
}
//we have them all so do something, like print to screen
foreach (string[] pokemon in pokemonStats)
{
foreach (string stat in pokemon)
Console.Write(stat + " ");
Console.WriteLine();
}
Console.ReadLine();
}
Where TextFile1.txt contains; which also happens to be the output...
1 1 1 1 1 1
2 2 2 2 2 2
3 3 3 3 3 3

Ideally, the file would contain the 6 stats, for example "100 90 80 70 60 50" on each line, with each of those valued being stored.
So this solution is actually very simple, assuming you have a file that looks like this:
100
90
80
70
60
50
You could write code that looks like this:
public string[] ReadStats(string fileName)
{
return File.ReadAllLines(fileName);
}
This would return:
[ "100", "90", "80", "70", "60", "50" ]
This is obviously an over-simplified version that does not checking on whether the file exists, whether there are more than 6 lines, etc. But it gets the idea across, and you could enhance it to suit your needs.
The answer to your main question:
Is there a simple way to read until a space, store that value, and keep doing that until the end of the line?
This is a bit more complicated, but this should work:
public IEnumerable<string[]> ReadAllPokemonStats(string fileName)
{
List<string[]> allPokemon = new List<string[]>();
string[] allStats = new string[6];
var allText = File.ReadAllText(fileName);
int nextStatIndex = 0;
string thisStat;
for(int i=0; i < allText.Length; i++)
{
var nextChar = allText[i];
if(nextChar == ' ')
{
allStats[nextStatIndex] = thisStat;
nextStatIndex++;
continue;
}
if(nextChar == '\r')
{
allPokemon.Add(allStats);
nextStatIndex = 0;
allStats = new string[6];
continue;
}
thisStat += nextChar.ToString();
}
return allPokemon;
}
Again, the above code is not perfect, for example it reads the entire file into memory so there's a potential attack vector there. It also does not ensure that the stats are actually numeric, but neither did your code (it would just blow up on the int.Parse()). But again, it gives you the general idea. You can see this approach is actually a lot more complex that reading each stat line by line.

I am thinking that each file is a one column file that represents one type of stat. If this is correct, you can try this:
Perhaps creating a class the represents all the stats that you are loading and then treat that as an array or a dictionary to fetch the proper stat or to create a single stat file.
public class PokemonStat
{
public int Hp { get; set; }
public int Atk { get; set; }
public int Def { get; set; }
public int SpAtk { get; set; }
public int SpDef { get; set; }
public int Spe { get; set; }
}
Then in your executing file:
var newPokemanData = new Dictionary<int, PokemonStat>();
var fileNames = new string[] { "Hp.txt", "Atk.txt", "Def.txt", "SpAtk.txt", "SpDef.txt", "Spe.txt" }
foreach (var fileName in fileNames)
{
var lineNumber = 0;
using (var stream = new StreamReader(fileName))
{
while (!stream.EndOfStream)
{
var singleStat = stream.ReadLine();
if (!newPokemanData.Keys.Contains(lineNumber))
{
newPokemanData.Add(lineNumber, new PokemonStat());
}
switch(fileName)
{
case "Hp.txt":
newPokemanData[lineNumber].Hp = int.Parse(singleStat);
break;
case "Atk.txt":
newPokemanData[lineNumber].Atk = int.Parse(singleStat);
break;
case "Def.txt":
newPokemanData[lineNumber].Def = int.Parse(singleStat);
break;
case "SpAtk.txt":
newPokemanData[lineNumber].SpAtk = int.Parse(singleStat);
break;
case "SpDef.txt":
newPokemanData[lineNumber].SpDef = int.Parse(singleStat);
break;
case "Spe.txt":
newPokemanData[lineNumber].Spe = int.Parse(singleStat);
break;
default:
Console.WriteLine("Error");
break;
}
lineNumber++;
}
}
}
using (var unifiedStats = new StreamWriter("unifieldFile.txt"))
{
foreach (var line in newPokemanData.Keys)
{
//write to a file
unifiedStats.WriteLine(newPokemanData[line].Hp.ToString() + " " +
newPokemanData[line].Atk.ToString() + " " +
newPokemanData[line].Def.ToString() + " " +
newPokemanData[line].SpAtk.ToString() + " " +
newPokemanData[line].SpDef.ToString() + " " +
newPokemanData[line].Spe.ToString() + " "
);
}
}
//

Related

Read from file split content into group when empty line

I´m reading a file in my C# code.
The file looks like this:
13
56
89
55
66
9
58
I´m trying to read the file and split the numbers up in different variabels.
Want it look like this:
numb1 = 13,56,89
numb2 = 55,66
numb3 = 9
numb4 = 58
When it is a blank line I want to split it up and group numbers together. But I don´t know how to do.
My code so far:
static void Main(string[] args)
{
InputReader inputReader = new InputReader();
var file =File.ReadAllLines(#"C:\Users\forsb\source\repos\ConsoleApp1\ConsoleApp1\Input\numberlist.txt");
string[] a = new string[] { };
foreach (var item in file)
{
a = item.Split(new string[] { "\r\n\r\n" },
StringSplitOptions.RemoveEmptyEntries);
}
Console.WriteLine();
Console.Read();
}
You can try the below code
public static void Main()
{
var fileName = "filename.txt";
// CREATE a file in your Sandbox using .NET Fiddle
WriteFile(fileName);
string line;
var str="";
// Read the file and display it line by line.
System.IO.StreamReader file =
new System.IO.StreamReader(fileName);
while((line = file.ReadLine()) != null)
{
line = line.Trim();
//if line is empty
//show total values until now
if (line == "")
{
Console.WriteLine(str);
str="";
continue;
}
else
{
//if first element, add here
if(str != "")
{
str=str+","+line;
}
else
{
str=line;
}
}
}
//for remaining last line
Console.WriteLine(str);
file.Close();
}
public static void WriteFile(string path)
{
File.WriteAllText(path, "13\n56\n89\n\n\n\n55\n66\n\n\n\n9\n58");
}
I have used the sample code in fiddle looks working.
https://dotnetfiddle.net/hOFSi2
basically, i am trying to read here line by line, if line is empty and there is some data saved in "str" print it, else continue
Note: You don't need to create file using "WriteFile" function, I have created it in fiddle, so adding code here for reference.
I would create a List<List<int>> to hold your sets of numbers. Then you can do whatever you want with them.
static void Main(string[] args)
{
String fileName = #"C:\Users\forsb\source\repos\ConsoleApp1\ConsoleApp1\Input\numberlist.txt";
List<List<int>> numberSets = new List<List<int>>();
// Read the file and collect the numbers in "curSet" until a blank line is encountered
List<int> curSet = new List<int>();
foreach (String line in System.IO.File.ReadLines(fileName))
{
if (line.Trim().Length == 0)
{
// blank line found, add the current set of numbers to our list,
// but only if the current set actually has any numbers in it.
if (curSet.Count > 0)
{
numberSets.Add(curSet);
curSet = new List<int>();
}
}
else
{
int number;
if (int.TryParse(line, out number))
{
curSet.Add(number);
}
}
}
// end of file reached, add a pending set of numbers if it exists
if (curSet.Count > 0)
{
numberSets.Add(curSet);
}
// do something with the collection of number lists:
for(int i=0; i<numberSets.Count; i++)
{
List<int> numberSet = numberSets[i];
// ... do something with "numberSet" ...
Console.WriteLine(i + ": " + String.Join(",", numberSet));
}
Console.Write("Press Enter to Quit");
Console.ReadLine();
}
Output on my system:
0: 13,56,89
1: 55,66
2: 9
3: 58
Press Enter to Quit
You are doing the split for each row that you read from the file. You need to do the split for the complete content of the file.
Code below will give the output you want.
a[0] = 13,56,89
a[1] = 55,66
a[2] = 9
a[3] = 58
var data = File.ReadAllText(#"C:\Users\forsb\source\repos\ConsoleApp1\ConsoleApp1\Input\numberlist.txt");
string[] a = data.Split("\r\n\r\n", System.StringSplitOptions.RemoveEmptyEntries);
for(int i = 0; i < split.Length; i++)
{
a[i] = a[i].Replace("\r\n", ",");
}

Applying Algorithm to multiple files

I'm currently working on a project where I have to choose a file and then sort it along with other files. The files are just filled with numbers but each file has linked data. So the first number in a file is linked to the first number in the second file and so on. I Currently have code that allows me to read a file and display the file unsorted and sorted using the bubble sort. I am not sure how I would be able to apply this same principle to multiple files at once. So that I could choose a file and then sort it in line with the same method I have for a single file.
Currently, the program loads and asks the user to choose between 1 and 2. 1 Shows the code unsorted and 2 shows the code sorted. I can read in the files but the problem is sorting and displaying in order. Basically How do I sort multiple files that are linked together. What steps do I need to take to do this?
An example of one file:
4
28
77
96
An example of the second file:
66.698
74.58
2.54
48.657
Any help would be appreciated.
Thanks
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
public class Program
{
public Program()
{
}
public void ReadFile(int [] numbers)
{
string path = "Data1/Day_1.txt";
StreamReader sr = new StreamReader(path);
for (int index = 0; index < numbers.Length; index++)
{
numbers[index] = Convert.ToInt32(sr.ReadLine());
}
sr.Close(); // closes file when done
}
public void SortArray(int[] numbers)
{
bool swap;
int temp;
do
{
swap = false;
for (int index = 0; index < (numbers.Length - 1); index++)
{
if (numbers[index] > numbers[index + 1])
{
temp = numbers[index];
numbers[index] = numbers[index + 1];
numbers[index + 1] = temp;
swap = true;
}
}
} while (swap == true);
}
public void DisplayArray(int[] numbers)
{
for(int index = 0; index < numbers.Length; index++)
{
Console.WriteLine("{0}", numbers[index]);
}
}
}
The main is in another file to keep work organised:
using System;
public class FileDemoTest
{
public static void Main(string[] args)
{
int[] numbers = new int[300];
Program obj = new Program();
int operation = 0;
Console.WriteLine("1 or 2 ?");
operation = Convert.ToInt32(Console.ReadLine());
// call the read from file method
obj.ReadFile(numbers);
if (operation == 1)
{
//Display unsorted values
Console.Write("Unsorted:");
obj.DisplayArray(numbers);
}
if (operation == 2)
{
//sort numbers and display
obj.SortArray(numbers);
Console.Write("Sorted: ");
obj.DisplayArray(numbers);
}
}
}
What I would do is create a class that will hold the values from file 1 and file 2. Then you can populate a list of these classes by reading values from both files. After that, you can sort the list of classes on either field, and the relationships will be maintained (since the two values are stored in a single object).
Here's an example of the class that would hold the file data:
public class FileData
{
public int File1Value { get; set; }
public decimal File2Value { get; set; }
/// <summary>
/// Provides a friendly string representation of this object
/// </summary>
/// <returns>A string showing the File1 and File2 values</returns>
public override string ToString()
{
return $"{File1Value}: {File2Value}";
}
}
Then you can create a method that reads both files and creates and returns a list of these objects:
public static FileData[] GetFileData(string firstFilePath, string secondFilePath)
{
// These guys hold the strongly typed version of the string values in the files
int intHolder = 0;
decimal decHolder = 0;
// Get a list of ints from the first file
var fileOneValues = File
.ReadAllLines(firstFilePath)
.Where(line => int.TryParse(line, out intHolder))
.Select(v => intHolder)
.ToArray();
// Get a list of decimals from the second file
var fileTwoValues = File
.ReadAllLines(secondFilePath)
.Where(line => decimal.TryParse(line, out decHolder))
.Select(v => decHolder)
.ToArray();
// I guess the file lengths should match, but in case they don't,
// use the size of the smaller one so we have matches for all items
var numItems = Math.Min(fileOneValues.Count(), fileTwoValues.Count());
// Populate an array of new FileData objects
var fileData = new FileData[numItems];
for (var index = 0; index < numItems; index++)
{
fileData[index] = new FileData
{
File1Value = fileOneValues[index],
File2Value = fileTwoValues[index]
};
}
return fileData;
}
Now, we need to modify your sorting method to work on a FileData array instead of an int array. I also added an argument that, if set to false, will sort on the File2Data field instead of File1Data:
public static void SortArray(FileData[] fileData, bool sortOnFile1Data = true)
{
bool swap;
do
{
swap = false;
for (int index = 0; index < (fileData.Length - 1); index++)
{
bool comparison = sortOnFile1Data
? fileData[index].File1Value > fileData[index + 1].File1Value
: fileData[index].File2Value > fileData[index + 1].File2Value;
if (comparison)
{
var temp = fileData[index];
fileData[index] = fileData[index + 1];
fileData[index + 1] = temp;
swap = true;
}
}
} while (swap);
}
And finally, you can display the non-sorted and sorted lists of data. Note I added a second question to the user if they choose "Sorted" where they can decide if it should be sorted by File1 or File2:
private static void Main()
{
Console.WriteLine("1 or 2 ?");
int operation = Convert.ToInt32(Console.ReadLine());
var fileData = GetFileData(#"f:\public\temp\temp1.txt", #"f:\public\temp\temp2.txt");
if (operation == 1)
{
//Display unsorted values
Console.WriteLine("Unsorted:");
foreach (var data in fileData)
{
Console.WriteLine(data);
}
}
if (operation == 2)
{
Console.WriteLine("Sort on File1 or File2 (1 or 2)?");
operation = Convert.ToInt32(Console.ReadLine());
//sort numbers and display
SortArray(fileData, operation == 1);
Console.WriteLine("Sorted: ");
foreach (var data in fileData)
{
Console.WriteLine(data);
}
}
Console.Write("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
If you wanted to use an int to decide which field to sort on, you could do something like the following:
public static void SortArray(FileData[] fileData, int sortFileNumber = 1)
{
bool swap;
do
{
swap = false;
for (int index = 0; index < (fileData.Length - 1); index++)
{
bool comparison;
// Set our comparison to the field sortFileNumber
if (sortFileNumber == 1)
{
comparison = fileData[index].File1Value > fileData[index + 1].File1Value;
}
else if (sortFileNumber == 2)
{
comparison = fileData[index].File2Value > fileData[index + 1].File2Value;
}
else // File3Value becomes default for anything else
{
comparison = fileData[index].File3Value > fileData[index + 1].File3Value;
}
if (comparison)
{
var temp = fileData[index];
fileData[index] = fileData[index + 1];
fileData[index + 1] = temp;
swap = true;
}
}
} while (swap);
}

C# Read string from CSV file and plot line graph

Currently, I am able to read data from multiple CSV file and plot line graph using windows form application. However, now I need to plot a line graph based on a CSV file's section name (3rd column of csv file).
Modified/New CSV file: (Added the Section Name column)
Values,Sector,Name
5.55,1024,red
5.37,1536,red
5.73,2048,blue
5.62,2560,.blue
5.12,3072,.yellow
...
Based on the Section Name column, my line graph need to be plotted accordingly in a Single line and different sections must be plotted with different colors, including the legends shown at the side of the graph must be shown based on the different section names.
1 csv file = 1 Series. But there are same section names in a csv file (csv file example shown above, e.g. red for the 1st 20lines). Same section names = same color. If I open 2 or more csv files = 2 Series. Each Series will have different colors due to different section names in the csv file.
I am quite new with programming, and would really appreciate someone could help me by editing from my code.
Thank you.
Updated code:
GraphDemo (Form):
List<Read> rrList = new List<Read>();
void openToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog ff = new OpenFileDialog();
Read rr;
ff.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); //"C:\\";
ff.Filter = "csv files (*.csv)|*.csv|All files (*.*)|*.*";
ff.Multiselect = true;
ff.FilterIndex = 1;
ff.RestoreDirectory = true;
if (ff.ShowDialog() == DialogResult.OK)
{
try
{
rrList.Clear();
foreach (String file in ff.FileNames) //if ((myStream = ff.OpenFile()) != null)
{
rr = new Read(file);
rrList.Add(rr);
}
//Populate the ComboBoxes
if (rrList.Count > 0)
{
string[] header = rrList[0].header; //header of first file
xBox.DataSource = header;
yBox.DataSource = header.Clone(); //without Clone the 2 comboboxes link together!
}
if (yBox.Items.Count > 1) yBox.SelectedIndex = 1; //select second item
}
catch (Exception err)
{
//Inform the user if we can't read the file
MessageBox.Show(err.Message);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
Plot.Draw(rrList, xBox, yBox, chart);
}
class Read:
public class Read
{
public int nLines { get; private set; }
public int nColumns { get; private set; }
public string[] header { get; private set; }
public float[,] data { get; private set; }
public string fileName { get; set; }
public string[] section { get; private set; }
public Read(string file)
{
string[] pieces;
fileName = Path.GetFileName(file);
string[] lines = File.ReadAllLines(file); // read all lines
if (lines == null || lines.Length < 2) return; //no data in file
header = lines[0].Split(','); //first line is header
nLines = lines.Length - 1; //first line is header
nColumns = header.Length;
//read the numerical data and section name from the file
data = new float[nLines, nColumns - 1]; // *** 1 less than nColumns as last col is sectionName
section = new string[nLines]; // ***
for (int i = 0; i < nLines; i++)
{
pieces = lines[i + 1].Split(','); // first line is header
if (pieces.Length != nColumns) { MessageBox.Show("Invalid data at line " + (i + 2) + " of file " + fileName); return; }
for (int j = 0; j < nColumns - 1; j++)
{
float.TryParse(pieces[j], out data[i, j]); //data[i, j] = float.Parse(pieces[j]);
}
section[i] = pieces[nColumns - 1]; //last item is section
}
}
}
class Plot:
public class Plot
{
//public Plot() { } //no constructor required as we use a static class to be called
public static void Draw(List<Read> rrList, ComboBox xBox, ComboBox yBox, Chart chart) //***
{
int indX = xBox.SelectedIndex;
int indY = yBox.SelectedIndex;
chart.Series.Clear(); //ensure that the chart is empty
chart.Legends.Clear();
Legend myLegend = chart.Legends.Add("myLegend");
myLegend.Title = "myTitle";
//define a set of colors to be used for sections
Color[] colors = new Color[] { Color.Black, Color.Blue, Color.Red, Color.Green, Color.Magenta, Color.DarkCyan, Color.Chocolate, Color.DarkMagenta };
//use a Dictionary to keep iColor of each section
// key=sectionName, value=iColor (color index in our colors array)
var sectionColors = new Dictionary<string, int>();
int i = 0;
int iColor = -1, maxColor = -1;
foreach (Read rr in rrList)
{
float[,] data = rr.data;
int nLines = rr.nLines;
int nColumns = rr.nColumns;
string[] header = rr.header;
chart.Series.Add("Series" + i);
chart.Series[i].ChartType = SeriesChartType.Line;
//chart.Series[i].LegendText = rr.fileName;
chart.Series[i].IsVisibleInLegend = false; //hide default item from legend
chart.ChartAreas[0].AxisX.LabelStyle.Format = "{F2}";
chart.ChartAreas[0].AxisX.Title = header[indX];
chart.ChartAreas[0].AxisY.Title = header[indY];
for (int j = 0; j < nLines; j++)
{
int k = chart.Series[i].Points.AddXY(data[j, indX], data[j, indY]);
string curSection = rr.section[j];
if (sectionColors.ContainsKey(curSection))
{
iColor = sectionColors[curSection];
}
else
{
maxColor++;
iColor = maxColor; sectionColors[curSection] = iColor;
}
chart.Series[i].Points[k].Color = colors[iColor];
}
i++; //series#
} //end foreach rr
//fill custom legends based on sections/colors
foreach (var x in sectionColors)
{
string section = x.Key;
iColor = x.Value;
myLegend.CustomItems.Add(colors[iColor], section); //new LegendItem()
}
}
}
You can separate the data by the section column and use the section names as index into the Series collection instead of using i.
Best use the section name as the Series.Name. I suggest using a data class containing the two numbers and the string and collect them in a List<Dataclass>. Then create Series for the distinct sections. Then loop over them..
Here are a few code examples:
Define a class for your data:
public class Data3
{
public int N1 { get; set;}
public double N2 { get; set;}
public string S1 { get; set;}
public Data3(double n2, int n1, string s1)
{
N1 = n1; N2 = n2; S1 = s1;
}
}
Pick your own names! Optional but always recommended: Add a nice ToString() overload!
Declare a class level varible:
List<Data3> data = new List<Data3>();
During the read collect the data there:
data.Add(new Data3(Convert.ToDouble(pieces[1]), Convert.ToInt32(pieces[0]), pieces[2]));
To plot the chart first create the Series:
var sections= data.Select(x => x.S1).Distinct<string>();
foreach (string s in sections)
chart.Series.Add(new Series(s) { ChartType = SeriesChartType.Line });
Then plot the data; the series can be indexed by their Names:
foreach (var d in data) chart.Series[d.S1].Points.AddXY(d.N1, d.N2);
I left out the nitty gritty of integrating the code into your application; if you run into issues, do show the new code by editing your question!
A few notes:
When in doubt always create a class to hold your data
When in doubt always choose classes over structures
When in doubt always choose List<T> over arrays
Always try to break your code down to small chunks with helpful names.
Example: To read all the data in a csv file create a function to do so:
public void AppendCsvToDataList(string file, List<Data3> list)
{
if (File.Exists(file))
{
var lines = File.ReadAllLines(file);
for (int l = 1; l < lines.Length; l++)
{
var pieces = lines[l].Split(',');
list.Add(new Data3(Convert.ToInt32(pieces[1]),
Convert.ToDouble(pieces[0]), pieces[2]));
}
}
}

Detecting repetition of part of received data

Based on the code shown.. Am I writing the right coding if i want to compare the data that were being stream in? Basically starting from the part
while(serialPort1.IsOpen)
For instance first string of data received was T 12 29.5 then next string was T 12 29.5 followed by T 20 24.5 and on so.. basically unpredictable what going to be received next.
I want to program to be able to detect/count the number of appearance for the middle value..like...
====================
[number] | [Repeated times]
12 | 2
=================== but when another different number received,
[number] | [Repeated]
20 | 1
=================== the counter for the number will be overwrite and reset whenever a different number was received.
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string time = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss.ff");
RxString = serialPort1.ReadLine();
string[] split = RxString.Split('.');
string dp = split[1];
Char c = dp[0];
split[1] = c.ToString();
RxString = split[0] + "." + split[1];
while (serialPort1.IsOpen)
{
string[] number = RxString.Split(' ');
string unit = number[1];
int count = 1;
for(int i = 1; i < unit.Count(); i++)
{
if(unit[i-1] == unit[i])
count++;
else
count = 1;
if(count == 4)
{
//execute some parameters
}
}
}
this.Invoke(new EventHandler(DisplayText));
StreamWriter MyStreamWriter = new StreamWriter(#"C:\Users\acer\Documents\Data3.txt", true);
MyStreamWriter.Write(time + " " + RxString + "\r\n");
MyStreamWriter.Flush();
MyStreamWriter.Close();
}
EDIT V2
Why wont the prog record data which only has count of 1?
string[] number = RxString.Split(' '); //split RxString by ' '
string unit = number[1]; //unit = unit no.
int count = 1;
for (int i = 1; i < unit.Count(); i++)
{
if (unit[i - 1] == unit[i])
count++;
else
{
count = 1;
StreamWriter MyStreamWriter = new StreamWriter(#"C:\Users\acer\Documents\Data3.txt", true); //True tell SW to append to file instead of overwriting
MyStreamWriter.Write(time + " " + RxString + "\r\n"); //Write time + string
MyStreamWriter.Flush();
MyStreamWriter.Close();
}
You should use a dictionary to store each element and its own count :
var dict = new Dictionary<string, int?>();
while (serialPort1.IsOpen)
{
string[] number = RxString.Split(' ');
string unit = number[1];
if (dict.ContainsKey(unit))
{
if (dict[unit].HasValue)
{
dict[unit]++;
if (dict[unit] == 4)
{
// execute some parameters
dict[unit] = null;
}
}
}
else
{
dict.Add(unit, 1);
}
}
I'd create a special struct for that:
struct DataInfo
{
public string Number { get; set; }
public int Counter { get; set; }
... Other data you require to work with
}
And use either List<DataInfo> or Dictionary<string, DataInfo> to store values;

C# Create high score text file and reorder the results

I'm trying to create a text file containing the players' scores of a hangman game. The structure of the text file should follow the order: number. name score (e.g. 1. Helen 2500). I tried to split the line so that I can introduce the data into a specific array for name and score so that I can compare the results and reorder them (the numbers remain the same: 1, 2, 3, etc.) but it doesn't work. I don't get errors but the building stops at this function because of an incorrect use of the array v[]. What do you suggest me to do to make it work?
[code]
private void New_Score(int score)
{
int k=0, i;
char[] sep = new char[] { ' ', '\n', '.' };
string line, aux1, aux2;
string n=null, s=null;
n = textBox1.Text;
s = Convert.ToString(score);
string[] part=null, nr=null, name=null, result=null;
file_path = #"D:\Visual Studio 2005\Projects\WindowsApplication2\WindowsApplication2\Resources\HighScore.txt";
StreamReader f = new StreamReader(file_path);
while ((line = f.ReadLine()) != null)
{
part = null;
v = null;
part = line.Split(sep);
i=0;
foreach(string c in part)
{
v[i]= c;
i++;
}
nr[k] = v[0];
name[k] = v[1];
result[k] = v[2];
}
for (i = 0; i < k; i++)
if (string.CompareOrdinal(s,result[i]) == 1)
{
aux1 = s;
s = result[i];
result[i] = aux1;
aux2 = n;
n = name[i];
name[i] = aux2;
}
for (i = 0; i < k; i++)
{
line = nr[i] + ". " + name[i] + " " + result[i] + "\n";
File.WriteAllText(file_path, line);
}
}
[/code]
I would personally abstract the code a bit more, but if you don't want to add any more classes or really do anything outside of the method, here's what I'd do:
Parse high scores in as a List<Tuple<int, string, int>>
Add the new score
Use either list.Sort() or LINQ to sort the list
Write the list out to the file.
It's a lot cleaner and way more readable than what you have in the question.
Despite the fact this goes entirely against the saying about fishing and eating which I strongly support, I took the liberty of making a few improvements by completely rewriting your code.
First of all, I got rid of storing the position of the player in the text file. This isn't efficient, since when you add a player with the highest score (rendering him #1), you'll have to re-number everyone else that's present in the file at that point.
So the resulting file looks like this:
Foo 123
Qux 714
Bar 456
Baz 999
The main() method looks like this:
var scores = ReadScoresFromFile("Highscores.txt");
scores.ForEach(s => Console.WriteLine(s));
Console.ReadKey();
Then there's the Highscore class:
class Highscore
{
public String Name { get; set; }
public int Position { get; set; }
public int Score { get; set; }
public Highscore(String data)
{
var d = data.Split(' ');
if (String.IsNullOrEmpty(data) || d.Length < 2)
throw new ArgumentException("Invalid high score string", "data");
this.Name = d[0];
int num;
if (int.TryParse(d[1], out num))
{
this.Score = num;
}
else
{
throw new ArgumentException("Invalid score", "data");
}
}
public override string ToString()
{
return String.Format("{0}. {1}: {2}", this.Position, this.Name, this.Score);
}
}
You see a Highscore populates itself based on the line from the Highscore file it's fed. I populate the list of scores using this method:
static List<Highscore> ReadScoresFromFile(String path)
{
var scores = new List<Highscore>();
using (StreamReader reader = new StreamReader(path))
{
String line;
while (!reader.EndOfStream)
{
line = reader.ReadLine();
try
{
scores.Add(new Highscore(line));
}
catch (ArgumentException ex)
{
Console.WriteLine("Invalid score at line \"{0}\": {1}", line, ex);
}
}
}
return SortAndPositionHighscores(scores);
}
And finally some sorting and position assigment:
static List<Highscore> SortAndPositionHighscores(List<Highscore> scores)
{
scores = scores.OrderByDescending(s => s.Score).ToList();
int pos = 1;
scores.ForEach(s => s.Position = pos++);
return scores.ToList();
}
Resulting in:
1. Baz: 999
2. Qux: 714
3. Bar: 456
4. Foo: 123
There is no reason to store number, line position can serve to that purpose. Even better would be serialize List with score objects to for example XML (if you want to keep human readable score file), to avoid line parsing. But if you want to store to plain text here is a simple example :
private void New_Score(int score, string name)
{
string filename = "scores.txt";
List<string> scoreList;
if (File.Exists(filename))
scoreList = File.ReadAllLines(filename).ToList();
else
scoreList = new List<string>();
scoreList.Add(name + " " + score.ToString());
var sortedScoreList = scoreList.OrderByDescending(ss => int.Parse(ss.Substring(ss.LastIndexOf(" ") + 1)));
File.WriteAllLines(filename, sortedScoreList.ToArray());
}
And later when displaying results add order number in front, something like this:
int xx = 1;
List<string> scoreList = File.ReadAllLines(filename).ToList();
foreach (string oneScore in scoreList)
{
Console.WriteLine(xx.ToString() + ". " + oneScore);
xx++;
}
Seems like a complicated way to store a simple list of high scores. Why don't you try the following.
Define a simple object to hold a player's score.
[Serializable]
public class HighScore
{
public string PlayerName { get; set; }
public int Score { get; set; }
}
Make sure you mark it with the [Serializable] attribute.
Let's quickly create a list of high scores for a couple of players.
var highScores = new List<HighScore>()
{
new HighScore { PlayerName = "Helen", Score = 1000 },
new HighScore { PlayerName = "Christophe", Score = 2000 },
new HighScore { PlayerName = "Ruben", Score = 3000 },
new HighScore { PlayerName = "John", Score = 4000 },
new HighScore { PlayerName = "The Last Starfighter", Score = 5000 }
};
Now you can use the BinaryFormatter to serialize the scores and save them to a local file.
using (var fileStream = new FileStream(#"C:\temp\scores.dat", FileMode.Create, FileAccess.Write))
{
var formatter = new BinaryFormatter();
formatter.Serialize(fileStream, highScores);
}
Later you can load the high scores from this files in a similar manner.
using (var fileStream = new FileStream(#"C:\temp\scores.dat", FileMode.Open, FileAccess.Read))
{
var formatter = new BinaryFormatter();
highScores = (List<HighScore>) formatter.Deserialize(fileStream);
}
If you want to sort them you can implement the IComparable interface on the HighScore type.
[Serializable]
public class HighScore : IComparable
{
//...
public int CompareTo(object obj)
{
var otherScore = (HighScore) obj;
if (Score == otherScore.Score)
return 0;
if (Score < otherScore.Score)
return 1;
return -1;
}
}
Now you can just call the Sort(...) on your generic List collection.
highScores.Sort();
And voila the scores are sorted in a descending order.
foreach(var score in highScores)
{
Console.WriteLine(String.Format("{0}: {1} points", score.PlayerName, score.Score));
}
Or even more simple, just use LINQ to sort the high scores.
var sortedScores = highScores.OrderByDescending(s => s.Score).ToList();

Categories