Loading CSV in C# - c#

I want to search the penultimate row in the first.csv which date is 1975-01-03 and the Lemon value is 17.0, after I search in the second.csv the same date which lemon is 19.0
After catching both values, I compute the difference 17.0 - 19.0 = -2.0
The next step is to sum the difference -2 to all Lemon's values in second.csv from the date 1975-01-03 to the end 1975-01-09
The final step is to write the third.csv where we add the first.csv until the date 1975-01-02 and the sum we've done with second.csv from 1975-01-03 to the end 1975-01-09
first.csv
Date,Lemon
1974-12-31,19.0
1975-01-02,18.0
1975-01-03,17.0
1975-01-06,16.0
second.csv
Date,Lemon
1975-01-02,18.0
1975-01-03,19.0
1975-01-06,19.5
1975-01-07,19.5
1975-01-08,18.0
1975-01-09,17.0
third.csv
Date,Lemon
1974-12-31,19.0
1975-01-02,18.0
1975-01-03,17.0
1975-01-06,17.5
1975-01-07,17.5
1975-01-08,16.0
1975-01-09,15.0
All in all, the read from CSV is not as important as to obtain the third result in an Array, DataTable, Dictionary or whatever. Thanks

Start with a handy struct to make the coding easier:
public struct Line
{
public DateTime Timestamp;
public decimal Lemon;
}
Then you can write a simple function to load your CSV files:
Func<string, Line[]> readCsv =
fn =>
File
.ReadLines(fn)
.Skip(1)
.Select(x => x.Split(','))
.Select(y => new Line()
{
Timestamp = DateTime.Parse(y[0]),
Lemon = decimal.Parse(y[1])
})
.ToArray();
Now the rest is just a reading the files and a couple of LINQ queries before writing out the results:
Line[] first = readCsv(#"C:\_temp\first.csv");
Line[] second = readCsv(#"C:\_temp\second.csv");
Line difference =
(
from pen in first.Skip(first.Length - 2).Take(1)
from mtch in second
where mtch.Timestamp == pen.Timestamp
select new Line()
{
Timestamp = pen.Timestamp,
Lemon = pen.Lemon - mtch.Lemon
}
).First();
IEnumerable<string> result =
new [] { "Date,Lemon" }
.Concat(
first
.Where(x => x.Timestamp < difference.Timestamp)
.Concat(
second
.Where(x => x.Timestamp >= difference.Timestamp)
.Select(x => new Line()
{
Timestamp = x.Timestamp,
Lemon = x.Lemon + difference.Lemon
}))
.Select(x => String.Format(
"{0},{1}",
x.Timestamp.ToString("yyyy-MM-dd"),
x.Lemon)));
File.WriteAllLines(#"C:\_temp\third.csv", result);
The result I get is:
Date,Lemon
1974-12-31,19.0
1975-01-02,18.0
1975-01-03,17.0
1975-01-06,17.5
1975-01-07,17.5
1975-01-08,16.0
1975-01-09,15.0

This looks like homework, I strongly advice you to do this exercice by yourself by learning about LINQ (just google it). If you are stuck or can't find the solution here is a way to do it :
class LemonAtDate
{
public DateTime Date { get; set; }
public double Value { get; set; }
public LemonAtDate(DateTime Date, double Value)
{
this.Date = Date;
this.Value = Value;
}
public static List<LemonAtDate> LoadFromFile(string filepath)
{
IEnumerable<String[]> lines = System.IO.File.ReadLines(filepath).Select(a => a.Split(','));
List<LemonAtDate> result = new List<LemonAtDate>();
int index = 0;
foreach (String[] line in lines)
{
index++;
if (index == 1) continue; //skip header
DateTime date = DateTime.ParseExact(line[0], "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
double value = Double.Parse(line[1], System.Globalization.CultureInfo.InvariantCulture);
result.Add(new LemonAtDate(date, value));
}
return result;
}
public static void WriteToFile(IEnumerable<LemonAtDate> lemons, string filename)
{
//Write to file
using (var sw = new System.IO.StreamWriter(filename))
{
foreach (LemonAtDate lemon in lemons)
{
sw.WriteLine("Date,Lemon");//Header
string date = lemon.Date.ToString("yyyy-MM-dd");
string value = lemon.Value.ToString();
string line = string.Format("{0},{1}", date, value);
sw.WriteLine(line);
}
}
}
}
static void Main(string[] args)
{
//Load first file
List<LemonAtDate> firstCsv = LemonAtDate.LoadFromFile("first.csv");
//Load second file
List<LemonAtDate> secondCsv = LemonAtDate.LoadFromFile("second.csv");
//We need at least two rows
if (firstCsv.Count >= 2)
{
//Penultimate row in first file
LemonAtDate lemonSecondLast = firstCsv[firstCsv.Count - 2];
//Find the value 19 in the second file
LemonAtDate lemonValue19 = secondCsv.Where(x => x.Value == 19).FirstOrDefault();
//Value found
if (lemonValue19 != null)
{
double delta = lemonSecondLast.Value - lemonValue19.Value;
//Get the items between the dates and add the delta
DateTime dateStart = new DateTime(1975, 1, 3);
DateTime dateEnd = new DateTime(1975, 1, 9);
IEnumerable<LemonAtDate> secondFileSelection = secondCsv.Where(x => x.Date >= dateStart && x.Date <= dateEnd)
.Select(x => { x.Value += delta; return x; });
//Create third CSV
List<LemonAtDate> thirdCsv = new List<LemonAtDate>();
//Add the rows from the first file until 1975-01-02
DateTime threshold = new DateTime(1975, 1, 2);
thirdCsv.AddRange(firstCsv.Where(x => x.Date <= threshold));
//Add the rows from the second file
thirdCsv.AddRange(secondFileSelection);
//Write to file
LemonAtDate.WriteToFile(thirdCsv, "third.csv");
}
}
}

There are better ways of doing this, I took a quick and dirty procedural approach instead of an OO one. I also took a peek at the other answer and I see he parsed out the datetimes. I decided not to since you weren't doing any math specifically based on that. However his answer would be more flexible as with datetimes you can do more operations in the future.
List<string> csvfile1Text = System.IO.File.ReadAllLines("file1.csv").ToList();
List<string> csvfile2Text = System.IO.File.ReadAllLines("file2.csv").ToList();
Dictionary<string, double> csv1Formatted = new Dictionary<string, double>();
Dictionary<string, double> csv2Formatted = new Dictionary<string, double>();
Dictionary<string, double> csv3Formatted = new Dictionary<string, double>();
foreach (string line in csvfile1Text)
{
var temp= line.Split(',');
csv1Formatted.Add(temp[0], Double.Parse(temp[1]));
}
foreach (string line in csvfile2Text)
{
var temp = line.Split(',');
csv2Formatted.Add(temp[0], Double.Parse(temp[1]));
}
//operation 1
var penultimate = csv1Formatted["1974-01-03"];
var corrsponding = csv2Formatted["1974-01-03"];
var difference = penultimate - corrsponding;
//operation 2
var start = csv2Formatted["1974-01-03"];
var end = csv2Formatted["1974-01-09"];
var intermediate = csv2Formatted.Keys.SkipWhile((element => element != "1974-01-03")).ToList();
Dictionary<string, double> newCSV2 = new Dictionary<string, double>();
foreach (string element in intermediate)
{
var found = csv2Formatted[element];
found = found + difference;
newCSV2.Add(element, found);
}
//operation 3
intermediate = csv1Formatted.Keys.TakeWhile((element => element != "1975-01-03")).ToList();
foreach (string element in intermediate)
{
var found = csv1Formatted[element];
csv3Formatted.Add(element, found);
}
foreach (KeyValuePair<string,double> kvp in newCSV2)
{
csv3Formatted.Add(kvp.Key,kvp.Value);
}
//writing CSV3
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<string,double> kvp in csv3Formatted)
{
sb.AppendLine(kvp.Key + "," + kvp.Value);
}
System.IO.File.WriteAllText("C:\\csv3.csv", sb.ToString());

This is my favorite to use with CSV
https://github.com/kentcb/KBCsv
and if you want to work with csv entries as model for each row:
http://www.filehelpers.net/quickstart/
I hope you find this helpful.
Good luck :) Enjoy coding

Related

C# Add lines with same Date.Time in .csv file

I'm currently making a program that tracks certain things (basic INT Values and the Date when they were saved).
My goal is to add up the INT values with the same Date.
20.11.2018 00:00:00; 1;1;1;1;1
20.11.2018 00:00:00; 1;1;1;1;1
22.11.2018 00:00:00; 1;1;1;1;1
Should basically look like this
20.11.2018 00:00:00; 2;2;2;2;2
22.11.2018 00:00:00; 1;1;1;1;1
The Saving Data and even the adding the 2 "Lines" together is working perfectly fine.
The problem is that When I add the Lines together, the 2 Lines with the 1 obviously don't get deleted.
This is the Method that Compares the Date and adds the lines together:
public static Dictionary<DateTime, int[]> CompareDateMethod(Dictionary<DateTime, int[]> oDateTimeAndIntDictionary,string[][] ReadData)
{
Dictionary<DateTime, int[]> oPrintRealData = new Dictionary<DateTime, int[]>();
Dictionary<DateTime, int[]> oAddRealData = new Dictionary<DateTime, int[]>();
for (int i = 0 ; i < ReadData.Length; i++)
{
DateTime dtDateValue;
if (DateTime.TryParse(ReadData[i][0], out dtDateValue))
{
int[] iValuesToAdd = ConvertArrayToInt(ReadData[i]);
if (dtDateValue.Date == DateTime.Now.Date)
{
for (int j = 0; j < iValuesToAdd.Length; j++)
{
oDateTimeAndIntDictionary[dtDateValue.Date][j] += iValuesToAdd[j];
}
}
else if (dtDateValue.Date != DateTime.Now.Date)
{
goto Endloop;
}
}
}
Endloop:
return oDateTimeAndIntDictionary;
This is the method that Writes the Data into the .CSV file
Dictionary<DateTime, int[]> oDateTimeAndIntDictionary = new Dictionary<DateTime, int[]>();
string[][] OldData= AddVariables.ReadOldData();
int[] iNewDataArray = new int[] { iVariable1, iVariable2, iVariable3, iVariable4, iVariable5};
oDateTimeAndIntDictionary.Add(DateTime.Now.Date, iNewDataArray);
using (System.IO.FileStream fileStream = new System.IO.FileStream(#"C: \Users\---\Csvsave\SaveDatei.csv", System.IO.FileMode.Append, System.IO.FileAccess.Write))
using (System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(fileStream))
{
foreach (KeyValuePair<DateTime, int[]> kvp in AddVariables.CompareDateMethod(oDateTimeAndIntDictionary, OldData))
{
streamWriter.WriteLine("{0}; {1}", kvp.Key, string.Join(";", kvp.Value));
}
}
I tried so hard to come up with something but nothing worked (I tried deleting lines from the .csv which seems really hard, I tried reading the file in backwards which didnt work etc.)
If someone can give me some pointers I would really appreciate it.
I think the problem with the original code is that it's a bit confused about what happens when. I've restructured it so that things happen in a logical order (and updated it a bit, simplifying variable names, etc). There's one function for combining rows with the same date, which is separate from the CSV writing code (which hasn't changed)
static void Main(string[] args)
{
var oldData = ReadOldData();
// Do the work
var results = SumValuesForSameDate(oldData);
// Write the file
using (System.IO.FileStream fileStream = new System.IO.FileStream(#"C: \Users\---\Csvsave\SaveDatei.csv", System.IO.FileMode.Append, System.IO.FileAccess.Write))
using (System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(fileStream))
{
foreach (KeyValuePair<DateTime, int[]> kvp in results)
{
streamWriter.WriteLine("{0}; {1}", kvp.Key, string.Join(";", kvp.Value));
}
}
}
public static Dictionary<DateTime, int[]> SumValuesForSameDate(string[][] readData)
{
var oDateTimeAndIntDictionary = new Dictionary<DateTime, int[]>();
var currentDate = DateTime.MinValue;
foreach (var row in readData)
{
DateTime dateValue;
if(!DateTime.TryParse(row[0], out dateValue)) continue;
dateValue = dateValue.Date;
var intValues = ConvertArrayToInt(row);
if (dateValue == currentDate)
{
for (var j = 0; j < intValues.Length; j++)
{
oDateTimeAndIntDictionary[dateValue][j] += intValues[j];
}
}
else
{
oDateTimeAndIntDictionary.Add(dateValue, intValues);
currentDate = dateValue;
}
}
return oDateTimeAndIntDictionary;
}
static int[] ConvertArrayToInt(string[] strings)
{
var output = new int[strings.Length - 1];
for (var i = 1; i < strings.Length; i++)
{
output[i - 1] = int.Parse(strings[i]);
}
return output;
}
static string[][] ReadOldData()
{
// Fake data
var data = new string[][]
{
new string[] { "20.11.2018 00:00:00", "1", "1", "1", "1", "1" },
new string[] { "20.11.2018 00:00:00", "1", "1", "1", "1", "1" },
new string[] { "22.11.2018 00:00:00", "1", "1", "1", "1", "1" },
};
return data;
}
}
For overwriting the previous CSV just use System.IO.FileMode.Create instead of Append. This will overwrite any previous data.
You need to overwrite the csv anyways to get rid of the written row.
So instead of returning oDateTimeAndIntDictionary from CompareDateMethod method, return ReadData and overwrite the parsed values of ReadData.
Something like this,
public static Dictionary<DateTime, int[]> CompareDateMethod(Dictionary<DateTime, int[]> oDateTimeAndIntDictionary,string[][] ReadData)
{
Dictionary<DateTime, int[]> oPrintRealData = new Dictionary<DateTime, int[]>();
Dictionary<DateTime, int[]> oAddRealData = new Dictionary<DateTime, int[]>();
for (int i = 0 ; i < ReadData.Length; i++)
{
DateTime dtDateValue;
if (DateTime.TryParse(oDateTimeAndIntDictionary[0][0], out dtDateValue))
{
int[] iValuesToAdd = ConvertArrayToInt(ReadData[i]);
if (dtDateValue.Date == DateTime.Now.Date)
{
for (int j = 0; j < iValuesToAdd.Length; j++)
{
//Add the ReadData values here and store at ReadData[i][j]
}
}
else if (dtDateValue.Date != DateTime.Now.Date)
{
goto Endloop;
}
}
}
Endloop:
return ReadData;
}
Hope this helps...
I readed your comment about not using linq and 3rd part lib too late.
But let me show you what you are missing.
Here it's a little Linq + CSVHelper
First Lest define your data, and define how to map them in the CSV
public sealed class data
{
public DateTime TimeStamp { get; set; }
public List<int> Numbers { get; set; }
}
public sealed class dataMapping : ClassMap<data>
{
public dataMapping()
{
Map(m => m.TimeStamp).Index(0);
Map(m => m.Numbers)
.ConvertUsing(
row =>
new List<int> {
row.GetField<int>(1),
row.GetField<int>(2),
row.GetField<int>(3)
}
);
}
}
And now this is a short demo:
class CsvExemple
{
string inputPath = "datas.csv";
string outputPath = "datasOut.csv";
List<data> datas;
public void Demo()
{
//no duplicate row in orginal input
InitialiseFile();
LoadExistingData();
//add some new row and some dupe
NewDatasArrived();
//save to an other Path, to Compare.
SaveDatas();
}
private void SaveDatas()
{
using (TextWriter writer = new StreamWriter(outputPath))
using (var csvWriter = new CsvWriter(writer))
{
csvWriter.Configuration.RegisterClassMap<dataMapping>();
csvWriter.Configuration.Delimiter = ";";
csvWriter.Configuration.HasHeaderRecord = false;
csvWriter.WriteRecords(datas);
}
}
static List<int> SuperZip(params List<int>[] sourceLists)
{
for (var i = 1; i < sourceLists.Length; i++)
{
sourceLists[0] = sourceLists[0].Zip(sourceLists[i], (a, b) => a + b).ToList();
}
return sourceLists[0];
}
private void NewDatasArrived()
{
var now = DateTime.Today;
// New rows
var outOfInitialDataRange = Enumerable.Range(11, 15)
.Select(x => new data { TimeStamp = now.AddDays(-x), Numbers = new List<int> { x, x, x } });
// Duplicate rows
var inOfInitialDataRange = Enumerable.Range(3, 7)
.Select(x => new data { TimeStamp = now.AddDays(-x), Numbers = new List<int> { x, x, x } });
//add all of them them together
datas.AddRange(outOfInitialDataRange);
datas.AddRange(inOfInitialDataRange);
// all this could have been one Line
var grouped = datas.GroupBy(x => x.TimeStamp);
var temp = grouped.Select(g => new { TimeStamp = g.Key, ManyNumbers = g.Select(x => x.Numbers).ToArray() });
// We can combine element of 2 list using Zip. ListA.Zip(ListB, (a, b) => a + b)
datas = temp.Select(x => new data { TimeStamp = x.TimeStamp, Numbers = SuperZip(x.ManyNumbers) }).ToList();
}
private void LoadExistingData()
{
if (File.Exists(inputPath))
{
using (TextReader reader = new StreamReader(inputPath))
using (var csvReader = new CsvReader(reader))
{
csvReader.Configuration.RegisterClassMap<dataMapping>();
csvReader.Configuration.HasHeaderRecord = false;
csvReader.Configuration.Delimiter = ";";
datas = csvReader.GetRecords<data>().ToList();
}
}
else
{
datas = new List<data>();
}
}
private void InitialiseFile()
{
if (File.Exists(inputPath))
{
return;
}
var now = DateTime.Today;
var ExistingData = Enumerable.Range(0, 10)
.Select(x => new data { TimeStamp = now.AddDays(-x), Numbers = new List<int> { x, x, x } });
using (TextWriter writer = new StreamWriter(inputPath))
using (var csvWriter = new CsvWriter(writer))
{
csvWriter.Configuration.RegisterClassMap<dataMapping>();
csvWriter.Configuration.Delimiter = ";";
csvWriter.Configuration.HasHeaderRecord = false;
csvWriter.WriteRecords(ExistingData);
}
}
}

How to map 2d array in csv file to poco collection or dictionary of dictionary using FileHelpers?

I have the following data structure in my csv file:
I want to either parse it into the following data structure :
[DelimitedRecord(","), IgnoreFirst(1)]
public class FxConversionRate
{
[FieldConverter(ConverterKind.Date, "d/M/yyyy")]
public DateTime Date;
public string Currency;
public double Rate;
}
Or else want to parse it into a Dictionary<string, Dictionary<DateTime, double>>
How can I accomplish either way? I do not want to modify the source csv table layout and believe I need to customize the import and mapping.
Thanks
EDIT
The following code snippet both, reads data from csv into a 2D array and also into a data structure (Dictionary of Dictionary in this case but could as well be the above proposed data structure FxConversionRate):
public class FxConversionTable
{
public Dictionary<Currency, Dictionary<DateTime, double>> FxConversionRates{ get; set; } //key1 = Currency, key2 = DateTime, value = double
public string[,] String2DArray{ get; set; }
public FxConversionTable()
{
FxConversionRates = new Dictionary<Currency, Dictionary<DateTime, double>>();
}
public void ReadFxConversionRatesFromCsvFile(string pathFileName)
{
var strings = new List<List<string>>();
using (var reader = new StreamReader(File.OpenRead(pathFileName)))
{
//read symbol rows and parse
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrEmpty(line))
continue;
var values = line.Split(',');
//populate string array
strings.Add(values.ToList());
//header
if (strings.Count == 1)
{
foreach (var currencyString in values.Skip(1))
{
Currency ccy = (Currency) Enum.Parse(typeof (Currency), currencyString);
FxConversionRates.Add(ccy, new Dictionary<DateTime, double>());
}
continue;
}
//populate data collection
var date = DateTime.ParseExact(values[0], "d/M/yyyy", CultureInfo.InvariantCulture);
for (int i = 1; i < values.Count(); i++)
{
var ccy = (Currency) Enum.Parse(typeof (Currency), strings[0][i]);
FxConversionRates[ccy].Add(date, Convert.ToDouble(values[i]));
}
}
}
String2DArray = FileIO.ListOfListTo2DArray<string>(strings);
}
}
I am, however, still looking for a more generic solution via FileHelpers...
You can use some fancy LINQ.
Helpful note: with FileHelpers it's easier to separate the class which defines the file format (FxConversionRateSpec) from the destination class (FxConversionRate) and map between the two.
// destination object
public class FxConversionRate
{
public DateTime Date { get; set; }
public string Currency { get; set; }
public double Rate { get; set; }
}
// file format specification (FileHelpers class)
[DelimitedRecord(","), IgnoreFirst(1)]
public class FxConversionRateSpec
{
[FieldConverter(ConverterKind.Date, "d/M/yyyy")]
public DateTime Date;
public double[] Rates;
}
class Program
{
static void Main(string[] args)
{
// trimmed down contents...
var contents =
#"DATE,AUD,CAD,CHF" + Environment.NewLine +
#"1/1/2000,88,71,3" + Environment.NewLine +
#"2/1/2000,82,83,86";
// get the records
var engine = new FileHelperEngine<FxConversionRateSpec>();
var records = engine.ReadString(contents);
// get the header
var currencies = contents
.Substring(0, contents.IndexOf(Environment.NewLine)) // take the first line
.Split(',') // split into currencies
.Skip(1); // skip the 'Date' column
// as IEnumerable<FxConversionRate>
var rates = records.SelectMany( // for each record of Date, Double[]
record => currencies.Zip(record.Rates, (c, r) => new { Currency = c, Rate = r}) // combine the rates array with the currency labels
.Select( // for each of the anonymous typed records Currency, Double
currencyRate =>
new FxConversionRate
{
Date = record.Date,
Currency = currencyRate.Currency,
Rate = currencyRate.Rate
}));
Assert.AreEqual(6, rates.Count(), "Exactly 6 records were expected");
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "AUD" && x.Rate == 88d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "CAD" && x.Rate == 71d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "CHF" && x.Rate == 3d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "AUD" && x.Rate == 82d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "CAD" && x.Rate == 83d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "CHF" && x.Rate == 86d) != null);
Console.WriteLine("All tests passed OK.");
Console.ReadKey();
}
}
Note it would be quite feasible to create a Dictionary instead, especially with the ToDictionary() LINQ extension.
This should do the trick for you. It's not the most elegant solution but it works. You will need to add plenty of error checking for things like missing columns or data or source file corruption etc.
private static void Main(string[] args)
{
var fileData = File.ReadAllBytes("Data.csv");
var tableData = CreateDataTableFromFile(fileData);
DataColumn dateColumn = tableData.Columns["Date"];
Dictionary<string, List<FxConversionRate>> rates = new Dictionary<string, List<FxConversionRate>>();
foreach (DataColumn column in tableData.Columns)
{
if (column != dateColumn)
{
foreach (DataRow row in tableData.Rows)
{
FxConversionRate rate = new FxConversionRate();
rate.Currency = column.ColumnName;
rate.Date = DateTime.Parse(row[dateColumn].ToString());
rate.Rate = double.Parse(row[column].ToString());
if (!rates.ContainsKey(column.ColumnName))
rates.Add(column.ColumnName, new List<FxConversionRate>());
rates[column.ColumnName].Add(rate);
}
}
}
foreach (var key in rates.Keys)
{
Console.WriteLine($"Found currency: {key}");
foreach (var rate in rates[key])
{
Console.WriteLine($" {rate.Date.ToShortDateString()} : {rate.Rate:###,###,##0.00}");
}
}
Console.WriteLine("Press any key");
Console.ReadKey();
}
private static DataTable CreateDataTableFromFile(byte[] importFile)
{
var cb = new DelimitedClassBuilder("temp", ",") { IgnoreFirstLines = 0, IgnoreEmptyLines = true, Delimiter = "," };
var ms = new MemoryStream(importFile);
var sr = new StreamReader(ms);
var headerArray = sr.ReadLine().Split(',');
foreach (var header in headerArray)
{
cb.AddField(header, typeof(string));
cb.LastField.FieldQuoted = true;
cb.LastField.QuoteChar = '"';
}
var engine = new FileHelperEngine(cb.CreateRecordClass());
return engine.ReadStreamAsDT(sr);
}
Note that the CreateDataTableFromFile routine was taken from https://stackoverflow.com/a/6045923/697159

Finding filenames containing version numbers in C#

I have file names with version numbers embedded, similar to NuGet's naming scheme. Examples:
A.B.C.1.2.3.4.zip
A.B.C.1.2.3.5.zip
A.B.C.3.4.5.dll
A.B.C.1.2.3.6.zip
A.B.C.1.2.3.dll
X.Y.Z.7.8.9.0.zip
X.Y.Z.7.8.9.1.zip
Given a pattern "A.B.C.1.2.3", how do I find all those files and directories that match, regardless of version number? I support both major.minor.build.revision and major.minor.build schemes.
That is, given "A.B.C.1.2.3", return the following list:
A.B.C.1.2.3.4.zip
A.B.C.1.2.3.5.zip
A.B.C.1.2.3.6.zip
A.B.C.1.2.3.dll
A.B.C.3.4.5.dll
Bonus points for determining which file name has the highest version.
If you know the filenames end with the version, you could Split the filename string on .. Then iterate backwards from the end (skipping the extension) and stop on the first non-numeric string. (TryParse is probably good for this.) Then you can string.Join the remaining parts and you have the package name.
Do this for the search term to find the package name, then each file in the directory, and you can compare just the package names.
Credits to jdwweng for his answer as well as 31eee384 for his thoughts. This answer basically combines both ideas.
First, you can create a custom class like so:
class CustomFile
{
public string FileName { get; private set; }
public Version FileVersion { get; private set; }
public CustomFile(string file)
{
var split = file.Split(".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
int versionIndex;
int temp;
for (int i = split.Length - 2; i >= 0; i--)
{
if (!Int32.TryParse(split[i], out temp))
{
versionIndex = i+1;
break;
}
}
FileName = string.Join(".", split, 0, versionIndex);
FileVersion = Version.Parse(string.Join(".", split, versionIndex, split.Length - versionIndex - 1));
}
}
Using it to parse the filename, you can then filter based on it.
string[] input = new string[] {
"A.B.C.D.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.3.4.5.dll",
"A.B.C.1.2.3.6.zip",
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip"
};
var parsed = input.Select(x => new CustomFile(x));
var results = parsed
.Where(cf => cf.FileName == "A.B.C")
.OrderByDescending(cf=>cf.FileVersion)
.ToList();
In this example, the first element would have the highest version.
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] input = new string[] {
"A.B.C.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.3.4.5.dll",
"A.B.C.1.2.3.6.zip",
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip"
};
var parsed = input.Select(x => x.Split(new char[] { '.' }))
.Select(y => new
{
name = string.Join(".", new string[] { y[0], y[1], y[2] }),
ext = y[y.Count() - 1],
major = int.Parse(y[3]),
minor = int.Parse(y[4]),
build = int.Parse(y[5]),
revision = y.Count() == 7 ? (int?)null : int.Parse(y[6])
}).ToList();
var results = parsed.Where(x => (x.major >= 1) && (x.major <= 3)).ToList();
var dict = parsed.GroupBy(x => x.name, y => y)
.ToDictionary(x => x.Key, y => y.ToList());
var abc = dict["A.B.C"];
}
}
}
​
​
you can use new Version() to compare versions like this:
List<string> fileNames = new List<string>();
fileNnames.AddRange(new[] {
"A.B.C.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.3.4.5.dll",
"A.B.C.1.2.3.6.zip",
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip" });
string filter = "a.b.c";
var files = fileNames
//Filter the filenames that start with your filter
.Where(f => f
.StartsWith(filter, StringComparison.InvariantCultureIgnoreCase)
)
//retrieve the version number and create a new version element to order by
.OrderBy(f =>
new Version(
f.Substring(filter.Length + 1, f.Length - filter.Length - 5)
)
);
Try to use regular expression like in example below
var firstPart = Console.ReadLine();
var names = new List<string>
{
"A.B.C.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.1.2.3.6.zip",
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip"
};
var versionRegexp = new Regex("^" + firstPart + "\\.([\\d]+\\.){1}([\\d]+\\.){1}([\\d]+\\.){1}([\\d]+\\.)?[\\w\\d]+$");
foreach (var name in names)
{
if (versionRegexp.IsMatch(name))
{
Console.WriteLine(name);
foreach (Group group in versionRegexp.Match(name).Groups)
{
Console.WriteLine("Index {0}: {1}", group.Index, group.Value);
}
}
}
Console.ReadKey();
This works using only LINQ, assuming the file name itself doesn't end with a digit:
List<string> names = new List<string> { "A.B.C.1.2.3.4.zip",
"A.B.C.1.2.3.5.zip",
"A.B.C.3.4.5.dll",
"A.B.C.1.2.3.6.zip" ,
"A.B.C.1.2.3.dll",
"X.Y.Z.7.8.9.0.zip",
"X.Y.Z.7.8.9.1.zip" };
var groupedFileNames = names.GroupBy(file => new string(Path.GetFileNameWithoutExtension(file)
.Reverse()
.SkipWhile(c => Char.IsDigit(c) || c == '.')
.Reverse().ToArray()));
foreach (var g in groupedFileNames)
{
Console.WriteLine(g.Key);
foreach (var file in g)
Console.WriteLine(" " + file);
}
First of all I think you can use Version class for comparison.
I believe function below can get you versions starting with certain name.
It matches the starting name then performs a non greedy search until a dot and digit followed by 2 or 3 dot digit pair and any character after.
public static List<Version> GetLibraryVersions(List<string> files, string Name)
{
string regexPattern = String.Format(#"\A{0}(?:.*?)(?:\.)(\d+(?:\.\d+){{2,3}})(?:\.)(?:.*)\Z", Regex.Escape(Name));
Regex regex = new Regex(regexPattern);
return files.Where(f => regex.Match(f).Success).
Select(f => new Version(regex.Match(f).Groups[1].Value)).ToList();
}

Get the values from CSV based on headers

I have a csv file with this data
Price,volume,"Local, Zones 1 & 2",Zone 3,Zone 4,Zone 5,Zone 6,Zone 7,Zone 8,Zone 9
1,0.1,4.58,4.65,4.74,4.99,5.23,5.47,5.82,6.98
2,0.2,4.99,5.12,5.28,5.5,5.7,5.91,6.3,7.56
3,0.3,5.22,5.61,6.12,7.64,8.39,9.09,9.96,11.94
4,0.4,5.4,6.31,7.24,9.13,10.73,11.77,13.26,15.91
5,0.5,6.18,7.21,8.35,11.5,13.41,14.82,16.97,20.36
Now i want to retrieve the values based on volume and zone.if volume is 0.1 and zone is 1,then i should get the value of 4.58.similarly if the volume is 0.5 and xone is 2,then the value should be 6.18.
How can i do this in c#?
Dummy way, given that format of file is not going to change and csv file is valid.
int zone = 1;
double value = 0.1;
int zoneColumnIndex = Math.Max(zone, 2);
string valueString = value.ToString(CultureInfo.InvariantCulture);
string result = File.ReadLines("sample.txt")
.Skip(1)
.Select(s => s.Split(','))
.Where(t => t[1] == valueString)
.Select(t => t[zoneColumnIndex])
.FirstOrDefault();
I would look at "LinqToExcel" if I were you. Then you could do this:
var csv = new LinqToExcel.ExcelQueryFactory(csvFile);
var query =
from row in csv.Worksheet()
let Volume = row["volume"].Cast<double>()
where Volume == 0.1
select new
{
Price = row["Price"].Cast<int>(),
Volume,
Local = row["Local, Zones 1 & 2"].Cast<decimal>(),
Zone3 = row["Zone 3"].Cast<decimal>(),
//etc
};
To to get the values into a an array or a list
var reader = new StreamReader(File.OpenRead(#"C:\test.csv"));
List<string> listA = new List<string>();
int i = 0;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');
listA.Add(values[i]);
i += 1;
}
now use your list to itterate through
foreach(int j in listA)
{
try
{/* do stuff */}
catch
{/* if your csv cotains strings it wall fall in here */}
}

How do I get the matching values in a csv file?

So I have a .csv file with 2 columns looking a bit like this:
01,23
02,45
03,178
etc.
Now I want to read the csv file, give it a value for the first column, and get the corresponding value from the second column back. (so say I give it a value of 03, it should give me 178 back)
Here's the code I've got so far but what should go in the if statement?
public int CalculateNextLevel(int current_xp, int current_lvl)
{
var reader = new StreamReader(File.OpenRead(#"C:\Users\Lennart\Desktop\Legends of Raymere\Files\Lvl.csv"));
List<int> levels = new List<int>();
List<int> exp = new List<int>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');
levels.Add(Convert.ToInt32(values[0]));
exp.Add(Convert.ToInt32(values[1]));
foreach (int level in levels)
{
if (current_lvl == level)
{
}
}
}
return XP_to_nxt_lvl;
}
You can use a Dictionary instead
var expValues = new Dictionary<int, int>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');
expValues.Add(Convert.ToInt32(values[0]), Convert.ToInt32(values[1]));
}
// Retrieve value based on level
if (expValues.ContainsKey(3))
{
int level03Exp = expValues[3];
}
try this
static void Main(string[] args)
{
string key = "03";
GetValue(key);
}
private static int GetValue(string key)
{
var lines = File.ReadAllLines("test.txt");
var dictonary = lines.ToDictionary(dict =>
{
return dict.Split(',')[0];
});
int valInt = int.Parse(dictonary[key].Split(',')[1]);
return valInt;
}
You should use Linq like this :
foreach (int level in levels)
{
if (current_lvl == level)
{
XP_to_nxt_lvl = exp[levels.IndexOf(level)];
}
}
You could use LINQ and a Lookup<TKey, TValue> which allows duplicate keys but is similar to a Dictionary. If the key(level) is not present you get an empty collection of xp's:
private ILookup<int, int> LevelLookup = null;
public void LoadAllLevels()
{
LevelLookup = File.ReadLines(#"C:\Temp\Lvl.csv")
.Select(l => l.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
.Select(cols =>
{
int level = 0, xp = 0;
bool validLine = cols.Length == 2;
if(validLine)
validLine = int.TryParse(cols[0].Trim(), out level);
if(validLine)
validLine = int.TryParse(cols[1].Trim(), out xp);
return new{ level, xp, validLine };
})
.Where(x => x.validLine)
.ToLookup(x => x.level, x => x.xp);
}
public int? CalculateNextLevel(int current_xp, int current_lvl)
{
int? nextLevel = null;
var xps = LevelLookup[current_lvl];
if (xps.Any())
nextLevel = xps.First();
return nextLevel;
}
You only need to initialize it once or when the file changed via LoadAllLevels().
For example:
LoadAllLevels();
int level3 = 3;
int level4 = 4;
int? xp3 = CalculateNextLevel(100,level3);
int? xp4 = CalculateNextLevel(150,level4);
I have used nullables to differentiate between XP=0 and a level has yet no defined xp.
bool hasLev4XP = xp4.HasValue;
if(hasLev4XP)
{
int lev4XpNeeded = xp4.Value;
}
If the level is guaranteed to be unique you could also use ToDictionary to create a dictionary and use similar code as above.

Categories

Resources