在 C# 中比较两个不同 CSV 文件的行值

compare rows values of two different CSV files in c#

我知道还有更多类似的问题,但我找不到我的答案。我有两个 CSV 文件。这两个文件都包含相同图像的图像元数据,但是,第一个文件图像 ID 已过时。所以我需要从第二个文件中获取 ID,并将过时的 ID 替换为新的 ID。我正在考虑比较图像的经度、纬度和海拔高度行值,以及它在两个文件中匹配的位置,我从第二个文件中获取图像 ID。这些 ID 将在新对象中使用。并且文件中的行顺序不同,第一个文件包含的行数比第二个文件多。

文件结构如下:

第一个文件:

ImgID,Longitude,Latitude,Altitude
01,44.7282372307,27.5786807185,14.1536407471
02,44.7287939869,27.5777060219,13.2340240479
03,44.7254687824,27.582636255,16.5887145996
04,44.7254294913,27.5826908925,16.5794525146
05,44.728785278,27.5777185252,13.2553100586
06,44.7282279311,27.5786933339,14.1576690674
07,44.7253847039,27.5827526969,16.6026000977
08,44.7287777782,27.5777295052,13.2788238525
09,44.7282196988,27.5787045314,14.1649169922
10,44.7253397041,27.5828151049,16.6300048828
11,44.728769439,27.5777417846,13.3072509766

第二个文件:

ImgID,Longitude,Latitude,Altitude
5702,44.7282372307,27.5786807185,14.1536407471
5703,44.7287939869,27.5777060219,13.2340240479
5704,44.7254687824,27.582636255,16.5887145996
5705,44.7254294913,27.5826908925,16.5794525146
5706,44.728785278,27.5777185252,13.2553100586
5707,44.7282279311,27.5786933339,14.1576690674

如何在 C# 中完成此操作?是否有一些方便的库可以使用?

我会为 CSV read/write 使用 CSVHelper library,因为它是一个完整的好库。为此,您应该声明一个 class 来保存您的数据,并且其 属性 名称必须与您的 CSV 文件的列名称相匹配。

public class ImageData
{
    public int ImgID { get; set; }
    public double Longitude { get; set; }
    public double Latitude { get; set; }
    public double Altitude { get; set; }
}

然后查看两行是否相等,您需要做的是查看一个文件中每一行中的每个 属性 是否与另一行匹配。您可以通过简单地比较属性来做到这一点,但我宁愿为此编写一个比较器,如下所示:

public class ImageDataComparer : IEqualityComparer<ImageData>
{
    public bool Equals(ImageData x, ImageData y)
    {
        return (x.Altitude == y.Altitude && x.Latitude == y.Latitude && x.Longitude == y.Longitude);
    }

    public int GetHashCode(ImageData obj)
    {
        unchecked
        {
            int hash = (int)2166136261;
            hash = (hash * 16777619) ^ obj.Altitude.GetHashCode();
            hash = (hash * 16777619) ^ obj.Latitude.GetHashCode();
            hash = (hash * 16777619) ^ obj.Longitude.GetHashCode();
            return hash;
        }
    }
}

简单的解释是,我们重写了Equals() 方法,并规定如果三个属性 值匹配,ImageData class 的两个实例就相等。我稍后会展示用法。

CSV read/write 部分非常简单(图书馆的帮助页面有一些很好的示例和提示,请阅读)。我可以像这样写两种读写方法:

public static List<ImageData> ReadCSVData(string filePath)
{
    List<ImageData> records;
    using (var reader = new StreamReader(filePath))
    {
        using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
        {
            csv.Configuration.HasHeaderRecord = true;
            records = csv.GetRecords<ImageData>().ToList();
        }
    }
    return records;
}

public static void WriteCSVData(string filePath, List<ImageData> records)
{
    using (var writer = new StreamWriter(filePath))
    {
        using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
        {
            csv.WriteRecords(records);
        }
    }
}

您实际上可以编写通用 <T> read/write 方法,这样这两种方法可用于不同的 classes,如果这对您有用的话。

接下来是关键部分。首先,使用我们刚刚定义的方法将这两个文件读入内存。

var oldData = ReadCSVData(Path.Combine(Directory.GetCurrentDirectory(), "OldFile.csv"));
var newData = ReadCSVData(Path.Combine(Directory.GetCurrentDirectory(), "NewFile.csv"));

现在,我可以遍历'old'数据中的每一行,看看'new'数据中是否有对应的记录。如果是这样,我从新数据中获取 ID 并用它替换旧数据的 ID。注意我们写的比较器的用法。

foreach (var line in oldData)
{
    var replace = newData.FirstOrDefault(x => new ImageDataComparer().Equals(x, line));
    if (replace != null && replace.ImgID != line.ImgID)
    {
        line.ImgID = replace.ImgID;
    }
}

接下来,简单地覆盖旧的数据文件。

WriteCSVData(Path.Combine(Directory.GetCurrentDirectory(), "OldFile.csv"), oldData);

结果

我正在使用您数据的简化版本来轻松验证我们的结果。

旧数据

ImgID,Longitude,Latitude,Altitude
1,1,2,3
2,2,3,4
3,3,4,5
4,4,5,6
5,5,6,7
6,6,7,8
7,7,8,9
8,8,9,10
9,9,10,11
10,10,11,12
11,11,12,13

新数据

ImgID,Longitude,Latitude,Altitude
5702,1,2,3
5703,2,3,4
5704,3,4,5
5705,4,5,6
5706,5,6,7
5707,6,7,8

现在我们的预期结果应该是旧文件的前 6 行应该更新 ID,这就是我们得到的结果:

更新旧数据

ImgID,Longitude,Latitude,Altitude
5702,1,2,3
5703,2,3,4
5704,3,4,5
5705,4,5,6
5706,5,6,7
5707,6,7,8
7,7,8,9
8,8,9,10
9,9,10,11
10,10,11,12
11,11,12,13

如果出于某种原因您不想使用 CSVHelper,另一种方法是编写一个方法来比较两行数据并确定它们是否相等(通过忽略第一列数据):

public static bool DataLinesAreEqual(string first, string second)
{
    if (first == null || second == null) return false;
    var xParts = first.Split(',');
    var yParts = second.Split(',');
    if (xParts.Length != 4 || yParts.Length != 4) return false;
    return xParts.Skip(1).SequenceEqual(yParts.Skip(1));
}

然后我们可以将两个文件中的所有行读入数组,然后如果我们的方法说它们相等,我们可以用第二个文件中的行更新我们的第一个文件行:

var csvPath1 = @"c:\temp\csvData1.csv";
var csvPath2 = @"c:\temp\csvData2.csv";

// Read lines from both files
var first = File.ReadAllLines(csvPath1);
var second = File.ReadAllLines(csvPath2);

// Select the updated line where necessary
var updated = first.Select(f => second.FirstOrDefault(s => DataLinesAreEqual(f, s)) ?? f);

// Write the updated result back to the first file
File.WriteAllLines(csvPath1, updated);