【问题标题】:Fastest way to get difference between List<object>获取 List<object> 之间差异的最快方法
【发布时间】:2013-03-14 19:31:38
【问题描述】:

我正在开发一个程序,例如,它能够找到文件夹之间的文件差异。我制作了一种遍历给定文件夹的文件夹结构并为每个子文件夹构建树的方法。每个节点都包含一个文件列表,即该文件夹中的文件。每个节点都有一定数量的子节点,对应于该文件夹中的文件夹。

现在的问题是找到存在于一棵树中的文件,而不是存在于另一棵树中的文件。我有一个方法:“private List Diff(Node index1, Node index2)”,应该这样做。但问题是我比较树木的方式。比较两棵树需要大量时间 - 当每个输入节点包含大约 70,000 个文件时,Diff 方法大约需要 3-5 分钟才能完成。

我目前正在这样做:

private List<MyFile> Diff(Node index1, Node index2)
    {
        List<MyFile> DifferentFiles = new List<MyFile>();

        List<MyFile> Index1Files = FindFiles(index1);
        List<MyFile> Index2Files = FindFiles(index2);

        List<MyFile> JoinedList = new List<MyFile>();
        JoinedList.AddRange(Index1Files);
        JoinedList.AddRange(Index2Files);
        List<MyFile> JoinedListCopy = new List<MyFile>();
        JoinedListCopy.AddRange(JoinedList);
        List<string> ChecksumList = new List<string>();

        foreach (MyFile m in JoinedList)
        {

            if (ChecksumList.Contains(m.Checksum))
            {
                JoinedListCopy.RemoveAll(x => x.Checksum == m.Checksum);
            }
            else
            {
                ChecksumList.Add(m.Checksum);
            }
        }

        return JoinedListCopy;
    }

Node 类看起来像这样:

class Node
{
    private string _Dir;
    private Node _Parent;
    private List<Node> _Children;
    private List<MyFile> _Files;
}

【问题讨论】:

  • 您能否(或您是否)在比较条目之前对其进行排序? IIRC,排序的集合通常在搜索方面提供更好的性能。
  • @KennethK。并且基于散列的结构提供比排序集合更快的搜索。
  • @Servy 同意。但是正在使用List。据我所知,列表没有散列。为了散列,是否需要使用新的数据结构(例如DictionaryHashTable)? *edit 我想你可以为现有列表编写一个散列函数,因为 List 是可索引的。
  • @KennethK。是的。实际上,您将节省 很多 时间,根据列表中的内容创建新的基于哈希的结构,然后搜索列表 N 次。
  • 您的代码将如何处理包含多个文件副本的单个文件夹。我不得不承认我不知道关于校验和的所有信息,但是文件 a.txt 和 a.txt 的副本是否具有相同的校验和,因为它们在除名称和物理磁盘位置之外的所有方面都是相同的吗?

标签: c# list tree duplicate-removal set-difference


【解决方案1】:

您可以将所有校验和放入HashSet 中,而不是通过List 结构进行大量搜索(这很慢),这样可以更有效地搜索。 p>

private List<MyFile> Diff(Node index1, Node index2)
{
    var Index1Files = FindFiles(index1);
    var Index2Files = FindFiles(index2);

    //this is all of the files in both
    var intersection = new HashSet<string>(Index1Files.Select(file => file.Checksum)
         .Intersect(Index2Files.Select(file => file.Checksum)));

    return Index1Files.Concat(Index2Files)
        .Where(file => !intersection.Contains(file.Checksum))
        .ToList();
}

【讨论】:

  • 哇!与我之前所做的相比,这表现得非常好。比较生成的两个 10mb 的索引文件大约需要 5-8 秒,每个索引文件包含 70,000+ 个文件。非常感谢!
【解决方案2】:

怎么样:

    public static IEnumerable<MyFile> FindUniqueFiles(IEnumerable<MyFile> index1, IEnumerable<MyFile> index2)
    {
        HashSet<string> hash = new HashSet<string>();

        foreach (var file in index1.Concat(index2))
        {
            if (!hash.Add(file.Checksum))
            {
                hash.Remove(file.Checksum);
            }
        }

        return index1.Concat(index2).Where(file => hash.Contains(file.Checksum));
    }

