一尘不染

使用C#有效识别CSV文件中的更改字段

algorithm

事实证明,这比我想象的要难。基本上,每天系统都会将客户主列表的快照转储到CSV中。它包含约120000条记录和60个字段。约25mb。无论如何,我想报告一个快照和另一个快照之间变化的值。它
不是 计划文件差异,因为它必须与包含客户唯一编号的最左边的列值匹配。可以插入/删除行等。所有字段均为字符串,包括参考编号。

我已经用LINQ编写了一个解决方案,但是它死于更大的数据集。对于10000条记录,这需要17秒。对于120000,需要花费近2个小时来比较这两个文件。现在,它使用出色且免费的’filehelpers’http:
//www.filehelpers.com/加载数据,然后仅需几秒钟。但是,检测哪些记录已更改更成问题。以下是2小时的查询:

    var changednames = from f in fffiltered
                       from s in sffiltered
                       where f.CustomerRef == s.CustomerRef &&
                       f.Customer_Name != s.Customer_Name
                       select new { f, s };

您会推荐哪种方法?我想立即将清单“删节”给那些进行了某种更改的人,然后将更具体的比较应用于该较小的子集。我的一些想法是:

a)使用字典或哈希表-尽管早期测试并未真正显示出改进

b)区分操作-在客户参考字段中使用第一个字符,并且仅与具有相同字符的字符匹配。虽然这可能涉及创建许多单独的集合,但看起来并不优雅。

c)远离类型化的数据安排,并使用数组进行处理。同样,收益不确定。

有什么想法吗?

谢谢!


阅读 261

收藏
2020-07-28

共1个答案

一尘不染

为了进行下面的讨论,我假设您有一些将CSV文件读入类的方法。我叫那个班MyRecord

将文件加载到单独的列表中,分别调用NewListOldList

List<MyRecord> NewList = LoadFile("newFilename");
List<MyRecord> OldList = LoadFile("oldFilename");

使用LINQ可能有一种更优雅的方法,但是想法是进行直接合并。首先,您必须对两个列表进行排序。您的MyRecord类实现了IComparable,或者您提供了自己的比较委托:

NewList.Sort(/* delegate here */);
OldList.Sort(/* delegate here */);

如果MyRecord实现,则可以跳过委托IComparable

现在是直接合并。

int ixNew = 0;
int ixOld = 0;
while (ixNew < NewList.Count && ixOld < OldList.Count)
{
    // Again with the comparison delegate.
    // I'll assume that MyRecord implements IComparable
    int cmpRslt = OldList[ixOld].CompareTo(NewList[ixNew]);
    if (cmpRslt == 0)
    {
        // records have the same customer id.
        // compare for changes.
        ++ixNew;
        ++ixOld;
    }
    else if (cmpRslt < 0)
    {
        // this old record is not in the new file.  It's been deleted.
        ++ixOld;
    }
    else
    {
        // this new record is not in the old file.  It was added.
        ++ixNew;
    }
}

// At this point, one of the lists might still have items.
while (ixNew < NewList.Count)
{
    // NewList[ixNew] is an added record
    ++ixNew;
}

while (ixOld < OldList.Count)
{
    // OldList[ixOld] is a deleted record
}

只有12万条记录,这应该很快执行。如果执行合并只花了从磁盘加载数据的时间,我将感到非常惊讶。

编辑:LINQ解决方案

我思考如何使用LINQ来做到这一点。我无法执行与上述合并完全相同的操作,但是可以在单独的集合中添加,删除和更改项目。
为此,MyRecord必须实现IEquatable<MyRecord>并重写GetHashCode

var AddedItems = NewList.Except(OldList);
var RemovedItems = OldList.Except(NewList);

var OldListLookup = OldList.ToLookup(t => t.Id);
var ItemsInBothLists =
    from newThing in NewList
    let oldThing = OldListLookup[newThing.Id].FirstOrDefault()
    where oldThing != null
    select new { oldThing = oldThing, newThing = newThing };

在上面,我假设MyRecord具有Id唯一的属性。

如果您只想更改的项目而不是两个列表中的所有项目:

var ChangedItems =
    from newThing in NewList
    let oldThing = OldListLookup[newThing.Id].FirstOrDefault()
    where oldThing != null && CompareItems(oldThing, newThing) != 0
    select new { oldThing = oldThing, newThing = newThing };

假设该CompareItems方法将对这两个项目进行深层比较,如果发生了变化则比较为相等或为非零,则返回0。

2020-07-28