Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 months ago.
Improve this question
I have a CSV File with these headers:
date;clock;value
My aim is to select the CSV line with a specific date to get the corresponding value.
For example:
I want to select date 20.08.22 and the result should be 130
15.08.22;07:05;100
20.08.22;08:04;130
21.08.22;10:04;150
With this code snippet I read the lines of the csv file:
private void Werte_aus_CSV_auslesen()
{
var path = #"E:\werte.csv";
using (TextFieldParser csvParser = new TextFieldParser(path))
{
csvParser.CommentTokens = new string[] { "#" };
csvParser.SetDelimiters(new string[] { ";" });
csvParser.HasFieldsEnclosedInQuotes = true;
// Skip the row with the column names
csvParser.ReadLine();
while (!csvParser.EndOfData)
{
// Read current line fields, pointer moves to the next line.
fields = csvParser.ReadFields();
datum.Add(fields[0]);
uhrzeit.Add(fields[1]);
wert.Add(double.Parse(fields[2], CultureInfo.InvariantCulture));
}
}
}
The approach you are using is going to have to scan the entire CSV every time you lookup a value. This might be a performance problem if this method is called multiple times. It would be better to build a dictionary that maps the date to the value that can be built once and reused for each subsequent lookup.
I maintain a couple libraries that make this pretty easy: Sylvan.Data and Sylvan.Data.Csv. Here is a complete C# 10 console app that demonstrates how to accomplish this:
using Sylvan.Data.Csv;
using Sylvan.Data;
// data: would normally use CsvDataReader.Create(csvFileName, opts);
var data =
new StringReader(
#"date;clock;value
15.08.22;07:05;100
20.08.22;08:04;130
21.08.22;10:04;150
");
// parameter:
var selectDate = new DateTime(2022, 8, 20);
// configure settings so the csv reader understands your data
var opts = new CsvDataReaderOptions
{
DateTimeFormat = "dd'.'MM'.'yy",
// ignore clock, as it isn't used
Schema = new CsvSchema(Schema.Parse("date:date,clock,value:int"))
};
var csvReader = CsvDataReader.Create(data, opts);
// create a dictionary to cache the CSV data for quick lookups
// creating the dictionary scans the whole dataset, but subsequent lookups will
// be blazing fast.
{
var dict =
csvReader
.GetRecords<Record>() // bind the CSV data to the Record class
.ToDictionary(r => r.Date, r => r.Value);
Console.WriteLine(dict.TryGetValue(selectDate, out var value) ? value.ToString() : "Value not found");
}
class Record
{
public DateTime Date { get; set; }
public int Value { get; set; }
}
Matched arrays/lists like datum, uhrzeit, and wert that relate values within each collection based on index is an anti-pattern... something to avoid. So much better to create a class with fields for each of the values, and then have one collection to hold the class.
public class MyData
{
public DateTime date {get;set;}
public int value {get;set;}
}
(Of course, give it a better name than "MyData")
Newer code might also use a record instead of a class.
We can further improve this by separating the code to read the csv data from the code that composes the objects. Start with something like this:
private IEnumerable<string[]> Werte_aus_CSV_auslesen(string path)
{
using (TextFieldParser csvParser = new TextFieldParser(path))
{
csvParser.CommentTokens = new string[] { "#" };
csvParser.SetDelimiters(new string[] { ";" });
csvParser.HasFieldsEnclosedInQuotes = true;
// Skip the row with the column names
csvParser.ReadLine();
while (!csvParser.EndOfData)
{
// Read current line fields, pointer moves to the next line.
yield return csvParser.ReadFields();
}
}
}
Notice how it accepts an input and returns an object (the enumerable with the data). Also notice how it avoids anything to do with processing the individual rows. It is only concerned with parsing the CSV/SSV inputs. It doesn't care what fields you expect to find, and can handle any file input with a header line, hash comments, and semi-colon field separators.
Since this gives us string[] values, we also add a method to transform a string[] into a class instance. I like to start out with this as a static method of the class itself, but as a project grows to have many of these methods they may eventually be moved to their own static type:
public class MyData
{
public DateTime date {get;set;}
public int value {get;set}
public static MyData FromCSVRow(string[] input)
{
return new MyData() {
date = DateTime.ParseExact($"{input[0]} {input[1]}", "dd.MM.yy HH:mm", null),
value = int.Parse(input[2])
};
}
}
And now with all that out of the way, we can finally put it all together to get your answer:
var targetDate = new DateTime(2022, 8, 20);
var csv = Werte_aus_CSV_auslesen(#"E:\werte.csv");
var rows = csv.Select(MyData.FromCSV);
var result = rows.Where(r => r.date.Date == targetDate);
If we really wanted to, we could even treat all that as a single line of code (it's probably better to keep it separate, for readability/maintainability):
var result = Werte_aus_CSV_auslesen(#"E:\werte.csv").
Select(MyData.FromCSV).
Where(r => r.date.Date == new DateTime(2022, 8, 20));
Note result is still an IEnumerable<MyData>, because there might be more than one row matching the criteria. If you are really sure there will only be one matching record, you can use this:
var result = rows.Where(r => r.date.Date == targetDate).FirstOrDefault();
or this:
var result = rows.Where(r => r.date.Date == targetDate).First();
depending on what you want to happen if no match is found.
One of the nice features here is this checks each record as it reads the file, and will stop reading the file as soon as it finds a match, which is potentially a very nice performance win.
Related
I want to sort a List Array on the basis of an array item.
I have a List Array of Strings as below:
List<String>[] MyProjects = new List<String>[20];
Through a loop, I have added five strings
(Id, Name, StartDate, EndDate, Status)
to each of the 20 projects from another detailed List source.
for(int i = 0; i<20; i++){
MyProjects[i].Add(DetailedProjectList.Id.ToString());
MyProjects[i].Add(DetailedProjectList.Name);
MyProjects[i].Add(DetailedProjectList.StartDate);
MyProjects[i].Add(DetailedProjectList.EndDate);
MyProjects[i].Add(DetailedProjectList.Status)}
The Status values are
"Slow", "Normal", "Fast", "Suspended" and "" for unknown status.
Based on Status, I want to sort MyProject List Array.
What I have done is that I have created another List as below
List<string> sortProjectsBy = new List<string>(){"Slow", "Normal", "Fast", "", "Suspended"};
I tried as below to sort, however unsuccessful.
MyProjects = MyProjects.OrderBy(x => sortProjectsBy.IndexOf(4));
Can anyone hint in the right direction. Thanks.
I suggest you to create class Project and then add all the fields inside it you need. It's much nicer and scalable in the future. Then create a List or an Array of projects and use the OrderBy() function to sort based on the field you want.
List<Project> projects = new List<>();
// Fill the list...
projects.OrderBy(project => project.Status);
The field Status has to be a primitive type or needs to implement the interface IComparable in order for the sorting to work. I suggest you add an enum for Status with int values.
First consider maybe to use Enum for status and put it in a different file lite (utils or something) - better to work like that.
enum Status {"Slow"=1, "Normal", "Fast", "", "Suspend"}
Now about the filtering you want to achieve do it like this (you need to tell which attribute of x you are referring to. In this case is status)
MyProjects = MyProjects.OrderBy(x => x.status == enum.Suspend);
Read about enums :
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum
Read about lambda expressions :
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions
First of all, storing project details as List is not adivisable. You need to create a Custom Class to represent them.
For example,
public class DetailedProjectList
{
public string Name {get;set;}
public eStatus Status {get;set;}
// rest of properties
}
Then You can use
var result = MyProjects.OrderBy(x=> sortProjectsBy.IndexOf(x.Status));
For example
List<string> sortProjectsBy = new List<string>(){"Slow", "Normal", "Fast", "", "Suspended"};
var MyProjects= new List<DetailedProjectList>{
new DetailedProjectList{Name="abc1", Status="Fast"},
new DetailedProjectList{Name="abc2", Status="Normal"},
new DetailedProjectList{Name="abc3", Status="Slow"},
};
var result = MyProjects.OrderBy(x=> sortProjectsBy.IndexOf(x.Status));
Output
abc3 Slow
abc2 Normal
abc1 Fast
A better approach thought would be to use Enum to represent Status.
public enum eStatus
{
Slow,
Normal,
Fast,
Unknown,
Suspended
}
Then your code can be simplified as
var MyProjects= new List<DetailedProjectList>{
new DetailedProjectList{Name="abc1", Status=eStatus.Fast},
new DetailedProjectList{Name="abc2", Status=eStatus.Normal},
new DetailedProjectList{Name="abc3", Status=eStatus.Slow},
};
var result = MyProjects.OrderBy(x=> x.Status);
Ok so you have a collection of 20 items. Based on them you need to create a list of strings(20 DetailedProjectList items).
What you can do to solve your problem is to SORT YOUR COLLECTION before you create your list of strings. In this way your list of strings will be sorted.
But your code is not optimal at all. So you should concider optimization on many levels.
Lets say you have ProjectDetail class as follow:
private class ProjectDetail
{
public int Id {get;set;}
public string Name {get;set;}
DateTime StartDate {get;set;} = DateTime.Now;
DateTime EndDate {get;set;} = DateTime.Now;
public string Status {get;set;}
public string toString => $"{Id} - {Name} - {StartDate} - {EndDate} - {Status}";
}
Notice that I have added a toString attribute to make things easier, and I also have added default values.
Then your program could be like:
static void Main(string[] args)
{
var projectDetails = MockProjectItems();
Console.WriteLine("Before sortig:");
foreach (var item in projectDetails)
{
Console.WriteLine(item.toString);
}
var myProjects = projectDetails.OrderBy(p => p.Status).Select(p => p.toString);
Console.WriteLine("\n\nAfter sorting:");
foreach (var item in myProjects)
{
Console.WriteLine(item);
}
}
where the helper method is
private static List<ProjectDetail> MockProjectItems()
{
var items = new List<ProjectDetail>(20);
for(int i = 0; i < 20 ; i += 4){
items.Add(new ProjectDetail{Id = i, Name = "RandomName "+i, Status = "Slow"});
items.Add(new ProjectDetail{Id = i+1, Name = "RandomName "+(i+1), Status = "Normal"});
items.Add(new ProjectDetail{Id = i+2, Name = "RandomName "+(i+2), Status = "Fast"});
items.Add(new ProjectDetail{Id = i+3, Name = "RandomName "+(i+3), Status = "Suspended"});
}
return items;
}
Then your program should print the following:
I have a testing framework that needs to be updated to include testing in Spanish. I have a CSV file that contains the field label, english text, and Spanish text. I've decided to use a dictionary to store the field label as the key and the values would be a list of strings for Spanish and English text.
private List<string> ReadTranslationCsv()
{
var pathToCSV = #"C:\Location";
Dictionary<string, List<string>> translations = new Dictionary<string, List<string>>();
string label, englishText, spanishText;
using (TextReader fileReader = File.OpenText(pathToCSV))
{
var csv = new CsvReader(fileReader);
csv.Configuration.HasHeaderRecord = false;
while (csv.Read())
{
for (int i = 0; csv.TryGetField<string>(i, out label);)
{
List<string> Spanglish = new List<string>();
csv.TryGetField<string>(i + 1, out englishText);
Spanglish.Add(englishText);
csv.TryGetField<string>(i + 2, out spanishText);
Spanglish.Add(spanishText);
if (label != "")
{
translations.Add(label, Spanglish);
}
i = i + 3;
}
}
}
}
I want to be able to search within the list of values to see if anything matches some string of text. I'm not sure how to search the lists that are within the dictionary, none of the default methods or properties are working.
I'm using the below code but this will return me a bool, which is not what I need, I need the list value that matches the elementWithText
public void GivenElementMatches(string elementWithText)
{
if (Config.Language == "Spanish")
{
var list = new List<string> { elementWithText };//must create list in order to pass text to any translations methods
Hooks.translations.ContainsValue(list); // Even though the labels are the key, I need to search for the english text which is index 1 of the list and all values should be returned
}
//TODO
}
My suggestion would be to use a Dictionary with a class you create, inside that class you can have a compare function.
The advantage of this method is you may add more language equivalents later and only have to change your model.
Please note, this code is not complete and you will have to bug check and alter it to suit.
Dictionary <string, LangEquivalents> model;
public KeyValuePair<string, LangEquivalents> findField(string input)
{
return model.First(x=>x.Value.Comparison(input));
}
You could also make it a comparable object type and just use model.First(x=>x.Value == input));
I've got dates from separate countries within a single List<>. I'm trying to get two records that contain the same characters before the second comma, and replace BOTH of those items with a new one.
Example:
From This:
18/04/2014,Good Friday,England and Wales
18/04/2014,Good Friday,Scotland
Into this:
18/04/2014,Good Friday,"England, Wales and Scotland"
Please note there may be multiple scenarios within the list like the above example. I've managed to get everything before the second Comma with:
splitSubstring = line.Remove(line.LastIndexOf(','));
I've tried the below, but it's clearly flawed since it won't delete both the records even if it does find a match:
foreach (var line in orderedLines)
{
if (splitSubstring == line.Remove(line.LastIndexOf(',')))
{
//Replace if previous is match here
}
splitSubstring = line.Remove(line.LastIndexOf(','));
File.AppendAllText(correctFile, line);
}
I would suggest parsing it into a structure you can work with e.g.
public class HolidayInfo
{
public DateTime Date { get; set; }
public string Name { get; set; }
public string[] Countries { get; set; }
};
And then
string[] lines = new string[]
{
"18/04/2014,Good Friday,England and Wales",
"18/04/2014,Good Friday,Scotland"
};
// splits the lines into an array of strings
IEnumerable<string[]> parsed = lines.Select(l => l.Split(','));
// copy the parsed lines into a data structure you can write code against
IEnumerable<HolidayInfo> info = parsed
.Select(l => new HolidayInfo
{
Date = DateTime.Parse(l[0]),
Name = l[1],
Countries = l[2].Split(new[] {",", " and " }, StringSplitOptions.RemoveEmptyEntries)
});
...etc. And once you have it in a helpful data structure you can begin to develop the required logic. The above code is just an example, the approach is what you should focus on.
I ended up using LINQ to pull apart the List and .Add() them into another based on an if statement. LINQ made it nice and simple.
//Using LINQ to seperate the two locations from the list.
var seperateScotland = from s in toBeInsertedList
where s.HolidayLocation == scotlandName
select s;
var seperateEngland = from e in toBeInsertedList
where e.HolidayLocation == engAndWales
select e;
Thanks for pointing me to LINQ
I'm new to C# and programming as a whole and I've been unable to come up with a solution to what I want to do. I want to be able to create a way to display several arrays containing elements from three external text files with values on each line (e.g. #"Files\Column1.txt", #"Files\Column2.txt" #"Files\Column3.txt"). They then need to be displayed like this in the command line:
https://www.dropbox.com/s/0telh1ils201wpy/Untitled.png?dl=0
I also need to be able to sort each column individually (e.g. column 3 from lowest to highest).
I've probably explained this horribly but I'm not sure how else to put it! Any possible solutions will be greatly appreciated!
One way to do it would be to store the corresponding items from each file in a Tuple, and then store those in a List. This way the items will all stay together, but you can sort your list on any of the Tuple fields. If you were doing anything more detailed with these items, I would suggest creating a simple class to store them, so the code would be more maintainable.
Something like:
public class Item
{
public DayOfWeek Day { get; set; }
public DateTime Date { get; set; }
public string Value { get; set; }
}
The example below could easily be converted to use such a class, but for now it uses a Tuple<string, string, string>. As an intermediate step, you could easily convert the items as you create the Tuple to get more strongly-typed versions, for example, you could have Tuple<DayOfWeek, DateTime, string>.
Here's the sample code for reading your file items into a list, and how to sort on each item type:
public static void Main()
{
// For testing sake, I created some dummy files
var file1 = #"D:\Public\Temp\File1.txt";
var file2 = #"D:\Public\Temp\File2.txt";
var file3 = #"D:\Public\Temp\File3.txt";
// Validation that files exist and have same number
// of items is intentionally left out for the example
// Read the contents of each file into a separate variable
var days = File.ReadAllLines(file1);
var dates = File.ReadAllLines(file2);
var values = File.ReadAllLines(file3);
var itemCount = days.Length;
// The list of items read from each file
var fileItems = new List<Tuple<string, string, string>>();
// Add a new item for each line in each file
for (int i = 0; i < itemCount; i++)
{
fileItems.Add(new Tuple<string, string, string>(
days[i], dates[i], values[i]));
}
// Display the items in console window
fileItems.ForEach(item =>
Console.WriteLine("{0} {1} = {2}",
item.Item1, item.Item2, item.Item3));
// Example for how to order the items:
// By days
fileItems = fileItems.OrderBy(item => item.Item1).ToList();
// By dates
fileItems = fileItems.OrderBy(item => item.Item2).ToList();
// By values
fileItems = fileItems.OrderBy(item => item.Item3).ToList();
// Order by descending
fileItems = fileItems.OrderByDescending(item => item.Item1).ToList();
// Show the values based on the last ordering
fileItems.ForEach(item =>
Console.WriteLine("{0} {1} = {2}",
item.Item1, item.Item2, item.Item3));
}
I have a flat file with an unfortunately dynamic column structure. There is a value that is in a hierarchy of values, and each tier in the hierarchy gets its own column. For example, my flat file might resemble this:
StatisticID|FileId|Tier0ObjectId|Tier1ObjectId|Tier2ObjectId|Tier3ObjectId|Status
1234|7890|abcd|efgh|ijkl|mnop|Pending
...
The same feed the next day may resemble this:
StatisticID|FileId|Tier0ObjectId|Tier1ObjectId|Tier2ObjectId|Status
1234|7890|abcd|efgh|ijkl|Complete
...
The thing is, I don't care much about all the tiers; I only care about the id of the last (bottom) tier, and all the other row data that is not a part of the tier columns. I need normalize the feed to something resembling this to inject into a relational database:
StatisticID|FileId|ObjectId|Status
1234|7890|ijkl|Complete
...
What would be an efficient, easy-to-read mechanism for determining the last tier object id, and organizing the data as described? Every attempt I've made feels kludgy to me.
Some things I've done:
I have tried to examine the column names for regular expression patterns, identify the columns that are tiered, order them by name descending, and select the first record... but I lose the ordinal column number this way, so that didn't look good.
I have placed the columns I want into an IDictionary<string, int> object to reference, but again reliably collecting the ordinal of the dynamic columns is an issue, and it seems this would be rather non-performant.
I ran into a simular problem a few years ago. I used a Dictionary to map the columns, it was not pretty, but it worked.
First make a Dictionary:
private Dictionary<int, int> GetColumnDictionary(string headerLine)
{
Dictionary<int, int> columnDictionary = new Dictionary<int, int>();
List<string> columnNames = headerLine.Split('|').ToList();
string maxTierObjectColumnName = GetMaxTierObjectColumnName(columnNames);
for (int index = 0; index < columnNames.Count; index++)
{
if (columnNames[index] == "StatisticID")
{
columnDictionary.Add(0, index);
}
if (columnNames[index] == "FileId")
{
columnDictionary.Add(1, index);
}
if (columnNames[index] == maxTierObjectColumnName)
{
columnDictionary.Add(2, index);
}
if (columnNames[index] == "Status")
{
columnDictionary.Add(3, index);
}
}
return columnDictionary;
}
private string GetMaxTierObjectColumnName(List<string> columnNames)
{
// Edit this function if Tier ObjectId is greater then 9
var maxTierObjectColumnName = columnNames.Where(c => c.Contains("Tier") && c.Contains("Object")).OrderBy(c => c).Last();
return maxTierObjectColumnName;
}
And after that it's simply running thru the file:
private List<DataObject> ParseFile(string fileName)
{
StreamReader streamReader = new StreamReader(fileName);
string headerLine = streamReader.ReadLine();
Dictionary<int, int> columnDictionary = this.GetColumnDictionary(headerLine);
string line;
List<DataObject> dataObjects = new List<DataObject>();
while ((line = streamReader.ReadLine()) != null)
{
var lineValues = line.Split('|');
string statId = lineValues[columnDictionary[0]];
dataObjects.Add(
new DataObject()
{
StatisticId = lineValues[columnDictionary[0]],
FileId = lineValues[columnDictionary[1]],
ObjectId = lineValues[columnDictionary[2]],
Status = lineValues[columnDictionary[3]]
}
);
}
return dataObjects;
}
I hope this helps (even a little bit).
Personally I would not try to reformat your file. I think the easiest approach would be to parse each row from the front and the back. For example:
itemArray = getMyItems();
statisticId = itemArray[0];
fileId = itemArray[1];
//and so on for the rest of your pre-tier columns
//Then get the second to last column which will be the last tier
lastTierId = itemArray[itemArray.length -1];
Since you know the last tier will always be second from the end you can just start at the end and work your way forwards. This seems like it would be much easier than trying to reformat the datafile.
If you really want to create a new file, you could use this approach to get the data you want to write out.
I don't know C# syntax, but something along these lines:
split line in parts with | as separator
get parts [0], [1], [length - 2] and [length - 1]
pass the parts to the database handling code