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

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);
}
}
}

Related

How to achieve desired results in multidimensional array? C#

I am trying to achieve the output by using multidimensional array which I can get by using KeyValuePair.
Input:
var foodPair = new Dictionary<string, string>
{
{"Pizza", "Italian"},
{"Curry", "Indian"},
{"Masala", "Indian"}
};
var teamPreference = new Dictionary<string, string>
{
{"Jose", "Italian" },
{"John", "Indian" },
{"Sarah", "Thai" },
{"Mary", "*" }
};
* means give everything
If selected food type is not available than give nothing. i.e Thai
Output:
Jose, Pizza
John, Curry
John, Masala
Mary, Pizza
Mary, Curry
Mary, Masala
Working Result by using KeyValuePair<string, string>:
https://dotnetfiddle.net/hNdlfy
I want to achieve same result by using string[,] but I don't know how to insert in dimensional array. Basically I am trying to learn how multidimensional arrays works by doing this kind of example.
You cannot "insert" in any array (single-dimensional or multidimensional). But you can create array with enough space.
var foodPair = new Dictionary<string, string> { { "Pizza", "Italian" }, { "Curry", "Indian" }, { "Masala", "Indian" } };
var teamPreference = new Dictionary<string, string> { { "Jose", "Italian" }, { "John", "Indian" }, { "Sarah", "Thai" }, { "Mary", "*" } };
var results = new List<KeyValuePair<string, string>>();
var results2 = new string[10, 2];
int rowIndex = 0;
foreach (var teamMember in teamPreference)
{
switch (teamMember.Key)
{
case "Jose":
var key = foodPair.FirstOrDefault(x => x.Value == "Italian").Key;
results.Add(new KeyValuePair<string, string>(teamMember.Key, key));
results2[rowIndex, 0] = teamMember.Key;
results2[rowIndex, 1] = key;
rowIndex++;
break;
case "John":
var getAll = foodPair.Where(x => x.Value == "Indian").ToList();
if (getAll.Any())
{
results.AddRange(getAll.Select(a => new KeyValuePair<string, string>(teamMember.Key, a.Key)));
}
foreach (var item in getAll)
{
results2[rowIndex, 0] = teamMember.Key;
results2[rowIndex, 1] = item.Key;
rowIndex++;
}
break;
case "Sarah":
var c = foodPair.FirstOrDefault(x => x.Value == "Thai").Key;
if (!string.IsNullOrEmpty(c))
{
results.Add(new KeyValuePair<string, string>(teamMember.Key, c));
}
if (!string.IsNullOrEmpty(c))
{
results2[rowIndex, 0] = teamMember.Key;
results2[rowIndex, 1] = c;
rowIndex++;
}
break;
case "Mary":
if (teamMember.Value == "*")
{
var everything = foodPair.Keys.ToList();
if (everything.Any())
{
results.AddRange(everything.Select(food => new KeyValuePair<string, string>(teamMember.Key, food)));
}
foreach (var item in everything)
{
results2[rowIndex, 0] = teamMember.Key;
results2[rowIndex, 1] = item;
rowIndex++;
}
}
break;
}
}
if (results.Any())
{
foreach (var result in results)
{
Console.WriteLine("{0}, {1}", result.Key, result.Value);
}
}
Console.WriteLine("Using multidimensional array");
for (int row = 0; row < rowIndex; row++)
{
Console.WriteLine("{0}, {1}", results2[row, 0], results2[row, 1]);
}
But multidimensional array is not suitable data structure for this task. (You can see code with array looks terrible). Data structure that you need is called tuple and the definition for results can look like var results3 = new List<Tuple<string, string>>();
Additional note - you not need using .Any() with foreach statement. foreach is not executed for empty collections.
Good luck with learning C#!
I used Extension method to achieve the desired result.
public static string[,] FoodPreferenceWithDimensionalArray()
{
var foodPair = new Dictionary<string, string>
{
{"Pizza", "Italian"},
{"Curry", "Indian"},
{"Masala", "Indian"}
};
var teamPreference = new Dictionary<string, string>
{
{"Jose", "Italian" },
{"John", "Indian" },
{"Sarah", "Thai" },
{"Mary", "*" }
};
var results = new List<KeyValuePair<string, string>>();
foreach (var teamMember in teamPreference)
{
switch (teamMember.Key)
{
case "Jose":
var italianDish = foodPair.FirstOrDefault(x => x.Value == "Italian").Key;
results.Add(new KeyValuePair<string, string>(teamMember.Key, italianDish));
break;
case "John":
var indianDish = foodPair.Where(x => x.Value == "Indian");
if (indianDish.Any())
{
results.AddRange(indianDish.Select(dish => new KeyValuePair<string, string>(teamMember.Key, dish.Key)));
}
break;
case "Sarah":
var thaiDish = foodPair.FirstOrDefault(x => x.Value == "Thai").Key;
if (!string.IsNullOrEmpty(thaiDish))
{
results.Add(new KeyValuePair<string, string>(teamMember.Key, thaiDish));
}
break;
case "Mary":
if (teamMember.Value == "*")
{
var everything = foodPair.Keys.ToList();
if (everything.Any())
{
results.AddRange(everything.Select(food => new KeyValuePair<string, string>(teamMember.Key, food)));
}
}
break;
}
}
var resultIn2DArray = results.To2DArray();
return resultIn2DArray;
}
// Created extension to convert List to String[,]
public static class Extension
{
public static string[,] To2DArray<T>(this List<T> list)
{
if (list.Count == 0)
{
throw new ArgumentException("The list must have non-zero dimensions.");
}
var result = new string[list.Count, list.Count];
for (var i = 0; i < list.Count; i++)
{
// This is set to 0 since I know the output but will work on this to make it dynamic.
result[0, i] = list[i].ToString();
}
return result;
}
}
Thank you all for your input.

