【问题标题】:C# Add lines with same Date.Time in .csv fileC# 在 .csv 文件中添加具有相同 Date.Time 的行
【发布时间】:2019-04-25 06:11:12
【问题描述】:

我目前正在制作一个程序来跟踪某些事情(基本 INT 值和保存它们的日期)。

我的目标是将具有相同日期的 INT 值相加。

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

基本上应该是这样的

20.11.2018 00:00:00; 2;2;2;2;2
22.11.2018 00:00:00; 1;1;1;1;1

保存数据,甚至将 2 条“线”加在一起都可以正常工作。

问题是当我将 Lines 添加在一起时,2 Lines with 1 显然不会被删除。

这是比较日期并将行加在一起的方法:

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;  

这是将数据写入 .CSV 文件的方法

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

我非常努力地想出一些东西,但没有任何效果(我尝试从 .csv 中删除行,这似乎真的很难,我尝试向后读取文件,但没有用等)

如果有人能给我一些建议,我将不胜感激。

【问题讨论】:

  • 那么问题是,当您第二次运行它时,结果会被添加到 CSV,而不是替换现有数据?
  • 我认为数据只会被添加一次。问题是当我添加第 1-2 行(具有相同日期)时,我得到第 3 行。我只需要第 3 行,因为第 1 行和第 2 行现在没用了。
  • @DragandDrop 我试图不使用库和 linq 之类的东西,因为我不太了解它们。我刚开始学习 c#,我想在开始使用高级东西之前先了解基础知识。
  • 我认为这里的问题是您正在为您读取的每一行写入一个值到文件中。您需要重新构建循环,以便能够多次读取,并且仅在找到新日期时才写入。

标签: c# csv


【解决方案1】:

我认为原始代码的问题在于它对什么时候发生的事情有点困惑。我已经对其进行了重组,以便事情以逻辑顺序发生(并对其进行了一些更新,简化了变量名称等)。合并同一日期的行有一个功能,它与 CSV 编写代码(没有改变)是分开的

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

【讨论】:

  • 您认为完全不使用二维数组是否可行?我现在试试
  • @Demokrit 您可以尝试使用 List 使用 LINQ...
  • @Vanest 完美,这就是我之前所做的,现在唯一的问题是我无法使用旧数据从文件中读取日期。你知道我需要如何修改这个 if (DateTime.TryParse(oReadCsvList[i][0], out OldDateTime)) 以使其与列表一起使用吗?
  • @Demokrit - 是的,个人会创建一个具有 Date 和 List 属性的轻量级 Row 类。它可以具有用于写入 CSV 文件的 ToString() 方法,甚至可以具有从另一行添加值的方法。但是,我想要一个可以从原始版本中识别出来的解决方案,以防我错过了一些重要的功能。
  • @Demokrit foreach (string[] csvDate in oReadCsvList) { if (DateTime.TryParse(csvDate[0], out OldDateTime)) { } } 这也是同样的工作方式。
【解决方案2】:

要覆盖以前的 CSV,只需使用 System.IO.FileMode.Create 而不是 Append。这将覆盖任何以前的数据。

【讨论】:

  • 但这只会删除没有相同日期的行吗?这意味着我需要先读入所有值?
  • 查看您的代码,看起来您只重新计算今天的数据 (dtDateValue.Date == DateTime.Now.Date) 所以在这种情况下,在写入数据之前,我会阅读CSV 的最后一行,如果最后一行有今天的数据,我会删除它。此外,您还需要确保在 CSV 中按时间顺序添加项目(按日期排序)
  • 这个线程上的答案看起来更有趣:forums.asp.net/t/1622656.aspx?Delete+last+line+in+a+text+file。您再次阅读 CSV,检查您尝试输入的行是否不存在,如果存在则将其删除,然后在其位置添加新计算。这应该足够通用,可以处理任何日期的任何更新,而不仅仅是今天。
  • 删除最后一行对我来说似乎是一个可能的解决方案。明天我得试试,然后回复你,非常感谢你的指点。
  • 在读取文件时需要添加和删除行的解决方案可能会出错。恕我直言,最好读取输入文件,构建输出,然后如果您对它感到满意,则覆盖输入文件。
【解决方案3】:

无论如何,您都需要覆盖 csv 以摆脱写入的行。 所以不要从CompareDateMethod方法返回oDateTimeAndIntDictionary,而是返回ReadData并覆盖ReadData的解析值。

类似的,

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

希望这会有所帮助...

【讨论】:

    【解决方案4】:

    我阅读了您关于不使用 linq 和第 3 部分库的评论为时已晚。
    但让我告诉你你缺少什么。
    这里有一点 Linq + CSVHelper

    首先要定义您的数据,并定义如何在 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)
                    }
                );
        }
    }
    

    现在这是一个简短的演示:

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

    【讨论】:

    • 通过在方法中获取Csv配置,去掉初始化假装有数据,剪掉绒毛,读取+解决重复,保存13行代码。
    • Super Zip 可能比将所有值加到第一个元素更有效,但我想在这部分有一个清晰的代码。
    猜你喜欢
    • 2013-07-24
    • 2014-10-12
    • 1970-01-01
    • 1970-01-01
    • 2013-05-10
    • 2018-07-17
    • 1970-01-01
    • 2012-03-25
    • 2014-03-14
    相关资源
    最近更新 更多