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

时间:2011-03-09 00:55:50

标签: c# algorithm diff

事实证明这比我想象的要困难。基本上,系统每天都会将客户主列表的快照转储为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)使用字典或Hashsets-尽管早期测试并未真正显示改进

b)对操作进行划分 - 使用客户参考字段中的第一个字符,并仅与具有相同字符的字符匹配。这可能涉及创建许多单独的集合,但似乎非常不优雅。

c)远离类型化数据排列,并使用数组进行操作。再次,利益不确定。

有什么想法吗?

谢谢!

5 个答案:

答案 0 :(得分:4)

出于以下讨论的目的,我假设您有一些方法可以将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
}

只有120,000条记录,应该可以非常快速地执行。如果进行合并只需要从磁盘加载数据,我会感到非常惊讶。

编辑: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。

答案 1 :(得分:2)

这可能最好在数据库而不是代码中完成:创建两个表(当前和旧),将CSV文件中的数据导入到正确的表中,并使用SQL查询的组合来生成输出。

答案 2 :(得分:0)

您从哪里导出CSV?

您的原始来源是数据库吗?如果是这样,为什么不能对数据库运行查询?它将比任何LINQ实现更高效。

答案 3 :(得分:0)

扩展Jims的答案,一个基本的例子:

public class MyRecord
{
  public MyRecord(int id)
  {
    Id = id;
    Fields = new int[60];
  }

  public int Id;
  public int[] Fields;
}

然后测试代码:

var recordsOld = new List<MyRecord>();
var recordsNew = new List<MyRecord>();

for (int i = 0; i < 120000; i++)
{
  recordsOld.Add(new MyRecord(i));
  recordsNew.Add(new MyRecord(i));
}

var watch = new System.Diagnostics.Stopwatch();
int j = 0;

watch.Start();
for (int i = 0; i < recordsOld.Count; i++)
{
  while (recordsOld[i].Id != recordsNew[j].Id)
  {
    j++;
  }

  for (int k = 0; k < recordsOld[i].Fields.Length; k++)
  {
    if (recordsOld[i].Fields[k] != recordsNew[j].Fields[k])
    {
      // do your stuff here
    }
  }
}
watch.Stop();
string time = watch.ToString();

假设列表有序,需要200ms才能运行。现在,我确信代码中有大量的bug,但从最基本的意义上来说,处理器很长时间才能完成数百万次迭代。您要么进行一些复杂的比较检查,要么某些代码非常低效。

答案 4 :(得分:0)

另一方已经提供了很好的答案,我只是提供一些不同的东西供你考虑。

伪代码:

Read 1000 from each source.
Compare the records.
If changed, store in list of changed records.
If not changed, discard from list.
If not exists, keep in list.
Repeat until all records are exhausted.

此代码假定记录未排序。

另一种选择是:

Read all the records and determine what are all the first characters.
Then for each character,
    Read and find records starting with that character.
    Perform comparison as necessary

如果使用的记录超过某个阈值,对上述内容的改进将是写入新文件。例如:

Read all the records and determine what are all the first characters and the number of occurrence.
Sort by characters with the highest occurrence.
Then for each character,
    Read and find records starting with that character.
    If number of occurrence exceed a certain limit, write records that doesn't start with the character into a new file. // this reduces the amount of data that must be read from file
    Perform comparison as necessary