2015-06-23 10 views
2

Я пишу дуплексный детектор файлов. Чтобы определить, дублируются ли два файла, я вычисляю контрольную сумму 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 или другая функция?

Примечание: я не включил остальную часть своего кода, чтобы избежать беспорядка. Если вы хотите это увидеть, я могу включить его.

+1

Вместо начала с 0 начинается с 1 и заканчивается на 'dupInfos.Count() -1' – 3dd

+3

Другая проблема, которую вы, возможно, не рассмотрели ... Что делать, если есть 4 файла с одинаковым размером, а первый и четвертый файлы идентичны. Ваш код здесь будет пропустить, потому что между ними находятся другие неидентичные файлы с одинаковым размером. – DiscipleMichael

+1

Если вы не уверены, что найдете все совпадения, вам нужно выполнить сравнение для каждой пары в каждой группе размеров файлов, а не только выше и ниже. – DiscipleMichael

ответ

1

Я отсортировал список файлов по размеру и прокручиваю до , сравнивая каждый элемент с тем, что находится над ним и под ним.

Следующий логический шаг - фактически группировать файлы по размеру. Сравнение последовательных файлов не всегда будет достаточным, если у вас более двух файлов одинакового размера. Вместо этого вам нужно будет сравнить каждый файл с любым другим файлом одинакового размера.

Я предлагаю такой подход

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

A demo этой идеи.

+0

Спасибо ryanyuyu, мне нравится идея использования groupby. Я пытаюсь выяснить, как применить шаг 2. Каждый DupInfo содержит путь к файлу, размер, поле контрольной суммы и поле папки сравнения (в случае, если пользователь хочет сравнить только файлы, разделенные n уровнями). Я хотел бы отображать только файлы, которые могут быть дублированы (одинаковый размер, контрольная сумма и папка сравнения), сгруппированы в наборы, пользователю и выбирать их для удаления. Мне нравится идея использования словаря для предыдущих контрольных сумм, и я пытаюсь выяснить, как получить от этого в мой отображаемый список дублирующих групп. –

+0

@ KalevMaricq обновлен. – ryanyuyu

+0

Спасибо за эту идею. Я знаю, что это своего рода отдельный вопрос, поэтому, если мне нужно будет опубликовать его, но я использую ListView (WPF) для отображения. Я не уверен, как загрузить словарь/данные в список для отображения, или даже если listview является правильным инструментом. –

0

Можете ли вы сделать союз между двумя вашими списками? Если у вас есть список имен файлов и есть объединение, это должно привести только к списку перекрывающихся файлов. Я могу написать пример, если вы хотите, но эта ссылка должна дать вам общую идею.

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

Edit: Извините за какой-то причине я думал, вы сопоставляли имя файла не размер.

Итак, вот вам реальный ответ.

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

Спасибо thinklarge, у меня есть только один список файлов, и вам нужно сравнить их для любого размера. Я не думаю, что союз является правильным инструментом для этого. Я рассмотрел вопрос о присоединении к моему списку, где критерии - это +/- 1, но я не уверен, что это лучшее решение. –

+0

Спасибо Kalev, Извините, я не читал ваше описание достаточно хорошо. Я переработал его так, чтобы он применялся. Это решение на основе linq, но у него будут некоторые накладные расходы, связанные с группой и где. – thinklarge

+1

Спасибо. Мне нравится идея использования groupby. Если простой цикл работает быстрее, я мог бы также использовать его. Когда я делал что-то подобное в excel, я просто добавил запись в начале и в конце, и цикл работал нормально. Однако в C# мне кажется, что мне нужен другой подход. –

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

После сортировки файлов по размеру и стс, определить дубликаты:

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

Да ... кроме того, единственный способ, по которому вы можете пропустить контрольную сумму, - это его единственный файл в списке. Если вы действительно хотите, чтобы вы могли исключить эти файлы в блок if. – DiscipleMichael

+0

В процедуре GetCrcs() вычисления контрольных сумм выполняются только в том случае, если 2 файла или более имеют одинаковый размер. Когда есть только один файл определенного размера, его контрольная сумма равна нулю, что проверяется в цикле GetDuplicates(). – Graffito

+0

Возможно, вы имели в виду: last line сказать dupInfos [i] ...? Хороший вопрос о том, что контрольная сумма является нулевой. Я изменил его с uint на uint ?. Нужно ли устанавливать его значение null или оно будет равно null по умолчанию? Для 2-й части вы имели в виду, что я ...? Мне нравится идея инвертировать его для удаления. В этой ситуации каждый DupInfo просто хранит информацию о файле на диске, поэтому мне нужно фактически выполнить удаление на нем, а не просто удалить его из списка.Тем не менее, я мог бы удалить его из списка. –

1

Я думаю, что цикл должен быть: для (INT I = 1 ; я < dupInfos.Count() - 1; я ++)

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

Мне нравится эта идея. У меня возникли проблемы с заполнением части ForEach. Я хотел бы установить свойство CRC для ComputeCheckSum файла для каждой группы размера DupInfo в размерах больше одной. Вы знаете, как я это сделаю? –

Смежные вопросы