I try to get a file's content into an array in C#.
My program is similar to a shopping list.
I'm not able to store the data as a JSON file cause of some requirements.
I decided to store the data like:
3x water
2x fish
I grabbed the data using a simple StreamReader and got all the data into a string.
I tried to separate the string by \n using split and store the data into a string array.
Now I wanted to split each string again to get the quantity of the product.
I tried splitting each index using a foreach and storing them in a second array. I also tried to store the data in the same array.
I want to get something like
string[] cars = {"3", "water", "2", "fish"};
or store the data in a list.
Is my attempt a bad one?
Is there a way to do it much more easily?
Easy is a very subjective requirement.
You could write it in one statement with a variant of Split that restricts it to splitting at most once (so you can use an x in a description), and uses SelectMany to fold down all the lines into a single array of strings - as you asked.
string[] cars = File.ReadAllLines("input.txt")
.SelectMany(line => line.Split(new char[] { 'x' }, 2))
.Select(s => s.Trim())
.ToArray();
...but I wouldn't.
I think that maintainability is important. This code is understandable, useful, and obvious.
using System;
using System.Collections.Generic;
using System.IO;
class Program
{
public class Car
{
private int quantity;
private string description;
public Car(int quantity, string description)
{
this.quantity = quantity;
this.description = description;
}
public override string ToString() => $"{quantity}x {description}";
}
static void Main(string[] args)
{
// Use a list to store all the entries.
List<Car> cars = new List<Car>();
string[] lines = File.ReadAllLines("input.txt");
// Parse each line of the file.
foreach (var line in lines)
{
// Ignore completely blank lines.
if (string.IsNullOrWhiteSpace(line))
continue;
// Find the delimiter 'x'.
int pos = line.IndexOf('x');
// Handle case where no delimiter is present.
if (pos < 0)
throw new FormatException("The line is in an invalid format because it does not contain an 'x'.");
// Split the string into two parts at the delimiter position.
string firstPart = line.Substring(0, pos); // everything before the delimiter
string lastPart = line.Substring(pos + 1); // everything after the delimiter
// Interpret the first part as an integer.
if (!int.TryParse(firstPart, out int quantity))
throw new FormatException("The quantity is not a number.");
// Disallow zero or less for quantities.
if (quantity < 1)
throw new InvalidDataException("The quantity is not a positive number.");
// Trim whitespace from the description.
string description = lastPart.Trim();
// Require a non-empty description.
if (string.IsNullOrWhiteSpace(description))
throw new InvalidDataException("The description is missing.");
cars.Add(new Car(quantity, description));
}
}
}
If we suppose that each item has it's own line inside the file, this solution goes through each line and separates the quantity and the name of the item, after that these are stored in an array.
private static string[] GetData()
{
using (StreamReader sr = new StreamReader("source.in"))
{
int numberOfItems = File.ReadAllLines("source.in").Length;
string[] Data = new string[numberOfItems * 2];
int DataIndex = -1;
string line;
while ((line = sr.ReadLine()) != null)
{
//Separate the things
int index = line.IndexOf('x');
string num = line.Substring(0, index++);
Data[++DataIndex] = num;
string itm = line.Substring(++index);
Data[++DataIndex] = itm;
}
return Data;
}
}
Related
I am trying to write a parser that reads the third to last element from a list. This is a number or nothing. This is my code:
public int parseLastId(String filepath)
{
List<string> lines = new List<string>();
String text = File.ReadAllLines(filepath).ToString();
lines = text.Split(',').ToList();
int id = Convert.ToInt32(lines[lines.Count - 3]);
return id;
}
the text in the file is like this:
1,Joe,2,Jeff,
File.ReadAllLines will read every line from a text file.
In your text file, you only have 1 line.
So you can change
String text = File.ReadAllLines(filepath).ToString();
to
// Get the first line from the text file.
String text = File.ReadAllLines(filepath)[0];
The rest of your program is fine then.
Idiomatic C#
Just to note, that your code isn't written in standard C# style.
In particular:
string should be used instead of String.
Method names should start with a capital letter
The call to .ToList() on text.Split(',') is unnecessary.
Here is an edited version of your code that's more in line with typical C# standards - hope that helps :)
public int ParseLastId(string filepath)
{
var text = File.ReadAllLines(filepath)[0];
var lines = text.Split(',');
var id = Convert.ToInt32(lines[lines.Length - 3]);
return id;
}
Error handling
You state that you want to return nothing if the program cannot parse the entry. To do that, you will want to return a "nullable int" (int?) from your method, and use the int.TryParse method.
For example:
public int? ParseLastId(string filepath)
{
var text = File.ReadAllLines(filepath)[0];
var lines = text.Split(',');
if (int.TryParse(lines[lines.Length - 3], out var id)
{
return id;
}
else
{
return null;
}
}
I assume by "return nothing" you mean 0
public int parseLastId(string filepath)
{
string text = File.ReadAllText(filepath);
string[] lines = text.Split(',');
return lines.Length >= 3 && int.TryParse(lines[lines.Length - 3], out int id) ? id : 0;
}
I'm looking to make selections from a second array, based on the results in my first array. The first array stores integers of the position of certain column headers reside in a csv.
var path = #"C:\Temp\file.csv";
using (TextFieldParser csvImport = new TextFieldParser(path))
{
csvImport.CommentTokens = new string[] { "#" };
csvImport.SetDelimiters(new string[] { "," });
csvImport.HasFieldsEnclosedInQuotes = true;
string[] header = csvImport.ReadFields();
foreach (string colheader in header)
{
index = index + 1; //this bit will return where my column headers are in the header line
if (colheader == "FirstColumn")
{
key = (index - 1); //stores int of my result
}
else if (colheader == "SecondColumn")
{
secondkey = (index - 1); //stores int of my result
}
}
csvImport.ReadLine(); //Behaves as carriage line return, this moves to position 2 before loop
while (!csvImport.EndOfData)
{
//Read each field, build Sql Statement
string[] fields = csvImport.ReadFields();
string MyKey = fields[1]; //Currently this is static pos 1 I want it to be the result of key
string MySecondKey = fields[74]; //Currently this is static pos 74 I want it to be the result of SecondKey
}
}
is there a simple way of assigning a variable into the [] to ad hoc pick out my array based on other variables at my disposal?
I've edited the question slightly as what I am looking to achieve is picking the field from a csv line based on the indexes from the first arra
//Read each field, build Sql Statement
string[] fields = csvImport.ReadFields();
string MyKey = fields[1]; //Currently this is static pos 1 I want it to be the result of key
string MySecondKey = fields[74]; //Currently this is static pos 74 I want it to be the result of SecondKey
Give the items in your second array an index (i here), then get all items that their index is in first array:
secondArray.Select((x,i) => new {x, i})
.Where(z => firstArray.Contains(z.i))
.Select(z => z.x);
and here is a live demo
(of course the type of second array is not important and I have used string[] just for simplicity, and it can be an array of a class, ... )
I have the following string that I need to parse out so I can insert them into a DB. The delimiter is "`":
`020 Some Description `060 A Different Description `100 And Yet Another `
I split the string into an array using this
var responseArray = response.Split('`');
So then each item in the responseArrray[] looks like this: 020 Some Description
How would I get the two different parts out of that array? The 1st part will be either 3 or 4 characters long. 2nd part will be no more then 35 characters long.
Due to some ridiculous strangeness beyond my control there is random amounts of space between the 1st and 2nd part.
Or put the other two answers together, and get something that's more complete:
string[] response = input.Split(`);
foreach (String str in response) {
int splitIndex = str.IndexOf(' ');
string num = str.Substring(0, splitIndex);
string desc = str.Substring(splitIndex);
desc.Trim();
}
so, basically you use the first space as a delimiter to create 2 strings. Then you trim the second one, since trim only applies to leading and trailing spaces, not everything in between.
Edit: this a straight implementation of Brad M's comment.
You can try this solution:
var inputString = "`020 Some Description `060 A Different Description `100 And Yet Another `";
int firstWordLength = 3;
int secondWordMaxLength = 35;
var result =inputString.Split('`')
.SelectMany(x => new[]
{
new String(x.Take(firstWordLength).ToArray()).Trim(),
new String(x.Skip(firstWordLength).Take(secondWordMaxLength).ToArray()).Trim()
});
Here is the result in LINQPad:
Update: My first solution has some problems because the use of Trim after Take.Here is another approach with an extension method:
public static class Extensions
{
public static IEnumerable<string> GetWords(this string source,int firstWordLengt,int secondWordLenght)
{
List<string> words = new List<string>();
foreach (var word in source.Split(new[] {'`'}, StringSplitOptions.RemoveEmptyEntries))
{
var parts = word.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
words.Add(new string(parts[0].Take(firstWordLengt).ToArray()));
words.Add(new string(string.Join(" ",parts.Skip(1)).Take(secondWordLenght).ToArray()));
}
return words;
}
}
And here is the test result:
Try this
string response = "020 Some Description060 A Different Description 100 And Yet Another";
var responseArray = response.Split('`');
string[] splitArray = {};
string result = "";
foreach (string it in responseArray)
{
splitArray = it.Split(' ');
foreach (string ot in splitArray)
{
if (!string.IsNullOrWhiteSpace(ot))
result += "-" + ot.Trim();
}
}
splitArray = result.Substring(1).Split('-');
string[] entries = input.Split('`');
foreach (string s in entries)
GetStringParts(s);
IEnumerable<String> GetStringParts(String input)
{
foreach (string s in input.Split(' ')
yield return s.Trim();
}
Trim only removes leading/trailing whitespace per MSDN, so spaces in the description won't hurt you.
If the first part is an integer
And you need to account for some empty
For me the first pass was empty
public void parse()
{
string s = #"`020 Some Description `060 A Different Description `100 And Yet Another `";
Int32 first;
String second;
if (s.Contains('`'))
{
foreach (string firstSecond in s.Split('`'))
{
System.Diagnostics.Debug.WriteLine(firstSecond);
if (!string.IsNullOrEmpty(firstSecond))
{
firstSecond.TrimStart();
Int32 firstSpace = firstSecond.IndexOf(' ');
if (firstSpace > 0)
{
System.Diagnostics.Debug.WriteLine("'" + firstSecond.Substring(0, firstSpace) + "'");
if (Int32.TryParse(firstSecond.Substring(0, firstSpace), out first))
{
System.Diagnostics.Debug.WriteLine("'" + firstSecond.Substring(firstSpace-1) + "'");
second = firstSecond.Substring(firstSpace).Trim();
}
}
}
}
}
}
You can get the first part by finding the first space and make a substring. The second is also a Substring. Try something like this.
foreach(string st in response)
{
int index = response.IndexOf(' ');
string firstPart = response.Substring(0, index);
//string secondPart = response.Substring(response.Lenght-35);
//better use this
string secondPart = response.Substring(index);
secondPart.Trim();
}
I have a code to read text file and save the data into a double array to plot graph:
string filename = openFileDialog1.FileName;
var lineCount = 0;
using (var reader = File.OpenText(#filename))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var data = line.Split(',');
GlobalDataClass.dDataArray[lineCount, 0] = double.Parse(data[0]);
GlobalDataClass.dDataArray[lineCount, 1] = double.Parse(data[1]);
lineCount++;
}
ShowGraphData(lineCount);
}
And I have created a public class that initiate the array to [2000,2]:
static class GlobalDataClass
{
public static double[,] dDataArray = new double[2000, 2];
public static long iTotalReadingPoint;
}
My text file will be like:
0,29
1,31
2,32
3,32
4,30
However I want my program to detect the EOF so that the text file can contain random rows and still able to plot the graph not restricted to 2000 rows. Is it possible?please advice.TQ
If you want to use same class then try given code. This is not most optimum memory usage wise but still this should work.
string filename = openFileDialog1.FileName;
var lineCount = 0;
while ((line = reader.ReadLine()) != null)
{
if (GlobalDataClass.dDataArray.GetLength(0) == lineCount)
{
double[,] newTemp = GlobalDataClass.dDataArray;
int increaseLenght = newTemp.Length + 1000;
GlobalDataClass.dDataArray = new double[increaseLenght, 2];
Array.Copy(newTemp, GlobalDataClass.dDataArray, newTemp.LongLength);
}
var data = line.Split(',');
GlobalDataClass.dDataArray[lineCount, 0] = double.Parse(data[0]);
GlobalDataClass.dDataArray[lineCount, 1] = double.Parse(data[1]);
lineCount++;
}
The StreamReader class has a boolean property named EndOfStream that is essentially EoF for FileStream objects, and there's the Peek() method which is usually the standard way of reading until EoF.
var reader = File.OpenText( ... );
while( reader.Peek() != -1 ) //Peek() returns -1 on EoF or if the stream is not seekable.
{
var line = reader.ReadLine();
...
}
However reading from the file isn't really the problem if you don't want to be restricted to 2,000 lines. You might consider using a generic container of some flavour. For example:
using System.Collections.Generic;
string line = "12,34";
var dDataList = new List<KeyValuePair<double, double>>();
string[] parts = line.Split( ',' );
dDataList.Add( new KeyValuePair<double, double>( Double.Parse( parts[0] ), Double.Parse( parts[1] ) ) );
...
foreach( var pair in dDataList )
Console.WriteLine( "{0} => {1}", pair.Key, pair.Value ); // 12 => 34
Will let you add as many pairs of doubles as you want, within reason, without having to fiddle with array resizing/copying or any of that nasty business. It's generally considered good practice to use a container like List<T> when dealing with an unknown amount of data, and to use arrays when you know exactly how much data you're going to have or the absolute maximum amount of data you can have.
I think you're asking your question a bit wrong; The problem you have is that you are declaring an array of 2000 unit fixed length, but you actually want it to be dynamic length. As Abhinav said, a list may work for you:
firstly, you might consider creating a class/struct for your coordinates:
public class Coordinate
{
public double x;
public double y;
}
Then, create a list of coordinates (of 0 length initially)...
static class GlobalDataClass
{
public static List<Coordinate> dDataArray = new List<Coordinate>();
public static long iTotalReadingPoint;
}
And then add Coordinate objects to your list as you find new lines..
string filename = openFileDialog1.FileName;
var lineCount = 0;
using (var reader = File.OpenText(#filename))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var data = line.Split(',');
Coordinate coord = new Coordinate();
coord.x = double.Parse(data[0]);
coord.y = double.Parse(data[1]);
GlobalDataClass.dDataArray.Add(coord);
lineCount++;
}
ShowGraphData(lineCount);
}
Im trying to read contents of a csv file into different variables in order to send to a web service.It has been working fine but suddenly today i got and exception.
index was outside the bounds of the array:
what Did I do wrong?
String sourceDir = #"\\198.0.0.4\e$\Globus\LIVE\bnk.run\URA.BP\WEBOUT\";
// Process the list of files found in the directory.
string[] fileEntries = Directory.GetFiles(sourceDir);
foreach (string fileName2 in fileEntries)
{
// read values
StreamReader st = new StreamReader(fileName2);
while (st.Peek() >= 0)
{
String report1 = st.ReadLine();
String[] columns = report1.Split(','); //split columns
String prnout = columns[0];
String tinout = columns[1];
String amtout = columns[2];
String valdate = columns[3];
String paydate = columns[4];
String status = columns[5];
String branch = columns[6];
String reference = columns[7];
}
}
It's hard to guess without even seeing the .csv file, but my first one would be that you don't have 8 columns.
It would be easier if you could show the original .csv file, and tell us where the exception pops.
edit: If you think the data is alright, I'd suggest you debugging and see what the split call returns in Visual Studio. That might help
edit2: And since you're doing that processing in a loop, make sure each row has at least 8 columns.
My money is on bad data file. If that is the only thing in the equation that has changed (aka you haven't made any code changes) then that's pretty much your only option.
If your data file isn't too long post it here and we can tell you for sure.
You can add something like below to check for invalid column lengths:
while (st.Peek() >= 0)
{
String report1 = st.ReadLine();
String[] columns = report1.Split(','); //split columns
if(columns.Length < 8)
{
//Log something useful, throw an exception, whatever.
//You have the option to quitely note that there was a problem and
//continue on processing the rest of the file if you want.
continue;
}
//working with columns below
}
Just for sanity's sake, I combined all the various notes written here. This code is a bit cleaner and has some validation in it.
Try this:
string dir = #"\\198.0.0.4\e$\Globus\LIVE\bnk.run\URA.BP\WEBOUT\";
foreach (string fileName2 in Directory.GetFiles(dir)) {
StreamReader st = new StreamReader(fileName2);
while (!sr.EndOfStream) {
string line = sr.ReadLine();
if (!String.IsNullOrEmpty(line)) {
string[] columns = line.Split(',');
if (columns.Length == 8) {
string prnout = columns[0];
string tinout = columns[1];
string amtout = columns[2];
string valdate = columns[3];
string paydate = columns[4];
string status = columns[5];
string branch = columns[6];
string reference = columns[7];
}
}
}
}
EDIT: As some other users have commented, the CSV format also accepts text qualifiers, which usually means the double quote symbol ("). For example, a text qualified line may look like this:
user,"Hello!",123.23,"$123,123.12",and so on,
Writing CSV parsing code is a little more complicated when you have a fully formatted file like this. Over the years I've been parsing improperly formatted CSV files, I've worked up a standard code script that passes virtually all unit tests, but it's a pain to explain.
/// <summary>
/// Read in a line of text, and use the Add() function to add these items to the current CSV structure
/// </summary>
/// <param name="s"></param>
public static bool TryParseLine(string s, char delimiter, char text_qualifier, out string[] array)
{
bool success = true;
List<string> list = new List<string>();
StringBuilder work = new StringBuilder();
for (int i = 0; i < s.Length; i++) {
char c = s[i];
// If we are starting a new field, is this field text qualified?
if ((c == text_qualifier) && (work.Length == 0)) {
int p2;
while (true) {
p2 = s.IndexOf(text_qualifier, i + 1);
// for some reason, this text qualifier is broken
if (p2 < 0) {
work.Append(s.Substring(i + 1));
i = s.Length;
success = false;
break;
}
// Append this qualified string
work.Append(s.Substring(i + 1, p2 - i - 1));
i = p2;
// If this is a double quote, keep going!
if (((p2 + 1) < s.Length) && (s[p2 + 1] == text_qualifier)) {
work.Append(text_qualifier);
i++;
// otherwise, this is a single qualifier, we're done
} else {
break;
}
}
// Does this start a new field?
} else if (c == delimiter) {
list.Add(work.ToString());
work.Length = 0;
// Test for special case: when the user has written a casual comma, space, and text qualifier, skip the space
// Checks if the second parameter of the if statement will pass through successfully
// e.g. "bob", "mary", "bill"
if (i + 2 <= s.Length - 1) {
if (s[i + 1].Equals(' ') && s[i + 2].Equals(text_qualifier)) {
i++;
}
}
} else {
work.Append(c);
}
}
list.Add(work.ToString());
// If we have nothing in the list, and it's possible that this might be a tab delimited list, try that before giving up
if (list.Count == 1 && delimiter != DEFAULT_TAB_DELIMITER) {
string[] tab_delimited_array = ParseLine(s, DEFAULT_TAB_DELIMITER, DEFAULT_QUALIFIER);
if (tab_delimited_array.Length > list.Count) {
array = tab_delimited_array;
return success;
}
}
// Return the array we parsed
array = list.ToArray();
return success;
}
You should note that, even as complicated as this algorithm is, it still is unable to parse CSV files where there are embedded newlines within a text qualified value, for example, this:
123,"Hi, I am a CSV File!
I am saying hello to you!
But I also have embedded newlines in my text.",2012-07-23
To solve those, I have a multiline parser that uses the Try() feature to add additional lines of text to verify that the main function worked correctly:
/// <summary>
/// Parse a line whose values may include newline symbols or CR/LF
/// </summary>
/// <param name="sr"></param>
/// <returns></returns>
public static string[] ParseMultiLine(StreamReader sr, char delimiter, char text_qualifier)
{
StringBuilder sb = new StringBuilder();
string[] array = null;
while (!sr.EndOfStream) {
// Read in a line
sb.Append(sr.ReadLine());
// Does it parse?
string s = sb.ToString();
if (TryParseLine(s, delimiter, text_qualifier, out array)) {
return array;
}
}
// Fails to parse - return the best array we were able to get
return array;
}
Since you don't know how many columns will be in csv file, you might need to test for length:
if (columns.Length == 8) {
String prnout = columns[0];
String tinout = columns[1];
...
}
I bet you just got an empty line (extra EOL at the end), and that's as simple as that