这将在假设一棵树不包含重复项的情况下起作用。 Servy 的答案适用于所有情况。

【讨论】:

  • 我严重质疑这将是“最快”的方法。
  • @Oliver 你的代码现在只是执行Distinct。那不是他想要的。他只想要那些不在另一个集合中的值。如果两个集合中都有一个值,则根本不应该返回它,但您的代码会产生一次。
  • 哦,仅供参考,您的整个 foreach 循环不需要迭代器块,您可以使用 Wherereturn index1.Concat(index2).Where(file =&gt; hash.Add(file.Checksum));
  • 根据您的新编辑,它无法正确处理给定元素多次出现在序列中的情况。如果它在两组中,但在一组中两次(所以总共三次)你的代码将产生它,即使它在另一组中。
  • 没想到会出现这种情况?
【解决方案3】:

您是否为树中的每个元素保留整个 FileSystemObject?如果是这样,我认为您的内存开销将是巨大的。为什么不直接使用文件名或校验和并将其放入列表中,然后对其进行比较?

【讨论】:

  • 我会假设因为他需要其他地方的其余数据。如果他将路径信息与文件名或 AS 文件名一起存储,他总是可以重新获取它......但我们不知道他的其余代码是什么样的
【解决方案4】:

我可以看到这不仅仅是一个“独特”的功能,您真正要寻找的是在 JoinedListCopy 集合中只存在一次的所有实例,而不仅仅是 JoinedListCopy 集合中所有不同实例的列表。

Servy 有一个很好的答案,我会建议一种不同的方法,它利用 linq 的一些更有趣的功能,或者至少我觉得它们很有趣。

var diff_Files = (from a in Index1Files
                 join b in Index2Files
                 on a.CheckSum equals b.CheckSum
                 where !(Index2Files.Contains(a) || Index1Files.Contains(b))).ToList()

另一种构建“位置”的方法可能会更好,文件实例实际上可能并不相同,就代码相等而言......

where !(Index2Files.Any(c=>c.Checksum == a.Checksum) || Index1Files.Any(c=>c.Checksum == b.Checksum))

查看单个校验和,而不是整个文件对象实例。

基本策略本质上与您已经在做的完全一样,只是效率更高一点:加入集合并相互过滤,以确保您只获得唯一的条目。

另一种方法是使用 linq 中的计数功能

var diff_Files = JoinedListCopy.Where(a=> JoinedListCopy.Count(b=>b.CheckSum == a.CheckSum) == 1).ToList();

嵌套 linq 并不总是世界上最有效的东西,但它应该工作得相当好,让所有实例只发生一次。实际上,我最喜欢这种方法,把事情搞砸的可能性最小,但我首先使用的连接可能更有效。

【讨论】:

  • 您的第一个选项不起作用。当您进行连接时,您会得到集合的交集,因此您已经排除了您需要的所有值。您实际上需要与 Join 返回的内容完全相同的相反。当你到达where 时,你已经“迷路”了,可以这么说。
  • 对于另外两个,他们都在列表上对另一个序列中的每个项目进行线性搜索。非常低效。它们比 OP 的代码短,但运行时复杂度相似。
  • 不是真的,请注意 where 子句对原始的两组起作用...您从 a 和 b 中排除事物然后加入结果...我不得不说您是正确的其他两个选项虽然效率较低,但更漂亮:-)
  • Where,在第一种情况下,应用于 Join 的结果。当连接完成时,从根本上说,是成对的对象,每组一个,匹配。这正是需要被排除的,并且不包含任何应该返回的值。进一步过滤甚至不值得研究,您需要返回的所有值都已被过滤掉。
  • 所以你是说虽然加入可以工作,但实际的答案是获取加入的结果,没有位置,将它应用到他的JoinedListCopy 并删除我的@中包含的每个值987654327@变量?
猜你喜欢
  • 1970-01-01
  • 2011-01-18
  • 2020-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多