Есть 2 csv файла с содержимым
1.csv
spain;russia;japan
italy;russia;france
2.csv
spain;russia;japan
india;iran;pakistan
Считываю оба файла и заношу их содержимое в список
var lst1= File.ReadAllLines("1.csv").ToList();
var lst2= File.ReadAllLines("2.csv").ToList();
Затем я ищу уникальные элементы из обоих списков
var rezList = lst1.Except(lst2).Union(lst2.Except(lst1)).ToList();
В rezlist у нас следюущие данные.
[0] = "italy;russia;france"
[1] = "india;iran;pakistan"
Теперь же я хочу сравнить оба csv-файла по второй и третьей колонке. Как мы выдими разделители колонок у нас
class StringLengthEqualityComparer : IEqualityComparer
public bool Equals(string x, string y)
{
return (x.Split(';')[1] == y.Split(';')[1] && x.Split(';')[2] == y.Split(';')[2]);
}
public int GetHashCode(string obj)
{
return obj.Split(';')[1].GetHashCode();
}
}
StringLengthEqualityComparer stringLengthComparer = new StringLengthEqualityComparer();
var rezList = lst1.Except(lst2,stringLengthComparer ).Union(lst2.Except(lst1,stringLengthComparer),stringLengthComparer).ToList();
Вопрос. Как правильно составить класс StringLengthEqualityComparer чтобы он искал уникальные значения по двум колонкам?
Ответ
Интерфейс IEqualityComparer
Как известно, для того, чтобы иметь возможность корректно сравнивать объекты, в них должны быть переопределены методы Equals() и GetHashCode(), причем должны соблюдаться следующие условия:
стандартные условия для равенства (например, транзитивность)
два равных объекта должны давать одинаковое значение хэшкода
(При этом два разных объекта могут иметь равные хэшкоды -- это называется коллизией и должно случаться как можно реже.)
Оба этих метода активно используются структурами Dictionary
В случае, когда мы не хотим или не имеем возможности изменять логику сравнения для существующих типов, на помощь нам и приходит интерфейс IEqualityComparer
Приведенная вами реализация компаратора дает корректные результаты сравнения, однако при большом объеме данных может свести на нет все преимущества быстрой работы метода Except(). Для пар a;b и a;c выдастся одинаковый хэшкод, они попадут в одну корзину внутри Set
Поэтому правильная реализация должна использовать те же поля, которые используются в Equals(), т.е. колонки 1 и 2 (про хэш-функции также читайте в статьях о хэш-таблицах). Например:
public int GetHashCode(string obj)
{
var valuesArray = obj.Split(';')
int hashcode = valuesArray[1].GetHashCode();
hashcode = hashcode * 31 + valuesArray[2].GetHashCode();
return hashcode;
}
На этом реализация компаратора закончена.
Однако в целом производительность такого решения оставляет желать лучшего. Поскольку вы вызываете Except() дважды, то для каждой пары строк из обоих файлов методы компаратора (по крайней мере, GetHashCode()) будут вызываться дважды. Вкупе с избыточными разбиениями внутри этих методов картина получается нерадостная.
Можно пойти по другому пути, например, заранее разбить строки:
var splittedLst1 = lst1.Select(i => i.Split(';'));
var splittedLst2 = lst2.Select(i => i.Split(';'));
Затем получить разницу:
var comparer = new StringLengthEqualityComparer();
var rezList = splittedLst1
.Except(splittedLst2, comparer)
.Union(splittedLst2.Except(splittedLst1, comparer));
А при необходимости снова склеить строки:
foreach (var item in rezList)
{
Console.WriteLine(string.Join(";", item));
}
При таком подходе каждая строка будет разбита всего один раз, а код компаратора упростится:
class StringLengthEqualityComparer : IEqualityComparer
public int GetHashCode(string[] obj)
{
var hashcode = obj[1].GetHashCode();
hashcode = hashcode * 31 + obj[2].GetHashCode();
return hashcode;
}
}
Если вы ожидаете строки разной длины (в смысле количества колонок), то нужно изменить компаратор таким образом, чтобы он корректно работал со строками разной длины.
Провел небольшой тест для сравнения производительности. В качестве тестовых данных склеил 10000 раз приведенные вам двухстрочники. Разница в результатах при этом получилась следующая:
Radzhab: 332ms
andreycha: 63ms
Комментариев нет:
Отправить комментарий