Loading CSV in 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

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

Generate all Combinations from Multiple (n) Lists

EDIT: I am completely redoing my questions as I have figured out the simplest way of asking it. Thanks to the commenters so far that got me thinking about the root problem.
public List<string> GetAllPossibleCombos(List<List<string>> strings)
{
List<string> PossibleCombos = new List<string>();
//????
{
string combo = string.Empty;
// ????
{
combo += ????
}
PossibleCombos.Add(combo);
}
return PossibleCombos;
}
I need to figure out how to recursively go through each List<string> and combine 1 string from each list into a combo string. Don't worry too much about formatting the string as the "live" code uses a custom object instead. Also, feel free to assume that every list will contain at least 1 string and that there are no null values.
Here is a simple non-recursive solution that just concatenates the elements of each combination:
public static List<string> GetAllPossibleCombos(List<List<string>> strings)
{
IEnumerable<string> combos = new [] { "" };
foreach (var inner in strings)
combos = from c in combos
from i in inner
select c + i;
return combos.ToList();
}
static void Main(string[] args)
{
var x = GetAllPossibleCombos(
new List<List<string>>{
new List<string> { "a", "b", "c" },
new List<string> { "x", "y" },
new List<string> { "1", "2", "3", "4" }});
}
You could generalize this to return an IEnumerable<IEnumerable<string>>, which allows the caller to apply any operation they like for transforming each combination into a string (such as the string.Join below). The combinations are enumerated using deferred execution.
public static IEnumerable<IEnumerable<string>> GetAllPossibleCombos(
IEnumerable<IEnumerable<string>> strings)
{
IEnumerable<IEnumerable<string>> combos = new string[][] { new string[0] };
foreach (var inner in strings)
combos = from c in combos
from i in inner
select c.Append(i);
return combos;
}
public static IEnumerable<TSource> Append<TSource>(
this IEnumerable<TSource> source, TSource item)
{
foreach (TSource element in source)
yield return element;
yield return item;
}
static void Main(string[] args)
{
var combos = GetAllPossibleCombos(
new List<List<string>>{
new List<string> { "a", "b", "c" },
new List<string> { "x", "y" },
new List<string> { "1", "2", "3", "4" }});
var result = combos.Select(c => string.Join(",", c)).ToList();
}
Hope this helps.
class NListBuilder
{
Dictionary<int, List<string>> tags = new Dictionary<int, List<string>>();
public NListBuilder()
{
tags.Add(1, new List<string>() { "A", "B", "C" });
tags.Add(2, new List<string>() { "+", "-", "*" });
tags.Add(3, new List<string>() { "1", "2", "3" });
}
public List<string> AllCombos
{
get
{
return GetCombos(tags);
}
}
List<string> GetCombos(IEnumerable<KeyValuePair<int, List<string>>> remainingTags)
{
if (remainingTags.Count() == 1)
{
return remainingTags.First().Value;
}
else
{
var current = remainingTags.First();
List<string> outputs = new List<string>();
List<string> combos = GetCombos(remainingTags.Where(tag => tag.Key != current.Key));
foreach (var tagPart in current.Value)
{
foreach (var combo in combos)
{
outputs.Add(tagPart + combo);
}
}
return outputs;
}
}
}
In case it helps anyone, here is the method syntax version for Douglas's GetAllPossibleCombos method.
public static List<string> GetAllPossibleCombos(List<List<string>> strings)
{
IEnumerable<string> combos = new[] { "" };
foreach (var inner in strings)
{
combos = combos.SelectMany(r => inner.Select(x => r + x));
}
return combos.ToList();
}
Here is a generic version that works with all object types:
public static List<List<T>> GetAllPossibleCombos<T>(List<List<T>> objects)
{
IEnumerable<List<T>> combos = new List<List<T>>() { new List<T>() };
foreach (var inner in objects)
{
combos = combos.SelectMany(r => inner
.Select(x => {
var n = r.DeepClone();
if (x != null)
{
n.Add(x);
}
return n;
}).ToList());
}
// Remove combinations were all items are empty
return combos.Where(c => c.Count > 0).ToList();
}
If you provide null values it will also give you empty combination. For example:
var list1 = new List<string>() { "1A", "1B", null };
var list2 = new List<string>() { "2A", "2B", null };
var output = GetAllPossibleCombos(allLists);
Will contain:
[["1A"], ["1B"], ["2A"], ["2B"], ["1A", "2A"], ["1A", "2B"], ["1B", "2A"], ["1B," "2B"]]
Rather than just:
var list1 = new List<string>() { "1A", "1B" };
var list2 = new List<string>() { "2A", "2B" };
var output = GetAllPossibleCombos(allLists);
[["1A", "2A"], ["1A", "2B"], ["1B", "2A"], ["1B," "2B"]]
Note: DeepClone is an extension method used for copying the list. This can be done in many ways
public static T DeepClone<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
Here is an answer that would work for any generic type, comes with a function to convert base-10 to base-n as well.
public static class IntExt
{
const string Symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static string ToString(this int value, int toBase)
{
switch (toBase)
{
case 2:
case 8:
case 10:
case 16:
return Convert.ToString(value, toBase);
case 64:
return Convert.ToBase64String(BitConverter.GetBytes(value));
default:
if (toBase < 2 || toBase > Symbols.Length)
throw new ArgumentOutOfRangeException(nameof(toBase));
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
int resultLength = 1 + (int)Math.Max(Math.Log(value, toBase), 0);
char[] results = new char[resultLength];
int num = value;
int index = resultLength - 1;
do
{
results[index--] = Symbols[num % toBase];
num /= toBase;
}
while (num != 0);
return new string(results);
}
}
}
public class UnitTest1
{
public static T[][] GetJoinCombinations<T>(T[][] arrs)
{
int maxLength = 0;
int total = 1;
for (int i = 0; i < arrs.Length; i++)
{
T[] arr = arrs[i];
maxLength = Math.Max(maxLength, arr.Length);
total *= arr.Length;
}
T[][] results = new T[total][];
int n = 0;
int count = (int)Math.Pow(maxLength, arrs.Length);
for (int i = 0; i < count; i++)
{
T[] combo = new T[arrs.Length];
string indices = i.ToString(maxLength).PadLeft(arrs.Length, '0');
bool skip = false;
for (int j = 0; j < indices.Length; j++)
{
T[] arr = arrs[j];
int index = int.Parse(indices[j].ToString());
if (index >= arr.Length)
{
skip = true;
break;
}
combo[j] = arr[index];
}
if (!skip)
results[n++] = combo;
}
return results;
}
[Fact]
public void Test1()
{
string[][] results = GetJoinCombinations(new string[][]
{
new string[] { "1", "2", "3" },
new string[] { "A", "B", "C" },
new string[] { "+", "-", "*", "/" },
});
}
}
public static IEnumerable<int[]> GetAllPossibleCombos(List<int[]> ints)
=> _getAllPossibleCombos(ints, 0, new List<int>());
private static IEnumerable<int[]> _getAllPossibleCombos(IReadOnlyList<int[]> ints, int index, IReadOnlyCollection<int> current)
{
return index == ints.Count - 1
? ints[index]
.Select(_ => new List<int>(current) {_}.ToArray())
: ints[index]
.SelectMany(_ => _getAllPossibleCombos(ints, index + 1, new List<int>(current) {_}));
}

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