I have a list with multiple variables and would like to add to an array the write to a text file.
class DataFields
{
public string name{get;set;}
public int id{get;set;}
public int age{get;set;}
}
List<DataFields> dfList;
would look something like
Adam 1234 23
Pete 3841 15
Scot 8435 30
DataFields[] result = dfList.ToArray();
File.WriteAllLines(#"C:\File\TextFile.txt", result);
I would like the result to be displayed in the text file similar to the list above but I am having trouble adding the list to the array then display in that order. Any ideas?
File.WriteAllLines wants a collection of strings. You can generate one using LINQ:
List<DataFields> dfList = new List<DataFields>();
// populate dfList here
var formattedData =
dfList
.OrderBy(df => df.Name)
.Select(df => string.Format("{0} {1} {2}", df.Name, df.Id, df.Age));
File.WriteAllLines(#"C:\File\TextFile.txt", formattedData);
You should use PascalCase for your property names (Name, Id, Age).
var result = dfList.Select(df => String.Format("{0}\t{1}\t{2}", df.name, df.id, df.age);
you could do something like this too..
List<DataFields> dfList = new List<DataFields>();
// populate dfList here
StreamWriter sw = new StreamWriter(#"C:\File\TextFile.txt");
foreach (DataFields df in dfList)
{
sw.WriteLine(df.id+"\t"+df.name+"\t"+df.age);
}
sw.Close();
or simply use foreach of List which is more efficient.
dfList.ForEach(df=> sw.WriteLine(df.id + "\t" + df.name + "\t" + df.age));
Related
as the title suggests, I am looking for guidance in how to turn a string (csvData) into a 2D string array by splitting it two times with ';' and ',' respectivly.
Currently I am at the stage where I am able to split it once into rows and turn it into an array, but I cannot figure out how to instead create a 2D array where the columns divided by ',' are also separate.
string[] Sep = csvData.Split(';').Select(csvData => csvData.Replace(" ","")).Where(csvData => !string.IsNullOrEmpty(csvData)).ToArray();
I have tried various things like :
string[,] Sep = csvData.Split(';',',').Select(csvData => csvData.Replace(" ","")).Where(csvData => !string.IsNullOrEmpty(csvData)).ToArray();
naivly thinking that c# would understand what I tried to achieve, but since I am here it's obvious that I got the error that "cannot implicitly convert type string[] to string [*,*]"
Note that I have not coded for a while, so if my thinking is completely wrong and you do not understand what I am trying to convey with this question, I apologize in advance.
Thanks!
In a strongly-typed language like C#, the compiler makes no assumptions about what you intend to do with your data. You must make your intent explicit through your code. Something like this should work:
string csvData = "A,B;C,D";
string[][] sep = csvData.Split(';') // Returns string[] {"A,B","C,D"}
.Select(str => str.Split(',')) // Returns IEnumerable<string[]> {{"A","B"},{"C","D"}}
.ToArray(); // Returns string[][] {{"A","B"},{"C","D"}}
Rows are separated by semicolon, columns by comma?
Splitting by ';' gives you an array of rows. Split a row by ',' gives you an array of values.
If your data has a consistent schema, as in each csv you process has the same columns, you could define a class to represent the entity to make the data easier to with with.
Let's say it's customer data:
John,Smith,8675309,johnsmith#gmail.com;
You could make a class with those properties:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
}
Then:
var rows = csvdata.Split(';');
List<Customer> customers = new();
foreach(var row in rows)
{
var customer = row.Split(',');
customers.Add(new()
{
FirstName = row[0],
LastName = row[1],
Phone = row[2],
Email = row[3]
});
}
Now you have a list of customers to do whatever it is you do with customers.
Here is an answer to present a few alternative ideas and things you can do with C# - more for educational/academic purposes than anything else. These days to consume a CSV we'd use a CSV library
If your data is definitely regularly formed you can get away with just one Split. The following code splits on either char to make one long array. It then stands to reason that every 4 elements is a new customer, the data of the customer being given by n+0, n+1, n+2 and n+3. Because we know how many data items we will consume, dividing it by 4 gives us the number of customers so we can presize our 2D array
var bits = data.Split(';',',');
var twoD = new string[bits.Length/4,4];
for(int x = 0; x < bits.Length; x+=4){
twoD[x/4,0] = bits[x+0];
twoD[x/4,1] = bits[x+1];
twoD[x/4,2] = bits[x+2];
twoD[x/4,3] = bits[x+3];
}
I don't think I'd use 2D arrays though - and I commend the other answer advising to create a class to hold the related data; you can use this same technique
var custs = new List<Customer>();
for(int x = 0; x < bits.Length;){
custs.Add(new()
{
FirstName = bits[x++],
LastName = bits[x++],
Phone = bits[x++],
Email = bits[x++]
});
}
Here we aren't incrementing x in the loop header; every time a bit of info is assigned x is bumped up by 1 in the loop body. We could have kept the same approach as before, jumping it by 4 - just demoing another approach that lends itself well here.
I mentioned that these days we probably wouldn't really read a csv manually and split ourselves - what if the data contains a comma, or a semicolon - it wrecks the file structure
There are a boatload of libraries that read CSV files, CsvHelper is a popular one, and you'd use it like:
using var reader = new StreamReader("path\\to\\file.csv");
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture)
var custs = csv.GetRecords<Customer>().ToList();
...
Your file would have a header line with column names that match your property names in c#. If it doesn't then you can use attributes on the properties to tell CsvH what column should be mapped to what property - https://joshclose.github.io/CsvHelper/getting-started/
Here's the simplest way I know to produce a 2d array by splitting a string.
string csvData = "A,B,C;D,E,F,G";
var temporary =
csvData
.Split(';')
.SelectMany((xs, i) => xs.Split(',').Select((x, j) => new { x, i, j }))
.ToArray();
int max_i = temporary.Max(x => x.i);
int max_j = temporary.Max(x => x.j);
string[,] array = new string[max_i + 1, max_j + 1];
foreach (var t in temporary)
{
array[t.i, t.j] = t.x;
}
I purposely chose csvData to be missing a value.
temporary is this:
And the final array is this:
I have such struct:
public struct ParsedUser
{
public string Username;
public string pk;
public string RandPhotoId;
}
And I have filled List of ParsedUser:
List<ParsedUser> users;
How to write to file only Usernames?
Something like File.AppendAllLines(tag + ".txt", users.Select(x => new { x.Username} ));
You were close:
File.AppendAllLines(tag + ".txt", string.Join("\r\n", users.Select(u => u.Username)));
First of all you don't need to create a new object, you want just the name, so select it plainly.
Second, ju need to join these strings with "\r\n", a newline plus carriage return, that is what the string.Join does.
You can simply use :
var userNames = users.Select(x => x.Username).ToArray();
File.AppendAllLines(tag + ".txt", userNames);
Method AppendAllLines create new line per every string in IEnumerable<string>.
I am picking distinct values from a datatable column like below.
var uniqueCC = dtNew.AsEnumerable().Select(s => new { cc = s.Field<string>("ID"), }).Distinct().ToList();
var uniqueCode = dtNew.AsEnumerable().Select(s => new { Code = s.Field<string>("EAI"), }).Distinct().ToList();
Now I need to get the values alone in a comma seperated string and I'm using the below code and it doesn't take the value alone.
string strCC = String.Join(",", uniqueCC);
string strEAI = String.Join(",", uniqueCode);
Please provide some suggestions.
List values
cc=1, cc=2, cc=3
Expected Result
1,2,3
You can just use LINQ Select() method to pass the value alone to String.Join() :
string strCC = String.Join(",", uniqueCC.Select(o => o.cc));
Or just return string values instead of anonymous type in the first place :
var uniqueCC = dtNew.AsEnumerable().Select(s => s.Field<string>("ID")).Distinct();
string strCC = String.Join(",", uniqueCC);
You can use aggregate functions available in c#.
Let say you have a list of string called listOfString, then you can call the aggregate function like this.
string outString = listOfString.Aggregate((a, b) => a + "," + b);
It will do the trick.
As an extension to har07's excellent answer, if this is something you do a lot of the time, to save writing loads of code you could implement this as a static extension method, where you pass the datatype, the column name and your required separator - so it could handle different column datatypes and separators - in a separate file:
namespace Extensions
{
public static class LinqExtensions
{
public static String ReturnSeparatedString<T>(this DataTable datatable, string field, string separator)
{
var unique =
datatable.AsEnumerable()
.Select(s => new {cc = s.Field<string>(field),}).Distinct();
return String.Join(separator, unique.Select(o => o.cc));
}
}
}
Then call it from your code by creating a reference to your new Extensions.LinqExtensions class, and calling it directly on the dataTable like this:
var commaSeparatedIds = dataTable.ReturnSeparatedString<string>("ID",",");
var commaSeparatedEAIs = dataTable.ReturnSeparatedString<string>("EAI",",");
var commaSeparatedInts = dataTable.ReturnSeparatedString<int>("MYINTS",",");
var dotSeparatedStrings = dataTable.ReturnSeparatedString<int>("OtherId", ".");
I have a dictionary:
<string,List<string>>
The key is the product code say "product1" then the list is a list of properties:
"Brand","10.40","64","red","S"
Then I 'can' have a list of rules/filters e.g.
var tmpFilter = new customfilters();
tmpFilter.Field = "2";
tmpFilter.Expression = ">";
tmpFilter.Filter = "10";
So for the above example this would pass because at index 2 (tmpFilter.Field) it is more than 10; then I have another object which defines which fields within the list I want to write to file. For that dictionary item I just want to write the product brand and price where the filters match.
At the moment without the filter I have:
var tmp = new custom();
tmp.Columns = "0,1";
tmp.Delimiter = ",";
tmp.Extention = ".csv";
tmp.CustomFilters = new List<customfilters>() {new customfilters(){ Field = "2", Expression = ">", Filter = "10"} };
public static void Custom(custom custom)
{
foreach (var x in Settings.Prods)
{
//Get Current Product Code
var curprod = Settings.ProductInformation[x];// the dictionary value
foreach (var column in custom.Columns)
{
var curVal = curprod[Convert.ToInt32(column)];
tsw.Write(curVal + custom.Delimiter);
}
Settings.Lines++;
tsw.WriteLine();
}
tsw.Close();
}
I only want to write the curprod if all the filters pass for that list of strings.
How I can do this?
There's a really nice Nuget package based on an example published by Microsoft, that they have decided to make really hard to find for some reason, that allows dynamic linq queries:
https://www.nuget.org/packages/System.Linq.Dynamic/1.0.2
Source:
https://github.com/kahanu/System.Linq.Dynamic
Using that you can do stuff like this very easily (note: I used strings here because the OP states they have a List<string>):
List<string> stuff = new List<string> { "10.40", "64", "5", "56", "99", "2" };
var selected = stuff.Select(s => new { d = double.Parse(s) }).Where("d > 10");
Console.WriteLine(string.Join(", ", selected.Select(s => s.d.ToString()).ToArray()));
Outputs:
10.4, 64, 56, 99
That may give you a place to start. One thing you are going to have to tackle is identifying which of your fields are numeric and should be converted to a numeric type before trying to apply your filter. Otherwise you are going to comparing as strings.
I have a simple class like this:
class QuickReport
{
public string DeviceName { get; set; }
public string GroupName { get; set; }
public string PinName { get; set; }
public override string ToString()
{
return DeviceName + "," + GroupName + "," + PinName;
}
}
Later I make a list of items with this class:
List<QuickReport> QR = new List<QuickReport>();
Later in my program it will fill up and when I save it in a text file it will be like this example:
HBM\D1,GND,10
HBM\D1,GND,12
HBM\D1,NT_IOp,115
HBM\D1,NT_IOp,117
HBM\D2,GND,8
HBM\D2,NT_IOp,115
HBM\D2,NT_IOp,116
Now I want to make a function to save the text file in more readable manner. That is formatting it by DEVICE, GROUPS and PINS. So the above example would result in:
HBM\D1
GND: 10, 12
NT_IOp: 115, 117
HBM\D2
GND: 8
NT_IOp: 115, 116
can you please help and give some ideas?
Thanks!
var query = QR.ToLookup(i=>i.DeviceName, i => new {i.GroupName, i.PinName})
.Select(i=>
new {DeviceName = i.Key,
Groups = i.ToLookup(g=>g.GroupName, g=>g.PinName)});
var sb = new StringBuilder();
foreach ( var device in query)
{
sb.AppendLine(device.DeviceName);
foreach ( var gr in device.Groups)
{
sb.Append(gr.Key + ": ");
sb.Append(String.Join(", ", gr.ToArray()));
sb.AppendLine();
}
sb.AppendLine();
}
var stringToWrite = sb.ToString();
As i understand you have tree structure, where Device have child Groups, and Groups have child pins.
You can create custom classes like this:
class Group
{
string Name;
//pins that belong to this group
List<string> pins;
}
class Device
{
string Name;
//groups that belong to this device
List<Group> Groups;
}
And than just collect it to List<Device> and serialize it using XML Serialization.
This isn't complete, but it should give you enough to go on. You'll still need to add your newlines, and remove trailing commas, etc.
// Make your key the device name
var qrHash = new Dictionary<string, List<QuickReport>>();
// Populate your QR Dictionary here.
var output = new StringBuilder();
foreach (var keyValuePair in qrHash)
{
output.Append(keyValuePair.Key);
var gnd = new StringBuilder("GND: ");
var nt = new StringBuilder("NT_IOp: ");
foreach (var qr in keyValuePair.Value)
{
gnd.Append(qr.GroupName);
nt.Append(qr.PinName);
}
output.Append(gnd);
output.Append(nt);
}
How about using the XmlSerializer to serialize and deserialize your class? This should provide some readable output.
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx
The quickest ways I can think of to do this would either be to loop over the List<> 3 times, eachtime checking on a seperate accessor, writing it out to a StringBuilder, then returning StringBuilder.ToString() from the function.
Or, you could use 3 stringbuilders to hold each accessor type, then push all 3 from the function on return.