在List <object> </object>之间获得差异的最快方法

时间:2013-03-14 19:31:39

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

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

现在的问题是找到一棵树中存在的文件,而不是另一棵树中存在的文件。我有一个方法:“私有列表Diff(节点索引1,节点索引2)”,它应该这样做。但问题是我比较树木的方式。比较两棵树需要花费大量时间 - 当每个输入节点包含大约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;
}

4 个答案:

答案 0 :(得分:4)

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

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();
}

答案 1 :(得分:1)

怎么样:

    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的答案适用于所有情况。

答案 2 :(得分:0)

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

答案 3 :(得分:0)

我可以看到这不仅仅是一个“不同”的函数,你真正需要的是所有只在JoinedListCopy集合中存在一次的实例,而不仅仅是JoinedListCopy集合中所有不同实例的列表。 / p>

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”的方法,可能效果更好,文件实例可能实际上并不相同,只要涉及代码相等...

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并不总是世界上最有效的东西,但它应该运行得相当好,让所有实例只出现一次。我最喜欢这种方法,最简单的方法是把事情弄得一团糟,但我先使用的连接可能更有效率。