What is the easiest way to split columns from a txt file - c#

I've been looking around a bit but haven't really found a good example with what I'm struggling right now.
I have a .txt file with a couple of columns as follows:
# ID,YYYYMMDD, COLD,WATER, OD, OP,
52,20120406, 112, 91, 20, 130,
53,20130601, 332, 11, 33, 120,
And I'm reading these from the file into a string[] array.
I'd like to split them into a list
for example
List results, and [0] index will be the first index of the columns
results[0].ID
results[0].COLD
etc..
Now I've been looking around, and came up with the "\\\s+" split
but I'm not sure how to go about it since each entry is under another one.
string[] lines = File.ReadAllLines(path);
List<Bus> results = new List<Bus>();
//Bus = class with all the vars in it
//such as Bus.ID, Bus.COLD, Bus.YYYYMMDD
foreach (line in lines) {
var val = line.Split("\\s+");
//not sure where to go from here
}
Would greatly appreciate any help!
Kind regards, Venomous.

I suggest using Linq, something like this:
List<Bus> results = File
.ReadLines(#"C:\MyFile.txt") // we have no need to read All lines in one go
.Skip(1) // skip file's title
.Select(line => line.Split(','))
.Select(items => new Bus( //TODO: check constructor's syntax
int.Parse(items[1]),
int.Parse(items[3]),
DateTime.ParseExact(items[2], "yyyyMMdd", CultureInfo.InvariantCulture)))
.ToList();

I would do
public class Foo
{
public int Id {get; set;}
public string Date {get; set;}
public double Cold {get; set;}
//...more
}
Then read the file
var l = new List<Foo>();
foreach (line in lines)
{
var sp = line.Split(',');
var foo = new Foo
{
Id = int.Parse(sp[0].Trim()),
Date = sp[1].Trim(),//or pharse the date to a date time struct
Cold = double.Parse(sp[2].Trim())
}
l.Add(foo);
}
//now l contains a list filled with Foo objects

I would probably keep a List of properties and use reflection to populate the object, something like this :
var columnMap = new[]{"ID","YYYYMMDD","COLD","WATER","OD","OP"};
var properties = columnMap.Select(typeof(Bus).GetProperty).ToList();
var resultList = new List<Bus>();
foreach(var line in lines)
{
var val = line.Split(',');
var adding = new Bus();
for(int i=0;i<val.Length;i++)
{
properties.ForEach(p=>p.SetValue(adding,val[i]));
}
resultList.Add(adding);
}
This is assuming that all of your properties are strings however

Something like this perhaps...
results.Add(new Bus
{
ID = val[0],
YYYYMMDD = val[1],
COLD = val[2],
WATER = val[3],
OD = val[4],
OP = val[5]
});
Keep in mind that all of the values in the val array are still strings at this point. If the properties of Bus are typed, you will need to parse them into the correct types e.g. assume ID is typed as an int...
ID = string.IsNullOrEmpty(val[0]) ? default(int) : int.Parse(val[0]),
Also, if the column headers are actually present in the file in the first line, you'll need to skip/disregard that line and process the rest.

Given that we have the Bus class with all the variables from your textfile:
class Bus
{
public int id;
public DateTime date;
public int cold;
public int water;
public int od;
public int op;
public Bus(int _id, DateTime _date, int _cold, int _water, int _od, int _op)
{
id = _id;
date = _date;
cold = _cold;
water = _water;
od = _od;
op = _op;
}
}
Then we can list them all in the results list like this:
List<Bus> results = new List<Bus>();
foreach (string line in File.ReadAllLines(path))
{
if (line.StartsWith("#"))
continue;
string[] parts = line.Replace(" ", "").Split(','); // Remove all spaces and split at commas
results.Add(new Bus(
int.Parse(parts[0]),
DateTime.ParseExact(parts[1], "yyyyMMdd", CultureInfo.InvariantCulture),
int.Parse(parts[2]),
int.Parse(parts[3]),
int.Parse(parts[4]),
int.Parse(parts[5])
));
}
And access the values as you wish:
results[0].id;
results[0].cold;
//etc.
I hope this helps.

Related

convert the strings in my list to a list c#

i'm quite new to this, i already did a little c# in unity, but never really in VS.
I have a txt file that looks approximatively like this :
monday;8;server1,server2,server3
tuesday;9;server3,server4
wedneday;8;server1,server2,server4
i splitted this into 3 list, one with the day (monday)... one with the hour (8)... and one with the servers(server1,server2,server3), but i would like to convert this server string into unique lists,
for example i would like a list1 to contain all servers to of monday (server1,server2,server3), a list2 to contain all servers of tuesday (server3,server4).
These servers names are splitted with a comma, and i would like to split every string into a
unique list
i know i was not very clear, please ask for any specifications
List<string> plages_horaires_ouverture = File.ReadAllLines(#"file.txt").ToList();
foreach (var fileLine in plages_horaires_ouverture)
{
var splitLignes = fileLine.Split(new[] { ";" }, StringSplitOptions.None);
listeJoursOuverture.Add(splitLignes[0]);
listeHeuresOuverture.Add(splitLignes[1]);
listeServeursOuverture.Add(splitLignes[2]);
}
this code splits the txt file into 3 separate lists containing strings, and now i would like to convert every element (string) of the list "listeServeursOuverture" (sorry for the french name) into a unique list. Every element in this list looks like this "server1,server2,server4" or "server2,server3" and is separated with a comma
Any ideas? I tried many things but none worked, this question is certainly stupid, and im sorry for that
Leno
Use List.AddRange():
List<String> servers = new List<String>();
servers.AddRange(splitLignes[2].Split(new[] { "," }, StringSplitOptions.None));
I'd use a dictionary here:
List<string> plages_horaires_ouverture = File.ReadAllLines(#"file.txt").ToList();
Dictionary<KeyValuePair<string, string>,List<string>> servers = new Dictionary<KeyValuePair<string, string>,List<string>>
foreach (var fileLine in plages_horaires_ouverture)
{
var splitLignes = fileLine.Split(new[] { ";" }, StringSplitOptions.None);
KeyValuePair<string, string> key = new KeyValuePair<string, string>(splitLignes[0], splitLignes[1]);
List<string> serversForDay = splitLignes[2].Split(new[] { "," }, StringSplitOptions.None);
servers.Add(key, serversForDay);
}
Instead of spreading your data into different Lists, I'd have one List (or at least some kind of DataStructure. You may later find a Dictionary helpful, maybe).
In that List, I'd have instances of a Model class like for example this one:
public class MyModel
{
public string Day {get; set;}
public int Hour {get; set;}
public List<string> Servers {get; set;} = new List<string>();
}
Then you can look up the Server Lists by Day as you want if I read the question correctly.
Populating may go something like this:
foreach (var fileLine in plages_horaires_ouverture)
{
var splitLignes = fileLine.Split(new[] { ";" }, StringSplitOptions.None);
var model = new MyModel{
Day = splitLignes[0],
Hour = int.Parse(splitLignes[1].Trim())
};
model.Servers.AddRange(splitLignes[2].Split(new[] { "," }, StringSplitOptions.None));
listeOverture.Add(model);
// don't need these anymore.
// listeJoursOuverture.Add(splitLignes[0]);
// listeHeuresOuverture.Add(splitLignes[1]);
// listeServeursOuverture.Add(splitLignes[2]);
}
You can edit the names and the types as needed. Make a class object to sort the data as you like, then make a method that returns a list of that class. This is more clear for you when you need to deal with your code later.
public List<DataModel> GetListFromData()
{
List<string> plages_horaires_ouverture = File.ReadAllLines( #"file.txt" ).ToList();
List<DataModel> list = new List<Dd>();
foreach ( var fileLine in plages_horaires_ouverture )
{
var splitLignes = fileLine.Split( ";" , StringSplitOptions.None );
List<string> d = splitLignes[0].Split( "," ).ToList();
List<string> h =splitLignes[1].Split( "," ).ToList();
List<string> s =splitLignes[2].Split( "," ).ToList();
list.Add( new DataModel( d , h[0] , s ) );
}
return list;
}
class DataModel
{
public DataModel( List<string> days,string hour , List<string> servers )
{
Hour = hour;
Days = days;
Servers = servers;
}
public string Hour { get; set; }
public List<string> Days { get; set; }
public List<string> Servers { get; set; }
}

c# use one variable value to set a second from a fixed list

I'm parsing a CSV file in a c# .net windows form app, taking each line into a class I've created, however I only need access to some of the columns AND the files being taken in are not standardized. That is to say, number of fields present could be different and the columns could appear in any column.
CSV Example 1:
Position, LOCATION, TAG, NAME, STANDARD, EFFICIENCY, IN USE,,
1, AFT-D3, P-D3101A, EQUIPMENT 1, A, 3, TRUE
2, AFT-D3, P-D3103A, EQUIPMENT 2, B, 3, FALSE
3, AFT-D3, P-D2301A, EQUIPMENT 3, A, 3, TRUE
...
CSV Example 2:
Position, TAG, STANDARD, NAME, EFFICIENCY, LOCATION, BACKUP, TESTED,,
1, P-D3101A, A, EQUIPMENT 1, 3, AFT-D3, FALSE, TRUE
2, P-D3103A, A, EQUIPMENT 2, 3, AFT-D3, TRUE, FALSE
3, P-D2301A, A, EQUIPMENT 3, 3, AFT-D3, FALSE, TRUE
...
As you can see, I will never know the format of the file I have to analyse, the only thing I know for sure is that it will always contain the few columns that I need.
My solution to this was to ask the user to enter the columns required and set as strings, the using their entry convert that to a corresponding integer that i could then use as a location.
string standardInpt = "";
string nameInpt = "";
string efficiencyInpt = "";
user would then enter a value from A to ZZ.
int standardLocation = 0;
int nameLocation = 0;
int efficiencyLocation = 0;
when the form is submitted. the ints get their final value by running through an if else... statement:
if(standard == "A")
{
standardLocation = 0;
}
else if(standard == "B")
{
standardLocation = 1;
}
...
etc running all the way to if VAR1 == ZZ and then the code is repeated for VAR2 and for VAR3 etc..
My class would partially look like:
class Equipment
{
public string Standard { get; set;}
public string Name { get; set; }
public int Efficiency { get; set; }
static Equipment FromLine(string line)
{
var data = line.split(',');
return new Equipment()
{
Standard = data[standardLocation],
Name = [nameLocation],
Efficiency = int.Parse(data[efficiencyLocation]),
};
}
}
I've got more code in there but i think this highlights where I would use the variables to set the indexes.
I'm very new to this and I'm hoping there has got to be a significantly better way to achieve this without having to write so much potentially excessive, repetitive If Else logic. I'm thinking some kind of lookup table maybe, but i cant figure out how to implement this, any pointers on where i could look?
You could make it automatic by finding the indexes of the columns in the header, and then use them to read the values from the correct place from the rest of the lines:
class EquipmentParser {
public IList<Equipment> Parse(string[] input) {
var result = new List<Equipment>();
var header = input[0].Split(',').Select(t => t.Trim().ToLower()).ToList();
var standardPosition = GetIndexOf(header, "std", "standard", "st");
var namePosition = GetIndexOf(header, "name", "nm");
var efficiencyPosition = GetIndexOf(header, "efficiency", "eff");
foreach (var s in input.Skip(1)) {
var line = s.Split(',');
result.Add(new Equipment {
Standard = line[standardPosition].Trim(),
Name = line[namePosition].Trim(),
Efficiency = int.Parse(line[efficiencyPosition])
});
}
return result;
}
private int GetIndexOf(IList<string> input, params string[] needles) {
return Array.FindIndex(input.ToArray(), needles.Contains);
}
}
You can use the reflection and attribute.
Write your samples in ,separated into DisplayName Attribute.
First call GetIndexes with the csv header string as parameter to get the mapping dictionary of class properties and csv fields.
Then call FromLine with each line and the mapping dictionary you just got.
class Equipment
{
[DisplayName("STND, STANDARD, ST")]
public string Standard { get; set; }
[DisplayName("NAME")]
public string Name { get; set; }
[DisplayName("EFFICIENCY, EFFI")]
public int Efficiency { get; set; }
// You can add any other property
public static Equipment FromLine(string line, Dictionary<PropertyInfo, int> map)
{
var data = line.Split(',').Select(t => t.Trim()).ToArray();
var ret = new Equipment();
Type type = typeof(Equipment);
foreach (PropertyInfo property in type.GetProperties())
{
int index = map[property];
property.SetValue(ret, Convert.ChangeType(data[index],
property.PropertyType));
}
return ret;
}
public static Dictionary<PropertyInfo, int> GetIndexes(string headers)
{
var headerArray = headers.Split(',').Select(t => t.Trim()).ToArray();
Type type = typeof(Equipment);
var ret = new Dictionary<PropertyInfo, int>();
foreach (PropertyInfo property in type.GetProperties())
{
var fieldNames = property.GetCustomAttribute<DisplayNameAttribute>()
.DisplayName.Split(',').Select(t => t.Trim()).ToArray();
for (int i = 0; i < headerArray.Length; ++i)
{
if (!fieldNames.Contains(headerArray[i])) continue;
ret[property] = i;
break;
}
}
return ret;
}
}
try this if helpful:
public int GetIndex(string input)
{
input = input.ToUpper();
char low = input[input.Length - 1];
char? high = input.Length == 2 ? input[0] : (char?)null;
int indexLow = low - 'A';
int? indexHigh = high.HasValue ? high.Value - 'A' : (int?)null;
return (indexHigh.HasValue ? (indexHigh.Value + 1) * 26 : 0) + indexLow;
}
You can use ASCII code for that , so no need to add if else every time
ex.
byte[] ASCIIValues = Encoding.ASCII.GetBytes(standard);
standardLocation = ASCIIValues[0]-65;

Output data to CSV specific columns from Dictionary c#

I am trying to output the values from the dictionary to the CSV and am able to do this. But facing issue with the specific columns this need to output to the csv. I need the specific data value from dictionary to be output to a specific column in csv.
Dictionary<string, List<string>> file = new Dictionary<string, List<string>>();
for (var i = 0; i < stringList.Count(); i++)
{
string line = stringList[i];
string path = line.Replace("\r\n", "");
path = path.Replace(" ", "");
path = path.TrimEnd(':');
if (File.Exists(path))
{
file[path] = file.ContainsKey(path) ? file[path] : new List<string>();
for (var j = i + 1; j < stringList.Count(); j++)
{
string line2 = stringList[j];
string path2 = line2.Replace("\r\n", "");
path2 = path2.Replace(" ", "");
path2 = path2.TrimEnd(':');
if (File.Exists(path2))
{
i = j - 1;
break;
}
else
{
if (path2.Contains("Verified") | path2.Contains("Algorithm"))
{
var strings = path2.Split(':');
var listValue = strings[1].Trim();
file[path].Add(listValue);
}
}
}
}
}
using (var writer = new StreamWriter(outputdir + "\\output_" +
DateTime.Now.ToString("yyyy_MM_dd_HHmmss") + ".csv"))
{
writer.WriteLine("FilePath,Signature,HashValueSHA1, HashValueSHA2, HashValueMD5, Other");
foreach (var keyvaluepair in file)
{
if (!keyvaluepair.Value.Contains("Unsigned"))
{
var values = String.Join(",", keyvaluepair.Value.Distinct().Select(x => x.ToString()).ToArray());
writer.WriteLine("{0},{1}", keyvaluepair.Key, values);
}
}
}
Current Output looks like below:
Sample output I need as below:
The Dictionary key(string) would hold the file path and the values(List) would hold something like below:
Signed
sha1RSA
md5RSA
md5RSA
Signed
sha1RSA
sha1RSA
sha256RSA
sha256RSA
Please suggest how can I get the one as required output.
I had a longer answer typed, but I see the problem.
On this line
var values = String.Join(",", keyvaluepair.Value.Distinct().Select(x => x.ToString()).ToArray());
take out Distinct. It looks like you have the correct number of items in each string, but if a list contains multiple blank entries Distinct is eliminating the duplicates. If a list contains two or three blank entries you need all of them. If you delete duplicate blanks your columns won't line up.
Also, when you use Distinct there's no guarantee that items will come back in any particular order. In this case the order is very important so that values end up in the right columns.
So in your example above, even though there's a blank in the third column of the first row, the value from the fourth column ends up in the third column and the blank goes to the end.
That will likely fix the immediate problem. I'd recommend not using a List<string> when you're expecting a certain number of values (they need to match up with columns) because a List<string> can contain any number of values.
Instead, try something like this:
public class WhateverThisIs
{
public string Signature { get; set; }
public string HashValueSha1 { get; set; }
public string HashValueSha2 { get; set; }
public string HashValueMd5 { get; set; }
public string Other { get; set; }
}
Then, as a starting point, use Dictionary<string, WhateverThisIs>.
Then the part that outputs lines would look more like this:
var value = keyvaluepair.Value;
var values = String.Join(",", value.Signature, value.HashValueSha1, value.HashValueSha2,
value.HashValueMd5, value.Other);
(and yes, that accounts for null values.)
If you want to replace nulls or empty values with "N/A" then you'd need a separate function for that, like
string ReplaceNullWithNa(string value)
{
return string.IsNullOrEmpty(value) ? "N/A" : value;
}

Reading a delimited text file by line and by delimiter in C#

I would just like to apologise for mposting this, there are a lot of questions like this here but I can't find one that is specific to this.
I have a list and each item contains a DateTime, int and a string. I have successfully written all list items to a .txt file which is delimited by commas.
For example 09/04/2015 22:12:00,10,Posting on Stackoverflow.
I need to loop through the file line by line, each line starting at index 0, through to index 2. At the moment I am able to call index 03, which returns the DateTime of the second list item in the text file. The file is written line by line, but I am struggling to read it back with the delimiters and line breaks.
I am sorry if I am not making much sense, I will appreciate any help, thank you.
string[] lines = File.ReadAllLines( filename );
foreach ( string line in lines )
{
string[] col = line.Split(',');
// process col[0], col[1], col[2]
}
You can read all the lines at once via var lines = File.ReadAllLines(pathToFile);. Then you can split each line into an array of fields via:
foreach (var line in lines) {
String[] fields = line.Split(',');
}
If you don't have any stray commas in your file and the only commas are true delimiters, this will mean that fields will always be a 3 element array, with each of your fields in succession.
Alternatively, you can do the following:
public static List<Values> GetValues(string path)
{
List<Values> valuesCollection = new List<Values>();;
using (var f = new StreamReader(path))
{
string line = string.Empty;
while ((line = f.ReadLine()) != null)
{
var parts = line.Split(',');
valuesCollection.Add(new Values(Convert.ToDateTime(parts[0]), Convert.ToInt32(parts[1]), parts[2]);
}
}
return valuesCollection;
}
class Values
{
public DateTime Date { get; set; }
public int IntValue { get; set; }
public string StringValue { get; set; }
public Values()
{
}
public Values(DateTime date, int intValue, string stringValue)
{
this.Date = date;
this.IntValue = intValue;
this.StringValue = stringValue;
}
}
You could then iterate through the list or collection of values and access each object and its properties. For example:
Console.WriteLine(values.Date);
Console.WriteLine(values.IntValue);
Console.WriteLine(values.StringValue);

Splitting data from a text file into parallel arrays

My professor gave the class an example of C# that can be used to split data from a text file. I am trying to use it for a project that involves splitting the contents of a txt. file into 4 arrays or fields. Here is the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
class Program
{
static void Main()
{
int i = 0;
foreach (string line in File.ReadAllLines("census.txt"))
{
string[] parts = line.Split(',');
foreach (string part in parts)
{
Console.WriteLine("{0}",
part);
}
i++;
}
}
}
Here is census.txt:
21,f, s, 14
41,f, m, 22
12, m, s, 12
11, f, s, 8
29, m, m, 4
6, m, s, 12
9, f, s, 2
30, f, s, 1
It's supposed to be hypothetical census data going by age, gender, marital status, and district. The output I keep getting is each of those numbers or chars in a single line like so:
21
f
s
14
41
f
m
22
and so on.
I think it means it's working but I'd like to know how to use this for entering into 4 parallel arrays. I would also to know more about splitting it into 4 fields, structs, or classes. The next part of the project involves counting every time a certain age number or district number appears and that will involve a lot of arrays.
I would extend irsog's answer a bit:
Use a class instead of a structure
Use properties instead of fields
Use Gender and MaritalStatus enums instead of plain strings
Code:
public class Person
{
public int Age { get; set; }
public MaritalStatus MaritalStatus { get; set; }
public Gender Gender { get; set; }
public int District { get; set; }
}
public enum MaritalStatus
{
Single, Married
}
public enum Gender
{
Male, Female
}
And usage:
var people = new List<Person>();
foreach (string line in File.ReadAllLines("Input.txt"))
{
string[] parts = line.Split(',');
people.Add(new Person() {
Age = int.Parse(parts[0]),
MaritalStatus = parts[1] == "s" ? MaritalStatus.Single : MaritalStatus.Married,
Gender = parts[2] == "m" ? Gender.Male : Gender.Female,
District = int.Parse(parts[3])
});
}
This is old thread, but as google shows it among first few pages I decided to send my comment. I strongly advise against given txt file format, because it is not error proof. If census.txt is not guaranteed to be ideal and especially if it is supposed to be created by some third party (user, administrator, whoever) then I strongly recommend records to be ended with some symbol, like this:
21,f, s, 14;
41,f, m, 22;
then first thing what we do - we get array of records, like this:
string[] lines = text.split(';');
then simply split again - this time to get record elements.
foreach (string record in lines)
{
string[] fields = record.split(',');
}
This way it is not only easier to read records/fields, but also you can easily check consistency of file, ignore errors (empty records), check number of fields in each records and etc.
A generic list (as used in the other 2 current answers here) is the best way to go. However, if you need to have the data in arrays (as your previous question seems to indicate), then you can modify your professor's code like this:
C#
int[] districtDataD = new int[900];
string[] districtDataG = new string[900];
string[] districtDataM = new string[900];
int[] districtDataA = new int[900];
int i = 0;
foreach (string line in File.ReadAllLines("census.txt"))
{
string[] parts = line.Split(',');
districtDataD[i] = int.Parse(parts[0]);
districtDataS[i] = parts[1];
districtDataM[i] = parts[2];
districtDataA[i] = int.Parse(parts[3]);
i++;
}
VB.NET (Since your original question was tagged with VB.NET):
Dim districtDataD() As New Integer(900)
Dim districtDataS() As New String(900)
Dim distrcitDataM() As New String(900)
Dim districtDataA() As New Integer(900)
Dim i As Integer = 0
For Each Dim line As String In File.ReadAllLines("census.txt")
Dim string() As parts = line.Split(',')
districtDataD(i) = Integer.Parse(parts(0))
districtDataS(i) = parts(1)
districtDataM(i) = parts(2)
districtDataA(i) = Integer.Parse(parts(3))
i++
Next
You could also use a struct or class and have one array that holds that object, but it looks like you're professor wants you to use 4 separate arrays. If you can use one, you can simply declare the array like this, for example:
C#
Person[] districtData = new Person[900];
VB.NET
Dim districtData() As New Person(900)
Then you could do this inside the split logic (note that if, say Distric and Age are integers in your object you'll have to cast or parse them as I show below):
C#
districtData[i] = new Person() { District = int.Parse(parts[0]), Gender = parts[1], MaritalStatus = parts[2], Age = int.Parse(parts[3]) };
VB.NET
districtData[i] = new Person() With { .District = Integer.Parse(parts[0]), .Gender = parts[1], .MaritalStatus = parts[2], .Age = Integer.Parse(parts[3]) }
There's a risk with this code that if you have more than 900 lines of data, you will get an index out of range exception. One way to avoid this would be to modify the code I put above with a while loop that checks the bounds of the target array(s) or the the number of lines haven't been exceed, like this:
C#
string[] lines = File.ReadAllLines("census.txt");
int i = 0;
while (i < 900 && i < parts.Length)
{
// split logic goes here
}
VB.NET
Dim lines As String() = File.ReadAllLines("census.txt")
Dim i As Integer = 0
While (i < 900 AndAlso i < lines.Length)
' split logic goes here
End While
I haven't tested the code, but this will hopefully help you if you must use arrays.
You can Make a structure for required information:
public struct Info
{
public int Age;
public string gender;
public string status;
public int district;
}
and inset data to your structure list:
List<Info> info = new List<Info>();
foreach (string line in File.ReadAllLines("census.txt"))
{
string[] parts = line.Split(',');
info.Add(new Info() {Age=int.Parse(parts[0]), gender=parts[1], status=parts[2], district=int.Parse(parts[3]) });
}
Now yo have a list of person information.

Categories