比较相邻列表项

Compare adjacent list items

提问人:Kalev Maricq 提问时间:6/24/2015 更新时间:6/25/2015 访问量:1689

问:

我正在编写一个重复文件检测器。为了确定两个文件是否重复,我计算了 CRC32 校验和。由于这可能是一项代价高昂的操作,因此我只想为具有另一个大小匹配的文件计算校验和。我已经按大小对文件列表进行了排序,并循环访问以将每个元素与其上方和下方的元素进行比较。不幸的是,开头和结尾都存在问题,因为分别没有上一个或下一个文件。我可以使用 if 语句解决这个问题,但感觉很笨拙。这是我的代码:

    public void GetCRCs(List<DupInfo> dupInfos)
    {
        var crc = new Crc32();
        for (int i = 0; i < dupInfos.Count(); i++)
        {
            if (dupInfos[i].Size == dupInfos[i - 1].Size || dupInfos[i].Size == dupInfos[i + 1].Size)
            {
                dupInfos[i].CheckSum = crc.ComputeChecksum(File.ReadAllBytes(dupInfos[i].FullName));
            }
        }
    }

我的问题是:

  1. 如何在没有越界错误的情况下将每个条目与其相邻条目进行比较?

  2. 我应该为此使用循环,还是有更好的 LINQ 或其他函数?

注意:为了避免混乱,我没有包含其余代码。如果你想看它,我可以包括它。

C# LINQ 列表 循环

评论

1赞 3dd 6/24/2015
不是从 0 开始,而是从 1 开始,在dupInfos.Count() -1
3赞 DiscipleMichael 6/24/2015
您可能没有考虑过的另一个问题......如果有 4 个文件大小相同,并且第一个和第 4 个文件相同怎么办。此处的代码将错过这些文件,因为它们之间有其他大小相同的不相同文件。
1赞 DiscipleMichael 6/24/2015
如果你不确定你能找到所有的匹配项,你需要对每个文件大小组中的每对进行比较,而不仅仅是上面和下面的一对。
0赞 Kalev Maricq 6/24/2015
3DD - 这是我目前的策略,但感觉不优雅。我只是想知道是否有更好的方法。门徒迈克尔 - 我的计划是按大小和CRC进行排序。然后,我只能再次检查相邻文件,而不是遍历大小组中的每个文件。
0赞 DiscipleMichael 6/24/2015
您不必检查整个集合中每个可能的对......只是一组相同文件大小的组中的每一个可能的对

答:

0赞 thinklarge 6/24/2015 #1

你能在两个列表之间做一个联合吗?如果您有一个文件名列表并执行合并,它应该只生成重叠文件的列表。如果你愿意,我可以写出一个例子,但这个链接应该给你一个大致的想法。

https://stackoverflow.com/a/13505715/1856992

编辑:对不起,出于某种原因,我以为您正在比较文件名而不是大小。

所以这里有一个实际的答案给你。

using System;
using System.Collections.Generic;
using System.Linq;


public class ObjectWithSize
{
    public int Size {get; set;}
    public ObjectWithSize(int size)
    {
        Size = size;
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("start");
        var list = new List<ObjectWithSize>();
        list.Add(new ObjectWithSize(12));
        list.Add(new ObjectWithSize(13));
        list.Add(new ObjectWithSize(14));
        list.Add(new ObjectWithSize(14));
        list.Add(new ObjectWithSize(18));
        list.Add(new ObjectWithSize(15));
        list.Add(new ObjectWithSize(15));
        var duplicates = list.GroupBy(x=>x.Size)
              .Where(g=>g.Count()>1);
        foreach (var dup in duplicates)
            foreach (var objWithSize in dup)
                Console.WriteLine(objWithSize.Size);
    }
}

这将打印出来

14
14
15
15

这里有一个 netFiddle。https://dotnetfiddle.net/0ub6Bs

最后说明。我实际上认为你的答案看起来更好,而且会跑得更快。这只是 Linq 中的一个实现。

评论

1赞 Kalev Maricq 6/24/2015
谢谢thinklarge,我只有一个文件列表,需要比较它们是否有任何相同大小的文件。我不认为工会是正确的工具。我考虑过将我的列表加入到其自身中,其中的标准是索引为 +/- 1,但我不确定这是否是最佳解决方案。
0赞 thinklarge 6/24/2015
谢谢卡列夫,对不起,我没有很好地阅读您的描述。我重新设计了它,使其适用,这是一个基于 linq 的解决方案,但它会有一些与组和位置相关的开销。
1赞 Kalev Maricq 6/24/2015
谢谢。我喜欢使用 groupby 的想法。不过,如果一个简单的循环工作得更快,我不妨使用它。当我在 excel 中做类似的事情时,我只是在开头和结尾添加了一条记录,循环工作正常。不过,在 c# 中,我似乎需要一种不同的方法。
2赞 Graffito 6/24/2015 #2

首先计算 Crcs:

// It is assumed that DupInfo.CheckSum is nullable
public void GetCRCs(List<DupInfo> dupInfos)
{
  dupInfos[0].CheckSum = null ;        
  for (int i = 1; i < dupInfos.Count(); i++)
    {
       dupInfos[i].CheckSum = null ;
       if (dupInfos[i].Size == dupInfos[i - 1].Size)
       {
         if (dupInfos[i-1].Checksum==null) dupInfos[i-1].CheckSum = crc.ComputeChecksum(File.ReadAllBytes(dupInfos[i-1].FullName));
         dupInfos[i].CheckSum = crc.ComputeChecksum(File.ReadAllBytes(dupInfos[i].FullName));
       }
    }
}

按大小和 crc 对文件进行排序后,识别重复项:

