более быстрый способ перебора и сравнения двух списков классов - PullRequest
1 голос
/ 27 марта 2020

Мне нужно перебрать и сравнить 2 списка классов, сравнить 2 и вывести соответствующие записи. это занимает часы, и я не могу найти способ ускорить процесс. списки примерно 600 тысяч записей за штуку. Вот мой код для класса и код для итераций и сравнения.

class Person
{
    string NPI;
    string address;
    string zip5;
    string lname;
    string lsk;
    string state;
    string fname;
    string zipfull;
    string seqNo;

    public Person(string npi, string Address, string Zip5, string Lname, string LSK, string st, string Fname, string zipFull, string seqno)
    {
        this.NPI = npi;
        this.address = Address;
        this.zip5 = Zip5;
        this.lname = Lname;
        this.lsk = LSK;
        this.state = st;
        this.fname = Fname;
        this.zipfull = zipFull;
        this.seqNo = seqno;
    }

    public string getNPI()
    {
        return NPI;
    }

    public string getzip5()
    {
        return zip5;
    }

    public string getaddress()
    {
        return address;
    }
    public string Full()
    {
        string full = NPI + "," + address + "," + zip5 + "," + lname + "," + lsk + "," + state + "," + fname + "," + zipfull + "," + seqNo;
        return full;
    }
}

Вот код для итераций. fuzz.ratio - это пакет нечетких совпадений, который я скачал, и я знаю, что это не проблема, поскольку я провел тесты скорости с ним, и он очень быстрый

string inputfile = @"C:\Input_File_150k.csv";
string blacklist = @"C:\Blacklist1.csv";
List<Person> input = Readcsv(inputfile);
List<Person> BL = Readcsv(blacklist);

string outputtest = @"C:\outputtest.csv";
StringBuilder csvcontent = new StringBuilder();

int lengthinput = input.Count();
for(int i = 0; i <lengthinput; i++)
{
    int lengthbl = BL.Count();
    for(int x = 0; x < lengthbl; x++)
    {
        if(input[i].getzip5() == BL[x].getzip5())
        {
            if(input[i].getNPI() == BL[x].getNPI())
            {
                if(Fuzz.Ratio(input[i].getaddress(),BL[x].getaddress()) > 90)
                {
                    csvcontent.AppendLine(input[i].Full());
                }
            }
        }
    }
}

File.AppendAllText(outputtest, csvcontent.ToString());

Ответы [ 5 ]

0 голосов
/ 27 марта 2020

Попробуйте создать словарь из одного списка, чтобы использовать его в качестве поиска во время итерации другого. Это изменит сложность от полиномиальной до линейной.

string inputfile = @"C:\Input_File_150k.csv";
string blacklist = @"C:\Blacklist1.csv";
List<Person> input = Readcsv(inputfile);
var blAddresses = Readcsv(blacklist).ToDictionary(
    x => (Zip : x.getzip5(), NPI : x.getNPI()),
    x => x.getaddress());

string outputtest = @"C:\outputtest.csv";
StringBuilder csvcontent = new StringBuilder();

int lengthinput = input.Count();
for(int i = 0; i <lengthinput; i++)
{
    var zip = input[i].getzip5();
    var npi = input[i].getNPI();

    if(blAddresses.TryGetValue((zip,npi), out var blAddress)
    {
        if(Fuzz.Ratio(input[i].getaddress(),blAddress) > 90)
        {
            csvcontent.AppendLine(input[i].Full());            
        }
    }
}

File.AppendAllText(outputtest, csvcontent.ToString());

В частности, я создал словарь, который задает ключи на Zip и NPI и получает адрес, который является всем необходимым. Я использую некоторые C# 7 такие вещи, как кортежи значений, но при необходимости их можно изменить на ссылочные кортежи, анонимный класс или пользовательский класс.

Редактировать

Вот изменение, чтобы заставить эту работу работать так же, как ваш текущий код, предполагая, что у вас есть дубликаты значений Zip / NPI

string inputfile = @"C:\Input_File_150k.csv";
string blacklist = @"C:\Blacklist1.csv";
List<Person> input = Readcsv(inputfile);
var blAddresses = Readcsv(blacklist)
    .GroupBy(x => (Zip : x.getzip5(), NPI : x.getNPI()))
    .ToDictionary(
        grp => grp.Key, 
        grp => grp.Select(y => y.getAddress()).ToList());

string outputtest = @"C:\outputtest.csv";
StringBuilder csvcontent = new StringBuilder();

int lengthinput = input.Count();
for(int i = 0; i <lengthinput; i++)
{
    var zip = input[i].getzip5();
    var npi = input[i].getNPI();

    if(blAddresses.TryGetValue((zip,npi), out var blAddressList)
    {
        foreach(var blAddress in blAddressList)
        {
            if(Fuzz.Ratio(input[i].getaddress(),blAddress) > 90)
            {
                csvcontent.AppendLine(input[i].Full());       
            }     
        }
    }
}

File.AppendAllText(outputtest, csvcontent.ToString());

В качестве альтернативы, если вам просто нужно отфильтровать что-нибудь в черном списке, где находится NPI пуст, чтобы иметь уникальные ключи, вы можете сделать это вместо

var blAddresses = Readcsv(blacklist)
    .Whree(x => x.getNPI().Length > 0) 
    .ToDictionary(
        x => (Zip : x.getzip5(), NPI : x.getNPI()),
        x => x.getaddress());
0 голосов
/ 27 марта 2020

Я бы определенно реализовал интерфейс IEqualityComparer.

Это позволяет гораздо более чистый код, а также извлечь выгоду из некоторых методов расширения Linq, доступных в коллекциях.

Они также поддерживают такие вещи, как параллелизм.

https://marcofranssen.nl/delegate-your-equality-comparisons/

0 голосов
/ 27 марта 2020

Я думаю, что эта часть кода может быть медленной

string full = NPI + "," + address + "," + zip5 + "," + lname + "," + lsk + "," + state + "," + fname + "," + zipfull + "," + seqNo;

Что если вы выполните csvcontent.Append для этих свойств и , "вручную" вместо использования Full()? PS: для этого потребуется добавить возможность читать его извне, поэтому publi c get / private set

0 голосов
/ 27 марта 2020

в дополнение к вышеуказанным комментариям ({ ссылка } и с использованием словарей) в ваших циклах

int lengthinput = input.Count();
    **int lengthbl = BL.Count();** //move out of the loops

    for(int i = 0; i <lengthinput; i++)
    {
        **var inputi = input[i];**  //move out of the inner loop
        for(int x = 0; x < lengthbl; x++)
        {
            **var blx =  BL[x];**
            if(inputi.getzip5() == blx.getzip5())
            {
                if(inputi.getNPI() == blx.getNPI())
                {
                    if(Fuzz.Ratio(inputi.getaddress(),blx.getaddress()) > 90)
                    {
                        csvcontent.AppendLine(inputi.Full());
                    }
                }
            }
        }
    }
0 голосов
/ 27 марта 2020

Вы можете перебирать каждый список один раз, чтобы поместить все значения в карту ha sh. Например, вы можете использовать почтовый индекс в качестве ключа, а значение - это массив людей, которые совпадают с этим почтовым индексом.

Затем вы можете перебирать по одной записи хеш-карты за раз, и вам нужно только сравнивайте людей в каждой корзине хеш-карт с eachother.

Если все ваши люди не находятся в одном и том же почтовом индексе (если так, надеюсь, один из ваших ключей будет работать для этого), это должно быть быстрее, чем сравнение N ^ 2 , Должно быть ближе к O (N) в зависимости от того, сколько человек в каждом ведре.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...