0

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.

Demokrit
  • 1
  • 6
  • So the problem is that when you run it the second time the result gets added to the CSV instead of replacing the existing data? – Bianca Nov 22 '18 at 14:29
  • I think the Data only gets added once. The problem is that the when i add line 1-2 (with the same date) I get line 3. I only need line 3 because line 1 and 2 are useless now. – Demokrit Nov 22 '18 at 14:31
  • https://joshclose.github.io/CsvHelper/ when playing with a csv – Drag and Drop Nov 22 '18 at 14:35
  • @DragandDrop im trying not to use libraries and stuff like linq yet because I dont really understand them. I just started learning c# and I want to get the basics down before I start using advanced stuff. – Demokrit Nov 22 '18 at 14:37
  • I think the problem here is that you're writing a value to the file for every line you read. You need to restructure your loops to be able to read multiple times and only write when you find a new date. – Robin Bennett Nov 22 '18 at 14:40
  • @RobinBennett I thought I already kind of did that with if (dtDateValue.Date == DateTime.Now.Date) (drDateValue.Date is the Date of a line (and it loops through every single line)). Can you give me a concrete example ? – Demokrit Nov 22 '18 at 14:43
  • 'CompareDateMethod' will return the first time it finds a new date, but in the main piece of code it looks like you're intending it to process all the rows. IMHO you should move it outside the 'using's and debug to see what values it produces. Also, are you writing your results to the same file you read at the start? It would be easier to debug if you use a separate file, at least until you're happy it's working properly. – Robin Bennett Nov 22 '18 at 14:59
  • @RobinBennett the first time part doesn't seem to be a problem in my case because there should not be more than 1 line with the same DateTime at a time. I think im kind of looking for a hardcoding (If thats the right term) shitty solution because i'm pretty sure that I got in over my head with the thousand dicitonary and 2 dimensional array's and stuff. Someone suggested always deleting the last line if the Date is the same which should work but I wont be able to test it until tomorrow – Demokrit Nov 22 '18 at 15:04
  • Rather than toying with a CSV as a data store, if you had a class and collection to store the data your could save and read it using serialization in a very few lines of code. Then a little linq for summing, grouping etc. Should be much simpler than what you have – Ňɏssa Pøngjǣrdenlarp Nov 22 '18 at 15:42

4 Answers4

1

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;
    }
}
Robin Bennett
  • 3,192
  • 1
  • 8
  • 18
  • Do you think its possible without using a 2 dimensional array at all ? Im trying it right now – Demokrit Nov 23 '18 at 10:28
  • @Demokrit you can try List with which you can play around using LINQ... – Vanest Nov 23 '18 at 10:45
  • @Vanest perfect thats what I did before the only problem right now is that I am having trouble reading the Date out of the File with the old data. Do you know how I would need to modify this if (DateTime.TryParse(oReadCsvList[i][0], out OldDateTime)) to make it work with a list ? – Demokrit Nov 23 '18 at 10:57
  • @Demokrit - yes, personally would have created a lightweight Row class with a Date and List properties. It could have a ToString() method for writing to the CSV file, and even a method to add the values from another Row. However I wanted a solution that was recognisable from the original, in case I'd missed some important feature. – Robin Bennett Nov 23 '18 at 11:29
  • @Demokrit foreach (string[] csvDate in oReadCsvList) { if (DateTime.TryParse(csvDate[0], out OldDateTime)) { } } that also works the same way. – Vanest Nov 23 '18 at 11:31
  • @Vanest what line would I replace to put this in ? When I try to replace my old tryparse datetime line, I get the following error: CS0165 C# Use of unassigned local variable. That seems super weird to me because they were at the same place and I thought they worked the same way – Demokrit Nov 23 '18 at 13:02
0

For overwriting the previous CSV just use System.IO.FileMode.Create instead of Append. This will overwrite any previous data.

Bianca
  • 382
  • 4
  • 12
  • But that would just delete the lines that don't have the same Date right ? That means I would need to read in all the values first ? – Demokrit Nov 22 '18 at 14:35
  • Looking at your code, it looks like you are recalculating only the data for today (dtDateValue.Date == DateTime.Now.Date) so in that case, before writing the data, I would read the last line of the CSV and if the last line has today's data I would remove it. Also you would need to make sure that the items are added chronologically in your CSV (ordered by date) – Bianca Nov 22 '18 at 14:51
  • The answer on this thread looks more interesting: https://forums.asp.net/t/1622656.aspx?Delete+last+line+in+a+text+file. You read the CSV again, check if the line you are trying to enter is not there already, remove it if it is, and then add the new calculation in it's place. This should be generic enough to handle any update on any date not only today. – Bianca Nov 22 '18 at 14:55
  • the delete last line looks like a possible solution to me. Ill have to try that tomorrow and get back to you thank you so much for the pointer though. – Demokrit Nov 22 '18 at 15:00
  • 2
    Solutions that require adding and deleting lines while reading from a file are likely to go wrong. IMHO it's better to read the input file, build the output, then overwrite the input file if you're happy with it. – Robin Bennett Nov 22 '18 at 15:01
  • @RobinBennett the sad part is that I even get the whole concept and would easily be able to do it but for some reason my head cant get around to 2-dimensional arrays and how to write them into files. I tried to google solutions but none of them seemed to work and to build the new output I would need the 2-Dimensional array that the values get read into if that makes sense. – Demokrit Nov 22 '18 at 15:07
0

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...

Vanest
  • 906
  • 5
  • 14
0

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);
        }
    }
}
xdtTransform
  • 1,986
  • 14
  • 34
  • By geting the Csv configuration in a method, removing the initialisation to pretend that there is data and cutting the fluff , reading + solving duplicate and saving it's 13 lines of codes. – xdtTransform Nov 22 '18 at 16:09
  • Super Zip could be more efficient than summing all value to the first element but I wanted to have a clear code on this part. – xdtTransform Nov 22 '18 at 16:10