public void GetDuplicates(List<DupInfo> dupInfos) 
{
  for (int i = dupInfos.Count();i>0 i++)
  { // loop is inverted to allow list items deletion
    if (dupInfos[i].Size     == dupInfos[i - 1].Size &&
        dupInfos[i].CheckSum != null &&
        dupInfos[i].CheckSum == dupInfos[i - 1].Checksum)
     { // i is duplicated with i-1
       ... // your code here
       ... // eventually, dupInfos.RemoveAt(i) ; 
     }
   }
}

评论

0赞 DiscipleMichael 6/24/2015
是的。。。此外,您可以跳过校验和的唯一方法是,如果它是列表中唯一具有其大小的文件。如果你真的愿意,你可以在if块中排除这些文件。
0赞 Graffito 6/24/2015
在 GetCrcs() 过程中,仅当 2 个或更多文件具有相同大小时,才会进行校验和计算。当只有一个特定大小的文件时,其校验和为 null,即在 GetDuplicates() 循环中测试的内容。
0赞 Kalev Maricq 6/24/2015
你的意思是最后一行说 dupInfos[i]...?关于校验和可为空的好点。我已将其从 uint 更改为 uint?。是否有必要将其设置为 null 还是默认为 null?对于第二部分,你是说我吗--?我喜欢将其反转以允许删除的想法。在这种情况下,每个 DupInfo 只保存有关磁盘上文件的信息,因此我需要实际对其执行删除操作,而不仅仅是将其从列表中删除。不过,我也可以将其从列表中删除。
0赞 Graffito 6/25/2015
是的,它的两边都是 dupInfos[i]。我刚刚编辑了建议的代码 - 对不起,剪切和粘贴错误!- 校验和在第 1 部分循环的第一条指令时初始化为 null。
1赞 SKG 6/24/2015 #3

我认为for循环应该是:for(int i = 1; i < dupInfos.Count()-1; i++)

var grps= dupInfos.GroupBy(d=>d.Size);
grps.Where(g=>g.Count>1).ToList().ForEach(g=>
{
    ...
});

评论

0赞 Kalev Maricq 6/24/2015
我喜欢这个主意。我在填写 ForEach 部分时遇到问题。我想将CRC属性设置为大于1的大小组中每个DupInfo的文件的ComputeCheckSum。你知道我该怎么做吗?
1赞 ryanyuyu 6/24/2015 #4

我已经按大小对文件列表进行了排序,并循环到 将每个元素与其上方和下方的元素进行比较。

下一个合乎逻辑的步骤是按大小对文件进行实际分组。如果有两个以上相同大小的文件,则比较连续文件并不总是足够的。相反,您需要将每个文件与其他所有相同大小的文件进行比较。

我建议采取这种方法

  1. 使用 LINQ 的 .GroupBy 创建文件大小的集合。然后仅保留具有多个文件的组。.Where

  2. 在这些组中,计算 CRC32 校验和并将其添加到已知校验和的集合中。与先前计算的校验和进行比较。如果你需要知道哪些文件是重复的,你可以使用一个由这个校验和键控的字典(你可以用另一个.否则,一个简单的列表就足以检测任何重复项。GroupBy

代码可能如下所示:

var filesSetsWithPossibleDupes = files.GroupBy(f => f.Length)
                                      .Where(group => group.Count() > 1);

foreach (var grp in filesSetsWithPossibleDupes)
{
    var checksums = new List<CRC32CheckSum>(); //or whatever type
    foreach (var file in grp)
    {
        var currentCheckSum = crc.ComputeChecksum(file);
        if (checksums.Contains(currentCheckSum))
        {
            //Found a duplicate
        }
        else
        {
            checksums.Add(currentCheckSum);
        }
    }
}

或者,如果您需要可能是重复的特定对象,则内部循环可能如下所示foreach

var filesSetsWithPossibleDupes = files.GroupBy(f => f.FileSize)
                                      .Where(grp => grp.Count() > 1);

var masterDuplicateDict = new Dictionary<DupStats, IEnumerable<DupInfo>>();
//A dictionary keyed by the basic duplicate stats
//, and whose value is a collection of the possible duplicates

foreach (var grp in filesSetsWithPossibleDupes)
{
    var likelyDuplicates = grp.GroupBy(dup => dup.Checksum)
                              .Where(g => g.Count() > 1);
    //Same GroupBy logic, but applied to the checksum (instead of file size)

    foreach(var dupGrp in likelyDuplicates)
    {
        //Create the key for the dictionary (your code is likely different)
        var sample = dupGrp.First();
        var key = new DupStats() {FileSize = sample.FileSize, Checksum = sample.Checksum};
        masterDuplicateDict.Add(key, dupGrp);
    }
}

这个想法的演示

评论

0赞 Kalev Maricq 6/24/2015
谢谢ryanyuyu,我喜欢使用groupby的想法。我正在尝试弄清楚如何应用第 2 步。每个 DupInfo 都包含一个文件路径、大小、校验和字段和一个比较文件夹字段(如果用户只想比较由 n 个级别分隔的文件)。我只想向用户显示可能是重复的文件(相同大小、校验和和比较文件夹),分组成组,并让他们选择要删除的文件。我喜欢使用字典来获取以前的校验和的想法,并试图弄清楚如何从它进入我显示的重复组列表。
0赞 Kalev Maricq 6/25/2015
谢谢你的想法。我知道这是一个单独的问题,所以如果我需要,我会发布它,但我目前正在使用 ListView (WPF) 进行显示。我不确定如何将字典/数据加载到列表视图中进行显示,或者即使列表视图是正确的工具。
0赞 ryanyuyu 6/25/2015
@KalevMaricq是的,这是一个单独的问题。如果有帮助,您可以随时包含指向此问题的链接以获取上下文。